@praxisui/page-builder 9.0.68-rc.1 → 9.0.68-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -3
- package/ai/component-registry.json +76 -10
- package/fesm2022/praxisui-page-builder.mjs +763 -380
- package/package.json +4 -4
- package/types/praxisui-page-builder.d.ts +85 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Inject, ChangeDetectionStrategy, Component, EventEmitter, Output, Input, inject, ElementRef, signal, computed, effect, Optional, input, output, Injector, viewChild, afterNextRender, afterEveryRender, InjectionToken, ENVIRONMENT_INITIALIZER, Injectable
|
|
2
|
+
import { Inject, ChangeDetectionStrategy, Component, EventEmitter, Output, Input, inject, ElementRef, signal, computed, effect, Optional, input, output, Injector, viewChild, afterNextRender, afterEveryRender, ViewChild, InjectionToken, ENVIRONMENT_INITIALIZER, Injectable } from '@angular/core';
|
|
3
3
|
import * as i2 from '@angular/material/button';
|
|
4
4
|
import { MatButtonModule } from '@angular/material/button';
|
|
5
5
|
import * as i1 from '@angular/material/dialog';
|
|
@@ -7,7 +7,7 @@ import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
|
|
7
7
|
import * as i2$1 from '@angular/material/icon';
|
|
8
8
|
import { MatIconModule } from '@angular/material/icon';
|
|
9
9
|
import * as i2$2 from '@praxisui/core';
|
|
10
|
-
import { PraxisIconDirective, providePraxisI18n, BUILTIN_WIDGET_SHELL_PRESETS, SETTINGS_EDITOR_SECTIONS_HOSTED, PraxisI18nService, getWidgetShellActionIdError, WidgetShellComponent, SettingsSectionTabsComponent, PraxisIconButtonComponent, GLOBAL_ACTION_CATALOG, ComponentMetadataRegistry, CompositionValidatorService, getGlobalActionCatalog, PRAXIS_GLOBAL_ACTION_CATALOG, SurfaceOpenActionEditorComponent, BUILTIN_PAGE_LAYOUT_PRESETS, BUILTIN_PAGE_THEME_PRESETS, SETTINGS_PANEL_DATA as SETTINGS_PANEL_DATA$1, PraxisJsonLogicService, NestedWidgetConfigAccessor, canonicalJsonSha256, ResourceDiscoveryService, ComponentRuntimeProfileService, buildApiUrl, deepMerge, generateId, ObservabilityDashboardService, PraxisRuntimeComponentObservationRegistryService, domainKnowledgeTimelineToRichContentDocument, DomainRuleService, DomainKnowledgeService, SETTINGS_PANEL_BRIDGE, DynamicWidgetPageComponent, DYNAMIC_PAGE_SHELL_EDITOR } from '@praxisui/core';
|
|
10
|
+
import { PraxisIconDirective, providePraxisI18n, BUILTIN_WIDGET_SHELL_PRESETS, SETTINGS_EDITOR_SECTIONS_HOSTED, PraxisI18nService, getWidgetShellActionIdError, WidgetShellComponent, SettingsSectionTabsComponent, PraxisIconButtonComponent, 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, validateCanvasArrangement, PraxisJsonLogicService, NestedWidgetConfigAccessor, canonicalJsonSha256, ResourceDiscoveryService, ComponentRuntimeProfileService, buildApiUrl, deepMerge, generateId, ObservabilityDashboardService, PraxisRuntimeComponentObservationRegistryService, 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
13
|
import * as i4 from '@angular/material/form-field';
|
|
@@ -17,10 +17,10 @@ import { MatInputModule } from '@angular/material/input';
|
|
|
17
17
|
import * as i1$1 from '@angular/forms';
|
|
18
18
|
import { FormsModule, FormGroup, FormArray, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
19
19
|
import * as i1$2 from '@angular/common';
|
|
20
|
-
import { CommonModule } from '@angular/common';
|
|
20
|
+
import { CommonModule, NgTemplateOutlet } from '@angular/common';
|
|
21
21
|
import * as i4$1 from '@angular/material/checkbox';
|
|
22
22
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
|
23
|
-
import * as
|
|
23
|
+
import * as i9 from '@angular/material/select';
|
|
24
24
|
import { MatSelectModule } from '@angular/material/select';
|
|
25
25
|
import { BehaviorSubject, merge, timeout, map, catchError, throwError, firstValueFrom, from, share, concatMap, of, timer, takeUntil, Subject, tap, filter, take } from 'rxjs';
|
|
26
26
|
import { SETTINGS_PANEL_DATA } from '@praxisui/settings-panel';
|
|
@@ -28,7 +28,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
|
28
28
|
import { CdkTrapFocus } from '@angular/cdk/a11y';
|
|
29
29
|
import * as i3$1 from '@angular/material/button-toggle';
|
|
30
30
|
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
|
31
|
-
import * as i8
|
|
31
|
+
import * as i8 from '@angular/material/tabs';
|
|
32
32
|
import { MatTabsModule } from '@angular/material/tabs';
|
|
33
33
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
|
34
34
|
import { AgenticAuthoringTurnClientService, withAuthoringScopePolicyContextHints, toPraxisAssistantConversationMessages, PraxisAssistantTurnOrchestratorService, PraxisAssistantSessionRegistryService, createPraxisAssistantViewportLayout, PraxisAiAssistantShellComponent } from '@praxisui/ai';
|
|
@@ -361,6 +361,97 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
361
361
|
}] } });
|
|
362
362
|
|
|
363
363
|
const PRAXIS_PAGE_BUILDER_EN_US = {
|
|
364
|
+
"praxis.pageBuilder.studio.gridSize": "Size in columns × rows",
|
|
365
|
+
"praxis.pageBuilder.studio.visualTab": "Visual templates",
|
|
366
|
+
"praxis.pageBuilder.studio.layoutTab": "Layout templates",
|
|
367
|
+
"praxis.pageBuilder.studio.arrangeTab": "Arrange",
|
|
368
|
+
"praxis.pageBuilder.studio.settingsTab": "Adjustments",
|
|
369
|
+
"praxis.pageBuilder.studio.visualEyebrow": "Appearance",
|
|
370
|
+
"praxis.pageBuilder.studio.visualTitle": "Give the page a visual language",
|
|
371
|
+
"praxis.pageBuilder.studio.visualHelp": "Choose surfaces, density and chart styling together. Individual widget overrides still take precedence.",
|
|
372
|
+
"praxis.pageBuilder.studio.layoutEyebrow": "Composition model",
|
|
373
|
+
"praxis.pageBuilder.studio.layoutTitle": "Choose the structure for your use case",
|
|
374
|
+
"praxis.pageBuilder.studio.layoutHelp": "Compare the roles and default theme of each model. To redistribute the widgets already on this canvas, open Arrange.",
|
|
375
|
+
"praxis.pageBuilder.studio.inheritLayout": "Use inherited model",
|
|
376
|
+
"praxis.pageBuilder.studio.goArrange": "Arrange existing widgets",
|
|
377
|
+
"praxis.pageBuilder.studio.arrangeEyebrow": "Existing widgets",
|
|
378
|
+
"praxis.pageBuilder.studio.arrangeTitle": "Find the right balance of space",
|
|
379
|
+
"praxis.pageBuilder.studio.arrangeHelp": "Choose a distribution, compare the real positions, then use it in the draft. Widgets keep their reading order and content.",
|
|
380
|
+
"praxis.pageBuilder.studio.samplesHelp": "Illustrative samples using the preset tokens. Apply to see the result with your content.",
|
|
381
|
+
"praxis.pageBuilder.studio.shellTitle": "Customize widget surfaces",
|
|
382
|
+
"praxis.pageBuilder.studio.shellHelp": "Override the page theme’s default widget surface. Per-widget appearance settings keep their priority.",
|
|
383
|
+
"praxis.pageBuilder.studio.density.compact": "Compact",
|
|
384
|
+
"praxis.pageBuilder.studio.density.comfortable": "Comfortable",
|
|
385
|
+
"praxis.pageBuilder.studio.moreColumns": "Needs more grid columns",
|
|
386
|
+
"praxis.pageBuilder.studio.reviewTitle": "Compare before using",
|
|
387
|
+
"praxis.pageBuilder.studio.changedWidgets": "widgets change",
|
|
388
|
+
"praxis.pageBuilder.studio.before": "Before",
|
|
389
|
+
"praxis.pageBuilder.studio.after": "After",
|
|
390
|
+
"praxis.pageBuilder.studio.widgetDetails": "Widget positions and sizes",
|
|
391
|
+
"praxis.pageBuilder.studio.mapHelp": "Numbered map of your widgets. Positions and spans are to scale; content height is evaluated on the page.",
|
|
392
|
+
"praxis.pageBuilder.studio.rules": "Scope, devices and locked widgets",
|
|
393
|
+
"praxis.pageBuilder.studio.goLimits": "Review widget limits",
|
|
394
|
+
"praxis.pageBuilder.studio.theme.analytics-calm": "Analytical reading with quiet surfaces and comfortable spacing.",
|
|
395
|
+
"praxis.pageBuilder.studio.theme.workspace-balanced": "Balance for everyday work and data exploration.",
|
|
396
|
+
"praxis.pageBuilder.studio.theme.ops-monitoring": "Higher density and emphasis on status, queues and alerts.",
|
|
397
|
+
"praxis.pageBuilder.studio.theme.executive-command-center": "Compact hierarchy for executive overviews and decisions.",
|
|
398
|
+
"praxis.pageBuilder.studio.arrange.stack": "One lane per widget for sequential reading.",
|
|
399
|
+
"praxis.pageBuilder.studio.arrange.compare": "Two equal lanes for comparing information.",
|
|
400
|
+
"praxis.pageBuilder.studio.arrange.primary": "More space on the left, with support on the right.",
|
|
401
|
+
"praxis.pageBuilder.studio.arrange.secondary": "Support on the left and more space on the right.",
|
|
402
|
+
"praxis.pageBuilder.studio.arrange.grid": "Three equal lanes for a simultaneous overview.",
|
|
403
|
+
"praxis.pageBuilder.studio.arrange.center": "A broad center between two supporting lanes.",
|
|
404
|
+
"praxis.pageBuilder.studio.arrange.wide-left": "Broad emphasis on the left, narrow support on the right.",
|
|
405
|
+
"praxis.pageBuilder.studio.arrange.wide-right": "Narrow support on the left, broad emphasis on the right.",
|
|
406
|
+
"praxis.pageBuilder.studio.arrange.four": "Four equal lanes for compact widgets.",
|
|
407
|
+
"praxis.pageBuilder.editor.arrange.secondary": "Right emphasis",
|
|
408
|
+
"praxis.pageBuilder.editor.arrange.center": "Center emphasis",
|
|
409
|
+
"praxis.pageBuilder.editor.arrange.wide-left": "Wide left",
|
|
410
|
+
"praxis.pageBuilder.editor.arrange.wide-right": "Wide right",
|
|
411
|
+
"praxis.pageBuilder.editor.arrange.four": "Four columns",
|
|
412
|
+
"praxis.pageBuilder.presets.inheritedLayout": "Inherited organization",
|
|
413
|
+
"praxis.pageBuilder.presets.roles": "Organization roles",
|
|
414
|
+
"praxis.pageBuilder.presets.slot.hero": "Highlight",
|
|
415
|
+
"praxis.pageBuilder.presets.slot.kpis": "Metrics",
|
|
416
|
+
"praxis.pageBuilder.presets.slot.charts": "Charts",
|
|
417
|
+
"praxis.pageBuilder.presets.slot.primary-chart": "Primary chart",
|
|
418
|
+
"praxis.pageBuilder.presets.slot.detail-table": "Detail table",
|
|
419
|
+
"praxis.pageBuilder.presets.slot.filters": "Filters",
|
|
420
|
+
"praxis.pageBuilder.presets.slot.table": "Main table",
|
|
421
|
+
"praxis.pageBuilder.presets.slot.aux": "Supporting charts",
|
|
422
|
+
"praxis.pageBuilder.presets.slot.master": "Master list",
|
|
423
|
+
"praxis.pageBuilder.presets.slot.detail-kpis": "Record metrics",
|
|
424
|
+
"praxis.pageBuilder.presets.slot.detail-tabs": "Tabbed details",
|
|
425
|
+
"praxis.pageBuilder.presets.slot.status-cards": "Status overview",
|
|
426
|
+
"praxis.pageBuilder.presets.slot.timelines": "Time series",
|
|
427
|
+
"praxis.pageBuilder.presets.slot.timeline-a": "Primary series",
|
|
428
|
+
"praxis.pageBuilder.presets.slot.queue": "Work queue",
|
|
429
|
+
"praxis.pageBuilder.presets.slot.alerts": "Alerts",
|
|
430
|
+
"praxis.pageBuilder.presets.scope": "Effect of this choice",
|
|
431
|
+
"praxis.pageBuilder.presets.canvas": "Canvas positions are preserved. This preset does not move or resize existing widgets.",
|
|
432
|
+
"praxis.pageBuilder.presets.flow": "Explicit layout and grouping take precedence over preset defaults. Selecting a preset does not create widgets or data connections.",
|
|
433
|
+
"praxis.pageBuilder.presets.effectiveTheme": "Effective page theme",
|
|
434
|
+
"praxis.pageBuilder.presets.hostTheme": "Host theme",
|
|
435
|
+
"praxis.pageBuilder.presets.explicit": "Explicit choice",
|
|
436
|
+
"praxis.pageBuilder.presets.inherited": "Inherited from layout",
|
|
437
|
+
"praxis.pageBuilder.presets.host": "No page theme override",
|
|
438
|
+
"praxis.pageBuilder.presets.suggested": "Suggested by the layout",
|
|
439
|
+
"praxis.pageBuilder.presets.notActive": "not active",
|
|
440
|
+
"praxis.pageBuilder.presets.compare": "Compare page organizations",
|
|
441
|
+
"praxis.pageBuilder.presets.diagramHelp": "Role diagrams, not a preview of your page. Compare the structure, then select an organization. Existing widgets and connections are preserved.",
|
|
442
|
+
"praxis.pageBuilder.presets.selected": "Selected",
|
|
443
|
+
"praxis.pageBuilder.presets.required": "required role",
|
|
444
|
+
"praxis.pageBuilder.presets.choose": "Choose",
|
|
445
|
+
"praxis.pageBuilder.presets.mobile": "Mobile preferences describe the intended composition. Verify the actual responsive layout; selection alone does not hide content, create tabs or connect master and detail.",
|
|
446
|
+
"praxis.pageBuilder.presets.declaredPolicy": "Declared preference, not an automatic transformation",
|
|
447
|
+
"praxis.pageBuilder.presets.layout.analytics-overview": "Analytics overview",
|
|
448
|
+
"praxis.pageBuilder.presets.layout.kpi-plus-table": "Metrics and table",
|
|
449
|
+
"praxis.pageBuilder.presets.layout.master-detail-dashboard": "Master and details",
|
|
450
|
+
"praxis.pageBuilder.presets.layout.ops-monitoring": "Operations monitoring",
|
|
451
|
+
"praxis.pageBuilder.presets.use.analytics-overview": "Compare metrics, trends and details across capacity, sales, costs and quality.",
|
|
452
|
+
"praxis.pageBuilder.presets.use.kpi-plus-table": "Review a central table supported by metrics, for orders, inventory and reconciliation.",
|
|
453
|
+
"praxis.pageBuilder.presets.use.master-detail-dashboard": "Investigate an item and its details, for customers, assets and cases. Selection and data connections must be configured.",
|
|
454
|
+
"praxis.pageBuilder.presets.use.ops-monitoring": "Follow status, trends, queues and alerts across services, production and logistics. Preserve access to alerts on mobile.",
|
|
364
455
|
"praxis.pageBuilder.shell.availabilityUnknown": "Availability has not been checked for this widget. Validate the requirements before use.",
|
|
365
456
|
"praxis.pageBuilder.shell.availabilityReady": "Available in this widget now.",
|
|
366
457
|
"praxis.pageBuilder.shell.availabilityBlocked": "Unavailable in this widget now.",
|
|
@@ -413,9 +504,36 @@ const PRAXIS_PAGE_BUILDER_EN_US = {
|
|
|
413
504
|
"praxis.pageBuilder.shell.scopeToolbar": "Toolbar",
|
|
414
505
|
"praxis.pageBuilder.shell.scopeSelection": "Selected item",
|
|
415
506
|
"praxis.pageBuilder.shell.scopeGeneral": "General use",
|
|
507
|
+
"praxis.pageBuilder.editor.arrange.title": "Arrange existing widgets",
|
|
508
|
+
"praxis.pageBuilder.editor.arrange.help": "Base canvas, in current reading order. Inheriting devices follow the base; explicit device positions are preserved. Locked positions stay in place.",
|
|
509
|
+
"praxis.pageBuilder.editor.arrange.stack": "Work column",
|
|
510
|
+
"praxis.pageBuilder.editor.arrange.compare": "Two-column comparison",
|
|
511
|
+
"praxis.pageBuilder.editor.arrange.primary": "Two columns: ⅔ and ⅓",
|
|
512
|
+
"praxis.pageBuilder.editor.arrange.grid": "Three-column grid",
|
|
513
|
+
"praxis.pageBuilder.editor.arrange.affected": "Widgets to change",
|
|
514
|
+
"praxis.pageBuilder.editor.arrange.position": "Column / row",
|
|
515
|
+
"praxis.pageBuilder.editor.arrange.use": "Use arrangement in draft",
|
|
516
|
+
"praxis.pageBuilder.editor.arrange.undo": "Undo arrangement",
|
|
517
|
+
"praxis.pageBuilder.editor.arrange.geometry": "Use a complete canvas with valid positions and no overlaps. The grid must fit the chosen columns.",
|
|
518
|
+
"praxis.pageBuilder.editor.arrange.limits": "This arrangement conflicts with the indicated widget limits or locks. Choose another arrangement or review the limits.",
|
|
519
|
+
"praxis.pageBuilder.editor.arrange.device-overrides": "Partial device positions depend on this geometry. Review those positions before arranging the base.",
|
|
520
|
+
"praxis.pageBuilder.editor.arrange.unchanged": "This arrangement already matches the page.",
|
|
521
|
+
"praxis.pageBuilder.editor.arrange.stale": "The draft changed. Prepare the arrangement again.",
|
|
522
|
+
"praxis.pageBuilder.editor.arrange.staged": "Arrangement added to the draft. Use Apply to preview it on the page.",
|
|
523
|
+
"praxis.pageBuilder.editor.arrange.undone": "The previous arrangement is restored in the draft. Use Apply to update the page.",
|
|
416
524
|
'praxis.pageBuilder.shell.sampleOnly': 'Illustrative data',
|
|
417
525
|
'praxis.pageBuilder.shell.sampleMetricLabel': 'Active employees',
|
|
418
526
|
'praxis.pageBuilder.shell.sampleMetricContext': '91% of planned workforce',
|
|
527
|
+
"praxis.pageBuilder.shell.cardFilter": "Backdrop filter",
|
|
528
|
+
"praxis.pageBuilder.shell.cardFilterHelp": "Use with a translucent background. Example: blur(16px) saturate(125%). Use none to disable.",
|
|
529
|
+
"praxis.pageBuilder.shell.cardFallbackBg": "Opaque fallback background",
|
|
530
|
+
"praxis.pageBuilder.shell.cardFallbackHelp": "Preserves readability in fullscreen, with reduced transparency or without backdrop support.",
|
|
531
|
+
"praxis.pageBuilder.shell.effectiveStyle": "Base style",
|
|
532
|
+
"praxis.pageBuilder.shell.styleFromWidget": "Chosen for this widget",
|
|
533
|
+
"praxis.pageBuilder.shell.styleFromPage": "Inherited from the page or theme",
|
|
534
|
+
"praxis.pageBuilder.shell.styleFromDefault": "Component default",
|
|
535
|
+
"praxis.pageBuilder.shell.adjustmentsWin": "adjustments override the style",
|
|
536
|
+
"praxis.pageBuilder.shell.surfaceDisabled": "Content-only presentation keeps the outer shell disabled.",
|
|
419
537
|
'praxis.pageBuilder.shell.customAdjustments': 'Custom adjustments',
|
|
420
538
|
'praxis.pageBuilder.shell.clearAdjustments': 'Use style only',
|
|
421
539
|
'praxis.pageBuilder.connections.editor.flowLegend': 'Connection types',
|
|
@@ -514,6 +632,14 @@ const PRAXIS_PAGE_BUILDER_EN_US = {
|
|
|
514
632
|
"praxis.pageBuilder.shell.windowControls": "Window controls",
|
|
515
633
|
"praxis.pageBuilder.shell.collapsible": "Allow collapsing",
|
|
516
634
|
"praxis.pageBuilder.shell.cardBg": "Card background",
|
|
635
|
+
"praxis.pageBuilder.shell.cardBorderWidth": "Border width",
|
|
636
|
+
"praxis.pageBuilder.shell.cardBorderStyle": "Border style",
|
|
637
|
+
"praxis.pageBuilder.shell.headerBorderWidth": "Divider width",
|
|
638
|
+
"praxis.pageBuilder.shell.headerBorderStyle": "Divider style",
|
|
639
|
+
"praxis.pageBuilder.shell.cardBorderWidthHelp": "E.g. 2px. Per side: 1px 1px 1px 6px (top, right, bottom, left).",
|
|
640
|
+
"praxis.pageBuilder.shell.headerBorderWidthHelp": "E.g. 2px. Applies only to the bottom header divider.",
|
|
641
|
+
"praxis.pageBuilder.shell.cardBorderStyleHelp": "CSS styles: solid, dashed, dotted, double or none. Accepts per-side values.",
|
|
642
|
+
"praxis.pageBuilder.shell.headerBorderStyleHelp": "Divider style: solid, dashed, dotted, double or none.",
|
|
517
643
|
"praxis.pageBuilder.shell.cardBorder": "Border color",
|
|
518
644
|
"praxis.pageBuilder.shell.cardRadius": "Corner rounding",
|
|
519
645
|
"praxis.pageBuilder.shell.cardShadow": "Shadow",
|
|
@@ -559,34 +685,6 @@ const PRAXIS_PAGE_BUILDER_EN_US = {
|
|
|
559
685
|
'praxis.pageBuilder.shell.inheritPagePreset': "Use the style defined on the page",
|
|
560
686
|
'praxis.pageBuilder.shell.useDefaultAppearance': "Use the default widget appearance",
|
|
561
687
|
'praxis.pageBuilder.shell.customPresetDescription': 'Style available on this page',
|
|
562
|
-
'praxis.widgetShell.presets.dark-glass.label': 'Dark glass',
|
|
563
|
-
'praxis.widgetShell.presets.dark-glass.description': 'Dark glass appearance for existing and new pages.',
|
|
564
|
-
'praxis.widgetShell.presets.light-neutral.label': 'Light neutral',
|
|
565
|
-
'praxis.widgetShell.presets.light-neutral.description': 'Light neutral appearance for existing and new pages.',
|
|
566
|
-
'praxis.widgetShell.presets.graphite.label': 'Graphite',
|
|
567
|
-
'praxis.widgetShell.presets.graphite.description': 'Graphite appearance for existing and new pages.',
|
|
568
|
-
'praxis.widgetShell.presets.data-panel.label': 'Data panel',
|
|
569
|
-
'praxis.widgetShell.presets.data-panel.description': 'Data panel appearance for existing and new pages.',
|
|
570
|
-
'praxis.widgetShell.presets.chart-panel.label': 'Chart panel',
|
|
571
|
-
'praxis.widgetShell.presets.chart-panel.description': 'Chart panel appearance for existing and new pages.',
|
|
572
|
-
'praxis.widgetShell.presets.metric-panel.label': 'Metric panel',
|
|
573
|
-
'praxis.widgetShell.presets.metric-panel.description': 'Metric panel appearance for existing and new pages.',
|
|
574
|
-
'praxis.widgetShell.presets.executive-card.label': 'Executive card',
|
|
575
|
-
'praxis.widgetShell.presets.executive-card.description': 'Executive card appearance for existing and new pages.',
|
|
576
|
-
'praxis.widgetShell.presets.filter-bar.label': 'Filter bar',
|
|
577
|
-
'praxis.widgetShell.presets.filter-bar.description': 'Filter bar appearance for existing and new pages.',
|
|
578
|
-
'praxis.widgetShell.presets.integratedCard.label': 'Integrated card',
|
|
579
|
-
'praxis.widgetShell.presets.integratedCard.description': 'One continuous surface for the header, actions and widget body.',
|
|
580
|
-
'praxis.widgetShell.presets.outlinedCard.label': 'Outlined card',
|
|
581
|
-
'praxis.widgetShell.presets.outlinedCard.description': 'A quiet continuous surface with a clear outer outline and no shadow.',
|
|
582
|
-
'praxis.widgetShell.presets.elevatedCard.label': 'Elevated card',
|
|
583
|
-
'praxis.widgetShell.presets.elevatedCard.description': 'A continuous surface separated from the page by a soft shadow.',
|
|
584
|
-
'praxis.widgetShell.presets.tonalCard.label': 'Tonal card',
|
|
585
|
-
'praxis.widgetShell.presets.tonalCard.description': 'A softly tinted continuous surface with minimal elevation.',
|
|
586
|
-
'praxis.widgetShell.presets.sectionedCard.label': 'Sectioned card',
|
|
587
|
-
'praxis.widgetShell.presets.sectionedCard.description': 'A distinct header layer for dense operational content.',
|
|
588
|
-
'praxis.widgetShell.presets.frameless.label': 'Frameless',
|
|
589
|
-
'praxis.widgetShell.presets.frameless.description': 'No outer surface, outline or shadow; keeps the content reading inset.',
|
|
590
688
|
'praxis.pageBuilder.authoringSource.alert.title': 'Semantic lineage requires attention',
|
|
591
689
|
'praxis.pageBuilder.authoringSource.alert.description': 'The page remains available, but this source will not be reused until new governed authoring restores its provenance.',
|
|
592
690
|
'praxis.pageBuilder.authoringSource.diagnostics.authoring-source-schema-invalid': 'The authoring envelope does not match the schema supported by Page Builder.',
|
|
@@ -640,6 +738,7 @@ const PRAXIS_PAGE_BUILDER_EN_US = {
|
|
|
640
738
|
'praxis.pageBuilder.editor.layout.none': 'None',
|
|
641
739
|
'praxis.pageBuilder.editor.layout.themePreset': 'Theme preset',
|
|
642
740
|
'praxis.pageBuilder.editor.layout.layoutDefault': 'Layout default',
|
|
741
|
+
'praxis.pageBuilder.editor.layout.inheritShell': 'Inherit appearance',
|
|
643
742
|
'praxis.pageBuilder.editor.layout.shellPreset': 'Shell visual preset',
|
|
644
743
|
'praxis.pageBuilder.editor.grouping.section': 'Grouping',
|
|
645
744
|
'praxis.pageBuilder.editor.grouping.title': 'Grouping',
|
|
@@ -1325,6 +1424,97 @@ const PRAXIS_PAGE_BUILDER_EN_US = {
|
|
|
1325
1424
|
};
|
|
1326
1425
|
|
|
1327
1426
|
const PRAXIS_PAGE_BUILDER_PT_BR = {
|
|
1427
|
+
"praxis.pageBuilder.studio.gridSize": "Tamanho em colunas × linhas",
|
|
1428
|
+
"praxis.pageBuilder.studio.visualTab": "Templates visuais",
|
|
1429
|
+
"praxis.pageBuilder.studio.layoutTab": "Templates de layout",
|
|
1430
|
+
"praxis.pageBuilder.studio.arrangeTab": "Reorganizar",
|
|
1431
|
+
"praxis.pageBuilder.studio.settingsTab": "Ajustes",
|
|
1432
|
+
"praxis.pageBuilder.studio.visualEyebrow": "Aparência",
|
|
1433
|
+
"praxis.pageBuilder.studio.visualTitle": "Dê uma linguagem visual à página",
|
|
1434
|
+
"praxis.pageBuilder.studio.visualHelp": "Escolha superfícies, densidade e estilo dos gráficos em conjunto. Personalizações de cada widget continuam tendo prioridade.",
|
|
1435
|
+
"praxis.pageBuilder.studio.layoutEyebrow": "Modelo de composição",
|
|
1436
|
+
"praxis.pageBuilder.studio.layoutTitle": "Escolha a estrutura para seu objetivo",
|
|
1437
|
+
"praxis.pageBuilder.studio.layoutHelp": "Compare os papéis e o tema padrão de cada modelo. Para redistribuir os widgets deste canvas, abra Reorganizar.",
|
|
1438
|
+
"praxis.pageBuilder.studio.inheritLayout": "Usar modelo herdado",
|
|
1439
|
+
"praxis.pageBuilder.studio.goArrange": "Reorganizar widgets existentes",
|
|
1440
|
+
"praxis.pageBuilder.studio.arrangeEyebrow": "Widgets existentes",
|
|
1441
|
+
"praxis.pageBuilder.studio.arrangeTitle": "Encontre o equilíbrio entre os espaços",
|
|
1442
|
+
"praxis.pageBuilder.studio.arrangeHelp": "Escolha uma distribuição, compare as posições reais e use no rascunho. Os widgets mantêm a ordem de leitura e o conteúdo.",
|
|
1443
|
+
"praxis.pageBuilder.studio.samplesHelp": "Amostras ilustrativas com os tokens do preset. Aplique para ver o resultado com seu conteúdo.",
|
|
1444
|
+
"praxis.pageBuilder.studio.shellTitle": "Personalizar superfícies dos widgets",
|
|
1445
|
+
"praxis.pageBuilder.studio.shellHelp": "Substitua a superfície padrão do tema da página. Aparências definidas em cada widget mantêm a prioridade.",
|
|
1446
|
+
"praxis.pageBuilder.studio.density.compact": "Compacto",
|
|
1447
|
+
"praxis.pageBuilder.studio.density.comfortable": "Confortável",
|
|
1448
|
+
"praxis.pageBuilder.studio.moreColumns": "Requer mais colunas na grade",
|
|
1449
|
+
"praxis.pageBuilder.studio.reviewTitle": "Compare antes de usar",
|
|
1450
|
+
"praxis.pageBuilder.studio.changedWidgets": "widgets mudam",
|
|
1451
|
+
"praxis.pageBuilder.studio.before": "Antes",
|
|
1452
|
+
"praxis.pageBuilder.studio.after": "Depois",
|
|
1453
|
+
"praxis.pageBuilder.studio.widgetDetails": "Posições e tamanhos dos widgets",
|
|
1454
|
+
"praxis.pageBuilder.studio.mapHelp": "Mapa numerado dos seus widgets. Posições e extensões estão em escala; a altura do conteúdo é avaliada na página.",
|
|
1455
|
+
"praxis.pageBuilder.studio.rules": "Escopo, dispositivos e widgets bloqueados",
|
|
1456
|
+
"praxis.pageBuilder.studio.goLimits": "Revisar limites dos widgets",
|
|
1457
|
+
"praxis.pageBuilder.studio.theme.analytics-calm": "Leitura analítica com superfícies leves e espaço confortável.",
|
|
1458
|
+
"praxis.pageBuilder.studio.theme.workspace-balanced": "Equilíbrio para rotinas de trabalho e consulta de dados.",
|
|
1459
|
+
"praxis.pageBuilder.studio.theme.ops-monitoring": "Maior densidade e destaque para status, filas e alertas.",
|
|
1460
|
+
"praxis.pageBuilder.studio.theme.executive-command-center": "Hierarquia compacta para visão executiva e decisões.",
|
|
1461
|
+
"praxis.pageBuilder.studio.arrange.stack": "Uma faixa por widget, para leitura sequencial.",
|
|
1462
|
+
"praxis.pageBuilder.studio.arrange.compare": "Duas faixas iguais para comparar informações.",
|
|
1463
|
+
"praxis.pageBuilder.studio.arrange.primary": "Mais espaço à esquerda, com apoio à direita.",
|
|
1464
|
+
"praxis.pageBuilder.studio.arrange.secondary": "Apoio à esquerda e mais espaço à direita.",
|
|
1465
|
+
"praxis.pageBuilder.studio.arrange.grid": "Três faixas iguais para uma visão simultânea.",
|
|
1466
|
+
"praxis.pageBuilder.studio.arrange.center": "Centro amplo entre duas faixas de apoio.",
|
|
1467
|
+
"praxis.pageBuilder.studio.arrange.wide-left": "Destaque amplo à esquerda, apoio estreito à direita.",
|
|
1468
|
+
"praxis.pageBuilder.studio.arrange.wide-right": "Apoio estreito à esquerda, destaque amplo à direita.",
|
|
1469
|
+
"praxis.pageBuilder.studio.arrange.four": "Quatro faixas iguais para widgets compactos.",
|
|
1470
|
+
"praxis.pageBuilder.editor.arrange.secondary": "Destaque à direita",
|
|
1471
|
+
"praxis.pageBuilder.editor.arrange.center": "Centro em destaque",
|
|
1472
|
+
"praxis.pageBuilder.editor.arrange.wide-left": "Panorama à esquerda",
|
|
1473
|
+
"praxis.pageBuilder.editor.arrange.wide-right": "Panorama à direita",
|
|
1474
|
+
"praxis.pageBuilder.editor.arrange.four": "Quatro colunas",
|
|
1475
|
+
"praxis.pageBuilder.presets.inheritedLayout": "Organização herdada",
|
|
1476
|
+
"praxis.pageBuilder.presets.roles": "Papéis da organização",
|
|
1477
|
+
"praxis.pageBuilder.presets.slot.hero": "Destaque",
|
|
1478
|
+
"praxis.pageBuilder.presets.slot.kpis": "Indicadores",
|
|
1479
|
+
"praxis.pageBuilder.presets.slot.charts": "Gráficos",
|
|
1480
|
+
"praxis.pageBuilder.presets.slot.primary-chart": "Gráfico principal",
|
|
1481
|
+
"praxis.pageBuilder.presets.slot.detail-table": "Tabela de detalhes",
|
|
1482
|
+
"praxis.pageBuilder.presets.slot.filters": "Filtros",
|
|
1483
|
+
"praxis.pageBuilder.presets.slot.table": "Tabela central",
|
|
1484
|
+
"praxis.pageBuilder.presets.slot.aux": "Gráficos auxiliares",
|
|
1485
|
+
"praxis.pageBuilder.presets.slot.master": "Lista principal",
|
|
1486
|
+
"praxis.pageBuilder.presets.slot.detail-kpis": "Indicadores do registro",
|
|
1487
|
+
"praxis.pageBuilder.presets.slot.detail-tabs": "Detalhes em abas",
|
|
1488
|
+
"praxis.pageBuilder.presets.slot.status-cards": "Situação geral",
|
|
1489
|
+
"praxis.pageBuilder.presets.slot.timelines": "Séries temporais",
|
|
1490
|
+
"praxis.pageBuilder.presets.slot.timeline-a": "Série principal",
|
|
1491
|
+
"praxis.pageBuilder.presets.slot.queue": "Fila de trabalho",
|
|
1492
|
+
"praxis.pageBuilder.presets.slot.alerts": "Alertas",
|
|
1493
|
+
"praxis.pageBuilder.presets.scope": "Efeito desta escolha",
|
|
1494
|
+
"praxis.pageBuilder.presets.canvas": "As posições do canvas são preservadas. Este preset não move nem redimensiona os widgets existentes.",
|
|
1495
|
+
"praxis.pageBuilder.presets.flow": "Layout e agrupamento explícitos prevalecem sobre os padrões do preset. Escolher uma organização não cria widgets nem conexões de dados.",
|
|
1496
|
+
"praxis.pageBuilder.presets.effectiveTheme": "Tema efetivo da página",
|
|
1497
|
+
"praxis.pageBuilder.presets.hostTheme": "Tema do aplicativo",
|
|
1498
|
+
"praxis.pageBuilder.presets.explicit": "Escolha explícita",
|
|
1499
|
+
"praxis.pageBuilder.presets.inherited": "Herdado da organização",
|
|
1500
|
+
"praxis.pageBuilder.presets.host": "Sem tema próprio da página",
|
|
1501
|
+
"praxis.pageBuilder.presets.suggested": "Tema sugerido pela organização",
|
|
1502
|
+
"praxis.pageBuilder.presets.notActive": "não ativo",
|
|
1503
|
+
"praxis.pageBuilder.presets.compare": "Comparar organizações da página",
|
|
1504
|
+
"praxis.pageBuilder.presets.diagramHelp": "Esquemas de papéis, não uma prévia da sua página. Compare a estrutura e escolha uma organização. Widgets e conexões existentes são preservados.",
|
|
1505
|
+
"praxis.pageBuilder.presets.selected": "Selecionado",
|
|
1506
|
+
"praxis.pageBuilder.presets.required": "papel obrigatório",
|
|
1507
|
+
"praxis.pageBuilder.presets.choose": "Escolher",
|
|
1508
|
+
"praxis.pageBuilder.presets.mobile": "As preferências mobile descrevem a intenção da composição. Confira o layout responsivo real: escolher um preset não oculta conteúdo, cria abas ou conecta principal e detalhe automaticamente.",
|
|
1509
|
+
"praxis.pageBuilder.presets.declaredPolicy": "Preferência declarada, não uma transformação automática",
|
|
1510
|
+
"praxis.pageBuilder.presets.layout.analytics-overview": "Visão analítica",
|
|
1511
|
+
"praxis.pageBuilder.presets.layout.kpi-plus-table": "Indicadores e tabela",
|
|
1512
|
+
"praxis.pageBuilder.presets.layout.master-detail-dashboard": "Principal e detalhes",
|
|
1513
|
+
"praxis.pageBuilder.presets.layout.ops-monitoring": "Monitoramento operacional",
|
|
1514
|
+
"praxis.pageBuilder.presets.use.analytics-overview": "Visão geral para comparar indicadores, tendências e detalhes. Útil em capacidade, vendas, custos e qualidade.",
|
|
1515
|
+
"praxis.pageBuilder.presets.use.kpi-plus-table": "Consulta e conferência com uma tabela central apoiada por métricas. Útil em pedidos, estoque e conciliação.",
|
|
1516
|
+
"praxis.pageBuilder.presets.use.master-detail-dashboard": "Investigar um item e seus detalhes. Útil em clientes, ativos e processos; a seleção e os vínculos precisam estar configurados.",
|
|
1517
|
+
"praxis.pageBuilder.presets.use.ops-monitoring": "Acompanhar status, séries, filas e alertas. Útil em serviços, produção e logística; preserve acesso aos alertas no mobile.",
|
|
1328
1518
|
"praxis.pageBuilder.shell.availabilityUnknown": "Disponibilidade não verificada neste widget. Valide os requisitos antes de usar.",
|
|
1329
1519
|
"praxis.pageBuilder.shell.availabilityReady": "Disponível neste widget agora.",
|
|
1330
1520
|
"praxis.pageBuilder.shell.availabilityBlocked": "Indisponível neste widget agora.",
|
|
@@ -1377,9 +1567,36 @@ const PRAXIS_PAGE_BUILDER_PT_BR = {
|
|
|
1377
1567
|
"praxis.pageBuilder.shell.scopeToolbar": "Barra de ferramentas",
|
|
1378
1568
|
"praxis.pageBuilder.shell.scopeSelection": "Item selecionado",
|
|
1379
1569
|
"praxis.pageBuilder.shell.scopeGeneral": "Uso geral",
|
|
1570
|
+
"praxis.pageBuilder.editor.arrange.title": "Reorganizar widgets existentes",
|
|
1571
|
+
"praxis.pageBuilder.editor.arrange.help": "Canvas base, na ordem atual de leitura. Dispositivos que herdam acompanham a base; posições explícitas por dispositivo são preservadas. Posições travadas permanecem no lugar.",
|
|
1572
|
+
"praxis.pageBuilder.editor.arrange.stack": "Coluna de trabalho",
|
|
1573
|
+
"praxis.pageBuilder.editor.arrange.compare": "Comparação em duas colunas",
|
|
1574
|
+
"praxis.pageBuilder.editor.arrange.primary": "Duas colunas: ⅔ e ⅓",
|
|
1575
|
+
"praxis.pageBuilder.editor.arrange.grid": "Grade de três colunas",
|
|
1576
|
+
"praxis.pageBuilder.editor.arrange.affected": "Widgets que serão alterados",
|
|
1577
|
+
"praxis.pageBuilder.editor.arrange.position": "Coluna / linha",
|
|
1578
|
+
"praxis.pageBuilder.editor.arrange.use": "Usar organização no rascunho",
|
|
1579
|
+
"praxis.pageBuilder.editor.arrange.undo": "Desfazer organização",
|
|
1580
|
+
"praxis.pageBuilder.editor.arrange.geometry": "Use um canvas completo, com posições válidas e sem sobreposições. A grade precisa comportar as colunas escolhidas.",
|
|
1581
|
+
"praxis.pageBuilder.editor.arrange.limits": "Esta organização não respeita os limites ou travas dos widgets indicados. Escolha outra disposição ou revise os limites.",
|
|
1582
|
+
"praxis.pageBuilder.editor.arrange.device-overrides": "Há posições parciais por dispositivo que dependem desta geometria. Revise essas posições antes de reorganizar a base.",
|
|
1583
|
+
"praxis.pageBuilder.editor.arrange.unchanged": "Esta organização já corresponde à página.",
|
|
1584
|
+
"praxis.pageBuilder.editor.arrange.stale": "O rascunho mudou. Prepare a organização novamente.",
|
|
1585
|
+
"praxis.pageBuilder.editor.arrange.staged": "Organização adicionada ao rascunho. Use Aplicar para visualizá-la na página.",
|
|
1586
|
+
"praxis.pageBuilder.editor.arrange.undone": "A organização anterior foi restaurada no rascunho. Use Aplicar para atualizar a página.",
|
|
1380
1587
|
'praxis.pageBuilder.shell.sampleOnly': 'Dados ilustrativos',
|
|
1381
1588
|
'praxis.pageBuilder.shell.sampleMetricLabel': 'Funcionários ativos',
|
|
1382
1589
|
'praxis.pageBuilder.shell.sampleMetricContext': '91% da força planejada',
|
|
1590
|
+
"praxis.pageBuilder.shell.cardFilter": "Desfoque do fundo",
|
|
1591
|
+
"praxis.pageBuilder.shell.cardFilterHelp": "Use com fundo translúcido. Exemplo: blur(16px) saturate(125%). Use none para desativar.",
|
|
1592
|
+
"praxis.pageBuilder.shell.cardFallbackBg": "Fundo opaco de alternativa",
|
|
1593
|
+
"praxis.pageBuilder.shell.cardFallbackHelp": "Preserva a leitura em tela cheia, com transparência reduzida ou sem suporte ao desfoque.",
|
|
1594
|
+
"praxis.pageBuilder.shell.effectiveStyle": "Estilo base",
|
|
1595
|
+
"praxis.pageBuilder.shell.styleFromWidget": "Escolhido neste widget",
|
|
1596
|
+
"praxis.pageBuilder.shell.styleFromPage": "Herdado da página ou do tema",
|
|
1597
|
+
"praxis.pageBuilder.shell.styleFromDefault": "Padrão do componente",
|
|
1598
|
+
"praxis.pageBuilder.shell.adjustmentsWin": "os ajustes prevalecem sobre o estilo",
|
|
1599
|
+
"praxis.pageBuilder.shell.surfaceDisabled": "A apresentação somente conteúdo mantém a moldura desativada.",
|
|
1383
1600
|
'praxis.pageBuilder.shell.customAdjustments': 'Ajustes personalizados',
|
|
1384
1601
|
'praxis.pageBuilder.shell.clearAdjustments': 'Usar apenas o estilo',
|
|
1385
1602
|
'praxis.pageBuilder.connections.editor.flowLegend': 'Tipos de conexão',
|
|
@@ -1469,6 +1686,14 @@ const PRAXIS_PAGE_BUILDER_PT_BR = {
|
|
|
1469
1686
|
"praxis.pageBuilder.shell.windowControls": "Controles de janela",
|
|
1470
1687
|
"praxis.pageBuilder.shell.collapsible": "Permitir recolher",
|
|
1471
1688
|
"praxis.pageBuilder.shell.cardBg": "Fundo do card",
|
|
1689
|
+
"praxis.pageBuilder.shell.cardBorderWidth": "Espessura da borda",
|
|
1690
|
+
"praxis.pageBuilder.shell.cardBorderStyle": "Traço da borda",
|
|
1691
|
+
"praxis.pageBuilder.shell.headerBorderWidth": "Espessura do divisor",
|
|
1692
|
+
"praxis.pageBuilder.shell.headerBorderStyle": "Traço do divisor",
|
|
1693
|
+
"praxis.pageBuilder.shell.cardBorderWidthHelp": "Ex.: 2px. Para variar por lado: 1px 1px 1px 6px (superior, direita, inferior, esquerda).",
|
|
1694
|
+
"praxis.pageBuilder.shell.headerBorderWidthHelp": "Ex.: 2px. Afeta somente o divisor inferior do cabeçalho.",
|
|
1695
|
+
"praxis.pageBuilder.shell.cardBorderStyleHelp": "Traços CSS: solid, dashed, dotted, double ou none. Aceita um valor por lado.",
|
|
1696
|
+
"praxis.pageBuilder.shell.headerBorderStyleHelp": "Traço do divisor: solid, dashed, dotted, double ou none.",
|
|
1472
1697
|
"praxis.pageBuilder.shell.cardBorder": "Cor da borda",
|
|
1473
1698
|
"praxis.pageBuilder.shell.cardRadius": "Arredondamento dos cantos",
|
|
1474
1699
|
"praxis.pageBuilder.shell.cardShadow": "Sombra",
|
|
@@ -1523,34 +1748,6 @@ const PRAXIS_PAGE_BUILDER_PT_BR = {
|
|
|
1523
1748
|
'praxis.pageBuilder.shell.inheritPagePreset': "Usar o estilo definido na página",
|
|
1524
1749
|
'praxis.pageBuilder.shell.useDefaultAppearance': "Usar a aparência padrão do widget",
|
|
1525
1750
|
'praxis.pageBuilder.shell.customPresetDescription': "Estilo disponibilizado nesta página",
|
|
1526
|
-
'praxis.widgetShell.presets.dark-glass.label': 'Vidro escuro',
|
|
1527
|
-
'praxis.widgetShell.presets.dark-glass.description': 'Aparência vidro escuro para páginas existentes e novas.',
|
|
1528
|
-
'praxis.widgetShell.presets.light-neutral.label': 'Neutro claro',
|
|
1529
|
-
'praxis.widgetShell.presets.light-neutral.description': 'Aparência neutro claro para páginas existentes e novas.',
|
|
1530
|
-
'praxis.widgetShell.presets.graphite.label': 'Grafite',
|
|
1531
|
-
'praxis.widgetShell.presets.graphite.description': 'Aparência grafite para páginas existentes e novas.',
|
|
1532
|
-
'praxis.widgetShell.presets.data-panel.label': 'Painel de dados',
|
|
1533
|
-
'praxis.widgetShell.presets.data-panel.description': 'Aparência painel de dados para páginas existentes e novas.',
|
|
1534
|
-
'praxis.widgetShell.presets.chart-panel.label': 'Painel de gráfico',
|
|
1535
|
-
'praxis.widgetShell.presets.chart-panel.description': 'Aparência painel de gráfico para páginas existentes e novas.',
|
|
1536
|
-
'praxis.widgetShell.presets.metric-panel.label': 'Painel de indicadores',
|
|
1537
|
-
'praxis.widgetShell.presets.metric-panel.description': 'Aparência painel de indicadores para páginas existentes e novas.',
|
|
1538
|
-
'praxis.widgetShell.presets.executive-card.label': 'Card executivo',
|
|
1539
|
-
'praxis.widgetShell.presets.executive-card.description': 'Aparência card executivo para páginas existentes e novas.',
|
|
1540
|
-
'praxis.widgetShell.presets.filter-bar.label': 'Barra de filtros',
|
|
1541
|
-
'praxis.widgetShell.presets.filter-bar.description': 'Aparência barra de filtros para páginas existentes e novas.',
|
|
1542
|
-
'praxis.widgetShell.presets.integratedCard.label': 'Card integrado',
|
|
1543
|
-
'praxis.widgetShell.presets.integratedCard.description': 'Uma superfície contínua para cabeçalho, ações e corpo do widget.',
|
|
1544
|
-
'praxis.widgetShell.presets.outlinedCard.label': 'Card contornado',
|
|
1545
|
-
'praxis.widgetShell.presets.outlinedCard.description': 'Superfície contínua e discreta, com contorno externo e sem sombra.',
|
|
1546
|
-
'praxis.widgetShell.presets.elevatedCard.label': 'Card elevado',
|
|
1547
|
-
'praxis.widgetShell.presets.elevatedCard.description': 'Superfície contínua separada da página por uma sombra suave.',
|
|
1548
|
-
'praxis.widgetShell.presets.tonalCard.label': 'Card tonal',
|
|
1549
|
-
'praxis.widgetShell.presets.tonalCard.description': 'Superfície contínua levemente tonalizada e com elevação mínima.',
|
|
1550
|
-
'praxis.widgetShell.presets.sectionedCard.label': 'Card seccionado',
|
|
1551
|
-
'praxis.widgetShell.presets.sectionedCard.description': 'Cabeçalho destacado para conteúdos operacionais densos.',
|
|
1552
|
-
'praxis.widgetShell.presets.frameless.label': 'Sem moldura',
|
|
1553
|
-
'praxis.widgetShell.presets.frameless.description': 'Sem superfície externa, contorno ou sombra; mantém o recuo de leitura do conteúdo.',
|
|
1554
1751
|
'praxis.pageBuilder.authoringSource.alert.title': 'Linhagem semântica requer atenção',
|
|
1555
1752
|
'praxis.pageBuilder.authoringSource.alert.description': 'A página continua disponível, mas esta fonte não será reutilizada até que uma nova autoria governada restabeleça sua proveniência.',
|
|
1556
1753
|
'praxis.pageBuilder.authoringSource.diagnostics.authoring-source-schema-invalid': 'O envelope de autoria não atende ao schema suportado pelo Page Builder.',
|
|
@@ -1604,6 +1801,7 @@ const PRAXIS_PAGE_BUILDER_PT_BR = {
|
|
|
1604
1801
|
'praxis.pageBuilder.editor.layout.none': 'Nenhum',
|
|
1605
1802
|
'praxis.pageBuilder.editor.layout.themePreset': 'Predefinição de tema',
|
|
1606
1803
|
'praxis.pageBuilder.editor.layout.layoutDefault': 'Padrão do layout',
|
|
1804
|
+
'praxis.pageBuilder.editor.layout.inheritShell': 'Herdar aparência',
|
|
1607
1805
|
'praxis.pageBuilder.editor.layout.shellPreset': 'Predefinição visual da shell',
|
|
1608
1806
|
'praxis.pageBuilder.editor.grouping.section': 'Agrupamento',
|
|
1609
1807
|
'praxis.pageBuilder.editor.grouping.title': 'Agrupamento',
|
|
@@ -2965,9 +3163,10 @@ class WidgetShellEditorComponent {
|
|
|
2965
3163
|
}
|
|
2966
3164
|
hasAppearanceErrors() {
|
|
2967
3165
|
return [
|
|
2968
|
-
this.form.controls.cardBg, this.form.controls.cardBorder,
|
|
3166
|
+
this.form.controls.cardBg, this.form.controls.cardBorder, this.form.controls.cardBorderWidth, this.form.controls.cardBorderStyle,
|
|
2969
3167
|
this.form.controls.cardRadius, this.form.controls.cardShadow,
|
|
2970
|
-
this.form.controls.
|
|
3168
|
+
this.form.controls.cardFilter, this.form.controls.cardFallbackBg,
|
|
3169
|
+
this.form.controls.headerBg, this.form.controls.headerBorder, this.form.controls.headerBorderWidth, this.form.controls.headerBorderStyle,
|
|
2971
3170
|
this.form.controls.titleColor, this.form.controls.subtitleColor,
|
|
2972
3171
|
this.form.controls.iconColor, this.form.controls.bodyBg,
|
|
2973
3172
|
this.form.controls.bodyColor, this.form.controls.bodyPadding,
|
|
@@ -3001,10 +3200,16 @@ class WidgetShellEditorComponent {
|
|
|
3001
3200
|
collapsible: new FormControl(true, { nonNullable: true }),
|
|
3002
3201
|
cardBg: new FormControl('', { nonNullable: true }),
|
|
3003
3202
|
cardBorder: new FormControl('', { nonNullable: true }),
|
|
3203
|
+
cardBorderWidth: new FormControl('', { nonNullable: true }),
|
|
3204
|
+
cardBorderStyle: new FormControl('', { nonNullable: true }),
|
|
3004
3205
|
cardRadius: new FormControl('', { nonNullable: true }),
|
|
3005
3206
|
cardShadow: new FormControl('', { nonNullable: true }),
|
|
3207
|
+
cardFilter: new FormControl('', { nonNullable: true }),
|
|
3208
|
+
cardFallbackBg: new FormControl('', { nonNullable: true }),
|
|
3006
3209
|
headerBg: new FormControl('', { nonNullable: true }),
|
|
3007
3210
|
headerBorder: new FormControl('', { nonNullable: true }),
|
|
3211
|
+
headerBorderWidth: new FormControl('', { nonNullable: true }),
|
|
3212
|
+
headerBorderStyle: new FormControl('', { nonNullable: true }),
|
|
3008
3213
|
titleColor: new FormControl('', { nonNullable: true }),
|
|
3009
3214
|
subtitleColor: new FormControl('', { nonNullable: true }),
|
|
3010
3215
|
iconColor: new FormControl('', { nonNullable: true }),
|
|
@@ -3054,8 +3259,8 @@ class WidgetShellEditorComponent {
|
|
|
3054
3259
|
this.getActionAvailability = data?.getActionAvailability;
|
|
3055
3260
|
if (this.canEditShellAppearance()) {
|
|
3056
3261
|
const properties = {
|
|
3057
|
-
cardBg: 'background', cardBorder: 'border-color', cardRadius: 'border-radius',
|
|
3058
|
-
cardShadow: 'box-shadow', headerBg: 'background', headerBorder: 'border-color',
|
|
3262
|
+
cardBg: 'background', cardBorder: 'border-color', cardBorderWidth: 'border-width', cardBorderStyle: 'border-style', cardRadius: 'border-radius',
|
|
3263
|
+
cardShadow: 'box-shadow', cardFilter: 'backdrop-filter', cardFallbackBg: 'background', headerBg: 'background', headerBorder: 'border-bottom-color', headerBorderWidth: 'border-bottom-width', headerBorderStyle: 'border-bottom-style',
|
|
3059
3264
|
titleColor: 'color', subtitleColor: 'color', iconColor: 'color',
|
|
3060
3265
|
bodyBg: 'background', bodyColor: 'color', bodyPadding: 'padding',
|
|
3061
3266
|
titleSize: 'font-size', titleWeight: 'font-weight', subtitleSize: 'font-size',
|
|
@@ -3115,6 +3320,11 @@ class WidgetShellEditorComponent {
|
|
|
3115
3320
|
presetDescription(preset) {
|
|
3116
3321
|
return this.i18n.resolve(preset.description, '');
|
|
3117
3322
|
}
|
|
3323
|
+
effectivePresetLabel() {
|
|
3324
|
+
const id = this.form.controls.preset.value || this.pagePreset;
|
|
3325
|
+
const preset = this.presetOptions.find(option => option.id === id);
|
|
3326
|
+
return preset ? this.presetLabel(preset) : id || this.noPresetLabel();
|
|
3327
|
+
}
|
|
3118
3328
|
presetTitle() {
|
|
3119
3329
|
return this.i18n.t('praxis.pageBuilder.shell.presetTitle', undefined, 'Ready-made styles');
|
|
3120
3330
|
}
|
|
@@ -3210,10 +3420,16 @@ class WidgetShellEditorComponent {
|
|
|
3210
3420
|
collapsible: window.collapsible !== false,
|
|
3211
3421
|
cardBg: card.background || '',
|
|
3212
3422
|
cardBorder: card.borderColor || '',
|
|
3423
|
+
cardBorderWidth: card.borderWidth || '',
|
|
3424
|
+
cardBorderStyle: card.borderStyle || '',
|
|
3213
3425
|
cardRadius: card.borderRadius || '',
|
|
3214
3426
|
cardShadow: card.shadow || '',
|
|
3427
|
+
cardFilter: card.backdropFilter || '',
|
|
3428
|
+
cardFallbackBg: card.fallbackBackground || '',
|
|
3215
3429
|
headerBg: header.background || '',
|
|
3216
3430
|
headerBorder: header.borderColor || '',
|
|
3431
|
+
headerBorderWidth: header.borderWidth || '',
|
|
3432
|
+
headerBorderStyle: header.borderStyle || '',
|
|
3217
3433
|
titleColor: header.titleColor || '',
|
|
3218
3434
|
subtitleColor: header.subtitleColor || '',
|
|
3219
3435
|
iconColor: header.iconColor || '',
|
|
@@ -3375,35 +3591,47 @@ class WidgetShellEditorComponent {
|
|
|
3375
3591
|
const header = {};
|
|
3376
3592
|
const body = {};
|
|
3377
3593
|
const typography = {};
|
|
3378
|
-
if (raw.cardBg)
|
|
3594
|
+
if (raw.cardBg.trim())
|
|
3379
3595
|
card.background = raw.cardBg.trim();
|
|
3380
|
-
if (raw.cardBorder)
|
|
3596
|
+
if (raw.cardBorder.trim())
|
|
3381
3597
|
card.borderColor = raw.cardBorder.trim();
|
|
3382
|
-
if (raw.
|
|
3598
|
+
if (raw.cardBorderWidth.trim())
|
|
3599
|
+
card.borderWidth = raw.cardBorderWidth.trim();
|
|
3600
|
+
if (raw.cardBorderStyle.trim())
|
|
3601
|
+
card.borderStyle = raw.cardBorderStyle.trim();
|
|
3602
|
+
if (raw.cardRadius.trim())
|
|
3383
3603
|
card.borderRadius = raw.cardRadius.trim();
|
|
3384
|
-
if (raw.cardShadow)
|
|
3604
|
+
if (raw.cardShadow.trim())
|
|
3385
3605
|
card.shadow = raw.cardShadow.trim();
|
|
3386
|
-
if (raw.
|
|
3606
|
+
if (raw.cardFilter.trim())
|
|
3607
|
+
card.backdropFilter = raw.cardFilter.trim();
|
|
3608
|
+
if (raw.cardFallbackBg.trim())
|
|
3609
|
+
card.fallbackBackground = raw.cardFallbackBg.trim();
|
|
3610
|
+
if (raw.headerBg.trim())
|
|
3387
3611
|
header.background = raw.headerBg.trim();
|
|
3388
|
-
if (raw.headerBorder)
|
|
3612
|
+
if (raw.headerBorder.trim())
|
|
3389
3613
|
header.borderColor = raw.headerBorder.trim();
|
|
3390
|
-
if (raw.
|
|
3614
|
+
if (raw.headerBorderWidth.trim())
|
|
3615
|
+
header.borderWidth = raw.headerBorderWidth.trim();
|
|
3616
|
+
if (raw.headerBorderStyle.trim())
|
|
3617
|
+
header.borderStyle = raw.headerBorderStyle.trim();
|
|
3618
|
+
if (raw.titleColor.trim())
|
|
3391
3619
|
header.titleColor = raw.titleColor.trim();
|
|
3392
|
-
if (raw.subtitleColor)
|
|
3620
|
+
if (raw.subtitleColor.trim())
|
|
3393
3621
|
header.subtitleColor = raw.subtitleColor.trim();
|
|
3394
|
-
if (raw.iconColor)
|
|
3622
|
+
if (raw.iconColor.trim())
|
|
3395
3623
|
header.iconColor = raw.iconColor.trim();
|
|
3396
|
-
if (raw.bodyBg)
|
|
3624
|
+
if (raw.bodyBg.trim())
|
|
3397
3625
|
body.background = raw.bodyBg.trim();
|
|
3398
|
-
if (raw.bodyColor)
|
|
3626
|
+
if (raw.bodyColor.trim())
|
|
3399
3627
|
body.textColor = raw.bodyColor.trim();
|
|
3400
|
-
if (raw.bodyPadding)
|
|
3628
|
+
if (raw.bodyPadding.trim())
|
|
3401
3629
|
body.padding = raw.bodyPadding.trim();
|
|
3402
|
-
if (raw.titleSize)
|
|
3630
|
+
if (raw.titleSize.trim())
|
|
3403
3631
|
typography.titleSize = raw.titleSize.trim();
|
|
3404
|
-
if (raw.titleWeight)
|
|
3632
|
+
if (raw.titleWeight.trim())
|
|
3405
3633
|
typography.titleWeight = raw.titleWeight.trim();
|
|
3406
|
-
if (raw.subtitleSize)
|
|
3634
|
+
if (raw.subtitleSize.trim())
|
|
3407
3635
|
typography.subtitleSize = raw.subtitleSize.trim();
|
|
3408
3636
|
const appearance = {};
|
|
3409
3637
|
if (Object.keys(card).length)
|
|
@@ -3435,7 +3663,7 @@ class WidgetShellEditorComponent {
|
|
|
3435
3663
|
return shellCapabilities[capability] !== false;
|
|
3436
3664
|
}
|
|
3437
3665
|
appearanceControls = [
|
|
3438
|
-
'cardBg', 'cardBorder', 'cardRadius', 'cardShadow', 'headerBg', 'headerBorder',
|
|
3666
|
+
'cardBg', 'cardBorder', 'cardBorderWidth', 'cardBorderStyle', 'cardRadius', 'cardShadow', 'cardFilter', 'cardFallbackBg', 'headerBg', 'headerBorder', 'headerBorderWidth', 'headerBorderStyle',
|
|
3439
3667
|
'titleColor', 'subtitleColor', 'iconColor', 'bodyBg', 'bodyColor', 'bodyPadding',
|
|
3440
3668
|
'titleSize', 'titleWeight', 'subtitleSize',
|
|
3441
3669
|
];
|
|
@@ -3789,6 +4017,12 @@ class WidgetShellEditorComponent {
|
|
|
3789
4017
|
<div class="shell-preset-field shell-field--wide">
|
|
3790
4018
|
<span class="shell-preset-label">{{ presetTitle() }}</span>
|
|
3791
4019
|
<p class="shell-hint">{{ shellText('presetHint') }}</p>
|
|
4020
|
+
<p class="shell-effective-style" role="status" data-testid="widget-shell-effective-style">
|
|
4021
|
+
<strong>{{ shellText('effectiveStyle') }}: {{ effectivePresetLabel() }}</strong>
|
|
4022
|
+
<span>{{ shellText(form.controls.preset.value ? 'styleFromWidget' : pagePreset ? 'styleFromPage' : 'styleFromDefault') }}</span>
|
|
4023
|
+
@if (appearanceOverrideCount()) { <span>{{ shellText('customAdjustments') }}: {{ appearanceOverrideCount() }} · {{ shellText('adjustmentsWin') }}</span> }
|
|
4024
|
+
@if (form.controls.kind.value === 'none') { <span>{{ shellText('surfaceDisabled') }}</span> }
|
|
4025
|
+
</p>
|
|
3792
4026
|
<div class="shell-preset-grid" role="group" [attr.aria-label]="presetTitle()">
|
|
3793
4027
|
<button
|
|
3794
4028
|
type="button"
|
|
@@ -3814,8 +4048,13 @@ class WidgetShellEditorComponent {
|
|
|
3814
4048
|
<span
|
|
3815
4049
|
class="shell-preset-preview"
|
|
3816
4050
|
aria-hidden="true"
|
|
3817
|
-
[
|
|
4051
|
+
[class.shell-preset-preview--backdrop]="!!p.appearance?.card?.backdropFilter && p.appearance?.card?.backdropFilter !== 'none'"
|
|
4052
|
+
[style.--shell-mini-bg]="p.appearance?.card?.background || null"
|
|
4053
|
+
[style.--shell-mini-fallback]="p.appearance?.card?.fallbackBackground || null"
|
|
4054
|
+
[style.--shell-mini-filter]="p.appearance?.card?.backdropFilter || null"
|
|
3818
4055
|
[style.border-color]="p.appearance?.card?.borderColor || null"
|
|
4056
|
+
[style.border-width]="p.appearance?.card?.borderWidth || null"
|
|
4057
|
+
[style.border-style]="p.appearance?.card?.borderStyle || null"
|
|
3819
4058
|
[style.border-radius]="p.appearance?.card?.borderRadius || null"
|
|
3820
4059
|
[style.box-shadow]="p.appearance?.card?.shadow || null"
|
|
3821
4060
|
>
|
|
@@ -3823,6 +4062,8 @@ class WidgetShellEditorComponent {
|
|
|
3823
4062
|
class="shell-preset-preview__header"
|
|
3824
4063
|
[style.background]="p.appearance?.header?.background || null"
|
|
3825
4064
|
[style.border-color]="p.appearance?.header?.borderColor || null"
|
|
4065
|
+
[style.border-bottom-width]="p.appearance?.header?.borderWidth || null"
|
|
4066
|
+
[style.border-bottom-style]="p.appearance?.header?.borderStyle || null"
|
|
3826
4067
|
><span class="shell-mini-title" [style.background]="p.appearance?.header?.titleColor || null"></span><span class="shell-mini-action" [style.background]="p.appearance?.header?.iconColor || null"></span></span>
|
|
3827
4068
|
<span class="shell-preset-preview__body" [style.background]="p.appearance?.body?.background || null" [style.color]="p.appearance?.body?.textColor || null">
|
|
3828
4069
|
<span class="shell-mini-value"></span>
|
|
@@ -3948,6 +4189,18 @@ class WidgetShellEditorComponent {
|
|
|
3948
4189
|
<h3 class="shell-section">{{ shellText('advancedAppearance') }}</h3>
|
|
3949
4190
|
<p class="shell-hint">{{ shellText('advancedHint') }}</p>
|
|
3950
4191
|
<div class="shell-grid">
|
|
4192
|
+
<mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
|
|
4193
|
+
<mat-label>{{ shellText('cardFilter') }}</mat-label>
|
|
4194
|
+
<input matInput [formControl]="form.controls.cardFilter" placeholder="blur(16px) saturate(125%)" />
|
|
4195
|
+
<button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('cardFilterHelp')" [attr.aria-label]="shellText('cardFilterHelp')"></button>
|
|
4196
|
+
@if (form.controls.cardFilter.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
|
|
4197
|
+
</mat-form-field>
|
|
4198
|
+
<mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
|
|
4199
|
+
<mat-label>{{ shellText('cardFallbackBg') }}</mat-label>
|
|
4200
|
+
<input matInput [formControl]="form.controls.cardFallbackBg" />
|
|
4201
|
+
<button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('cardFallbackHelp')" [attr.aria-label]="shellText('cardFallbackHelp')"></button>
|
|
4202
|
+
@if (form.controls.cardFallbackBg.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
|
|
4203
|
+
</mat-form-field>
|
|
3951
4204
|
<mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
|
|
3952
4205
|
<mat-label>{{ shellText('cardBg') }}</mat-label>
|
|
3953
4206
|
<input
|
|
@@ -3970,6 +4223,18 @@ class WidgetShellEditorComponent {
|
|
|
3970
4223
|
<mat-error>{{ appearanceError() }}</mat-error>
|
|
3971
4224
|
}
|
|
3972
4225
|
</mat-form-field>
|
|
4226
|
+
<mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
|
|
4227
|
+
<mat-label>{{ shellText('cardBorderWidth') }}</mat-label>
|
|
4228
|
+
<input matInput [formControl]="form.controls.cardBorderWidth" placeholder="2px" />
|
|
4229
|
+
<button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('cardBorderWidthHelp')" [attr.aria-label]="shellText('cardBorderWidthHelp')"></button>
|
|
4230
|
+
@if (form.controls.cardBorderWidth.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
|
|
4231
|
+
</mat-form-field>
|
|
4232
|
+
<mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
|
|
4233
|
+
<mat-label>{{ shellText('cardBorderStyle') }}</mat-label>
|
|
4234
|
+
<input matInput [formControl]="form.controls.cardBorderStyle" placeholder="solid" />
|
|
4235
|
+
<button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('cardBorderStyleHelp')" [attr.aria-label]="shellText('cardBorderStyleHelp')"></button>
|
|
4236
|
+
@if (form.controls.cardBorderStyle.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
|
|
4237
|
+
</mat-form-field>
|
|
3973
4238
|
<mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
|
|
3974
4239
|
<mat-label>{{ shellText('cardRadius') }}</mat-label>
|
|
3975
4240
|
<input
|
|
@@ -4014,6 +4279,18 @@ class WidgetShellEditorComponent {
|
|
|
4014
4279
|
<mat-error>{{ appearanceError() }}</mat-error>
|
|
4015
4280
|
}
|
|
4016
4281
|
</mat-form-field>
|
|
4282
|
+
<mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
|
|
4283
|
+
<mat-label>{{ shellText('headerBorderWidth') }}</mat-label>
|
|
4284
|
+
<input matInput [formControl]="form.controls.headerBorderWidth" placeholder="2px" />
|
|
4285
|
+
<button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('headerBorderWidthHelp')" [attr.aria-label]="shellText('headerBorderWidthHelp')"></button>
|
|
4286
|
+
@if (form.controls.headerBorderWidth.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
|
|
4287
|
+
</mat-form-field>
|
|
4288
|
+
<mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
|
|
4289
|
+
<mat-label>{{ shellText('headerBorderStyle') }}</mat-label>
|
|
4290
|
+
<input matInput [formControl]="form.controls.headerBorderStyle" placeholder="solid" />
|
|
4291
|
+
<button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('headerBorderStyleHelp')" [attr.aria-label]="shellText('headerBorderStyleHelp')"></button>
|
|
4292
|
+
@if (form.controls.headerBorderStyle.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
|
|
4293
|
+
</mat-form-field>
|
|
4017
4294
|
<mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
|
|
4018
4295
|
<mat-label>{{ shellText('titleColor') }}</mat-label>
|
|
4019
4296
|
<input
|
|
@@ -4317,7 +4594,7 @@ class WidgetShellEditorComponent {
|
|
|
4317
4594
|
</div>
|
|
4318
4595
|
</details>
|
|
4319
4596
|
</div>
|
|
4320
|
-
`, isInline: true, styles: [":host{display:block;container:shell-editor / inline-size}.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(--md-sys-color-surface)}.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$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { 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: i5.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: i8.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: i8.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: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4597
|
+
`, 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$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: 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: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i4.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: i5.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: i9.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: i9.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 });
|
|
4321
4598
|
}
|
|
4322
4599
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: WidgetShellEditorComponent, decorators: [{
|
|
4323
4600
|
type: Component,
|
|
@@ -4328,6 +4605,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4328
4605
|
ReactiveFormsModule,
|
|
4329
4606
|
MatButtonModule,
|
|
4330
4607
|
MatCheckboxModule,
|
|
4608
|
+
MatTooltipModule,
|
|
4331
4609
|
MatFormFieldModule,
|
|
4332
4610
|
MatIconModule,
|
|
4333
4611
|
MatInputModule,
|
|
@@ -4403,6 +4681,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4403
4681
|
<div class="shell-preset-field shell-field--wide">
|
|
4404
4682
|
<span class="shell-preset-label">{{ presetTitle() }}</span>
|
|
4405
4683
|
<p class="shell-hint">{{ shellText('presetHint') }}</p>
|
|
4684
|
+
<p class="shell-effective-style" role="status" data-testid="widget-shell-effective-style">
|
|
4685
|
+
<strong>{{ shellText('effectiveStyle') }}: {{ effectivePresetLabel() }}</strong>
|
|
4686
|
+
<span>{{ shellText(form.controls.preset.value ? 'styleFromWidget' : pagePreset ? 'styleFromPage' : 'styleFromDefault') }}</span>
|
|
4687
|
+
@if (appearanceOverrideCount()) { <span>{{ shellText('customAdjustments') }}: {{ appearanceOverrideCount() }} · {{ shellText('adjustmentsWin') }}</span> }
|
|
4688
|
+
@if (form.controls.kind.value === 'none') { <span>{{ shellText('surfaceDisabled') }}</span> }
|
|
4689
|
+
</p>
|
|
4406
4690
|
<div class="shell-preset-grid" role="group" [attr.aria-label]="presetTitle()">
|
|
4407
4691
|
<button
|
|
4408
4692
|
type="button"
|
|
@@ -4428,8 +4712,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4428
4712
|
<span
|
|
4429
4713
|
class="shell-preset-preview"
|
|
4430
4714
|
aria-hidden="true"
|
|
4431
|
-
[
|
|
4715
|
+
[class.shell-preset-preview--backdrop]="!!p.appearance?.card?.backdropFilter && p.appearance?.card?.backdropFilter !== 'none'"
|
|
4716
|
+
[style.--shell-mini-bg]="p.appearance?.card?.background || null"
|
|
4717
|
+
[style.--shell-mini-fallback]="p.appearance?.card?.fallbackBackground || null"
|
|
4718
|
+
[style.--shell-mini-filter]="p.appearance?.card?.backdropFilter || null"
|
|
4432
4719
|
[style.border-color]="p.appearance?.card?.borderColor || null"
|
|
4720
|
+
[style.border-width]="p.appearance?.card?.borderWidth || null"
|
|
4721
|
+
[style.border-style]="p.appearance?.card?.borderStyle || null"
|
|
4433
4722
|
[style.border-radius]="p.appearance?.card?.borderRadius || null"
|
|
4434
4723
|
[style.box-shadow]="p.appearance?.card?.shadow || null"
|
|
4435
4724
|
>
|
|
@@ -4437,6 +4726,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4437
4726
|
class="shell-preset-preview__header"
|
|
4438
4727
|
[style.background]="p.appearance?.header?.background || null"
|
|
4439
4728
|
[style.border-color]="p.appearance?.header?.borderColor || null"
|
|
4729
|
+
[style.border-bottom-width]="p.appearance?.header?.borderWidth || null"
|
|
4730
|
+
[style.border-bottom-style]="p.appearance?.header?.borderStyle || null"
|
|
4440
4731
|
><span class="shell-mini-title" [style.background]="p.appearance?.header?.titleColor || null"></span><span class="shell-mini-action" [style.background]="p.appearance?.header?.iconColor || null"></span></span>
|
|
4441
4732
|
<span class="shell-preset-preview__body" [style.background]="p.appearance?.body?.background || null" [style.color]="p.appearance?.body?.textColor || null">
|
|
4442
4733
|
<span class="shell-mini-value"></span>
|
|
@@ -4562,6 +4853,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4562
4853
|
<h3 class="shell-section">{{ shellText('advancedAppearance') }}</h3>
|
|
4563
4854
|
<p class="shell-hint">{{ shellText('advancedHint') }}</p>
|
|
4564
4855
|
<div class="shell-grid">
|
|
4856
|
+
<mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
|
|
4857
|
+
<mat-label>{{ shellText('cardFilter') }}</mat-label>
|
|
4858
|
+
<input matInput [formControl]="form.controls.cardFilter" placeholder="blur(16px) saturate(125%)" />
|
|
4859
|
+
<button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('cardFilterHelp')" [attr.aria-label]="shellText('cardFilterHelp')"></button>
|
|
4860
|
+
@if (form.controls.cardFilter.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
|
|
4861
|
+
</mat-form-field>
|
|
4862
|
+
<mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
|
|
4863
|
+
<mat-label>{{ shellText('cardFallbackBg') }}</mat-label>
|
|
4864
|
+
<input matInput [formControl]="form.controls.cardFallbackBg" />
|
|
4865
|
+
<button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('cardFallbackHelp')" [attr.aria-label]="shellText('cardFallbackHelp')"></button>
|
|
4866
|
+
@if (form.controls.cardFallbackBg.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
|
|
4867
|
+
</mat-form-field>
|
|
4565
4868
|
<mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
|
|
4566
4869
|
<mat-label>{{ shellText('cardBg') }}</mat-label>
|
|
4567
4870
|
<input
|
|
@@ -4584,6 +4887,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4584
4887
|
<mat-error>{{ appearanceError() }}</mat-error>
|
|
4585
4888
|
}
|
|
4586
4889
|
</mat-form-field>
|
|
4890
|
+
<mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
|
|
4891
|
+
<mat-label>{{ shellText('cardBorderWidth') }}</mat-label>
|
|
4892
|
+
<input matInput [formControl]="form.controls.cardBorderWidth" placeholder="2px" />
|
|
4893
|
+
<button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('cardBorderWidthHelp')" [attr.aria-label]="shellText('cardBorderWidthHelp')"></button>
|
|
4894
|
+
@if (form.controls.cardBorderWidth.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
|
|
4895
|
+
</mat-form-field>
|
|
4896
|
+
<mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
|
|
4897
|
+
<mat-label>{{ shellText('cardBorderStyle') }}</mat-label>
|
|
4898
|
+
<input matInput [formControl]="form.controls.cardBorderStyle" placeholder="solid" />
|
|
4899
|
+
<button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('cardBorderStyleHelp')" [attr.aria-label]="shellText('cardBorderStyleHelp')"></button>
|
|
4900
|
+
@if (form.controls.cardBorderStyle.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
|
|
4901
|
+
</mat-form-field>
|
|
4587
4902
|
<mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
|
|
4588
4903
|
<mat-label>{{ shellText('cardRadius') }}</mat-label>
|
|
4589
4904
|
<input
|
|
@@ -4628,6 +4943,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4628
4943
|
<mat-error>{{ appearanceError() }}</mat-error>
|
|
4629
4944
|
}
|
|
4630
4945
|
</mat-form-field>
|
|
4946
|
+
<mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
|
|
4947
|
+
<mat-label>{{ shellText('headerBorderWidth') }}</mat-label>
|
|
4948
|
+
<input matInput [formControl]="form.controls.headerBorderWidth" placeholder="2px" />
|
|
4949
|
+
<button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('headerBorderWidthHelp')" [attr.aria-label]="shellText('headerBorderWidthHelp')"></button>
|
|
4950
|
+
@if (form.controls.headerBorderWidth.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
|
|
4951
|
+
</mat-form-field>
|
|
4952
|
+
<mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
|
|
4953
|
+
<mat-label>{{ shellText('headerBorderStyle') }}</mat-label>
|
|
4954
|
+
<input matInput [formControl]="form.controls.headerBorderStyle" placeholder="solid" />
|
|
4955
|
+
<button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('headerBorderStyleHelp')" [attr.aria-label]="shellText('headerBorderStyleHelp')"></button>
|
|
4956
|
+
@if (form.controls.headerBorderStyle.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
|
|
4957
|
+
</mat-form-field>
|
|
4631
4958
|
<mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
|
|
4632
4959
|
<mat-label>{{ shellText('titleColor') }}</mat-label>
|
|
4633
4960
|
<input
|
|
@@ -4931,7 +5258,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4931
5258
|
</div>
|
|
4932
5259
|
</details>
|
|
4933
5260
|
</div>
|
|
4934
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;container:shell-editor / inline-size}.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(--md-sys-color-surface)}.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"] }]
|
|
5261
|
+
`, 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"] }]
|
|
4935
5262
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
4936
5263
|
type: Optional
|
|
4937
5264
|
}, {
|
|
@@ -12890,6 +13217,129 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
12890
13217
|
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{position:fixed;inset:0;z-index:1000;pointer-events:none}.connection-editor{--pce-fg: var(--md-sys-color-on-surface, #dce6f4);--pce-strong: var(--md-sys-color-on-surface, #f8fafc);--pce-muted: var(--md-sys-color-on-surface-variant, #94a3b8);--pce-primary: var(--md-sys-color-primary, #19c8e8);--pce-secondary: var(--md-sys-color-secondary, #b46cff);--pce-tertiary: var(--md-sys-color-tertiary, #ff9f2f);--pce-success: var(--md-sys-color-tertiary, #10b981);--pce-warning: var(--md-sys-color-error, #ff9f2f);--pce-error: var(--md-sys-color-error, #ef4444);--pce-surface-base: color-mix(in srgb, var(--md-sys-color-surface, #07111f) 80%, #020617);--pce-surface: color-mix(in srgb, var(--md-sys-color-surface-container, #08101d) 78%, transparent);--pce-surface-strong: color-mix(in srgb, var(--md-sys-color-surface-container-high, #0f172a) 88%, transparent);--pce-surface-soft: color-mix(in srgb, var(--md-sys-color-surface-container-low, #080d18) 64%, transparent);--pce-outline: color-mix(in srgb, var(--md-sys-color-outline-variant, #899ab7) 46%, transparent);--pce-outline-strong: color-mix(in srgb, var(--md-sys-color-outline, #94a3b8) 58%, transparent);--pce-grid: color-mix(in srgb, var(--md-sys-color-outline-variant, #7d8fa9) 24%, transparent);--pce-glass-highlight: color-mix(in srgb, var(--md-sys-color-on-surface, #ffffff) 8%, transparent);--pce-shadow: color-mix(in srgb, #000 56%, transparent);--pce-shadow-soft: color-mix(in srgb, #000 30%, transparent);--pce-stage-bg: color-mix(in srgb, var(--md-sys-color-surface-container-lowest, #040a13) 42%, transparent);--pce-dock-bg: color-mix(in srgb, var(--md-sys-color-surface-container-high, #050912) 94%, transparent);position:absolute;inset:12px;height:calc(100vh - 24px);box-sizing:border-box;overflow:hidden;padding:16px;border-radius:18px;color:var(--pce-fg);background:radial-gradient(circle at 72% 20%,color-mix(in srgb,var(--pce-secondary) 16%,transparent),transparent 28%),radial-gradient(circle at 26% 74%,color-mix(in srgb,var(--pce-primary) 10%,transparent),transparent 34%),radial-gradient(circle at 48% 52%,color-mix(in srgb,var(--pce-tertiary) 5%,transparent),transparent 35%),linear-gradient(var(--pce-grid) 1px,transparent 1px),linear-gradient(90deg,var(--pce-grid) 1px,transparent 1px),var(--pce-surface-base);background-size:auto,auto,auto,38px 38px,38px 38px,auto;border:1px solid var(--pce-outline);box-shadow:0 28px 90px var(--pce-shadow);pointer-events:auto;display:grid;grid-template-columns:minmax(0,1fr) minmax(300px,380px);grid-template-rows:auto minmax(0,1fr) minmax(44px,118px);gap:14px}:host-context(.theme-light) .connection-editor{--pce-surface-base: color-mix(in srgb, var(--md-sys-color-surface, #ffffff) 94%, var(--md-sys-color-primary, #2563eb) 6%);--pce-surface: color-mix(in srgb, var(--md-sys-color-surface-container, #f8fafc) 82%, transparent);--pce-surface-strong: color-mix(in srgb, var(--md-sys-color-surface-container-high, #eef2ff) 88%, transparent);--pce-surface-soft: color-mix(in srgb, var(--md-sys-color-surface-container-low, #f8fafc) 70%, transparent);--pce-outline: color-mix(in srgb, var(--md-sys-color-outline-variant, #cbd5e1) 68%, transparent);--pce-outline-strong: color-mix(in srgb, var(--md-sys-color-outline, #64748b) 62%, transparent);--pce-grid: color-mix(in srgb, var(--md-sys-color-outline-variant, #cbd5e1) 34%, transparent);--pce-glass-highlight: color-mix(in srgb, #fff 64%, transparent);--pce-shadow: color-mix(in srgb, var(--md-sys-color-shadow, #64748b) 22%, transparent);--pce-shadow-soft: color-mix(in srgb, var(--md-sys-color-shadow, #64748b) 14%, transparent);--pce-stage-bg: color-mix(in srgb, var(--md-sys-color-surface-container-lowest, #ffffff) 58%, transparent);--pce-dock-bg: color-mix(in srgb, var(--md-sys-color-surface-container, #f8fafc) 92%, transparent)}.connection-editor--dock-collapsed{grid-template-rows:auto minmax(0,1fr) 48px}.connection-editor__header{grid-column:1 / -1;display:flex;align-items:center;justify-content:space-between;gap:16px;min-height:42px;padding:8px 14px;border-radius:999px;background:var(--pce-surface);border:1px solid var(--pce-outline);box-shadow:inset 0 1px var(--pce-glass-highlight);-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.connection-editor__mode-frame{position:relative;min-width:0;flex:1 1 auto;display:flex;justify-content:center}.connection-editor__close{width:32px;height:32px;flex:0 0 auto;display:inline-grid;place-items:center;border:1px solid var(--pce-outline);border-radius:999px;color:var(--pce-text);background:var(--pce-surface-strong);box-shadow:inset 0 1px var(--pce-glass-highlight);cursor:pointer;font:inherit;font-size:1.2rem;line-height:1}.connection-editor__close:hover,.connection-editor__close:focus-visible{border-color:var(--pce-outline-strong);background:color-mix(in srgb,var(--pce-accent) 16%,var(--pce-surface-strong));outline:none}.connection-editor__mode-shell{display:inline-flex;align-items:center;gap:4px;min-width:0;padding:4px;border-radius:999px;border:1px solid var(--pce-outline);background:linear-gradient(180deg,var(--pce-surface-strong),var(--pce-surface));box-shadow:inset 0 1px var(--pce-glass-highlight),0 10px 26px var(--pce-shadow-soft);scrollbar-width:none}.connection-editor__mode-shell::-webkit-scrollbar{display:none}.connection-editor__mode{min-height:30px;display:inline-flex;align-items:center;gap:7px;padding:0 12px;border:1px solid transparent;border-radius:999px;background:transparent;color:var(--pce-muted);cursor:pointer;font-size:.76rem;font-weight:800;white-space:nowrap;transition:background .16s ease,border-color .16s ease,color .16s ease,box-shadow .16s ease}.connection-editor__mode:hover,.connection-editor__mode--active{color:var(--pce-strong);border-color:color-mix(in srgb,var(--pce-primary) 28%,transparent);background:color-mix(in srgb,var(--pce-surface-strong) 82%,transparent);box-shadow:0 0 18px color-mix(in srgb,var(--pce-primary) 12%,transparent)}.connection-editor__mode-glyph{color:var(--pce-muted);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;font-size:.72rem;font-weight:900}.connection-editor__mode--active .connection-editor__mode-glyph{color:var(--pce-primary)}.connection-editor__mode-dot{width:6px;height:6px;border-radius:999px;background:var(--pce-success);box-shadow:0 0 12px color-mix(in srgb,var(--pce-success) 70%,transparent)}.connection-editor__status-pill{display:inline-flex;align-items:center;gap:8px;min-height:24px;padding:0 12px;border-radius:999px;border:1px solid color-mix(in srgb,var(--pce-primary) 36%,transparent);background:var(--pce-surface-soft);color:var(--pce-muted);font-size:.72rem;font-weight:800;white-space:nowrap}.connection-editor__status-pill--button{cursor:pointer;transition:border-color .16s ease,background .16s ease,color .16s ease,box-shadow .16s ease}.connection-editor__status-pill--button:hover,.connection-editor__status-pill--button:focus-visible{border-color:color-mix(in srgb,var(--pce-primary) 62%,transparent);background:color-mix(in srgb,var(--pce-primary) 12%,var(--pce-surface-soft));color:var(--pce-strong);box-shadow:0 0 18px color-mix(in srgb,var(--pce-primary) 14%,transparent);outline:none}.connection-editor__status-pill span{width:6px;height:6px;border-radius:999px;background:var(--pce-primary);box-shadow:0 0 12px color-mix(in srgb,var(--pce-primary) 70%,transparent)}.connection-editor__section{display:grid;gap:10px}.connection-editor__dock{grid-column:1 / -1;grid-row:3;position:relative;z-index:4;min-height:0;display:grid;border-radius:14px;border:1px solid var(--pce-outline);background:radial-gradient(circle at 16% 0%,color-mix(in srgb,var(--pce-primary) 22%,transparent),transparent 34%),radial-gradient(circle at 78% 0%,color-mix(in srgb,var(--pce-secondary) 25%,transparent),transparent 38%),linear-gradient(90deg,var(--pce-dock-bg),var(--pce-surface-strong) 46%,color-mix(in srgb,var(--pce-secondary) 14%,var(--pce-dock-bg))),var(--pce-dock-bg);box-shadow:0 18px 58px var(--pce-shadow-soft),inset 0 1px var(--pce-glass-highlight);overflow:hidden;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.connection-editor__dock--collapsed .connection-editor__dock-tabs{border-block-end:0}.connection-editor__dock-tabs{display:flex;align-items:center;gap:10px;min-height:52px;padding:9px 14px;border-block-end:1px solid var(--pce-outline);background:linear-gradient(180deg,var(--pce-glass-highlight),transparent),color-mix(in srgb,var(--pce-surface-soft) 34%,transparent)}.connection-editor__dock-tablist{display:flex;align-items:center;gap:10px;min-width:0}.connection-editor__dock-tab,.connection-editor__dock-toggle{min-height:30px;border-radius:999px;border:1px solid transparent;background:transparent;color:var(--pce-muted);cursor:pointer;font-size:.72rem;font-weight:900;letter-spacing:.14em;text-transform:uppercase;transition:border-color .15s ease,background .15s ease,color .15s ease,box-shadow .15s ease,transform .15s ease}.connection-editor__dock-tab{display:inline-flex;align-items:center;gap:8px;padding:0 13px;white-space:nowrap}.connection-editor__dock-tab-glyph{color:var(--pce-muted);font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace;font-size:.8rem;letter-spacing:0;text-transform:none}.connection-editor__dock-tab--active{color:var(--pce-strong);border-color:var(--pce-outline-strong);background:linear-gradient(180deg,color-mix(in srgb,var(--pce-strong) 14%,transparent),color-mix(in srgb,var(--pce-strong) 6%,transparent));box-shadow:0 0 0 1px color-mix(in srgb,var(--pce-shadow) 72%,transparent),0 0 22px color-mix(in srgb,var(--pce-strong) 8%,transparent),inset 0 1px var(--pce-glass-highlight)}.connection-editor__dock-tab--active .connection-editor__dock-tab-glyph{color:var(--pce-strong)}.connection-editor__dock-count{margin-inline-start:auto;color:color-mix(in srgb,var(--pce-muted) 62%,transparent);font-size:.7rem;font-weight:800;letter-spacing:.16em;text-transform:uppercase;white-space:nowrap}.connection-editor__dock-toggle{width:32px;padding:0;border-color:var(--pce-outline);background:var(--pce-surface-soft);color:var(--pce-muted);font-size:.88rem;letter-spacing:0}.connection-editor__dock-body{max-height:154px;overflow:auto;padding:12px 12px 14px;border-block-start:1px solid var(--pce-outline);background:linear-gradient(90deg,color-mix(in srgb,var(--pce-primary) 9%,transparent),color-mix(in srgb,var(--pce-secondary) 10%,transparent) 62%,color-mix(in srgb,var(--pce-surface-strong) 18%,transparent)),color-mix(in srgb,var(--pce-surface-soft) 30%,transparent)}.connection-editor__dock-body pre{max-height:60px}.connection-editor__dock-links{display:grid;gap:8px}.connection-editor__dock-link-row{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:10px;width:100%;padding:7px 10px;border-radius:10px;border:1px solid var(--pce-outline);background:linear-gradient(90deg,var(--pce-surface-soft),color-mix(in srgb,var(--pce-secondary) 10%,var(--pce-surface-soft)) 72%,var(--pce-surface-soft)),var(--pce-surface-soft);color:var(--pce-fg);transition:border-color .15s ease,background .15s ease,box-shadow .15s ease,transform .15s ease}.connection-editor__dock-link{display:grid;grid-template-columns:minmax(116px,.52fr) minmax(300px,1.72fr) minmax(160px,.9fr) auto;align-items:center;gap:12px;width:100%;min-height:42px;padding:0;border:0;border-radius:8px;background:transparent;color:inherit;cursor:pointer;text-align:start}.connection-editor__dock-link-row:hover,.connection-editor__dock-link--active{transform:translateY(-1px);border-color:color-mix(in srgb,var(--pce-primary) 42%,transparent);background:linear-gradient(90deg,color-mix(in srgb,var(--pce-primary) 13%,transparent),color-mix(in srgb,var(--pce-secondary) 16%,transparent),var(--pce-surface-strong)),var(--pce-surface-strong);box-shadow:0 12px 32px var(--pce-shadow-soft),inset 0 0 18px color-mix(in srgb,var(--pce-primary) 6%,transparent)}.connection-editor__dock-link--active{border-color:#facc1575;box-shadow:0 12px 32px var(--pce-shadow-soft),0 0 24px #facc151f}.connection-editor__dock-link--new{border-color:#fb923ca3;background:linear-gradient(90deg,#fb923c2e,#a855f729,#0ea5e91f),#0f172ac2;box-shadow:0 12px 34px #02061752,0 0 30px #fb923c2e;animation:new-dock-link-flash 1.35s ease-out forwards}.connection-editor__dock-link--error{border-color:#ef444461}.connection-editor__dock-jump{min-width:62px;min-height:30px;padding:0 10px;border-radius:999px;border:1px solid color-mix(in srgb,var(--pce-primary) 28%,transparent);background:color-mix(in srgb,var(--pce-surface-soft) 54%,transparent);color:var(--pce-primary);cursor:pointer;font-size:.66rem;font-weight:900;letter-spacing:.06em;text-transform:uppercase;transition:border-color .15s ease,background .15s ease,color .15s ease,box-shadow .15s ease}.connection-editor__dock-jump:hover,.connection-editor__dock-jump:focus-visible{border-color:color-mix(in srgb,var(--pce-primary) 56%,transparent);background:color-mix(in srgb,var(--pce-primary) 16%,transparent);color:var(--pce-strong);box-shadow:0 0 18px color-mix(in srgb,var(--pce-primary) 18%,transparent);outline:none}.connection-editor__dock-link-kind{display:inline-flex;align-items:center;gap:7px;min-width:0;color:var(--pce-muted);font-size:.66rem;font-weight:900;letter-spacing:.1em;text-transform:uppercase;white-space:nowrap}.connection-editor__dock-link-kind span{width:8px;height:8px;border-radius:999px;background:#b46cff;box-shadow:0 0 14px #b46cff7a}.connection-editor__dock-link--state .connection-editor__dock-link-kind span{background:#ff9f2f;box-shadow:0 0 16px #ff9f2f94}.connection-editor__dock-link--state-read .connection-editor__dock-link-kind span{background:#8b7dff;box-shadow:0 0 13px #8b7dff75}.connection-editor__dock-link--projection .connection-editor__dock-link-kind span{background:#19c8e8;box-shadow:0 0 10px #19c8e857}.connection-editor__dock-link--event-propagation .connection-editor__dock-link-kind span{background:#ffb454;box-shadow:0 0 15px #ffb45485}.connection-editor__dock-link--policy .connection-editor__dock-link-kind span{outline:1px solid rgba(34,197,94,.76);outline-offset:2px;box-shadow:0 0 16px #22c55e61}.connection-editor__dock-link--policy:not(.connection-editor__dock-link--state,.connection-editor__dock-link--state-read,.connection-editor__dock-link--projection,.connection-editor__dock-link--event-propagation) .connection-editor__dock-link-kind span{background:#22c55e}.connection-editor__dock-link-route{display:inline-flex;align-items:center;gap:8px;min-width:0}.connection-editor__dock-link-chip{min-width:0;overflow:hidden;padding:4px 8px;border-radius:999px;border:1px solid var(--pce-outline);background:color-mix(in srgb,var(--pce-surface-soft) 72%,transparent);color:var(--pce-fg);font-size:.72rem;font-weight:900;text-overflow:ellipsis;white-space:nowrap}.connection-editor__dock-link-chip--from{border-color:#ff9f2f66;color:#ffbd62}.connection-editor__dock-link-chip--to{border-color:#19c8e83d;color:#7dddeb}:host-context(.theme-light) .connection-editor__dock-link-chip--from{border-color:color-mix(in srgb,var(--pce-tertiary) 46%,transparent);color:#8a4600}:host-context(.theme-light) .connection-editor__dock-link-chip--to{border-color:color-mix(in srgb,var(--pce-primary) 34%,transparent);color:#075f73}.connection-editor__dock-link-arrow{flex:0 0 auto;color:var(--pce-muted);font-weight:900}.connection-editor__dock-link-id{min-width:0;overflow:hidden;color:var(--pce-muted);font-size:.66rem;font-weight:800;font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace;text-overflow:ellipsis;white-space:nowrap}.connection-editor__dock-link-badges{display:inline-flex;justify-content:flex-end;gap:5px;min-width:70px}.connection-editor__dock-link-badges span{padding:3px 7px;border-radius:999px;background:#a855f724;color:#d8b4fe;font-size:.62rem;font-weight:900;text-transform:uppercase;white-space:nowrap}:host-context(.theme-light) .connection-editor__dock-link-badges span{background:color-mix(in srgb,var(--pce-secondary) 14%,var(--pce-surface-soft));color:#6d2a9f}.connection-editor__dock-empty{padding:12px;border-radius:12px;border:1px dashed var(--pce-outline);color:var(--pce-muted);font-size:.76rem;font-weight:700}.connection-editor__inspector{border-color:color-mix(in srgb,var(--pce-primary) 26%,transparent);background:linear-gradient(135deg,color-mix(in srgb,var(--pce-primary) 10%,transparent),color-mix(in srgb,var(--pce-secondary) 8%,transparent)),var(--pce-surface)}.connection-editor__eyebrow,.connection-editor__section-title{font-size:.72rem;text-transform:uppercase;letter-spacing:.16em;color:var(--pce-muted);font-weight:700}.connection-editor__title{font-size:1.02rem;font-weight:800}.connection-editor__subtitle,.connection-editor__item-copy,.connection-editor__item-meta,.connection-editor__details-row span,.connection-editor__details-label,.connection-editor__empty{color:var(--pce-muted);font-size:.84rem}.connection-editor__inspector-heading{display:grid;gap:4px;padding:10px 12px;border-radius:14px;background:var(--pce-surface-soft);border:1px solid var(--pce-outline)}.connection-editor__inspector-heading strong{color:var(--pce-strong);font-size:.96rem;overflow-wrap:anywhere}.connection-editor__inspector-heading span{color:var(--pce-primary);font-size:.76rem;font-weight:800;text-transform:uppercase;letter-spacing:.1em}.connection-editor__human-summary{display:grid;gap:6px;padding:12px;border-radius:14px;border:1px solid color-mix(in srgb,var(--pce-primary) 24%,transparent);background:linear-gradient(135deg,color-mix(in srgb,var(--pce-primary) 12%,transparent),color-mix(in srgb,var(--pce-secondary) 8%,transparent)),var(--pce-surface-soft);color:var(--pce-fg);line-height:1.35}.connection-editor__nested-summary{display:grid;gap:6px;padding:10px;border-radius:12px;border:1px solid color-mix(in srgb,var(--pce-primary) 22%,transparent);background:color-mix(in srgb,var(--pce-primary) 8%,var(--pce-surface-soft))}.connection-editor__nested-summary strong{color:var(--pce-strong);font-size:.82rem}.connection-editor__nested-summary span:last-child{color:var(--pce-primary);font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace;font-size:.72rem;overflow-wrap:anywhere}.connection-editor__recommended{border-color:color-mix(in srgb,var(--pce-secondary) 32%,transparent);background:linear-gradient(135deg,color-mix(in srgb,var(--pce-secondary) 14%,transparent),color-mix(in srgb,var(--pce-primary) 10%,transparent)),var(--pce-surface)}.connection-editor__recommended-card{display:grid;gap:8px;width:100%;padding:13px;border-radius:16px;border:1px solid color-mix(in srgb,var(--pce-secondary) 36%,transparent);background:linear-gradient(135deg,color-mix(in srgb,var(--pce-secondary) 16%,transparent),color-mix(in srgb,var(--pce-primary) 10%,transparent)),var(--pce-surface-strong);color:var(--pce-fg);text-align:start;cursor:pointer;transition:transform .16s ease,border-color .16s ease,background .16s ease}.connection-editor__recommended-card:hover{transform:translateY(-1px);border-color:color-mix(in srgb,var(--pce-primary) 52%,transparent);background:linear-gradient(135deg,color-mix(in srgb,var(--pce-secondary) 22%,transparent),color-mix(in srgb,var(--pce-primary) 14%,transparent)),var(--pce-surface-strong)}.connection-editor__recommended-card strong{color:var(--pce-strong);font-size:.92rem;line-height:1.28}.connection-editor__recommended-card span:not(.connection-editor__human-summary-label):not(.connection-editor__suggestion-action),.connection-editor__recommended-card small{color:var(--pce-muted);font-size:.76rem;line-height:1.35}.connection-editor__success{display:grid;gap:4px;padding:10px 12px;border-radius:14px;border:1px solid rgba(34,197,94,.34);background:#22c55e1f;color:var(--pce-fg);line-height:1.35}.connection-editor__success strong{color:#bbf7d0;font-size:.78rem;text-transform:uppercase;letter-spacing:.08em}.connection-editor__success span{color:var(--pce-strong);font-size:.82rem}.connection-editor__human-summary-label,.connection-editor__suggestion-intent{justify-self:start;padding:3px 8px;border-radius:999px;background:color-mix(in srgb,var(--pce-primary) 16%,transparent);color:var(--pce-fg);font-size:.66rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}.connection-editor__human-summary strong{color:var(--pce-strong);font-size:.88rem}.connection-editor__human-summary span:last-child,.connection-editor__suggestion-preview{color:var(--pce-muted);font-size:.76rem;line-height:1.35}.connection-editor__technical-details{display:grid;gap:10px;padding:10px 12px;border-radius:14px;border:1px solid var(--pce-outline);background:var(--pce-surface-soft)}.connection-editor__technical-details summary{cursor:pointer;color:var(--pce-muted);font-size:.78rem;font-weight:800}.connection-editor__technical-details[open] summary{margin-bottom:10px;color:var(--pce-strong)}.connection-editor__inspector-ports{display:flex;flex-wrap:wrap;gap:6px}.connection-editor__inspector-port{max-width:100%;padding:4px 8px;border-radius:999px;border:1px solid rgba(148,163,184,.16);background:#0f172a94;color:#cbd5e1;font-size:.68rem;font-weight:800;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.connection-editor__inspector-port--input{border-color:#22d3ee57;color:#67e8f9}.connection-editor__inspector-port--output{border-color:#f59e0b57;color:#fbbf24}.connection-editor__inspector-port--state{border-color:#a855f761;color:#d8b4fe}.connection-editor__segment-kind-legend{display:grid;gap:10px;padding:12px;border-radius:16px;border:1px solid rgba(148,163,184,.14);background:linear-gradient(135deg,#0f172aa8,#080d1885),#02061757}.connection-editor__segment-kind-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}.connection-editor__segment-kind-pill{min-width:0;display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:28px;padding:0 8px;border-radius:999px;border:1px solid rgba(148,163,184,.14);background:#0f172a94;color:#cbd5e1;font-size:.68rem;font-weight:900;overflow:hidden;white-space:nowrap}.connection-editor__segment-kind-pill span{width:8px;height:8px;flex:0 0 auto;border-radius:999px;background:currentColor;box-shadow:0 0 12px currentColor}.connection-editor__segment-kind-pill--input{border-color:#19c8e83d;color:#7dddeb}.connection-editor__segment-kind-pill--output{border-color:#ff9f2f61;color:#ffbd62}.connection-editor__segment-kind-pill--state{border-color:#b46cff61;color:#d9b8ff}:host-context(.theme-light) .connection-editor__segment-kind-pill{border-color:var(--pce-outline);background:color-mix(in srgb,var(--pce-surface-strong) 88%,transparent)}:host-context(.theme-light) .connection-editor__segment-kind-pill--input{color:#075f73}:host-context(.theme-light) .connection-editor__segment-kind-pill--output{color:#8a4600}:host-context(.theme-light) .connection-editor__segment-kind-pill--state{color:#6d2a9f}.connection-editor__stage{grid-column:1;grid-row:2;position:relative;min-height:0;height:100%;overflow:visible;border-radius:16px;border:1px solid var(--pce-outline);background:radial-gradient(circle at center,color-mix(in srgb,var(--pce-secondary) 7%,transparent),transparent 38%),radial-gradient(circle,var(--pce-grid) 1px,transparent 1.5px),var(--pce-stage-bg);background-size:auto,31px 31px,auto;box-shadow:inset 0 0 0 1px var(--pce-glass-highlight);cursor:grab;touch-action:none}.connection-editor__stage--panning,.connection-editor__stage:active{cursor:grabbing}.connection-editor__stage--dragging{cursor:crosshair}.connection-editor__stage--rotating{cursor:grabbing}.connection-editor__viewport{position:absolute;inset-block-start:0;inset-inline-start:0;width:900px;height:540px;overflow:visible;transform-origin:0 0;transition:transform .14s ease}.connection-editor__wires{position:absolute;inset:0;width:100%;height:100%;overflow:visible}.connection-editor__wire,.connection-editor__wire-hit{fill:none;stroke-linecap:round}.connection-editor__wire{stroke:#b46cff;stroke-width:1.45;stroke-opacity:.48;pointer-events:none;filter:url(#connection-glow);stroke-dasharray:10 8;animation:connection-flow 1.65s linear infinite}.connection-editor__wire-hit{stroke:transparent;stroke-width:24;cursor:pointer;pointer-events:stroke}.connection-editor__inspector-header{display:flex;align-items:center;justify-content:space-between;gap:12px}.connection-editor__wire-hit--active{stroke-width:24}.connection-editor__wire--halo{stroke:#b46cff1f;stroke-width:7;stroke-dasharray:none;animation:none}.connection-editor__wire--state{stroke:#ff9f2f;stroke-width:2.25;stroke-dasharray:8 7;animation:connection-flow .92s linear infinite}.connection-editor__wire--state.connection-editor__wire--halo{stroke:#ff9f2f29;stroke-width:11}.connection-editor__wire--projection{stroke:#15b8d8;stroke-width:1.75;stroke-dasharray:20 8;animation:connection-flow 1.9s linear infinite}.connection-editor__wire--projection.connection-editor__wire--halo{stroke:#15b8d81a;stroke-width:7}.connection-editor__wire--state-read{stroke:#8b7dff;stroke-width:1.65;stroke-dasharray:3 11;animation:connection-flow 2.25s linear infinite reverse}.connection-editor__wire--state-read.connection-editor__wire--halo{stroke:#8b7dff21;stroke-width:8}.connection-editor__wire--event-propagation{stroke:#ffb454;stroke-width:2.05;stroke-dasharray:5 7;animation:connection-flow .78s linear infinite}.connection-editor__wire--event-propagation.connection-editor__wire--halo{stroke:#ffb4542e;stroke-width:10}.connection-editor__wire--policy{filter:drop-shadow(0 0 6px rgba(34,197,94,.2)) url(#connection-glow)}.connection-editor__wire--policy.connection-editor__wire--halo{stroke:#22c55e1f;stroke-width:13}.connection-editor__wire--warning{stroke:#ff9f2f}.connection-editor__wire--warning.connection-editor__wire--halo{stroke:#ff9f2f2e}.connection-editor__wire--error{stroke:#ef4444}.connection-editor__wire--error.connection-editor__wire--halo{stroke:#ef44442e}.connection-editor__wire--active{stroke:#d9b8ff;stroke-width:3;stroke-opacity:1;stroke-dasharray:5 7;animation-duration:.58s}.connection-editor__wire--halo.connection-editor__wire--active{stroke-width:16;stroke-opacity:.8;animation:selected-wire-aura 1.35s ease-in-out infinite}.connection-editor__wire--new{stroke-width:3.4;stroke-opacity:1;stroke-dasharray:640;stroke-dashoffset:640;animation:wire-draw-in .62s cubic-bezier(.16,1,.3,1) forwards,connection-flow 1.05s linear .62s infinite}.connection-editor__wire--halo.connection-editor__wire--new{stroke-width:22;stroke-opacity:0;animation:new-wire-halo 1.45s ease-out forwards}.connection-editor__wire--trace{stroke:#9b5cff;stroke-width:3.2;stroke-opacity:1;stroke-dasharray:7 6;animation:connection-flow .72s linear infinite,trace-wire-pulse 1.18s ease-in-out infinite}.connection-editor__wire--halo.connection-editor__wire--trace{stroke:#9b5cff57;stroke-width:18;stroke-opacity:.86;animation:trace-halo-pulse 1.18s ease-in-out infinite}.connection-editor__wire--mode-dimmed{stroke-opacity:.14;stroke-width:1.05;filter:none;animation-duration:2.6s}.connection-editor--guided .connection-editor__wire:not(.connection-editor__wire--active,.connection-editor__wire--new,.connection-editor__wire--trace){stroke-opacity:.85;filter:none;animation:none}.connection-editor--guided .connection-editor__wire--halo:not(.connection-editor__wire--active,.connection-editor__wire--new,.connection-editor__wire--trace){stroke-opacity:.08;animation:none}.connection-editor__wire--halo.connection-editor__wire--mode-dimmed{stroke-opacity:.05;stroke-width:5;animation:none}.connection-editor__wire-hit--mode-dimmed{stroke-width:12}.connection-editor__wire-endpoint{fill:#f8fafc;stroke:#0f172ae6;stroke-width:1.5;filter:drop-shadow(0 0 8px rgba(216,180,254,.8));pointer-events:none;animation:endpoint-pulse 1.2s ease-in-out infinite}.connection-editor__wire-endpoint--source{fill:#ff9f2f}.connection-editor__wire-endpoint--target{fill:#19c8e8}.connection-editor__connection-tooltip{position:absolute;z-index:6;width:min(312px,calc(100% - 24px));display:grid;gap:8px;padding:12px 14px;border-radius:14px;border:1px solid var(--pce-outline);background:linear-gradient(135deg,var(--pce-surface-strong),var(--pce-surface)),var(--pce-surface-strong);box-shadow:0 18px 44px var(--pce-shadow-soft),inset 0 1px var(--pce-glass-highlight);color:var(--pce-fg);pointer-events:none;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px);animation:tooltip-rise .14s ease-out}.connection-editor__connection-tooltip-callout{position:absolute;inset-block-start:100%;inset-inline-start:var(--connection-tooltip-callout-x, 42px);width:2px;height:28px;border-radius:999px;background:linear-gradient(180deg,#e2e8f0bd,#19c8e800);box-shadow:0 0 12px #19c8e852;transform:translate(-1px);pointer-events:none}.connection-editor__connection-tooltip-callout:before,.connection-editor__connection-tooltip-callout:after{content:\"\";position:absolute;border-radius:999px;pointer-events:none}.connection-editor__connection-tooltip-callout:before{inset-block-start:-5px;inset-inline-start:-5px;width:12px;height:12px;border:1px solid rgba(148,163,184,.24);background:#080d18f5;box-shadow:inset 0 1px #ffffff14}.connection-editor__connection-tooltip-callout:after{inset-block-end:-3px;inset-inline-start:-4px;width:10px;height:10px;background:#19c8e8;box-shadow:0 0 16px #19c8e88f}.connection-editor__connection-tooltip--projection{border-color:#19c8e833;background:linear-gradient(135deg,#15b8d81f,#0f172ae0),#070c16eb}.connection-editor__connection-tooltip--state{border-color:#ff9f2f66;background:linear-gradient(135deg,#ff9f2f3d,#0f172ae0),#070c16eb}.connection-editor__connection-tooltip--state .connection-editor__connection-tooltip-callout{background:linear-gradient(180deg,#ffbd62d1,#ff9f2f00);box-shadow:0 0 14px #ff9f2f61}.connection-editor__connection-tooltip--state .connection-editor__connection-tooltip-callout:after{background:#ff9f2f;box-shadow:0 0 18px #ff9f2fad}.connection-editor__connection-tooltip--error{border-color:#f871716b}.connection-editor__connection-tooltip--error .connection-editor__connection-tooltip-callout:after{background:#fb7185;box-shadow:0 0 18px #fb7185ad}.connection-editor__connection-tooltip-kind{display:inline-flex;align-items:center;gap:8px;color:#94a3b8;font-size:.62rem;font-weight:900;letter-spacing:.16em;text-transform:uppercase}.connection-editor__connection-tooltip-kind span{width:8px;height:8px;border-radius:999px;background:#19c8e8;box-shadow:0 0 12px #19c8e880}.connection-editor__connection-tooltip--state .connection-editor__connection-tooltip-kind span{background:#ff9f2f;box-shadow:0 0 16px #ff9f2fc7}.connection-editor__connection-tooltip-row{min-width:0;display:grid;grid-template-columns:54px minmax(0,1fr);align-items:center;gap:8px;min-height:28px;padding:0 10px;border-radius:999px;background:#0f172a94}.connection-editor__connection-tooltip-row span{color:#8d9bb0;font-size:.62rem;font-weight:900;letter-spacing:.12em;text-transform:uppercase}.connection-editor__connection-tooltip-row strong{min-width:0;overflow:hidden;color:#e2e8f0;font-size:.75rem;font-weight:900;text-overflow:ellipsis;white-space:nowrap}.connection-editor__connection-tooltip-badges{display:flex;flex-wrap:wrap;gap:6px}.connection-editor__connection-tooltip-badges span{padding:3px 8px;border-radius:999px;background:#02061799;color:#fbbf24;font-size:.66rem;font-weight:900}.connection-editor__trace-overlay{position:absolute;z-index:6;inset-block-start:16px;inset-inline-start:50%;width:min(560px,calc(100% - 360px));min-width:320px;padding:12px 14px;border-radius:16px;border:1px solid rgba(34,197,94,.26);background:linear-gradient(135deg,#22c55e1f,#0ea5e91a),#080d18f0;box-shadow:0 22px 56px #02061775,0 0 42px #22c55e1f;transform:translate(-50%);pointer-events:auto;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px);animation:tooltip-rise .16s ease-out}.connection-editor__trace-overlay-head,.connection-editor__trace-overlay-route{display:flex;align-items:center;justify-content:space-between;gap:12px;min-width:0}.connection-editor__trace-overlay-head span,.connection-editor__trace-overlay-route span{color:#86efac;font-size:.66rem;font-weight:900;letter-spacing:.12em;text-transform:uppercase;white-space:nowrap}.connection-editor__trace-overlay-head strong{color:#cbd5e1;font-size:.7rem;font-weight:900;font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace}.connection-editor__trace-overlay-route{margin-top:6px}.connection-editor__trace-overlay-controls{display:grid;grid-template-columns:28px minmax(0,1fr) 28px;align-items:center;gap:8px;margin-top:10px}.connection-editor__trace-overlay-button{width:28px;height:24px;display:inline-grid;place-items:center;border:1px solid rgba(148,163,184,.18);border-radius:999px;background:#0206176b;color:#e2e8f0;cursor:pointer;font-size:1rem;font-weight:900;line-height:1;transition:border-color .15s ease,background .15s ease,color .15s ease,opacity .15s ease}.connection-editor__trace-overlay-button:hover:not(:disabled){border-color:#22d3ee6b;background:#0ea5e929;color:#f8fafc}.connection-editor__trace-overlay-button:disabled,.connection-editor__trace-overlay-button[aria-disabled=true]{cursor:default;opacity:.36}.connection-editor__trace-overlay-steps{min-width:0;display:flex;align-items:center;gap:6px;overflow-x:auto;padding:2px;scrollbar-width:none}.connection-editor__trace-overlay-steps::-webkit-scrollbar{display:none}.connection-editor__trace-overlay-step{position:relative;flex:1 0 28px;min-width:28px;height:22px;border:1px solid rgba(148,163,184,.16);border-radius:999px;background:#0f172a9e;color:#94a3b8;cursor:pointer;font-size:.62rem;font-weight:900;font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace;transition:border-color .15s ease,background .15s ease,color .15s ease,box-shadow .15s ease}.connection-editor__trace-overlay-step--active{border-color:#22d3eea3;background:linear-gradient(90deg,#22d3ee3d,#9b5cff33);color:#f8fafc;box-shadow:0 0 18px #22d3ee33}.connection-editor__trace-overlay-step--blocked{border-color:#fb71855c;color:#fecdd3}.connection-editor__trace-overlay-route strong{min-width:0;overflow:hidden;color:#f8fafc;font-size:.82rem;font-weight:900;text-overflow:ellipsis;white-space:nowrap}.connection-editor__trace-overlay-bar{position:relative;height:5px;margin-top:10px;overflow:hidden;border-radius:999px;background:#94a3b829}.connection-editor__trace-overlay-bar span{position:absolute;inset-block:0;inset-inline-start:0;border-radius:inherit;background:linear-gradient(90deg,#22c55e,#22d3ee);box-shadow:0 0 18px #22c55e61;min-width:8%}.connection-editor__drag-wire{fill:none;stroke:#94a3b8;stroke-width:2.4;stroke-linecap:round;stroke-dasharray:6 7;pointer-events:none;filter:url(#connection-glow);animation:connection-flow 1.05s linear infinite,drag-wire-pulse 1.2s ease-in-out infinite}.connection-editor__drag-wire--halo{stroke:#94a3b829;stroke-width:14;stroke-dasharray:none;animation:drag-halo-pulse 1.2s ease-in-out infinite}.connection-editor__drag-wire--valid{stroke:#22c55e}.connection-editor__drag-wire--halo.connection-editor__drag-wire--valid{stroke:#22c55e47}.connection-editor__drag-endpoint{fill:#94a3b8;stroke:#020617eb;stroke-width:2;pointer-events:none;filter:url(#connection-glow);transform-box:fill-box;transform-origin:center;animation:endpoint-pulse 1s ease-in-out infinite}.connection-editor__drag-endpoint--source{fill:#ff9f2f}.connection-editor__drag-endpoint--target{fill:#7dddeb}.connection-editor__drag-endpoint--valid{fill:#22c55e;stroke:#bbf7d0e0}.connection-editor__wire-handle-line{stroke-width:2;stroke-linecap:round;stroke-dasharray:4 5;pointer-events:none;opacity:.88;filter:url(#connection-glow)}.connection-editor__wire-handle-line--source{stroke:#ff9f2ff0}.connection-editor__wire-handle-line--target{stroke:#19c8e8c2}.connection-editor__wire-control{fill:#0f172af5;stroke:#cbd5e1c7;stroke-width:1.8;pointer-events:none;filter:url(#connection-glow)}.connection-editor__wire-label{pointer-events:none;filter:drop-shadow(0 0 10px rgba(2,6,23,.44));animation:tooltip-rise .14s ease-out}.connection-editor__wire-label rect{fill:#080d18eb;stroke-width:1}.connection-editor__wire-label text{fill:#e2e8f0;font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace;font-size:8px;font-weight:900;letter-spacing:.08em;text-transform:uppercase}.connection-editor__wire-label--source rect{stroke:#ff9f2fad}.connection-editor__wire-label--target rect{stroke:#19c8e86b}.connection-editor__zoom-controls{position:absolute;inset-inline-start:14px;inset-block-start:14px;z-index:4;display:grid;grid-template-columns:max-content;gap:6px;width:46px;pointer-events:auto}.connection-editor__tool-group,.connection-editor__trace-source{display:grid;gap:6px;padding:6px;border-radius:12px;border:1px solid var(--pce-outline);background:linear-gradient(180deg,var(--pce-surface-strong),var(--pce-surface));box-shadow:0 16px 36px var(--pce-shadow-soft),inset 0 1px var(--pce-glass-highlight);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px)}.connection-editor__tool-group{grid-template-columns:30px;width:max-content}.connection-editor__tool-group:nth-of-type(2){grid-template-columns:30px}.connection-editor__arrange-button{width:34px;min-height:34px;display:inline-flex;align-items:center;justify-content:center;gap:0;padding:0;border-radius:999px;border:1px solid rgba(168,85,247,.34);background:linear-gradient(90deg,#a855f72e,#22d3ee1f);color:#e9d5ff;font-size:0;font-weight:800;letter-spacing:0;cursor:pointer;opacity:1;transform:scale(1);transition:opacity .14s ease,transform .14s ease,border-color .14s ease,background .14s ease}.connection-editor--guided .connection-editor__arrange-button{opacity:.34;transform:scale(.9);border-color:color-mix(in srgb,var(--pce-outline) 82%,transparent);background:color-mix(in srgb,var(--pce-surface-soft) 84%,transparent)}.connection-editor--guided .connection-editor__zoom-controls:hover .connection-editor__arrange-button,.connection-editor--guided .connection-editor__zoom-controls:focus-within .connection-editor__arrange-button,.connection-editor--guided .connection-editor__arrange-button:hover,.connection-editor--guided .connection-editor__arrange-button:focus-visible,.connection-editor--guided .connection-editor__arrange-button--active{opacity:1;transform:scale(1);border-color:#a855f757;background:linear-gradient(90deg,#a855f72e,#22d3ee1f)}.connection-editor__arrange-button span{color:#c084fc;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;font-size:.78rem;font-weight:900}.connection-editor__tool-button{width:30px;height:30px;border-radius:999px;border:1px solid var(--pce-outline);background:var(--pce-surface-soft);color:var(--pce-fg);font-size:.9rem;font-weight:800;letter-spacing:.02em;cursor:pointer;line-height:1}.connection-editor__tool-button:hover:not(:disabled):not([aria-disabled=true]){border-color:color-mix(in srgb,var(--pce-primary) 42%,transparent);color:var(--pce-primary);box-shadow:0 0 18px color-mix(in srgb,var(--pce-primary) 16%,transparent)}.connection-editor__tool-button[aria-disabled=true]{cursor:default;opacity:.42}.connection-editor__history-button:disabled{cursor:not-allowed;opacity:.42}.connection-editor__arrange-button--active{border-color:#22d3ee94;background:linear-gradient(90deg,#0ea5e93d,#22d3ee2e);color:#cffafe}.connection-editor__trace-source{gap:4px;max-height:230px;overflow:auto}.connection-editor__trace-source-title{padding:2px 4px 6px;color:var(--pce-muted);font-size:.64rem;font-weight:900;letter-spacing:.18em;text-transform:uppercase}.connection-editor__trace-source-item{min-width:0;min-height:30px;display:flex;align-items:center;gap:8px;padding:0 8px;border:0;border-radius:8px;background:transparent;color:var(--pce-fg);cursor:pointer;font-size:.74rem;font-weight:800;text-align:start}.connection-editor__trace-source-item span{color:#f59e0b;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;font-size:.72rem;font-weight:900}.connection-editor__trace-source-item:hover,.connection-editor__trace-source-item--active{background:var(--pce-surface-strong);color:var(--pce-strong);box-shadow:inset 3px 0 #f59e0bd1}.connection-editor__minimap{position:absolute;inset-inline-end:14px;inset-block-end:14px;z-index:4;width:184px;height:124px;padding:18px 10px 10px;overflow:hidden;border-radius:16px;border:1px solid color-mix(in srgb,var(--pce-primary) 30%,transparent);background:linear-gradient(135deg,color-mix(in srgb,var(--pce-primary) 12%,transparent),var(--pce-surface-strong)),var(--pce-surface);box-shadow:0 20px 48px var(--pce-shadow-soft),inset 0 1px var(--pce-glass-highlight);cursor:pointer;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);opacity:1;transform:scale(1);transform-origin:100% 100%;transition:opacity .16s ease,transform .16s ease,border-color .16s ease,box-shadow .16s ease}.connection-editor--guided .connection-editor__minimap{opacity:.54;transform:scale(.88);border-color:color-mix(in srgb,var(--pce-outline) 64%,transparent);box-shadow:0 14px 34px color-mix(in srgb,var(--pce-shadow-soft) 70%,transparent),inset 0 1px var(--pce-glass-highlight)}.connection-editor--guided .connection-editor__minimap:hover,.connection-editor--guided .connection-editor__minimap:focus-visible,.connection-editor--guided .connection-editor__minimap--panning{opacity:1;transform:scale(1)}.connection-editor__minimap:hover,.connection-editor__minimap--panning{border-color:#22d3ee75;box-shadow:0 20px 48px var(--pce-shadow-soft),0 0 24px color-mix(in srgb,var(--pce-primary) 14%,transparent),inset 0 1px var(--pce-glass-highlight)}.connection-editor__minimap--panning{cursor:grabbing}.connection-editor__minimap:before{content:\"\";position:absolute;inset:22px 10px 10px;border-radius:10px;background-image:radial-gradient(circle,rgba(125,211,252,.18) 1px,transparent 1px);background-size:22px 22px;opacity:.58;pointer-events:none}.connection-editor__minimap-title{position:absolute;inset-block-start:7px;inset-inline-start:12px;color:var(--pce-muted);font-size:.6rem;font-weight:900;letter-spacing:.16em;text-transform:uppercase}.connection-editor__minimap svg{position:relative;width:100%;height:100%;display:block;overflow:visible}.connection-editor__minimap-wire{fill:none;stroke:#b46cff8f;stroke-width:4;stroke-linecap:round;opacity:.86}.connection-editor__minimap-wire--state{stroke:#ff9f2fdb}.connection-editor__minimap-wire--state-read{stroke:#8b7dffb8;stroke-dasharray:4 9}.connection-editor__minimap-wire--projection{stroke:#19c8e88a;stroke-dasharray:18 8}.connection-editor__minimap-wire--event-propagation{stroke:#ffb454cc;stroke-dasharray:6 7}.connection-editor__minimap-wire--policy{filter:drop-shadow(0 0 6px rgba(34,197,94,.28))}.connection-editor__minimap-wire--trace{stroke:#9b5cffe0;stroke-width:5;stroke-dasharray:12 9;animation:connection-flow 1s linear infinite}.connection-editor__minimap-wire--active{stroke:#facc15eb;stroke-width:6;filter:drop-shadow(0 0 8px rgba(250,204,21,.38))}.connection-editor__minimap-wire--mode-dimmed{opacity:.2;stroke-width:2.6;filter:none}.connection-editor__minimap-node{fill:#f59e0be0;stroke:#020617db;stroke-width:4;filter:drop-shadow(0 0 7px rgba(245,158,11,.24))}.connection-editor__minimap-node--state{fill:#a855f7e6;filter:drop-shadow(0 0 8px rgba(168,85,247,.34))}.connection-editor__minimap-node--selected{fill:#facc15f5;stroke:#fef08aeb;filter:drop-shadow(0 0 10px rgba(250,204,21,.42))}.connection-editor__minimap-node--trace-source{fill:#facc15f5;filter:drop-shadow(0 0 10px rgba(250,204,21,.44))}.connection-editor__minimap-node--trace-reacting{fill:#22c55eeb;filter:drop-shadow(0 0 10px rgba(34,197,94,.42))}.connection-editor__minimap-node--mode-dimmed{opacity:.28;filter:none}.connection-editor__minimap-viewport{fill:#7dd3fc1f;stroke:#7dd3fce6;stroke-width:5;stroke-dasharray:18 10;filter:drop-shadow(0 0 10px rgba(125,211,252,.3));transition:x .12s ease,y .12s ease,width .12s ease,height .12s ease}.connection-editor__minimap--panning .connection-editor__minimap-viewport{fill:#22d3ee2e;stroke:#22d3eef5;stroke-dasharray:none}.connection-editor__node{position:absolute;z-index:10;width:168px;height:168px;transform:translate(-84px,-84px);border:0;padding:0;background:transparent;color:inherit;cursor:pointer;transition:filter .16s ease}.connection-editor__node--dragging{cursor:grabbing;filter:drop-shadow(0 0 22px rgba(34,211,238,.26));z-index:12}.connection-editor__node--connection-focus{filter:drop-shadow(0 0 16px rgba(34,211,238,.2))}.connection-editor__node--compat-target .connection-editor__node-ring{box-shadow:0 0 0 2px #22c55e66,0 0 0 18px #22c55e0b,0 0 32px #22c55e29}.connection-editor__node--mode-dimmed{opacity:.42;filter:grayscale(.22) saturate(.56)}.connection-editor__node--quiet{opacity:.58;filter:saturate(.68) brightness(.86)}.connection-editor__node--mode-dimmed .connection-editor__node-ring{animation:none;box-shadow:0 0 0 1px #2dd4bf14,0 0 14px #0ea5e90f}.connection-editor__node--mode-dimmed .connection-editor__node-halo{stroke-opacity:.28}.connection-editor__node-ring,.connection-editor__node-core,.connection-editor__node-svg{position:absolute;inset:0}.connection-editor__node-ring,.connection-editor__node-core{display:grid;place-items:center;clip-path:polygon(30% 0,70% 0,100% 30%,100% 70%,70% 100%,30% 100%,0 70%,0 30%)}.connection-editor__node-ring{background:radial-gradient(circle at center,transparent 0 44%,rgba(7,17,31,.96) 45% 57%,transparent 58%),conic-gradient(from -28deg,#06b6d4b3 0 28%,#f59e0b9e 28% 48%,#a855f780 48% 68%,#06b6d4a3 68% 100%);box-shadow:0 0 0 1px #2dd4bf2e,0 0 26px #0ea5e929;animation:node-breathe 2.8s ease-in-out infinite}.connection-editor__node-svg{overflow:visible;filter:drop-shadow(0 0 16px rgba(14,165,233,.16))}.connection-editor__node-halo{fill:transparent;stroke:#38bdf838;stroke-width:1;stroke-dasharray:0;pointer-events:none}.connection-editor__node-halo:nth-child(2){stroke:#38bdf824}.connection-editor__node-halo:nth-child(3){stroke:#94a3b81a}.connection-editor__node-segment{fill-opacity:.28;stroke-opacity:.72;stroke-width:1.15;transition:fill-opacity .16s ease,stroke-opacity .16s ease,stroke-width .16s ease;cursor:grab}.connection-editor__node:hover .connection-editor__node-segment,.connection-editor__node--selected .connection-editor__node-segment,.connection-editor__node--connection-focus .connection-editor__node-segment{fill-opacity:.54;stroke-opacity:.95;stroke-width:1.55}.connection-editor__node-segment--connection-focus{fill-opacity:.76;stroke-opacity:1;stroke-width:2;filter:drop-shadow(0 0 8px rgba(34,211,238,.76));animation:segment-focus-sweep 1.45s ease-in-out infinite}.connection-editor__node-segment--source{fill-opacity:.86;stroke-opacity:1;stroke-width:2.45;filter:drop-shadow(0 0 12px rgba(251,146,60,.82))}.connection-editor__node-segment--compatible{fill-opacity:.74;stroke-opacity:1;stroke-width:2;filter:drop-shadow(0 0 10px rgba(34,197,94,.72));animation:snap-pulse 1s ease-in-out infinite}.connection-editor__node-segment--compat-preview{fill-opacity:.86;stroke-opacity:1;stroke-width:2.45;filter:drop-shadow(0 0 13px rgba(245,158,11,.78))}.connection-editor__node-segment--candidate{fill-opacity:.92;stroke-opacity:1;stroke-width:2.8;filter:drop-shadow(0 0 16px rgba(34,197,94,.86))}.connection-editor__node-segment--incompatible{fill-opacity:.12;stroke-opacity:.28;cursor:not-allowed}.connection-editor__node-segment--invalid-drop{fill-opacity:.72;stroke:#fb7185;stroke-opacity:1;stroke-width:3;filter:drop-shadow(0 0 16px rgba(244,63,94,.84));animation:invalid-drop-jolt .36s ease-out}.connection-editor__node-segment--state{fill-opacity:.44;filter:drop-shadow(0 0 8px rgba(180,108,255,.2))}.connection-editor__node-segment--mode-dimmed{fill-opacity:.08;stroke-opacity:.2;stroke-width:.9;filter:none}.connection-editor__node-anchor{opacity:.95;filter:drop-shadow(0 0 6px currentColor)}.connection-editor__node-rotate-handle{cursor:grab;opacity:0;pointer-events:none;transition:opacity .16s ease,filter .16s ease;filter:drop-shadow(0 0 8px rgba(34,211,238,.28))}.connection-editor__node:hover .connection-editor__node-rotate-handle,.connection-editor__node--selected .connection-editor__node-rotate-handle,.connection-editor__node--rotating .connection-editor__node-rotate-handle{opacity:1;pointer-events:auto}.connection-editor__node--rotating .connection-editor__node-rotate-handle{cursor:grabbing;filter:drop-shadow(0 0 12px rgba(34,211,238,.54))}.connection-editor__node-rotate-hit{fill:transparent}.connection-editor__node-rotate-dot{fill:var(--pce-surface-strong);stroke:var(--pce-primary);stroke-opacity:.88;stroke-width:1.4}.connection-editor__node-rotate-arc,.connection-editor__node-rotate-arrow{fill:none;stroke:var(--pce-primary);stroke-linecap:round;stroke-linejoin:round;stroke-opacity:.96;stroke-width:1.2;pointer-events:none}.connection-editor__segment-icon,.connection-editor__segment-label{pointer-events:none;fill:#cbd5e1;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;font-weight:800;paint-order:stroke;stroke:#020617c2;stroke-width:3px;stroke-linejoin:round}.connection-editor__segment-icon{font-size:12px;fill:#19c8e8;opacity:1;transition:opacity .14s ease}.connection-editor__segment-icon--output{fill:#ff9f2f}.connection-editor--guided .connection-editor__segment-icon:not(.connection-editor__segment-icon--guided-visible){opacity:0}.connection-editor--guided .connection-editor__node--rotating .connection-editor__segment-icon{opacity:0}.connection-editor__segment-label{font-size:9.5px;letter-spacing:0;fill:#e2e8f0e0;opacity:1;transition:opacity .14s ease}.connection-editor--guided .connection-editor__segment-label:not(.connection-editor__segment-label--guided-visible){opacity:0}.connection-editor--guided .connection-editor__node--rotating .connection-editor__segment-label{opacity:0}.connection-editor__node-core{inset:43px;padding:12px;border-radius:999px;clip-path:none;display:flex;flex-direction:column;align-items:center;justify-content:center;background:radial-gradient(circle at center,#111827 0 58%,#060b14);border:1px solid rgba(226,232,240,.1);box-shadow:0 12px 28px #00000073,inset 0 1px #ffffff0d;color:#f8fafc;text-shadow:0 1px 2px rgba(0,0,0,.62)}.connection-editor__node-icon{display:block;max-width:44px;overflow:hidden;color:#f8fafc;font-family:Material Symbols Outlined,Material Icons,sans-serif;font-size:1.22rem;font-weight:400;line-height:1;text-align:center;text-transform:none;letter-spacing:0;white-space:nowrap;filter:drop-shadow(0 0 10px rgba(226,232,240,.18))}.connection-editor__node--state .connection-editor__node-ring{background:radial-gradient(circle at center,transparent 0 44%,rgba(7,17,31,.96) 45% 57%,transparent 58%),conic-gradient(from 14deg,#a855f7c7 0 42%,#7e3af26b 42% 78%,#f59e0b47 78% 100%);box-shadow:0 0 0 1px #a855f738,0 0 30px #a855f733}.connection-editor__node--selected .connection-editor__node-ring{box-shadow:0 0 0 2px #facc15bd,0 0 34px #facc153d}.connection-editor__node--connection-focus .connection-editor__node-ring{box-shadow:0 0 0 2px #22d3eead,0 0 38px #22d3ee42;animation:node-focus-breathe 1.7s ease-in-out infinite}.connection-editor__node--trace-source .connection-editor__node-ring{box-shadow:0 0 0 2px #facc15c7,0 0 0 18px #facc150f,0 0 54px #facc154d;animation:trace-source-pulse 1.38s ease-in-out infinite}.connection-editor__node--trace-source:before{content:\"\";position:absolute;z-index:0;inset:-44px;border-radius:999px;border:2px solid rgba(250,204,21,.36);background:radial-gradient(circle at center,transparent 0 64%,rgba(250,204,21,.05) 65% 66%,transparent 67%),conic-gradient(from -20deg,#facc1500,#facc1552,#facc1500);box-shadow:0 0 36px #facc151f;opacity:.92;pointer-events:none;animation:trace-source-orbit 2.8s linear infinite}.connection-editor__node--trace-reacting .connection-editor__node-ring{box-shadow:0 0 0 2px #22c55e94,0 0 46px #22c55e38;animation:trace-reacting-pulse 1.38s ease-in-out infinite}.connection-editor__node--trace-source:after,.connection-editor__node--trace-reacting:after{position:absolute;z-index:3;inset-inline-start:50%;inset-block-start:-42px;padding:0;border:0;background:transparent;color:#facc15;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;font-size:.68rem;font-weight:900;letter-spacing:.18em;text-transform:uppercase;transform:translate(-50%);text-shadow:0 0 12px rgba(250,204,21,.52);pointer-events:none}.connection-editor__node--trace-source:after{content:attr(data-trace-label)}.connection-editor__node--trace-reacting:after{content:attr(data-trace-label);color:#86efac;text-shadow:0 0 12px rgba(34,197,94,.44)}.connection-editor__node--trace-source .connection-editor__node-segment--output{fill-opacity:.66;stroke-width:2.2;filter:drop-shadow(0 0 14px rgba(255,159,47,.42))}.connection-editor__node--expanded .connection-editor__node-ring{box-shadow:0 0 0 2px #22d3eebd,0 0 34px #22d3ee3d}.connection-editor__node--rotating .connection-editor__node-ring{box-shadow:0 0 0 2px #facc15c7,0 0 42px #facc1547;animation:node-focus-breathe 1.1s ease-in-out infinite}.connection-editor__node--rotating .connection-editor__node-segment{fill-opacity:.68;stroke-opacity:1;stroke-width:1.9;cursor:grabbing}.connection-editor__node-title,.connection-editor__node-subtitle{display:block;max-width:92px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:center}.connection-editor__node-title{color:#f8fafc;font-size:.82rem;font-weight:800}.connection-editor__node-subtitle{margin-top:4px;color:#a7b4c8;font-size:.62rem;text-transform:uppercase;letter-spacing:.08em}.connection-editor__ports{position:absolute;z-index:30;display:grid;gap:6px;width:104px;opacity:0;pointer-events:none;transform:translateY(3px);transition:opacity .16s ease,transform .16s ease}.connection-editor__ports--visible{opacity:1;pointer-events:auto;transform:translateY(0)}.connection-editor__ports--outputs{z-index:42}.connection-editor__ports--inputs{z-index:36}.connection-editor__segment-popover{position:absolute;z-index:80;width:292px;max-height:320px;overflow:auto;display:grid;gap:10px;padding:14px;border-radius:16px;border:1px solid rgba(148,163,184,.18);background:linear-gradient(135deg,#0f172af5,#110c20f0),#090c16eb;box-shadow:0 28px 72px #0000007a,0 0 48px #a855f71a,inset 0 1px #ffffff0d;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.connection-editor__segment-popover-header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding-bottom:2px}.connection-editor__segment-popover-title{color:#94a3b8;font-size:.68rem;font-weight:800;letter-spacing:.14em;text-transform:uppercase}.connection-editor__segment-popover-hint{margin-top:5px;color:#64748b;font-size:.66rem;font-weight:700}.connection-editor__segment-rotation{flex:0 0 auto;min-width:54px;padding:5px 8px;border-radius:999px;border:1px solid rgba(34,211,238,.28);background:#082f4957;color:#7dd3fc;font-size:.66rem;font-weight:900;font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace;text-align:center;box-shadow:inset 0 0 16px #0ea5e914}.connection-editor__segment-row{display:grid;grid-template-columns:28px 72px minmax(0,1fr) auto;align-items:center;gap:8px;min-height:40px;padding:7px;border-radius:12px;border:1px solid rgba(148,163,184,.12);background:linear-gradient(90deg,#0f172ac7,#0f172a75),#0f172a99;transition:border-color .15s ease,background .15s ease,box-shadow .15s ease,transform .15s ease}.connection-editor__segment-row--input{border-color:#22d3ee42}.connection-editor__segment-row--output{border-color:#f59e0b47}.connection-editor__segment-row--state{border-color:#a855f74d}.connection-editor__segment-row--source,.connection-editor__segment-row--candidate{transform:translate(2px);box-shadow:0 0 22px #f59e0b24,inset 0 0 20px #f59e0b14}.connection-editor__segment-row--compatible{border-color:#22d3ee6b;box-shadow:inset 0 0 18px #22d3ee14}.connection-editor__segment-row--compat-preview{border-color:#f59e0b94;background:linear-gradient(90deg,#f59e0b1f,#0f172a8a),#0f172ab3;box-shadow:0 0 22px #f59e0b1f,inset 0 0 18px #f59e0b14}.connection-editor__segment-row--invalid-drop{border-color:#fb7185b3;background:linear-gradient(90deg,#f43f5e24,#0f172a94),#0f172ab3;box-shadow:0 0 24px #f43f5e2e,inset 0 0 18px #f43f5e1a;animation:invalid-drop-jolt .36s ease-out}.connection-editor__segment-index{color:#64748b;font-size:.64rem;font-weight:900;font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace;letter-spacing:.08em}.connection-editor__segment-kind{display:inline-flex;align-items:center;gap:6px;color:#94a3b8;font-size:.62rem;font-weight:800;text-transform:uppercase;min-width:0;white-space:nowrap}.connection-editor__segment-kind-dot{width:7px;height:7px;border-radius:999px;background:#94a3b8;box-shadow:0 0 12px #94a3b847}.connection-editor__segment-row--input .connection-editor__segment-kind-dot{background:#22d3ee;box-shadow:0 0 12px #22d3ee61}.connection-editor__segment-row--output .connection-editor__segment-kind-dot{background:#fb923c;box-shadow:0 0 12px #fb923c66}.connection-editor__segment-row--state .connection-editor__segment-kind-dot{background:#a855f7;box-shadow:0 0 12px #a855f76b}.connection-editor__segment-label{display:inline-flex;align-items:center;gap:5px;min-width:0;overflow:hidden;color:#e2e8f0;font-size:.74rem;font-weight:700;text-overflow:ellipsis;white-space:nowrap}.connection-editor__segment-badge{flex:0 0 auto;padding:2px 5px;border-radius:999px;border:1px solid rgba(56,189,248,.28);background:#0ea5e91a;color:#bae6fd;font-size:.55rem;font-weight:900;text-transform:uppercase}.connection-editor__segment-path{grid-column:3 / 4;min-width:0;margin-top:-5px;overflow:hidden;color:#93c5fd;font-size:.62rem;font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace;text-overflow:ellipsis;white-space:nowrap}.connection-editor__segment-row--nested{min-height:56px;border-color:#38bdf84d;background:linear-gradient(90deg,#0ea5e91a,#0f172a80),#0f172aa3}.connection-editor__segment-actions{display:inline-flex;gap:4px}.connection-editor__segment-action{width:24px;height:24px;border-radius:999px;border:1px solid rgba(148,163,184,.18);background:#060c16b8;color:#dbeafe;cursor:pointer;font-weight:900;line-height:1}.connection-editor__segment-action:hover{border-color:#22d3ee75;box-shadow:0 0 14px #22d3ee29}.connection-editor__port{min-height:24px;border-radius:999px;border:1px solid rgba(148,163,184,.14);background:#080d18c2;color:#cbd5e1;font-size:.64rem;font-weight:700;font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:crosshair;box-shadow:0 8px 20px #00000038}.connection-editor--guided .connection-editor__ports--visible .connection-editor__port:not(.connection-editor__port--guided-visible){display:none}.connection-editor__port--output{border-color:#f59e0b75;color:#fbbf24}.connection-editor__port--input{border-color:#06b6d475;color:#22d3ee}.connection-editor__port--state{border-color:#a855f785;color:#c084fc}.connection-editor__port--compatible{background:#22c55e29;box-shadow:0 0 0 1px #22c55e61,0 0 18px #22c55e2e;animation:snap-pulse 1s ease-in-out infinite}.connection-editor__port--source{border-color:#fb923cb8;background:#fb923c29;color:#fed7aa;box-shadow:0 0 0 1px #fb923c73,0 0 20px #fb923c2e}.connection-editor__port--candidate{background:#22c55e3d;box-shadow:0 0 0 2px #22c55e94,0 0 24px #22c55e3d}.connection-editor__port--incompatible{opacity:.42;cursor:not-allowed;filter:grayscale(.45)}.connection-editor__port--invalid-drop{opacity:1;border-color:#fb7185c7;color:#fecdd3;background:#f43f5e2e;box-shadow:0 0 0 2px #f43f5e75,0 0 24px #f43f5e38;filter:none;animation:invalid-drop-jolt .36s ease-out}.connection-editor__port--connection-focus{border-color:#facc15ad;color:#fde68a;background:#facc151f;box-shadow:0 0 0 1px #facc156b,0 0 20px #facc152e;animation:port-focus-pulse 1.25s ease-in-out infinite}:host-context(.theme-light) .connection-editor__port{color:var(--pce-fg)}.connection-editor__drag-tip{position:absolute;z-index:6;display:grid;gap:4px;width:min(330px,calc(100% - 28px));padding:10px 12px;border-radius:14px;border:1px solid rgba(125,211,252,.26);background:linear-gradient(135deg,#0ea5e91f,#0f172aeb),#080d18f0;box-shadow:0 18px 44px #0206176b,0 0 28px #0ea5e91a;font-size:.76rem;pointer-events:none;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);animation:tooltip-rise .14s ease-out}.connection-editor__drag-tip--valid{border-color:#22c55e66;background:linear-gradient(135deg,#22c55e29,#0f172aeb),#080d18f0;box-shadow:0 18px 44px #0206176b,0 0 30px #22c55e24}.connection-editor__drag-tip--invalid{z-index:7;border-color:#fb71858a;background:linear-gradient(135deg,#f43f5e2e,#0f172af0),#080d18f5;box-shadow:0 18px 44px #0206176b,0 0 34px #f43f5e29;animation:tooltip-rise .14s ease-out,invalid-drop-jolt .36s ease-out}.connection-editor__invalid-drop-pulse{position:absolute;z-index:6;width:16px;height:16px;border-radius:999px;border:2px solid rgba(251,113,133,.88);background:#f43f5e38;box-shadow:0 0 0 8px #f43f5e1a,0 0 28px #f43f5e3d;pointer-events:none;transform:translate(-50%,-50%);animation:invalid-drop-ping 1.2s ease-out both}.connection-editor__drag-tip span{color:#7dd3fc;text-transform:uppercase;letter-spacing:.12em;font-size:.62rem;font-weight:800}.connection-editor__drag-tip strong{min-width:0;overflow:hidden;color:#f8fafc;font-size:.78rem;font-weight:900;text-overflow:ellipsis;white-space:nowrap}.connection-editor__drag-tip small{color:#94a3b8;font-size:.68rem;font-weight:700}.connection-editor__drag-tip--valid small{color:#86efac}.connection-editor__drag-tip--invalid span,.connection-editor__drag-tip--invalid small{color:#fda4af}.connection-editor__summary{grid-column:1;grid-row:2;align-self:end;justify-self:start;z-index:6;display:flex;flex-wrap:wrap;gap:6px;padding:0 0 12px 12px;pointer-events:none}.connection-editor__side-panel{grid-column:2;grid-row:2 / 3;position:relative;z-index:8;min-width:0;min-height:0;display:grid;align-content:start;gap:14px;overflow:auto;padding-inline-end:4px;scrollbar-color:var(--pce-outline-strong) var(--pce-surface-soft)}.connection-editor__side-panel--contextual .connection-editor__inspector{order:-3}.connection-editor__side-panel--contextual .connection-editor__recommended{order:2}.connection-editor__side-panel--contextual .connection-editor__filters-section{order:3}.connection-editor__side-panel--contextual .connection-editor__recommended,.connection-editor__side-panel--contextual .connection-editor__filters-section{opacity:.82}.connection-editor__metric,.connection-editor__item{border-radius:16px;background:var(--pce-surface);border:1px solid var(--pce-outline);box-shadow:inset 0 1px var(--pce-glass-highlight);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px)}.connection-editor__metric{min-width:auto;display:inline-flex;align-items:baseline;gap:7px;padding:6px 9px;border-radius:999px;background:color-mix(in srgb,var(--pce-surface-strong) 86%,transparent);box-shadow:0 10px 26px var(--pce-shadow-soft),inset 0 1px var(--pce-glass-highlight)}.connection-editor__metric span{color:var(--pce-muted);font-size:.62rem;text-transform:uppercase;letter-spacing:.08em}.connection-editor__metric strong,.connection-editor__details-row strong{color:var(--pce-strong);font-size:.78rem}.connection-editor__filters,.connection-editor__actions,.connection-editor__badges{display:flex;gap:8px;flex-wrap:wrap}.connection-editor__filter,.connection-editor__action{border:1px solid var(--pce-outline-strong);background:var(--pce-surface-soft);color:var(--pce-fg);border-radius:999px;padding:6px 10px;font-size:.75rem;font-weight:700;cursor:pointer}.connection-editor__filter--active,.connection-editor__action:hover{border-color:color-mix(in srgb,var(--pce-secondary) 62%,transparent);background:color-mix(in srgb,var(--pce-secondary) 16%,var(--pce-surface-soft));color:var(--pce-strong)}.connection-editor__action--danger{border-color:color-mix(in srgb,var(--pce-error) 48%,transparent);color:var(--pce-error)}.connection-editor__item{width:100%;text-align:start;padding:12px;display:grid;gap:6px;cursor:pointer;color:inherit}.connection-editor__item--active{border-color:#a855f7b8;background:linear-gradient(90deg,#0ea5e91a,#a855f721)}.connection-editor__item-title{font-weight:800;color:#f8fafc;word-break:break-word}.connection-editor__badge{padding:3px 8px;border-radius:999px;background:color-mix(in srgb,var(--pce-primary) 12%,var(--pce-surface));color:var(--pce-fg);font-size:.72rem;font-weight:800}.connection-editor__section{min-width:0;padding:14px;border-radius:18px;background:var(--pce-surface);border:1px solid var(--pce-outline);box-shadow:inset 0 1px var(--pce-glass-highlight),0 18px 50px var(--pce-shadow-soft);-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.connection-editor__badge--warning{background:color-mix(in srgb,var(--pce-warning) 14%,var(--pce-surface));color:var(--pce-fg)}.connection-editor__badge--error{background:color-mix(in srgb,var(--pce-error) 14%,var(--pce-surface));color:var(--pce-fg)}.connection-editor__diagnostics{display:grid;gap:8px}.connection-editor__diagnostic{display:grid;gap:4px;padding:10px;border-radius:12px;border:1px solid var(--pce-outline);background:var(--pce-surface-soft);color:var(--pce-fg);font-size:.78rem;line-height:1.35}.connection-editor__diagnostic--warning{border-color:#f59e0b47;background:#f59e0b1a}.connection-editor__diagnostic--error{border-color:#ef44445c;background:#ef44441f}.connection-editor__diagnostic-severity{font-size:.68rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase;color:var(--pce-fg)}.connection-editor__suggestions{display:grid;gap:10px;margin-top:14px;padding-top:14px;border-top:1px solid var(--pce-outline)}.connection-editor__suggestion{display:grid;gap:8px;width:100%;padding:12px;border-radius:16px;border:1px solid color-mix(in srgb,var(--pce-secondary) 26%,transparent);background:linear-gradient(135deg,color-mix(in srgb,var(--pce-secondary) 12%,transparent),color-mix(in srgb,var(--pce-primary) 8%,transparent)),var(--pce-surface-strong);color:var(--pce-fg);text-align:start;cursor:pointer;transition:transform .16s ease,border-color .16s ease,background .16s ease}.connection-editor__suggestion:hover{transform:translateY(-1px);border-color:color-mix(in srgb,var(--pce-primary) 46%,transparent);background:linear-gradient(135deg,color-mix(in srgb,var(--pce-secondary) 18%,transparent),color-mix(in srgb,var(--pce-primary) 12%,transparent)),var(--pce-surface-strong)}.connection-editor__suggestion-route{display:grid;gap:4px;font-size:.78rem}.connection-editor__suggestion-route strong{color:var(--pce-strong);font-size:.82rem}.connection-editor__suggestion-reason{color:var(--pce-muted);font-size:.76rem;line-height:1.35}.connection-editor__suggestion-diagnostic{padding:10px 12px;border-radius:14px;border:1px solid rgba(245,158,11,.28);background:#f59e0b1a;color:var(--pce-strong);font-size:.78rem;line-height:1.35}.connection-editor__suggestion-outcome{color:var(--pce-strong);font-size:.84rem;line-height:1.3}.connection-editor__suggestion-action{justify-self:start;padding:3px 8px;border-radius:999px;background:#22d3ee24;color:#67e8f9;font-size:.68rem;font-weight:900;letter-spacing:.06em;text-transform:uppercase}:host-context(.theme-light) .connection-editor__suggestion-action{background:color-mix(in srgb,var(--pce-primary) 12%,var(--pce-surface-soft));color:#075f73}.connection-editor__trace{display:grid;gap:10px;margin-top:14px;padding-top:14px;border-top:1px solid var(--pce-outline)}.connection-editor__trace-summary{color:var(--pce-muted);font-size:.78rem;line-height:1.35}.connection-editor__trace-step{display:grid;grid-template-columns:24px minmax(76px,auto) 1fr;gap:8px;align-items:center;padding:9px;border-radius:12px;border:1px solid rgba(34,211,238,.18);background:var(--pce-surface-soft);font-size:.76rem}.connection-editor__trace-step--blocked{border-color:#ef444457;background:#ef44441a}.connection-editor__trace-order{display:inline-grid;place-items:center;width:22px;height:22px;border-radius:999px;background:#22d3ee29;color:#67e8f9;font-weight:900}.connection-editor__trace-phase{color:var(--pce-strong);font-weight:900;text-transform:uppercase;letter-spacing:.06em;font-size:.66rem}.connection-editor__trace-copy{min-width:0;color:var(--pce-fg);word-break:break-word}.connection-editor__transform-steps{display:grid;gap:8px}.connection-editor__transform-step{display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:8px;align-items:center;padding:9px;border-radius:12px;border:1px solid rgba(168,85,247,.22);background:var(--pce-surface-soft)}.connection-editor__format-fields{grid-column:1 / -1;display:grid;gap:8px;min-width:0}.connection-editor__format-fields label{display:grid;gap:4px}.connection-editor__format-fields input,.connection-editor__format-fields select{width:100%;min-width:0;box-sizing:border-box;padding:8px;color:var(--md-sys-color-on-surface);background:var(--md-sys-color-surface);border:1px solid var(--md-sys-color-outline);border-radius:4px}.connection-editor__format-fields p{margin:0}.connection-editor__transform-kind{min-width:0;color:var(--pce-strong);font-weight:900;word-break:break-word}.connection-editor__transform-phase{color:var(--pce-muted);font-size:.68rem;font-weight:800;text-transform:uppercase;letter-spacing:.06em}.connection-editor__details-row,.connection-editor__details-block{display:grid;gap:6px}.connection-editor__payload-input{width:100%;min-height:128px;box-sizing:border-box;resize:vertical;border:1px solid var(--pce-outline);border-radius:8px;background:color-mix(in srgb,var(--pce-surface) 92%,black);color:var(--pce-fg);padding:10px;font:.76rem/1.45 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace}.connection-editor__surface-action-editor{display:block;max-height:min(58vh,620px);overflow:auto;border:1px solid var(--pce-outline);border-radius:8px;background:color-mix(in srgb,var(--pce-surface) 96%,black);padding:10px;margin-bottom:10px}.connection-editor__rule-grid{display:grid;gap:10px}.connection-editor__rule-card{display:grid;gap:8px;padding:10px;border-radius:12px;border:1px solid var(--pce-outline);background:var(--pce-surface-soft)}.connection-editor__rule-heading{color:var(--pce-fg);font-size:.72rem;font-weight:900;text-transform:uppercase;letter-spacing:.06em}pre{margin:0;padding:12px;border-radius:12px;background:color-mix(in srgb,var(--pce-surface-strong) 82%,#020617);color:var(--pce-fg);overflow:auto;font-size:.75rem}@keyframes connection-flow{to{stroke-dashoffset:-38}}@keyframes drag-wire-pulse{0%,to{stroke-opacity:.76}50%{stroke-opacity:1}}@keyframes drag-halo-pulse{0%,to{stroke-opacity:.48}50%{stroke-opacity:.82}}@keyframes endpoint-pulse{0%,to{opacity:.78;transform:scale(1)}50%{opacity:1;transform:scale(1.25)}}@keyframes selected-wire-aura{0%,to{stroke-opacity:.46}50%{stroke-opacity:.92}}@keyframes wire-draw-in{0%{stroke-dashoffset:640;opacity:.32}70%{opacity:1}to{stroke-dashoffset:0;opacity:1}}@keyframes new-wire-halo{0%{stroke-opacity:0;stroke-width:30}28%{stroke-opacity:.78}to{stroke-opacity:0;stroke-width:12}}@keyframes new-dock-link-flash{0%{transform:translateY(-2px);box-shadow:0 16px 40px #0206175c,0 0 42px #fb923c47}to{transform:translateY(0)}}@keyframes trace-wire-pulse{0%,to{stroke-opacity:.78}50%{stroke-opacity:1}}@keyframes trace-halo-pulse{0%,to{stroke-opacity:.34}50%{stroke-opacity:.86}}@keyframes tooltip-rise{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@keyframes invalid-drop-jolt{0%,to{transform:translate(0)}28%{transform:translate(-3px)}56%{transform:translate(3px)}}@keyframes invalid-drop-ping{0%{opacity:1;transform:translate(-50%,-50%) scale(.72)}72%{opacity:.74;transform:translate(-50%,-50%) scale(1.35)}to{opacity:0;transform:translate(-50%,-50%) scale(1.8)}}@keyframes node-breathe{50%{transform:scale(1.035)}}@keyframes node-focus-breathe{0%,to{transform:scale(1)}50%{transform:scale(1.055)}}@keyframes trace-source-pulse{50%{transform:scale(1.04);box-shadow:0 0 0 3px #facc15db,0 0 0 22px #facc1514,0 0 64px #facc1557}}@keyframes trace-source-orbit{to{transform:rotate(360deg)}}@keyframes trace-reacting-pulse{50%{transform:scale(1.045);box-shadow:0 0 0 3px #22c55ead,0 0 62px #22c55e4d}}@keyframes segment-focus-sweep{0%,to{stroke-opacity:.82}50%{stroke-opacity:1}}@keyframes snap-pulse{50%{filter:brightness(1.16)}}@keyframes port-focus-pulse{50%{transform:translateY(-1px);box-shadow:0 0 0 2px #facc158f,0 0 26px #facc153d}}.connection-editor__link-list{order:-2}.connection-editor__item-title{color:var(--pce-fg);font-weight:650;overflow-wrap:anywhere}.connection-editor__link-list .connection-editor__item{border:0;border-radius:6px;background:transparent;border-bottom:1px solid var(--pce-outline);box-shadow:none;padding:12px 4px;gap:4px}.connection-editor__link-list .connection-editor__item--active{background:var(--pce-surface-strong)}.connection-editor--guided .connection-editor__widget-list,.connection-editor--guided .connection-editor__link-list .connection-editor__item-copy{display:none}.connection-editor__filters-section,.connection-editor__side-panel--contextual .connection-editor__inspector{order:-3}.connection-editor__working-note{order:4}.connection-editor__draft{order:-5}.connection-editor--guided .connection-editor__segment-kind-legend{background:var(--pce-surface-strong);color:var(--pce-fg)}.connection-editor__node-caption{position:absolute;left:50%;top:-16px;transform:translate(-50%,-100%);color:var(--pce-fg);background:var(--pce-surface-base);border:1px solid var(--pce-outline);border-radius:8px;padding:6px 8px;line-height:1.3;font-weight:650;white-space:normal;overflow-wrap:anywhere}.connection-editor__node-core{padding:8px}.connection-editor__node-icon{font-size:30px}.connection-editor__node-subtitle{display:none}.connection-editor__wire:not(.connection-editor__wire--halo){stroke:var(--pce-primary);stroke-opacity:.85;stroke-width:2;vector-effect:non-scaling-stroke;stroke-dasharray:none;animation:none;filter:none}.connection-editor__wire--state:not(.connection-editor__wire--halo){stroke:var(--pce-tertiary)}.connection-editor__wire--state-read:not(.connection-editor__wire--halo){stroke:var(--pce-secondary)}.connection-editor__wire--error:not(.connection-editor__wire--halo){stroke:var(--pce-error)}.connection-editor__wire--active:not(.connection-editor__wire--halo),.connection-editor__wire--trace:not(.connection-editor__wire--halo){stroke-width:3;stroke-opacity:1;stroke-dasharray:12 6;animation:connection-flow 1.8s linear infinite}.connection-editor__wire--mode-dimmed:not(.connection-editor__wire--halo){stroke-opacity:.16}.connection-editor__header{position:relative;padding-right:58px;gap:12px;flex-wrap:wrap;border-radius:18px}.connection-editor__close{position:absolute;top:8px;right:14px}.connection-editor__mode-frame{min-width:max-content;flex:1 0 auto}.connection-editor__mode-shell{max-width:100%;overflow-x:auto;box-sizing:border-box}.connection-editor__header-actions{flex:0 1 auto}@media(max-width:1400px){.connection-editor__eyebrow{display:none}}@media(min-width:901px)and (max-width:1200px){.connection-editor__mode-frame{order:2;flex-basis:100%}.connection-editor__header-actions{margin-inline-start:auto}}@media(prefers-reduced-motion:reduce){.connection-editor__viewport,.connection-editor__node,.connection-editor__node-ring,.connection-editor__node-core,.connection-editor__node-segment,.connection-editor__dock-link,.connection-editor__port{transition-duration:1ms}.connection-editor__wire,.connection-editor__wire--state,.connection-editor__wire--projection,.connection-editor__wire--state-read,.connection-editor__wire--event-propagation,.connection-editor__wire--halo.connection-editor__wire--active,.connection-editor__wire--trace,.connection-editor__wire--halo.connection-editor__wire--trace,.connection-editor__wire-endpoint,.connection-editor__connection-tooltip,.connection-editor__dock-link--new,.connection-editor__trace-overlay,.connection-editor__trace-overlay-step,.connection-editor__drag-wire,.connection-editor__drag-wire--halo,.connection-editor__drag-endpoint,.connection-editor__drag-tip,.connection-editor__node-ring,.connection-editor__node--connection-focus .connection-editor__node-ring,.connection-editor__node--trace-source .connection-editor__node-ring,.connection-editor__node--trace-source:before,.connection-editor__node--trace-reacting .connection-editor__node-ring,.connection-editor__node--rotating .connection-editor__node-ring,.connection-editor__node-segment--connection-focus,.connection-editor__node-segment--compatible,.connection-editor__node-segment--invalid-drop,.connection-editor__segment-popover,.connection-editor__port--compatible,.connection-editor__port--invalid-drop,.connection-editor__port--connection-focus,.connection-editor__invalid-drop,.connection-editor__invalid-drop-pulse{animation:none}.connection-editor__wire{stroke-dashoffset:0}.connection-editor__wire--new{stroke-dasharray:10 8;stroke-dashoffset:0;opacity:1}.connection-editor__wire--halo.connection-editor__wire--new{stroke-width:12;stroke-opacity:.16}.connection-editor__wire-endpoint,.connection-editor__drag-endpoint,.connection-editor__node-ring,.connection-editor__node--trace-source:before{transform:none}.connection-editor__invalid-drop-pulse{opacity:0}}@media(max-width:900px){:host{inset:0}.connection-editor{inset:8px;height:calc(100vh - 16px);padding:12px;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(260px,1fr) minmax(150px,30vh) minmax(76px,112px)}.connection-editor--dock-collapsed{grid-template-rows:auto minmax(260px,1fr) minmax(150px,30vh) 48px}.connection-editor__header,.connection-editor__stage,.connection-editor__summary,.connection-editor__side-panel,.connection-editor__dock{grid-column:1}.connection-editor__header{align-items:stretch;flex-wrap:wrap;border-radius:18px}.connection-editor__mode-frame{order:3;width:100%;max-width:100%}.connection-editor__close{margin-inline-start:auto}.connection-editor__mode-frame:before,.connection-editor__mode-frame:after{content:\"\";position:absolute;z-index:2;inset-block:4px;width:24px;border-radius:999px;pointer-events:none}.connection-editor__mode-frame:before{inset-inline-start:1px;background:linear-gradient(90deg,var(--pce-surface-base),transparent)}.connection-editor__mode-frame:after{inset-inline-end:1px;background:linear-gradient(270deg,var(--pce-surface-base),transparent)}.connection-editor__mode-shell{width:100%;max-width:100%;overflow-x:auto;scroll-padding-inline:28px;-webkit-overflow-scrolling:touch}.connection-editor__subtitle{margin-inline-start:auto}.connection-editor__stage{grid-row:2;min-height:260px}.connection-editor__summary{grid-row:2;max-width:calc(100% - 24px)}.connection-editor__side-panel{grid-row:3;max-height:30vh}.connection-editor__dock{grid-row:4;min-height:0}.connection-editor__connection-tooltip{width:min(620px,calc(100% - 24px));max-height:min(260px,calc(100% - 24px));overflow:auto}.connection-editor__dock-tabs{width:100%;min-width:0;min-height:0;flex-wrap:wrap;align-items:flex-start}.connection-editor__dock-tablist{width:100%;flex-wrap:wrap}.connection-editor__dock-count{flex:1 1 100%;margin-inline-start:0}.connection-editor__dock-body{width:100%;min-width:0;max-height:76px;box-sizing:border-box}.connection-editor__dock-body pre{white-space:pre-wrap;overflow-wrap:anywhere}.connection-editor__dock-link{grid-template-columns:1fr;align-items:start}.connection-editor__dock-link-route{flex-wrap:wrap}.connection-editor__dock-link-badges{justify-content:flex-start}.connection-editor__trace-overlay{inset-inline-start:14px;width:min(420px,calc(100% - 28px));min-width:0;transform:none}.connection-editor__drag-tip{width:min(300px,calc(100% - 28px))}.connection-editor__segment-popover{width:min(280px,calc(100% - 28px));max-height:min(280px,calc(100% - 28px))}.connection-editor__minimap{inset-inline-end:10px;inset-block-end:10px;width:132px;height:94px;padding:16px 8px 8px;border-radius:14px}.connection-editor__minimap:before{inset:20px 8px 8px;background-size:18px 18px}.connection-editor__minimap-title{inset-block-start:6px;inset-inline-start:10px;font-size:.54rem}}@media(max-width:640px){.connection-editor{inset:4px;height:calc(100vh - 8px);padding:8px;gap:8px;grid-template-rows:auto minmax(240px,1fr) minmax(140px,28vh) minmax(72px,104px)}.connection-editor--dock-collapsed{grid-template-rows:auto minmax(240px,1fr) minmax(140px,28vh) 46px}.connection-editor__header{gap:8px;padding:10px}.connection-editor__mode-shell{padding:4px;gap:2px}.connection-editor__mode{min-height:28px;gap:4px;padding:0 7px;font-size:.68rem}.connection-editor__mode-glyph{font-size:.64rem}.connection-editor__stage{min-height:240px}.connection-editor__zoom-controls{inset-inline-start:8px;inset-block-start:8px;gap:6px;width:42px}.connection-editor__tool-group,.connection-editor__trace-source{gap:6px;padding:8px;border-radius:12px}.connection-editor__tool-group{grid-template-columns:28px}.connection-editor__tool-group:nth-of-type(2){grid-template-columns:28px}.connection-editor__tool-button{width:28px;height:28px}.connection-editor__arrange-button,.connection-editor__simulate-button{width:30px;min-height:30px}.connection-editor__summary{gap:6px;padding-inline-end:8px}.connection-editor__side-panel{max-height:28vh}.connection-editor__dock{border-radius:16px}.connection-editor__dock-tabs,.connection-editor__dock-tablist{gap:4px}.connection-editor__dock-tab{padding:0 9px}.connection-editor__dock-count{font-size:.62rem}.connection-editor__dock-body{max-height:66px;padding:8px}.connection-editor__minimap{width:116px;height:84px}.connection-editor__segment-row{grid-template-columns:24px minmax(54px,.55fr) minmax(0,1fr) auto}}.connection-editor__ports{width:max-content;visibility:hidden}.connection-editor__ports--visible{visibility:visible}.connection-editor__port{font-size:inherit;white-space:normal;overflow:visible;overflow-wrap:anywhere;padding:4px 8px}.connection-editor__header-actions{display:flex;flex-wrap:wrap;gap:8px}.connection-editor__action--primary{background:var(--pce-primary);color:var(--pce-surface-base);font-weight:700}.connection-editor__action:disabled{opacity:.5;cursor:default}.connection-editor__draft{order:-4;padding:16px;border-bottom:1px solid var(--pce-outline)}.connection-editor__draft p,.connection-editor__working-note{font-size:.8rem;line-height:1.55;color:var(--pce-muted)}.connection-editor__working-note{margin:12px 16px}.connection-editor__draft label{display:grid;gap:6px;margin:14px 0;font-size:.82rem}.connection-editor__draft select,.connection-editor__draft input{box-sizing:border-box;width:100%;min-width:0;min-height:40px;padding:8px;color:var(--pce-fg);background:var(--pce-surface-base);border:1px solid var(--pce-outline-strong);border-radius:8px}.connection-editor__draft-review{display:grid;gap:6px;padding:12px 0;font-size:.85rem;overflow-wrap:anywhere}.connection-editor__draft-actions{position:sticky;bottom:0;z-index:4;padding:12px 0;background:var(--pce-surface-base);border-top:1px solid var(--pce-outline)}.connection-editor--draft .connection-editor__filters-section,.connection-editor--draft .connection-editor__link-list{display:none}.connection-editor__draft-status{color:var(--pce-primary)}.connection-editor__advanced{margin:16px 0}.connection-editor__advanced summary{cursor:pointer;font-size:.82rem;padding:10px 0}.connection-editor__node-title{white-space:normal;overflow:visible;text-overflow:clip;overflow-wrap:anywhere;line-height:1.25}.connection-editor--presentation{grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr)}.connection-editor--presentation .connection-editor__side-panel,.connection-editor--presentation .connection-editor__summary,.connection-editor--presentation .connection-editor__dock,.connection-editor--presentation .connection-editor__subtitle,.connection-editor--presentation .connection-editor__mode-frame{display:none}.connection-editor--presentation .connection-editor__stage{grid-column:1;grid-row:2}@media(max-width:900px){.connection-editor__header{flex-wrap:wrap}.connection-editor__header-actions{order:2;width:100%}.connection-editor--draft{grid-template-rows:auto minmax(160px,.45fr) minmax(260px,1fr) 46px}.connection-editor--draft .connection-editor__stage{min-height:0}.connection-editor__side-panel{max-height:none}.connection-editor--draft .connection-editor__summary{display:none}}@media(max-width:640px){.connection-editor__eyebrow,.connection-editor__subtitle,.connection-editor__summary,.connection-editor__minimap{display:none}.connection-editor__header{position:relative;padding-right:42px}.connection-editor__close{position:absolute;top:8px;right:8px}.connection-editor__header-actions{flex-wrap:nowrap}.connection-editor__header-actions .connection-editor__action{min-height:36px}.connection-editor__mode-frame{flex-basis:auto}.connection-editor__side-panel{max-height:none}.connection-editor__draft{padding:10px}.connection-editor__stage{overflow:hidden}.connection-editor__connection-tooltip{display:none}.connection-editor__dock{min-width:0}.connection-editor__dock-tablist{min-width:0;overflow-x:auto;max-width:100%}.connection-editor__dock-meta{display:none}}@media(prefers-reduced-motion:reduce){.connection-editor *,.connection-editor *:before,.connection-editor *:after{animation:none!important;transition:none!important}}.connection-editor__terminal{fill:none;stroke:var(--pce-fg);stroke-width:1.6;stroke-linecap:round;opacity:.72;pointer-events:none}.connection-editor__terminal--state{stroke:var(--pce-tertiary)}.connection-editor__terminal--read{stroke:var(--pce-secondary)}.connection-editor__terminal--dimmed{opacity:.14}.connection-editor .connection-editor__segment-rotation{color:var(--pce-fg);background:var(--pce-surface-base);box-shadow:none}.connection-editor__flow-legend{position:absolute;top:12px;right:12px;z-index:85;max-width:calc(100% - 80px);background:var(--md-sys-color-surface-container, var(--pce-surface-base));color:var(--pce-fg);padding:8px 12px;border:1px solid var(--pce-outline);border-radius:10px;font-size:12px}.connection-editor__flow-legend summary{cursor:pointer}.connection-editor__flow-legend>span{display:flex;align-items:center;gap:10px;margin-top:10px}.connection-editor__flow-legend svg{width:48px;height:12px}.connection-editor .connection-editor__wire--state-read:not(.connection-editor__wire--halo){stroke-dasharray:8 4}.connection-editor .connection-editor__wire--event-propagation:not(.connection-editor__wire--halo){stroke-dasharray:2 4}.connection-editor .connection-editor__wire--new:not(.connection-editor__wire--halo){stroke-dashoffset:0}.connection-editor{grid-template-columns:minmax(0,1fr)}.connection-editor__side-panel{display:none}.connection-editor--inspector-open:not(.connection-editor--presentation) .connection-editor__side-panel{display:grid;grid-column:1;grid-row:2;justify-self:end;align-self:stretch;width:min(380px,100%);max-height:100%;padding:12px;box-sizing:border-box;background:var(--md-sys-color-surface-container, var(--pce-surface-base));border:1px solid var(--pce-outline-strong);border-radius:12px;z-index:90;box-shadow:0 12px 32px var(--pce-shadow-soft)}.connection-editor .connection-editor__segment-popover{z-index:100;box-sizing:border-box;transform-origin:top left;padding:12px;background:var(--md-sys-color-surface-container, var(--pce-surface-base));color:var(--pce-fg);border-color:var(--pce-outline-strong);-webkit-backdrop-filter:none;backdrop-filter:none;box-shadow:0 12px 32px var(--pce-shadow-soft);overscroll-behavior:contain}.connection-editor .connection-editor__segment-popover-header{position:sticky;top:-12px;padding:8px 0;z-index:1;background:var(--md-sys-color-surface-container, var(--pce-surface-base))}.connection-editor .connection-editor__segment-row{grid-template-columns:22px 64px minmax(0,1fr) auto;background:var(--md-sys-color-surface-container-low, var(--pce-surface-base));color:var(--pce-fg);border-color:var(--pce-outline);box-shadow:none}.connection-editor .connection-editor__segment-row .connection-editor__port-select{color:var(--pce-fg);white-space:normal;overflow-wrap:anywhere;font-size:12px}.connection-editor__port-select{min-width:0;font:inherit;border:0;background:transparent;text-align:start;cursor:pointer;padding:4px}.connection-editor .connection-editor__segment-action{background:var(--md-sys-color-surface-container-high, var(--pce-surface-base));color:var(--pce-fg);box-shadow:none}.connection-editor .connection-editor__segment-kind,.connection-editor .connection-editor__segment-index,.connection-editor .connection-editor__segment-popover-hint{color:var(--pce-muted)}.connection-editor .connection-editor__segment-popover-title{color:var(--pce-fg)}.connection-editor .connection-editor__port{background:var(--md-sys-color-surface-container, var(--pce-surface-base));color:var(--pce-fg);box-shadow:none}.connection-editor__port-select:focus-visible,.connection-editor__segment-action:focus-visible{outline:2px solid var(--pce-primary);outline-offset:2px}.connection-editor .connection-editor__wire:not(.connection-editor__wire--trace),.connection-editor .connection-editor__node:hover .connection-editor__node-ring,.connection-editor .connection-editor__node:focus-within .connection-editor__node-ring,.connection-editor .connection-editor__node--expanded .connection-editor__node-ring{animation:none}.connection-editor .connection-editor__wire:not(.connection-editor__wire--halo,.connection-editor__wire--mode-dimmed){stroke-opacity:.8}@media(max-width:900px){.connection-editor{grid-template-rows:auto minmax(0,1fr) minmax(0,32vh)}.connection-editor .connection-editor__mode-frame{min-width:0;flex:1 1 100%}.connection-editor.connection-editor--dock-collapsed{grid-template-rows:auto minmax(0,1fr) 48px}.connection-editor .connection-editor__dock{grid-row:3;grid-template-rows:48px minmax(0,1fr)}.connection-editor .connection-editor__dock--collapsed{grid-template-rows:48px}.connection-editor .connection-editor__dock-tabs{box-sizing:border-box;flex-wrap:nowrap;align-items:center;padding:6px 8px}.connection-editor .connection-editor__dock-tablist{flex:1 1 0;width:auto;flex-wrap:nowrap;overflow-x:auto}.connection-editor .connection-editor__dock-tab,.connection-editor .connection-editor__dock-toggle{flex-shrink:0}.connection-editor .connection-editor__dock-count{display:none}.connection-editor .connection-editor__dock-body{min-height:0;max-height:none}.connection-editor .connection-editor__dock-link-row{box-sizing:border-box;grid-template-columns:minmax(0,1fr)}.connection-editor .connection-editor__dock-link-chip{white-space:normal;overflow-wrap:anywhere}.connection-editor .connection-editor__stage{min-height:0}.connection-editor.connection-editor--presentation{grid-template-rows:auto minmax(0,1fr)}}.connection-editor__node{--pce-node-accent: hsl(var(--p-connection-node-hue, 210) 58% 44%)}.connection-editor .connection-editor__node-ring{background:radial-gradient(circle,transparent 57%,color-mix(in srgb,var(--pce-node-accent) 28%,var(--pce-surface-base)) 58% 85%,var(--pce-node-accent) 86%);box-shadow:none;animation:none}.connection-editor .connection-editor__node-core{background:color-mix(in srgb,var(--pce-node-accent) 18%,var(--pce-surface-base));color:var(--pce-fg);border:3px solid var(--pce-node-accent);box-shadow:none;text-shadow:none}.connection-editor .connection-editor__node-icon{color:inherit;filter:none}.connection-editor .connection-editor__node-caption{border-inline-start:5px solid var(--pce-node-accent);transform:translate(-50%,-50%);box-sizing:border-box;display:flex;align-items:center;justify-content:center;overflow:hidden}.connection-editor .connection-editor__node-caption>span{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}.connection-editor .connection-editor__connection-tooltip{z-index:88;box-sizing:border-box;max-height:220px;overflow:hidden;background:var(--pce-surface-base);color:var(--pce-fg);-webkit-backdrop-filter:none;backdrop-filter:none;animation:none}.connection-editor .connection-editor__connection-tooltip-row strong{white-space:normal;overflow-wrap:anywhere}.connection-editor .connection-editor__connection-tooltip-callout{display:none}.connection-editor--guided .connection-editor__node-segment{opacity:.12}.connection-editor--guided .connection-editor__node-halo,.connection-editor--guided .connection-editor__node-anchor,.connection-editor--guided .connection-editor__node-rotate-handle,.connection-editor--guided .connection-editor__segment-rotation{display:none}.connection-editor__port-inspection{grid-column:1 / -1;min-width:0;padding:8px}.connection-editor__port-inspection p{margin:0 0 8px;line-height:1.5;color:var(--pce-fg)}.connection-editor__port-connection,.connection-editor .connection-editor__port-create{display:block;width:100%;min-height:36px;padding:8px;margin-block:4px;text-align:start;white-space:normal;overflow-wrap:anywhere;height:auto;color:var(--pce-fg);background:var(--pce-surface-base);border:1px solid var(--pce-outline);border-radius:8px;cursor:pointer}.connection-editor__port-select:focus-visible,.connection-editor__port-connection:focus-visible,.connection-editor__port-create:focus-visible{outline:2px solid var(--pce-primary);outline-offset:2px}.connection-editor.connection-editor--tracing{grid-template-rows:auto auto minmax(0,1fr) minmax(0,28%)}.connection-editor.connection-editor--tracing.connection-editor--dock-collapsed{grid-template-rows:auto auto minmax(0,1fr) 48px}.connection-editor.connection-editor--tracing.connection-editor--presentation{grid-template-rows:auto auto minmax(0,1fr)}.connection-editor.connection-editor--tracing .connection-editor__stage,.connection-editor.connection-editor--tracing .connection-editor__side-panel{grid-row:3}.connection-editor.connection-editor--tracing .connection-editor__summary{display:none}.connection-editor.connection-editor--tracing .connection-editor__dock{grid-row:4}.connection-editor .connection-editor__trace-overlay{position:relative;grid-row:2;grid-column:1 / -1;inset:auto;transform:none;box-sizing:border-box;width:100%;min-width:0;max-height:210px;overflow:auto;padding:10px 14px;background:var(--pce-surface-base);color:var(--pce-fg);border-color:var(--pce-outline);box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none;animation:none}.connection-editor .connection-editor__trace-overlay-head span,.connection-editor .connection-editor__trace-overlay-route strong{color:var(--pce-fg);font-size:13px;white-space:normal;overflow-wrap:anywhere}.connection-editor .connection-editor__trace-overlay-head strong{color:var(--pce-fg);white-space:nowrap;flex-shrink:0}.connection-editor__trace-explanation{font-size:12px;color:var(--pce-muted);margin:6px 0;line-height:1.4}.connection-editor .connection-editor__trace-overlay-button,.connection-editor .connection-editor__trace-overlay-step{width:32px;height:32px;min-width:32px;flex-shrink:0;font-size:12px;color:var(--pce-fg);background:var(--pce-surface-soft);border-color:var(--pce-outline)}.connection-editor .connection-editor__trace-overlay-step--active{background:color-mix(in srgb,var(--pce-primary) 24%,var(--pce-surface-base));color:var(--pce-fg)}.connection-editor__trace-technical{margin-top:8px;font-size:12px}.connection-editor__trace-technical summary{cursor:pointer}.connection-editor__trace-technical span{display:inline-block;margin:8px 12px 0 0}.connection-editor__compact{height:100%;overflow:auto;padding:12px;box-sizing:border-box;touch-action:pan-y;color:var(--pce-fg)}.connection-editor__compact>p{margin:0 0 12px;color:var(--pce-muted);line-height:1.5;font-size:13px}.connection-editor__compact-node{border-bottom:1px solid var(--pce-outline);padding:6px 0}.connection-editor__compact-node summary{display:flex;align-items:center;gap:10px;min-height:48px;cursor:pointer}.connection-editor__compact-node summary strong{flex:1;min-width:0;overflow-wrap:anywhere;font-size:14px}.connection-editor__compact-node .connection-editor__node-icon{color:var(--pce-fg);border-inline-start:4px solid hsl(var(--p-connection-node-hue) 58% 44%);padding:6px;font-size:24px}.connection-editor .connection-editor__compact-edge--active{border-inline-start:4px solid var(--pce-primary);font-weight:650;background:color-mix(in srgb,var(--pce-primary) 12%,var(--pce-surface-base))}.connection-editor__compact-count{font-size:12px;color:var(--pce-muted)}.connection-editor__compact-node p{font-size:13px}.connection-editor__compact-edge,.connection-editor__compact-ports{display:block;box-sizing:border-box;width:100%;padding:12px;margin:6px 0;min-height:44px;color:var(--pce-fg);background:var(--pce-surface-base);border:1px solid var(--pce-outline);border-radius:8px;text-align:start;font:inherit;font-size:13px;white-space:normal;overflow-wrap:anywhere;cursor:pointer}.connection-editor__compact-edge small{display:block;margin-top:6px;color:var(--pce-muted)}.connection-editor__compact :focus-visible,.connection-editor__trace-overlay :focus-visible{outline:2px solid var(--pce-primary);outline-offset:2px}.connection-editor__stage{display:grid;grid-template-rows:minmax(0,1fr) auto;overflow:hidden}.connection-editor__map-surface{position:relative;min-width:0;min-height:0;overflow:clip;isolation:isolate}.connection-editor__canvas-controls{display:grid;grid-template-columns:minmax(0,1fr) 116px;align-items:center;gap:6px 12px;padding:6px 10px;border-top:1px solid var(--pce-outline);background:var(--pce-surface-base);cursor:auto;touch-action:auto;min-width:0}.connection-editor__canvas-tools{display:flex;align-items:center;gap:16px;min-width:0;overflow-x:auto;padding:3px}.connection-editor__canvas-controls .connection-editor__zoom-controls{position:static;display:flex;align-items:center;width:auto;flex:0 0 auto}.connection-editor__canvas-controls .connection-editor__tool-group{display:flex;padding:3px;gap:4px;box-shadow:none}.connection-editor__canvas-controls .connection-editor__arrange-button{opacity:1;transform:none;color:var(--pce-fg)}.connection-editor__canvas-controls .connection-editor__arrange-button span{color:var(--pce-primary)}.connection-editor__canvas-controls .connection-editor__flow-legend{position:static;flex:0 0 auto;max-width:none;padding:4px 8px;box-shadow:none}.connection-editor__canvas-controls .connection-editor__flow-legend>span{display:inline-flex;margin:6px 12px 0 0;gap:5px}.connection-editor__canvas-controls .connection-editor__flow-legend svg{width:30px}.connection-editor__canvas-controls .connection-editor__summary{position:static;display:flex;align-self:center;flex:0 0 auto;gap:8px;margin:0;padding:0;max-width:none}.connection-editor__canvas-controls .connection-editor__metric{display:grid;gap:2px;text-align:center;min-width:0;padding:0;background:none;border:0;box-shadow:none}.connection-editor__canvas-controls .connection-editor__minimap{position:relative;inset:auto;display:block;width:116px;height:64px;padding:18px 6px 4px;opacity:1;transform:none;box-shadow:none;border-radius:8px}.connection-editor__canvas-controls .connection-editor__trace-source{grid-column:1 / -1;display:flex;align-items:center;overflow-x:auto;max-height:44px;padding:3px;box-shadow:none;white-space:nowrap}.connection-editor__canvas-controls .connection-editor__trace-source>*{flex:0 0 auto}.connection-editor__map-surface:has(.connection-editor__compact)>.connection-editor__viewport,.connection-editor__stage:has(.connection-editor__compact) .connection-editor__canvas-tools,.connection-editor__stage:has(.connection-editor__compact) .connection-editor__minimap,.connection-editor__stage:has(.connection-editor__compact)>.connection-editor__canvas-controls:not(:has(.connection-editor__trace-source)){display:none}.connection-editor__stage:has(.connection-editor__compact)>.connection-editor__canvas-controls{grid-template-columns:minmax(0,1fr)}.connection-editor__stage:has(.connection-editor__compact){touch-action:pan-y;cursor:auto;background:var(--pce-stage-bg)}\n"] }]
|
|
12891
13218
|
}], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], focusWidget: [{ type: i0.Output, args: ["focusWidget"] }], openPageSettings: [{ type: i0.Output, args: ["openPageSettings"] }], close: [{ type: i0.Output, args: ["close"] }] } });
|
|
12892
13219
|
|
|
13220
|
+
/** Internal presentation of the existing catalog. Never rearranges or persists a page. */
|
|
13221
|
+
class PagePresetComparisonComponent {
|
|
13222
|
+
expanded = input(false, ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
|
|
13223
|
+
layoutId = input('', ...(ngDevMode ? [{ debugName: "layoutId" }] : /* istanbul ignore next */ []));
|
|
13224
|
+
themeId = input('', ...(ngDevMode ? [{ debugName: "themeId" }] : /* istanbul ignore next */ []));
|
|
13225
|
+
layoutPresetOptions = input(...(ngDevMode ? [undefined, { debugName: "layoutPresetOptions" }] : /* istanbul ignore next */ []));
|
|
13226
|
+
canvas = input(false, ...(ngDevMode ? [{ debugName: "canvas" }] : /* istanbul ignore next */ []));
|
|
13227
|
+
choose = output();
|
|
13228
|
+
presets = Object.values(BUILTIN_PAGE_LAYOUT_PRESETS);
|
|
13229
|
+
i18n = inject(PraxisI18nService);
|
|
13230
|
+
effectiveLayout = computed(() => resolveWidgetPageLayoutPreset({ layoutPreset: this.layoutId(), layoutPresetOptions: this.layoutPresetOptions() }), ...(ngDevMode ? [{ debugName: "effectiveLayout" }] : /* istanbul ignore next */ []));
|
|
13231
|
+
suggestedTheme = computed(() => {
|
|
13232
|
+
const id = this.effectiveLayout()?.defaultThemePreset;
|
|
13233
|
+
return id ? BUILTIN_PAGE_THEME_PRESETS[id] : undefined;
|
|
13234
|
+
}, ...(ngDevMode ? [{ debugName: "suggestedTheme" }] : /* istanbul ignore next */ []));
|
|
13235
|
+
effectiveTheme = computed(() => this.themeId() ? BUILTIN_PAGE_THEME_PRESETS[this.themeId()] : this.suggestedTheme(), ...(ngDevMode ? [{ debugName: "effectiveTheme" }] : /* istanbul ignore next */ []));
|
|
13236
|
+
t(key, fallback) { return resolvePraxisPageBuilderText(this.i18n, 'presets.' + key, fallback); }
|
|
13237
|
+
groupLabel(group) { return this.t('slot.' + group.id, 'label' in group ? group.label || group.id : group.id); }
|
|
13238
|
+
presetLabel(id, fallback) { return this.t('layout.' + id, fallback); }
|
|
13239
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PagePresetComparisonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13240
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PagePresetComparisonComponent, isStandalone: true, selector: "praxis-page-preset-comparison", inputs: { expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null }, layoutId: { classPropertyName: "layoutId", publicName: "layoutId", isSignal: true, isRequired: false, transformFunction: null }, themeId: { classPropertyName: "themeId", publicName: "themeId", isSignal: true, isRequired: false, transformFunction: null }, layoutPresetOptions: { classPropertyName: "layoutPresetOptions", publicName: "layoutPresetOptions", isSignal: true, isRequired: false, transformFunction: null }, canvas: { classPropertyName: "canvas", publicName: "canvas", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { choose: "choose" }, ngImport: i0, template: `
|
|
13241
|
+
<ng-template #context>
|
|
13242
|
+
<section class="preset-context" [attr.aria-label]="t('scope', 'Effect of this choice')">
|
|
13243
|
+
<strong>{{ t('scope', 'Effect of this choice') }}</strong>
|
|
13244
|
+
<p>{{ canvas() ? t('canvas', 'Canvas positions are preserved. This preset does not move or resize existing widgets.') : t('flow', 'Explicit layout and grouping take precedence over preset defaults. Selecting a preset does not create widgets or data connections.') }}</p>
|
|
13245
|
+
@if (effectiveLayout(); as layout) {
|
|
13246
|
+
@if (layout.id !== layoutId().trim()) {
|
|
13247
|
+
<p>{{ t('inheritedLayout', 'Inherited organization') }}: <strong>{{ presetLabel(layout.id, layout.label) }}</strong></p>
|
|
13248
|
+
}
|
|
13249
|
+
}
|
|
13250
|
+
<p class="theme" data-testid="preset-effective-theme"><span>{{ t('effectiveTheme', 'Effective page theme') }}</span><strong>{{ effectiveTheme()?.label || themeId() || t('hostTheme', 'Host theme') }}</strong>
|
|
13251
|
+
<span>{{ themeId() ? t('explicit', 'Explicit choice') : effectiveTheme() ? t('inherited', 'Inherited from layout') : t('host', 'No page theme override') }}</span>
|
|
13252
|
+
</p>
|
|
13253
|
+
@if (suggestedTheme(); as theme) {
|
|
13254
|
+
@if (themeId() && themeId() !== theme.id) {
|
|
13255
|
+
<p>{{ t('suggested', 'Suggested by the layout') }}: {{ theme.label }} · {{ t('notActive', 'not active') }}</p>
|
|
13256
|
+
}
|
|
13257
|
+
}
|
|
13258
|
+
</section>
|
|
13259
|
+
</ng-template>
|
|
13260
|
+
@if (!expanded()) { <ng-container [ngTemplateOutlet]="context" /> }
|
|
13261
|
+
<details class="comparison" [open]="expanded()" [class.expanded]="expanded()">
|
|
13262
|
+
<summary>{{ t('compare', 'Compare page organizations') }} · {{ presets.length }}</summary>
|
|
13263
|
+
<p class="hint">{{ t('diagramHelp', 'Role diagrams, not a preview of your page. Compare the structure, then select an organization. Existing widgets and connections are preserved.') }}</p>
|
|
13264
|
+
<div class="choices">
|
|
13265
|
+
@for (preset of presets; track preset.id) {
|
|
13266
|
+
<article [class.selected]="effectiveLayout()?.id === preset.id" [attr.data-layout-preset]="preset.id">
|
|
13267
|
+
<header><strong>{{ presetLabel(preset.id, preset.label) }}</strong>@if (effectiveLayout()?.id === preset.id) { <span>{{ t('selected', 'Selected') }}</span> }</header>
|
|
13268
|
+
<ul class="diagram" [attr.aria-label]="t('roles', 'Organization roles')">
|
|
13269
|
+
@for (group of preset.defaultGrouping || []; track group.id) {
|
|
13270
|
+
<li class="role" [class.wide]="group.kind === 'hero' || group.kind === 'tabs'" [class.rail]="group.kind === 'rail'">
|
|
13271
|
+
{{ groupLabel(group) }}
|
|
13272
|
+
</li>
|
|
13273
|
+
}
|
|
13274
|
+
</ul>
|
|
13275
|
+
<p>{{ t('use.' + preset.id, preset.description || preset.label) }}</p>
|
|
13276
|
+
<ul>
|
|
13277
|
+
@for (slot of preset.slotModel; track slot.id) {
|
|
13278
|
+
@if (slot.required) { <li>{{ t('slot.' + slot.id, slot.label) }} · {{ t('required', 'required role') }}</li> }
|
|
13279
|
+
}
|
|
13280
|
+
</ul>
|
|
13281
|
+
<button type="button" [attr.aria-pressed]="effectiveLayout()?.id === preset.id" (click)="choose.emit(preset.id)">{{ t('choose', 'Choose') }} {{ presetLabel(preset.id, preset.label) }}</button>
|
|
13282
|
+
</article>
|
|
13283
|
+
}
|
|
13284
|
+
</div>
|
|
13285
|
+
<p class="hint">{{ t('mobile', 'Mobile preferences describe the intended composition. Verify the actual responsive layout; selection alone does not hide content, create tabs or connect master and detail.') }}</p>
|
|
13286
|
+
</details>
|
|
13287
|
+
@if (expanded()) { <ng-container [ngTemplateOutlet]="context" /> }
|
|
13288
|
+
`, isInline: true, styles: [":host{display:block;container-type:inline-size;color:var(--md-sys-color-on-surface)}p{margin:var(--p-space-2,8px) 0;line-height:1.5}.preset-context{border-inline-start:3px solid var(--md-sys-color-primary);padding:var(--p-space-3,12px);background:var(--md-sys-color-surface-container-low)}.theme{display:flex;flex-wrap:wrap;gap:var(--p-space-2,8px)}.theme span,.hint,li{color:var(--md-sys-color-on-surface-variant);font-size:var(--p-font-size-sm,12px)}.expanded>summary{display:none}summary{cursor:pointer;padding-block:var(--p-space-3,12px);font-weight:600}.choices{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--p-space-3,12px)}article{padding:var(--p-space-3,12px);border:1px solid var(--md-sys-color-outline-variant);border-radius:var(--p-radius-md,8px);display:flex;flex-direction:column;min-width:0}article.selected{border-color:var(--md-sys-color-primary);background:var(--md-sys-color-surface-container-low)}header{display:flex;flex-wrap:wrap;justify-content:space-between;gap:var(--p-space-2,8px)}header span{font-size:var(--p-font-size-sm,12px);color:var(--md-sys-color-primary)}.diagram{list-style:none;padding:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--p-space-1,4px);margin-block:var(--p-space-3,12px)}.role{background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container);padding:var(--p-space-2,8px);font-size:var(--p-font-size-sm,12px);overflow-wrap:anywhere}.wide{grid-column:1 / -1}.rail{border-inline-start:3px solid var(--md-sys-color-primary)}ul{padding-inline-start:var(--p-space-4,16px)}button{margin-top:auto;min-height:44px;padding:var(--p-space-2,8px);border:1px solid var(--md-sys-color-outline);border-radius:var(--p-radius-sm,4px);background:var(--md-sys-color-surface);color:var(--md-sys-color-primary);font:inherit;cursor:pointer;text-align:start}button:hover{background:var(--md-sys-color-surface-container)}button:focus-visible,summary:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:3px}@container (max-width:520px){.choices{grid-template-columns:minmax(0,1fr)}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
13289
|
+
}
|
|
13290
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PagePresetComparisonComponent, decorators: [{
|
|
13291
|
+
type: Component,
|
|
13292
|
+
args: [{ selector: 'praxis-page-preset-comparison', imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
13293
|
+
<ng-template #context>
|
|
13294
|
+
<section class="preset-context" [attr.aria-label]="t('scope', 'Effect of this choice')">
|
|
13295
|
+
<strong>{{ t('scope', 'Effect of this choice') }}</strong>
|
|
13296
|
+
<p>{{ canvas() ? t('canvas', 'Canvas positions are preserved. This preset does not move or resize existing widgets.') : t('flow', 'Explicit layout and grouping take precedence over preset defaults. Selecting a preset does not create widgets or data connections.') }}</p>
|
|
13297
|
+
@if (effectiveLayout(); as layout) {
|
|
13298
|
+
@if (layout.id !== layoutId().trim()) {
|
|
13299
|
+
<p>{{ t('inheritedLayout', 'Inherited organization') }}: <strong>{{ presetLabel(layout.id, layout.label) }}</strong></p>
|
|
13300
|
+
}
|
|
13301
|
+
}
|
|
13302
|
+
<p class="theme" data-testid="preset-effective-theme"><span>{{ t('effectiveTheme', 'Effective page theme') }}</span><strong>{{ effectiveTheme()?.label || themeId() || t('hostTheme', 'Host theme') }}</strong>
|
|
13303
|
+
<span>{{ themeId() ? t('explicit', 'Explicit choice') : effectiveTheme() ? t('inherited', 'Inherited from layout') : t('host', 'No page theme override') }}</span>
|
|
13304
|
+
</p>
|
|
13305
|
+
@if (suggestedTheme(); as theme) {
|
|
13306
|
+
@if (themeId() && themeId() !== theme.id) {
|
|
13307
|
+
<p>{{ t('suggested', 'Suggested by the layout') }}: {{ theme.label }} · {{ t('notActive', 'not active') }}</p>
|
|
13308
|
+
}
|
|
13309
|
+
}
|
|
13310
|
+
</section>
|
|
13311
|
+
</ng-template>
|
|
13312
|
+
@if (!expanded()) { <ng-container [ngTemplateOutlet]="context" /> }
|
|
13313
|
+
<details class="comparison" [open]="expanded()" [class.expanded]="expanded()">
|
|
13314
|
+
<summary>{{ t('compare', 'Compare page organizations') }} · {{ presets.length }}</summary>
|
|
13315
|
+
<p class="hint">{{ t('diagramHelp', 'Role diagrams, not a preview of your page. Compare the structure, then select an organization. Existing widgets and connections are preserved.') }}</p>
|
|
13316
|
+
<div class="choices">
|
|
13317
|
+
@for (preset of presets; track preset.id) {
|
|
13318
|
+
<article [class.selected]="effectiveLayout()?.id === preset.id" [attr.data-layout-preset]="preset.id">
|
|
13319
|
+
<header><strong>{{ presetLabel(preset.id, preset.label) }}</strong>@if (effectiveLayout()?.id === preset.id) { <span>{{ t('selected', 'Selected') }}</span> }</header>
|
|
13320
|
+
<ul class="diagram" [attr.aria-label]="t('roles', 'Organization roles')">
|
|
13321
|
+
@for (group of preset.defaultGrouping || []; track group.id) {
|
|
13322
|
+
<li class="role" [class.wide]="group.kind === 'hero' || group.kind === 'tabs'" [class.rail]="group.kind === 'rail'">
|
|
13323
|
+
{{ groupLabel(group) }}
|
|
13324
|
+
</li>
|
|
13325
|
+
}
|
|
13326
|
+
</ul>
|
|
13327
|
+
<p>{{ t('use.' + preset.id, preset.description || preset.label) }}</p>
|
|
13328
|
+
<ul>
|
|
13329
|
+
@for (slot of preset.slotModel; track slot.id) {
|
|
13330
|
+
@if (slot.required) { <li>{{ t('slot.' + slot.id, slot.label) }} · {{ t('required', 'required role') }}</li> }
|
|
13331
|
+
}
|
|
13332
|
+
</ul>
|
|
13333
|
+
<button type="button" [attr.aria-pressed]="effectiveLayout()?.id === preset.id" (click)="choose.emit(preset.id)">{{ t('choose', 'Choose') }} {{ presetLabel(preset.id, preset.label) }}</button>
|
|
13334
|
+
</article>
|
|
13335
|
+
}
|
|
13336
|
+
</div>
|
|
13337
|
+
<p class="hint">{{ t('mobile', 'Mobile preferences describe the intended composition. Verify the actual responsive layout; selection alone does not hide content, create tabs or connect master and detail.') }}</p>
|
|
13338
|
+
</details>
|
|
13339
|
+
@if (expanded()) { <ng-container [ngTemplateOutlet]="context" /> }
|
|
13340
|
+
`, styles: [":host{display:block;container-type:inline-size;color:var(--md-sys-color-on-surface)}p{margin:var(--p-space-2,8px) 0;line-height:1.5}.preset-context{border-inline-start:3px solid var(--md-sys-color-primary);padding:var(--p-space-3,12px);background:var(--md-sys-color-surface-container-low)}.theme{display:flex;flex-wrap:wrap;gap:var(--p-space-2,8px)}.theme span,.hint,li{color:var(--md-sys-color-on-surface-variant);font-size:var(--p-font-size-sm,12px)}.expanded>summary{display:none}summary{cursor:pointer;padding-block:var(--p-space-3,12px);font-weight:600}.choices{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--p-space-3,12px)}article{padding:var(--p-space-3,12px);border:1px solid var(--md-sys-color-outline-variant);border-radius:var(--p-radius-md,8px);display:flex;flex-direction:column;min-width:0}article.selected{border-color:var(--md-sys-color-primary);background:var(--md-sys-color-surface-container-low)}header{display:flex;flex-wrap:wrap;justify-content:space-between;gap:var(--p-space-2,8px)}header span{font-size:var(--p-font-size-sm,12px);color:var(--md-sys-color-primary)}.diagram{list-style:none;padding:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--p-space-1,4px);margin-block:var(--p-space-3,12px)}.role{background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container);padding:var(--p-space-2,8px);font-size:var(--p-font-size-sm,12px);overflow-wrap:anywhere}.wide{grid-column:1 / -1}.rail{border-inline-start:3px solid var(--md-sys-color-primary)}ul{padding-inline-start:var(--p-space-4,16px)}button{margin-top:auto;min-height:44px;padding:var(--p-space-2,8px);border:1px solid var(--md-sys-color-outline);border-radius:var(--p-radius-sm,4px);background:var(--md-sys-color-surface);color:var(--md-sys-color-primary);font:inherit;cursor:pointer;text-align:start}button:hover{background:var(--md-sys-color-surface-container)}button:focus-visible,summary:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:3px}@container (max-width:520px){.choices{grid-template-columns:minmax(0,1fr)}}\n"] }]
|
|
13341
|
+
}], propDecorators: { expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }], layoutId: [{ type: i0.Input, args: [{ isSignal: true, alias: "layoutId", required: false }] }], themeId: [{ type: i0.Input, args: [{ isSignal: true, alias: "themeId", required: false }] }], layoutPresetOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "layoutPresetOptions", required: false }] }], canvas: [{ type: i0.Input, args: [{ isSignal: true, alias: "canvas", required: false }] }], choose: [{ type: i0.Output, args: ["choose"] }] } });
|
|
13342
|
+
|
|
12893
13343
|
/** Preserve layout semantics while clearing an authored inset back to the host token. */
|
|
12894
13344
|
function withPagePaddingInline(layout, value) {
|
|
12895
13345
|
const next = { ...layout };
|
|
@@ -13095,11 +13545,122 @@ const THEME_PRESET_ENTRIES = Object.values(BUILTIN_PAGE_THEME_PRESETS).map((pres
|
|
|
13095
13545
|
const SHELL_PRESET_ENTRIES = BUILTIN_WIDGET_SHELL_PRESETS;
|
|
13096
13546
|
const integerColumnValidator = (control) => control.value == null || Number.isInteger(control.value) ? null : { integer: true };
|
|
13097
13547
|
class DynamicPageConfigEditorComponent {
|
|
13098
|
-
set page(value) { this.pageInput = value ?? null; this.hydrate(); }
|
|
13548
|
+
set page(value) { this.pageInput = value ?? null; this.hydrate(true); }
|
|
13099
13549
|
layout;
|
|
13100
13550
|
presets;
|
|
13101
13551
|
pagePreset;
|
|
13102
13552
|
pageChange = new EventEmitter();
|
|
13553
|
+
arrangementFeedback;
|
|
13554
|
+
focusArrangementFeedback = false;
|
|
13555
|
+
workspaceTab = signal(0, ...(ngDevMode ? [{ debugName: "workspaceTab" }] : /* istanbul ignore next */ []));
|
|
13556
|
+
arrangementSelection = signal('', ...(ngDevMode ? [{ debugName: "arrangementSelection" }] : /* istanbul ignore next */ []));
|
|
13557
|
+
// Transient authoring choices, expressed through the existing Core width contract.
|
|
13558
|
+
arrangementChoices = [
|
|
13559
|
+
{ id: 'stack', weights: [1] }, { id: 'compare', weights: [1, 1] },
|
|
13560
|
+
{ id: 'primary', weights: [2, 1] }, { id: 'secondary', weights: [1, 2] },
|
|
13561
|
+
{ id: 'grid', weights: [1, 1, 1] }, { id: 'center', weights: [1, 2, 1] },
|
|
13562
|
+
{ id: 'wide-left', weights: [3, 1] }, { id: 'wide-right', weights: [1, 3] },
|
|
13563
|
+
{ id: 'four', weights: [1, 1, 1, 1] },
|
|
13564
|
+
];
|
|
13565
|
+
arrangementWidths(id) {
|
|
13566
|
+
const weights = this.arrangementChoices.find(choice => choice.id === id)?.weights || [1];
|
|
13567
|
+
const columns = this.canvasColumnsControl.value || 0;
|
|
13568
|
+
if (!Number.isInteger(columns) || columns < 1)
|
|
13569
|
+
return weights.map(() => 0);
|
|
13570
|
+
const total = weights.reduce((sum, value) => sum + value, 0);
|
|
13571
|
+
const quotas = weights.map(weight => columns * weight / total);
|
|
13572
|
+
const widths = quotas.map(Math.floor);
|
|
13573
|
+
// Apportion indivisible columns by fractional remainder, keeping balanced lanes
|
|
13574
|
+
// within one column and favoring the wider lane when asymmetric quotas tie.
|
|
13575
|
+
const ranking = quotas.map((quota, index) => ({ index, remainder: quota - widths[index] }))
|
|
13576
|
+
.sort((a, b) => b.remainder - a.remainder || weights[b.index] - weights[a.index] || b.index - a.index);
|
|
13577
|
+
const remaining = columns - widths.reduce((sum, value) => sum + value, 0);
|
|
13578
|
+
for (let index = 0; index < remaining; index++)
|
|
13579
|
+
widths[ranking[index].index]++;
|
|
13580
|
+
return widths;
|
|
13581
|
+
}
|
|
13582
|
+
arrangementFits(id) { return this.arrangementWidths(id).every(width => Number.isInteger(width) && width > 0); }
|
|
13583
|
+
themeTokens(id) { return BUILTIN_PAGE_THEME_PRESETS[id]?.tokens || {}; }
|
|
13584
|
+
shellName(id) { const preset = this.shellPresetEntries.find(item => item.id === id); return preset ? this.shellPresetLabel(preset) : id; }
|
|
13585
|
+
shellDescription(preset) { return this.i18n.resolve(preset.description, ''); }
|
|
13586
|
+
previewRowCount(before, after) {
|
|
13587
|
+
return Math.max(1, ...Object.values(before.items).map(item => item.row + item.rowSpan - 1), ...Object.values(after.items).map(item => item.row + item.rowSpan - 1));
|
|
13588
|
+
}
|
|
13589
|
+
previewItems(canvas) {
|
|
13590
|
+
const source = this.arrangementPreview()?.before || canvas;
|
|
13591
|
+
return Object.keys(source.items).sort((a, b) => source.items[a].row - source.items[b].row || source.items[a].col - source.items[b].col || a.localeCompare(b)).map((key, index) => ({
|
|
13592
|
+
...canvas.items[key], key, number: index + 1,
|
|
13593
|
+
label: this.canvasLimits.targets.find(target => target.scope === 'base' && target.key === key)?.title || key,
|
|
13594
|
+
}));
|
|
13595
|
+
}
|
|
13596
|
+
arrangementPreview = signal(null, ...(ngDevMode ? [{ debugName: "arrangementPreview" }] : /* istanbul ignore next */ []));
|
|
13597
|
+
arrangementMessage = signal('', ...(ngDevMode ? [{ debugName: "arrangementMessage" }] : /* istanbul ignore next */ []));
|
|
13598
|
+
arrangementUndoAvailable = signal(false, ...(ngDevMode ? [{ debugName: "arrangementUndoAvailable" }] : /* istanbul ignore next */ []));
|
|
13599
|
+
arrangedItems;
|
|
13600
|
+
arrangementAppliedDocument = '';
|
|
13601
|
+
arrangementSourcePage = null;
|
|
13602
|
+
arrangementGeometry = '';
|
|
13603
|
+
previousArrangementItems;
|
|
13604
|
+
prepareArrangement(id) {
|
|
13605
|
+
this.arrangementPreview.set(null);
|
|
13606
|
+
const page = this.buildCurrentPage(true);
|
|
13607
|
+
if (!page)
|
|
13608
|
+
return;
|
|
13609
|
+
this.arrangementSelection.set('');
|
|
13610
|
+
const widths = this.arrangementWidths(id);
|
|
13611
|
+
const proposal = prepareCanvasArrangement(page, widths);
|
|
13612
|
+
this.focusArrangementFeedback = true;
|
|
13613
|
+
if (!proposal.canvas) {
|
|
13614
|
+
this.arrangementMessage.set(this.tx('editor.arrange.' + proposal.reason, 'Review the canvas before arranging widgets.')
|
|
13615
|
+
+ (proposal.blockedKeys.length ? ' · ' + proposal.blockedKeys.join(', ') : ''));
|
|
13616
|
+
return;
|
|
13617
|
+
}
|
|
13618
|
+
this.arrangementSelection.set(id);
|
|
13619
|
+
this.arrangementMessage.set(proposal.changedKeys.length ? '' : this.tx('editor.arrange.unchanged', 'This arrangement already matches the page.'));
|
|
13620
|
+
if (proposal.changedKeys.length)
|
|
13621
|
+
this.arrangementPreview.set({ source: this.draftFingerprint(), before: page.canvas, canvas: proposal.canvas, keys: proposal.changedKeys });
|
|
13622
|
+
}
|
|
13623
|
+
useArrangement() {
|
|
13624
|
+
const preview = this.arrangementPreview();
|
|
13625
|
+
if (!preview)
|
|
13626
|
+
return;
|
|
13627
|
+
if (preview.source !== this.draftFingerprint()) {
|
|
13628
|
+
this.arrangementPreview.set(null);
|
|
13629
|
+
this.arrangementMessage.set(this.tx('editor.arrange.stale', 'The draft changed. Prepare the arrangement again.'));
|
|
13630
|
+
return;
|
|
13631
|
+
}
|
|
13632
|
+
const current = this.buildCurrentPage(true);
|
|
13633
|
+
if (!current?.canvas) {
|
|
13634
|
+
this.arrangementPreview.set(null);
|
|
13635
|
+
this.arrangementMessage.set(this.tx('editor.arrange.stale', 'The draft changed. Prepare the arrangement again.'));
|
|
13636
|
+
return;
|
|
13637
|
+
}
|
|
13638
|
+
this.arrangementSourcePage = this.clone(current);
|
|
13639
|
+
this.previousArrangementItems = this.clone(current.canvas.items);
|
|
13640
|
+
this.arrangedItems = preview.canvas.items;
|
|
13641
|
+
this.arrangementGeometry = this.geometryFingerprint(preview.canvas);
|
|
13642
|
+
this.arrangementUndoAvailable.set(true);
|
|
13643
|
+
this.arrangementPreview.set(null);
|
|
13644
|
+
this.arrangementMessage.set(this.tx('editor.arrange.staged', 'Arrangement added to the draft. Use Apply to preview it on the page.'));
|
|
13645
|
+
this.refreshPanelState();
|
|
13646
|
+
this.arrangementAppliedDocument = JSON.stringify(this.buildCurrentPage(false));
|
|
13647
|
+
}
|
|
13648
|
+
undoArrangement() {
|
|
13649
|
+
if (!this.arrangementUndoAvailable())
|
|
13650
|
+
return;
|
|
13651
|
+
this.arrangementSelection.set('');
|
|
13652
|
+
const current = this.arrangedItems ?? this.currentPageInput()?.canvas?.items ?? {};
|
|
13653
|
+
const restored = Object.fromEntries(Object.entries(current).map(([key, item]) => {
|
|
13654
|
+
const previous = this.previousArrangementItems?.[key];
|
|
13655
|
+
return [key, previous ? { ...item, col: previous.col, row: previous.row,
|
|
13656
|
+
colSpan: previous.colSpan, rowSpan: previous.rowSpan } : item];
|
|
13657
|
+
}));
|
|
13658
|
+
this.arrangedItems = JSON.stringify(restored) === JSON.stringify(this.currentPageInput()?.canvas?.items) ? undefined : restored;
|
|
13659
|
+
this.arrangementUndoAvailable.set(false);
|
|
13660
|
+
this.arrangementPreview.set(null);
|
|
13661
|
+
this.arrangementMessage.set(this.tx('editor.arrange.undone', 'The previous arrangement is restored in the draft. Use Apply to update the page.'));
|
|
13662
|
+
this.refreshPanelState();
|
|
13663
|
+
}
|
|
13103
13664
|
layoutPresetEntries = LAYOUT_PRESET_ENTRIES;
|
|
13104
13665
|
themePresetEntries = THEME_PRESET_ENTRIES;
|
|
13105
13666
|
shellPresetEntries = SHELL_PRESET_ENTRIES;
|
|
@@ -13142,7 +13703,14 @@ class DynamicPageConfigEditorComponent {
|
|
|
13142
13703
|
renderedValidationFingerprint = '';
|
|
13143
13704
|
tabOrder = ['canvas', 'grouping', 'devices', 'context', 'state', 'limits'];
|
|
13144
13705
|
constructor() {
|
|
13145
|
-
|
|
13706
|
+
this.injectedData?.appliedValues$?.pipe(takeUntilDestroyed()).subscribe((value) => this.acceptAppliedValue(value));
|
|
13707
|
+
afterEveryRender({ write: () => {
|
|
13708
|
+
if (this.focusArrangementFeedback && this.arrangementFeedback) {
|
|
13709
|
+
this.focusArrangementFeedback = false;
|
|
13710
|
+
this.arrangementFeedback.nativeElement.focus({ preventScroll: true });
|
|
13711
|
+
this.arrangementFeedback.nativeElement.scrollIntoView({ block: 'nearest' });
|
|
13712
|
+
}
|
|
13713
|
+
}, read: () => this.refreshRenderedValidation() });
|
|
13146
13714
|
merge(this.canvasLimits.form.valueChanges, this.canvasColumnsControl.valueChanges, this.canvasRowUnitControl.valueChanges, this.canvasGapControl.valueChanges, this.paddingInlineControl.valueChanges, this.canvasAutoRowsControl.valueChanges, this.layoutPresetControl.valueChanges, this.themePresetControl.valueChanges, this.shellPresetControl.valueChanges, this.contextControl.valueChanges, this.stateControl.valueChanges, this.groupingNodes.valueChanges, this.deviceForms.desktop.valueChanges, this.deviceForms.tablet.valueChanges, this.deviceForms.mobile.valueChanges).pipe(takeUntilDestroyed()).subscribe(() => { if (!this.hydrating)
|
|
13147
13715
|
this.refreshPanelState(); });
|
|
13148
13716
|
}
|
|
@@ -13168,13 +13736,27 @@ class DynamicPageConfigEditorComponent {
|
|
|
13168
13736
|
}
|
|
13169
13737
|
get selectedLayoutPresetLabel() { return this.tx('editor.summary.layout', 'Layout: {{label}}').replace('{{label}}', this.lookupPresetLabel(this.layoutPresetEntries, this.layoutPresetControl.value, this.tx('editor.fallback.manual', 'manual'))); }
|
|
13170
13738
|
get selectedThemePresetLabel() { return this.tx('editor.summary.theme', 'Theme: {{label}}').replace('{{label}}', this.lookupPresetLabel(this.themePresetEntries, this.themePresetControl.value, this.tx('editor.layout.layoutDefault', 'Layout default'))); }
|
|
13171
|
-
get selectedLayoutPresetEntry() { return this.layoutPresetEntries.find(
|
|
13739
|
+
get selectedLayoutPresetEntry() { const id = resolveWidgetPageLayoutPreset({ layoutPreset: this.layoutPresetControl.value, layoutPresetOptions: this.layoutPresetOptions })?.id; return this.layoutPresetEntries.find(entry => entry.id === id) || null; }
|
|
13172
13740
|
get selectedThemePresetEntry() { return this.themePresetEntries.find((entry) => entry.id === this.themePresetControl.value) || null; }
|
|
13173
13741
|
get recommendedThemePresetEntry() { const themeId = this.selectedLayoutPresetEntry?.defaultThemePreset; return themeId ? this.themePresetEntries.find((entry) => entry.id === themeId) || null : null; }
|
|
13174
13742
|
get canvasSummaryLabel() { return this.tx('editor.summary.canvasColumns', 'Canvas: {{count}} columns').replace('{{count}}', String(this.canvasColumnsControl.value)); }
|
|
13175
|
-
|
|
13743
|
+
focusEditorTab(tab) { this.workspaceTab.set(3); this.activeTab.set(tab); }
|
|
13744
|
+
selectDetailTab(index) { this.activeTab.set(this.tabOrder[index] || 'canvas'); }
|
|
13745
|
+
onSelectedTabIndexChange(index) { this.focusEditorTab(this.tabOrder[index] || 'canvas'); }
|
|
13176
13746
|
getSettingsValue() { return this.buildCurrentPage(true); }
|
|
13177
13747
|
onSave() { return this.getSettingsValue(); }
|
|
13748
|
+
acceptAppliedValue(value) {
|
|
13749
|
+
// Only the document owner acknowledges successful Apply. Merely reading the
|
|
13750
|
+
// provider for a preview must never advance its baseline.
|
|
13751
|
+
if (!value || JSON.stringify(value) !== JSON.stringify(this.buildCurrentPage(false)))
|
|
13752
|
+
return;
|
|
13753
|
+
const accepted = this.buildCurrentPage(false);
|
|
13754
|
+
if (this.arrangementUndoAvailable() && this.geometryFingerprint(accepted.canvas) === this.arrangementGeometry) {
|
|
13755
|
+
this.arrangementAppliedDocument = JSON.stringify(value);
|
|
13756
|
+
}
|
|
13757
|
+
this.pageInput = JSON.stringify(value);
|
|
13758
|
+
this.hydrate(true);
|
|
13759
|
+
}
|
|
13178
13760
|
reset() { this.hydrate(); }
|
|
13179
13761
|
pinRecommendedThemePreset() { const theme = this.recommendedThemePresetEntry; if (theme)
|
|
13180
13762
|
this.themePresetControl.setValue(theme.id); }
|
|
@@ -13221,7 +13803,7 @@ class DynamicPageConfigEditorComponent {
|
|
|
13221
13803
|
preset.preferTabsOnMobile ? this.tx('editor.preview.prefersMobileTabs', 'prefers mobile tabs') : '',
|
|
13222
13804
|
preset.hideSecondaryContentOnMobile ? this.tx('editor.preview.hidesSecondaryContent', 'hides secondary content') : '',
|
|
13223
13805
|
].filter(Boolean);
|
|
13224
|
-
return policies.join(' · ') || this.tx('editor.preview.noDevicePolicy', 'No specific mobile policy');
|
|
13806
|
+
return this.tx('presets.declaredPolicy', 'Declared preference, not an automatic transformation') + ': ' + (policies.join(' · ') || this.tx('editor.preview.noDevicePolicy', 'No specific mobile policy'));
|
|
13225
13807
|
}
|
|
13226
13808
|
inheritedThemeHelp(theme) { return this.themePresetControl.value ? theme.description : this.tx('editor.preview.inheritedThemeHelp', '{{description}} This theme is inherited automatically while the theme field stays on layout default.').replace('{{description}}', theme.description); }
|
|
13227
13809
|
groupLabel(index) { return this.tx('editor.grouping.groupN', 'Group {{index}}').replace('{{index}}', String(index + 1)); }
|
|
@@ -13251,7 +13833,7 @@ class DynamicPageConfigEditorComponent {
|
|
|
13251
13833
|
this.layoutPresetControl.value, this.themePresetControl.value, this.shellPresetControl.value,
|
|
13252
13834
|
this.contextControl.value, this.stateControl.value, this.groupingNodes.getRawValue(),
|
|
13253
13835
|
this.deviceKeys.map(key => this.deviceForms[key].getRawValue()),
|
|
13254
|
-
this.canvasLimits.form.getRawValue(), [...this.stackedDevices],
|
|
13836
|
+
this.canvasLimits.form.getRawValue(), [...this.stackedDevices], this.arrangedItems,
|
|
13255
13837
|
]);
|
|
13256
13838
|
}
|
|
13257
13839
|
limitsGeometryLabel() {
|
|
@@ -13270,10 +13852,26 @@ class DynamicPageConfigEditorComponent {
|
|
|
13270
13852
|
const form = target && target.scope !== 'base' ? this.deviceForms[target.scope] : undefined;
|
|
13271
13853
|
return (form?.controls.autoRows.value || this.canvasAutoRowsControl.value) === 'content';
|
|
13272
13854
|
}
|
|
13855
|
+
geometryFingerprint(canvas) {
|
|
13856
|
+
return JSON.stringify([canvas?.columns, Object.entries(canvas?.items ?? {}).sort(([a], [b]) => a.localeCompare(b))
|
|
13857
|
+
.map(([key, item]) => [key, item.col, item.row, item.colSpan, item.rowSpan])]);
|
|
13858
|
+
}
|
|
13273
13859
|
applyCanvasLimits(page, reportErrors) {
|
|
13274
13860
|
this.renderedCandidate = null;
|
|
13275
13861
|
const issue = this.canvasLimits.apply(page);
|
|
13276
13862
|
if (!issue) {
|
|
13863
|
+
if (this.arrangementSourcePage) {
|
|
13864
|
+
const arrangementIssue = validateCanvasArrangement(page, this.arrangementSourcePage);
|
|
13865
|
+
if (arrangementIssue) {
|
|
13866
|
+
this.canvasError.set(this.tx('editor.arrange.' + arrangementIssue.reason, 'Review the canvas before arranging widgets.')
|
|
13867
|
+
+ (arrangementIssue.blockedKeys.length ? ' · ' + arrangementIssue.blockedKeys.join(', ') : ''));
|
|
13868
|
+
if (reportErrors) {
|
|
13869
|
+
this.focusEditorTab('canvas');
|
|
13870
|
+
this.isValid$.next(false);
|
|
13871
|
+
}
|
|
13872
|
+
return null;
|
|
13873
|
+
}
|
|
13874
|
+
}
|
|
13277
13875
|
this.limitsError.set(null);
|
|
13278
13876
|
// Optional transient runtime validation supplied through SETTINGS_PANEL_DATA.
|
|
13279
13877
|
// The canonical output remains the page only, never the validator/measurements.
|
|
@@ -13291,7 +13889,7 @@ class DynamicPageConfigEditorComponent {
|
|
|
13291
13889
|
this.limitsTargetControl.setValue(index);
|
|
13292
13890
|
}
|
|
13293
13891
|
if (reportErrors) {
|
|
13294
|
-
this.
|
|
13892
|
+
this.focusEditorTab('limits');
|
|
13295
13893
|
this.isValid$.next(false);
|
|
13296
13894
|
}
|
|
13297
13895
|
return null;
|
|
@@ -13306,7 +13904,7 @@ class DynamicPageConfigEditorComponent {
|
|
|
13306
13904
|
this.limitsError.set(this.limitsTargetLabel(issue.index) + ': ' + messages[issue.code]);
|
|
13307
13905
|
if (reportErrors) {
|
|
13308
13906
|
this.limitsTargetControl.setValue(issue.index);
|
|
13309
|
-
this.
|
|
13907
|
+
this.focusEditorTab('limits');
|
|
13310
13908
|
this.isValid$.next(false);
|
|
13311
13909
|
}
|
|
13312
13910
|
return null;
|
|
@@ -13326,7 +13924,13 @@ class DynamicPageConfigEditorComponent {
|
|
|
13326
13924
|
}
|
|
13327
13925
|
createDeviceVariantForm() { return new FormGroup({ columns: new FormControl(null, { validators: [Validators.min(1), integerColumnValidator] }), rowUnit: new FormControl('', { nonNullable: true }), gap: new FormControl('', { nonNullable: true }), paddingInline: new FormControl('', { nonNullable: true }), autoRows: new FormControl('', { nonNullable: true }), groupingOverrides: new FormControl('', { nonNullable: true }), widgetOverrides: new FormControl('', { nonNullable: true }) }); }
|
|
13328
13926
|
createGroupingNodeGroup(seed) { const label = seed && 'label' in seed && typeof seed.label === 'string' ? seed.label : ''; return new FormGroup({ kind: new FormControl(seed?.kind || 'section', { nonNullable: true }), id: new FormControl(seed?.id || '', { nonNullable: true, validators: [Validators.required] }), label: new FormControl(label, { nonNullable: true }), widgetKeysCsv: new FormControl(this.stringifyGroupingWidgetKeys(seed), { nonNullable: true }), layout: new FormControl(seed?.kind === 'section' ? seed.layout || '' : '', { nonNullable: true }), emphasis: new FormControl(seed?.kind === 'hero' ? seed.emphasis || '' : '', { nonNullable: true }), side: new FormControl(seed?.kind === 'rail' ? seed.side || 'right' : 'right', { nonNullable: true }), tabsJson: new FormControl(seed?.kind === 'tabs' ? JSON.stringify(seed.tabs || [], null, 2) : '', { nonNullable: true }) }); }
|
|
13329
|
-
hydrate() { this.
|
|
13927
|
+
hydrate(preserveEcho = false) { this.arrangementSelection.set(''); this.focusArrangementFeedback = false; const keepUndo = preserveEcho && this.arrangementUndoAvailable() && this.arrangementAppliedDocument === JSON.stringify(this.currentPageInput()); const undoItems = this.previousArrangementItems; this.arrangementPreview.set(null); this.arrangementMessage.set(''); this.arrangedItems = undefined; this.previousArrangementItems = undefined; this.arrangementUndoAvailable.set(keepUndo); if (keepUndo)
|
|
13928
|
+
this.previousArrangementItems = undoItems;
|
|
13929
|
+
else {
|
|
13930
|
+
this.arrangementAppliedDocument = '';
|
|
13931
|
+
this.arrangementSourcePage = null;
|
|
13932
|
+
this.arrangementGeometry = '';
|
|
13933
|
+
} this.stackedDevices.clear(); const page = this.currentPageInput() || { widgets: [] }; const canvas = page.canvas || this.defaultCanvas(); const context = this.clone(page.context || {}); this.hydrating = true; this.canvasLimits.hydrate(page); this.limitsTargetControl.setValue(0, { emitEvent: false }); this.limitsError.set(null); this.canvasColumnsControl.setValue(canvas.columns || 12, { emitEvent: false }); this.canvasRowUnitControl.setValue(canvas.rowUnit || '80px', { emitEvent: false }); this.canvasGapControl.setValue(canvas.gap || '16px', { emitEvent: false }); this.paddingInlineControl.setValue(page.layout?.paddingInline || '', { emitEvent: false }); this.canvasAutoRowsControl.setValue(canvas.autoRows || 'fixed', { emitEvent: false }); this.layoutPresetControl.setValue(page.layoutPreset || '', { emitEvent: false }); this.themePresetControl.setValue(page.themePreset || '', { emitEvent: false }); this.shellPresetControl.setValue(this.extractShellPreset(context) || '', { emitEvent: false }); this.replaceGroupingNodes(page.grouping); this.contextControl.setValue(this.stringify(this.removeShellPresetFromContext(context)), { emitEvent: false }); this.stateControl.setValue(this.stringify(page.state || {}), { emitEvent: false }); for (const device of this.deviceKeys) {
|
|
13330
13934
|
const variant = page.deviceLayouts?.[device];
|
|
13331
13935
|
this.deviceForms[device].setValue({ columns: variant?.canvas?.columns ?? null, rowUnit: variant?.canvas?.rowUnit || '', gap: variant?.canvas?.gap || '', paddingInline: variant?.layout?.paddingInline || '', autoRows: variant?.canvas?.autoRows || '', groupingOverrides: this.stringify(variant?.groupingOverrides || []), widgetOverrides: this.stringify(variant?.widgetOverrides || {}) }, { emitEvent: false });
|
|
13332
13936
|
} this.canvasError.set(null); this.groupingError.set(null); this.deviceError.set(null); this.contextError.set(null); this.stateError.set(null); this.hydrating = false; this.refreshPanelState(true); }
|
|
@@ -13337,7 +13941,8 @@ class DynamicPageConfigEditorComponent {
|
|
|
13337
13941
|
return null; this.groupingError.set(null); const context = this.parseRecordJson(this.contextControl.value, {}); if (context === null)
|
|
13338
13942
|
return this.invalidate('context', this.contextError, reportErrors, this.tx('editor.errors.contextInvalid', 'Invalid context')); this.contextError.set(null); const state = this.parseRecordJson(this.stateControl.value, {}); if (state === null)
|
|
13339
13943
|
return this.invalidate('state', this.stateError, reportErrors, this.tx('editor.errors.stateInvalid', 'Invalid state')); this.stateError.set(null); const deviceLayouts = this.buildDeviceLayouts(reportErrors, next.deviceLayouts); if (deviceLayouts === null)
|
|
13340
|
-
return null; next.canvas =
|
|
13944
|
+
return null; next.canvas = this.buildCanvas(next); if (next.canvas && this.arrangedItems)
|
|
13945
|
+
next.canvas = { ...next.canvas, items: this.clone(this.arrangedItems) }; next.layout = withPagePaddingInline(next.layout, this.paddingInlineControl.value); next.layoutPreset = this.layoutPresetControl.value.trim() || undefined; next.themePreset = this.themePresetControl.value.trim() || undefined; next.grouping = grouping.length ? grouping : undefined; next.deviceLayouts = Object.keys(deviceLayouts).length ? deviceLayouts : undefined; next.context = this.buildPageContext(next.context, context); next.state = Object.keys(state).length ? state : undefined; return this.applyCanvasLimits(next, reportErrors); }
|
|
13341
13946
|
buildGrouping(reportErrors) { const grouping = []; const seenIds = new Set(); for (const group of this.groupingNodes.controls) {
|
|
13342
13947
|
const kind = group.controls.kind.value;
|
|
13343
13948
|
const id = group.controls.id.value.trim();
|
|
@@ -13367,6 +13972,37 @@ class DynamicPageConfigEditorComponent {
|
|
|
13367
13972
|
}
|
|
13368
13973
|
grouping.push({ kind: 'rail', id, side: (group.controls.side.value || 'right'), widgetKeys });
|
|
13369
13974
|
} return grouping; }
|
|
13975
|
+
buildPageContext(original, context) {
|
|
13976
|
+
const shellPreset = this.shellPresetControl.value.trim();
|
|
13977
|
+
const untouched = this.contextControl.value === this.stringify(this.removeShellPresetFromContext(original || {}))
|
|
13978
|
+
&& shellPreset === this.extractShellPreset(original || {});
|
|
13979
|
+
return untouched ? original : this.applyShellPresetToContext(context, shellPreset);
|
|
13980
|
+
}
|
|
13981
|
+
buildCanvas(page) {
|
|
13982
|
+
const original = page.canvas;
|
|
13983
|
+
const initial = original || this.defaultCanvas();
|
|
13984
|
+
const columns = this.canvasColumnsControl.value;
|
|
13985
|
+
const rowUnit = this.canvasRowUnitControl.value.trim() || '80px';
|
|
13986
|
+
const gap = this.canvasGapControl.value.trim() || '16px';
|
|
13987
|
+
const autoRows = this.canvasAutoRowsControl.value;
|
|
13988
|
+
const columnsChanged = columns !== (initial.columns || 12);
|
|
13989
|
+
const rowUnitChanged = rowUnit !== (initial.rowUnit || '80px');
|
|
13990
|
+
const gapChanged = gap !== (initial.gap || '16px');
|
|
13991
|
+
const autoRowsChanged = autoRows !== (initial.autoRows || 'fixed');
|
|
13992
|
+
// Displayed defaults are not authored overrides. Selecting a reference must
|
|
13993
|
+
// neither freeze defaults nor replace a flow page with a generated canvas.
|
|
13994
|
+
if (!columnsChanged && !rowUnitChanged && !gapChanged && !autoRowsChanged)
|
|
13995
|
+
return original;
|
|
13996
|
+
if (!original)
|
|
13997
|
+
return { ...initial, columns, rowUnit, gap, autoRows, items: this.completeCanvasItems(page) };
|
|
13998
|
+
return {
|
|
13999
|
+
...original,
|
|
14000
|
+
...(columnsChanged ? { columns } : {}),
|
|
14001
|
+
...(rowUnitChanged ? { rowUnit } : {}),
|
|
14002
|
+
...(gapChanged ? { gap } : {}),
|
|
14003
|
+
...(autoRowsChanged ? { autoRows } : {}),
|
|
14004
|
+
};
|
|
14005
|
+
}
|
|
13370
14006
|
/** A grid must place every existing widget before replacing the flow layout. */
|
|
13371
14007
|
completeCanvasItems(page) {
|
|
13372
14008
|
const items = { ...(page.canvas?.items || {}) };
|
|
@@ -13418,7 +14054,7 @@ class DynamicPageConfigEditorComponent {
|
|
|
13418
14054
|
} this.deviceError.set(null); return next; }
|
|
13419
14055
|
invalidate(tab, target, reportErrors, message) { if (reportErrors) {
|
|
13420
14056
|
target.set(message);
|
|
13421
|
-
this.
|
|
14057
|
+
this.focusEditorTab(tab);
|
|
13422
14058
|
this.isValid$.next(false);
|
|
13423
14059
|
} return null; }
|
|
13424
14060
|
refreshPanelState(resetSnapshot = false) {
|
|
@@ -13426,10 +14062,17 @@ class DynamicPageConfigEditorComponent {
|
|
|
13426
14062
|
const current = this.buildCurrentPage(false);
|
|
13427
14063
|
this.isValid$.next(!!current);
|
|
13428
14064
|
const fingerprint = this.draftFingerprint();
|
|
14065
|
+
if (this.arrangementPreview() && this.arrangementPreview().source !== fingerprint) {
|
|
14066
|
+
this.arrangementPreview.set(null);
|
|
14067
|
+
this.arrangementSelection.set('');
|
|
14068
|
+
this.focusArrangementFeedback = false;
|
|
14069
|
+
this.arrangementMessage.set(this.tx('editor.arrange.stale', 'The draft changed. Prepare the arrangement again.'));
|
|
14070
|
+
}
|
|
13429
14071
|
if (resetSnapshot)
|
|
13430
14072
|
this.snapshot = fingerprint;
|
|
13431
14073
|
this.isDirty$.next(fingerprint !== this.snapshot);
|
|
13432
14074
|
}
|
|
14075
|
+
get layoutPresetOptions() { return this.currentPageInput()?.layoutPresetOptions; }
|
|
13433
14076
|
currentPageInput() { return this.parsePage(this.pageInput) || this.parsePage(this.injectedData?.page || null); }
|
|
13434
14077
|
parsePage(input) { if (!input)
|
|
13435
14078
|
return null; if (typeof input === 'string') {
|
|
@@ -13487,289 +14130,11 @@ class DynamicPageConfigEditorComponent {
|
|
|
13487
14130
|
clone(value) { if (value == null || typeof value !== 'object')
|
|
13488
14131
|
return value; return JSON.parse(JSON.stringify(value)); }
|
|
13489
14132
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicPageConfigEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13490
|
-
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()], ngImport: i0, template: `
|
|
13491
|
-
<div class="editor">
|
|
13492
|
-
@if (!isHostedInSettingsPanel) { <div class="head">
|
|
13493
|
-
<div>{{ tx('editor.title', 'Page settings') }}</div>
|
|
13494
|
-
<button mat-stroked-button type="button" (click)="reset()">{{ tx('editor.reload', 'Reload') }}</button>
|
|
13495
|
-
</div> }
|
|
13496
|
-
<details class="summary-details">
|
|
13497
|
-
<summary>{{ tx('editor.summaryAria', 'Configuration summary') }}</summary>
|
|
13498
|
-
<div class="summary">
|
|
13499
|
-
@if (!isHostedInSettingsPanel) {
|
|
13500
|
-
<span>{{ validationStateLabel }}</span>
|
|
13501
|
-
<span>{{ dirtyStateLabel }}</span>
|
|
13502
|
-
}
|
|
13503
|
-
<span>{{ selectedLayoutPresetLabel }}</span>
|
|
13504
|
-
<span>{{ selectedThemePresetLabel }}</span>
|
|
13505
|
-
<span>{{ canvasSummaryLabel }}</span>
|
|
13506
|
-
</div>
|
|
13507
|
-
</details>
|
|
13508
|
-
@if (nonLimitError) { <p class="limits-error" role="alert">{{ nonLimitError }}</p> }
|
|
13509
|
-
@if (activeTab() === 'limits' && canvasLimits.targets.length) {
|
|
13510
|
-
<div class="selection-identity" data-testid="canvas-limits-identity">
|
|
13511
|
-
<mat-icon aria-hidden="true">dashboard_customize</mat-icon>
|
|
13512
|
-
<strong>{{ limitsTargetLabel(limitsTargetControl.value) }}</strong>
|
|
13513
|
-
</div>
|
|
13514
|
-
}
|
|
13515
|
-
<mat-tab-group class="editor-tabs" [mat-stretch-tabs]="false" [selectedIndex]="selectedTabIndex" (selectedIndexChange)="onSelectedTabIndexChange($event)">
|
|
13516
|
-
<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><mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.layoutPreset', 'Layout preset') }}</mat-label><mat-select [formControl]="layoutPresetControl"><mat-option value="">{{ tx('editor.layout.none', 'None') }}</mat-option>@for (preset of layoutPresetEntries; track preset.id) {<mat-option [value]="preset.id">{{ preset.label }}</mat-option>}</mat-select></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.themePreset', 'Theme preset') }}</mat-label><mat-select [formControl]="themePresetControl"><mat-option value="">{{ tx('editor.layout.layoutDefault', 'Layout default') }}</mat-option>@for (preset of themePresetEntries; track preset.id) {<mat-option [value]="preset.id">{{ preset.label }}</mat-option>}</mat-select></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.shellPreset', 'Shell visual preset') }}</mat-label><mat-select [formControl]="shellPresetControl"><mat-option value="">{{ tx('editor.layout.none', 'None') }}</mat-option>@for (preset of shellPresetEntries; track preset.id) {<mat-option [value]="preset.id">{{ shellPresetLabel(preset) }}</mat-option>}</mat-select></mat-form-field></div>@if (selectedLayoutPresetEntry; as preset) {<div class="preset-preview"><div class="preset-preview__head"><div><span class="eyebrow">{{ tx('editor.preview.layoutEyebrow', 'Layout template') }} · {{ preset.category }}</span><strong>{{ preset.label }}</strong></div><span>{{ layoutPresetSummary(preset) }}</span></div><p>{{ preset.description || tx('editor.preview.layoutFallbackDescription', 'Page organization preset.') }}</p><div class="preset-preview__chips"><span>{{ slotsSummary(preset) }}</span><span>{{ requiredSlotsSummary(preset) }}</span><span>{{ devicePolicySummary(preset) }}</span></div>@if (recommendedThemePresetEntry; as theme) {<div class="preset-preview__recommendation"><div><span class="eyebrow">{{ tx('editor.preview.inheritedTheme', 'Inherited theme') }}</span><strong>{{ theme.label }}</strong><span>{{ inheritedThemeHelp(theme) }}</span></div><button mat-stroked-button type="button" [disabled]="themePresetControl.value === theme.id" (click)="pinRecommendedThemePreset()">{{ tx('editor.preview.pinTheme', 'Pin theme') }}</button></div>}</div>} @if (selectedThemePresetEntry; as theme) {<div class="preset-preview preset-preview--theme"><div class="preset-preview__head"><div><span class="eyebrow">{{ tx('editor.preview.explicitTheme', 'Explicit theme') }}</span><strong>{{ theme.label }}</strong></div><span>{{ theme.density }} · {{ theme.motion }}</span></div><p>{{ theme.description || tx('editor.preview.themeFallbackDescription', 'Page visual preset.') }}</p><div class="preset-preview__chips"><span>{{ tx('editor.preview.shellChip', 'Shell: {{value}}').replace('{{value}}', theme.shellPreset) }}</span><span>{{ tx('editor.preview.chartsChip', 'Charts: {{value}}').replace('{{value}}', theme.chartThemePreset) }}</span></div></div>}</mat-tab>
|
|
13517
|
-
<mat-tab [label]="tx('editor.tabs.grouping', 'Grouping')"><div class="panel">
|
|
13518
|
-
<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 {<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>
|
|
13519
|
-
<mat-tab [label]="tx('editor.tabs.devices', 'Device Layouts')">
|
|
13520
|
-
<div class="panel">
|
|
13521
|
-
<mat-button-toggle-group [value]="activeDevice()" (change)="activeDevice.set($event.value)" [attr.aria-label]="tx('editor.devices.target', 'Device to configure')">
|
|
13522
|
-
@for (device of deviceKeys; track device) {
|
|
13523
|
-
<mat-button-toggle [value]="device">{{ deviceLabels[device] }}</mat-button-toggle>
|
|
13524
|
-
}
|
|
13525
|
-
</mat-button-toggle-group>
|
|
13526
|
-
@for (device of deviceKeys; track device) {
|
|
13527
|
-
@if (activeDevice() === device) {
|
|
13528
|
-
<div class="card">
|
|
13529
|
-
<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>
|
|
13530
|
-
<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>
|
|
13531
|
-
@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> }
|
|
13532
|
-
<div class="grid">
|
|
13533
|
-
<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>
|
|
13534
|
-
<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>
|
|
13535
|
-
<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>
|
|
13536
|
-
</div>
|
|
13537
|
-
<details><summary>{{ tx('editor.devices.advanced', 'Advanced overrides') }}</summary><div class="grid">
|
|
13538
|
-
<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>
|
|
13539
|
-
<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>
|
|
13540
|
-
<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-form-field>
|
|
13541
|
-
</div></details>
|
|
13542
|
-
@if (!stackedDevices.has(device)) { <div class="hint">{{ tx('editor.preview.deviceCanvasHint', 'Existing canvas item overrides are preserved.') }}</div> }
|
|
13543
|
-
</div>
|
|
13544
|
-
}
|
|
13545
|
-
}
|
|
13546
|
-
</div>
|
|
13547
|
-
</mat-tab>
|
|
13548
|
-
<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>
|
|
13549
|
-
<mat-tab [label]="tx('editor.tabs.state', 'Page State')"><mat-form-field appearance="outline" class="wide"><mat-label>{{ tx('editor.state.valuesJson', 'Primary values (JSON)') }}</mat-label><textarea matInput rows="14" [formControl]="stateControl"></textarea></mat-form-field></mat-tab>
|
|
13550
|
-
<mat-tab [label]="tx('editor.limits.tab', 'Widget limits')">
|
|
13551
|
-
<section class="limits-panel" data-testid="canvas-limits-editor">
|
|
13552
|
-
<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>
|
|
13553
|
-
@if (canvasLimits.targets.length) {
|
|
13554
|
-
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
13555
|
-
<mat-label>{{ tx('editor.limits.target', 'Widget and layout') }}</mat-label>
|
|
13556
|
-
<mat-select [formControl]="limitsTargetControl" data-testid="canvas-limits-target">
|
|
13557
|
-
@for (target of canvasLimits.targets; track target.scope + ':' + target.key; let index = $index) {
|
|
13558
|
-
<mat-option [value]="index">{{ limitsTargetLabel(index) }}</mat-option>
|
|
13559
|
-
}
|
|
13560
|
-
</mat-select>
|
|
13561
|
-
</mat-form-field>
|
|
13562
|
-
<p class="hint">{{ limitsGeometryLabel() }}</p>
|
|
13563
|
-
@if (canvasLimits.form.controls[limitsTargetControl.value]; as limitsForm) {
|
|
13564
|
-
<div class="limits-fields" aria-describedby="canvas-limits-help">
|
|
13565
|
-
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
13566
|
-
<mat-label>{{ tx('editor.limits.minColSpan', 'Minimum width (columns)') }}</mat-label>
|
|
13567
|
-
<input matInput [errorStateMatcher]="limitsErrorMatcher" type="number" min="1" step="1" [formControl]="limitsForm.controls.minColSpan" data-testid="canvas-limit-minColSpan">
|
|
13568
|
-
<mat-error>{{ limitFieldError(limitsForm.controls.minColSpan) }}</mat-error>
|
|
13569
|
-
</mat-form-field>
|
|
13570
|
-
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
13571
|
-
<mat-label>{{ tx('editor.limits.maxColSpan', 'Maximum width (columns)') }}</mat-label>
|
|
13572
|
-
<input matInput [errorStateMatcher]="limitsErrorMatcher" type="number" min="1" step="1" [formControl]="limitsForm.controls.maxColSpan" data-testid="canvas-limit-maxColSpan">
|
|
13573
|
-
<mat-error>{{ limitFieldError(limitsForm.controls.maxColSpan) }}</mat-error>
|
|
13574
|
-
</mat-form-field>
|
|
13575
|
-
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
13576
|
-
<mat-label>{{ tx('editor.limits.minRowSpan', 'Minimum height (rows)') }}</mat-label>
|
|
13577
|
-
<input matInput [errorStateMatcher]="limitsErrorMatcher" type="number" min="1" step="1" [formControl]="limitsForm.controls.minRowSpan" data-testid="canvas-limit-minRowSpan">
|
|
13578
|
-
<mat-error>{{ limitFieldError(limitsForm.controls.minRowSpan) }}</mat-error>
|
|
13579
|
-
</mat-form-field>
|
|
13580
|
-
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
13581
|
-
<mat-label>{{ tx('editor.limits.maxRowSpan', 'Maximum height (rows)') }}</mat-label>
|
|
13582
|
-
<input matInput [errorStateMatcher]="limitsErrorMatcher" type="number" min="1" step="1" [formControl]="limitsForm.controls.maxRowSpan" data-testid="canvas-limit-maxRowSpan">
|
|
13583
|
-
<mat-error>{{ limitFieldError(limitsForm.controls.maxRowSpan) }}</mat-error>
|
|
13584
|
-
</mat-form-field>
|
|
13585
|
-
</div>
|
|
13586
|
-
@if (limitsUseContentHeight()) {
|
|
13587
|
-
<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>
|
|
13588
|
-
<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">
|
|
13589
|
-
<mat-button-toggle value="inherit">{{ tx('editor.height.inherit', 'Default') }}</mat-button-toggle>
|
|
13590
|
-
<mat-button-toggle value="auto">{{ tx('editor.height.auto', 'Automatic') }}</mat-button-toggle>
|
|
13591
|
-
<mat-button-toggle value="fixed">{{ tx('editor.height.fixed', 'Adjustable') }}</mat-button-toggle>
|
|
13592
|
-
</mat-button-toggle-group>
|
|
13593
|
-
@if (limitsForm.controls.heightMode.value === 'fixed') {
|
|
13594
|
-
<div class="limits-fields">
|
|
13595
|
-
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
13596
|
-
<mat-label>{{ tx('editor.height.value', 'Height (px)') }}</mat-label>
|
|
13597
|
-
<input matInput type="number" min="160" step="1" [errorStateMatcher]="limitsErrorMatcher" [formControl]="limitsForm.controls.heightPx" data-testid="canvas-height-px">
|
|
13598
|
-
<mat-error>{{ limitFieldError(limitsForm.controls.heightPx) }}</mat-error>
|
|
13599
|
-
</mat-form-field>
|
|
13600
|
-
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
13601
|
-
<mat-label>{{ tx('editor.height.top', 'Top inset (px)') }}</mat-label>
|
|
13602
|
-
<input matInput type="number" min="0" step="1" [errorStateMatcher]="limitsErrorMatcher" [formControl]="limitsForm.controls.offsetTopPx" data-testid="canvas-height-top">
|
|
13603
|
-
<mat-error>{{ limitFieldError(limitsForm.controls.offsetTopPx) }}</mat-error>
|
|
13604
|
-
</mat-form-field>
|
|
13605
|
-
</div>
|
|
13606
|
-
}
|
|
13607
|
-
<div class="limits-fields">
|
|
13608
|
-
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
13609
|
-
<mat-label>{{ tx('editor.height.min', 'Minimum height (px)') }}</mat-label>
|
|
13610
|
-
<input matInput type="number" min="160" step="1" [errorStateMatcher]="limitsErrorMatcher" [formControl]="limitsForm.controls.minHeightPx" data-testid="canvas-limit-minHeightPx">
|
|
13611
|
-
<mat-error>{{ limitFieldError(limitsForm.controls.minHeightPx) }}</mat-error>
|
|
13612
|
-
</mat-form-field>
|
|
13613
|
-
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
13614
|
-
<mat-label>{{ tx('editor.height.max', 'Maximum height (px)') }}</mat-label>
|
|
13615
|
-
<input matInput type="number" min="160" step="1" [errorStateMatcher]="limitsErrorMatcher" [formControl]="limitsForm.controls.maxHeightPx" data-testid="canvas-limit-maxHeightPx">
|
|
13616
|
-
<mat-error>{{ limitFieldError(limitsForm.controls.maxHeightPx) }}</mat-error>
|
|
13617
|
-
</mat-form-field>
|
|
13618
|
-
</div>
|
|
13619
|
-
}
|
|
13620
|
-
}
|
|
13621
|
-
@if (limitsSummaryError) { <p class="limits-error" role="alert">{{ limitsSummaryError }}</p> }
|
|
13622
|
-
} @else {
|
|
13623
|
-
<p>{{ tx('editor.limits.empty', 'Add widgets to the canvas to configure their limits.') }}</p>
|
|
13624
|
-
}
|
|
13625
|
-
</section>
|
|
13626
|
-
</mat-tab>
|
|
13627
|
-
</mat-tab-group>
|
|
13628
|
-
</div>
|
|
13629
|
-
`, isInline: true, 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{position:sticky;top:0;z-index:var(--praxis-layer-sticky-local, 100);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))}@media(max-width:600px){.grid{grid-template-columns:minmax(0,1fr)}.head,.summary,.preset-preview__head,.preset-preview__recommendation{align-items:stretch}.head>button,.preset-preview__recommendation 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)}.wide{grid-column:1 / -1}.hint{color:var(--md-sys-color-on-surface-variant, currentColor);font-size:12px}.preset-preview{display:grid;gap:12px;margin-top:12px;padding:16px;border:1px solid var(--md-sys-color-outline-variant, color-mix(in srgb, currentColor 18%, transparent));border-radius:8px;background:color-mix(in srgb,var(--md-sys-color-surface-container-low, transparent) 88%,var(--md-sys-color-primary-container, transparent) 12%)}.preset-preview--theme{background:var(--md-sys-color-surface-container-low, transparent)}.preset-preview__head,.preset-preview__recommendation{display:flex;justify-content:space-between;align-items:center;gap:16px}.preset-preview__head>div,.preset-preview__recommendation>div{display:grid;gap:4px;min-width:0}.preset-preview p{margin:0;color:var(--md-sys-color-on-surface-variant, currentColor);line-height:1.45}.preset-preview__chips{display:flex;flex-wrap:wrap;gap:8px}.preset-preview__chips span{padding:5px 9px;border-radius:999px;background:var(--md-sys-color-surface-container, color-mix(in srgb, currentColor 8%, transparent));color:var(--md-sys-color-on-surface-variant, currentColor);font-size:12px;line-height:1.3}.preset-preview__recommendation{padding-top:12px;border-top:1px solid var(--md-sys-color-outline-variant, color-mix(in srgb, currentColor 14%, transparent))}.preset-preview__recommendation span:last-child{color:var(--md-sys-color-on-surface-variant, currentColor);font-size:13px;line-height:1.35}.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}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.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: MatButtonToggleModule }, { kind: "directive", type: i3$1.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i3$1.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: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i4.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: i5.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: i8.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: i8.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i8$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i8$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 });
|
|
14133
|
+
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 <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 <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) {<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 {<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-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')\"><mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.state.valuesJson', 'Primary values (JSON)') }}</mat-label><textarea matInput rows=\"14\" [formControl]=\"stateControl\"></textarea></mat-form-field></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\" data-testid=\"canvas-limits-identity\">\n <mat-icon aria-hidden=\"true\">dashboard_customize</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 @for (target of canvasLimits.targets; track target.scope + ':' + target.key; let index = $index) {\n <mat-option [value]=\"index\">{{ limitsTargetLabel(index) }}</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))}@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)}.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)}\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: 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: i3$1.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i3$1.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: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i4.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: i5.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: i9.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: i9.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i8.MatTabContent, selector: "[matTabContent]" }, { kind: "component", type: i8.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i8.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 });
|
|
13630
14134
|
}
|
|
13631
14135
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicPageConfigEditorComponent, decorators: [{
|
|
13632
14136
|
type: Component,
|
|
13633
|
-
args: [{ selector: 'praxis-dynamic-page-config-editor', standalone: true, imports: [ReactiveFormsModule, MatButtonModule, MatButtonToggleModule, MatFormFieldModule, MatIconModule, MatInputModule, MatSelectModule, MatTabsModule, MatTooltipModule, PraxisIconButtonComponent], providers: [providePraxisPageBuilderI18n()], template: `
|
|
13634
|
-
<div class="editor">
|
|
13635
|
-
@if (!isHostedInSettingsPanel) { <div class="head">
|
|
13636
|
-
<div>{{ tx('editor.title', 'Page settings') }}</div>
|
|
13637
|
-
<button mat-stroked-button type="button" (click)="reset()">{{ tx('editor.reload', 'Reload') }}</button>
|
|
13638
|
-
</div> }
|
|
13639
|
-
<details class="summary-details">
|
|
13640
|
-
<summary>{{ tx('editor.summaryAria', 'Configuration summary') }}</summary>
|
|
13641
|
-
<div class="summary">
|
|
13642
|
-
@if (!isHostedInSettingsPanel) {
|
|
13643
|
-
<span>{{ validationStateLabel }}</span>
|
|
13644
|
-
<span>{{ dirtyStateLabel }}</span>
|
|
13645
|
-
}
|
|
13646
|
-
<span>{{ selectedLayoutPresetLabel }}</span>
|
|
13647
|
-
<span>{{ selectedThemePresetLabel }}</span>
|
|
13648
|
-
<span>{{ canvasSummaryLabel }}</span>
|
|
13649
|
-
</div>
|
|
13650
|
-
</details>
|
|
13651
|
-
@if (nonLimitError) { <p class="limits-error" role="alert">{{ nonLimitError }}</p> }
|
|
13652
|
-
@if (activeTab() === 'limits' && canvasLimits.targets.length) {
|
|
13653
|
-
<div class="selection-identity" data-testid="canvas-limits-identity">
|
|
13654
|
-
<mat-icon aria-hidden="true">dashboard_customize</mat-icon>
|
|
13655
|
-
<strong>{{ limitsTargetLabel(limitsTargetControl.value) }}</strong>
|
|
13656
|
-
</div>
|
|
13657
|
-
}
|
|
13658
|
-
<mat-tab-group class="editor-tabs" [mat-stretch-tabs]="false" [selectedIndex]="selectedTabIndex" (selectedIndexChange)="onSelectedTabIndexChange($event)">
|
|
13659
|
-
<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><mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.layoutPreset', 'Layout preset') }}</mat-label><mat-select [formControl]="layoutPresetControl"><mat-option value="">{{ tx('editor.layout.none', 'None') }}</mat-option>@for (preset of layoutPresetEntries; track preset.id) {<mat-option [value]="preset.id">{{ preset.label }}</mat-option>}</mat-select></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.themePreset', 'Theme preset') }}</mat-label><mat-select [formControl]="themePresetControl"><mat-option value="">{{ tx('editor.layout.layoutDefault', 'Layout default') }}</mat-option>@for (preset of themePresetEntries; track preset.id) {<mat-option [value]="preset.id">{{ preset.label }}</mat-option>}</mat-select></mat-form-field><mat-form-field appearance="outline"><mat-label>{{ tx('editor.layout.shellPreset', 'Shell visual preset') }}</mat-label><mat-select [formControl]="shellPresetControl"><mat-option value="">{{ tx('editor.layout.none', 'None') }}</mat-option>@for (preset of shellPresetEntries; track preset.id) {<mat-option [value]="preset.id">{{ shellPresetLabel(preset) }}</mat-option>}</mat-select></mat-form-field></div>@if (selectedLayoutPresetEntry; as preset) {<div class="preset-preview"><div class="preset-preview__head"><div><span class="eyebrow">{{ tx('editor.preview.layoutEyebrow', 'Layout template') }} · {{ preset.category }}</span><strong>{{ preset.label }}</strong></div><span>{{ layoutPresetSummary(preset) }}</span></div><p>{{ preset.description || tx('editor.preview.layoutFallbackDescription', 'Page organization preset.') }}</p><div class="preset-preview__chips"><span>{{ slotsSummary(preset) }}</span><span>{{ requiredSlotsSummary(preset) }}</span><span>{{ devicePolicySummary(preset) }}</span></div>@if (recommendedThemePresetEntry; as theme) {<div class="preset-preview__recommendation"><div><span class="eyebrow">{{ tx('editor.preview.inheritedTheme', 'Inherited theme') }}</span><strong>{{ theme.label }}</strong><span>{{ inheritedThemeHelp(theme) }}</span></div><button mat-stroked-button type="button" [disabled]="themePresetControl.value === theme.id" (click)="pinRecommendedThemePreset()">{{ tx('editor.preview.pinTheme', 'Pin theme') }}</button></div>}</div>} @if (selectedThemePresetEntry; as theme) {<div class="preset-preview preset-preview--theme"><div class="preset-preview__head"><div><span class="eyebrow">{{ tx('editor.preview.explicitTheme', 'Explicit theme') }}</span><strong>{{ theme.label }}</strong></div><span>{{ theme.density }} · {{ theme.motion }}</span></div><p>{{ theme.description || tx('editor.preview.themeFallbackDescription', 'Page visual preset.') }}</p><div class="preset-preview__chips"><span>{{ tx('editor.preview.shellChip', 'Shell: {{value}}').replace('{{value}}', theme.shellPreset) }}</span><span>{{ tx('editor.preview.chartsChip', 'Charts: {{value}}').replace('{{value}}', theme.chartThemePreset) }}</span></div></div>}</mat-tab>
|
|
13660
|
-
<mat-tab [label]="tx('editor.tabs.grouping', 'Grouping')"><div class="panel">
|
|
13661
|
-
<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 {<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>
|
|
13662
|
-
<mat-tab [label]="tx('editor.tabs.devices', 'Device Layouts')">
|
|
13663
|
-
<div class="panel">
|
|
13664
|
-
<mat-button-toggle-group [value]="activeDevice()" (change)="activeDevice.set($event.value)" [attr.aria-label]="tx('editor.devices.target', 'Device to configure')">
|
|
13665
|
-
@for (device of deviceKeys; track device) {
|
|
13666
|
-
<mat-button-toggle [value]="device">{{ deviceLabels[device] }}</mat-button-toggle>
|
|
13667
|
-
}
|
|
13668
|
-
</mat-button-toggle-group>
|
|
13669
|
-
@for (device of deviceKeys; track device) {
|
|
13670
|
-
@if (activeDevice() === device) {
|
|
13671
|
-
<div class="card">
|
|
13672
|
-
<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>
|
|
13673
|
-
<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>
|
|
13674
|
-
@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> }
|
|
13675
|
-
<div class="grid">
|
|
13676
|
-
<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>
|
|
13677
|
-
<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>
|
|
13678
|
-
<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>
|
|
13679
|
-
</div>
|
|
13680
|
-
<details><summary>{{ tx('editor.devices.advanced', 'Advanced overrides') }}</summary><div class="grid">
|
|
13681
|
-
<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>
|
|
13682
|
-
<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>
|
|
13683
|
-
<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-form-field>
|
|
13684
|
-
</div></details>
|
|
13685
|
-
@if (!stackedDevices.has(device)) { <div class="hint">{{ tx('editor.preview.deviceCanvasHint', 'Existing canvas item overrides are preserved.') }}</div> }
|
|
13686
|
-
</div>
|
|
13687
|
-
}
|
|
13688
|
-
}
|
|
13689
|
-
</div>
|
|
13690
|
-
</mat-tab>
|
|
13691
|
-
<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>
|
|
13692
|
-
<mat-tab [label]="tx('editor.tabs.state', 'Page State')"><mat-form-field appearance="outline" class="wide"><mat-label>{{ tx('editor.state.valuesJson', 'Primary values (JSON)') }}</mat-label><textarea matInput rows="14" [formControl]="stateControl"></textarea></mat-form-field></mat-tab>
|
|
13693
|
-
<mat-tab [label]="tx('editor.limits.tab', 'Widget limits')">
|
|
13694
|
-
<section class="limits-panel" data-testid="canvas-limits-editor">
|
|
13695
|
-
<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>
|
|
13696
|
-
@if (canvasLimits.targets.length) {
|
|
13697
|
-
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
13698
|
-
<mat-label>{{ tx('editor.limits.target', 'Widget and layout') }}</mat-label>
|
|
13699
|
-
<mat-select [formControl]="limitsTargetControl" data-testid="canvas-limits-target">
|
|
13700
|
-
@for (target of canvasLimits.targets; track target.scope + ':' + target.key; let index = $index) {
|
|
13701
|
-
<mat-option [value]="index">{{ limitsTargetLabel(index) }}</mat-option>
|
|
13702
|
-
}
|
|
13703
|
-
</mat-select>
|
|
13704
|
-
</mat-form-field>
|
|
13705
|
-
<p class="hint">{{ limitsGeometryLabel() }}</p>
|
|
13706
|
-
@if (canvasLimits.form.controls[limitsTargetControl.value]; as limitsForm) {
|
|
13707
|
-
<div class="limits-fields" aria-describedby="canvas-limits-help">
|
|
13708
|
-
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
13709
|
-
<mat-label>{{ tx('editor.limits.minColSpan', 'Minimum width (columns)') }}</mat-label>
|
|
13710
|
-
<input matInput [errorStateMatcher]="limitsErrorMatcher" type="number" min="1" step="1" [formControl]="limitsForm.controls.minColSpan" data-testid="canvas-limit-minColSpan">
|
|
13711
|
-
<mat-error>{{ limitFieldError(limitsForm.controls.minColSpan) }}</mat-error>
|
|
13712
|
-
</mat-form-field>
|
|
13713
|
-
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
13714
|
-
<mat-label>{{ tx('editor.limits.maxColSpan', 'Maximum width (columns)') }}</mat-label>
|
|
13715
|
-
<input matInput [errorStateMatcher]="limitsErrorMatcher" type="number" min="1" step="1" [formControl]="limitsForm.controls.maxColSpan" data-testid="canvas-limit-maxColSpan">
|
|
13716
|
-
<mat-error>{{ limitFieldError(limitsForm.controls.maxColSpan) }}</mat-error>
|
|
13717
|
-
</mat-form-field>
|
|
13718
|
-
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
13719
|
-
<mat-label>{{ tx('editor.limits.minRowSpan', 'Minimum height (rows)') }}</mat-label>
|
|
13720
|
-
<input matInput [errorStateMatcher]="limitsErrorMatcher" type="number" min="1" step="1" [formControl]="limitsForm.controls.minRowSpan" data-testid="canvas-limit-minRowSpan">
|
|
13721
|
-
<mat-error>{{ limitFieldError(limitsForm.controls.minRowSpan) }}</mat-error>
|
|
13722
|
-
</mat-form-field>
|
|
13723
|
-
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
13724
|
-
<mat-label>{{ tx('editor.limits.maxRowSpan', 'Maximum height (rows)') }}</mat-label>
|
|
13725
|
-
<input matInput [errorStateMatcher]="limitsErrorMatcher" type="number" min="1" step="1" [formControl]="limitsForm.controls.maxRowSpan" data-testid="canvas-limit-maxRowSpan">
|
|
13726
|
-
<mat-error>{{ limitFieldError(limitsForm.controls.maxRowSpan) }}</mat-error>
|
|
13727
|
-
</mat-form-field>
|
|
13728
|
-
</div>
|
|
13729
|
-
@if (limitsUseContentHeight()) {
|
|
13730
|
-
<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>
|
|
13731
|
-
<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">
|
|
13732
|
-
<mat-button-toggle value="inherit">{{ tx('editor.height.inherit', 'Default') }}</mat-button-toggle>
|
|
13733
|
-
<mat-button-toggle value="auto">{{ tx('editor.height.auto', 'Automatic') }}</mat-button-toggle>
|
|
13734
|
-
<mat-button-toggle value="fixed">{{ tx('editor.height.fixed', 'Adjustable') }}</mat-button-toggle>
|
|
13735
|
-
</mat-button-toggle-group>
|
|
13736
|
-
@if (limitsForm.controls.heightMode.value === 'fixed') {
|
|
13737
|
-
<div class="limits-fields">
|
|
13738
|
-
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
13739
|
-
<mat-label>{{ tx('editor.height.value', 'Height (px)') }}</mat-label>
|
|
13740
|
-
<input matInput type="number" min="160" step="1" [errorStateMatcher]="limitsErrorMatcher" [formControl]="limitsForm.controls.heightPx" data-testid="canvas-height-px">
|
|
13741
|
-
<mat-error>{{ limitFieldError(limitsForm.controls.heightPx) }}</mat-error>
|
|
13742
|
-
</mat-form-field>
|
|
13743
|
-
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
13744
|
-
<mat-label>{{ tx('editor.height.top', 'Top inset (px)') }}</mat-label>
|
|
13745
|
-
<input matInput type="number" min="0" step="1" [errorStateMatcher]="limitsErrorMatcher" [formControl]="limitsForm.controls.offsetTopPx" data-testid="canvas-height-top">
|
|
13746
|
-
<mat-error>{{ limitFieldError(limitsForm.controls.offsetTopPx) }}</mat-error>
|
|
13747
|
-
</mat-form-field>
|
|
13748
|
-
</div>
|
|
13749
|
-
}
|
|
13750
|
-
<div class="limits-fields">
|
|
13751
|
-
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
13752
|
-
<mat-label>{{ tx('editor.height.min', 'Minimum height (px)') }}</mat-label>
|
|
13753
|
-
<input matInput type="number" min="160" step="1" [errorStateMatcher]="limitsErrorMatcher" [formControl]="limitsForm.controls.minHeightPx" data-testid="canvas-limit-minHeightPx">
|
|
13754
|
-
<mat-error>{{ limitFieldError(limitsForm.controls.minHeightPx) }}</mat-error>
|
|
13755
|
-
</mat-form-field>
|
|
13756
|
-
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
13757
|
-
<mat-label>{{ tx('editor.height.max', 'Maximum height (px)') }}</mat-label>
|
|
13758
|
-
<input matInput type="number" min="160" step="1" [errorStateMatcher]="limitsErrorMatcher" [formControl]="limitsForm.controls.maxHeightPx" data-testid="canvas-limit-maxHeightPx">
|
|
13759
|
-
<mat-error>{{ limitFieldError(limitsForm.controls.maxHeightPx) }}</mat-error>
|
|
13760
|
-
</mat-form-field>
|
|
13761
|
-
</div>
|
|
13762
|
-
}
|
|
13763
|
-
}
|
|
13764
|
-
@if (limitsSummaryError) { <p class="limits-error" role="alert">{{ limitsSummaryError }}</p> }
|
|
13765
|
-
} @else {
|
|
13766
|
-
<p>{{ tx('editor.limits.empty', 'Add widgets to the canvas to configure their limits.') }}</p>
|
|
13767
|
-
}
|
|
13768
|
-
</section>
|
|
13769
|
-
</mat-tab>
|
|
13770
|
-
</mat-tab-group>
|
|
13771
|
-
</div>
|
|
13772
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, host: { '(window:resize)': 'refreshRenderedValidation()' }, 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{position:sticky;top:0;z-index:var(--praxis-layer-sticky-local, 100);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))}@media(max-width:600px){.grid{grid-template-columns:minmax(0,1fr)}.head,.summary,.preset-preview__head,.preset-preview__recommendation{align-items:stretch}.head>button,.preset-preview__recommendation 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)}.wide{grid-column:1 / -1}.hint{color:var(--md-sys-color-on-surface-variant, currentColor);font-size:12px}.preset-preview{display:grid;gap:12px;margin-top:12px;padding:16px;border:1px solid var(--md-sys-color-outline-variant, color-mix(in srgb, currentColor 18%, transparent));border-radius:8px;background:color-mix(in srgb,var(--md-sys-color-surface-container-low, transparent) 88%,var(--md-sys-color-primary-container, transparent) 12%)}.preset-preview--theme{background:var(--md-sys-color-surface-container-low, transparent)}.preset-preview__head,.preset-preview__recommendation{display:flex;justify-content:space-between;align-items:center;gap:16px}.preset-preview__head>div,.preset-preview__recommendation>div{display:grid;gap:4px;min-width:0}.preset-preview p{margin:0;color:var(--md-sys-color-on-surface-variant, currentColor);line-height:1.45}.preset-preview__chips{display:flex;flex-wrap:wrap;gap:8px}.preset-preview__chips span{padding:5px 9px;border-radius:999px;background:var(--md-sys-color-surface-container, color-mix(in srgb, currentColor 8%, transparent));color:var(--md-sys-color-on-surface-variant, currentColor);font-size:12px;line-height:1.3}.preset-preview__recommendation{padding-top:12px;border-top:1px solid var(--md-sys-color-outline-variant, color-mix(in srgb, currentColor 14%, transparent))}.preset-preview__recommendation span:last-child{color:var(--md-sys-color-on-surface-variant, currentColor);font-size:13px;line-height:1.35}.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}\n"] }]
|
|
14137
|
+
args: [{ selector: 'praxis-dynamic-page-config-editor', standalone: true, imports: [PagePresetComparisonComponent, ReactiveFormsModule, MatButtonModule, MatButtonToggleModule, MatFormFieldModule, MatIconModule, MatInputModule, MatSelectModule, MatTabsModule, MatTooltipModule, PraxisIconButtonComponent], providers: [providePraxisPageBuilderI18n()], changeDetection: ChangeDetectionStrategy.OnPush, host: { '(window:resize)': 'refreshRenderedValidation()' }, 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 <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 <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) {<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 {<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-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')\"><mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.state.valuesJson', 'Primary values (JSON)') }}</mat-label><textarea matInput rows=\"14\" [formControl]=\"stateControl\"></textarea></mat-form-field></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\" data-testid=\"canvas-limits-identity\">\n <mat-icon aria-hidden=\"true\">dashboard_customize</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 @for (target of canvasLimits.targets; track target.scope + ':' + target.key; let index = $index) {\n <mat-option [value]=\"index\">{{ limitsTargetLabel(index) }}</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))}@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)}.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)}\n"] }]
|
|
13773
14138
|
}], ctorParameters: () => [], propDecorators: { page: [{
|
|
13774
14139
|
type: Input
|
|
13775
14140
|
}], layout: [{
|
|
@@ -13780,6 +14145,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
13780
14145
|
type: Input
|
|
13781
14146
|
}], pageChange: [{
|
|
13782
14147
|
type: Output
|
|
14148
|
+
}], arrangementFeedback: [{
|
|
14149
|
+
type: ViewChild,
|
|
14150
|
+
args: ['arrangementFeedback']
|
|
13783
14151
|
}] } });
|
|
13784
14152
|
|
|
13785
14153
|
const DERIVED_OPERATORS = ['merge-objects', 'pick', 'omit'];
|
|
@@ -14456,6 +14824,7 @@ class PageConfigEditorComponent {
|
|
|
14456
14824
|
lookupPresetLabel(entries, value, fallback) {
|
|
14457
14825
|
return entries.find((entry) => entry.id === value)?.label || fallback;
|
|
14458
14826
|
}
|
|
14827
|
+
get layoutPresetOptions() { return this.currentPageInput()?.layoutPresetOptions; }
|
|
14459
14828
|
currentPageInput() {
|
|
14460
14829
|
return this.parsePage(this.page()) || this.parsePage(this.injectedData?.page || null);
|
|
14461
14830
|
}
|
|
@@ -14729,7 +15098,7 @@ class PageConfigEditorComponent {
|
|
|
14729
15098
|
<mat-select [formControl]="layoutPresetControl">
|
|
14730
15099
|
<mat-option value="">{{ tx('editor.layout.none', 'None') }}</mat-option>
|
|
14731
15100
|
@for (preset of layoutPresetEntries; track preset.id) {
|
|
14732
|
-
<mat-option [value]="preset.id">{{ preset.label }}</mat-option>
|
|
15101
|
+
<mat-option [value]="preset.id">{{ tx('presets.layout.' + preset.id, preset.label) }}</mat-option>
|
|
14733
15102
|
}
|
|
14734
15103
|
</mat-select>
|
|
14735
15104
|
<button praxisIconButton="help_outline" size="dense" matSuffix class="help-icon-button" type="button" [matTooltip]="layoutPresetHint" [attr.aria-label]="layoutPresetHint" matTooltipPosition="above"></button>
|
|
@@ -14755,6 +15124,7 @@ class PageConfigEditorComponent {
|
|
|
14755
15124
|
<button praxisIconButton="help_outline" size="dense" matSuffix class="help-icon-button" type="button" [matTooltip]="shellPresetHint" [attr.aria-label]="shellPresetHint" matTooltipPosition="above"></button>
|
|
14756
15125
|
</mat-form-field>
|
|
14757
15126
|
</div>
|
|
15127
|
+
<praxis-page-preset-comparison [layoutId]="layoutPresetControl.value" [layoutPresetOptions]="layoutPresetOptions" [themeId]="themePresetControl.value" (choose)="layoutPresetControl.setValue($event)" />
|
|
14758
15128
|
</div>
|
|
14759
15129
|
</mat-tab>
|
|
14760
15130
|
<mat-tab [label]="tx('editor.tabs.grouping', 'Grouping')">
|
|
@@ -15240,11 +15610,11 @@ class PageConfigEditorComponent {
|
|
|
15240
15610
|
</div>
|
|
15241
15611
|
}
|
|
15242
15612
|
</div>
|
|
15243
|
-
`, 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: "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: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: i8.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: i8.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i8$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i8$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 });
|
|
15613
|
+
`, 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: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: i9.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: i9.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i8.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i8.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 });
|
|
15244
15614
|
}
|
|
15245
15615
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PageConfigEditorComponent, decorators: [{
|
|
15246
15616
|
type: Component,
|
|
15247
|
-
args: [{ selector: 'praxis-page-config-editor', standalone: true, imports: [
|
|
15617
|
+
args: [{ selector: 'praxis-page-config-editor', standalone: true, imports: [PagePresetComparisonComponent,
|
|
15248
15618
|
ReactiveFormsModule,
|
|
15249
15619
|
MatButtonModule,
|
|
15250
15620
|
MatFormFieldModule,
|
|
@@ -15327,7 +15697,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
15327
15697
|
<mat-select [formControl]="layoutPresetControl">
|
|
15328
15698
|
<mat-option value="">{{ tx('editor.layout.none', 'None') }}</mat-option>
|
|
15329
15699
|
@for (preset of layoutPresetEntries; track preset.id) {
|
|
15330
|
-
<mat-option [value]="preset.id">{{ preset.label }}</mat-option>
|
|
15700
|
+
<mat-option [value]="preset.id">{{ tx('presets.layout.' + preset.id, preset.label) }}</mat-option>
|
|
15331
15701
|
}
|
|
15332
15702
|
</mat-select>
|
|
15333
15703
|
<button praxisIconButton="help_outline" size="dense" matSuffix class="help-icon-button" type="button" [matTooltip]="layoutPresetHint" [attr.aria-label]="layoutPresetHint" matTooltipPosition="above"></button>
|
|
@@ -15353,6 +15723,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
15353
15723
|
<button praxisIconButton="help_outline" size="dense" matSuffix class="help-icon-button" type="button" [matTooltip]="shellPresetHint" [attr.aria-label]="shellPresetHint" matTooltipPosition="above"></button>
|
|
15354
15724
|
</mat-form-field>
|
|
15355
15725
|
</div>
|
|
15726
|
+
<praxis-page-preset-comparison [layoutId]="layoutPresetControl.value" [layoutPresetOptions]="layoutPresetOptions" [themeId]="themePresetControl.value" (choose)="layoutPresetControl.setValue($event)" />
|
|
15356
15727
|
</div>
|
|
15357
15728
|
</mat-tab>
|
|
15358
15729
|
<mat-tab [label]="tx('editor.tabs.grouping', 'Grouping')">
|
|
@@ -17045,6 +17416,18 @@ const PRAXIS_PAGE_BUILDER_AUTHORING_MANIFEST = {
|
|
|
17045
17416
|
targetKind: 'widgetShell',
|
|
17046
17417
|
target: { kind: 'widgetShell', resolver: 'widget-shell-by-widget-key', ambiguityPolicy: 'fail', required: true },
|
|
17047
17418
|
inputSchema: { type: 'object', required: ['widgetKey'], properties: { widgetKey: { type: 'string' }, shell: { type: 'object', properties: {
|
|
17419
|
+
appearance: { type: 'object', properties: {
|
|
17420
|
+
card: { type: 'object', properties: {
|
|
17421
|
+
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' },
|
|
17422
|
+
backdropFilter: { type: 'string', description: 'CSS backdrop effect for a translucent card, e.g. blur(16px) saturate(125%). none disables it.' },
|
|
17423
|
+
fallbackBackground: { type: 'string', description: 'Opaque background for reduced transparency, unsupported blur and fullscreen/expanded modes.' },
|
|
17424
|
+
} },
|
|
17425
|
+
header: { type: 'object', properties: {
|
|
17426
|
+
background: { type: 'string' }, borderColor: { type: 'string' },
|
|
17427
|
+
borderWidth: { type: 'string', description: 'CSS width of the bottom header divider.' }, borderStyle: { type: 'string', description: 'CSS style of the bottom header divider.' },
|
|
17428
|
+
titleColor: { type: 'string' }, subtitleColor: { type: 'string' }, iconColor: { type: 'string' },
|
|
17429
|
+
} },
|
|
17430
|
+
} },
|
|
17048
17431
|
avatar: { type: 'object', additionalProperties: false, properties: {
|
|
17049
17432
|
imageSrc: { type: 'string', description: 'Photo URL or shell template, including ${transient.employee.avatarUrl}.' },
|
|
17050
17433
|
name: { type: 'string', description: 'Accessible name and initials fallback; supports shell templates.' },
|