@praxisui/core 10.0.0-rc.4 → 10.0.0-rc.6

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.
@@ -1,12 +1,12 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Component, InjectionToken, Injectable, inject, Inject, Optional, SkipSelf, makeEnvironmentProviders, APP_INITIALIZER, signal, computed, DestroyRef, ENVIRONMENT_INITIALIZER, ErrorHandler, isSignal, Input, Directive, input, booleanAttribute, ChangeDetectionStrategy, EventEmitter, Output, SecurityContext, ViewContainerRef, Injector, effect, SimpleChange, ElementRef, ChangeDetectorRef, afterEveryRender, contentChild, viewChild, HostBinding, output, afterNextRender, ViewChild, ViewEncapsulation, createEnvironmentInjector, EnvironmentInjector, reflectComponentType, HostListener, ViewChildren } from '@angular/core';
2
+ import { Component, InjectionToken, Injectable, inject, Inject, Optional, SkipSelf, makeEnvironmentProviders, APP_INITIALIZER, signal, computed, DestroyRef, ENVIRONMENT_INITIALIZER, ErrorHandler, isSignal, Input, Directive, input, booleanAttribute, ChangeDetectionStrategy, EventEmitter, Output, SecurityContext, ViewContainerRef, Injector, effect, SimpleChange, ElementRef, ChangeDetectorRef, afterEveryRender, contentChild, viewChild, HostBinding, output, afterNextRender, ViewChild, ViewEncapsulation, createEnvironmentInjector, EnvironmentInjector, reflectComponentType, HostListener, ViewChildren, TemplateRef, Renderer2 } from '@angular/core';
3
3
  import * as i1 from '@angular/common/http';
4
- import { HttpHeaders, HttpClient, HttpParams, HttpResponse, HttpContextToken, HTTP_INTERCEPTORS, withInterceptors } from '@angular/common/http';
5
- import { of, defer, throwError, from, EMPTY, BehaviorSubject, firstValueFrom, Subject, finalize as finalize$1, shareReplay as shareReplay$1, map as map$1, switchMap as switchMap$1, catchError as catchError$1, forkJoin, isObservable, combineLatest, merge, timeout } from 'rxjs';
6
- import { switchMap, map, take, catchError, concatMap, tap, shareReplay, takeUntil, toArray, finalize } from 'rxjs/operators';
4
+ import { HttpHeaders, HttpClient, HttpParams, HttpResponse, HttpContextToken, HttpErrorResponse, HTTP_INTERCEPTORS, withInterceptors } from '@angular/common/http';
5
+ import { of, defer, throwError, from, EMPTY, BehaviorSubject, firstValueFrom, Subject, finalize as finalize$1, shareReplay as shareReplay$1, map as map$1, switchMap as switchMap$1, catchError as catchError$1, forkJoin, isObservable, combineLatest, merge, Subscription, timeout } from 'rxjs';
6
+ import { switchMap, map, take, catchError, concatMap, tap, shareReplay, takeUntil, toArray, finalize, filter } from 'rxjs/operators';
7
7
  import * as i1$3 from '@angular/common';
8
8
  import { Location, DOCUMENT, NgTemplateOutlet, CommonModule, NgComponentOutlet } from '@angular/common';
9
- import { Router, ActivatedRoute } from '@angular/router';
9
+ import { Router, ActivatedRoute, RouterLink, RouterLinkActive } from '@angular/router';
10
10
  import * as i1$2 from '@angular/forms';
11
11
  import { Validators, FormGroup, FormControl, FormArray, FormsModule, ReactiveFormsModule } from '@angular/forms';
12
12
  import { MatSnackBar } from '@angular/material/snack-bar';
@@ -32,7 +32,7 @@ import { Overlay, OverlayModule } from '@angular/cdk/overlay';
32
32
  import { DomPortal } from '@angular/cdk/portal';
33
33
  import * as i4 from '@angular/material/menu';
34
34
  import { MAT_MENU_SCROLL_STRATEGY, MatMenuTrigger, MatMenuModule } from '@angular/material/menu';
35
- import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
35
+ import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
36
36
  import * as i5$1 from '@angular/material/radio';
37
37
  import { MatRadioModule } from '@angular/material/radio';
38
38
  import * as i3$1 from '@angular/material/progress-spinner';
@@ -1156,6 +1156,65 @@ function getPraxisTableCellVisualizationConstraint(kind) {
1156
1156
  }
1157
1157
  return null;
1158
1158
  }
1159
+ /**
1160
+ * Resolucao pura e canonica de tom semantico e rotulo acessivel para micro-visualizacoes.
1161
+ *
1162
+ * Precedencia de avaliacao:
1163
+ * 1. Correspondencia discreta (`equals`), tolerante a numeros e strings.
1164
+ * 2. Faixa numerica continua (`min` e `max`, inclusiva).
1165
+ * 3. Faixa aberta a direita (`min` ate infinito).
1166
+ * 4. Faixa aberta a esquerda (menos infinito ate `max`).
1167
+ * 5. Limiar de corte progressivo (`value`).
1168
+ * 6. Fallback estatico (`fallbackTone`).
1169
+ */
1170
+ function resolvePraxisMicroVisualizationTone(value, thresholds, fallbackTone) {
1171
+ if (value === undefined || value === null || !Array.isArray(thresholds) || thresholds.length === 0) {
1172
+ return fallbackTone ? { tone: fallbackTone } : undefined;
1173
+ }
1174
+ const num = typeof value === 'number' ? value : Number(value);
1175
+ const isNum = Number.isFinite(num);
1176
+ // 1. Pass 1: Correspondencia discreta (equals) tem prioridade maxima
1177
+ for (const t of thresholds) {
1178
+ if (!t || !t.tone || t.equals === undefined) {
1179
+ continue;
1180
+ }
1181
+ if (t.equals === value || String(t.equals) === String(value)) {
1182
+ return { tone: t.tone, label: t.label, matchedThreshold: t };
1183
+ }
1184
+ if (isNum && Number.isFinite(Number(t.equals)) && Number(t.equals) === num) {
1185
+ return { tone: t.tone, label: t.label, matchedThreshold: t };
1186
+ }
1187
+ }
1188
+ // 2. Pass 2: Faixas continuas ou limiares de corte
1189
+ if (isNum) {
1190
+ for (const t of thresholds) {
1191
+ if (!t || !t.tone) {
1192
+ continue;
1193
+ }
1194
+ const hasMin = t.min !== undefined && Number.isFinite(t.min);
1195
+ const hasMax = t.max !== undefined && Number.isFinite(t.max);
1196
+ if (hasMin && hasMax) {
1197
+ if (num >= t.min && num <= t.max) {
1198
+ return { tone: t.tone, label: t.label, matchedThreshold: t };
1199
+ }
1200
+ }
1201
+ else if (hasMin) {
1202
+ if (num >= t.min) {
1203
+ return { tone: t.tone, label: t.label, matchedThreshold: t };
1204
+ }
1205
+ }
1206
+ else if (hasMax) {
1207
+ if (num <= t.max) {
1208
+ return { tone: t.tone, label: t.label, matchedThreshold: t };
1209
+ }
1210
+ }
1211
+ else if (t.value !== undefined && Number.isFinite(t.value) && num >= t.value) {
1212
+ return { tone: t.tone, label: t.label, matchedThreshold: t };
1213
+ }
1214
+ }
1215
+ }
1216
+ return fallbackTone ? { tone: fallbackTone } : undefined;
1217
+ }
1159
1218
  function normalizePraxisPresentationVisualization(value) {
1160
1219
  if (!value || typeof value !== 'object') {
1161
1220
  return undefined;
@@ -1170,14 +1229,40 @@ function normalizePraxisPresentationVisualization(value) {
1170
1229
  const tone = normalizeEnum$1(value.tone, VISUALIZATION_TONES);
1171
1230
  const ariaLabel = normalizeText$2(value.ariaLabel);
1172
1231
  const valueSuffix = normalizeText$2(value.valueSuffix);
1232
+ const thresholdsObj = normalizeThresholds(value.thresholds);
1233
+ let resolvedTone = tone;
1234
+ let resolvedAriaLabel = ariaLabel;
1235
+ if (!resolvedTone && thresholdsObj?.thresholds?.length) {
1236
+ let evalVal = value.value;
1237
+ if (evalVal === undefined && Array.isArray(value.points) && value.points.length > 0) {
1238
+ const last = value.points[value.points.length - 1];
1239
+ if (last && typeof last === 'object' && Number.isFinite(last.value)) {
1240
+ evalVal = last.value;
1241
+ }
1242
+ }
1243
+ else if (Array.isArray(evalVal) && evalVal.length > 0) {
1244
+ const last = evalVal[evalVal.length - 1];
1245
+ evalVal = typeof last === 'object' && last !== null && 'value' in last ? last.value : last;
1246
+ }
1247
+ if (evalVal !== undefined && evalVal !== null) {
1248
+ const resolution = resolvePraxisMicroVisualizationTone(evalVal, thresholdsObj.thresholds);
1249
+ if (resolution?.tone) {
1250
+ resolvedTone = resolution.tone;
1251
+ }
1252
+ if (resolution?.label && !resolvedAriaLabel) {
1253
+ resolvedAriaLabel = `${fallbackText}: ${resolution.label}`;
1254
+ }
1255
+ }
1256
+ }
1173
1257
  return {
1174
1258
  kind,
1175
1259
  fallbackText,
1176
1260
  ...(surface ? { surface } : {}),
1177
1261
  ...(size ? { size } : {}),
1178
- ...(tone ? { tone } : {}),
1179
- ...(ariaLabel ? { ariaLabel } : {}),
1262
+ ...(resolvedTone ? { tone: resolvedTone } : {}),
1263
+ ...(resolvedAriaLabel ? { ariaLabel: resolvedAriaLabel } : {}),
1180
1264
  ...(valueSuffix ? { valueSuffix } : {}),
1265
+ ...(typeof value.compactValue === 'boolean' ? { compactValue: value.compactValue } : {}),
1181
1266
  ...(Object.prototype.hasOwnProperty.call(value, 'value') ? { value: value.value } : {}),
1182
1267
  ...(normalizeExpression(value.valueExpr, 'valueExpr') ?? {}),
1183
1268
  ...(normalizeExpression(value.valueSuffixExpr, 'valueSuffixExpr') ?? {}),
@@ -1196,6 +1281,12 @@ function normalizePraxisPresentationVisualization(value) {
1196
1281
  ...(normalizeItems(value.items) ?? {}),
1197
1282
  ...(normalizeExpression(value.itemsExpr, 'itemsExpr') ?? {}),
1198
1283
  ...(normalizeExpression(value.toneExpr, 'toneExpr') ?? {}),
1284
+ ...(value.stepLabelWidth !== undefined && (typeof value.stepLabelWidth === 'number' || typeof value.stepLabelWidth === 'string')
1285
+ ? { stepLabelWidth: value.stepLabelWidth }
1286
+ : {}),
1287
+ ...(typeof value.stepLabelLineClamp === 'number' && Number.isFinite(value.stepLabelLineClamp)
1288
+ ? { stepLabelLineClamp: value.stepLabelLineClamp }
1289
+ : {}),
1199
1290
  ...(normalizeExpression(value.ariaLabelExpr, 'ariaLabelExpr') ?? {}),
1200
1291
  ...(normalizeExpression(value.fallbackTextExpr, 'fallbackTextExpr') ?? {}),
1201
1292
  };
@@ -1284,12 +1375,15 @@ function renderStackedBarVisualizationHtml(visualization, options) {
1284
1375
  }
1285
1376
  function resolveBulletMax(visualization) {
1286
1377
  const thresholds = visualization.thresholds ?? [];
1378
+ const thresholdValues = thresholds
1379
+ .map(t => t.value ?? t.max ?? t.min ?? 0)
1380
+ .filter((v) => Number.isFinite(v) && v > 0);
1287
1381
  const values = [
1288
1382
  toFiniteNumber(visualization.value) ?? 0,
1289
1383
  visualization.target ?? 0,
1290
1384
  visualization.total ?? 0,
1291
- ...thresholds.map(t => t.value)
1292
- ].filter(v => Number.isFinite(v) && v > 0);
1385
+ ...thresholdValues
1386
+ ].filter((v) => Number.isFinite(v) && v > 0);
1293
1387
  return Math.max(...values, 100);
1294
1388
  }
1295
1389
  function renderBulletVisualizationHtml(visualization, options) {
@@ -1301,11 +1395,17 @@ function renderBulletVisualizationHtml(visualization, options) {
1301
1395
  const target = toFiniteNumber(visualization.target);
1302
1396
  const targetLeft = target === undefined ? null : percent(target, max);
1303
1397
  const isTable = visualization.surface === 'table-cell';
1304
- // thresholds
1398
+ // thresholds: sort ascending by boundary value so bands draw continuously regardless of declaration order
1305
1399
  const thresholds = visualization.thresholds ?? [];
1400
+ const sortedThresholds = [...thresholds].sort((a, b) => {
1401
+ const aVal = a.value ?? a.max ?? a.min ?? 0;
1402
+ const bVal = b.value ?? b.max ?? b.min ?? 0;
1403
+ return aVal - bVal;
1404
+ });
1306
1405
  let previous = 0;
1307
- const bands = thresholds.map(t => {
1308
- const current = Math.max(t.value, previous);
1406
+ const bands = sortedThresholds.map(t => {
1407
+ const tVal = t.value ?? t.max ?? t.min ?? 0;
1408
+ const current = Math.max(tVal, previous);
1309
1409
  const left = max > 0 ? percent(previous, max) : '0';
1310
1410
  const width = max > 0 ? percent(current - previous, max) : '0';
1311
1411
  previous = current;
@@ -1325,6 +1425,10 @@ function renderBulletVisualizationHtml(visualization, options) {
1325
1425
  <span>${escapeHtml$1(formatMicroNumber(visualization.baseline ?? 0, options.locale))}</span>
1326
1426
  <span>${escapeHtml$1(formatMicroNumber(max, options.locale))}</span>
1327
1427
  </div>`;
1428
+ const showCompactValue = isTable && visualization.compactValue !== false;
1429
+ const compactValueHtml = showCompactValue
1430
+ ? `<span class="pfx-micro-bullet__compact-value">${escapeHtml$1(formatMicroNumber(currentValue, options.locale))}${escapeHtml$1(visualization.valueSuffix ?? '')}</span>`
1431
+ : '';
1328
1432
  return `
1329
1433
  <span class="pfx-micro-viz pfx-micro-viz__bullet" data-tone="${escapeHtml$1(visualization.tone ?? 'info')}" role="img" aria-label="${escapeHtml$1(visualization.ariaLabel ?? visualization.fallbackText)}">
1330
1434
  ${topHtml}
@@ -1334,6 +1438,7 @@ function renderBulletVisualizationHtml(visualization, options) {
1334
1438
  ${targetLeft === null ? '' : `<span class="pfx-micro-bullet__target" style="left: ${targetLeft}%;"></span>`}
1335
1439
  </span>
1336
1440
  ${bottomHtml}
1441
+ ${compactValueHtml}
1337
1442
  </span>`;
1338
1443
  }
1339
1444
  function renderDeltaVisualizationHtml(visualization, options) {
@@ -1395,7 +1500,32 @@ function normalizeSegments(value) {
1395
1500
  return segments.length ? { segments } : undefined;
1396
1501
  }
1397
1502
  function normalizeThresholds(value) {
1398
- const thresholds = normalizeNumericEntries(value);
1503
+ if (!Array.isArray(value)) {
1504
+ return undefined;
1505
+ }
1506
+ const thresholds = value
1507
+ .map((item) => {
1508
+ if (!item || typeof item !== 'object')
1509
+ return undefined;
1510
+ const numVal = toFiniteNumber(item.value);
1511
+ const minVal = toFiniteNumber(item.min);
1512
+ const maxVal = toFiniteNumber(item.max);
1513
+ const tone = normalizeEnum$1(item.tone, VISUALIZATION_TONES);
1514
+ const label = normalizeText$2(item.label);
1515
+ const hasCondition = numVal !== undefined || minVal !== undefined || maxVal !== undefined || item.equals !== undefined;
1516
+ if (!hasCondition) {
1517
+ return undefined;
1518
+ }
1519
+ return {
1520
+ ...(label ? { label } : {}),
1521
+ ...(numVal !== undefined ? { value: numVal } : {}),
1522
+ ...(minVal !== undefined ? { min: minVal } : {}),
1523
+ ...(maxVal !== undefined ? { max: maxVal } : {}),
1524
+ ...(item.equals !== undefined ? { equals: item.equals } : {}),
1525
+ ...(tone ? { tone } : {}),
1526
+ };
1527
+ })
1528
+ .filter((item) => !!item);
1399
1529
  return thresholds.length ? { thresholds } : undefined;
1400
1530
  }
1401
1531
  function normalizeItems(value) {
@@ -1674,7 +1804,10 @@ function renderColumnVisualizationHtml(visualization, options) {
1674
1804
  const x = padding + i * ((width - 2 * padding) / points.length) + 1;
1675
1805
  const rectHeight = Math.max(2, ((p.value - minVal) / valRange) * (height - 2 * padding));
1676
1806
  const rectY = height - padding - rectHeight;
1677
- const itemTone = p.tone ?? tone;
1807
+ const itemTone = p.tone ??
1808
+ (visualization.thresholds?.length
1809
+ ? resolvePraxisMicroVisualizationTone(p.value, visualization.thresholds)?.tone ?? tone
1810
+ : tone);
1678
1811
  return `<rect class="pfx-micro-column__bar" x="${x.toFixed(1)}" y="${rectY.toFixed(1)}" width="${colWidth.toFixed(1)}" height="${rectHeight.toFixed(1)}" fill="currentColor" data-tone="${escapeHtml$1(itemTone)}" />`;
1679
1812
  }).join('');
1680
1813
  const isTable = visualization.surface === 'table-cell';
@@ -1709,12 +1842,23 @@ function renderProcessFlowVisualizationHtml(visualization, options) {
1709
1842
  const isTable = visualization.surface === 'table-cell';
1710
1843
  const stepLabels = items.map((item, index) => item.label ?? item.id ?? `${index + 1}`);
1711
1844
  const aria = visualization.ariaLabel ?? `${visualization.fallbackText}: ${stepLabels.join(' > ')}`;
1845
+ const labelStyleAttr = [];
1846
+ if (visualization.stepLabelWidth !== undefined) {
1847
+ const widthVal = typeof visualization.stepLabelWidth === 'number'
1848
+ ? `${visualization.stepLabelWidth}px`
1849
+ : String(visualization.stepLabelWidth);
1850
+ labelStyleAttr.push(`inline-size: ${escapeHtml$1(widthVal)}; max-width: ${escapeHtml$1(widthVal)};`);
1851
+ }
1852
+ if (visualization.stepLabelLineClamp !== undefined) {
1853
+ labelStyleAttr.push(`-webkit-line-clamp: ${visualization.stepLabelLineClamp};`);
1854
+ }
1855
+ const labelInlineStyle = labelStyleAttr.length ? ` style="${labelStyleAttr.join(' ')}"` : '';
1712
1856
  const stepsHtml = items.map((item, index) => {
1713
1857
  const itemTone = item.tone ?? 'neutral';
1714
1858
  const itemState = item.state ?? '';
1715
1859
  const isLast = index === items.length - 1;
1716
1860
  const itemLabel = stepLabels[index];
1717
- const label = !isTable && item.label ? `<span class="pfx-micro-step__label">${escapeHtml$1(item.label)}</span>` : '';
1861
+ const label = !isTable && item.label ? `<span class="pfx-micro-step__label"${labelInlineStyle}>${escapeHtml$1(item.label)}</span>` : '';
1718
1862
  const connector = !isLast ? `<span class="pfx-micro-step__connector" data-tone="${escapeHtml$1(itemTone)}"></span>` : '';
1719
1863
  const stepCircle = `
1720
1864
  <span class="pfx-micro-step__node" data-tone="${escapeHtml$1(itemTone)}" aria-hidden="true">
@@ -1803,6 +1947,12 @@ function normalizeFieldPresentation(value) {
1803
1947
  const tooltip = normalizeText$1(value.tooltip);
1804
1948
  const prefix = normalizeText$1(value.prefix);
1805
1949
  const suffix = normalizeText$1(value.suffix);
1950
+ const iconTrue = normalizeText$1(value.iconTrue);
1951
+ const iconFalse = normalizeText$1(value.iconFalse);
1952
+ const toneTrue = normalizeTone(value.toneTrue);
1953
+ const toneFalse = normalizeTone(value.toneFalse);
1954
+ const labelTrue = normalizeText$1(value.labelTrue);
1955
+ const labelFalse = normalizeText$1(value.labelFalse);
1806
1956
  const visualization = normalizePraxisPresentationVisualization(value.visualization);
1807
1957
  return {
1808
1958
  ...(presenter ? { presenter } : {}),
@@ -1814,6 +1964,12 @@ function normalizeFieldPresentation(value) {
1814
1964
  ...(tooltip ? { tooltip } : {}),
1815
1965
  ...(prefix ? { prefix } : {}),
1816
1966
  ...(suffix ? { suffix } : {}),
1967
+ ...(iconTrue ? { iconTrue } : {}),
1968
+ ...(iconFalse ? { iconFalse } : {}),
1969
+ ...(toneTrue ? { toneTrue } : {}),
1970
+ ...(toneFalse ? { toneFalse } : {}),
1971
+ ...(labelTrue ? { labelTrue } : {}),
1972
+ ...(labelFalse ? { labelFalse } : {}),
1817
1973
  ...(value.valuePresentation && typeof value.valuePresentation === 'object'
1818
1974
  ? { valuePresentation: value.valuePresentation }
1819
1975
  : {}),
@@ -1941,9 +2097,10 @@ const LOOKUP_FILTER_OPERATORS = new Set([
1941
2097
  'lte',
1942
2098
  ]);
1943
2099
  /**
1944
- * SchemaNormalizerService
2100
+ * SchemaNormalizationKernel (internal)
1945
2101
  * -----------------------
1946
- * Serviço responsável por converter um esquema bruto (OpenAPI + x-ui) em uma
2102
+ * Cálculo compartilhado pelo serviço Angular, sem DI ou export público, que converte
2103
+ * um esquema bruto (OpenAPI + x-ui) em uma
1947
2104
  * lista tipada de FieldDefinition consumida pelo restante do sistema
1948
2105
  * (mapeadores de metadata, criador de FormGroup, carregador de campos dinâmicos, etc.).
1949
2106
  *
@@ -1976,7 +2133,7 @@ const LOOKUP_FILTER_OPERATORS = new Set([
1976
2133
  * const fields = normalizer.normalizeSchema(schema);
1977
2134
  * // fields: [{ name: 'email', label: 'E-mail', type: 'string', controlType: 'input', required: true, email: true }]
1978
2135
  */
1979
- class SchemaNormalizerService {
2136
+ class SchemaNormalizationKernel {
1980
2137
  clonePlain(value) {
1981
2138
  if (value === undefined || value === null) {
1982
2139
  return value;
@@ -2590,16 +2747,30 @@ class SchemaNormalizerService {
2590
2747
  }
2591
2748
  return Object.keys(filtering).length ? filtering : undefined;
2592
2749
  }
2593
- /** Parse option arrays into `{ key, value }` objects. */
2750
+ /** Parse option arrays into `{ key, value }` objects with label preservation. */
2594
2751
  parseOptions(value) {
2595
2752
  if (!Array.isArray(value)) {
2596
2753
  return [];
2597
2754
  }
2598
2755
  return value.map((opt) => {
2599
- if (typeof opt === 'object') {
2600
- const k = opt.key ?? opt.value ?? opt.label ?? '';
2601
- const v = opt.value ?? opt.label ?? opt.key ?? '';
2602
- return { key: String(k), value: String(v) };
2756
+ if (typeof opt === 'object' && opt !== null) {
2757
+ const k = opt.key ?? opt.value ?? opt.id ?? opt.label ?? '';
2758
+ const display = opt.label ??
2759
+ opt.text ??
2760
+ opt.title ??
2761
+ (opt.key !== undefined && opt.value !== undefined && opt.key !== opt.value
2762
+ ? opt.value
2763
+ : undefined);
2764
+ const v = display ?? opt.value ?? opt.key ?? '';
2765
+ const result = {
2766
+ ...opt,
2767
+ key: String(k),
2768
+ value: String(v),
2769
+ };
2770
+ if (display !== undefined && display !== null) {
2771
+ result.label = String(display);
2772
+ }
2773
+ return result;
2603
2774
  }
2604
2775
  return { key: String(opt), value: String(opt) };
2605
2776
  });
@@ -3416,6 +3587,26 @@ class SchemaNormalizerService {
3416
3587
  }
3417
3588
  return undefined;
3418
3589
  }
3590
+ }
3591
+
3592
+ /** Normalizes the exact schema/root object using the canonical Core calculation.
3593
+ * The caller must admit source, content and execution before invoking this function:
3594
+ * declared function expressions may execute during field construction.
3595
+ * This performs neither strict JSON parsing nor provenance/schema admission.
3596
+ */
3597
+ function normalizeSchemaFields(schema) {
3598
+ return new SchemaNormalizationKernel().normalizeSchema(schema);
3599
+ }
3600
+
3601
+ /** Official Angular schema normalization facade.
3602
+ * The shared internal kernel owns the existing field semantics and local refs.
3603
+ * Origin/content and execution authority must be admitted before this call:
3604
+ * normalization can evaluate declared function expressions. This is not a sandbox.
3605
+ */
3606
+ class SchemaNormalizerService {
3607
+ normalizeSchema(schema) {
3608
+ return normalizeSchemaFields(schema);
3609
+ }
3419
3610
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SchemaNormalizerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3420
3611
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: SchemaNormalizerService, providedIn: 'root' });
3421
3612
  }
@@ -4452,6 +4643,7 @@ class GenericCrudService {
4452
4643
  _lastResourceCapabilityDigest = null;
4453
4644
  _lastResourceIdentity = null;
4454
4645
  _lastResourceIdentityDiagnostics = [];
4646
+ _lastResourceQuickFilters = null;
4455
4647
  // Última informação do schema consumido (para auditoria/merge)
4456
4648
  _lastSchemaInfo = {};
4457
4649
  _resourceEtags = new Map();
@@ -4563,6 +4755,7 @@ class GenericCrudService {
4563
4755
  this._lastResourceCapabilityDigest = null;
4564
4756
  this._lastResourceIdentity = null;
4565
4757
  this._lastResourceIdentityDiagnostics = [];
4758
+ this._lastResourceQuickFilters = null;
4566
4759
  this._resourceEtags.clear();
4567
4760
  debugCrudService('[CRUD:Service] configure', {
4568
4761
  resourcePath, baseApiUrl: this.baseApiUrl,
@@ -4855,6 +5048,12 @@ class GenericCrudService {
4855
5048
  filterExpressionSupported: canonicalOperations['filterExpression'] === true,
4856
5049
  };
4857
5050
  }
5051
+ if (Array.isArray(resource?.quickFilters)) {
5052
+ this._lastResourceQuickFilters = [...resource.quickFilters];
5053
+ }
5054
+ else {
5055
+ this._lastResourceQuickFilters = null;
5056
+ }
4858
5057
  }
4859
5058
  normalizeCanonicalCapabilities(value) {
4860
5059
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
@@ -4995,6 +5194,10 @@ class GenericCrudService {
4995
5194
  : undefined,
4996
5195
  }));
4997
5196
  }
5197
+ /** Retorna os filtros rápidos canônicos descobertos no schema x-ui.resource.quickFilters. */
5198
+ getResourceQuickFilters() {
5199
+ return this._lastResourceQuickFilters ? [...this._lastResourceQuickFilters] : null;
5200
+ }
4998
5201
  /** Retorna o último schemaId e hash conhecidos após getSchema()/getFilteredSchema(). */
4999
5202
  getLastSchemaInfo() {
5000
5203
  return { ...this._lastSchemaInfo };
@@ -7437,6 +7640,82 @@ function rotateRight(value, amount) {
7437
7640
  return (value >>> amount) | (value << (32 - amount));
7438
7641
  }
7439
7642
 
7643
+ /** Copy a raw authoring/patch object without JSON's silent omissions or coercions.
7644
+ * No schema, authority, merge, normalization or server quota is evaluated here.
7645
+ */
7646
+ function cloneUiLayoutJsonDocument(value) {
7647
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
7648
+ throw invalid();
7649
+ return copyJson(value, false);
7650
+ }
7651
+ /** Local structural identity: null and array order count; object insertion order does not.
7652
+ * This is not a persisted hash or a native admission receipt.
7653
+ */
7654
+ function uiLayoutJsonIdentity(value) {
7655
+ try {
7656
+ return JSON.stringify(copyJson(value, true));
7657
+ }
7658
+ catch {
7659
+ throw invalid();
7660
+ }
7661
+ }
7662
+ function copyJson(value, sorted) {
7663
+ const result = {};
7664
+ const parents = new Set();
7665
+ const stack = [{ value, parent: result, key: 'value' }];
7666
+ while (stack.length) {
7667
+ const task = stack.pop();
7668
+ const item = task.value;
7669
+ if (task.exit) {
7670
+ parents.delete(item);
7671
+ continue;
7672
+ }
7673
+ if (item === null || typeof item === 'string' || typeof item === 'boolean'
7674
+ || typeof item === 'number' && Number.isFinite(item)) {
7675
+ put(task.parent, task.key, item);
7676
+ continue;
7677
+ }
7678
+ if (typeof item !== 'object' || item === null || parents.has(item)
7679
+ || Object.getOwnPropertySymbols(item).length)
7680
+ throw invalid();
7681
+ const array = Array.isArray(item);
7682
+ if (!array && ![Object.prototype, null].includes(Object.getPrototypeOf(item)))
7683
+ throw invalid();
7684
+ const descriptors = Object.getOwnPropertyDescriptors(item);
7685
+ let keys = Object.keys(descriptors);
7686
+ if (array) {
7687
+ const length = descriptors['length'].value;
7688
+ if (keys.length !== length + 1)
7689
+ throw invalid();
7690
+ keys = keys.filter(key => key !== 'length');
7691
+ for (let index = 0; index < length; index++)
7692
+ if (!Object.hasOwn(descriptors, index))
7693
+ throw invalid();
7694
+ }
7695
+ else if (sorted)
7696
+ keys.sort();
7697
+ for (const key of keys) {
7698
+ const descriptor = descriptors[key];
7699
+ if (!descriptor.enumerable || !Object.hasOwn(descriptor, 'value'))
7700
+ throw invalid();
7701
+ }
7702
+ const copy = array ? [] : {};
7703
+ put(task.parent, task.key, copy);
7704
+ parents.add(item);
7705
+ stack.push({ ...task, exit: true });
7706
+ for (let index = keys.length - 1; index >= 0; index--) {
7707
+ const key = keys[index];
7708
+ stack.push({ value: descriptors[key].value, parent: copy, key });
7709
+ }
7710
+ }
7711
+ return result.value;
7712
+ }
7713
+ function put(target, key, value) {
7714
+ // Define data properties: __proto__ is JSON content, never a prototype setter.
7715
+ Object.defineProperty(target, key, { value, enumerable: true, configurable: true, writable: true });
7716
+ }
7717
+ function invalid() { return new TypeError('UI layout requires lossless JSON values.'); }
7718
+
7440
7719
  const PRAXIS_I18N_CONFIG = new InjectionToken('PRAXIS_I18N_CONFIG', {
7441
7720
  factory: () => ({}),
7442
7721
  });
@@ -12189,14 +12468,18 @@ function mapFieldDefinitionsToMetadata(fields) {
12189
12468
  return fields.map(mapFieldDefinitionToMetadata);
12190
12469
  }
12191
12470
 
12471
+ const DEFAULT_DYNAMIC_FORM_SCHEMA_CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
12472
+ const DEFAULT_DYNAMIC_FORM_SCHEMA_CACHE_MAX_ENTRIES = 100;
12192
12473
  /**
12193
12474
  * DynamicFormService
12194
12475
  * -------------------
12195
12476
  * Serviço utilitário central para criação e atualização de formulários dinâmicos
12196
12477
  * (Reactive Forms) a partir de metadados corporativos (FieldMetadata) ou
12197
- * definições do servidor (FieldDefinition).
12478
+ * definições do servidor (FieldDefinition), com cache de primeiro nível (L1) em
12479
+ * memória para esquemas OpenAPI compilados.
12198
12480
  *
12199
12481
  * Principais responsabilidades
12482
+ * - Cache L1 em memória para esquemas normalizados e compilados (Issue #2).
12200
12483
  * - Converter ValidatorOptions/FieldDefinition em ValidatorFn/AsyncValidatorFn.
12201
12484
  * - Aplicar updateOn por campo a partir de updateOn ou validationTrigger (change/blur/submit).
12202
12485
  * - Injetar validadores específicos de UI conforme o controlType:
@@ -12210,6 +12493,7 @@ function mapFieldDefinitionsToMetadata(fields) {
12210
12493
  * - createFormGroupFromMetadata(fields): cria FormGroup diretamente dos metadados normalizados.
12211
12494
  * - createFormGroupFromFields(defs): cria FormGroup a partir de definições cruas do backend.
12212
12495
  * - updateControlFromMetadata/Field: reavalia e aplica validadores/estado em controles existentes.
12496
+ * - getCompiledSchema / setCompiledSchema: recuperação e persistência em cache L1 para 0ms tab transitions.
12213
12497
  *
12214
12498
  * Boas práticas
12215
12499
  * - Prefira trabalhar com FieldMetadata + ValidatorOptions (camada normalizada) no app.
@@ -12222,6 +12506,108 @@ function mapFieldDefinitionsToMetadata(fields) {
12222
12506
  * dynamicForm.updateControlFromMetadata(group, { ...meta, validators: { ...meta.validators, required: true }});
12223
12507
  */
12224
12508
  class DynamicFormService {
12509
+ global;
12510
+ compiledSchemaCache = new Map();
12511
+ constructor(global) {
12512
+ if (global !== undefined) {
12513
+ this.global = global;
12514
+ }
12515
+ else {
12516
+ try {
12517
+ this.global = inject(GlobalConfigService, { optional: true });
12518
+ }
12519
+ catch {
12520
+ this.global = null;
12521
+ }
12522
+ }
12523
+ }
12524
+ // ------------------- Schema L1 Cache -------------------
12525
+ /**
12526
+ * Constrói a chave estável de cache para um esquema com base nos parâmetros estruturais.
12527
+ */
12528
+ buildSchemaCacheKey(params) {
12529
+ const schemaId = buildSchemaId(params);
12530
+ const prefix = params.baseUrl ? `${params.baseUrl}::` : '';
12531
+ const tenantPart = params.tenant ? `::tenant:${params.tenant}` : '';
12532
+ const localePart = params.locale ? `::locale:${params.locale}` : '';
12533
+ return `${prefix}${schemaId}${tenantPart}${localePart}`;
12534
+ }
12535
+ /**
12536
+ * Obtém um esquema compilado em cache L1 em memória, respeitando o TTL de expiração.
12537
+ */
12538
+ getCompiledSchema(cacheKey) {
12539
+ const entry = this.compiledSchemaCache.get(cacheKey);
12540
+ if (!entry)
12541
+ return undefined;
12542
+ if (Date.now() > entry.expiresAt) {
12543
+ this.compiledSchemaCache.delete(cacheKey);
12544
+ return undefined;
12545
+ }
12546
+ return entry.compiled;
12547
+ }
12548
+ /**
12549
+ * Armazena um esquema compilado em cache L1 em memória com TTL governado.
12550
+ */
12551
+ setCompiledSchema(cacheKey, compiled, customTtlMs) {
12552
+ const ttlMs = customTtlMs ?? this.resolveSchemaCacheTtlMs();
12553
+ const expiresAt = Date.now() + ttlMs;
12554
+ const maxEntries = this.resolveSchemaCacheMaxEntries();
12555
+ if (this.compiledSchemaCache.size >= maxEntries && !this.compiledSchemaCache.has(cacheKey)) {
12556
+ const oldestKey = this.compiledSchemaCache.keys().next().value;
12557
+ if (oldestKey)
12558
+ this.compiledSchemaCache.delete(oldestKey);
12559
+ }
12560
+ this.compiledSchemaCache.set(cacheKey, { compiled, expiresAt });
12561
+ }
12562
+ /**
12563
+ * Verifica se existe um esquema compilado válido e não-expirado em cache.
12564
+ */
12565
+ hasCompiledSchema(cacheKey) {
12566
+ return this.getCompiledSchema(cacheKey) !== undefined;
12567
+ }
12568
+ /**
12569
+ * Invalida uma entrada específica ou todo o cache L1 de esquemas compilados.
12570
+ */
12571
+ invalidateCompiledSchema(cacheKey) {
12572
+ if (cacheKey) {
12573
+ this.compiledSchemaCache.delete(cacheKey);
12574
+ }
12575
+ else {
12576
+ this.clearCompiledSchemaCache();
12577
+ }
12578
+ }
12579
+ /**
12580
+ * Limpa integralmente o cache L1 em memória.
12581
+ */
12582
+ clearCompiledSchemaCache() {
12583
+ this.compiledSchemaCache.clear();
12584
+ }
12585
+ /**
12586
+ * Retorna a quantidade de entradas atualmente em cache L1.
12587
+ */
12588
+ getCompiledSchemaCacheSize() {
12589
+ return this.compiledSchemaCache.size;
12590
+ }
12591
+ resolveSchemaCacheTtlMs() {
12592
+ try {
12593
+ const configured = this.global?.get('cache.schemaCacheTtlMs');
12594
+ if (typeof configured === 'number' && Number.isFinite(configured) && configured > 0) {
12595
+ return configured;
12596
+ }
12597
+ }
12598
+ catch { }
12599
+ return DEFAULT_DYNAMIC_FORM_SCHEMA_CACHE_TTL_MS;
12600
+ }
12601
+ resolveSchemaCacheMaxEntries() {
12602
+ try {
12603
+ const configured = this.global?.get('cache.schemaCacheMaxEntries');
12604
+ if (typeof configured === 'number' && Number.isFinite(configured) && configured > 0) {
12605
+ return configured;
12606
+ }
12607
+ }
12608
+ catch { }
12609
+ return DEFAULT_DYNAMIC_FORM_SCHEMA_CACHE_MAX_ENTRIES;
12610
+ }
12225
12611
  // ------------------- Path helpers (nested groups) -------------------
12226
12612
  isPath(name) {
12227
12613
  return name.includes('.');
@@ -12817,13 +13203,15 @@ class DynamicFormService {
12817
13203
  });
12818
13204
  return root;
12819
13205
  }
12820
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
13206
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormService, deps: [{ token: GlobalConfigService, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
12821
13207
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormService, providedIn: 'root' });
12822
13208
  }
12823
13209
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormService, decorators: [{
12824
13210
  type: Injectable,
12825
13211
  args: [{ providedIn: 'root' }]
12826
- }] });
13212
+ }], ctorParameters: () => [{ type: GlobalConfigService, decorators: [{
13213
+ type: Optional
13214
+ }] }] });
12827
13215
 
12828
13216
  /**
12829
13217
  * Maps HTTP or network errors to user-friendly messages.
@@ -16417,15 +16805,7 @@ function providePraxisEnterpriseRuntimeContext(options = {}) {
16417
16805
  { provide: PRAXIS_ENTERPRISE_RUNTIME_CONTEXT_OPTIONS, useValue: normalized },
16418
16806
  {
16419
16807
  provide: PRAXIS_ENTERPRISE_RUNTIME_CONTEXT_READY,
16420
- useFactory: (service) => {
16421
- let promise = null;
16422
- return () => {
16423
- if (promise)
16424
- return promise;
16425
- promise = service.ready().then(() => undefined);
16426
- return promise;
16427
- };
16428
- },
16808
+ useFactory: (service) => () => service.ready().then(() => undefined),
16429
16809
  deps: [EnterpriseRuntimeContextService],
16430
16810
  },
16431
16811
  {
@@ -16437,6 +16817,18 @@ function providePraxisEnterpriseRuntimeContext(options = {}) {
16437
16817
  ];
16438
16818
  }
16439
16819
 
16820
+ const CONTEXT_HEADERS = new Set([
16821
+ 'x-tenant-id', 'x-tenant', 'x-user-id', 'x-env', 'x-updated-by',
16822
+ 'x-praxis-profile-id', 'x-praxis-context-version',
16823
+ ]);
16824
+ const EXPERIENCE_HEADERS = new Set(['accept-language', 'x-timezone', 'x-praxis-module-key']);
16825
+ /** A response for an older selection must never be presented as confirmed. */
16826
+ class EnterpriseRuntimeContextObsoleteError extends Error {
16827
+ constructor() {
16828
+ super('Runtime context changed before the switch was confirmed.');
16829
+ this.name = 'EnterpriseRuntimeContextObsoleteError';
16830
+ }
16831
+ }
16440
16832
  class EnterpriseRuntimeContextService {
16441
16833
  http = inject(HttpClient);
16442
16834
  apiUrl = inject(API_URL, { optional: true });
@@ -16445,10 +16837,15 @@ class EnterpriseRuntimeContextService {
16445
16837
  });
16446
16838
  contextSubject = new BehaviorSubject(null);
16447
16839
  loadPromise = null;
16840
+ contextRevision = 0;
16448
16841
  contextChanges$ = this.contextSubject.asObservable();
16449
16842
  get snapshot() {
16450
16843
  return this.contextSubject.value;
16451
16844
  }
16845
+ get effectiveContext() {
16846
+ const context = this.snapshot;
16847
+ return context?.state === 'ready' ? context.effectiveContext : null;
16848
+ }
16452
16849
  ready() {
16453
16850
  if (this.contextSubject.value) {
16454
16851
  return Promise.resolve(this.contextSubject.value);
@@ -16456,7 +16853,9 @@ class EnterpriseRuntimeContextService {
16456
16853
  if (this.loadPromise) {
16457
16854
  return this.loadPromise;
16458
16855
  }
16459
- this.loadPromise = this.load()
16856
+ const revision = this.contextRevision;
16857
+ let currentLoad;
16858
+ currentLoad = this.load(revision)
16460
16859
  .catch((error) => {
16461
16860
  if ((this.options?.errorPolicy ?? 'ignore') === 'fail') {
16462
16861
  throw error;
@@ -16464,50 +16863,144 @@ class EnterpriseRuntimeContextService {
16464
16863
  return null;
16465
16864
  })
16466
16865
  .finally(() => {
16467
- this.loadPromise = null;
16866
+ if (this.loadPromise === currentLoad)
16867
+ this.loadPromise = null;
16468
16868
  });
16469
- return this.loadPromise;
16869
+ this.loadPromise = currentLoad;
16870
+ return currentLoad;
16470
16871
  }
16471
16872
  refresh() {
16472
- this.loadPromise = null;
16473
- return this.load().catch((error) => {
16873
+ const revision = this.invalidateContext();
16874
+ let currentLoad;
16875
+ currentLoad = this.load(revision).catch((error) => {
16474
16876
  if ((this.options?.errorPolicy ?? 'ignore') === 'fail') {
16475
16877
  throw error;
16476
16878
  }
16477
16879
  return null;
16880
+ }).finally(() => {
16881
+ if (this.loadPromise === currentLoad)
16882
+ this.loadPromise = null;
16478
16883
  });
16884
+ this.loadPromise = currentLoad;
16885
+ return currentLoad;
16886
+ }
16887
+ invalidate() {
16888
+ this.invalidateContext();
16479
16889
  }
16890
+ /** Independent host tenant discovery; it never selects the runtime context. */
16480
16891
  tenants() {
16481
16892
  return this.getRuntimeSurface('tenants');
16482
16893
  }
16894
+ choices(options = {}) {
16895
+ const pageSize = options.pageSize ?? 50;
16896
+ if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) {
16897
+ return Promise.reject(new RangeError('pageSize must be an integer between 1 and 100.'));
16898
+ }
16899
+ let params = new HttpParams().set('pageSize', pageSize);
16900
+ if (options.cursor !== undefined)
16901
+ params = params.set('cursor', options.cursor);
16902
+ return firstValueFrom(this.http.get(this.endpoint('choices'), {
16903
+ headers: this.requestHeaders({ includeSnapshot: true }), params,
16904
+ }));
16905
+ }
16483
16906
  navigation() {
16484
16907
  return this.getRuntimeSurface('navigation');
16485
16908
  }
16486
16909
  securityEvents() {
16487
16910
  return this.getRuntimeSurface('securityEvents');
16488
16911
  }
16489
- async switchContext(command) {
16490
- const response = await firstValueFrom(this.http.put(this.endpoint('context'), command, {
16491
- headers: this.requestHeaders({ includeSnapshot: true }),
16492
- }));
16493
- if (response?.effectiveContext) {
16494
- this.contextSubject.next(response.effectiveContext);
16495
- }
16496
- return response;
16912
+ switchContext(command) {
16913
+ const revision = this.invalidateContext();
16914
+ const operation = firstValueFrom(this.http.put(this.endpoint('context'), command, { headers: this.requestHeaders() })).then((response) => {
16915
+ if (revision !== this.contextRevision)
16916
+ throw new EnterpriseRuntimeContextObsoleteError();
16917
+ const context = this.normalizeContext(response);
16918
+ this.contextSubject.next(context);
16919
+ return context;
16920
+ });
16921
+ let currentSwitch;
16922
+ currentSwitch = operation.finally(() => {
16923
+ if (this.loadPromise === currentSwitch)
16924
+ this.loadPromise = null;
16925
+ });
16926
+ this.loadPromise = currentSwitch;
16927
+ return currentSwitch;
16497
16928
  }
16498
16929
  headers(fallback) {
16499
- return this.mergeHeaders(fallback ?? {}, this.headersFromSnapshot(this.contextSubject.value));
16930
+ return {
16931
+ ...this.withoutContextHeaders(this.normalizeHeaders(fallback ?? {}), true),
16932
+ ...this.normalizeHeaders(this.headersFromSnapshot(this.contextSubject.value)),
16933
+ };
16500
16934
  }
16935
+ /** Preserve host authentication headers while removing unconfirmed contextual claims. */
16501
16936
  effectiveRequestHeaders(fallback) {
16502
- return this.mergeHeaders(this.options?.includeGlobalFetchHeaders ?? true ? this.globalFetchHeaders() : {}, this.options?.headersFactory?.() ?? {}, fallback ?? {}, this.headersFromSnapshot(this.contextSubject.value));
16937
+ const bootstrap = this.withoutContextHeaders(this.normalizeHeaders({
16938
+ ...(this.options?.includeGlobalFetchHeaders ?? true ? this.globalFetchHeaders() : {}),
16939
+ ...(this.options?.headersFactory?.() ?? {}),
16940
+ ...(fallback ?? {}),
16941
+ }), true);
16942
+ const merged = { ...bootstrap, ...this.headersFromSnapshot(this.contextSubject.value) };
16943
+ const result = {};
16944
+ for (const [name, value] of Object.entries(merged)) {
16945
+ if (typeof value === 'string' && value.trim())
16946
+ result[name] = value;
16947
+ }
16948
+ return result;
16949
+ }
16950
+ /**
16951
+ * Applies the confirmed runtime snapshot to an existing request header set.
16952
+ * Contextual names are removed case-insensitively first, so nullable snapshot
16953
+ * fields cannot leave bootstrap or caller identity behind. Unrelated headers,
16954
+ * including repeated values, remain untouched.
16955
+ */
16956
+ applyHeaders(headers) {
16957
+ if (!this.effectiveContext) {
16958
+ return headers;
16959
+ }
16960
+ let next = headers;
16961
+ for (const name of headers.keys()) {
16962
+ if (this.isContextualHeader(name)) {
16963
+ next = next.delete(name);
16964
+ }
16965
+ }
16966
+ for (const [name, value] of Object.entries(this.headersFromSnapshot(this.contextSubject.value))) {
16967
+ if (typeof value === 'string') {
16968
+ next = next.set(name, value);
16969
+ }
16970
+ }
16971
+ return next;
16972
+ }
16973
+ invalidateContext() {
16974
+ this.contextRevision += 1;
16975
+ this.loadPromise = null;
16976
+ this.contextSubject.next(null);
16977
+ return this.contextRevision;
16503
16978
  }
16504
- async load() {
16505
- const context = await firstValueFrom(this.http.get(this.endpoint('context'), {
16979
+ async load(revision) {
16980
+ const response = await firstValueFrom(this.http.get(this.endpoint('context'), {
16506
16981
  headers: this.requestHeaders(),
16507
16982
  }));
16983
+ if (revision !== this.contextRevision)
16984
+ return null;
16985
+ const context = this.normalizeContext(response);
16508
16986
  this.contextSubject.next(context ?? null);
16509
16987
  return context ?? null;
16510
16988
  }
16989
+ normalizeContext(context) {
16990
+ if (context.state !== 'ready' || !context.effectiveContext) {
16991
+ return context;
16992
+ }
16993
+ const effective = context.effectiveContext;
16994
+ return {
16995
+ ...context,
16996
+ effectiveContext: {
16997
+ ...effective,
16998
+ activeOrganizationId: effective.activeOrganizationId ?? null,
16999
+ capabilities: Array.isArray(effective.capabilities) ? effective.capabilities : [],
17000
+ authorities: Array.isArray(effective.authorities) ? effective.authorities : [],
17001
+ },
17002
+ };
17003
+ }
16511
17004
  async getRuntimeSurface(surface) {
16512
17005
  return firstValueFrom(this.http.get(this.endpoint(surface), {
16513
17006
  headers: this.requestHeaders({ includeSnapshot: true }),
@@ -16542,6 +17035,8 @@ class EnterpriseRuntimeContextService {
16542
17035
  return 'context';
16543
17036
  case 'tenants':
16544
17037
  return 'tenants';
17038
+ case 'choices':
17039
+ return 'context/choices';
16545
17040
  case 'navigation':
16546
17041
  return 'navigation';
16547
17042
  case 'securityEvents':
@@ -16549,7 +17044,14 @@ class EnterpriseRuntimeContextService {
16549
17044
  }
16550
17045
  }
16551
17046
  requestHeaders(options = {}) {
16552
- const merged = this.mergeHeaders(this.options?.includeGlobalFetchHeaders ?? true ? this.globalFetchHeaders() : {}, this.options?.headersFactory?.() ?? {}, options.includeSnapshot ? this.headersFromSnapshot(this.contextSubject.value) : {});
17047
+ const bootstrap = this.withoutContextHeaders(this.normalizeHeaders({
17048
+ ...(this.options?.includeGlobalFetchHeaders ?? true ? this.globalFetchHeaders() : {}),
17049
+ ...(this.options?.headersFactory?.() ?? {}),
17050
+ }), options.includeSnapshot ?? false);
17051
+ const merged = {
17052
+ ...bootstrap,
17053
+ ...(options.includeSnapshot ? this.headersFromSnapshot(this.contextSubject.value) : {}),
17054
+ };
16553
17055
  let headers = new HttpHeaders();
16554
17056
  for (const [key, value] of Object.entries(merged)) {
16555
17057
  if (value) {
@@ -16568,22 +17070,31 @@ class EnterpriseRuntimeContextService {
16568
17070
  }
16569
17071
  }
16570
17072
  headersFromSnapshot(context) {
16571
- if (!context) {
17073
+ if (context?.state !== 'ready' || !context.effectiveContext) {
16572
17074
  return {};
16573
17075
  }
17076
+ const effective = context.effectiveContext;
16574
17077
  return {
16575
- 'X-Tenant-ID': context.activeTenant?.tenantId,
17078
+ 'X-Praxis-Context-Version': effective.contextVersion,
17079
+ 'X-Tenant-ID': effective.activeTenant?.tenantId,
16576
17080
  'X-User-ID': context.user?.userId,
16577
- 'X-Env': context.environment ?? undefined,
16578
- 'Accept-Language': context.locale ?? undefined,
16579
- 'X-Timezone': context.timezone ?? undefined,
16580
- 'X-Praxis-Profile-ID': context.activeProfileId ?? undefined,
16581
- 'X-Praxis-Module-Key': context.activeModuleKey ?? undefined,
17081
+ 'X-Env': effective.environment ?? undefined,
17082
+ 'Accept-Language': effective.locale ?? undefined,
17083
+ 'X-Timezone': effective.timezone ?? undefined,
17084
+ 'X-Praxis-Profile-ID': effective.activeProfileId ?? undefined,
17085
+ 'X-Praxis-Module-Key': effective.activeModuleKey ?? undefined,
16582
17086
  };
16583
17087
  }
17088
+ withoutContextHeaders(headers, includeExperience) {
17089
+ return Object.fromEntries(Object.entries(headers).filter(([key]) => !CONTEXT_HEADERS.has(key.toLowerCase())
17090
+ && (!includeExperience || !EXPERIENCE_HEADERS.has(key.toLowerCase()))));
17091
+ }
17092
+ isContextualHeader(name) {
17093
+ const normalized = name.toLowerCase();
17094
+ return CONTEXT_HEADERS.has(normalized) || EXPERIENCE_HEADERS.has(normalized);
17095
+ }
16584
17096
  normalizeHeaders(headers) {
16585
17097
  const normalized = {};
16586
- const canonicalKeyByLowercase = new Map();
16587
17098
  for (const [key, value] of Object.entries(headers)) {
16588
17099
  if (typeof value !== 'string') {
16589
17100
  continue;
@@ -16592,19 +17103,10 @@ class EnterpriseRuntimeContextService {
16592
17103
  if (!trimmed) {
16593
17104
  continue;
16594
17105
  }
16595
- const lowercase = key.toLowerCase();
16596
- const previousKey = canonicalKeyByLowercase.get(lowercase);
16597
- if (previousKey) {
16598
- delete normalized[previousKey];
16599
- }
16600
17106
  normalized[key] = trimmed;
16601
- canonicalKeyByLowercase.set(lowercase, key);
16602
17107
  }
16603
17108
  return normalized;
16604
17109
  }
16605
- mergeHeaders(...sources) {
16606
- return this.normalizeHeaders(Object.assign({}, ...sources.map((source) => this.normalizeHeaders(source))));
16607
- }
16608
17110
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: EnterpriseRuntimeContextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
16609
17111
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: EnterpriseRuntimeContextService, providedIn: 'root' });
16610
17112
  }
@@ -16613,6 +17115,537 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
16613
17115
  args: [{ providedIn: 'root' }]
16614
17116
  }] });
16615
17117
 
17118
+ /** B1b wire discriminator published by Config's effective-composition endpoint. */
17119
+ const EFFECTIVE_UI_LAYOUT_COMPOSITION_SCHEMA_VERSION = 'praxis.effective-ui-layout-composition/v1';
17120
+
17121
+ const PRAXIS_EFFECTIVE_UI_LAYOUT_OPTIONS = new InjectionToken('PRAXIS_EFFECTIVE_UI_LAYOUT_OPTIONS');
17122
+
17123
+ /** The effective-layout contract cannot be safely used for this request. */
17124
+ class EffectiveUiLayoutContractError extends Error {
17125
+ code;
17126
+ constructor(code) {
17127
+ super(`Effective UI layout contract error: ${code}.`);
17128
+ this.code = code;
17129
+ this.name = 'EffectiveUiLayoutContractError';
17130
+ }
17131
+ }
17132
+ /** Explicit Config read failure. Callers must render this state, never a local fallback. */
17133
+ class EffectiveUiLayoutHttpError extends Error {
17134
+ status;
17135
+ originalError;
17136
+ constructor(status, originalError) {
17137
+ super(`Effective UI layout request failed with HTTP ${status}.`);
17138
+ this.status = status;
17139
+ this.originalError = originalError;
17140
+ this.name = 'EffectiveUiLayoutHttpError';
17141
+ }
17142
+ }
17143
+ /**
17144
+ * Canonical conditional client for Config's read-only effective-layout surface.
17145
+ * It never calculates audience, precedence, permissions, hashes or local fallbacks.
17146
+ */
17147
+ class EffectiveUiLayoutService {
17148
+ http = inject(HttpClient);
17149
+ apiUrl = inject(API_URL, { optional: true });
17150
+ options = inject(PRAXIS_EFFECTIVE_UI_LAYOUT_OPTIONS, {
17151
+ optional: true,
17152
+ });
17153
+ runtime = inject(EnterpriseRuntimeContextService);
17154
+ cache = new Map();
17155
+ compositionCache = new Map();
17156
+ compositionInFlight = new Map();
17157
+ contextRevision = 0;
17158
+ constructor() {
17159
+ this.runtime.contextChanges$.subscribe(() => {
17160
+ this.contextRevision += 1;
17161
+ this.cache.clear();
17162
+ this.compositionCache.clear();
17163
+ this.compositionInFlight.clear();
17164
+ });
17165
+ }
17166
+ async resolve(target) {
17167
+ const normalizedTarget = normalizeTarget$1(target);
17168
+ const context = this.runtime.snapshot ?? await this.runtime.ready();
17169
+ const contextVersion = context?.state === 'ready'
17170
+ ? context.effectiveContext?.contextVersion
17171
+ : null;
17172
+ if (!contextVersion) {
17173
+ throw new EffectiveUiLayoutContractError('CONTEXT_NOT_READY');
17174
+ }
17175
+ const revision = this.contextRevision;
17176
+ const cacheKey = this.cacheKey(normalizedTarget, contextVersion);
17177
+ const cached = this.cache.get(cacheKey);
17178
+ let headers = this.requestHeaders();
17179
+ if (cached?.validator) {
17180
+ headers = headers.set('If-None-Match', cached.validator);
17181
+ }
17182
+ try {
17183
+ const httpResponse = await firstValueFrom(this.http.get(this.endpoint(), {
17184
+ headers,
17185
+ params: new HttpParams()
17186
+ .set('componentType', normalizedTarget.componentType)
17187
+ .set('componentId', normalizedTarget.componentId),
17188
+ observe: 'response',
17189
+ }));
17190
+ if (revision !== this.contextRevision) {
17191
+ throw new EffectiveUiLayoutContractError('CONTEXT_OBSOLETE');
17192
+ }
17193
+ const response = httpResponse.body;
17194
+ if (!response) {
17195
+ throw new EffectiveUiLayoutContractError('TARGET_MISMATCH');
17196
+ }
17197
+ this.assertCompatible(response, normalizedTarget, contextVersion);
17198
+ const validator = httpResponse.headers.get('ETag');
17199
+ if (!validator) {
17200
+ throw new EffectiveUiLayoutContractError('MISSING_VALIDATOR');
17201
+ }
17202
+ this.cache.set(cacheKey, {
17203
+ response: cloneLayoutResponse(response),
17204
+ validator,
17205
+ });
17206
+ return cloneLayoutResponse(response);
17207
+ }
17208
+ catch (error) {
17209
+ if (error instanceof EffectiveUiLayoutContractError) {
17210
+ throw error;
17211
+ }
17212
+ if (error instanceof HttpErrorResponse && error.status === 304) {
17213
+ if (revision !== this.contextRevision) {
17214
+ throw new EffectiveUiLayoutContractError('CONTEXT_OBSOLETE');
17215
+ }
17216
+ if (!cached) {
17217
+ throw new EffectiveUiLayoutContractError('CACHE_MISS_304');
17218
+ }
17219
+ this.assertCompatible(cached.response, normalizedTarget, contextVersion);
17220
+ return cloneLayoutResponse(cached.response);
17221
+ }
17222
+ if (error instanceof HttpErrorResponse) {
17223
+ throw new EffectiveUiLayoutHttpError(error.status, error);
17224
+ }
17225
+ throw error;
17226
+ }
17227
+ }
17228
+ /**
17229
+ * Reads one coherent server-resolved composition. This intentionally does
17230
+ * not fan out into unit effective-layout requests or infer membership.
17231
+ */
17232
+ async resolveComposition(rootTarget) {
17233
+ const normalizedRoot = normalizeTarget$1(rootTarget);
17234
+ const context = this.runtime.snapshot ?? await this.runtime.ready();
17235
+ const contextVersion = context?.state === 'ready'
17236
+ ? context.effectiveContext?.contextVersion
17237
+ : null;
17238
+ if (!contextVersion) {
17239
+ throw new EffectiveUiLayoutContractError('CONTEXT_NOT_READY');
17240
+ }
17241
+ const revision = this.contextRevision;
17242
+ const cacheKey = this.compositionCacheKey(normalizedRoot, contextVersion);
17243
+ const inFlight = this.compositionInFlight.get(cacheKey);
17244
+ if (inFlight) {
17245
+ return cloneCompositionResponse(await inFlight);
17246
+ }
17247
+ const request = this.requestComposition(normalizedRoot, contextVersion, revision, cacheKey);
17248
+ this.compositionInFlight.set(cacheKey, request);
17249
+ try {
17250
+ return cloneCompositionResponse(await request);
17251
+ }
17252
+ finally {
17253
+ if (this.compositionInFlight.get(cacheKey) === request) {
17254
+ this.compositionInFlight.delete(cacheKey);
17255
+ }
17256
+ }
17257
+ }
17258
+ endpoint() {
17259
+ const configured = this.options?.endpoint?.trim().replace(/\/+$/, '');
17260
+ if (configured)
17261
+ return configured;
17262
+ const entry = this.apiUrl?.['default'];
17263
+ const base = entry ? buildApiUrl(entry) : '';
17264
+ return base
17265
+ ? `${base}/praxis/config/ui-layouts/effective`
17266
+ : '/api/praxis/config/ui-layouts/effective';
17267
+ }
17268
+ compositionEndpoint() {
17269
+ const configured = this.options?.compositionEndpoint?.trim().replace(/\/+$/, '');
17270
+ if (configured)
17271
+ return configured;
17272
+ const entry = this.apiUrl?.['default'];
17273
+ const base = entry ? buildApiUrl(entry) : '';
17274
+ return base
17275
+ ? `${base}/praxis/config/ui-layouts/effective-composition`
17276
+ : '/api/praxis/config/ui-layouts/effective-composition';
17277
+ }
17278
+ requestHeaders() {
17279
+ const entryHeaders = this.apiUrl?.['default']
17280
+ ? buildHeaders(this.apiUrl['default'])
17281
+ : undefined;
17282
+ return this.runtime.applyHeaders(entryHeaders ?? new HttpHeaders());
17283
+ }
17284
+ cacheKey(target, contextVersion) {
17285
+ return `${contextVersion}\u0000${target.componentType}\u0000${target.componentId}`;
17286
+ }
17287
+ compositionCacheKey(rootTarget, contextVersion) {
17288
+ return `composition\u0000${contextVersion}\u0000${rootTarget.componentType}\u0000${rootTarget.componentId}`;
17289
+ }
17290
+ async requestComposition(rootTarget, contextVersion, revision, cacheKey) {
17291
+ const cached = this.compositionCache.get(cacheKey);
17292
+ let headers = this.requestHeaders();
17293
+ if (cached?.validator) {
17294
+ headers = headers.set('If-None-Match', cached.validator);
17295
+ }
17296
+ try {
17297
+ const httpResponse = await firstValueFrom(this.http.get(this.compositionEndpoint(), {
17298
+ headers,
17299
+ params: new HttpParams()
17300
+ .set('rootComponentType', rootTarget.componentType)
17301
+ .set('rootComponentId', rootTarget.componentId),
17302
+ observe: 'response',
17303
+ }));
17304
+ if (revision !== this.contextRevision) {
17305
+ throw new EffectiveUiLayoutContractError('CONTEXT_OBSOLETE');
17306
+ }
17307
+ const response = httpResponse.body;
17308
+ if (!response) {
17309
+ throw new EffectiveUiLayoutContractError('COMPOSITION_ROOT_MISMATCH');
17310
+ }
17311
+ this.assertCompositionCompatible(response, rootTarget, contextVersion);
17312
+ const validator = httpResponse.headers.get('ETag');
17313
+ if (!validator) {
17314
+ throw new EffectiveUiLayoutContractError('MISSING_VALIDATOR');
17315
+ }
17316
+ this.compositionCache.set(cacheKey, {
17317
+ response: cloneCompositionResponse(response),
17318
+ validator,
17319
+ });
17320
+ return response;
17321
+ }
17322
+ catch (error) {
17323
+ if (error instanceof EffectiveUiLayoutContractError) {
17324
+ throw error;
17325
+ }
17326
+ if (error instanceof HttpErrorResponse && error.status === 304) {
17327
+ if (revision !== this.contextRevision) {
17328
+ throw new EffectiveUiLayoutContractError('CONTEXT_OBSOLETE');
17329
+ }
17330
+ if (!cached) {
17331
+ throw new EffectiveUiLayoutContractError('CACHE_MISS_304');
17332
+ }
17333
+ this.assertCompositionCompatible(cached.response, rootTarget, contextVersion);
17334
+ return cloneCompositionResponse(cached.response);
17335
+ }
17336
+ if (error instanceof HttpErrorResponse) {
17337
+ throw new EffectiveUiLayoutHttpError(error.status, error);
17338
+ }
17339
+ throw error;
17340
+ }
17341
+ }
17342
+ assertCompatible(response, target, contextVersion) {
17343
+ if (response.target?.componentType !== target.componentType
17344
+ || response.target?.componentId !== target.componentId) {
17345
+ throw new EffectiveUiLayoutContractError('TARGET_MISMATCH');
17346
+ }
17347
+ if (response.contextVersion !== contextVersion) {
17348
+ throw new EffectiveUiLayoutContractError('CONTEXT_MISMATCH');
17349
+ }
17350
+ }
17351
+ assertCompositionCompatible(response, rootTarget, contextVersion) {
17352
+ if (!response
17353
+ || response.schemaVersion !== EFFECTIVE_UI_LAYOUT_COMPOSITION_SCHEMA_VERSION) {
17354
+ throw new EffectiveUiLayoutContractError('COMPOSITION_SCHEMA_MISMATCH');
17355
+ }
17356
+ if (!sameTarget(response.rootTarget, rootTarget)) {
17357
+ throw new EffectiveUiLayoutContractError('COMPOSITION_ROOT_MISMATCH');
17358
+ }
17359
+ if (response.contextVersion !== contextVersion) {
17360
+ throw new EffectiveUiLayoutContractError('CONTEXT_MISMATCH');
17361
+ }
17362
+ if (!Array.isArray(response.members)) {
17363
+ throw new EffectiveUiLayoutContractError('COMPOSITION_MEMBER_INVALID');
17364
+ }
17365
+ const memberTargets = new Set();
17366
+ for (const [index, member] of response.members.entries()) {
17367
+ const target = member?.target;
17368
+ const memberOrder = member?.memberOrder;
17369
+ if (!member
17370
+ || !target
17371
+ || !target.componentType?.trim()
17372
+ || !target.componentId?.trim()
17373
+ || memberOrder !== index
17374
+ || !Array.isArray(member.appliedContributions)
17375
+ || !Object.hasOwn(member, 'effectiveLayout')
17376
+ || (member.effectiveLayout !== null && typeof member.effectiveLayout !== 'object')
17377
+ || (index === 0 && !sameTarget(target, rootTarget))) {
17378
+ throw new EffectiveUiLayoutContractError('COMPOSITION_MEMBER_INVALID');
17379
+ }
17380
+ const targetKey = `${target.componentType}\u0000${target.componentId}`;
17381
+ if (memberTargets.has(targetKey)) {
17382
+ throw new EffectiveUiLayoutContractError('COMPOSITION_MEMBER_INVALID');
17383
+ }
17384
+ memberTargets.add(targetKey);
17385
+ if (member.effectiveLayout !== null) {
17386
+ this.assertCompatible(member.effectiveLayout, target, contextVersion);
17387
+ }
17388
+ }
17389
+ if (response.members.length === 0) {
17390
+ throw new EffectiveUiLayoutContractError('COMPOSITION_MEMBER_INVALID');
17391
+ }
17392
+ }
17393
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: EffectiveUiLayoutService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
17394
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: EffectiveUiLayoutService, providedIn: 'root' });
17395
+ }
17396
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: EffectiveUiLayoutService, decorators: [{
17397
+ type: Injectable,
17398
+ args: [{ providedIn: 'root' }]
17399
+ }], ctorParameters: () => [] });
17400
+ function cloneLayoutResponse(response) {
17401
+ return structuredClone(response);
17402
+ }
17403
+ function cloneCompositionResponse(response) {
17404
+ return structuredClone(response);
17405
+ }
17406
+ function sameTarget(left, right) {
17407
+ return left?.componentType === right.componentType && left?.componentId === right.componentId;
17408
+ }
17409
+ function normalizeTarget$1(target) {
17410
+ const componentType = target?.componentType?.trim();
17411
+ const componentId = target?.componentId?.trim();
17412
+ if (!componentType || !componentId || componentType.length > 64 || componentId.length > 255) {
17413
+ throw new EffectiveUiLayoutContractError('INVALID_TARGET');
17414
+ }
17415
+ return { componentType, componentId };
17416
+ }
17417
+
17418
+ class UiLayoutLifecycleContractError extends Error {
17419
+ code;
17420
+ constructor(code) {
17421
+ super(`UI layout lifecycle contract error: ${code}.`);
17422
+ this.code = code;
17423
+ this.name = 'UiLayoutLifecycleContractError';
17424
+ }
17425
+ }
17426
+ /** Sanitized transport failure: original problem details are intentionally not re-exposed. */
17427
+ class UiLayoutLifecycleHttpError extends Error {
17428
+ status;
17429
+ category;
17430
+ constructor(status, category) {
17431
+ super(`UI layout lifecycle request failed: ${category} (${status}).`);
17432
+ this.status = status;
17433
+ this.category = category;
17434
+ this.name = 'UiLayoutLifecycleHttpError';
17435
+ }
17436
+ }
17437
+ /**
17438
+ * Typed B1c transport client. It never derives admission, actor, tenant,
17439
+ * authority, lifecycle state, selector semantics or retry policy.
17440
+ */
17441
+ class UiLayoutLifecycleService {
17442
+ http = inject(HttpClient);
17443
+ apiUrl = inject(API_URL, { optional: true });
17444
+ runtime = inject(EnterpriseRuntimeContextService);
17445
+ contextRevision = 0;
17446
+ constructor() {
17447
+ this.runtime.contextChanges$.subscribe(() => this.contextRevision += 1);
17448
+ }
17449
+ /** Reads a creation association only. A missing association never authorizes automatic recreation. */
17450
+ async readDraftByCreationKey(target, creationKey) {
17451
+ const key = requireIdempotencyKey(creationKey);
17452
+ const admitted = await this.admit(target);
17453
+ const headers = admitted.headers.delete('If-Match').delete('If-None-Match').delete('Idempotency-Key').set('Idempotency-Key', key);
17454
+ this.assertDispatchContext(admitted.revision);
17455
+ try {
17456
+ const response = await firstValueFrom(this.http.get(`${this.root()}/drafts/by-creation-key`, {
17457
+ headers, params: admitted.params, observe: 'response',
17458
+ }));
17459
+ this.assertCurrent(admitted.revision, false);
17460
+ return this.receipt(response.body, response.headers);
17461
+ }
17462
+ catch (error) {
17463
+ throw this.normalizeError(error, admitted.revision, false);
17464
+ }
17465
+ }
17466
+ readDraft(target, draftId, ifNoneMatch) {
17467
+ return this.read(`${this.root()}/drafts/${encodeURIComponent(draftId)}`, target, ifNoneMatch);
17468
+ }
17469
+ readReview(target, releaseId, ifNoneMatch) {
17470
+ return this.read(`${this.root()}/releases/${encodeURIComponent(releaseId)}/review`, target, ifNoneMatch);
17471
+ }
17472
+ readHead(target, ifNoneMatch) {
17473
+ return this.read(`${this.root()}/release-head`, target, ifNoneMatch);
17474
+ }
17475
+ createDraft(target, idempotencyKey) {
17476
+ return this.write('POST', `${this.root()}/drafts`, target, null, undefined, idempotencyKey);
17477
+ }
17478
+ createRevision(target, draftId, draftEtag, command) {
17479
+ // Snapshot before asynchronous context admission; mutations cannot change the submitted intent.
17480
+ const snapshot = cloneUiLayoutJsonDocument(command);
17481
+ return this.write('POST', `${this.root()}/drafts/${encodeURIComponent(draftId)}/revisions`, target, snapshot, this.strongMatch(draftEtag));
17482
+ }
17483
+ createAssignment(target, draftId, draftEtag, command) {
17484
+ return this.write('POST', `${this.root()}/drafts/${encodeURIComponent(draftId)}/assignments`, target, command, this.strongMatch(draftEtag));
17485
+ }
17486
+ freezeRelease(target, draftId, draftEtag, command) {
17487
+ return this.write('POST', `${this.root()}/drafts/${encodeURIComponent(draftId)}/releases`, target, command, this.strongMatch(draftEtag));
17488
+ }
17489
+ submit(target, releaseId, reviewEtag) {
17490
+ return this.write('POST', `${this.root()}/releases/${encodeURIComponent(releaseId)}/submit`, target, null, this.strongMatch(reviewEtag));
17491
+ }
17492
+ approve(target, releaseId, reviewEtag, command) {
17493
+ return this.write('POST', `${this.root()}/releases/${encodeURIComponent(releaseId)}/approve`, target, command, this.strongMatch(reviewEtag));
17494
+ }
17495
+ publish(target, condition, command) {
17496
+ return this.write('POST', `${this.root()}/release-head/publish`, target, command, condition);
17497
+ }
17498
+ withdraw(target, headEtag, command) {
17499
+ return this.write('POST', `${this.root()}/release-head/withdraw`, target, command, this.strongMatch(headEtag));
17500
+ }
17501
+ rollback(target, headEtag, command) {
17502
+ return this.write('POST', `${this.root()}/release-head/rollback`, target, command, this.strongMatch(headEtag));
17503
+ }
17504
+ async read(url, target, ifNoneMatch) {
17505
+ const admitted = await this.admit(target);
17506
+ let headers = admitted.headers.delete('If-Match').delete('If-None-Match').delete('Idempotency-Key');
17507
+ if (ifNoneMatch)
17508
+ headers = headers.set('If-None-Match', ifNoneMatch);
17509
+ this.assertDispatchContext(admitted.revision);
17510
+ try {
17511
+ const response = await firstValueFrom(this.http.get(url, { headers, params: admitted.params, observe: 'response' }));
17512
+ this.assertCurrent(admitted.revision, false);
17513
+ return this.receipt(response.body, response.headers ?? new HttpHeaders());
17514
+ }
17515
+ catch (error) {
17516
+ if (error instanceof HttpErrorResponse && error.status === 304) {
17517
+ this.assertCurrent(admitted.revision, false);
17518
+ const etag = error.headers.get('ETag');
17519
+ if (!etag)
17520
+ throw new UiLayoutLifecycleContractError('NOT_MODIFIED_WITHOUT_ETAG');
17521
+ return { status: 'not-modified', etag };
17522
+ }
17523
+ throw this.normalizeError(error, admitted.revision, false);
17524
+ }
17525
+ }
17526
+ async write(method, url, target, body, condition, idempotencyKey) {
17527
+ const admitted = await this.admit(target);
17528
+ const headers = this.writeHeaders(admitted.headers, condition, idempotencyKey);
17529
+ this.assertDispatchContext(admitted.revision);
17530
+ let responseReceived = false;
17531
+ try {
17532
+ const response = await firstValueFrom(this.http.request(method, url, { body, headers, params: admitted.params, observe: 'response' }));
17533
+ responseReceived = true;
17534
+ this.assertCurrent(admitted.revision, true);
17535
+ return this.receipt(response.body, response.headers ?? new HttpHeaders());
17536
+ }
17537
+ catch (error) {
17538
+ if (responseReceived) {
17539
+ throw new UiLayoutLifecycleContractError('COMMAND_OUTCOME_UNCERTAIN');
17540
+ }
17541
+ throw this.normalizeError(error, admitted.revision, true);
17542
+ }
17543
+ }
17544
+ async admit(target) {
17545
+ const normalized = normalizeTarget(target);
17546
+ const revision = this.contextRevision;
17547
+ const context = this.runtime.snapshot ?? await this.runtime.ready();
17548
+ if (revision !== this.contextRevision)
17549
+ throw new UiLayoutLifecycleContractError('CONTEXT_OBSOLETE');
17550
+ if (context?.state !== 'ready' || !context.effectiveContext?.contextVersion) {
17551
+ throw new UiLayoutLifecycleContractError('CONTEXT_NOT_READY');
17552
+ }
17553
+ const entryHeaders = this.apiUrl?.['default'] ? (buildHeaders(this.apiUrl['default']) ?? new HttpHeaders()) : new HttpHeaders();
17554
+ return {
17555
+ revision,
17556
+ headers: this.runtime.applyHeaders(entryHeaders),
17557
+ params: new HttpParams().set('rootComponentType', normalized.componentType).set('rootComponentId', normalized.componentId),
17558
+ };
17559
+ }
17560
+ writeHeaders(headers, condition, idempotencyKey) {
17561
+ let unconditional = headers.delete('If-Match').delete('If-None-Match').delete('Idempotency-Key');
17562
+ if (idempotencyKey !== undefined)
17563
+ unconditional = unconditional.set('Idempotency-Key', requireIdempotencyKey(idempotencyKey));
17564
+ if (!condition)
17565
+ return unconditional;
17566
+ if (condition.kind === 'first-publish')
17567
+ return unconditional.set('If-None-Match', '*');
17568
+ return unconditional.set('If-Match', requireStrongEtag(condition.etag));
17569
+ }
17570
+ strongMatch(etag) {
17571
+ return { kind: 'match', etag: requireStrongEtag(etag) };
17572
+ }
17573
+ receipt(body, headers) {
17574
+ const etag = headers.get('ETag');
17575
+ if (!etag || body === null || body === undefined)
17576
+ throw new UiLayoutLifecycleContractError('MISSING_ETAG');
17577
+ return { body, etag, location: headers.get('Location') };
17578
+ }
17579
+ assertCurrent(revision, write) {
17580
+ if (revision !== this.contextRevision) {
17581
+ throw new UiLayoutLifecycleContractError(write ? 'COMMAND_OUTCOME_UNCERTAIN' : 'CONTEXT_OBSOLETE');
17582
+ }
17583
+ }
17584
+ assertDispatchContext(revision) {
17585
+ if (revision !== this.contextRevision) {
17586
+ throw new UiLayoutLifecycleContractError('CONTEXT_OBSOLETE');
17587
+ }
17588
+ }
17589
+ normalizeError(error, revision, write) {
17590
+ if (error instanceof UiLayoutLifecycleContractError)
17591
+ return error;
17592
+ if (revision !== this.contextRevision)
17593
+ return new UiLayoutLifecycleContractError(write ? 'COMMAND_OUTCOME_UNCERTAIN' : 'CONTEXT_OBSOLETE');
17594
+ if (error instanceof HttpErrorResponse && write && [0, 502, 504].includes(error.status)) {
17595
+ return new UiLayoutLifecycleContractError('COMMAND_OUTCOME_UNCERTAIN');
17596
+ }
17597
+ if (error instanceof HttpErrorResponse)
17598
+ return new UiLayoutLifecycleHttpError(error.status, categoryFor(error.status));
17599
+ return new UiLayoutLifecycleHttpError(0, 'HTTP_FAILURE');
17600
+ }
17601
+ root() {
17602
+ const entry = this.apiUrl?.['default'];
17603
+ const base = entry ? buildApiUrl(entry) : '';
17604
+ return base ? `${base}/praxis/config/ui-layouts` : '/api/praxis/config/ui-layouts';
17605
+ }
17606
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: UiLayoutLifecycleService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
17607
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: UiLayoutLifecycleService, providedIn: 'root' });
17608
+ }
17609
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: UiLayoutLifecycleService, decorators: [{
17610
+ type: Injectable,
17611
+ args: [{ providedIn: 'root' }]
17612
+ }], ctorParameters: () => [] });
17613
+ function normalizeTarget(target) {
17614
+ const componentType = target?.componentType?.trim();
17615
+ const componentId = target?.componentId?.trim();
17616
+ if (!componentType || !componentId || componentType.length > 64 || componentId.length > 255) {
17617
+ throw new UiLayoutLifecycleContractError('INVALID_TARGET');
17618
+ }
17619
+ return { componentType, componentId };
17620
+ }
17621
+ function requireStrongEtag(etag) {
17622
+ const normalized = String(etag ?? '').trim();
17623
+ if (!/^"[^"\\]+"$/.test(normalized))
17624
+ throw new UiLayoutLifecycleContractError('INVALID_STRONG_ETAG');
17625
+ return normalized;
17626
+ }
17627
+ function requireIdempotencyKey(value) {
17628
+ const normalized = String(value ?? '').trim();
17629
+ if (!normalized || normalized.length > 180) {
17630
+ throw new UiLayoutLifecycleContractError('INVALID_IDEMPOTENCY_KEY');
17631
+ }
17632
+ return normalized;
17633
+ }
17634
+ function categoryFor(status) {
17635
+ switch (status) {
17636
+ case 400: return 'BAD_REQUEST';
17637
+ case 401: return 'UNAUTHENTICATED';
17638
+ case 403: return 'DENIED';
17639
+ case 404: return 'NOT_FOUND';
17640
+ case 409: return 'CONFLICT';
17641
+ case 412: return 'PRECONDITION';
17642
+ case 413: return 'PAYLOAD_TOO_LARGE';
17643
+ case 422: return 'UNPROCESSABLE';
17644
+ case 503: return 'UNAVAILABLE';
17645
+ default: return 'HTTP_FAILURE';
17646
+ }
17647
+ }
17648
+
16616
17649
  function clonePraxisRuntimeComponentObservation(observation) {
16617
17650
  assertPraxisRuntimeComponentObservationSerializable(observation);
16618
17651
  return JSON.parse(JSON.stringify(observation));
@@ -21651,6 +22684,24 @@ const FieldDataType = {
21651
22684
  * - Supports Material Design 3 specifications
21652
22685
  */
21653
22686
 
22687
+ /**
22688
+ * Normalizes user/schema declared aspect ratio strings (such as "16:9")
22689
+ * into valid CSS aspect-ratio values ("16 / 9").
22690
+ */
22691
+ function normalizeCardMediaAspectRatio(aspectRatio) {
22692
+ if (!aspectRatio || typeof aspectRatio !== 'string')
22693
+ return null;
22694
+ const trimmed = aspectRatio.trim();
22695
+ if (!trimmed)
22696
+ return null;
22697
+ if (trimmed === 'auto')
22698
+ return 'auto';
22699
+ const ratioMatch = trimmed.match(/^(\d+(?:\.\d+)?)\s*[:/]\s*(\d+(?:\.\d+)?)$/);
22700
+ if (ratioMatch) {
22701
+ return `${ratioMatch[1]} / ${ratioMatch[2]}`;
22702
+ }
22703
+ return trimmed;
22704
+ }
21654
22705
  function createEmptyRichContentDocument() {
21655
22706
  return {
21656
22707
  kind: 'praxis.rich-content',
@@ -22554,6 +23605,76 @@ function hasOnlyKeys(value, allowed) {
22554
23605
  return Object.keys(value).every((key) => allowedSet.has(key));
22555
23606
  }
22556
23607
 
23608
+ /** Does not mutate input, synthesize IDs, or attest a historical upgrade baseline. */
23609
+ function formIdentityIssues(config) {
23610
+ const issues = [];
23611
+ const record = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : {};
23612
+ const list = (value) => Array.isArray(value) ? value : [];
23613
+ const root = record(config);
23614
+ const add = (level, code, path, message) => issues.push({ level, code, path, message });
23615
+ const identity = (value, path, seen, optional) => {
23616
+ if (value === undefined && optional) {
23617
+ add('warning', 'unattested', path, 'Missing identity can be assigned during creation but does not attest historical identity.');
23618
+ }
23619
+ else if (typeof value !== 'string' || !value || value.trim() !== value) {
23620
+ add('error', 'invalid', path, 'Identity must be a non-empty string without surrounding whitespace.');
23621
+ }
23622
+ else if (seen.has(value)) {
23623
+ add('error', 'duplicate', path, 'Identity is duplicated within its container.');
23624
+ }
23625
+ else {
23626
+ seen.add(value);
23627
+ }
23628
+ };
23629
+ const fields = new Set();
23630
+ list(root['fieldMetadata']).forEach((field, index) => identity(record(field)['name'], `fieldMetadata[${index}].name`, fields, false));
23631
+ const hasCatalog = Array.isArray(root['fieldMetadata']);
23632
+ const reference = (value, path) => {
23633
+ if (typeof value !== 'string' || !value || value.trim() !== value) {
23634
+ add('error', 'invalid', path, 'Field reference must be a non-empty canonical field name.');
23635
+ }
23636
+ else if (!hasCatalog) {
23637
+ add('warning', 'catalog-missing', path, 'Field reference cannot be verified without the field catalog.');
23638
+ }
23639
+ else if (!fields.has(value)) {
23640
+ add('error', 'reference-missing', path, 'Field reference is absent from the declared field catalog.');
23641
+ }
23642
+ };
23643
+ const sections = new Set();
23644
+ list(root['sections']).forEach((section, si) => {
23645
+ const s = record(section), sp = `sections[${si}]`, rows = new Set();
23646
+ identity(s['id'], `${sp}.id`, sections, true);
23647
+ list(s['rows']).forEach((row, ri) => {
23648
+ const r = record(row), rp = `${sp}.rows[${ri}]`, columns = new Set();
23649
+ identity(r['id'], `${rp}.id`, rows, true);
23650
+ list(r['columns']).forEach((column, ci) => {
23651
+ const c = record(column), cp = `${rp}.columns[${ci}]`, items = new Set();
23652
+ identity(c['id'], `${cp}.id`, columns, true);
23653
+ if (c['items'] !== undefined && !Array.isArray(c['items'])) {
23654
+ add('error', 'invalid', `${cp}.items`, 'Canonical layout items must be an array.');
23655
+ }
23656
+ else if (Array.isArray(c['items'])) {
23657
+ c['items'].forEach((item, ii) => {
23658
+ const i = record(item), ip = `${cp}.items[${ii}]`;
23659
+ identity(i['id'], `${ip}.id`, items, true);
23660
+ if (i['kind'] === 'field')
23661
+ reference(i['fieldName'], `${ip}.fieldName`);
23662
+ else if (i['kind'] !== 'richContent')
23663
+ add('error', 'invalid', `${ip}.kind`, 'Layout item kind must be field or richContent.');
23664
+ else if (!i['document'] || typeof i['document'] !== 'object' || Array.isArray(i['document'])) {
23665
+ add('error', 'invalid', `${ip}.document`, 'Rich content item requires a document object.');
23666
+ }
23667
+ });
23668
+ }
23669
+ else {
23670
+ list(c['fields']).forEach((field, fi) => reference(typeof field === 'string' ? field : record(field)['name'], `${cp}.fields[${fi}]`));
23671
+ }
23672
+ });
23673
+ });
23674
+ });
23675
+ return issues;
23676
+ }
23677
+
22557
23678
  /**
22558
23679
  * Materializes a concrete FormConfig from a reusable editorial template.
22559
23680
  *
@@ -27144,6 +28265,652 @@ function resolveBuiltinPresets(locale, dateAdapter, opts = {}) {
27144
28265
  return presets;
27145
28266
  }
27146
28267
 
28268
+ const DEFAULT_FUNCTIONS = {
28269
+ round: (v) => Math.round(Number(v)),
28270
+ ceil: (v) => Math.ceil(Number(v)),
28271
+ floor: (v) => Math.floor(Number(v)),
28272
+ abs: (v) => Math.abs(Number(v)),
28273
+ min: (...vals) => Math.min(...vals.map((v) => Number(v))),
28274
+ max: (...vals) => Math.max(...vals.map((v) => Number(v))),
28275
+ now: () => Date.now(),
28276
+ date: (v) => {
28277
+ if (v == null)
28278
+ return null;
28279
+ const d = v instanceof Date ? v : new Date(v);
28280
+ const ts = d.getTime();
28281
+ return isNaN(ts) ? null : ts;
28282
+ },
28283
+ yearsSince: (v) => {
28284
+ const ts = DEFAULT_FUNCTIONS.date(v);
28285
+ if (ts == null)
28286
+ return null;
28287
+ const diff = Date.now() - ts;
28288
+ if (!isFinite(diff))
28289
+ return null;
28290
+ return Math.floor(diff / 31557600000);
28291
+ },
28292
+ monthsSince: (v) => {
28293
+ const ts = DEFAULT_FUNCTIONS.date(v);
28294
+ if (ts == null)
28295
+ return null;
28296
+ const diff = Date.now() - ts;
28297
+ if (!isFinite(diff))
28298
+ return null;
28299
+ return Math.floor(diff / 2629800000);
28300
+ },
28301
+ daysSince: (v) => {
28302
+ const ts = DEFAULT_FUNCTIONS.date(v);
28303
+ if (ts == null)
28304
+ return null;
28305
+ const diff = Date.now() - ts;
28306
+ if (!isFinite(diff))
28307
+ return null;
28308
+ return Math.floor(diff / 86400000);
28309
+ },
28310
+ coalesce: (...vals) => {
28311
+ for (const v of vals) {
28312
+ if (v !== null && v !== undefined && v !== '')
28313
+ return v;
28314
+ }
28315
+ return null;
28316
+ },
28317
+ len: (v) => {
28318
+ if (v == null)
28319
+ return 0;
28320
+ if (typeof v === 'string' || Array.isArray(v))
28321
+ return v.length;
28322
+ return 0;
28323
+ },
28324
+ toNumber: (v) => {
28325
+ const n = Number(v);
28326
+ return Number.isFinite(n) ? n : null;
28327
+ },
28328
+ toString: (v) => (v == null ? '' : String(v)),
28329
+ if: (condition, truthyVal, falsyVal) => (Boolean(condition) ? truthyVal : falsyVal),
28330
+ };
28331
+ const DEFAULT_FUNCTION_ARITY = {
28332
+ round: { minArgs: 1, maxArgs: 1 },
28333
+ ceil: { minArgs: 1, maxArgs: 1 },
28334
+ floor: { minArgs: 1, maxArgs: 1 },
28335
+ abs: { minArgs: 1, maxArgs: 1 },
28336
+ min: { minArgs: 1, maxArgs: Number.MAX_SAFE_INTEGER },
28337
+ max: { minArgs: 1, maxArgs: Number.MAX_SAFE_INTEGER },
28338
+ now: { minArgs: 0, maxArgs: 0 },
28339
+ date: { minArgs: 1, maxArgs: 1 },
28340
+ yearsSince: { minArgs: 1, maxArgs: 1 },
28341
+ monthsSince: { minArgs: 1, maxArgs: 1 },
28342
+ daysSince: { minArgs: 1, maxArgs: 1 },
28343
+ coalesce: { minArgs: 1, maxArgs: Number.MAX_SAFE_INTEGER },
28344
+ len: { minArgs: 1, maxArgs: 1 },
28345
+ toNumber: { minArgs: 1, maxArgs: 1 },
28346
+ toString: { minArgs: 1, maxArgs: 1 },
28347
+ if: { minArgs: 3, maxArgs: 3 },
28348
+ };
28349
+ const BLOCKED_KEYS$1 = new Set(['__proto__', 'prototype', 'constructor']);
28350
+ class Tokenizer {
28351
+ input;
28352
+ i = 0;
28353
+ len;
28354
+ constructor(input) {
28355
+ this.input = input;
28356
+ this.len = input.length;
28357
+ }
28358
+ tokenize() {
28359
+ const tokens = [];
28360
+ while (this.i < this.len) {
28361
+ const ch = this.input[this.i];
28362
+ if (this.isWhitespace(ch)) {
28363
+ this.i += 1;
28364
+ continue;
28365
+ }
28366
+ const pos = this.i;
28367
+ if (this.isDigit(ch) || (ch === '.' && this.isDigit(this.peek()))) {
28368
+ tokens.push(this.readNumber(pos));
28369
+ continue;
28370
+ }
28371
+ if (ch === '"' || ch === "'") {
28372
+ tokens.push(this.readString(pos, ch));
28373
+ continue;
28374
+ }
28375
+ if (this.isIdentifierStart(ch)) {
28376
+ tokens.push(this.readIdentifier(pos));
28377
+ continue;
28378
+ }
28379
+ const op = this.readOperator();
28380
+ if (op) {
28381
+ tokens.push({ type: 'operator', value: op, pos });
28382
+ continue;
28383
+ }
28384
+ if (ch === '(' || ch === ')') {
28385
+ tokens.push({ type: 'paren', value: ch, pos });
28386
+ this.i += 1;
28387
+ continue;
28388
+ }
28389
+ if (ch === ',') {
28390
+ tokens.push({ type: 'comma', value: ch, pos });
28391
+ this.i += 1;
28392
+ continue;
28393
+ }
28394
+ if (ch === '.') {
28395
+ tokens.push({ type: 'dot', value: ch, pos });
28396
+ this.i += 1;
28397
+ continue;
28398
+ }
28399
+ throw new Error(`Unexpected character "${ch}"`);
28400
+ }
28401
+ tokens.push({ type: 'eof', value: '', pos: this.i });
28402
+ return tokens;
28403
+ }
28404
+ readNumber(pos) {
28405
+ let hasDot = false;
28406
+ let value = '';
28407
+ while (this.i < this.len) {
28408
+ const ch = this.input[this.i];
28409
+ if (this.isDigit(ch)) {
28410
+ value += ch;
28411
+ this.i += 1;
28412
+ continue;
28413
+ }
28414
+ if (ch === '.' && !hasDot && this.isDigit(this.peek())) {
28415
+ hasDot = true;
28416
+ value += ch;
28417
+ this.i += 1;
28418
+ continue;
28419
+ }
28420
+ break;
28421
+ }
28422
+ return { type: 'number', value, pos };
28423
+ }
28424
+ readString(pos, quote) {
28425
+ let value = '';
28426
+ this.i += 1; // skip quote
28427
+ while (this.i < this.len) {
28428
+ const ch = this.input[this.i];
28429
+ if (ch === '\\') {
28430
+ const next = this.peek();
28431
+ if (next) {
28432
+ value += next;
28433
+ this.i += 2;
28434
+ continue;
28435
+ }
28436
+ }
28437
+ if (ch === quote) {
28438
+ this.i += 1;
28439
+ return { type: 'string', value, pos };
28440
+ }
28441
+ value += ch;
28442
+ this.i += 1;
28443
+ }
28444
+ throw new Error('Unterminated string');
28445
+ }
28446
+ readIdentifier(pos) {
28447
+ let value = '';
28448
+ while (this.i < this.len) {
28449
+ const ch = this.input[this.i];
28450
+ if (this.isIdentifierPart(ch)) {
28451
+ value += ch;
28452
+ this.i += 1;
28453
+ continue;
28454
+ }
28455
+ break;
28456
+ }
28457
+ return { type: 'identifier', value, pos };
28458
+ }
28459
+ readOperator() {
28460
+ const two = this.input.slice(this.i, this.i + 2);
28461
+ const three = this.input.slice(this.i, this.i + 3);
28462
+ const ops = ['===', '!==', '==', '!=', '>=', '<='];
28463
+ if (ops.includes(three)) {
28464
+ this.i += 3;
28465
+ return three;
28466
+ }
28467
+ if (ops.includes(two)) {
28468
+ this.i += 2;
28469
+ return two;
28470
+ }
28471
+ const ch = this.input[this.i];
28472
+ if ('+-*/><?:'.includes(ch)) {
28473
+ this.i += 1;
28474
+ return ch;
28475
+ }
28476
+ return null;
28477
+ }
28478
+ peek() {
28479
+ return this.input[this.i + 1] || '';
28480
+ }
28481
+ isWhitespace(ch) {
28482
+ return ch === ' ' || ch === '\n' || ch === '\r' || ch === '\t';
28483
+ }
28484
+ isDigit(ch) {
28485
+ return ch >= '0' && ch <= '9';
28486
+ }
28487
+ isIdentifierStart(ch) {
28488
+ return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch === '_';
28489
+ }
28490
+ isIdentifierPart(ch) {
28491
+ return this.isIdentifierStart(ch) || this.isDigit(ch);
28492
+ }
28493
+ }
28494
+ class Parser {
28495
+ tokens;
28496
+ opts;
28497
+ current = 0;
28498
+ opCount = 0;
28499
+ depth = 0;
28500
+ constructor(tokens, opts) {
28501
+ this.tokens = tokens;
28502
+ this.opts = opts;
28503
+ }
28504
+ parse() {
28505
+ const expr = this.parseTernary();
28506
+ this.consume('eof');
28507
+ if (this.opCount > this.opts.maxTokens) {
28508
+ throw new Error('Expression too complex');
28509
+ }
28510
+ return expr;
28511
+ }
28512
+ parseTernary() {
28513
+ let expr = this.parseComparison();
28514
+ if (this.matchOperator('?')) {
28515
+ this.enterDepth();
28516
+ const whenTrue = this.parseTernary();
28517
+ this.consumeOperator(':');
28518
+ const whenFalse = this.parseTernary();
28519
+ this.exitDepth();
28520
+ this.opCount += 2;
28521
+ return { kind: 'call', name: 'if', args: [expr, whenTrue, whenFalse] };
28522
+ }
28523
+ return expr;
28524
+ }
28525
+ parseComparison() {
28526
+ let expr = this.parseAdditive();
28527
+ while (this.matchOperator('==', '!=', '===', '!==', '>=', '<=', '>', '<')) {
28528
+ if (!this.opts.allowComparisons) {
28529
+ throw new Error('Comparisons not allowed');
28530
+ }
28531
+ const op = this.previous().value;
28532
+ const right = this.parseAdditive();
28533
+ expr = this.makeBinary(op, expr, right);
28534
+ }
28535
+ return expr;
28536
+ }
28537
+ parseAdditive() {
28538
+ let expr = this.parseMultiplicative();
28539
+ while (this.matchOperator('+', '-')) {
28540
+ const op = this.previous().value;
28541
+ const right = this.parseMultiplicative();
28542
+ expr = this.makeBinary(op, expr, right);
28543
+ }
28544
+ return expr;
28545
+ }
28546
+ parseMultiplicative() {
28547
+ let expr = this.parseUnary();
28548
+ while (this.matchOperator('*', '/')) {
28549
+ const op = this.previous().value;
28550
+ const right = this.parseUnary();
28551
+ expr = this.makeBinary(op, expr, right);
28552
+ }
28553
+ return expr;
28554
+ }
28555
+ parseUnary() {
28556
+ if (this.matchOperator('+', '-')) {
28557
+ const op = this.previous().value;
28558
+ const expr = this.parseUnary();
28559
+ this.opCount += 1;
28560
+ return { kind: 'unary', op, expr };
28561
+ }
28562
+ return this.parsePrimary();
28563
+ }
28564
+ parsePrimary() {
28565
+ if (this.match('number')) {
28566
+ return { kind: 'literal', value: Number(this.previous().value) };
28567
+ }
28568
+ if (this.match('string')) {
28569
+ return { kind: 'literal', value: this.previous().value };
28570
+ }
28571
+ if (this.match('identifier')) {
28572
+ const name = this.previous().value;
28573
+ if (name === 'true' || name === 'false') {
28574
+ return { kind: 'literal', value: name === 'true' };
28575
+ }
28576
+ if (this.matchValue('paren', '(')) {
28577
+ const args = this.parseArguments();
28578
+ return { kind: 'call', name, args };
28579
+ }
28580
+ const path = [name];
28581
+ while (this.matchValue('dot', '.')) {
28582
+ const seg = this.consume('identifier').value;
28583
+ path.push(seg);
28584
+ }
28585
+ if ((name === 'row' || name === 'rowData') && path.length === 1) {
28586
+ throw new Error('row reference must access a field');
28587
+ }
28588
+ return { kind: 'field', path };
28589
+ }
28590
+ if (this.matchValue('paren', '(')) {
28591
+ this.enterDepth();
28592
+ const expr = this.parseTernary();
28593
+ this.consumeValue('paren', ')');
28594
+ this.exitDepth();
28595
+ return expr;
28596
+ }
28597
+ throw new Error('Unexpected token');
28598
+ }
28599
+ parseArguments() {
28600
+ const args = [];
28601
+ if (this.checkValue('paren', ')')) {
28602
+ this.consumeValue('paren', ')');
28603
+ return args;
28604
+ }
28605
+ do {
28606
+ if (args.length >= this.opts.maxArgs) {
28607
+ throw new Error('Too many arguments');
28608
+ }
28609
+ args.push(this.parseTernary());
28610
+ } while (this.matchValue('comma', ','));
28611
+ this.consumeValue('paren', ')');
28612
+ return args;
28613
+ }
28614
+ consumeOperator(op) {
28615
+ if (this.check('operator') && this.peek().value === op) {
28616
+ return this.advance();
28617
+ }
28618
+ throw new Error(`Expected operator "${op}"`);
28619
+ }
28620
+ makeBinary(op, left, right) {
28621
+ this.opCount += 1;
28622
+ return { kind: 'binary', op, left, right };
28623
+ }
28624
+ enterDepth() {
28625
+ this.depth += 1;
28626
+ if (this.depth > this.opts.maxDepth) {
28627
+ throw new Error('Expression too deep');
28628
+ }
28629
+ }
28630
+ exitDepth() {
28631
+ this.depth = Math.max(0, this.depth - 1);
28632
+ }
28633
+ match(type) {
28634
+ if (this.check(type)) {
28635
+ this.current += 1;
28636
+ return true;
28637
+ }
28638
+ return false;
28639
+ }
28640
+ matchValue(type, value) {
28641
+ if (this.checkValue(type, value)) {
28642
+ this.current += 1;
28643
+ return true;
28644
+ }
28645
+ return false;
28646
+ }
28647
+ matchOperator(...ops) {
28648
+ if (this.check('operator') && ops.includes(this.peek().value)) {
28649
+ this.current += 1;
28650
+ return true;
28651
+ }
28652
+ return false;
28653
+ }
28654
+ consume(type) {
28655
+ if (this.check(type))
28656
+ return this.advance();
28657
+ throw new Error(`Expected ${type}`);
28658
+ }
28659
+ consumeValue(type, value) {
28660
+ if (this.checkValue(type, value))
28661
+ return this.advance();
28662
+ throw new Error(`Expected ${value}`);
28663
+ }
28664
+ check(type) {
28665
+ return this.peek().type === type;
28666
+ }
28667
+ checkValue(type, value) {
28668
+ const tk = this.peek();
28669
+ return tk.type === type && tk.value === value;
28670
+ }
28671
+ advance() {
28672
+ if (!this.isAtEnd())
28673
+ this.current += 1;
28674
+ return this.previous();
28675
+ }
28676
+ isAtEnd() {
28677
+ return this.peek().type === 'eof';
28678
+ }
28679
+ peek() {
28680
+ return this.tokens[this.current];
28681
+ }
28682
+ previous() {
28683
+ return this.tokens[this.current - 1];
28684
+ }
28685
+ }
28686
+ class SafeExpressionEvaluator {
28687
+ cache = new Map();
28688
+ opts;
28689
+ functions;
28690
+ functionArity;
28691
+ constructor(options = {}) {
28692
+ this.opts = {
28693
+ maxLength: options.maxLength ?? 200,
28694
+ maxTokens: options.maxTokens ?? 80,
28695
+ maxDepth: options.maxDepth ?? 12,
28696
+ maxArgs: options.maxArgs ?? 6,
28697
+ allowComparisons: options.allowComparisons ?? true,
28698
+ allowedFunctions: options.allowedFunctions ?? DEFAULT_FUNCTIONS,
28699
+ allowedFunctionArity: options.allowedFunctionArity ?? DEFAULT_FUNCTION_ARITY,
28700
+ };
28701
+ this.functions = this.opts.allowedFunctions;
28702
+ this.functionArity = this.resolveFunctionArity();
28703
+ }
28704
+ evaluate(expression, row) {
28705
+ const normalized = this.normalize(expression);
28706
+ if (!normalized)
28707
+ return { value: null, error: 'empty' };
28708
+ if (normalized.length > this.opts.maxLength)
28709
+ return { value: null, error: 'too_long' };
28710
+ const node = this.getOrParse(normalized);
28711
+ if (!node)
28712
+ return { value: null, error: 'parse' };
28713
+ try {
28714
+ return { value: this.evalNode(node, row) };
28715
+ }
28716
+ catch {
28717
+ return { value: null, error: 'eval' };
28718
+ }
28719
+ }
28720
+ getOrParse(expression) {
28721
+ if (this.cache.has(expression))
28722
+ return this.cache.get(expression) || null;
28723
+ try {
28724
+ const tokenizer = new Tokenizer(expression);
28725
+ const tokens = tokenizer.tokenize();
28726
+ if (tokens.length > this.opts.maxTokens) {
28727
+ throw new Error('Expression too complex');
28728
+ }
28729
+ const parser = new Parser(tokens, this.opts);
28730
+ const ast = parser.parse();
28731
+ this.cache.set(expression, ast);
28732
+ return ast;
28733
+ }
28734
+ catch {
28735
+ this.cache.set(expression, null);
28736
+ return null;
28737
+ }
28738
+ }
28739
+ normalize(expression) {
28740
+ if (typeof expression !== 'string')
28741
+ return null;
28742
+ let out = expression.trim();
28743
+ if (!out)
28744
+ return null;
28745
+ if (out.startsWith('='))
28746
+ out = out.replace(/^=+/, '').trim();
28747
+ return out || null;
28748
+ }
28749
+ evalNode(node, row) {
28750
+ switch (node.kind) {
28751
+ case 'literal':
28752
+ return node.value;
28753
+ case 'field':
28754
+ return this.resolvePath(row, node.path);
28755
+ case 'unary': {
28756
+ const val = this.evalNode(node.expr, row);
28757
+ const num = this.toNumber(val);
28758
+ return num == null ? null : (node.op === '-' ? -num : num);
28759
+ }
28760
+ case 'binary':
28761
+ return this.evalBinary(node, row);
28762
+ case 'call':
28763
+ return this.evalCall(node, row);
28764
+ default:
28765
+ return null;
28766
+ }
28767
+ }
28768
+ evalBinary(node, row) {
28769
+ const left = this.evalNode(node.left, row);
28770
+ const right = this.evalNode(node.right, row);
28771
+ switch (node.op) {
28772
+ case '+':
28773
+ const leftNum = this.toNumber(left);
28774
+ const rightNum = this.toNumber(right);
28775
+ if (leftNum != null && rightNum != null) {
28776
+ return leftNum + rightNum;
28777
+ }
28778
+ if (typeof left === 'string' || typeof right === 'string') {
28779
+ return String(left ?? '') + String(right ?? '');
28780
+ }
28781
+ return this.applyNumberOp(left, right, (a, b) => a + b);
28782
+ case '-':
28783
+ return this.applyNumberOp(left, right, (a, b) => a - b);
28784
+ case '*':
28785
+ return this.applyNumberOp(left, right, (a, b) => a * b);
28786
+ case '/':
28787
+ return this.applyNumberOp(left, right, (a, b) => (b === 0 ? null : a / b));
28788
+ case '>':
28789
+ case '>=':
28790
+ case '<':
28791
+ case '<=':
28792
+ case '==':
28793
+ case '!=':
28794
+ case '===':
28795
+ case '!==':
28796
+ return this.applyComparison(left, right, node.op);
28797
+ default:
28798
+ return null;
28799
+ }
28800
+ }
28801
+ evalCall(node, row) {
28802
+ const fn = this.functions[node.name];
28803
+ if (!fn) {
28804
+ throw new Error(`Function "${node.name}" is not allowlisted`);
28805
+ }
28806
+ const arity = this.functionArity[node.name];
28807
+ if (!arity) {
28808
+ throw new Error(`Function "${node.name}" has no arity contract`);
28809
+ }
28810
+ if (node.name === 'if' && fn === DEFAULT_FUNCTIONS['if']) {
28811
+ if (node.args.length !== 3) {
28812
+ throw new Error('Function "if" called with invalid arity');
28813
+ }
28814
+ const cond = this.evalNode(node.args[0], row);
28815
+ const branch = Boolean(cond) ? node.args[1] : node.args[2];
28816
+ return this.evalNode(branch, row);
28817
+ }
28818
+ const args = node.args.map((arg) => this.evalNode(arg, row));
28819
+ if (args.length < arity.minArgs || args.length > arity.maxArgs) {
28820
+ throw new Error(`Function "${node.name}" called with invalid arity`);
28821
+ }
28822
+ const value = fn(...args);
28823
+ if (typeof value === 'number' && !Number.isFinite(value)) {
28824
+ throw new Error(`Function "${node.name}" returned non-finite number`);
28825
+ }
28826
+ return value;
28827
+ }
28828
+ resolveFunctionArity() {
28829
+ const out = {};
28830
+ const userMap = this.opts.allowedFunctionArity || {};
28831
+ for (const [name, fn] of Object.entries(this.functions)) {
28832
+ const raw = userMap[name];
28833
+ const minFromFn = Number.isInteger(fn.length) && fn.length >= 0 ? fn.length : 0;
28834
+ const minRaw = Number(raw?.minArgs);
28835
+ const min = Number.isFinite(minRaw) && minRaw >= 0 ? Math.floor(minRaw) : minFromFn;
28836
+ const maxRaw = Number(raw?.maxArgs);
28837
+ let maxBase = this.opts.maxArgs;
28838
+ if (raw && Object.prototype.hasOwnProperty.call(raw, 'maxArgs')) {
28839
+ maxBase = Number.isFinite(maxRaw) && maxRaw >= min
28840
+ ? Math.floor(maxRaw)
28841
+ : min;
28842
+ }
28843
+ const max = Math.max(min, Math.min(maxBase, this.opts.maxArgs));
28844
+ out[name] = { minArgs: min, maxArgs: max };
28845
+ }
28846
+ return out;
28847
+ }
28848
+ applyNumberOp(left, right, op) {
28849
+ const a = this.toNumber(left);
28850
+ const b = this.toNumber(right);
28851
+ if (a == null || b == null)
28852
+ return null;
28853
+ const out = op(a, b);
28854
+ return typeof out === 'number' && isFinite(out) ? out : null;
28855
+ }
28856
+ applyComparison(left, right, op) {
28857
+ switch (op) {
28858
+ case '==':
28859
+ case '===':
28860
+ return left === right;
28861
+ case '!=':
28862
+ case '!==':
28863
+ return left !== right;
28864
+ case '>':
28865
+ return this.toComparable(left) > this.toComparable(right);
28866
+ case '>=':
28867
+ return this.toComparable(left) >= this.toComparable(right);
28868
+ case '<':
28869
+ return this.toComparable(left) < this.toComparable(right);
28870
+ case '<=':
28871
+ return this.toComparable(left) <= this.toComparable(right);
28872
+ default:
28873
+ return false;
28874
+ }
28875
+ }
28876
+ toComparable(value) {
28877
+ const num = this.toNumber(value);
28878
+ if (num != null)
28879
+ return num;
28880
+ return String(value ?? '');
28881
+ }
28882
+ toNumber(value) {
28883
+ if (value == null || value === '')
28884
+ return 0;
28885
+ const num = Number(value);
28886
+ return isFinite(num) ? num : null;
28887
+ }
28888
+ resolvePath(row, path) {
28889
+ if (!Array.isArray(path) || !path.length)
28890
+ return undefined;
28891
+ let effectivePath = path;
28892
+ if ((path[0] === 'row' || path[0] === 'rowData') &&
28893
+ row != null &&
28894
+ typeof row === 'object' &&
28895
+ !(path[0] in row)) {
28896
+ effectivePath = path.slice(1);
28897
+ }
28898
+ if (!effectivePath.length)
28899
+ return undefined;
28900
+ let current = row;
28901
+ for (let i = 0; i < effectivePath.length; i += 1) {
28902
+ const key = effectivePath[i];
28903
+ if (!key || BLOCKED_KEYS$1.has(key))
28904
+ return undefined;
28905
+ if (current == null || (typeof current !== 'object' && typeof current !== 'function')) {
28906
+ return undefined;
28907
+ }
28908
+ current = current[key];
28909
+ }
28910
+ return current;
28911
+ }
28912
+ }
28913
+
27147
28914
  /**
27148
28915
  * Binds a stored icon string to a mat-icon element supporting:
27149
28916
  * - Material Icons (classic ligatures): "mi:pending"
@@ -29930,7 +31697,7 @@ const ENUMS = {
29930
31697
  layoutOrientation: ['vertical', 'columns'],
29931
31698
  canvasMode: ['grid'],
29932
31699
  canvasAutoRows: ['fixed', 'content'],
29933
- canvasCollisionPolicy: ['block', 'swap'],
31700
+ canvasCollisionPolicy: ['block', 'swap', 'push-down'],
29934
31701
  groupingKind: ['section', 'tabs', 'hero', 'rail'],
29935
31702
  groupingLayout: ['stack', 'grid', 'row'],
29936
31703
  heroEmphasis: ['high', 'medium'],
@@ -33824,6 +35591,19 @@ class DynamicWidgetLoaderDirective {
33824
35591
  }
33825
35592
  return false;
33826
35593
  }
35594
+ /**
35595
+ * Resets any active visual selection on the hosted component instance if supported (e.g. PraxisChartComponent).
35596
+ */
35597
+ clearSelection() {
35598
+ const instance = this.compRef?.instance;
35599
+ if (!instance || this.destroyed)
35600
+ return false;
35601
+ if (typeof instance.clearSelection === 'function') {
35602
+ instance.clearSelection();
35603
+ return true;
35604
+ }
35605
+ return false;
35606
+ }
33827
35607
  ngOnInit() {
33828
35608
  this.tryRender();
33829
35609
  }
@@ -41822,6 +43602,188 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
41822
43602
  `, styles: [":host{display:block;min-width:0;color:var(--md-sys-color-on-surface)}.size-editor{display:grid;gap:var(--pdx-editor-gap, 20px)}.context{display:grid;gap:4px;overflow-wrap:anywhere}.context span,.hint,.preset-unit{color:var(--md-sys-color-on-surface-variant);font-size:12px}p,h3{margin:0}h3,.section-label{font-size:14px;font-weight:500}.dimensions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;align-items:start}mat-form-field{min-width:0}mat-hint{color:var(--md-sys-color-on-surface-variant)}.presets{display:flex;flex-wrap:wrap;gap:8px}.presets button{min-height:44px}.preset-unit{margin-inline-start:8px}.preset-section .presets{display:grid;grid-template-columns:repeat(4,minmax(0,1fr))}.preset-section .presets button{height:auto;min-height:60px;padding:8px}.preset-section .presets strong,.preset-section .preset-unit{display:block;margin:0}.preset-copy{display:grid;gap:4px;text-align:center;white-space:normal}.preset-section,.fill-section{display:grid;gap:10px}.direction-grid{display:grid;grid-template-columns:repeat(3,minmax(44px,72px));gap:6px;justify-content:center}.direction-cell{display:block}.direction-cell button{width:100%;min-width:44px;height:44px;padding:0;font-size:24px}.direction-cell button.selected{background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container);border-color:var(--md-sys-color-primary)}.direction-cell button:disabled{background:transparent;color:var(--md-sys-color-on-surface);opacity:.38}.direction-center{display:grid;place-items:center;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.selection-summary{text-align:center;font-size:14px;font-weight:500}.hint{line-height:1.5}summary{cursor:pointer;padding-block:8px;font-weight:500}.advanced-content{display:grid;gap:16px;padding-block:8px}mat-radio-group{display:grid;grid-template-columns:repeat(2,minmax(0,1fr))}.recovery{display:grid;gap:4px;justify-items:start}.error{color:var(--md-sys-color-error);font-size:12px}.layout-preview{display:grid;gap:8px}.layout-comparison{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.layout-map{position:relative;height:112px;margin-block-start:6px;background:var(--md-sys-color-surface-container)}.layout-block{position:absolute;box-sizing:border-box;border:2px solid var(--md-sys-color-surface-container);padding:4px;background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface-variant);font-size:11px;overflow:hidden}.layout-block.target{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container);font-weight:600}.layout-block.changed{outline:1px dashed var(--md-sys-color-primary);outline-offset:-3px}.changed-neighbors{margin:0;padding-inline-start:20px;font-size:12px}.direction-reasons{margin:0;padding-inline-start:20px;color:var(--md-sys-color-on-surface-variant);font-size:12px;line-height:1.5}.direction-reasons li+li{margin-block-start:8px}@media(max-width:480px){.dimensions{grid-template-columns:minmax(0,1fr)}.preset-section .presets{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:380px){mat-radio-group{grid-template-columns:minmax(0,1fr)}}\n"] }]
41823
43603
  }], propDecorators: { layoutItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "layoutItems", required: false }] }], resetRevision: [{ type: i0.Input, args: [{ isSignal: true, alias: "resetRevision", required: false }] }], restoredValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "restoredValue", required: false }] }], state: [{ type: i0.Output, args: ["state"] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], freeSpaceWidths: [{ type: i0.Input, args: [{ isSignal: true, alias: "freeSpaceWidths", required: false }] }], northFreeSpaceHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "northFreeSpaceHeight", required: false }] }], northUnavailable: [{ type: i0.Input, args: [{ isSignal: true, alias: "northUnavailable", required: false }] }], southFreeSpaceHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "southFreeSpaceHeight", required: false }] }], southUnavailable: [{ type: i0.Input, args: [{ isSignal: true, alias: "southUnavailable", required: false }] }], cornerFreeSpaceSizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "cornerFreeSpaceSizes", required: false }] }], cornerUnavailable: [{ type: i0.Input, args: [{ isSignal: true, alias: "cornerUnavailable", required: false }] }], expandWidths: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandWidths", required: false }] }], notice: [{ type: i0.Input, args: [{ isSignal: true, alias: "notice", required: false }] }], hasUndo: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasUndo", required: false }] }], canUndo: [{ type: i0.Input, args: [{ isSignal: true, alias: "canUndo", required: false }] }], undo: [{ type: i0.Output, args: ["undo"] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: true }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: true }] }], contentRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentRows", required: false }] }], rowSpan: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowSpan", required: false }] }], widgetLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "widgetLabel", required: true }] }], scopeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "scopeLabel", required: true }] }], previewPending: [{ type: i0.Input, args: [{ isSignal: true, alias: "previewPending", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], preview: [{ type: i0.Output, args: ["preview"] }], apply: [{ type: i0.Output, args: ["apply"] }], cancel: [{ type: i0.Output, args: ["cancel"] }] } });
41824
43604
 
43605
+ let eventCounter = 0;
43606
+ /**
43607
+ * Praxis UI Platform — Canonical Widget Event Bus (Issue #3)
43608
+ *
43609
+ * Barramento centralizado e desacoplado para comunicação declarativa
43610
+ * entre widgets heterogêneos de páginas ricas e dashboards.
43611
+ */
43612
+ class PraxisWidgetEventBus {
43613
+ eventSubject$ = new Subject();
43614
+ internalSubs = new Subscription();
43615
+ parentBus;
43616
+ scopeId;
43617
+ constructor() {
43618
+ this.scopeId = `scope-${Date.now()}-${++eventCounter}`;
43619
+ }
43620
+ /**
43621
+ * Publica um evento no barramento.
43622
+ */
43623
+ publish(event) {
43624
+ const enriched = {
43625
+ id: event.id || `evt-${Date.now()}-${++eventCounter}`,
43626
+ timestamp: event.timestamp || Date.now(),
43627
+ ...event,
43628
+ metadata: {
43629
+ scopeId: this.scopeId,
43630
+ ...(event.metadata || {}),
43631
+ },
43632
+ };
43633
+ this.eventSubject$.next(enriched);
43634
+ // Propaga para o escopo pai caso exista
43635
+ if (this.parentBus) {
43636
+ this.parentBus.publish(enriched);
43637
+ }
43638
+ }
43639
+ /**
43640
+ * Escuta eventos filtrados por tipo semântico e/ou widget destinatário.
43641
+ */
43642
+ on(eventType, targetWidgetId) {
43643
+ return this.eventSubject$.asObservable().pipe(filter((evt) => {
43644
+ if (eventType && evt.eventType !== eventType) {
43645
+ return false;
43646
+ }
43647
+ if (targetWidgetId) {
43648
+ return (!evt.targetWidgetId ||
43649
+ evt.targetWidgetId === '*' ||
43650
+ evt.targetWidgetId === targetWidgetId);
43651
+ }
43652
+ return true;
43653
+ }));
43654
+ }
43655
+ /**
43656
+ * Helper conveniente para escutar mutações de filtro ('filter.apply' e 'filter.clear').
43657
+ */
43658
+ onFilter(targetWidgetId) {
43659
+ return this.eventSubject$.asObservable().pipe(filter((evt) => {
43660
+ const isFilterEvent = evt.eventType === 'filter.apply' || evt.eventType === 'filter.clear';
43661
+ if (!isFilterEvent)
43662
+ return false;
43663
+ if (targetWidgetId) {
43664
+ return (!evt.targetWidgetId ||
43665
+ evt.targetWidgetId === '*' ||
43666
+ evt.targetWidgetId === targetWidgetId);
43667
+ }
43668
+ return true;
43669
+ }));
43670
+ }
43671
+ /**
43672
+ * Helper conveniente para escutar alterações de seleção de linha/entidade.
43673
+ */
43674
+ onSelection(targetWidgetId) {
43675
+ return this.on('selection.change', targetWidgetId);
43676
+ }
43677
+ /**
43678
+ * Helper conveniente para despachar aplicação de filtros para um widget alvo.
43679
+ */
43680
+ emitFilterApply(targetWidgetId, criteria, sourceWidgetId) {
43681
+ this.publish({
43682
+ eventType: 'filter.apply',
43683
+ targetWidgetId,
43684
+ sourceWidgetId,
43685
+ payload: criteria,
43686
+ });
43687
+ }
43688
+ /**
43689
+ * Helper conveniente para despachar limpeza de filtros para um widget alvo.
43690
+ */
43691
+ emitFilterClear(targetWidgetId, sourceWidgetId) {
43692
+ this.publish({
43693
+ eventType: 'filter.clear',
43694
+ targetWidgetId,
43695
+ sourceWidgetId,
43696
+ payload: {},
43697
+ });
43698
+ }
43699
+ /**
43700
+ * Cria um escopo isolado filho (ex.: para modais, gavetas analíticas ou abas keep-alive).
43701
+ * Impede vazamento acidental de eventos para outras seções da aplicação.
43702
+ */
43703
+ createChildScope(scopeName) {
43704
+ const child = new PraxisWidgetEventBus();
43705
+ child.parentBus = this;
43706
+ return child;
43707
+ }
43708
+ /**
43709
+ * Aplica um conjunto declarativo de regras de fiação (wiring) entre widgets.
43710
+ * Transforma automaticamente eventos da origem e publica ações ou filtros no destino.
43711
+ */
43712
+ applyWiring(rules) {
43713
+ const wiringSub = new Subscription();
43714
+ if (!rules || rules.length === 0) {
43715
+ return wiringSub;
43716
+ }
43717
+ for (const rule of rules) {
43718
+ wiringSub.add(this.eventSubject$
43719
+ .pipe(filter((evt) => {
43720
+ // Previne loops de reentrância em eventos já despachados por fiação
43721
+ if (evt.metadata?.['viaWiring']) {
43722
+ return false;
43723
+ }
43724
+ // Verifica widget de origem
43725
+ if (evt.sourceWidgetId !== rule.from.widgetId) {
43726
+ return false;
43727
+ }
43728
+ // Verifica correspondência de evento ou actionId
43729
+ if (rule.from.event && evt.eventType !== rule.from.event) {
43730
+ return false;
43731
+ }
43732
+ if (rule.from.actionId) {
43733
+ const payloadAction = evt.payload?.actionId ||
43734
+ evt.payload?.action ||
43735
+ evt.payload?.id;
43736
+ if (payloadAction !== rule.from.actionId) {
43737
+ return false;
43738
+ }
43739
+ }
43740
+ return true;
43741
+ }))
43742
+ .subscribe((sourceEvt) => {
43743
+ let transformedPayload;
43744
+ if (typeof rule.transform === 'function') {
43745
+ transformedPayload = rule.transform(sourceEvt.payload);
43746
+ }
43747
+ else if (rule.transform && typeof rule.transform === 'object') {
43748
+ transformedPayload = { ...rule.transform };
43749
+ }
43750
+ else {
43751
+ transformedPayload = sourceEvt.payload;
43752
+ }
43753
+ const targetEventType = rule.to.target === 'filterCriteria'
43754
+ ? 'filter.apply'
43755
+ : rule.to.target === 'selection'
43756
+ ? 'selection.change'
43757
+ : rule.to.target === 'refresh'
43758
+ ? 'refresh'
43759
+ : 'action.dispatch';
43760
+ this.publish({
43761
+ sourceWidgetId: rule.from.widgetId,
43762
+ targetWidgetId: rule.to.widgetId,
43763
+ eventType: targetEventType,
43764
+ payload: transformedPayload,
43765
+ metadata: {
43766
+ targetProperty: rule.to.target,
43767
+ viaWiring: true,
43768
+ },
43769
+ });
43770
+ }));
43771
+ }
43772
+ this.internalSubs.add(wiringSub);
43773
+ return wiringSub;
43774
+ }
43775
+ ngOnDestroy() {
43776
+ this.internalSubs.unsubscribe();
43777
+ this.eventSubject$.complete();
43778
+ }
43779
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisWidgetEventBus, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
43780
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisWidgetEventBus, providedIn: 'root' });
43781
+ }
43782
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisWidgetEventBus, decorators: [{
43783
+ type: Injectable,
43784
+ args: [{ providedIn: 'root' }]
43785
+ }], ctorParameters: () => [] });
43786
+
41825
43787
  class DynamicWidgetContextToolbarComponent {
41826
43788
  showSize = input(false, ...(ngDevMode ? [{ debugName: "showSize" }] : /* istanbul ignore next */ []));
41827
43789
  sizeDisabled = input(false, ...(ngDevMode ? [{ debugName: "sizeDisabled" }] : /* istanbul ignore next */ []));
@@ -42617,6 +44579,8 @@ const CANVAS_RESIZE_HANDLES = [
42617
44579
  class DynamicWidgetPageComponent {
42618
44580
  widgetSizeTemplate;
42619
44581
  pageCanvasHost;
44582
+ widgetEventBus = inject(PraxisWidgetEventBus, { optional: true });
44583
+ wiringSubscription;
42620
44584
  page;
42621
44585
  context = null;
42622
44586
  strictValidation = true;
@@ -42688,6 +44652,8 @@ class DynamicWidgetPageComponent {
42688
44652
  blockedCanvasWidgetKey = null;
42689
44653
  canvasPreviewState = signal(null, ...(ngDevMode ? [{ debugName: "canvasPreviewState" }] : /* istanbul ignore next */ []));
42690
44654
  canvasPreviewInvalidState = signal(false, ...(ngDevMode ? [{ debugName: "canvasPreviewInvalidState" }] : /* istanbul ignore next */ []));
44655
+ canvasPreviewInvalidMessageState = signal(null, ...(ngDevMode ? [{ debugName: "canvasPreviewInvalidMessageState" }] : /* istanbul ignore next */ []));
44656
+ canvasPreviewSwapTargetState = signal(null, ...(ngDevMode ? [{ debugName: "canvasPreviewSwapTargetState" }] : /* istanbul ignore next */ []));
42691
44657
  resizeFeedbackState = signal({}, ...(ngDevMode ? [{ debugName: "resizeFeedbackState" }] : /* istanbul ignore next */ []));
42692
44658
  resizeFeedbackOrigin = signal(null, ...(ngDevMode ? [{ debugName: "resizeFeedbackOrigin" }] : /* istanbul ignore next */ []));
42693
44659
  resizeFeedbackPositions = [
@@ -42831,6 +44797,7 @@ class DynamicWidgetPageComponent {
42831
44797
  this.runtimeObservationRegistration = null;
42832
44798
  this.hostResizeObserver?.disconnect();
42833
44799
  this.hostResizeObserver = undefined;
44800
+ this.wiringSubscription?.unsubscribe();
42834
44801
  this.compositionRuntime.destroy();
42835
44802
  }
42836
44803
  ngOnChanges(changes) {
@@ -42948,6 +44915,9 @@ class DynamicWidgetPageComponent {
42948
44915
  if (evt?.output && this.maybeExecuteMappedAction(fromKey, evt)) {
42949
44916
  return;
42950
44917
  }
44918
+ if (evt?.output === 'queryContextClear') {
44919
+ this.handleQueryContextClear(fromKey, evt);
44920
+ }
42951
44921
  const page = this.ensurePageDefinition();
42952
44922
  const widgetInputPatchResult = this.applyWidgetInputPatchToPage(page, fromKey, evt);
42953
44923
  const pageWithPatchedInputs = widgetInputPatchResult.page;
@@ -45131,6 +47101,64 @@ class DynamicWidgetPageComponent {
45131
47101
  findWidgetLoader(key) {
45132
47102
  return this.widgetLoaders?.find((loader) => loader.ownerWidgetKey === key);
45133
47103
  }
47104
+ handleQueryContextClear(fromKey, _evt) {
47105
+ const page = this.ensurePageDefinition();
47106
+ const links = page.composition?.links || [];
47107
+ if (!links.length) {
47108
+ return;
47109
+ }
47110
+ const statePathsToReset = new Set();
47111
+ const sourceWidgetKeys = new Set();
47112
+ for (const link of links) {
47113
+ if (link.to.kind === 'component-port' &&
47114
+ link.to.ref.widget === fromKey &&
47115
+ (link.to.ref.port === 'queryContext' || link.to.ref.port === 'filterCriteria')) {
47116
+ if (link.from.kind === 'component-port') {
47117
+ sourceWidgetKeys.add(link.from.ref.widget);
47118
+ }
47119
+ else if (link.from.kind === 'state') {
47120
+ statePathsToReset.add(link.from.ref.path);
47121
+ }
47122
+ }
47123
+ }
47124
+ if (statePathsToReset.size > 0) {
47125
+ for (const link of links) {
47126
+ if (link.to.kind === 'state' &&
47127
+ statePathsToReset.has(link.to.ref.path) &&
47128
+ link.from.kind === 'component-port') {
47129
+ sourceWidgetKeys.add(link.from.ref.widget);
47130
+ }
47131
+ }
47132
+ }
47133
+ for (const sourceKey of sourceWidgetKeys) {
47134
+ if (sourceKey === fromKey)
47135
+ continue;
47136
+ const loader = this.findWidgetLoader(sourceKey);
47137
+ if (loader && typeof loader.clearSelection === 'function') {
47138
+ loader.clearSelection();
47139
+ }
47140
+ }
47141
+ if (statePathsToReset.size > 0) {
47142
+ const currentState = this.stateRuntime.normalizeState(page.state);
47143
+ const nextValues = { ...(currentState.values || {}) };
47144
+ let stateChanged = false;
47145
+ for (const path of statePathsToReset) {
47146
+ if (Object.prototype.hasOwnProperty.call(nextValues, path) && nextValues[path] != null) {
47147
+ delete nextValues[path];
47148
+ stateChanged = true;
47149
+ }
47150
+ }
47151
+ if (stateChanged) {
47152
+ const nextState = {
47153
+ ...currentState,
47154
+ values: nextValues,
47155
+ };
47156
+ const next = this.clonePageDefinition(page);
47157
+ next.state = nextState;
47158
+ this.applyPageUpdate(next, true, undefined, false, true);
47159
+ }
47160
+ }
47161
+ }
45134
47162
  dispatchWidgetComponentSettingsToRuntime(key) {
45135
47163
  const loader = this.findWidgetLoader(String(key || '').trim());
45136
47164
  if (typeof loader?.dispatchAction !== 'function') {
@@ -45516,6 +47544,12 @@ class DynamicWidgetPageComponent {
45516
47544
  this.pageState = runtime.state;
45517
47545
  this.pageRuntime = runtime;
45518
47546
  this.applyResponsivePresentation(hydrated, hydrated?.widgets || stateBoundWidgets, runtime);
47547
+ if (this.widgetEventBus) {
47548
+ this.wiringSubscription?.unsubscribe();
47549
+ if (hydrated?.wiring && hydrated.wiring.length > 0) {
47550
+ this.wiringSubscription = this.widgetEventBus.applyWiring(hydrated.wiring);
47551
+ }
47552
+ }
45519
47553
  const previousAuthoredPage = this.authoredPageDefinition;
45520
47554
  const canonicalPage = this.toPersistedCanonicalPage(hydrated);
45521
47555
  const authoredPageChanged = !previousAuthoredPage ||
@@ -45684,8 +47718,7 @@ class DynamicWidgetPageComponent {
45684
47718
  this.activeCanvasInteraction = null;
45685
47719
  this.resizeFeedbackOrigin.set(null);
45686
47720
  this.releaseCanvasPointerCapture(interaction);
45687
- this.canvasPreviewState.set(null);
45688
- this.canvasPreviewInvalidState.set(false);
47721
+ this.resetCanvasPreviewState();
45689
47722
  this.transientCanvasItemsState.set({});
45690
47723
  if (interaction.kind === 'resize') {
45691
47724
  const moved = interaction.moved || Math.hypot(event.clientX - interaction.startX, event.clientY - interaction.startY) >= 4;
@@ -45705,15 +47738,13 @@ class DynamicWidgetPageComponent {
45705
47738
  if (interaction.kind === 'drag') {
45706
47739
  const result = this.updateCanvasItem(interaction.widgetKey, (item) => this.applyCanvasDrag(item, interaction, colDelta, rowDelta), true, this.canvasAnnouncement('drag', interaction.widgetKey), this.blockedCanvasAnnouncement('drag'), 'drag');
45707
47740
  if (result.blocked) {
45708
- this.canvasPreviewState.set(null);
45709
- this.canvasPreviewInvalidState.set(false);
47741
+ this.resetCanvasPreviewState();
45710
47742
  }
45711
47743
  return;
45712
47744
  }
45713
47745
  const result = this.updateCanvasItem(interaction.widgetKey, (item) => this.applyCanvasResize(item, interaction, colDelta, rowDelta), true, this.canvasAnnouncement('resize', interaction.widgetKey), this.blockedCanvasAnnouncement('resize'), 'resize');
45714
47746
  if (result.blocked) {
45715
- this.canvasPreviewState.set(null);
45716
- this.canvasPreviewInvalidState.set(false);
47747
+ this.resetCanvasPreviewState();
45717
47748
  }
45718
47749
  }
45719
47750
  onCanvasPointerCancel(event) {
@@ -46212,8 +48243,7 @@ class DynamicWidgetPageComponent {
46212
48243
  this.sizeEditorNotice.set(null);
46213
48244
  this.transientCanvasItemsState.set({});
46214
48245
  this.resizeFeedbackState.set({});
46215
- this.canvasPreviewState.set(null);
46216
- this.canvasPreviewInvalidState.set(false);
48246
+ this.resetCanvasPreviewState();
46217
48247
  if (restoreChoices) {
46218
48248
  const revision = this.sizePreviewRevision;
46219
48249
  afterNextRender({ read: () => {
@@ -47126,6 +49156,34 @@ class DynamicWidgetPageComponent {
47126
49156
  canvasPreviewInvalid() {
47127
49157
  return this.canvasPreviewInvalidState();
47128
49158
  }
49159
+ canvasPreviewInvalidMessage() {
49160
+ return this.canvasPreviewInvalidMessageState() || this.t('canvas.blocked', 'Posição ocupada');
49161
+ }
49162
+ canvasPreviewSwapTarget() {
49163
+ return this.canvasPreviewSwapTargetState();
49164
+ }
49165
+ canvasPreviewSwapKey() {
49166
+ return this.canvasPreviewSwapTarget()?.widgetKey || null;
49167
+ }
49168
+ canvasPreviewSwapMessage(target) {
49169
+ const targetKey = target.widgetKey || '';
49170
+ const targetLabel = this.resolveWidgetDisplayLabel(targetKey);
49171
+ return this.t('canvas.swapWith', 'Trocar com {{target}}', { target: targetLabel });
49172
+ }
49173
+ resolveWidgetDisplayLabel(widgetKey) {
49174
+ const widget = this.widgets().find((w) => w.key === widgetKey);
49175
+ if (widget) {
49176
+ return this.resolveWidgetDisplayName(widget);
49177
+ }
49178
+ return widgetKey;
49179
+ }
49180
+ resetCanvasPreviewState() {
49181
+ this.canvasPreviewState.set(null);
49182
+ this.canvasPreviewInvalidState.set(false);
49183
+ this.canvasPreviewInvalidMessageState.set(null);
49184
+ this.canvasPreviewSwapTargetState.set(null);
49185
+ this.blockedCanvasWidgetKey = null;
49186
+ }
47129
49187
  startCanvasDrag(widgetKey, event) {
47130
49188
  if (!this.canCustomizeCanvas())
47131
49189
  return;
@@ -47283,9 +49341,7 @@ class DynamicWidgetPageComponent {
47283
49341
  this.resizeClickTargets = [];
47284
49342
  this.resizeFeedbackOrigin.set(null);
47285
49343
  this.releaseCanvasPointerCapture(interaction);
47286
- this.blockedCanvasWidgetKey = null;
47287
- this.canvasPreviewState.set(null);
47288
- this.canvasPreviewInvalidState.set(false);
49344
+ this.resetCanvasPreviewState();
47289
49345
  this.transientCanvasItemsState.set({});
47290
49346
  this.resizeFeedbackState.set({});
47291
49347
  }
@@ -47402,6 +49458,13 @@ class DynamicWidgetPageComponent {
47402
49458
  : undefined,
47403
49459
  });
47404
49460
  this.canvasPreviewInvalidState.set(true);
49461
+ const invalidMsg = placement?.blockedReason === 'lock-position'
49462
+ ? this.t('canvas.blockedLocked', 'Posição bloqueada — widget fixado')
49463
+ : placement?.blockedReason === 'asymmetric-swap'
49464
+ ? this.t('canvas.blockedAsymmetricSwap', 'Espaço incompatível para troca direta')
49465
+ : this.t('canvas.blocked', 'Posição ocupada');
49466
+ this.canvasPreviewInvalidMessageState.set(invalidMsg);
49467
+ this.canvasPreviewSwapTargetState.set(null);
47405
49468
  if (blockedAnnouncement) {
47406
49469
  this.layoutAnnouncement = blockedAnnouncement;
47407
49470
  }
@@ -47409,11 +49472,17 @@ class DynamicWidgetPageComponent {
47409
49472
  }
47410
49473
  this.blockedCanvasWidgetKey = null;
47411
49474
  this.canvasPreviewInvalidState.set(false);
49475
+ this.canvasPreviewInvalidMessageState.set(null);
49476
+ if (placement?.swappedWith && placement.targetItem) {
49477
+ this.canvasPreviewSwapTargetState.set(placement.targetItem);
49478
+ }
49479
+ else {
49480
+ this.canvasPreviewSwapTargetState.set(null);
49481
+ }
47412
49482
  const changed = placement.changed;
47413
49483
  if (!changed) {
47414
49484
  this.transientCanvasItemsState.set({});
47415
- this.canvasPreviewState.set(null);
47416
- this.canvasPreviewInvalidState.set(false);
49485
+ this.resetCanvasPreviewState();
47417
49486
  return { changed: false, blocked: false };
47418
49487
  }
47419
49488
  if (announcement && changed) {
@@ -47431,8 +49500,7 @@ class DynamicWidgetPageComponent {
47431
49500
  };
47432
49501
  }
47433
49502
  this.transientCanvasItemsState.set({});
47434
- this.canvasPreviewState.set(null);
47435
- this.canvasPreviewInvalidState.set(false);
49503
+ this.resetCanvasPreviewState();
47436
49504
  this.applyAuthoringPageUpdate(page, true);
47437
49505
  }
47438
49506
  else {
@@ -47456,6 +49524,7 @@ class DynamicWidgetPageComponent {
47456
49524
  }
47457
49525
  : null);
47458
49526
  this.canvasPreviewInvalidState.set(false);
49527
+ this.canvasPreviewInvalidMessageState.set(null);
47459
49528
  }
47460
49529
  return { changed, blocked: false, swappedWith: placement.swappedWith };
47461
49530
  }
@@ -47496,9 +49565,7 @@ class DynamicWidgetPageComponent {
47496
49565
  if (persist) {
47497
49566
  this.resizeFeedbackState.set({});
47498
49567
  this.transientCanvasItemsState.set({});
47499
- this.canvasPreviewState.set(null);
47500
- this.canvasPreviewInvalidState.set(false);
47501
- this.blockedCanvasWidgetKey = null;
49568
+ this.resetCanvasPreviewState();
47502
49569
  if (changed) {
47503
49570
  const target = this.resolveCanvasTarget(page);
47504
49571
  const affected = Object.keys(placement.items).filter(itemKey => !this.isSameCanvasItem(canvas.items[itemKey], placement.items[itemKey]));
@@ -47527,6 +49594,8 @@ class DynamicWidgetPageComponent {
47527
49594
  // Render the real clamped distribution, never an impossible overlapping ghost.
47528
49595
  this.canvasPreviewState.set(placement.primaryItem);
47529
49596
  this.canvasPreviewInvalidState.set(false);
49597
+ this.canvasPreviewInvalidMessageState.set(null);
49598
+ this.canvasPreviewSwapTargetState.set(null);
47530
49599
  this.blockedCanvasWidgetKey = null;
47531
49600
  }
47532
49601
  return { changed, blocked: limited };
@@ -47706,7 +49775,12 @@ class DynamicWidgetPageComponent {
47706
49775
  };
47707
49776
  }
47708
49777
  resolveCanvasCollisionPolicy(canvas) {
47709
- return canvas.collisionPolicy === 'swap' ? 'swap' : 'block';
49778
+ const policy = canvas.collisionPolicy;
49779
+ if (policy === 'swap')
49780
+ return 'swap';
49781
+ if (policy === 'push-down' || policy === 'cascade')
49782
+ return 'push-down';
49783
+ return 'block';
47710
49784
  }
47711
49785
  resolveCanvasPlacement(widgetKey, current, candidate, canvas, collisionPolicy) {
47712
49786
  const collisions = this.findCanvasCollisions(widgetKey, candidate, canvas);
@@ -47718,36 +49792,115 @@ class DynamicWidgetPageComponent {
47718
49792
  items: { [widgetKey]: candidate },
47719
49793
  };
47720
49794
  }
49795
+ if (collisionPolicy === 'push-down') {
49796
+ const pushDownResult = this.buildCanvasPushDownItems(widgetKey, candidate, canvas);
49797
+ if (!pushDownResult || pushDownResult.blocked) {
49798
+ return {
49799
+ blocked: true,
49800
+ changed: false,
49801
+ primaryItem: candidate,
49802
+ items: { [widgetKey]: candidate },
49803
+ blockedReason: pushDownResult?.blockedReason || 'collision',
49804
+ };
49805
+ }
49806
+ const changed = Object.entries(pushDownResult.items).some(([k, item]) => {
49807
+ const orig = canvas.items[k];
49808
+ return !orig || !this.isSameCanvasItem(orig, item);
49809
+ });
49810
+ return {
49811
+ blocked: false,
49812
+ changed,
49813
+ primaryItem: candidate,
49814
+ items: pushDownResult.items,
49815
+ };
49816
+ }
47721
49817
  if (collisionPolicy !== 'swap' || collisions.length !== 1) {
47722
49818
  return {
47723
49819
  blocked: true,
47724
49820
  changed: false,
47725
49821
  primaryItem: candidate,
47726
49822
  items: { [widgetKey]: candidate },
49823
+ blockedReason: 'collision',
47727
49824
  };
47728
49825
  }
47729
49826
  const [collision] = collisions;
47730
- const swappedItem = this.buildCanvasSwapItem(widgetKey, current, collision.key, collision.item, candidate, canvas);
47731
- if (!swappedItem) {
49827
+ const targetWithKey = { ...collision.item, widgetKey: collision.key };
49828
+ const swapResult = this.buildCanvasSwapItem(widgetKey, current, collision.key, collision.item, candidate, canvas);
49829
+ if (!swapResult.item) {
47732
49830
  return {
47733
49831
  blocked: true,
47734
49832
  changed: false,
47735
49833
  primaryItem: candidate,
47736
49834
  items: { [widgetKey]: candidate },
49835
+ blockedReason: swapResult.blockedReason || 'collision',
49836
+ targetItem: targetWithKey,
47737
49837
  };
47738
49838
  }
47739
49839
  return {
47740
49840
  blocked: false,
47741
49841
  changed: !this.isSameCanvasItem(current, candidate) ||
47742
- !this.isSameCanvasItem(collision.item, swappedItem),
49842
+ !this.isSameCanvasItem(collision.item, swapResult.item),
47743
49843
  primaryItem: candidate,
47744
49844
  items: {
47745
49845
  [widgetKey]: candidate,
47746
- [collision.key]: swappedItem,
49846
+ [collision.key]: swapResult.item,
47747
49847
  },
47748
49848
  swappedWith: collision.key,
49849
+ targetItem: targetWithKey,
47749
49850
  };
47750
49851
  }
49852
+ buildCanvasPushDownItems(movingWidgetKey, candidate, canvas) {
49853
+ const resultItems = {
49854
+ [movingWidgetKey]: candidate,
49855
+ };
49856
+ const otherItems = Object.entries(canvas.items || {})
49857
+ .filter(([key]) => key !== movingWidgetKey)
49858
+ .map(([key, item]) => ({ key, item: { ...item } }));
49859
+ otherItems.sort((a, b) => a.item.row - b.item.row);
49860
+ let hasDisplacements = true;
49861
+ let iterations = 0;
49862
+ const maxIterations = otherItems.length * 2 + 10;
49863
+ while (hasDisplacements && iterations < maxIterations) {
49864
+ hasDisplacements = false;
49865
+ iterations++;
49866
+ for (const other of otherItems) {
49867
+ for (const [placedKey, placedItem] of Object.entries(resultItems)) {
49868
+ if (other.key === placedKey)
49869
+ continue;
49870
+ if (this.canvasItemsOverlap(other.item, placedItem)) {
49871
+ if (other.item.constraints?.lockPosition) {
49872
+ return {
49873
+ blocked: true,
49874
+ items: { [movingWidgetKey]: candidate },
49875
+ blockedReason: 'lock-position',
49876
+ };
49877
+ }
49878
+ const nextRow = placedItem.row + placedItem.rowSpan;
49879
+ if (nextRow > other.item.row) {
49880
+ other.item.row = nextRow;
49881
+ hasDisplacements = true;
49882
+ }
49883
+ }
49884
+ }
49885
+ resultItems[other.key] = other.item;
49886
+ }
49887
+ }
49888
+ for (const [key, item] of Object.entries(resultItems)) {
49889
+ if (key !== movingWidgetKey) {
49890
+ const original = canvas.items[key];
49891
+ if (original &&
49892
+ original.constraints?.lockPosition &&
49893
+ !this.isSameCanvasItem(original, item)) {
49894
+ return {
49895
+ blocked: true,
49896
+ items: { [movingWidgetKey]: candidate },
49897
+ blockedReason: 'lock-position',
49898
+ };
49899
+ }
49900
+ }
49901
+ }
49902
+ return { blocked: false, items: resultItems };
49903
+ }
47751
49904
  findCanvasCollisions(widgetKey, candidate, canvas) {
47752
49905
  return Object.entries(canvas.items || {})
47753
49906
  .filter(([otherKey, otherItem]) => otherKey !== widgetKey &&
@@ -47756,18 +49909,18 @@ class DynamicWidgetPageComponent {
47756
49909
  }
47757
49910
  buildCanvasSwapItem(movingWidgetKey, sourceItem, targetWidgetKey, targetItem, movedItem, canvas) {
47758
49911
  if (targetItem.constraints?.lockPosition) {
47759
- return null;
49912
+ return { item: null, blockedReason: 'lock-position' };
47760
49913
  }
47761
49914
  const intendedSwapItem = {
47762
49915
  ...targetItem,
47763
49916
  col: sourceItem.col,
47764
49917
  row: sourceItem.row,
47765
- colSpan: sourceItem.colSpan,
47766
- rowSpan: sourceItem.rowSpan,
49918
+ colSpan: targetItem.colSpan,
49919
+ rowSpan: targetItem.rowSpan,
47767
49920
  };
47768
49921
  const normalizedSwapItem = this.normalizeCanvasItem(intendedSwapItem, canvas.columns);
47769
49922
  if (!this.isSameCanvasItem(intendedSwapItem, normalizedSwapItem)) {
47770
- return null;
49923
+ return { item: null, blockedReason: 'asymmetric-swap' };
47771
49924
  }
47772
49925
  const collides = Object.entries(canvas.items || {}).some(([otherKey, otherItem]) => {
47773
49926
  if (otherKey === movingWidgetKey || otherKey === targetWidgetKey) {
@@ -47776,9 +49929,9 @@ class DynamicWidgetPageComponent {
47776
49929
  return this.canvasItemsOverlap(normalizedSwapItem, otherItem);
47777
49930
  });
47778
49931
  if (collides || this.canvasItemsOverlap(normalizedSwapItem, movedItem)) {
47779
- return null;
49932
+ return { item: null, blockedReason: 'asymmetric-swap' };
47780
49933
  }
47781
- return normalizedSwapItem;
49934
+ return { item: normalizedSwapItem };
47782
49935
  }
47783
49936
  canPlaceCanvasItem(widgetKey, candidate, canvas) {
47784
49937
  return !Object.entries(canvas.items || {}).some(([otherKey, otherItem]) => otherKey !== widgetKey && this.canvasItemsOverlap(candidate, otherItem));
@@ -48609,9 +50762,22 @@ class DynamicWidgetPageComponent {
48609
50762
  <div
48610
50763
  class="pdx-canvas-snap-preview"
48611
50764
  [class.pdx-canvas-snap-preview--invalid]="canvasPreviewInvalid()"
50765
+ [class.pdx-canvas-snap-preview--swap]="canvasPreviewSwapKey()"
48612
50766
  [style.gridColumn]="canvasPreviewGridColumn()"
48613
50767
  [style.gridRow]="canvasPreviewGridRow()"
48614
- ></div>
50768
+ >
50769
+ @if (canvasPreviewInvalid()) {
50770
+ <div class="pdx-canvas-snap-preview__badge pdx-canvas-snap-preview__badge--invalid">
50771
+ <mat-icon class="pdx-canvas-snap-preview__badge-icon">block</mat-icon>
50772
+ <span>{{ canvasPreviewInvalidMessage() }}</span>
50773
+ </div>
50774
+ } @else if (canvasPreviewSwapTarget(); as swapTarget) {
50775
+ <div class="pdx-canvas-snap-preview__badge pdx-canvas-snap-preview__badge--swap">
50776
+ <mat-icon class="pdx-canvas-snap-preview__badge-icon">swap_horiz</mat-icon>
50777
+ <span>{{ canvasPreviewSwapMessage(swapTarget) }}</span>
50778
+ </div>
50779
+ }
50780
+ </div>
48615
50781
  }
48616
50782
  } @else if (renderedGroups().length) {
48617
50783
  @for (group of renderedGroups(); track group.id) {
@@ -48878,7 +51044,7 @@ class DynamicWidgetPageComponent {
48878
51044
  </div>
48879
51045
  }
48880
51046
  </div>
48881
- `, isInline: true, styles: [":host{display:block;min-width:0}.pdx-page-wrapper{box-sizing:border-box;padding-inline:var(--pdx-page-padding-inline, 0px);position:relative;display:block;background:var(--pdx-page-bg, transparent);color:var(--pdx-page-color, inherit)}.pdx-page{display:grid;gap:var(--pdx-page-gap, 12px);grid-template-columns:minmax(0,1fr)}.pdx-page--canvas{align-items:start;grid-auto-flow:dense}.pdx-page-wrapper.editing .pdx-page--canvas{padding-block-start:var(--pdx-canvas-context-toolbar-safe-area, 60px);padding-block-end:22px;padding-inline:22px;box-sizing:border-box}.pdx-page-settings{position:sticky;top:8px;z-index:2;color:inherit;margin-bottom:6px}.pdx-widget{position:relative;background:var(--pfx-surface, transparent);border-radius:6px;min-width:0;min-height:0}.pdx-widget--interactive{cursor:pointer}.pdx-widget--selected:before{content:\"\";position:absolute;inset:0;border:1px dashed color-mix(in srgb,var(--md-sys-color-primary) 34%,transparent);border-radius:14px;pointer-events:none;z-index:1;box-shadow:0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 6%,transparent),0 8px 20px color-mix(in srgb,var(--md-sys-color-primary) 6%,transparent)}.pdx-widget--canvas{align-self:stretch;--pdx-resize-gradient-diagonal-se: linear-gradient( 135deg, color-mix(in srgb, var(--md-sys-color-primary) 94%, white 6%), color-mix(in srgb, var(--md-sys-color-tertiary) 90%, white 10%), color-mix(in srgb, var(--md-sys-color-secondary) 88%, white 12%) );--pdx-active-resize-gradient: var(--pdx-resize-gradient-diagonal-se)}.pdx-widget--canvas:after{content:\"\";position:absolute;inset:0;padding:2px;border-radius:14px;background:var(--pdx-active-resize-gradient);opacity:0;pointer-events:none;transition:opacity .14s ease,filter .14s ease;filter:saturate(1.02) drop-shadow(0 0 5px color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent));-webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);-webkit-mask-composite:xor;mask-composite:exclude}.pdx-widget--canvas-selected:before,.pdx-widget--canvas-blocked:before{content:\"\";position:absolute;inset:0;border-radius:14px;pointer-events:none;z-index:1;transition:opacity .14s ease,box-shadow .14s ease,border-color .14s ease}.pdx-widget--canvas-blocked:before{border:1px solid color-mix(in srgb,var(--md-sys-color-error) 74%,transparent);box-shadow:0 0 0 1px color-mix(in srgb,var(--md-sys-color-error) 20%,transparent),0 10px 24px color-mix(in srgb,var(--md-sys-color-error) 12%,transparent)}.pdx-widget--canvas-selected:has(>.pdx-canvas-edges .pdx-canvas-edge:hover):after,.pdx-widget--canvas-selected:has(>.pdx-canvas-edges .pdx-canvas-edge:focus-visible):after{opacity:.82}[data-resize-feedback=valid]{--pdx-resize-feedback-color: #27864b}[data-resize-feedback=minimum]{--pdx-resize-feedback-color: #bc8a00}[data-resize-feedback=blocked]{--pdx-resize-feedback-color: var(--md-sys-color-error)}.pdx-widget--canvas[data-resize-feedback]:after{background:var(--pdx-resize-feedback-color);opacity:1;z-index:2}.pdx-resize-feedback{position:absolute;inset-inline:8px;bottom:8px;width:fit-content;max-width:calc(100% - 16px);box-sizing:border-box;display:flex;align-items:center;gap:6px;padding:4px 8px;border:2px solid var(--pdx-resize-feedback-color);border-radius:8px;color:var(--md-sys-color-on-surface);background:var(--md-sys-color-surface);font-size:12px;line-height:18px;pointer-events:none;z-index:3}.pdx-standalone-size-editor{box-sizing:border-box;width:min(720px,calc(100vw - 32px));max-height:calc(100vh - 32px);overflow:auto;padding:20px;border:1px solid var(--md-sys-color-outline-variant);border-radius:16px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface);box-shadow:var(--mat-sys-level3, 0 8px 24px rgba(0, 0, 0, .24))}.pdx-standalone-size-editor__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:20px;padding-top:16px;border-top:1px solid var(--md-sys-color-outline-variant)}.pdx-resize-feedback mat-icon{flex:0 0 18px;width:18px;height:18px;font-size:18px}.pdx-resize-feedback--floating{position:static;max-width:min(320px,calc(100vw - 24px));overflow-wrap:anywhere}.pdx-resize-feedback--floating strong{display:block}.pdx-canvas-edges{position:absolute;inset:0;pointer-events:none;z-index:5}.pdx-canvas-edge{position:absolute;display:grid;place-items:center;box-sizing:border-box;width:min(24px,50%);height:min(24px,50%);padding:0;border:0;background:transparent;color:var(--md-sys-color-primary);pointer-events:auto;touch-action:none;transform:translate(-50%,-50%)}.pdx-canvas-edge:after{content:\"\";box-sizing:border-box;width:min(12px,100%);height:min(12px,100%);pointer-events:none;border:2px solid currentColor;border-radius:2px;background:var(--md-sys-color-surface)}.pdx-canvas-edge:hover:after,.pdx-canvas-edge:focus-visible:after{background:var(--md-sys-color-primary-container)}.pdx-canvas-edge[data-canvas-edge=north-west]{left:0;top:0;cursor:nwse-resize}.pdx-canvas-edge[data-canvas-edge=north]{left:50%;top:0;cursor:ns-resize}.pdx-canvas-edge[data-canvas-edge=north-east]{left:100%;top:0;cursor:nesw-resize}.pdx-canvas-edge[data-canvas-edge=east]{left:100%;top:50%;width:min(24px,100%);cursor:ew-resize}.pdx-canvas-edge[data-canvas-edge=south-east]{left:100%;top:100%;cursor:nwse-resize}.pdx-canvas-edge[data-canvas-edge=south]{left:50%;top:100%;cursor:ns-resize}.pdx-canvas-edge[data-canvas-edge=south-west]{left:0;top:100%;cursor:nesw-resize}.pdx-canvas-edge[data-canvas-edge=west]{left:0;top:50%;width:min(24px,100%);cursor:ew-resize}.pdx-page-wrapper:has(>.pdx-page>.pdx-widget>praxis-widget-shell>.pdx-shell.expanded)>.pdx-page>.pdx-widget>.pdx-canvas-edges,.pdx-page-wrapper:has(>.pdx-page>.pdx-widget>praxis-widget-shell>.pdx-shell.fullscreen)>.pdx-page>.pdx-widget>.pdx-canvas-edges{display:none}.pdx-widget--canvas:focus-visible,.pdx-canvas-edge:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.pdx-canvas-snap-preview{position:relative;align-self:stretch;border-radius:12px;pointer-events:none;z-index:0;background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent),color-mix(in srgb,var(--md-sys-color-tertiary) 12%,transparent));box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 42%,transparent),inset 0 0 0 2px color-mix(in srgb,var(--md-sys-color-surface) 55%,transparent);animation:pdx-snap-preview-pulse .48s ease-out infinite alternate}.pdx-canvas-snap-preview:before,.pdx-canvas-snap-preview:after{content:\"\";position:absolute;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary) 82%,var(--md-sys-color-tertiary) 18%);opacity:.72}.pdx-canvas-snap-preview:before{inset:0 auto 0 0;width:3px}.pdx-canvas-snap-preview:after{inset:0 0 auto;height:3px}.pdx-canvas-snap-preview--invalid{background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-error) 18%,transparent),color-mix(in srgb,var(--md-sys-color-error-container) 22%,transparent));box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-error) 58%,transparent),inset 0 0 0 2px color-mix(in srgb,var(--md-sys-color-surface) 40%,transparent)}.pdx-canvas-snap-preview--invalid:before,.pdx-canvas-snap-preview--invalid:after{background:color-mix(in srgb,var(--md-sys-color-error) 86%,var(--md-sys-color-error-container) 14%)}@media(prefers-reduced-motion:reduce){.pdx-canvas-snap-preview{animation:none}.pdx-widget--canvas:after,.pdx-widget--canvas-selected:before,.pdx-widget--canvas-blocked:before{transition:none}}@media(forced-colors:active){.pdx-widget--canvas-selected:before{border:2px solid Highlight}.pdx-widget--canvas:focus-visible,.pdx-canvas-edge:focus-visible{outline-color:Highlight}}@keyframes pdx-snap-preview-pulse{0%{opacity:.72;transform:scale(.996)}to{opacity:1;transform:scale(1)}}.pdx-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.pdx-group{display:grid;gap:12px;grid-column:1 / -1;padding:12px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 76%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 72%,transparent)}.pdx-group--hero{padding:16px;background:linear-gradient(180deg,color-mix(in srgb,var(--md-sys-color-primary-container) 42%,transparent),color-mix(in srgb,var(--md-sys-color-surface-container-low) 86%,transparent))}.pdx-group--rail{align-self:start}.pdx-group-header{display:flex;align-items:center;justify-content:space-between;gap:12px}.pdx-group-title{font-size:.95rem;font-weight:600;color:var(--md-sys-color-on-surface)}.pdx-group-content{display:grid;gap:12px}.pdx-group-content--stack{grid-template-columns:minmax(0,1fr)}.pdx-group-content--row{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.pdx-group-content--grid{grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}.pdx-group-tabs{display:grid;gap:12px}.pdx-tabs-header{display:flex;flex-wrap:wrap;gap:8px}.pdx-tab-chip{appearance:none;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface);border-radius:999px;padding:8px 12px;font:inherit;cursor:pointer}.pdx-tab-chip.active{border-color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 78%,transparent);color:var(--md-sys-color-on-primary-container)}.pdx-page-wrapper.editing .pdx-widget-settings{opacity:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i5$2.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i6$1.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: CanvasSizeEditorComponent, selector: "praxis-canvas-size-editor", inputs: ["layoutItems", "resetRevision", "restoredValue", "columns", "freeSpaceWidths", "northFreeSpaceHeight", "northUnavailable", "southFreeSpaceHeight", "southUnavailable", "cornerFreeSpaceSizes", "cornerUnavailable", "expandWidths", "notice", "hasUndo", "canUndo", "width", "height", "contentRows", "rowSpan", "widgetLabel", "scopeLabel", "previewPending", "error"], outputs: ["state", "undo", "preview", "apply", "cancel"] }, { kind: "component", type: DynamicWidgetContextToolbarComponent, selector: "praxis-dynamic-widget-context-toolbar", inputs: ["showSize", "sizeDisabled", "sizeLabel", "toolbarLabel", "contextLabel", "contextTooltip", "showAssistant", "assistantLabel", "assistantTooltip", "showComponentSettings", "componentSettingsBusy", "componentSettingsLabel", "componentSettingsTooltip", "showShellSettings", "shellSettingsLabel", "shellSettingsTooltip", "showRemove", "moreActionsLabel", "removeLabel"], outputs: ["size", "assistant", "componentSettings", "shellSettings", "remove"] }, { kind: "directive", type: DynamicWidgetLoaderDirective, selector: "[dynamicWidgetLoader]", inputs: ["dynamicWidgetLoader", "ownerWidgetKey", "context", "strictValidation", "autoWireOutputs"], outputs: ["widgetEvent", "widgetDiagnostic"], exportAs: ["dynamicWidgetLoader"] }, { kind: "component", type: WidgetShellComponent, selector: "praxis-widget-shell", inputs: ["shell", "context", "dragSurfaceEnabled", "dragSurfaceLabel"], outputs: ["action", "dragSurfacePointerDown", "dragSurfaceKeydown"] }] });
51047
+ `, isInline: true, styles: [":host{display:block;min-width:0}.pdx-page-wrapper{box-sizing:border-box;padding-inline:var(--pdx-page-padding-inline, 0px);position:relative;display:block;background:var(--pdx-page-bg, transparent);color:var(--pdx-page-color, inherit)}.pdx-page{display:grid;gap:var(--pdx-page-gap, 12px);grid-template-columns:minmax(0,1fr)}.pdx-page--canvas{align-items:start;grid-auto-flow:dense}.pdx-page-wrapper.editing .pdx-page--canvas{padding-block-start:var(--pdx-canvas-context-toolbar-safe-area, 60px);padding-block-end:22px;padding-inline:22px;box-sizing:border-box}.pdx-page-settings{position:sticky;top:8px;z-index:2;color:inherit;margin-bottom:6px}.pdx-widget{position:relative;background:var(--pfx-surface, transparent);border-radius:6px;min-width:0;min-height:0}.pdx-widget--interactive{cursor:pointer}.pdx-widget--selected:before{content:\"\";position:absolute;inset:0;border:1px dashed color-mix(in srgb,var(--md-sys-color-primary) 34%,transparent);border-radius:14px;pointer-events:none;z-index:1;box-shadow:0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 6%,transparent),0 8px 20px color-mix(in srgb,var(--md-sys-color-primary) 6%,transparent)}.pdx-widget--canvas{align-self:stretch;--pdx-resize-gradient-diagonal-se: linear-gradient( 135deg, color-mix(in srgb, var(--md-sys-color-primary) 94%, white 6%), color-mix(in srgb, var(--md-sys-color-tertiary) 90%, white 10%), color-mix(in srgb, var(--md-sys-color-secondary) 88%, white 12%) );--pdx-active-resize-gradient: var(--pdx-resize-gradient-diagonal-se)}.pdx-widget--canvas:after{content:\"\";position:absolute;inset:0;padding:2px;border-radius:14px;background:var(--pdx-active-resize-gradient);opacity:0;pointer-events:none;transition:opacity .14s ease,filter .14s ease;filter:saturate(1.02) drop-shadow(0 0 5px color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent));-webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);-webkit-mask-composite:xor;mask-composite:exclude}.pdx-widget--canvas-selected:before,.pdx-widget--canvas-blocked:before{content:\"\";position:absolute;inset:0;border-radius:14px;pointer-events:none;z-index:1;transition:opacity .14s ease,box-shadow .14s ease,border-color .14s ease}.pdx-widget--canvas-blocked:before{border:1px solid color-mix(in srgb,var(--md-sys-color-error) 74%,transparent);box-shadow:0 0 0 1px color-mix(in srgb,var(--md-sys-color-error) 20%,transparent),0 10px 24px color-mix(in srgb,var(--md-sys-color-error) 12%,transparent)}.pdx-widget--canvas-selected:has(>.pdx-canvas-edges .pdx-canvas-edge:hover):after,.pdx-widget--canvas-selected:has(>.pdx-canvas-edges .pdx-canvas-edge:focus-visible):after{opacity:.82}[data-resize-feedback=valid]{--pdx-resize-feedback-color: #27864b}[data-resize-feedback=minimum]{--pdx-resize-feedback-color: #bc8a00}[data-resize-feedback=blocked]{--pdx-resize-feedback-color: var(--md-sys-color-error)}.pdx-widget--canvas[data-resize-feedback]:after{background:var(--pdx-resize-feedback-color);opacity:1;z-index:2}.pdx-resize-feedback{position:absolute;inset-inline:8px;bottom:8px;width:fit-content;max-width:calc(100% - 16px);box-sizing:border-box;display:flex;align-items:center;gap:6px;padding:4px 8px;border:2px solid var(--pdx-resize-feedback-color);border-radius:8px;color:var(--md-sys-color-on-surface);background:var(--md-sys-color-surface);font-size:12px;line-height:18px;pointer-events:none;z-index:3}.pdx-standalone-size-editor{box-sizing:border-box;width:min(720px,calc(100vw - 32px));max-height:calc(100vh - 32px);overflow:auto;padding:20px;border:1px solid var(--md-sys-color-outline-variant);border-radius:16px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface);box-shadow:var(--mat-sys-level3, 0 8px 24px rgba(0, 0, 0, .24))}.pdx-standalone-size-editor__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:20px;padding-top:16px;border-top:1px solid var(--md-sys-color-outline-variant)}.pdx-resize-feedback mat-icon{flex:0 0 18px;width:18px;height:18px;font-size:18px}.pdx-resize-feedback--floating{position:static;max-width:min(320px,calc(100vw - 24px));overflow-wrap:anywhere}.pdx-resize-feedback--floating strong{display:block}.pdx-canvas-edges{position:absolute;inset:0;pointer-events:none;z-index:5}.pdx-canvas-edge{position:absolute;display:grid;place-items:center;box-sizing:border-box;width:min(24px,50%);height:min(24px,50%);padding:0;border:0;background:transparent;color:var(--md-sys-color-primary);pointer-events:auto;touch-action:none;transform:translate(-50%,-50%)}.pdx-canvas-edge:after{content:\"\";box-sizing:border-box;width:min(12px,100%);height:min(12px,100%);pointer-events:none;border:2px solid currentColor;border-radius:2px;background:var(--md-sys-color-surface)}.pdx-canvas-edge:hover:after,.pdx-canvas-edge:focus-visible:after{background:var(--md-sys-color-primary-container)}.pdx-canvas-edge[data-canvas-edge=north-west]{left:0;top:0;cursor:nwse-resize}.pdx-canvas-edge[data-canvas-edge=north]{left:50%;top:0;cursor:ns-resize}.pdx-canvas-edge[data-canvas-edge=north-east]{left:100%;top:0;cursor:nesw-resize}.pdx-canvas-edge[data-canvas-edge=east]{left:100%;top:50%;width:min(24px,100%);cursor:ew-resize}.pdx-canvas-edge[data-canvas-edge=south-east]{left:100%;top:100%;cursor:nwse-resize}.pdx-canvas-edge[data-canvas-edge=south]{left:50%;top:100%;cursor:ns-resize}.pdx-canvas-edge[data-canvas-edge=south-west]{left:0;top:100%;cursor:nesw-resize}.pdx-canvas-edge[data-canvas-edge=west]{left:0;top:50%;width:min(24px,100%);cursor:ew-resize}.pdx-page-wrapper:has(>.pdx-page>.pdx-widget>praxis-widget-shell>.pdx-shell.expanded)>.pdx-page>.pdx-widget>.pdx-canvas-edges,.pdx-page-wrapper:has(>.pdx-page>.pdx-widget>praxis-widget-shell>.pdx-shell.fullscreen)>.pdx-page>.pdx-widget>.pdx-canvas-edges{display:none}.pdx-widget--canvas:focus-visible,.pdx-canvas-edge:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.pdx-canvas-snap-preview{position:relative;display:flex;align-items:center;justify-content:center;align-self:stretch;border-radius:12px;pointer-events:none;z-index:2;background:color-mix(in srgb,var(--md-sys-color-primary) 6%,transparent);box-shadow:inset 0 0 0 1.5px color-mix(in srgb,var(--md-sys-color-primary) 45%,transparent),inset 0 0 0 2px color-mix(in srgb,var(--md-sys-color-surface) 60%,transparent);animation:pdx-snap-preview-pulse .48s ease-out infinite alternate}.pdx-canvas-snap-preview:before,.pdx-canvas-snap-preview:after{content:\"\";position:absolute;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary) 82%,var(--md-sys-color-tertiary) 18%);opacity:.72}.pdx-canvas-snap-preview:before{inset:0 auto 0 0;width:3px}.pdx-canvas-snap-preview:after{inset:0 0 auto;height:3px}.pdx-canvas-snap-preview--invalid{background:color-mix(in srgb,var(--md-sys-color-error) 4%,transparent);box-shadow:inset 0 0 0 1.5px color-mix(in srgb,var(--md-sys-color-error) 65%,transparent),inset 0 0 0 2px color-mix(in srgb,var(--md-sys-color-surface) 60%,transparent)}.pdx-canvas-snap-preview--invalid:before,.pdx-canvas-snap-preview--invalid:after{background:color-mix(in srgb,var(--md-sys-color-error) 86%,var(--md-sys-color-error-container) 14%)}.pdx-canvas-snap-preview--swap{background:color-mix(in srgb,var(--md-sys-color-primary) 6%,transparent);box-shadow:inset 0 0 0 1.5px color-mix(in srgb,var(--md-sys-color-primary) 65%,transparent),inset 0 0 0 2px color-mix(in srgb,var(--md-sys-color-surface) 60%,transparent)}.pdx-canvas-snap-preview--swap:before,.pdx-canvas-snap-preview--swap:after{background:color-mix(in srgb,var(--md-sys-color-primary) 86%,var(--md-sys-color-tertiary) 14%)}.pdx-canvas-snap-preview__badge{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;border-radius:999px;font-size:12px;font-weight:600;line-height:1.2;letter-spacing:.02em;white-space:nowrap;background:var(--md-sys-color-surface-container-highest, #ffffff);color:var(--md-sys-color-on-surface, #1e293b);box-shadow:0 4px 16px color-mix(in srgb,var(--mat-sys-shadow, #000) 20%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 40%,transparent);pointer-events:none}.pdx-canvas-snap-preview__badge-icon{font-size:16px;width:16px;height:16px;line-height:16px}.pdx-canvas-snap-preview__badge--invalid{color:var(--md-sys-color-error, #ba1a1a);border-color:color-mix(in srgb,var(--md-sys-color-error) 30%,transparent)}.pdx-canvas-snap-preview__badge--swap{color:var(--md-sys-color-primary, #0061a4);border-color:color-mix(in srgb,var(--md-sys-color-primary) 30%,transparent)}@media(prefers-reduced-motion:reduce){.pdx-canvas-snap-preview{animation:none}.pdx-widget--canvas:after,.pdx-widget--canvas-selected:before,.pdx-widget--canvas-blocked:before{transition:none}}@media(forced-colors:active){.pdx-widget--canvas-selected:before{border:2px solid Highlight}.pdx-widget--canvas:focus-visible,.pdx-canvas-edge:focus-visible{outline-color:Highlight}}@keyframes pdx-snap-preview-pulse{0%{opacity:.72;transform:scale(.996)}to{opacity:1;transform:scale(1)}}.pdx-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.pdx-group{display:grid;gap:12px;grid-column:1 / -1;padding:12px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 76%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 72%,transparent)}.pdx-group--hero{padding:16px;background:linear-gradient(180deg,color-mix(in srgb,var(--md-sys-color-primary-container) 42%,transparent),color-mix(in srgb,var(--md-sys-color-surface-container-low) 86%,transparent))}.pdx-group--rail{align-self:start}.pdx-group-header{display:flex;align-items:center;justify-content:space-between;gap:12px}.pdx-group-title{font-size:.95rem;font-weight:600;color:var(--md-sys-color-on-surface)}.pdx-group-content{display:grid;gap:12px}.pdx-group-content--stack{grid-template-columns:minmax(0,1fr)}.pdx-group-content--row{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.pdx-group-content--grid{grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}.pdx-group-tabs{display:grid;gap:12px}.pdx-tabs-header{display:flex;flex-wrap:wrap;gap:8px}.pdx-tab-chip{appearance:none;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface);border-radius:999px;padding:8px 12px;font:inherit;cursor:pointer}.pdx-tab-chip.active{border-color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 78%,transparent);color:var(--md-sys-color-on-primary-container)}.pdx-page-wrapper.editing .pdx-widget-settings{opacity:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i5$2.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i6$1.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: CanvasSizeEditorComponent, selector: "praxis-canvas-size-editor", inputs: ["layoutItems", "resetRevision", "restoredValue", "columns", "freeSpaceWidths", "northFreeSpaceHeight", "northUnavailable", "southFreeSpaceHeight", "southUnavailable", "cornerFreeSpaceSizes", "cornerUnavailable", "expandWidths", "notice", "hasUndo", "canUndo", "width", "height", "contentRows", "rowSpan", "widgetLabel", "scopeLabel", "previewPending", "error"], outputs: ["state", "undo", "preview", "apply", "cancel"] }, { kind: "component", type: DynamicWidgetContextToolbarComponent, selector: "praxis-dynamic-widget-context-toolbar", inputs: ["showSize", "sizeDisabled", "sizeLabel", "toolbarLabel", "contextLabel", "contextTooltip", "showAssistant", "assistantLabel", "assistantTooltip", "showComponentSettings", "componentSettingsBusy", "componentSettingsLabel", "componentSettingsTooltip", "showShellSettings", "shellSettingsLabel", "shellSettingsTooltip", "showRemove", "moreActionsLabel", "removeLabel"], outputs: ["size", "assistant", "componentSettings", "shellSettings", "remove"] }, { kind: "directive", type: DynamicWidgetLoaderDirective, selector: "[dynamicWidgetLoader]", inputs: ["dynamicWidgetLoader", "ownerWidgetKey", "context", "strictValidation", "autoWireOutputs"], outputs: ["widgetEvent", "widgetDiagnostic"], exportAs: ["dynamicWidgetLoader"] }, { kind: "component", type: WidgetShellComponent, selector: "praxis-widget-shell", inputs: ["shell", "context", "dragSurfaceEnabled", "dragSurfaceLabel"], outputs: ["action", "dragSurfacePointerDown", "dragSurfaceKeydown"] }] });
48882
51048
  }
48883
51049
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicWidgetPageComponent, decorators: [{
48884
51050
  type: Component,
@@ -49105,9 +51271,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
49105
51271
  <div
49106
51272
  class="pdx-canvas-snap-preview"
49107
51273
  [class.pdx-canvas-snap-preview--invalid]="canvasPreviewInvalid()"
51274
+ [class.pdx-canvas-snap-preview--swap]="canvasPreviewSwapKey()"
49108
51275
  [style.gridColumn]="canvasPreviewGridColumn()"
49109
51276
  [style.gridRow]="canvasPreviewGridRow()"
49110
- ></div>
51277
+ >
51278
+ @if (canvasPreviewInvalid()) {
51279
+ <div class="pdx-canvas-snap-preview__badge pdx-canvas-snap-preview__badge--invalid">
51280
+ <mat-icon class="pdx-canvas-snap-preview__badge-icon">block</mat-icon>
51281
+ <span>{{ canvasPreviewInvalidMessage() }}</span>
51282
+ </div>
51283
+ } @else if (canvasPreviewSwapTarget(); as swapTarget) {
51284
+ <div class="pdx-canvas-snap-preview__badge pdx-canvas-snap-preview__badge--swap">
51285
+ <mat-icon class="pdx-canvas-snap-preview__badge-icon">swap_horiz</mat-icon>
51286
+ <span>{{ canvasPreviewSwapMessage(swapTarget) }}</span>
51287
+ </div>
51288
+ }
51289
+ </div>
49111
51290
  }
49112
51291
  } @else if (renderedGroups().length) {
49113
51292
  @for (group of renderedGroups(); track group.id) {
@@ -49374,7 +51553,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
49374
51553
  </div>
49375
51554
  }
49376
51555
  </div>
49377
- `, styles: [":host{display:block;min-width:0}.pdx-page-wrapper{box-sizing:border-box;padding-inline:var(--pdx-page-padding-inline, 0px);position:relative;display:block;background:var(--pdx-page-bg, transparent);color:var(--pdx-page-color, inherit)}.pdx-page{display:grid;gap:var(--pdx-page-gap, 12px);grid-template-columns:minmax(0,1fr)}.pdx-page--canvas{align-items:start;grid-auto-flow:dense}.pdx-page-wrapper.editing .pdx-page--canvas{padding-block-start:var(--pdx-canvas-context-toolbar-safe-area, 60px);padding-block-end:22px;padding-inline:22px;box-sizing:border-box}.pdx-page-settings{position:sticky;top:8px;z-index:2;color:inherit;margin-bottom:6px}.pdx-widget{position:relative;background:var(--pfx-surface, transparent);border-radius:6px;min-width:0;min-height:0}.pdx-widget--interactive{cursor:pointer}.pdx-widget--selected:before{content:\"\";position:absolute;inset:0;border:1px dashed color-mix(in srgb,var(--md-sys-color-primary) 34%,transparent);border-radius:14px;pointer-events:none;z-index:1;box-shadow:0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 6%,transparent),0 8px 20px color-mix(in srgb,var(--md-sys-color-primary) 6%,transparent)}.pdx-widget--canvas{align-self:stretch;--pdx-resize-gradient-diagonal-se: linear-gradient( 135deg, color-mix(in srgb, var(--md-sys-color-primary) 94%, white 6%), color-mix(in srgb, var(--md-sys-color-tertiary) 90%, white 10%), color-mix(in srgb, var(--md-sys-color-secondary) 88%, white 12%) );--pdx-active-resize-gradient: var(--pdx-resize-gradient-diagonal-se)}.pdx-widget--canvas:after{content:\"\";position:absolute;inset:0;padding:2px;border-radius:14px;background:var(--pdx-active-resize-gradient);opacity:0;pointer-events:none;transition:opacity .14s ease,filter .14s ease;filter:saturate(1.02) drop-shadow(0 0 5px color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent));-webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);-webkit-mask-composite:xor;mask-composite:exclude}.pdx-widget--canvas-selected:before,.pdx-widget--canvas-blocked:before{content:\"\";position:absolute;inset:0;border-radius:14px;pointer-events:none;z-index:1;transition:opacity .14s ease,box-shadow .14s ease,border-color .14s ease}.pdx-widget--canvas-blocked:before{border:1px solid color-mix(in srgb,var(--md-sys-color-error) 74%,transparent);box-shadow:0 0 0 1px color-mix(in srgb,var(--md-sys-color-error) 20%,transparent),0 10px 24px color-mix(in srgb,var(--md-sys-color-error) 12%,transparent)}.pdx-widget--canvas-selected:has(>.pdx-canvas-edges .pdx-canvas-edge:hover):after,.pdx-widget--canvas-selected:has(>.pdx-canvas-edges .pdx-canvas-edge:focus-visible):after{opacity:.82}[data-resize-feedback=valid]{--pdx-resize-feedback-color: #27864b}[data-resize-feedback=minimum]{--pdx-resize-feedback-color: #bc8a00}[data-resize-feedback=blocked]{--pdx-resize-feedback-color: var(--md-sys-color-error)}.pdx-widget--canvas[data-resize-feedback]:after{background:var(--pdx-resize-feedback-color);opacity:1;z-index:2}.pdx-resize-feedback{position:absolute;inset-inline:8px;bottom:8px;width:fit-content;max-width:calc(100% - 16px);box-sizing:border-box;display:flex;align-items:center;gap:6px;padding:4px 8px;border:2px solid var(--pdx-resize-feedback-color);border-radius:8px;color:var(--md-sys-color-on-surface);background:var(--md-sys-color-surface);font-size:12px;line-height:18px;pointer-events:none;z-index:3}.pdx-standalone-size-editor{box-sizing:border-box;width:min(720px,calc(100vw - 32px));max-height:calc(100vh - 32px);overflow:auto;padding:20px;border:1px solid var(--md-sys-color-outline-variant);border-radius:16px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface);box-shadow:var(--mat-sys-level3, 0 8px 24px rgba(0, 0, 0, .24))}.pdx-standalone-size-editor__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:20px;padding-top:16px;border-top:1px solid var(--md-sys-color-outline-variant)}.pdx-resize-feedback mat-icon{flex:0 0 18px;width:18px;height:18px;font-size:18px}.pdx-resize-feedback--floating{position:static;max-width:min(320px,calc(100vw - 24px));overflow-wrap:anywhere}.pdx-resize-feedback--floating strong{display:block}.pdx-canvas-edges{position:absolute;inset:0;pointer-events:none;z-index:5}.pdx-canvas-edge{position:absolute;display:grid;place-items:center;box-sizing:border-box;width:min(24px,50%);height:min(24px,50%);padding:0;border:0;background:transparent;color:var(--md-sys-color-primary);pointer-events:auto;touch-action:none;transform:translate(-50%,-50%)}.pdx-canvas-edge:after{content:\"\";box-sizing:border-box;width:min(12px,100%);height:min(12px,100%);pointer-events:none;border:2px solid currentColor;border-radius:2px;background:var(--md-sys-color-surface)}.pdx-canvas-edge:hover:after,.pdx-canvas-edge:focus-visible:after{background:var(--md-sys-color-primary-container)}.pdx-canvas-edge[data-canvas-edge=north-west]{left:0;top:0;cursor:nwse-resize}.pdx-canvas-edge[data-canvas-edge=north]{left:50%;top:0;cursor:ns-resize}.pdx-canvas-edge[data-canvas-edge=north-east]{left:100%;top:0;cursor:nesw-resize}.pdx-canvas-edge[data-canvas-edge=east]{left:100%;top:50%;width:min(24px,100%);cursor:ew-resize}.pdx-canvas-edge[data-canvas-edge=south-east]{left:100%;top:100%;cursor:nwse-resize}.pdx-canvas-edge[data-canvas-edge=south]{left:50%;top:100%;cursor:ns-resize}.pdx-canvas-edge[data-canvas-edge=south-west]{left:0;top:100%;cursor:nesw-resize}.pdx-canvas-edge[data-canvas-edge=west]{left:0;top:50%;width:min(24px,100%);cursor:ew-resize}.pdx-page-wrapper:has(>.pdx-page>.pdx-widget>praxis-widget-shell>.pdx-shell.expanded)>.pdx-page>.pdx-widget>.pdx-canvas-edges,.pdx-page-wrapper:has(>.pdx-page>.pdx-widget>praxis-widget-shell>.pdx-shell.fullscreen)>.pdx-page>.pdx-widget>.pdx-canvas-edges{display:none}.pdx-widget--canvas:focus-visible,.pdx-canvas-edge:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.pdx-canvas-snap-preview{position:relative;align-self:stretch;border-radius:12px;pointer-events:none;z-index:0;background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent),color-mix(in srgb,var(--md-sys-color-tertiary) 12%,transparent));box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 42%,transparent),inset 0 0 0 2px color-mix(in srgb,var(--md-sys-color-surface) 55%,transparent);animation:pdx-snap-preview-pulse .48s ease-out infinite alternate}.pdx-canvas-snap-preview:before,.pdx-canvas-snap-preview:after{content:\"\";position:absolute;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary) 82%,var(--md-sys-color-tertiary) 18%);opacity:.72}.pdx-canvas-snap-preview:before{inset:0 auto 0 0;width:3px}.pdx-canvas-snap-preview:after{inset:0 0 auto;height:3px}.pdx-canvas-snap-preview--invalid{background:linear-gradient(135deg,color-mix(in srgb,var(--md-sys-color-error) 18%,transparent),color-mix(in srgb,var(--md-sys-color-error-container) 22%,transparent));box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--md-sys-color-error) 58%,transparent),inset 0 0 0 2px color-mix(in srgb,var(--md-sys-color-surface) 40%,transparent)}.pdx-canvas-snap-preview--invalid:before,.pdx-canvas-snap-preview--invalid:after{background:color-mix(in srgb,var(--md-sys-color-error) 86%,var(--md-sys-color-error-container) 14%)}@media(prefers-reduced-motion:reduce){.pdx-canvas-snap-preview{animation:none}.pdx-widget--canvas:after,.pdx-widget--canvas-selected:before,.pdx-widget--canvas-blocked:before{transition:none}}@media(forced-colors:active){.pdx-widget--canvas-selected:before{border:2px solid Highlight}.pdx-widget--canvas:focus-visible,.pdx-canvas-edge:focus-visible{outline-color:Highlight}}@keyframes pdx-snap-preview-pulse{0%{opacity:.72;transform:scale(.996)}to{opacity:1;transform:scale(1)}}.pdx-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.pdx-group{display:grid;gap:12px;grid-column:1 / -1;padding:12px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 76%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 72%,transparent)}.pdx-group--hero{padding:16px;background:linear-gradient(180deg,color-mix(in srgb,var(--md-sys-color-primary-container) 42%,transparent),color-mix(in srgb,var(--md-sys-color-surface-container-low) 86%,transparent))}.pdx-group--rail{align-self:start}.pdx-group-header{display:flex;align-items:center;justify-content:space-between;gap:12px}.pdx-group-title{font-size:.95rem;font-weight:600;color:var(--md-sys-color-on-surface)}.pdx-group-content{display:grid;gap:12px}.pdx-group-content--stack{grid-template-columns:minmax(0,1fr)}.pdx-group-content--row{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.pdx-group-content--grid{grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}.pdx-group-tabs{display:grid;gap:12px}.pdx-tabs-header{display:flex;flex-wrap:wrap;gap:8px}.pdx-tab-chip{appearance:none;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface);border-radius:999px;padding:8px 12px;font:inherit;cursor:pointer}.pdx-tab-chip.active{border-color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 78%,transparent);color:var(--md-sys-color-on-primary-container)}.pdx-page-wrapper.editing .pdx-widget-settings{opacity:1}\n"] }]
51556
+ `, styles: [":host{display:block;min-width:0}.pdx-page-wrapper{box-sizing:border-box;padding-inline:var(--pdx-page-padding-inline, 0px);position:relative;display:block;background:var(--pdx-page-bg, transparent);color:var(--pdx-page-color, inherit)}.pdx-page{display:grid;gap:var(--pdx-page-gap, 12px);grid-template-columns:minmax(0,1fr)}.pdx-page--canvas{align-items:start;grid-auto-flow:dense}.pdx-page-wrapper.editing .pdx-page--canvas{padding-block-start:var(--pdx-canvas-context-toolbar-safe-area, 60px);padding-block-end:22px;padding-inline:22px;box-sizing:border-box}.pdx-page-settings{position:sticky;top:8px;z-index:2;color:inherit;margin-bottom:6px}.pdx-widget{position:relative;background:var(--pfx-surface, transparent);border-radius:6px;min-width:0;min-height:0}.pdx-widget--interactive{cursor:pointer}.pdx-widget--selected:before{content:\"\";position:absolute;inset:0;border:1px dashed color-mix(in srgb,var(--md-sys-color-primary) 34%,transparent);border-radius:14px;pointer-events:none;z-index:1;box-shadow:0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 6%,transparent),0 8px 20px color-mix(in srgb,var(--md-sys-color-primary) 6%,transparent)}.pdx-widget--canvas{align-self:stretch;--pdx-resize-gradient-diagonal-se: linear-gradient( 135deg, color-mix(in srgb, var(--md-sys-color-primary) 94%, white 6%), color-mix(in srgb, var(--md-sys-color-tertiary) 90%, white 10%), color-mix(in srgb, var(--md-sys-color-secondary) 88%, white 12%) );--pdx-active-resize-gradient: var(--pdx-resize-gradient-diagonal-se)}.pdx-widget--canvas:after{content:\"\";position:absolute;inset:0;padding:2px;border-radius:14px;background:var(--pdx-active-resize-gradient);opacity:0;pointer-events:none;transition:opacity .14s ease,filter .14s ease;filter:saturate(1.02) drop-shadow(0 0 5px color-mix(in srgb,var(--md-sys-color-primary) 10%,transparent));-webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);-webkit-mask-composite:xor;mask-composite:exclude}.pdx-widget--canvas-selected:before,.pdx-widget--canvas-blocked:before{content:\"\";position:absolute;inset:0;border-radius:14px;pointer-events:none;z-index:1;transition:opacity .14s ease,box-shadow .14s ease,border-color .14s ease}.pdx-widget--canvas-blocked:before{border:1px solid color-mix(in srgb,var(--md-sys-color-error) 74%,transparent);box-shadow:0 0 0 1px color-mix(in srgb,var(--md-sys-color-error) 20%,transparent),0 10px 24px color-mix(in srgb,var(--md-sys-color-error) 12%,transparent)}.pdx-widget--canvas-selected:has(>.pdx-canvas-edges .pdx-canvas-edge:hover):after,.pdx-widget--canvas-selected:has(>.pdx-canvas-edges .pdx-canvas-edge:focus-visible):after{opacity:.82}[data-resize-feedback=valid]{--pdx-resize-feedback-color: #27864b}[data-resize-feedback=minimum]{--pdx-resize-feedback-color: #bc8a00}[data-resize-feedback=blocked]{--pdx-resize-feedback-color: var(--md-sys-color-error)}.pdx-widget--canvas[data-resize-feedback]:after{background:var(--pdx-resize-feedback-color);opacity:1;z-index:2}.pdx-resize-feedback{position:absolute;inset-inline:8px;bottom:8px;width:fit-content;max-width:calc(100% - 16px);box-sizing:border-box;display:flex;align-items:center;gap:6px;padding:4px 8px;border:2px solid var(--pdx-resize-feedback-color);border-radius:8px;color:var(--md-sys-color-on-surface);background:var(--md-sys-color-surface);font-size:12px;line-height:18px;pointer-events:none;z-index:3}.pdx-standalone-size-editor{box-sizing:border-box;width:min(720px,calc(100vw - 32px));max-height:calc(100vh - 32px);overflow:auto;padding:20px;border:1px solid var(--md-sys-color-outline-variant);border-radius:16px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface);box-shadow:var(--mat-sys-level3, 0 8px 24px rgba(0, 0, 0, .24))}.pdx-standalone-size-editor__footer{display:flex;justify-content:flex-end;gap:8px;margin-top:20px;padding-top:16px;border-top:1px solid var(--md-sys-color-outline-variant)}.pdx-resize-feedback mat-icon{flex:0 0 18px;width:18px;height:18px;font-size:18px}.pdx-resize-feedback--floating{position:static;max-width:min(320px,calc(100vw - 24px));overflow-wrap:anywhere}.pdx-resize-feedback--floating strong{display:block}.pdx-canvas-edges{position:absolute;inset:0;pointer-events:none;z-index:5}.pdx-canvas-edge{position:absolute;display:grid;place-items:center;box-sizing:border-box;width:min(24px,50%);height:min(24px,50%);padding:0;border:0;background:transparent;color:var(--md-sys-color-primary);pointer-events:auto;touch-action:none;transform:translate(-50%,-50%)}.pdx-canvas-edge:after{content:\"\";box-sizing:border-box;width:min(12px,100%);height:min(12px,100%);pointer-events:none;border:2px solid currentColor;border-radius:2px;background:var(--md-sys-color-surface)}.pdx-canvas-edge:hover:after,.pdx-canvas-edge:focus-visible:after{background:var(--md-sys-color-primary-container)}.pdx-canvas-edge[data-canvas-edge=north-west]{left:0;top:0;cursor:nwse-resize}.pdx-canvas-edge[data-canvas-edge=north]{left:50%;top:0;cursor:ns-resize}.pdx-canvas-edge[data-canvas-edge=north-east]{left:100%;top:0;cursor:nesw-resize}.pdx-canvas-edge[data-canvas-edge=east]{left:100%;top:50%;width:min(24px,100%);cursor:ew-resize}.pdx-canvas-edge[data-canvas-edge=south-east]{left:100%;top:100%;cursor:nwse-resize}.pdx-canvas-edge[data-canvas-edge=south]{left:50%;top:100%;cursor:ns-resize}.pdx-canvas-edge[data-canvas-edge=south-west]{left:0;top:100%;cursor:nesw-resize}.pdx-canvas-edge[data-canvas-edge=west]{left:0;top:50%;width:min(24px,100%);cursor:ew-resize}.pdx-page-wrapper:has(>.pdx-page>.pdx-widget>praxis-widget-shell>.pdx-shell.expanded)>.pdx-page>.pdx-widget>.pdx-canvas-edges,.pdx-page-wrapper:has(>.pdx-page>.pdx-widget>praxis-widget-shell>.pdx-shell.fullscreen)>.pdx-page>.pdx-widget>.pdx-canvas-edges{display:none}.pdx-widget--canvas:focus-visible,.pdx-canvas-edge:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.pdx-canvas-snap-preview{position:relative;display:flex;align-items:center;justify-content:center;align-self:stretch;border-radius:12px;pointer-events:none;z-index:2;background:color-mix(in srgb,var(--md-sys-color-primary) 6%,transparent);box-shadow:inset 0 0 0 1.5px color-mix(in srgb,var(--md-sys-color-primary) 45%,transparent),inset 0 0 0 2px color-mix(in srgb,var(--md-sys-color-surface) 60%,transparent);animation:pdx-snap-preview-pulse .48s ease-out infinite alternate}.pdx-canvas-snap-preview:before,.pdx-canvas-snap-preview:after{content:\"\";position:absolute;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary) 82%,var(--md-sys-color-tertiary) 18%);opacity:.72}.pdx-canvas-snap-preview:before{inset:0 auto 0 0;width:3px}.pdx-canvas-snap-preview:after{inset:0 0 auto;height:3px}.pdx-canvas-snap-preview--invalid{background:color-mix(in srgb,var(--md-sys-color-error) 4%,transparent);box-shadow:inset 0 0 0 1.5px color-mix(in srgb,var(--md-sys-color-error) 65%,transparent),inset 0 0 0 2px color-mix(in srgb,var(--md-sys-color-surface) 60%,transparent)}.pdx-canvas-snap-preview--invalid:before,.pdx-canvas-snap-preview--invalid:after{background:color-mix(in srgb,var(--md-sys-color-error) 86%,var(--md-sys-color-error-container) 14%)}.pdx-canvas-snap-preview--swap{background:color-mix(in srgb,var(--md-sys-color-primary) 6%,transparent);box-shadow:inset 0 0 0 1.5px color-mix(in srgb,var(--md-sys-color-primary) 65%,transparent),inset 0 0 0 2px color-mix(in srgb,var(--md-sys-color-surface) 60%,transparent)}.pdx-canvas-snap-preview--swap:before,.pdx-canvas-snap-preview--swap:after{background:color-mix(in srgb,var(--md-sys-color-primary) 86%,var(--md-sys-color-tertiary) 14%)}.pdx-canvas-snap-preview__badge{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;border-radius:999px;font-size:12px;font-weight:600;line-height:1.2;letter-spacing:.02em;white-space:nowrap;background:var(--md-sys-color-surface-container-highest, #ffffff);color:var(--md-sys-color-on-surface, #1e293b);box-shadow:0 4px 16px color-mix(in srgb,var(--mat-sys-shadow, #000) 20%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 40%,transparent);pointer-events:none}.pdx-canvas-snap-preview__badge-icon{font-size:16px;width:16px;height:16px;line-height:16px}.pdx-canvas-snap-preview__badge--invalid{color:var(--md-sys-color-error, #ba1a1a);border-color:color-mix(in srgb,var(--md-sys-color-error) 30%,transparent)}.pdx-canvas-snap-preview__badge--swap{color:var(--md-sys-color-primary, #0061a4);border-color:color-mix(in srgb,var(--md-sys-color-primary) 30%,transparent)}@media(prefers-reduced-motion:reduce){.pdx-canvas-snap-preview{animation:none}.pdx-widget--canvas:after,.pdx-widget--canvas-selected:before,.pdx-widget--canvas-blocked:before{transition:none}}@media(forced-colors:active){.pdx-widget--canvas-selected:before{border:2px solid Highlight}.pdx-widget--canvas:focus-visible,.pdx-canvas-edge:focus-visible{outline-color:Highlight}}@keyframes pdx-snap-preview-pulse{0%{opacity:.72;transform:scale(.996)}to{opacity:1;transform:scale(1)}}.pdx-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.pdx-group{display:grid;gap:12px;grid-column:1 / -1;padding:12px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 76%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 72%,transparent)}.pdx-group--hero{padding:16px;background:linear-gradient(180deg,color-mix(in srgb,var(--md-sys-color-primary-container) 42%,transparent),color-mix(in srgb,var(--md-sys-color-surface-container-low) 86%,transparent))}.pdx-group--rail{align-self:start}.pdx-group-header{display:flex;align-items:center;justify-content:space-between;gap:12px}.pdx-group-title{font-size:.95rem;font-weight:600;color:var(--md-sys-color-on-surface)}.pdx-group-content{display:grid;gap:12px}.pdx-group-content--stack{grid-template-columns:minmax(0,1fr)}.pdx-group-content--row{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.pdx-group-content--grid{grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}.pdx-group-tabs{display:grid;gap:12px}.pdx-tabs-header{display:flex;flex-wrap:wrap;gap:8px}.pdx-tab-chip{appearance:none;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface);border-radius:999px;padding:8px 12px;font:inherit;cursor:pointer}.pdx-tab-chip.active{border-color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 78%,transparent);color:var(--md-sys-color-on-primary-container)}.pdx-page-wrapper.editing .pdx-widget-settings{opacity:1}\n"] }]
49378
51557
  }], ctorParameters: () => [], propDecorators: { widgetSizeTemplate: [{
49379
51558
  type: ViewChild,
49380
51559
  args: ['widgetSizeTemplate', { static: true }]
@@ -53331,6 +55510,909 @@ function provideHookWhitelist(allowed) {
53331
55510
  return [{ provide: FORM_HOOKS_WHITELIST, useValue: allowed, multi: true }];
53332
55511
  }
53333
55512
 
55513
+ /**
55514
+ * PraxisKeepAliveTabDirective
55515
+ * ----------------------------
55516
+ * Diretiva canônica de plataforma para preservação de estado e ciclo de vida
55517
+ * em layouts de abas, dossiês, painéis expansíveis e drawers (Issue #2).
55518
+ *
55519
+ * Evita a destruição da árvore do DOM que ocorre com `@if` tradicional,
55520
+ * preservando instâncias de componentes, formulários reativos (`FormGroup`),
55521
+ * seleções de dropdowns, campos digitados e uploads em andamento.
55522
+ *
55523
+ * Suporta dois padrões canônicos:
55524
+ *
55525
+ * 1. Padrão Estrutural (Lazy Mounting + Keep-Alive):
55526
+ * ```html
55527
+ * <div *praxisKeepAliveTab="activeTab() === 'identidade'">
55528
+ * <praxis-dynamic-form ... />
55529
+ * </div>
55530
+ * ```
55531
+ * - Monta o componente somente na primeira vez que a aba fica ativa.
55532
+ * - Nas trocas de aba subsequentes, oculta via `display: none !important;` e `hidden`,
55533
+ * sem desmontar nem recriar o componente.
55534
+ *
55535
+ * 2. Padrão Atributo:
55536
+ * ```html
55537
+ * <div [praxisKeepAliveTab]="activeTab() === 'competencias'">
55538
+ * <praxis-dynamic-form ... />
55539
+ * </div>
55540
+ * ```
55541
+ */
55542
+ class PraxisKeepAliveTabDirective {
55543
+ templateRef = inject(TemplateRef, { optional: true });
55544
+ viewContainer = inject(ViewContainerRef, { optional: true });
55545
+ elementRef = inject(ElementRef, { optional: true });
55546
+ renderer = inject(Renderer2);
55547
+ viewRef = null;
55548
+ isActive = false;
55549
+ set active(condition) {
55550
+ this.isActive = !!condition;
55551
+ this.updateVisibility();
55552
+ }
55553
+ get active() {
55554
+ return this.isActive;
55555
+ }
55556
+ updateVisibility() {
55557
+ if (this.templateRef && this.viewContainer) {
55558
+ this.updateStructuralView();
55559
+ }
55560
+ else if (this.elementRef) {
55561
+ this.updateAttributeView();
55562
+ }
55563
+ }
55564
+ updateStructuralView() {
55565
+ if (this.isActive) {
55566
+ if (!this.viewRef) {
55567
+ // Primeira ativação: cria a view no container
55568
+ this.viewRef = this.viewContainer.createEmbeddedView(this.templateRef);
55569
+ }
55570
+ else {
55571
+ // Reativação: exibe os nós preservados
55572
+ this.setNodesVisibility(this.viewRef.rootNodes, true);
55573
+ }
55574
+ }
55575
+ else if (this.viewRef) {
55576
+ // Inativação: oculta nós sem destruir a view
55577
+ this.setNodesVisibility(this.viewRef.rootNodes, false);
55578
+ }
55579
+ }
55580
+ updateAttributeView() {
55581
+ const el = this.elementRef.nativeElement;
55582
+ if (this.isActive) {
55583
+ this.renderer.removeAttribute(el, 'hidden');
55584
+ if (el.__praxisOriginalDisplay !== undefined) {
55585
+ const original = el.__praxisOriginalDisplay;
55586
+ if (original) {
55587
+ this.renderer.setStyle(el, 'display', original);
55588
+ }
55589
+ else {
55590
+ this.renderer.removeStyle(el, 'display');
55591
+ }
55592
+ delete el.__praxisOriginalDisplay;
55593
+ }
55594
+ }
55595
+ else {
55596
+ if (el.__praxisOriginalDisplay === undefined) {
55597
+ el.__praxisOriginalDisplay = el.style?.display || '';
55598
+ }
55599
+ this.renderer.setAttribute(el, 'hidden', '');
55600
+ this.renderer.setStyle(el, 'display', 'none', 1 /* !important */);
55601
+ }
55602
+ }
55603
+ setNodesVisibility(nodes, visible) {
55604
+ for (const node of nodes) {
55605
+ if (node && node.nodeType === 1 /* Node.ELEMENT_NODE */) {
55606
+ if (visible) {
55607
+ this.renderer.removeAttribute(node, 'hidden');
55608
+ if (node.__praxisOriginalDisplay !== undefined) {
55609
+ const original = node.__praxisOriginalDisplay;
55610
+ if (original) {
55611
+ this.renderer.setStyle(node, 'display', original);
55612
+ }
55613
+ else {
55614
+ this.renderer.removeStyle(node, 'display');
55615
+ }
55616
+ delete node.__praxisOriginalDisplay;
55617
+ }
55618
+ }
55619
+ else {
55620
+ if (node.__praxisOriginalDisplay === undefined) {
55621
+ node.__praxisOriginalDisplay = node.style?.display || '';
55622
+ }
55623
+ this.renderer.setAttribute(node, 'hidden', '');
55624
+ this.renderer.setStyle(node, 'display', 'none', 1 /* !important */);
55625
+ }
55626
+ }
55627
+ }
55628
+ }
55629
+ ngOnDestroy() {
55630
+ if (this.viewRef) {
55631
+ this.viewRef.destroy();
55632
+ this.viewRef = null;
55633
+ }
55634
+ }
55635
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisKeepAliveTabDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
55636
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.14", type: PraxisKeepAliveTabDirective, isStandalone: true, selector: "[praxisKeepAliveTab]", inputs: { active: ["praxisKeepAliveTab", "active"] }, ngImport: i0 });
55637
+ }
55638
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisKeepAliveTabDirective, decorators: [{
55639
+ type: Directive,
55640
+ args: [{
55641
+ selector: '[praxisKeepAliveTab]',
55642
+ standalone: true,
55643
+ }]
55644
+ }], propDecorators: { active: [{
55645
+ type: Input,
55646
+ args: ['praxisKeepAliveTab']
55647
+ }] } });
55648
+
55649
+ /**
55650
+ * =============================================================================
55651
+ * DYNAMIC DATA RECORD & METADATA-DRIVEN TYPES (Issue #35)
55652
+ * =============================================================================
55653
+ * Fornece contratos genéricos dinâmicos e utilitários puros para manipulação
55654
+ * de registros guiados por schema (OpenAPI / /schemas/filtered), eliminando a
55655
+ * necessidade de declarar DTOs TypeScript estáticos redundantes nas aplicações.
55656
+ */
55657
+ const BLOCKED_KEYS = new Set(['__proto__', 'prototype', 'constructor']);
55658
+ /**
55659
+ * Acessa com segurança uma propriedade (inclusive aninhada com pontos, ex: 'endereco.cidade'
55660
+ * ou 'cliente.limiteCredito') em um registro dinâmico.
55661
+ *
55662
+ * @param record O registro ou objeto alvo
55663
+ * @param path Caminho da propriedade (ex.: 'nome', 'perfil.cargo', 'financeiro.total')
55664
+ * @param fallback Valor padrão retornado caso a propriedade seja nula ou indefinida
55665
+ */
55666
+ function resolveDataField(record, path, fallback) {
55667
+ if (!record || typeof record !== 'object' || !path || typeof path !== 'string') {
55668
+ return fallback;
55669
+ }
55670
+ const normalized = path.trim();
55671
+ if (!normalized) {
55672
+ return fallback;
55673
+ }
55674
+ // Acesso direto O(1) se a chave existir no primeiro nível
55675
+ if (Object.prototype.hasOwnProperty.call(record, normalized)) {
55676
+ const val = record[normalized];
55677
+ return (val !== undefined && val !== null ? val : fallback);
55678
+ }
55679
+ // Decomposição de caminho com notação de ponto e colchetes de array (ex: 'pedidos[0].itens[1].valor')
55680
+ const pathWithDots = normalized.replace(/\[(\w+)\]/g, '.$1').replace(/^\./, '');
55681
+ const segments = pathWithDots.split('.');
55682
+ let current = record;
55683
+ for (let i = 0; i < segments.length; i++) {
55684
+ const segment = segments[i].trim();
55685
+ if (!segment || BLOCKED_KEYS.has(segment)) {
55686
+ return fallback;
55687
+ }
55688
+ if (current == null || (typeof current !== 'object' && typeof current !== 'function')) {
55689
+ return fallback;
55690
+ }
55691
+ current = current[segment];
55692
+ }
55693
+ return (current !== undefined && current !== null ? current : fallback);
55694
+ }
55695
+ /**
55696
+ * Cria ou encapsula um registro dinâmico anexando metadados de forma imutável.
55697
+ */
55698
+ function createDynamicRecord(data, meta) {
55699
+ const base = { ...(data || {}) };
55700
+ if (meta) {
55701
+ base._meta = { ...(base._meta || {}), ...meta };
55702
+ }
55703
+ return base;
55704
+ }
55705
+ /**
55706
+ * Type-guard para verificar se um valor desconhecido é um objeto de registro válido.
55707
+ */
55708
+ function isDynamicDataRecord(value) {
55709
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
55710
+ }
55711
+ /**
55712
+ * Extrai o identificador canônico de um registro com suporte a chaves alternativas ('id', '_id', 'codigo').
55713
+ */
55714
+ function extractRecordId(record, fallbackKey) {
55715
+ if (!record || typeof record !== 'object') {
55716
+ return null;
55717
+ }
55718
+ const r = record;
55719
+ if (r['id'] !== undefined && r['id'] !== null && r['id'] !== '') {
55720
+ return r['id'];
55721
+ }
55722
+ if (r['_id'] !== undefined && r['_id'] !== null && r['_id'] !== '') {
55723
+ return r['_id'];
55724
+ }
55725
+ if (fallbackKey && r[fallbackKey] !== undefined && r[fallbackKey] !== null && r[fallbackKey] !== '') {
55726
+ return r[fallbackKey];
55727
+ }
55728
+ if (r['codigo'] !== undefined && r['codigo'] !== null && r['codigo'] !== '') {
55729
+ return r['codigo'];
55730
+ }
55731
+ return null;
55732
+ }
55733
+
55734
+ /**
55735
+ * Praxis UI Platform — Enterprise App Shell Models
55736
+ *
55737
+ * Contratos declarativos e fortemente tipados para a casca de aplicação corporativa
55738
+ * governada (PraxisAppShell).
55739
+ */
55740
+
55741
+ class PraxisAppShellComponent {
55742
+ brand;
55743
+ navigation = [];
55744
+ user;
55745
+ showThemeToggle = true;
55746
+ cdr = inject(ChangeDetectorRef);
55747
+ loadingOrchestrator = inject(LoadingOrchestrator, { optional: true });
55748
+ orchestratorLoading = this.loadingOrchestrator
55749
+ ? toSignal(this.loadingOrchestrator.watch().pipe(map((list) => list.length > 0)), {
55750
+ initialValue: false,
55751
+ })
55752
+ : signal(false);
55753
+ _isDarkTheme = signal(true, ...(ngDevMode ? [{ debugName: "_isDarkTheme" }] : /* istanbul ignore next */ []));
55754
+ set isDarkTheme(val) {
55755
+ this._isDarkTheme.set(!!val);
55756
+ this.cdr.markForCheck();
55757
+ }
55758
+ get isDarkTheme() {
55759
+ return this._isDarkTheme();
55760
+ }
55761
+ showSearch = true;
55762
+ searchPlaceholder = 'Buscar no sistema ou executar comando...';
55763
+ _footerStatus = signal(undefined, ...(ngDevMode ? [{ debugName: "_footerStatus" }] : /* istanbul ignore next */ []));
55764
+ set footerStatus(val) {
55765
+ this._footerStatus.set(val);
55766
+ this.cdr.markForCheck();
55767
+ }
55768
+ get footerStatus() {
55769
+ return this._footerStatus();
55770
+ }
55771
+ _isAnyLoading = signal(false, ...(ngDevMode ? [{ debugName: "_isAnyLoading" }] : /* istanbul ignore next */ []));
55772
+ set isAnyLoading(val) {
55773
+ this._isAnyLoading.set(!!val);
55774
+ this.cdr.markForCheck();
55775
+ }
55776
+ get isAnyLoading() {
55777
+ return this._isAnyLoading();
55778
+ }
55779
+ _collapsed = signal(false, ...(ngDevMode ? [{ debugName: "_collapsed" }] : /* istanbul ignore next */ []));
55780
+ set collapsed(val) {
55781
+ this._collapsed.set(!!val);
55782
+ this.cdr.markForCheck();
55783
+ }
55784
+ get collapsed() {
55785
+ return this._collapsed();
55786
+ }
55787
+ themeToggle = new EventEmitter();
55788
+ searchSubmit = new EventEmitter();
55789
+ searchChange = new EventEmitter();
55790
+ userProfileClick = new EventEmitter();
55791
+ collapsedChange = new EventEmitter();
55792
+ navItemClick = new EventEmitter();
55793
+ mobileOpen = signal(false, ...(ngDevMode ? [{ debugName: "mobileOpen" }] : /* istanbul ignore next */ []));
55794
+ searchQuery = signal('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : /* istanbul ignore next */ []));
55795
+ effectiveLoading = computed(() => {
55796
+ return this._isAnyLoading() || this.orchestratorLoading();
55797
+ }, ...(ngDevMode ? [{ debugName: "effectiveLoading" }] : /* istanbul ignore next */ []));
55798
+ handleEscape() {
55799
+ if (this.mobileOpen()) {
55800
+ this.closeMobile();
55801
+ }
55802
+ }
55803
+ toggleCollapse() {
55804
+ this.collapsed = !this.collapsed;
55805
+ this.collapsedChange.emit(this.collapsed);
55806
+ }
55807
+ openMobile() {
55808
+ this.mobileOpen.set(true);
55809
+ }
55810
+ closeMobile() {
55811
+ this.mobileOpen.set(false);
55812
+ }
55813
+ onThemeToggleClick() {
55814
+ this.isDarkTheme = !this.isDarkTheme;
55815
+ this.themeToggle.emit(this.isDarkTheme);
55816
+ }
55817
+ onSearchInput(value) {
55818
+ this.searchQuery.set(value);
55819
+ this.searchChange.emit(value);
55820
+ }
55821
+ onSearchEnter() {
55822
+ const query = this.searchQuery().trim();
55823
+ if (query) {
55824
+ this.searchSubmit.emit(query);
55825
+ }
55826
+ }
55827
+ onUserClick() {
55828
+ if (this.user) {
55829
+ this.userProfileClick.emit(this.user);
55830
+ }
55831
+ }
55832
+ onItemClick(item) {
55833
+ if (item.disabled)
55834
+ return;
55835
+ this.closeMobile();
55836
+ this.navItemClick.emit(item);
55837
+ }
55838
+ getUserInitials(name) {
55839
+ if (!name)
55840
+ return 'U';
55841
+ const parts = name.trim().split(/\s+/);
55842
+ if (parts.length === 1)
55843
+ return parts[0].substring(0, 2).toUpperCase();
55844
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
55845
+ }
55846
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisAppShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
55847
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisAppShellComponent, isStandalone: true, selector: "praxis-app-shell", inputs: { brand: "brand", navigation: "navigation", user: "user", showThemeToggle: "showThemeToggle", isDarkTheme: "isDarkTheme", showSearch: "showSearch", searchPlaceholder: "searchPlaceholder", footerStatus: "footerStatus", isAnyLoading: "isAnyLoading", collapsed: "collapsed" }, outputs: { themeToggle: "themeToggle", searchSubmit: "searchSubmit", searchChange: "searchChange", userProfileClick: "userProfileClick", collapsedChange: "collapsedChange", navItemClick: "navItemClick" }, host: { listeners: { "keydown.escape": "handleEscape()" } }, ngImport: i0, template: `
55848
+ <div
55849
+ class="praxis-shell"
55850
+ [class.praxis-shell--dark]="isDarkTheme"
55851
+ [class.praxis-shell--collapsed]="collapsed"
55852
+ [class.praxis-shell--mobile-open]="mobileOpen()"
55853
+ >
55854
+ <!-- A11y Skip to Main Content Link -->
55855
+ <a href="#praxis-main-content" class="praxis-shell__skip-link">
55856
+ Pular para o conteúdo principal
55857
+ </a>
55858
+
55859
+ <!-- Tactical Top Shimmer Progress Bar -->
55860
+ @if (effectiveLoading()) {
55861
+ <div
55862
+ class="praxis-shell__progress-bar"
55863
+ role="progressbar"
55864
+ aria-valuemin="0"
55865
+ aria-valuemax="100"
55866
+ aria-label="Sincronização em andamento"
55867
+ >
55868
+ <div class="praxis-shell__progress-laser"></div>
55869
+ </div>
55870
+ }
55871
+
55872
+ <!-- Mobile Backdrop -->
55873
+ @if (mobileOpen()) {
55874
+ <div
55875
+ class="praxis-shell__backdrop"
55876
+ (click)="closeMobile()"
55877
+ aria-hidden="true"
55878
+ ></div>
55879
+ }
55880
+
55881
+ <!-- Sidebar -->
55882
+ <aside class="praxis-shell__sidebar" id="praxis-sidebar-nav" aria-label="Navegação Principal">
55883
+ <!-- Brand Header -->
55884
+ <div class="praxis-shell__brand">
55885
+ <a
55886
+ class="praxis-shell__brand-link"
55887
+ [routerLink]="brand?.href || '/'"
55888
+ (click)="closeMobile()"
55889
+ >
55890
+ @if (brand?.logoUrl) {
55891
+ <img
55892
+ [src]="brand?.logoUrl"
55893
+ [alt]="brand?.title || 'Logo'"
55894
+ class="praxis-shell__brand-logo"
55895
+ />
55896
+ } @else {
55897
+ <div class="praxis-shell__brand-icon">
55898
+ <span class="material-symbols-outlined">{{ brand?.icon || 'apps' }}</span>
55899
+ </div>
55900
+ }
55901
+
55902
+ @if (!collapsed) {
55903
+ <div class="praxis-shell__brand-info">
55904
+ <span class="praxis-shell__brand-title">{{ brand?.title || 'Praxis' }}</span>
55905
+ @if (brand?.version || brand?.subtitle) {
55906
+ <span class="praxis-shell__brand-meta">
55907
+ {{ brand?.version || brand?.subtitle }}
55908
+ </span>
55909
+ }
55910
+ </div>
55911
+ }
55912
+ </a>
55913
+
55914
+ <ng-content select="[shell-brand-extra]"></ng-content>
55915
+
55916
+ <!-- Mobile Close Button -->
55917
+ <button
55918
+ type="button"
55919
+ class="praxis-shell__mobile-close-btn"
55920
+ (click)="closeMobile()"
55921
+ aria-label="Fechar navegação"
55922
+ >
55923
+ <span class="material-symbols-outlined">close</span>
55924
+ </button>
55925
+ </div>
55926
+
55927
+ <!-- Navigation Groups -->
55928
+ <nav class="praxis-shell__nav">
55929
+ @for (group of navigation; track group.label || $index) {
55930
+ <div class="praxis-shell__nav-group">
55931
+ @if (group.label && !collapsed) {
55932
+ <div class="praxis-shell__nav-group-title">{{ group.label }}</div>
55933
+ }
55934
+ <ul class="praxis-shell__nav-list">
55935
+ @for (item of group.items; track item.path) {
55936
+ <li class="praxis-shell__nav-item">
55937
+ <a
55938
+ [routerLink]="item.path"
55939
+ routerLinkActive="active"
55940
+ [routerLinkActiveOptions]="{ exact: item.exact ?? (item.path === '/') }"
55941
+ (click)="onItemClick(item)"
55942
+ class="praxis-shell__nav-link"
55943
+ [class.disabled]="item.disabled"
55944
+ [attr.title]="collapsed ? item.label : (item.title || null)"
55945
+ [attr.aria-disabled]="item.disabled ? 'true' : null"
55946
+ >
55947
+ <span class="praxis-shell__nav-icon">
55948
+ <span class="material-symbols-outlined">{{ item.icon || 'circle' }}</span>
55949
+ </span>
55950
+
55951
+ @if (!collapsed) {
55952
+ <span class="praxis-shell__nav-label">{{ item.label }}</span>
55953
+
55954
+ @if (item.badge !== undefined && item.badge !== null) {
55955
+ <span
55956
+ class="praxis-shell__nav-badge"
55957
+ [ngClass]="'praxis-shell__nav-badge--' + (item.badgeTone || 'primary')"
55958
+ >
55959
+ {{ item.badge }}
55960
+ </span>
55961
+ }
55962
+ }
55963
+ </a>
55964
+ </li>
55965
+ }
55966
+ </ul>
55967
+ </div>
55968
+ }
55969
+ </nav>
55970
+
55971
+ <!-- Sidebar Footer Status -->
55972
+ @if (footerStatus) {
55973
+ <div class="praxis-shell__sidebar-footer-status">
55974
+ @if (!collapsed) {
55975
+ <div
55976
+ class="praxis-shell__status-card"
55977
+ [ngClass]="'praxis-shell__status-card--' + (footerStatus.statusTone || 'info')"
55978
+ >
55979
+ <div class="praxis-shell__status-card-header">
55980
+ @if (footerStatus.pulse !== false) {
55981
+ <span class="praxis-shell__pulse-indicator">
55982
+ <span class="praxis-shell__pulse-ring"></span>
55983
+ <span class="praxis-shell__pulse-dot"></span>
55984
+ </span>
55985
+ }
55986
+ <span class="praxis-shell__status-card-title">{{ footerStatus.title }}</span>
55987
+ </div>
55988
+ @if (footerStatus.metric) {
55989
+ <div class="praxis-shell__status-card-metric">{{ footerStatus.metric }}</div>
55990
+ }
55991
+ </div>
55992
+ }
55993
+ </div>
55994
+ }
55995
+
55996
+ <ng-content select="[shell-footer-extra]"></ng-content>
55997
+
55998
+ <!-- Sidebar Collapse Toggle Button -->
55999
+ <div class="praxis-shell__sidebar-toggle-bar">
56000
+ <button
56001
+ type="button"
56002
+ class="praxis-shell__collapse-btn"
56003
+ (click)="toggleCollapse()"
56004
+ [attr.aria-expanded]="!collapsed"
56005
+ [attr.aria-label]="collapsed ? 'Expandir barra lateral' : 'Recolher barra lateral'"
56006
+ >
56007
+ <span class="material-symbols-outlined">
56008
+ {{ collapsed ? 'chevron_right' : 'chevron_left' }}
56009
+ </span>
56010
+ @if (!collapsed) {
56011
+ <span class="praxis-shell__collapse-btn-text">Recolher menu</span>
56012
+ }
56013
+ </button>
56014
+ </div>
56015
+ </aside>
56016
+
56017
+ <!-- Main Stage Container -->
56018
+ <div class="praxis-shell__stage">
56019
+ <!-- Top Application Bar -->
56020
+ <header class="praxis-shell__topbar" role="banner">
56021
+ <!-- Mobile Menu Trigger -->
56022
+ <button
56023
+ type="button"
56024
+ class="praxis-shell__burger-btn"
56025
+ (click)="openMobile()"
56026
+ [attr.aria-expanded]="mobileOpen()"
56027
+ aria-controls="praxis-sidebar-nav"
56028
+ aria-label="Abrir navegação"
56029
+ >
56030
+ <span class="material-symbols-outlined">menu</span>
56031
+ </button>
56032
+
56033
+ <!-- Search / Quick Command Area -->
56034
+ @if (showSearch) {
56035
+ <div class="praxis-shell__search-box" role="search">
56036
+ <span class="material-symbols-outlined praxis-shell__search-icon" aria-hidden="true">search</span>
56037
+ <input
56038
+ type="text"
56039
+ class="praxis-shell__search-input"
56040
+ [placeholder]="searchPlaceholder"
56041
+ [value]="searchQuery()"
56042
+ (input)="onSearchInput($any($event.target).value)"
56043
+ (keydown.enter)="onSearchEnter()"
56044
+ aria-label="Buscar no sistema"
56045
+ />
56046
+ </div>
56047
+ }
56048
+
56049
+ <!-- Spacer -->
56050
+ <div class="praxis-shell__topbar-spacer"></div>
56051
+
56052
+ <!-- Header Actions Content Slot -->
56053
+ <div class="praxis-shell__header-actions">
56054
+ <ng-content select="[shell-header-actions]"></ng-content>
56055
+ </div>
56056
+
56057
+ <!-- Theme Toggle -->
56058
+ @if (showThemeToggle) {
56059
+ <button
56060
+ type="button"
56061
+ class="praxis-shell__icon-btn"
56062
+ (click)="onThemeToggleClick()"
56063
+ [attr.aria-label]="isDarkTheme ? 'Ativar modo claro' : 'Ativar modo escuro'"
56064
+ [attr.title]="isDarkTheme ? 'Modo Claro' : 'Modo Escuro'"
56065
+ >
56066
+ <span class="material-symbols-outlined">
56067
+ {{ isDarkTheme ? 'light_mode' : 'dark_mode' }}
56068
+ </span>
56069
+ </button>
56070
+ }
56071
+
56072
+ <!-- User Profile Badge -->
56073
+ @if (user) {
56074
+ <div
56075
+ class="praxis-shell__user-profile"
56076
+ (click)="onUserClick()"
56077
+ role="button"
56078
+ tabindex="0"
56079
+ (keydown.enter)="onUserClick()"
56080
+ (keydown.space)="onUserClick()"
56081
+ [attr.aria-label]="'Perfil de ' + user.name"
56082
+ >
56083
+ @if (user.avatarUrl) {
56084
+ <img [src]="user.avatarUrl" [alt]="user.name" class="praxis-shell__user-avatar" />
56085
+ } @else {
56086
+ <div class="praxis-shell__user-initials">
56087
+ {{ user.initials || getUserInitials(user.name) }}
56088
+ </div>
56089
+ }
56090
+
56091
+ <div class="praxis-shell__user-info">
56092
+ <span class="praxis-shell__user-name">{{ user.name }}</span>
56093
+ @if (user.role) {
56094
+ <span class="praxis-shell__user-role">{{ user.role }}</span>
56095
+ }
56096
+ </div>
56097
+ </div>
56098
+ }
56099
+ </header>
56100
+
56101
+ <!-- Main Viewport Content Area -->
56102
+ <main class="praxis-shell__main" id="praxis-main-content">
56103
+ <ng-content></ng-content>
56104
+ </main>
56105
+ </div>
56106
+ </div>
56107
+ `, isInline: true, styles: [":host{display:block;width:100%;height:100vh;overflow:hidden;font-family:inherit}.praxis-shell{display:flex;width:100%;height:100%;background:var(--background, #090d16);color:var(--foreground, #f0f4f8);position:relative;overflow:hidden;box-sizing:border-box}.praxis-shell__skip-link{position:absolute;top:-100px;left:16px;background:var(--primary, #0ea5e9);color:#fff;padding:8px 16px;border-radius:6px;font-weight:600;font-size:.875rem;z-index:9999;text-decoration:none;transition:top .2s ease}.praxis-shell__skip-link:focus{top:16px;outline:2px solid #ffffff;outline-offset:2px}.praxis-shell__progress-bar{position:absolute;top:0;left:0;right:0;height:3px;background:#38bdf826;z-index:1000;overflow:hidden}.praxis-shell__progress-laser{width:40%;height:100%;background:linear-gradient(90deg,transparent,var(--primary, #0ea5e9),#38bdf8,transparent);box-shadow:0 0 10px #38bdf8;animation:praxis-laser-slide 1.4s infinite cubic-bezier(.4,0,.2,1)}@keyframes praxis-laser-slide{0%{transform:translate(-150%)}to{transform:translate(350%)}}.praxis-shell__backdrop{position:fixed;inset:0;background:#0000008c;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);z-index:200;animation:praxis-fade-in .2s ease}@keyframes praxis-fade-in{0%{opacity:0}to{opacity:1}}.praxis-shell__sidebar{width:260px;min-width:260px;height:100%;background:var(--card, #0f172a);border-right:1px solid var(--border, rgba(255, 255, 255, .08));display:flex;flex-direction:column;transition:width .25s cubic-bezier(.16,1,.3,1),transform .25s cubic-bezier(.16,1,.3,1);z-index:250;overflow:hidden}.praxis-shell--collapsed .praxis-shell__sidebar{width:72px;min-width:72px}.praxis-shell__brand{height:64px;padding:0 16px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--border, rgba(255, 255, 255, .06))}.praxis-shell__brand-link{display:flex;align-items:center;gap:12px;text-decoration:none;color:inherit;min-width:0}.praxis-shell__brand-icon{width:40px;height:40px;border-radius:10px;background:linear-gradient(135deg,var(--primary, #0ea5e9),#6366f1);color:#fff;display:flex;align-items:center;justify-content:center;flex-shrink:0;box-shadow:0 4px 12px #0ea5e940}.praxis-shell__brand-logo{height:36px;max-width:140px;object-fit:contain}.praxis-shell__brand-info{display:flex;flex-direction:column;overflow:hidden}.praxis-shell__brand-title{font-weight:700;font-size:.95rem;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;letter-spacing:-.01em}.praxis-shell__brand-meta{font-size:.72rem;color:var(--muted-foreground, #94a3b8);white-space:nowrap}.praxis-shell__mobile-close-btn{display:none;background:transparent;border:none;color:var(--muted-foreground, #94a3b8);cursor:pointer;padding:6px;border-radius:6px}.praxis-shell__nav{flex:1;padding:16px 10px;overflow-y:auto;overflow-x:hidden}.praxis-shell__nav-group{margin-bottom:20px}.praxis-shell__nav-group-title{padding:0 10px 8px;font-size:.7rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--muted-foreground, #64748b)}.praxis-shell__nav-list{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:4px}.praxis-shell__nav-item{margin:0}.praxis-shell__nav-link{display:flex;align-items:center;gap:12px;padding:9px 12px;border-radius:9px;color:var(--muted-foreground, #94a3b8);text-decoration:none;font-size:.88rem;font-weight:500;transition:background .15s ease,color .15s ease;cursor:pointer;white-space:nowrap}.praxis-shell__nav-link:hover{background:#ffffff0d;color:var(--foreground, #f8fafc)}.praxis-shell__nav-link.active{background:color-mix(in oklab,var(--primary, #0ea5e9) 16%,transparent);color:var(--primary, #38bdf8);font-weight:600}.praxis-shell__nav-icon{display:flex;align-items:center;justify-content:center;width:24px;height:24px;flex-shrink:0}.praxis-shell__nav-icon .material-symbols-outlined{font-size:20px}.praxis-shell__nav-label{flex:1;overflow:hidden;text-overflow:ellipsis}.praxis-shell__nav-badge{font-size:.7rem;font-weight:700;padding:2px 7px;border-radius:9999px;line-height:1}.praxis-shell__nav-badge--primary{background:var(--primary, #0ea5e9);color:#fff}.praxis-shell__nav-badge--success{background:#10b981;color:#fff}.praxis-shell__nav-badge--warning{background:#f59e0b;color:#fff}.praxis-shell__nav-badge--error{background:#ef4444;color:#fff}.praxis-shell__nav-badge--info{background:#3b82f6;color:#fff}.praxis-shell__nav-badge--neutral{background:#64748b;color:#fff}.praxis-shell__sidebar-footer-status{padding:10px}.praxis-shell__status-card{padding:10px 12px;border-radius:10px;background:#ffffff08;border:1px solid var(--border, rgba(255, 255, 255, .07))}.praxis-shell__status-card--success{border-color:#10b9814d}.praxis-shell__status-card--warning{border-color:#f59e0b4d}.praxis-shell__status-card--error{border-color:#ef44444d}.praxis-shell__status-card-header{display:flex;align-items:center;gap:8px;font-size:.75rem;font-weight:600}.praxis-shell__pulse-indicator{position:relative;display:inline-flex;width:8px;height:8px}.praxis-shell__pulse-ring{position:absolute;inset:-2px;border-radius:9999px;background:#10b981;opacity:.6;animation:praxis-pulse 1.8s cubic-bezier(0,0,.2,1) infinite}.praxis-shell__pulse-dot{width:8px;height:8px;border-radius:9999px;background:#10b981}@keyframes praxis-pulse{0%{transform:scale(.9);opacity:.8}70%{transform:scale(2.2);opacity:0}to{transform:scale(2.2);opacity:0}}.praxis-shell__status-card-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.praxis-shell__status-card-metric{font-size:.7rem;color:var(--muted-foreground, #94a3b8);margin-top:4px}.praxis-shell__sidebar-toggle-bar{padding:10px;border-top:1px solid var(--border, rgba(255, 255, 255, .06))}.praxis-shell__collapse-btn{display:flex;align-items:center;gap:10px;width:100%;height:38px;padding:0 10px;border-radius:8px;background:transparent;border:none;color:var(--muted-foreground, #94a3b8);cursor:pointer;font-size:.85rem;font-weight:500;transition:background .15s ease,color .15s ease}.praxis-shell__collapse-btn:hover{background:#ffffff0d;color:var(--foreground, #f8fafc)}.praxis-shell__stage{flex:1;min-width:0;display:flex;flex-direction:column;height:100%;overflow:hidden}.praxis-shell__topbar{height:64px;padding:0 24px;background:var(--card, #0f172a);border-bottom:1px solid var(--border, rgba(255, 255, 255, .08));display:flex;align-items:center;gap:16px;flex-shrink:0;z-index:100}.praxis-shell__burger-btn{display:none;background:transparent;border:none;color:var(--foreground, #f8fafc);cursor:pointer;padding:8px;border-radius:8px}.praxis-shell__search-box{display:flex;align-items:center;gap:10px;width:320px;max-width:100%;height:38px;padding:0 12px;background:#ffffff0a;border:1px solid var(--border, rgba(255, 255, 255, .08));border-radius:9px;color:var(--muted-foreground, #94a3b8);transition:border-color .15s ease,box-shadow .15s ease}.praxis-shell__search-box:focus-within{border-color:var(--primary, #0ea5e9);box-shadow:0 0 0 2px #0ea5e933;color:var(--foreground, #f8fafc)}.praxis-shell__search-icon{font-size:18px}.praxis-shell__search-input{flex:1;background:transparent;border:none;outline:none;color:inherit;font-size:.85rem}.praxis-shell__topbar-spacer{flex:1}.praxis-shell__header-actions{display:flex;align-items:center;gap:8px}.praxis-shell__icon-btn{display:flex;align-items:center;justify-content:center;width:38px;height:38px;border-radius:9px;background:transparent;border:1px solid var(--border, rgba(255, 255, 255, .08));color:var(--muted-foreground, #94a3b8);cursor:pointer;transition:all .15s ease}.praxis-shell__icon-btn:hover{background:#ffffff0f;color:var(--foreground, #f8fafc);border-color:#ffffff26}.praxis-shell__user-profile{display:flex;align-items:center;gap:10px;padding:5px 10px 5px 6px;border-radius:9999px;background:#ffffff08;border:1px solid var(--border, rgba(255, 255, 255, .08));cursor:pointer;transition:all .15s ease;-webkit-user-select:none;user-select:none}.praxis-shell__user-profile:hover{background:#ffffff12;border-color:#ffffff29}.praxis-shell__user-avatar{width:28px;height:28px;border-radius:9999px;object-fit:cover}.praxis-shell__user-initials{width:28px;height:28px;border-radius:9999px;background:var(--primary, #0ea5e9);color:#fff;display:flex;align-items:center;justify-content:center;font-size:.75rem;font-weight:700}.praxis-shell__user-info{display:flex;flex-direction:column;line-height:1.2}.praxis-shell__user-name{font-size:.82rem;font-weight:600;color:var(--foreground, #f8fafc)}.praxis-shell__user-role{font-size:.68rem;color:var(--muted-foreground, #94a3b8)}.praxis-shell__main{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;padding:24px;box-sizing:border-box;background:var(--background, #090d16)}@media(max-width:900px){.praxis-shell__burger-btn,.praxis-shell__mobile-close-btn{display:flex}.praxis-shell__sidebar{position:fixed;top:0;bottom:0;left:0;transform:translate(-100%);width:280px!important;box-shadow:4px 0 24px #00000080}.praxis-shell--mobile-open .praxis-shell__sidebar{transform:translate(0)}.praxis-shell__search-box{width:200px}.praxis-shell__user-info{display:none}}@media(max-width:600px){.praxis-shell__topbar{padding:0 12px}.praxis-shell__search-box{display:none}.praxis-shell__main{padding:16px 12px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
56108
+ }
56109
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisAppShellComponent, decorators: [{
56110
+ type: Component,
56111
+ args: [{ selector: 'praxis-app-shell', standalone: true, imports: [CommonModule, RouterLink, RouterLinkActive], changeDetection: ChangeDetectionStrategy.OnPush, template: `
56112
+ <div
56113
+ class="praxis-shell"
56114
+ [class.praxis-shell--dark]="isDarkTheme"
56115
+ [class.praxis-shell--collapsed]="collapsed"
56116
+ [class.praxis-shell--mobile-open]="mobileOpen()"
56117
+ >
56118
+ <!-- A11y Skip to Main Content Link -->
56119
+ <a href="#praxis-main-content" class="praxis-shell__skip-link">
56120
+ Pular para o conteúdo principal
56121
+ </a>
56122
+
56123
+ <!-- Tactical Top Shimmer Progress Bar -->
56124
+ @if (effectiveLoading()) {
56125
+ <div
56126
+ class="praxis-shell__progress-bar"
56127
+ role="progressbar"
56128
+ aria-valuemin="0"
56129
+ aria-valuemax="100"
56130
+ aria-label="Sincronização em andamento"
56131
+ >
56132
+ <div class="praxis-shell__progress-laser"></div>
56133
+ </div>
56134
+ }
56135
+
56136
+ <!-- Mobile Backdrop -->
56137
+ @if (mobileOpen()) {
56138
+ <div
56139
+ class="praxis-shell__backdrop"
56140
+ (click)="closeMobile()"
56141
+ aria-hidden="true"
56142
+ ></div>
56143
+ }
56144
+
56145
+ <!-- Sidebar -->
56146
+ <aside class="praxis-shell__sidebar" id="praxis-sidebar-nav" aria-label="Navegação Principal">
56147
+ <!-- Brand Header -->
56148
+ <div class="praxis-shell__brand">
56149
+ <a
56150
+ class="praxis-shell__brand-link"
56151
+ [routerLink]="brand?.href || '/'"
56152
+ (click)="closeMobile()"
56153
+ >
56154
+ @if (brand?.logoUrl) {
56155
+ <img
56156
+ [src]="brand?.logoUrl"
56157
+ [alt]="brand?.title || 'Logo'"
56158
+ class="praxis-shell__brand-logo"
56159
+ />
56160
+ } @else {
56161
+ <div class="praxis-shell__brand-icon">
56162
+ <span class="material-symbols-outlined">{{ brand?.icon || 'apps' }}</span>
56163
+ </div>
56164
+ }
56165
+
56166
+ @if (!collapsed) {
56167
+ <div class="praxis-shell__brand-info">
56168
+ <span class="praxis-shell__brand-title">{{ brand?.title || 'Praxis' }}</span>
56169
+ @if (brand?.version || brand?.subtitle) {
56170
+ <span class="praxis-shell__brand-meta">
56171
+ {{ brand?.version || brand?.subtitle }}
56172
+ </span>
56173
+ }
56174
+ </div>
56175
+ }
56176
+ </a>
56177
+
56178
+ <ng-content select="[shell-brand-extra]"></ng-content>
56179
+
56180
+ <!-- Mobile Close Button -->
56181
+ <button
56182
+ type="button"
56183
+ class="praxis-shell__mobile-close-btn"
56184
+ (click)="closeMobile()"
56185
+ aria-label="Fechar navegação"
56186
+ >
56187
+ <span class="material-symbols-outlined">close</span>
56188
+ </button>
56189
+ </div>
56190
+
56191
+ <!-- Navigation Groups -->
56192
+ <nav class="praxis-shell__nav">
56193
+ @for (group of navigation; track group.label || $index) {
56194
+ <div class="praxis-shell__nav-group">
56195
+ @if (group.label && !collapsed) {
56196
+ <div class="praxis-shell__nav-group-title">{{ group.label }}</div>
56197
+ }
56198
+ <ul class="praxis-shell__nav-list">
56199
+ @for (item of group.items; track item.path) {
56200
+ <li class="praxis-shell__nav-item">
56201
+ <a
56202
+ [routerLink]="item.path"
56203
+ routerLinkActive="active"
56204
+ [routerLinkActiveOptions]="{ exact: item.exact ?? (item.path === '/') }"
56205
+ (click)="onItemClick(item)"
56206
+ class="praxis-shell__nav-link"
56207
+ [class.disabled]="item.disabled"
56208
+ [attr.title]="collapsed ? item.label : (item.title || null)"
56209
+ [attr.aria-disabled]="item.disabled ? 'true' : null"
56210
+ >
56211
+ <span class="praxis-shell__nav-icon">
56212
+ <span class="material-symbols-outlined">{{ item.icon || 'circle' }}</span>
56213
+ </span>
56214
+
56215
+ @if (!collapsed) {
56216
+ <span class="praxis-shell__nav-label">{{ item.label }}</span>
56217
+
56218
+ @if (item.badge !== undefined && item.badge !== null) {
56219
+ <span
56220
+ class="praxis-shell__nav-badge"
56221
+ [ngClass]="'praxis-shell__nav-badge--' + (item.badgeTone || 'primary')"
56222
+ >
56223
+ {{ item.badge }}
56224
+ </span>
56225
+ }
56226
+ }
56227
+ </a>
56228
+ </li>
56229
+ }
56230
+ </ul>
56231
+ </div>
56232
+ }
56233
+ </nav>
56234
+
56235
+ <!-- Sidebar Footer Status -->
56236
+ @if (footerStatus) {
56237
+ <div class="praxis-shell__sidebar-footer-status">
56238
+ @if (!collapsed) {
56239
+ <div
56240
+ class="praxis-shell__status-card"
56241
+ [ngClass]="'praxis-shell__status-card--' + (footerStatus.statusTone || 'info')"
56242
+ >
56243
+ <div class="praxis-shell__status-card-header">
56244
+ @if (footerStatus.pulse !== false) {
56245
+ <span class="praxis-shell__pulse-indicator">
56246
+ <span class="praxis-shell__pulse-ring"></span>
56247
+ <span class="praxis-shell__pulse-dot"></span>
56248
+ </span>
56249
+ }
56250
+ <span class="praxis-shell__status-card-title">{{ footerStatus.title }}</span>
56251
+ </div>
56252
+ @if (footerStatus.metric) {
56253
+ <div class="praxis-shell__status-card-metric">{{ footerStatus.metric }}</div>
56254
+ }
56255
+ </div>
56256
+ }
56257
+ </div>
56258
+ }
56259
+
56260
+ <ng-content select="[shell-footer-extra]"></ng-content>
56261
+
56262
+ <!-- Sidebar Collapse Toggle Button -->
56263
+ <div class="praxis-shell__sidebar-toggle-bar">
56264
+ <button
56265
+ type="button"
56266
+ class="praxis-shell__collapse-btn"
56267
+ (click)="toggleCollapse()"
56268
+ [attr.aria-expanded]="!collapsed"
56269
+ [attr.aria-label]="collapsed ? 'Expandir barra lateral' : 'Recolher barra lateral'"
56270
+ >
56271
+ <span class="material-symbols-outlined">
56272
+ {{ collapsed ? 'chevron_right' : 'chevron_left' }}
56273
+ </span>
56274
+ @if (!collapsed) {
56275
+ <span class="praxis-shell__collapse-btn-text">Recolher menu</span>
56276
+ }
56277
+ </button>
56278
+ </div>
56279
+ </aside>
56280
+
56281
+ <!-- Main Stage Container -->
56282
+ <div class="praxis-shell__stage">
56283
+ <!-- Top Application Bar -->
56284
+ <header class="praxis-shell__topbar" role="banner">
56285
+ <!-- Mobile Menu Trigger -->
56286
+ <button
56287
+ type="button"
56288
+ class="praxis-shell__burger-btn"
56289
+ (click)="openMobile()"
56290
+ [attr.aria-expanded]="mobileOpen()"
56291
+ aria-controls="praxis-sidebar-nav"
56292
+ aria-label="Abrir navegação"
56293
+ >
56294
+ <span class="material-symbols-outlined">menu</span>
56295
+ </button>
56296
+
56297
+ <!-- Search / Quick Command Area -->
56298
+ @if (showSearch) {
56299
+ <div class="praxis-shell__search-box" role="search">
56300
+ <span class="material-symbols-outlined praxis-shell__search-icon" aria-hidden="true">search</span>
56301
+ <input
56302
+ type="text"
56303
+ class="praxis-shell__search-input"
56304
+ [placeholder]="searchPlaceholder"
56305
+ [value]="searchQuery()"
56306
+ (input)="onSearchInput($any($event.target).value)"
56307
+ (keydown.enter)="onSearchEnter()"
56308
+ aria-label="Buscar no sistema"
56309
+ />
56310
+ </div>
56311
+ }
56312
+
56313
+ <!-- Spacer -->
56314
+ <div class="praxis-shell__topbar-spacer"></div>
56315
+
56316
+ <!-- Header Actions Content Slot -->
56317
+ <div class="praxis-shell__header-actions">
56318
+ <ng-content select="[shell-header-actions]"></ng-content>
56319
+ </div>
56320
+
56321
+ <!-- Theme Toggle -->
56322
+ @if (showThemeToggle) {
56323
+ <button
56324
+ type="button"
56325
+ class="praxis-shell__icon-btn"
56326
+ (click)="onThemeToggleClick()"
56327
+ [attr.aria-label]="isDarkTheme ? 'Ativar modo claro' : 'Ativar modo escuro'"
56328
+ [attr.title]="isDarkTheme ? 'Modo Claro' : 'Modo Escuro'"
56329
+ >
56330
+ <span class="material-symbols-outlined">
56331
+ {{ isDarkTheme ? 'light_mode' : 'dark_mode' }}
56332
+ </span>
56333
+ </button>
56334
+ }
56335
+
56336
+ <!-- User Profile Badge -->
56337
+ @if (user) {
56338
+ <div
56339
+ class="praxis-shell__user-profile"
56340
+ (click)="onUserClick()"
56341
+ role="button"
56342
+ tabindex="0"
56343
+ (keydown.enter)="onUserClick()"
56344
+ (keydown.space)="onUserClick()"
56345
+ [attr.aria-label]="'Perfil de ' + user.name"
56346
+ >
56347
+ @if (user.avatarUrl) {
56348
+ <img [src]="user.avatarUrl" [alt]="user.name" class="praxis-shell__user-avatar" />
56349
+ } @else {
56350
+ <div class="praxis-shell__user-initials">
56351
+ {{ user.initials || getUserInitials(user.name) }}
56352
+ </div>
56353
+ }
56354
+
56355
+ <div class="praxis-shell__user-info">
56356
+ <span class="praxis-shell__user-name">{{ user.name }}</span>
56357
+ @if (user.role) {
56358
+ <span class="praxis-shell__user-role">{{ user.role }}</span>
56359
+ }
56360
+ </div>
56361
+ </div>
56362
+ }
56363
+ </header>
56364
+
56365
+ <!-- Main Viewport Content Area -->
56366
+ <main class="praxis-shell__main" id="praxis-main-content">
56367
+ <ng-content></ng-content>
56368
+ </main>
56369
+ </div>
56370
+ </div>
56371
+ `, styles: [":host{display:block;width:100%;height:100vh;overflow:hidden;font-family:inherit}.praxis-shell{display:flex;width:100%;height:100%;background:var(--background, #090d16);color:var(--foreground, #f0f4f8);position:relative;overflow:hidden;box-sizing:border-box}.praxis-shell__skip-link{position:absolute;top:-100px;left:16px;background:var(--primary, #0ea5e9);color:#fff;padding:8px 16px;border-radius:6px;font-weight:600;font-size:.875rem;z-index:9999;text-decoration:none;transition:top .2s ease}.praxis-shell__skip-link:focus{top:16px;outline:2px solid #ffffff;outline-offset:2px}.praxis-shell__progress-bar{position:absolute;top:0;left:0;right:0;height:3px;background:#38bdf826;z-index:1000;overflow:hidden}.praxis-shell__progress-laser{width:40%;height:100%;background:linear-gradient(90deg,transparent,var(--primary, #0ea5e9),#38bdf8,transparent);box-shadow:0 0 10px #38bdf8;animation:praxis-laser-slide 1.4s infinite cubic-bezier(.4,0,.2,1)}@keyframes praxis-laser-slide{0%{transform:translate(-150%)}to{transform:translate(350%)}}.praxis-shell__backdrop{position:fixed;inset:0;background:#0000008c;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);z-index:200;animation:praxis-fade-in .2s ease}@keyframes praxis-fade-in{0%{opacity:0}to{opacity:1}}.praxis-shell__sidebar{width:260px;min-width:260px;height:100%;background:var(--card, #0f172a);border-right:1px solid var(--border, rgba(255, 255, 255, .08));display:flex;flex-direction:column;transition:width .25s cubic-bezier(.16,1,.3,1),transform .25s cubic-bezier(.16,1,.3,1);z-index:250;overflow:hidden}.praxis-shell--collapsed .praxis-shell__sidebar{width:72px;min-width:72px}.praxis-shell__brand{height:64px;padding:0 16px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--border, rgba(255, 255, 255, .06))}.praxis-shell__brand-link{display:flex;align-items:center;gap:12px;text-decoration:none;color:inherit;min-width:0}.praxis-shell__brand-icon{width:40px;height:40px;border-radius:10px;background:linear-gradient(135deg,var(--primary, #0ea5e9),#6366f1);color:#fff;display:flex;align-items:center;justify-content:center;flex-shrink:0;box-shadow:0 4px 12px #0ea5e940}.praxis-shell__brand-logo{height:36px;max-width:140px;object-fit:contain}.praxis-shell__brand-info{display:flex;flex-direction:column;overflow:hidden}.praxis-shell__brand-title{font-weight:700;font-size:.95rem;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;letter-spacing:-.01em}.praxis-shell__brand-meta{font-size:.72rem;color:var(--muted-foreground, #94a3b8);white-space:nowrap}.praxis-shell__mobile-close-btn{display:none;background:transparent;border:none;color:var(--muted-foreground, #94a3b8);cursor:pointer;padding:6px;border-radius:6px}.praxis-shell__nav{flex:1;padding:16px 10px;overflow-y:auto;overflow-x:hidden}.praxis-shell__nav-group{margin-bottom:20px}.praxis-shell__nav-group-title{padding:0 10px 8px;font-size:.7rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--muted-foreground, #64748b)}.praxis-shell__nav-list{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:4px}.praxis-shell__nav-item{margin:0}.praxis-shell__nav-link{display:flex;align-items:center;gap:12px;padding:9px 12px;border-radius:9px;color:var(--muted-foreground, #94a3b8);text-decoration:none;font-size:.88rem;font-weight:500;transition:background .15s ease,color .15s ease;cursor:pointer;white-space:nowrap}.praxis-shell__nav-link:hover{background:#ffffff0d;color:var(--foreground, #f8fafc)}.praxis-shell__nav-link.active{background:color-mix(in oklab,var(--primary, #0ea5e9) 16%,transparent);color:var(--primary, #38bdf8);font-weight:600}.praxis-shell__nav-icon{display:flex;align-items:center;justify-content:center;width:24px;height:24px;flex-shrink:0}.praxis-shell__nav-icon .material-symbols-outlined{font-size:20px}.praxis-shell__nav-label{flex:1;overflow:hidden;text-overflow:ellipsis}.praxis-shell__nav-badge{font-size:.7rem;font-weight:700;padding:2px 7px;border-radius:9999px;line-height:1}.praxis-shell__nav-badge--primary{background:var(--primary, #0ea5e9);color:#fff}.praxis-shell__nav-badge--success{background:#10b981;color:#fff}.praxis-shell__nav-badge--warning{background:#f59e0b;color:#fff}.praxis-shell__nav-badge--error{background:#ef4444;color:#fff}.praxis-shell__nav-badge--info{background:#3b82f6;color:#fff}.praxis-shell__nav-badge--neutral{background:#64748b;color:#fff}.praxis-shell__sidebar-footer-status{padding:10px}.praxis-shell__status-card{padding:10px 12px;border-radius:10px;background:#ffffff08;border:1px solid var(--border, rgba(255, 255, 255, .07))}.praxis-shell__status-card--success{border-color:#10b9814d}.praxis-shell__status-card--warning{border-color:#f59e0b4d}.praxis-shell__status-card--error{border-color:#ef44444d}.praxis-shell__status-card-header{display:flex;align-items:center;gap:8px;font-size:.75rem;font-weight:600}.praxis-shell__pulse-indicator{position:relative;display:inline-flex;width:8px;height:8px}.praxis-shell__pulse-ring{position:absolute;inset:-2px;border-radius:9999px;background:#10b981;opacity:.6;animation:praxis-pulse 1.8s cubic-bezier(0,0,.2,1) infinite}.praxis-shell__pulse-dot{width:8px;height:8px;border-radius:9999px;background:#10b981}@keyframes praxis-pulse{0%{transform:scale(.9);opacity:.8}70%{transform:scale(2.2);opacity:0}to{transform:scale(2.2);opacity:0}}.praxis-shell__status-card-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.praxis-shell__status-card-metric{font-size:.7rem;color:var(--muted-foreground, #94a3b8);margin-top:4px}.praxis-shell__sidebar-toggle-bar{padding:10px;border-top:1px solid var(--border, rgba(255, 255, 255, .06))}.praxis-shell__collapse-btn{display:flex;align-items:center;gap:10px;width:100%;height:38px;padding:0 10px;border-radius:8px;background:transparent;border:none;color:var(--muted-foreground, #94a3b8);cursor:pointer;font-size:.85rem;font-weight:500;transition:background .15s ease,color .15s ease}.praxis-shell__collapse-btn:hover{background:#ffffff0d;color:var(--foreground, #f8fafc)}.praxis-shell__stage{flex:1;min-width:0;display:flex;flex-direction:column;height:100%;overflow:hidden}.praxis-shell__topbar{height:64px;padding:0 24px;background:var(--card, #0f172a);border-bottom:1px solid var(--border, rgba(255, 255, 255, .08));display:flex;align-items:center;gap:16px;flex-shrink:0;z-index:100}.praxis-shell__burger-btn{display:none;background:transparent;border:none;color:var(--foreground, #f8fafc);cursor:pointer;padding:8px;border-radius:8px}.praxis-shell__search-box{display:flex;align-items:center;gap:10px;width:320px;max-width:100%;height:38px;padding:0 12px;background:#ffffff0a;border:1px solid var(--border, rgba(255, 255, 255, .08));border-radius:9px;color:var(--muted-foreground, #94a3b8);transition:border-color .15s ease,box-shadow .15s ease}.praxis-shell__search-box:focus-within{border-color:var(--primary, #0ea5e9);box-shadow:0 0 0 2px #0ea5e933;color:var(--foreground, #f8fafc)}.praxis-shell__search-icon{font-size:18px}.praxis-shell__search-input{flex:1;background:transparent;border:none;outline:none;color:inherit;font-size:.85rem}.praxis-shell__topbar-spacer{flex:1}.praxis-shell__header-actions{display:flex;align-items:center;gap:8px}.praxis-shell__icon-btn{display:flex;align-items:center;justify-content:center;width:38px;height:38px;border-radius:9px;background:transparent;border:1px solid var(--border, rgba(255, 255, 255, .08));color:var(--muted-foreground, #94a3b8);cursor:pointer;transition:all .15s ease}.praxis-shell__icon-btn:hover{background:#ffffff0f;color:var(--foreground, #f8fafc);border-color:#ffffff26}.praxis-shell__user-profile{display:flex;align-items:center;gap:10px;padding:5px 10px 5px 6px;border-radius:9999px;background:#ffffff08;border:1px solid var(--border, rgba(255, 255, 255, .08));cursor:pointer;transition:all .15s ease;-webkit-user-select:none;user-select:none}.praxis-shell__user-profile:hover{background:#ffffff12;border-color:#ffffff29}.praxis-shell__user-avatar{width:28px;height:28px;border-radius:9999px;object-fit:cover}.praxis-shell__user-initials{width:28px;height:28px;border-radius:9999px;background:var(--primary, #0ea5e9);color:#fff;display:flex;align-items:center;justify-content:center;font-size:.75rem;font-weight:700}.praxis-shell__user-info{display:flex;flex-direction:column;line-height:1.2}.praxis-shell__user-name{font-size:.82rem;font-weight:600;color:var(--foreground, #f8fafc)}.praxis-shell__user-role{font-size:.68rem;color:var(--muted-foreground, #94a3b8)}.praxis-shell__main{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;padding:24px;box-sizing:border-box;background:var(--background, #090d16)}@media(max-width:900px){.praxis-shell__burger-btn,.praxis-shell__mobile-close-btn{display:flex}.praxis-shell__sidebar{position:fixed;top:0;bottom:0;left:0;transform:translate(-100%);width:280px!important;box-shadow:4px 0 24px #00000080}.praxis-shell--mobile-open .praxis-shell__sidebar{transform:translate(0)}.praxis-shell__search-box{width:200px}.praxis-shell__user-info{display:none}}@media(max-width:600px){.praxis-shell__topbar{padding:0 12px}.praxis-shell__search-box{display:none}.praxis-shell__main{padding:16px 12px}}\n"] }]
56372
+ }], propDecorators: { brand: [{
56373
+ type: Input
56374
+ }], navigation: [{
56375
+ type: Input
56376
+ }], user: [{
56377
+ type: Input
56378
+ }], showThemeToggle: [{
56379
+ type: Input
56380
+ }], isDarkTheme: [{
56381
+ type: Input
56382
+ }], showSearch: [{
56383
+ type: Input
56384
+ }], searchPlaceholder: [{
56385
+ type: Input
56386
+ }], footerStatus: [{
56387
+ type: Input
56388
+ }], isAnyLoading: [{
56389
+ type: Input
56390
+ }], collapsed: [{
56391
+ type: Input
56392
+ }], themeToggle: [{
56393
+ type: Output
56394
+ }], searchSubmit: [{
56395
+ type: Output
56396
+ }], searchChange: [{
56397
+ type: Output
56398
+ }], userProfileClick: [{
56399
+ type: Output
56400
+ }], collapsedChange: [{
56401
+ type: Output
56402
+ }], navItemClick: [{
56403
+ type: Output
56404
+ }], handleEscape: [{
56405
+ type: HostListener,
56406
+ args: ['keydown.escape']
56407
+ }] } });
56408
+
56409
+ /**
56410
+ * Praxis UI Platform — Canonical Widget Event Bus Models (Issue #3)
56411
+ *
56412
+ * Contratos canônicos de eventos, escopos e fiação (wiring) declarativa
56413
+ * entre widgets heterogêneos (KPIs, Charts, Tabelas, Filtros, Formulários).
56414
+ */
56415
+
53334
56416
  /*
53335
56417
  * Public API Surface of praxis-core
53336
56418
  */
@@ -53339,4 +56421,4 @@ function provideHookWhitelist(allowed) {
53339
56421
  * Generated bundle index. Do not edit.
53340
56422
  */
53341
56423
 
53342
- export { API_CONFIG_STORAGE_OPTIONS, API_URL, ASYNC_CONFIG_STORAGE, AllowedFileTypes, AnalyticsPresentationResolver, AnalyticsSchemaContractService, AnalyticsStatsRequestBuilderService, ApiConfigStorage, ApiEndpoint, BUILTIN_PAGE_LAYOUT_PRESETS, BUILTIN_PAGE_THEME_PRESETS, BUILTIN_SHELL_PRESETS, BUILTIN_WIDGET_SHELL_PRESETS, COMPONENT_METADATA_REGISTRY_I18N_CONFIG, COMPONENT_METADATA_REGISTRY_I18N_NAMESPACE, CONFIG_STORAGE, CONNECTION_STORAGE, ComponentAuthoringManifestProjectionError, ComponentKeyService, ComponentMetadataRegistry, ComponentRuntimeProfileService, CompositionRuntimeFacade, CompositionValidatorService, ConsoleLoggerSink, CrudOperationResolutionService, DEFAULT_FIELD_SELECTOR_CONTROL_TYPE_MAP, DEFAULT_JSON_LOGIC_OPERATORS, DEFAULT_SCHEMA_REQUEST_TIMEOUT_MS, DEFAULT_TABLE_CONFIG, DOMAIN_CATALOG_COMPONENT_CONTEXT_PACK, DOMAIN_CATALOG_CONTEXT_HINT_SCHEMA_VERSION, DYNAMIC_PAGE_AI_CAPABILITIES, DYNAMIC_PAGE_COMPONENT_CONTEXT_PACK, DYNAMIC_PAGE_CONFIG_EDITOR, DYNAMIC_PAGE_SHELL_EDITOR, DefaultLoadingRenderer, DeferredAsyncConfigStorage, DomainCatalogService, DomainKnowledgeService, DomainRuleService, DynamicFormService, DynamicWidgetLoaderDirective, DynamicWidgetPageComponent, EDITORIAL_ALLOWED_CONTENT_FORMATS, EDITORIAL_COMPLIANCE_PRESETS, EDITORIAL_EXTERNAL_LINK_REL, EDITORIAL_FORM_TEMPLATE_CATALOG, EDITORIAL_HTML_ENABLED, EDITORIAL_MARKDOWN_IMAGES_ENABLED, EDITORIAL_SOLUTION_CATALOG, EDITORIAL_SOLUTION_PRESETS, EDITORIAL_THEME_PRESETS, EDITORIAL_WIDGET_CONVENTION_INPUTS, EDITORIAL_WIDGET_TAG, EMPLOYEE_ONBOARDING_EDITORIAL_SOLUTION, EMPLOYEE_ONBOARDING_EDITORIAL_TEMPLATE, EMPLOYEE_ONBOARDING_GUIDED_EDITORIAL_SOLUTION, EMPLOYEE_ONBOARDING_GUIDED_EDITORIAL_TEMPLATE, EVENT_REGISTRATION_EDITORIAL_SOLUTION, EVENT_REGISTRATION_EDITORIAL_TEMPLATE, EmptyStateCardComponent, EnterpriseRuntimeContextService, ErrorMessageService, FIELD_METADATA_CAPABILITIES, FIELD_SELECTOR_REGISTRY_BASE, FIELD_SELECTOR_REGISTRY_DISABLE_DEFAULTS, FIELD_SELECTOR_REGISTRY_OVERRIDES, FORM_HOOKS, FORM_HOOKS_PRESETS, FORM_HOOKS_WHITELIST, FORM_HOOK_RESOLVERS, FieldControlType, FieldDataType, FieldSelectorRegistry, FormHooksRegistry, GLOBAL_ACTION_CATALOG, GLOBAL_ACTION_HANDLERS, GLOBAL_ACTION_UI_SCHEMAS, GLOBAL_ANALYTICS_SERVICE, GLOBAL_API_CLIENT, GLOBAL_CONFIG, GLOBAL_DIALOG_SERVICE, GLOBAL_ROUTE_GUARD_RESOLVER, GLOBAL_SURFACE_SERVICE, GLOBAL_TOAST_SERVICE, GOVERNED_COLOR_PALETTE_OPTIONS, GenericCrudService, GlobalActionService, GlobalConfigService, GovernedColorPaletteService, INLINE_FILTER_ALIAS_TOKENS, INLINE_FILTER_CONTROL_TYPES, INLINE_FILTER_CONTROL_TYPE_SET, INLINE_FILTER_CONTROL_TYPE_VALUES, INLINE_FILTER_TOKEN_TO_BASE_CONTROL_TYPE, INLINE_FILTER_TOKEN_TO_CONTROL_TYPE, IconPickerService, IconPosition, IconSize, LOGGER_LEVEL_BY_ENV, LOGGER_LEVEL_PRIORITY, LoadingOrchestrator, LocalConnectionStorage, LocalStorageAsyncAdapter, LocalStorageCacheAdapter, LocalStorageConfigService, LoggerService, LoggerThrottleTracker, LoggerWarnOnceTracker, MemoryCacheAdapter, NestedPortCatalogService, NestedWidgetConfigAccessor, NumericFormat, OVERLAY_DECIDER_DEBUG, OVERLAY_DECISION_MATRIX, ObservabilityDashboardService, OverlayDeciderService, PRAXIS_ACTION_CONTROL_DEFAULTS, PRAXIS_ACTION_CONTROL_VARS, PRAXIS_COLLECTION_EXPORT_HTTP_OPTIONS, PRAXIS_COLLECTION_EXPORT_PROVIDER, PRAXIS_COLLECTION_SEARCH_DEFAULTS, PRAXIS_COLLECTION_SEARCH_VARS, PRAXIS_CORPORATE_SENSITIVE_KEYS, PRAXIS_DEFAULT_EXPORT_SECURITY_POLICY, PRAXIS_DEFAULT_OBSERVABILITY_ALERT_RULES, PRAXIS_DYNAMIC_PAGE_COMPONENT_METADATA, PRAXIS_ENTERPRISE_RUNTIME_CONTEXT_OPTIONS, PRAXIS_ENTERPRISE_RUNTIME_CONTEXT_READY, PRAXIS_EXPORT_FORMULA_PREFIXES, PRAXIS_EXPORT_SECURITY_POLICY, PRAXIS_FOOTER_LINKS_METADATA, PRAXIS_GLOBAL_ACTION_CATALOG, PRAXIS_GLOBAL_CONFIG_BOOTSTRAP_OPTIONS, PRAXIS_GLOBAL_CONFIG_BOOTSTRAP_READY, PRAXIS_GLOBAL_CONFIG_TENANT_RESOLVER, PRAXIS_HERO_BANNER_METADATA, PRAXIS_I18N_CONFIG, PRAXIS_I18N_TRANSLATOR, PRAXIS_JSON_LOGIC_OPERATORS, PRAXIS_LAYER_SCALE_DEFAULTS, PRAXIS_LAYER_SCALE_VARS, PRAXIS_LEGAL_NOTICE_METADATA, PRAXIS_LOADING_CTX, PRAXIS_LOADING_RENDERER, PRAXIS_LOGGER_CONFIG, PRAXIS_LOGGER_SINKS, PRAXIS_OBSERVABILITY_DASHBOARD_OPTIONS, PRAXIS_QUERY_FILTER_EXPRESSION_SCHEMA_VERSION, PRAXIS_RELATED_RESOURCE_OUTLET_AUTHORING_MANIFEST, PRAXIS_RELATED_RESOURCE_OUTLET_COMPONENT_METADATA, PRAXIS_RELATED_RESOURCE_OUTLET_PORTS, PRAXIS_RICH_TEXT_BLOCK_METADATA, PRAXIS_TABLE_DETAIL_INLINE_NODE_RESOLVERS, PRAXIS_TABLE_DETAIL_INLINE_RENDERERS, PRAXIS_TABLE_DETAIL_RESOURCE_RESOLVER, PRAXIS_TELEMETRY_TRANSPORT, PRAXIS_THEME_SURFACE_DEFAULTS, PRAXIS_THEME_SURFACE_VARS, PRAXIS_USER_CONTEXT_SUMMARY_METADATA, PRIVACY_CONSENT_EDITORIAL_SOLUTION, PRIVACY_CONSENT_EDITORIAL_TEMPLATE, PraxisCollectionExportService, PraxisCore, PraxisFooterLinksComponent, PraxisGlobalErrorHandler, PraxisHeroBannerComponent, PraxisHttpCollectionExportProvider, PraxisI18nService, PraxisIconButtonComponent, PraxisIconDirective, PraxisIconPickerComponent, PraxisJsonLogicError, PraxisJsonLogicService, PraxisLayerScaleStyleService, PraxisLegalNoticeComponent, PraxisLoadingInterceptor, PraxisRelatedResourceOutletComponent, PraxisRelatedResourceOutletConfigEditorComponent, PraxisResourceIdentityComponent, PraxisRichTextBlockComponent, PraxisRuntimeComponentObservationRegistryService, PraxisSurfaceHostComponent, PraxisUserContextSummaryComponent, RESOURCE_DISCOVERY_I18N_CONFIG, RESOURCE_DISCOVERY_I18N_NAMESPACE, RULE_PROPERTY_SCHEMA, RelatedResourceSurfaceResolverService, RemoteConfigStorage, ResourceActionOpenAdapterService, ResourceDiscoveryService, ResourceQuickConnectComponent, ResourceRecordOpenError, ResourceRecordOpenService, ResourceSurfaceOpenAdapterService, SCHEMA_METADATA_CLIENT_OPTIONS, SCHEMA_VIEWER_CONTEXT, SETTINGS_EDITOR_SECTIONS_HOSTED, SETTINGS_PANEL_BRIDGE, SETTINGS_PANEL_DATA, STEPPER_CONFIG_EDITOR, SURFACE_DRAWER_BRIDGE, SURFACE_DRAWER_CONTENT_DATA, SURFACE_DRAWER_REF, SURFACE_NAVIGATION_I18N_CONFIG, SURFACE_NAVIGATION_I18N_NAMESPACE, SURFACE_OPEN_I18N_CONFIG, SURFACE_OPEN_I18N_NAMESPACE, SURFACE_OPEN_PRESETS, SchemaMetadataClient, SchemaNormalizerService, SchemaViewerComponent, SettingsSectionTabsComponent, SurfaceBindingRuntimeService, SurfaceNavigationError, SurfaceOpenActionEditorComponent, SurfaceOpenMaterializerService, SurfaceOutletRegistryService, TABLE_CONFIG_EDITOR, TableConfigService, TelemetryLoggerSink, TelemetryService, ValidationPattern, WidgetPageCompositionFactory, WidgetPageStateRuntimeService, WidgetShellComponent, applyLocalCustomizations$2 as applyLocalCustomizations, applyLocalCustomizations$1 as applyLocalFormCustomizations, assertPraxisCollectionExportArtifact, assertPraxisRuntimeComponentObservationSerializable, buildAngularValidators, buildApiUrl, buildBaseColumnFromDef, buildBaseFormField, buildFormConfigFromEditorialTemplate, buildHeaders, buildPageKey, buildPraxisActionControlCss, buildPraxisCollectionSearchCss, buildPraxisEffectDistinctKey, buildPraxisLayerScaleCss, buildPraxisThemeSurfaceCss, buildSchemaId, buildSchemaIdStorageKeySegment, buildValidatorsFromValidatorOptions, cancelIfCpfInvalidHook, canonicalJsonSha256, canonicalJsonStringify, clampRange, classifyEntityLookupResult, clonePraxisRuntimeComponentObservation, cloneTableConfig, cnpjAlphaValidator, collapseWhitespace, composeHeadersWithVersion, conditionalAsyncValidator, convertFormLayoutToConfig, createCorporateLoggerConfig, createCorporateObservabilityOptions, createCpfCnpjValidator, createDefaultFormConfig, createDefaultTableConfig, createEmptyFormConfig, createEmptyRichContentDocument, createFieldLayoutItem, createPersistedPage, customAsyncValidatorFn, customValidatorFn, debounceAsyncValidator, deepMerge, domainKnowledgeTimelineToRichContentDocument, domainRuleTimelineToRichContentDocument, ensureIds, ensureNoConflictsHookFactory, ensurePageIds, escapePraxisExportCell, evaluateFieldAccess, extractNormalizedError, fetchWithETag, fileTypeValidator, fillUndefined, findBuiltinWidgetShellPreset, generateId, getDefaultFormHints, getEditorialCompliancePresetById, getEditorialFormTemplateById, getEditorialFormTemplateCatalog, getEditorialSolutionById, getEditorialSolutionCatalog, getEditorialSolutionPresetById, getEditorialThemePresetById, getEssentialConfig, getFieldMetadataCapabilities, getFormColumnFieldNames, getFormLayoutFieldNames, getGlobalActionCatalog, getGlobalActionPayloadActualType, getGlobalActionPayloadTypeIssue, getGlobalActionProviderEvidence, getGlobalActionUiSchema, getMissingGlobalActionPayloadKeys, getPraxisTableCellVisualizationConstraint, getPraxisTableCellVisualizationGuidance, getReferencedFieldMetadata, getRequiredGlobalActionPayloadKeys, getTextTransformer, getWidgetShellActionIdError, hasMeaningfulGlobalActionPayloadValue, hasPraxisCollectionExportArtifact, interpolatePraxisTranslation, isAllowedEditorialContentFormat, isAllowedEditorialHref, isCssTextTransform, isDomainRuleSnapshotProblemResponse, isEditorialComponentMeta, isEntityLookupMultiplePayloadMode, isEntityLookupPayloadMode, isEntityLookupPayloadModeCompatible, isEntityLookupResultSelectable, isEntityLookupSinglePayloadMode, isFormLayoutItem, isGlobalActionProviderOperational, isGlobalActionRef, isInlineFilterControlType, isLookupDialogSize, isLookupFilterFieldType, isLookupFilterOperator, isPraxisI18nMessageDescriptor, isPraxisPresentationVisualizationTableSafe, isPraxisRuntimeGlobalActionEffect, isProgrammaticDateRangePreset, isRangeValidForFilter, isRequiredGlobalActionParamPayloadMissing, isRequiredGlobalActionPayloadMissing, isStaticDateRangePreset, isSurfaceNavigationError, isTableConfigV2, isValidFormConfig, isValidTableConfig, legacyCnpjValidator, legacyCpfValidator, logOnErrorHook, mapFieldDefinitionToMetadata, mapFieldDefinitionsToMetadata, markGlobalActionProviderOperational, matchFieldValidator, materializeFormLayoutFromMetadata, materializeGovernedPaletteColors, materializeGovernedPaletteEntries, materializeResourceIdentity, maxFileSizeValidator, mergeFieldMetadata, mergePraxisI18nConfigs, mergeTableConfigs, migrateFormLayoutRule, migrateLegacyCompositionLink, migrateLegacyCompositionLinks, minWordsValidator, nestedPortPathIdentity, normalizeControlTypeKey, normalizeControlTypeToken, normalizeEditorialLink, normalizeEnd, normalizeFieldAccessMetadata, normalizeFieldConstraints, normalizeFieldPresentation, normalizeFormConfig, normalizeFormLayoutItems, normalizeFormMetadata, normalizeGlobalActionRef$1 as normalizeGlobalActionRef, normalizeLayoutPolicy, normalizeLookupFilterRequest, normalizePath, normalizePraxisDataQueryContext, normalizePraxisEffectPolicy, normalizePraxisPresentationVisualization, normalizePraxisQueryFilterExpression, normalizePraxisQueryFilterNode, normalizeReactiveDeterminations, normalizeResourceAvailabilityReasonCode, normalizeResourceIdentityContract, normalizeStart, normalizeSurfaceOperationContext, normalizeUnknownError, normalizeWidgetEventPath, notifySuccessHook, parseJsonResponseOrEmpty, praxisLoadingInterceptorFn, prefillFromContextHook, prepareCanvasArrangement, projectComponentAuthoringManifest, projectGroupedCommandPartialRows, provideDefaultFormHooks, provideFieldSelectorRegistryBase, provideFieldSelectorRegistryOverride, provideFieldSelectorRegistryRuntime, provideFormHookPresets, provideFormHooks, provideGlobalActionCatalog, provideGlobalActionHandler, provideGlobalConfig, provideGlobalConfigReady, provideGlobalConfigSeed, provideGlobalConfigTenant, provideHookResolvers, provideHookWhitelist, provideOverlayDecisionMatrix, providePraxisAnalyticsGlobalActions, providePraxisCollectionExportProvider, providePraxisDynamicPageMetadata, providePraxisEnterpriseRuntimeContext, providePraxisFooterLinksMetadata, providePraxisGlobalActionCatalog, providePraxisGlobalActions, providePraxisGlobalConfigBootstrap, providePraxisHeroBannerMetadata, providePraxisHttpCollectionExportProvider, providePraxisHttpLoading, providePraxisI18n, providePraxisI18nConfig, providePraxisI18nTranslator, providePraxisIconDefaults, providePraxisJsonLogicOperator, providePraxisJsonLogicOperatorOverride, providePraxisLegalNoticeMetadata, providePraxisLoadingDefaults, providePraxisLogging, providePraxisRelatedResourceOutletMetadata, providePraxisRichTextBlockMetadata, providePraxisTelemetry, providePraxisToastGlobalActions, providePraxisUserContextSummaryMetadata, provideRemoteGlobalConfig, readPraxisExportValue, reconcileFilterConfig, reconcileFormConfig, reconcileTableConfig, registerPraxisRuntimeComponentObservation, removeDiacritics, renderPraxisPresentationVisualizationHtml, reportTelemetryHookFactory, requiredCheckedValidator, requiredPresenceValidator, resolveBuiltinPresets, resolveBuiltinWidgetShellAppearance, resolveColumnTypeFromFieldDefinition, resolveControlTypeAlias, resolveDateRangeShortcutPreset, resolveDateRangeShortcutPresets, resolveDefaultValuePresentationFormat, resolveEntityLookupPayloadMode, resolveFieldPresentation, resolveGroupedCommandPartialRowSpans, resolveHidden, resolveInlineFilterControlType, resolveInlineFilterControlTypeToBaseControlType, resolveLoggerConfig, resolveObservabilityOptions, resolveOffset, resolveOrder, resolvePraxisCollectionExportItems, resolvePraxisExportFields, resolvePraxisExportScope, resolvePraxisFilterCriteria, resolvePraxisI18nDocument, resolveResourceAvailabilityReasonKey, resolveResourceIdentityContract, resolveSpan, resolveTextMaskFormat, resolveTextMaskFormatFromFieldDefinition, resolveValuePresentation, resolveValuePresentationLocale, resolveWidgetPageLayoutPreset, serializeEntityLookupValueForPayload, serializeOptionSourceFilterRequest, serializePraxisCollectionToCsv, serializePraxisCollectionToExcel, serializePraxisCollectionToJson, slugify, staticDateRangePresetToPreset, stripMasksHook, supportsConfigDocuments, supportsImplicitValuePresentation, syncWithServerMetadata, textSha256, toCamel, toCapitalize, toKebab, toPascal, toSentenceCase, toSnake, toTitleCase, translateResourceAvailabilityReason, translateResourceDiscoveryText, translateSurfaceNavigationRejected, translateSurfaceOpenFailed, translateUnavailableWorkflowMessage, trim, uniqueAsyncValidator, urlValidator, validateCanvasArrangement, validateGlobalActionRef, validateGlobalActionRefs, withFormConfigSections, withMessage, withPraxisHttpLoading };
56424
+ export { API_CONFIG_STORAGE_OPTIONS, API_URL, ASYNC_CONFIG_STORAGE, AllowedFileTypes, AnalyticsPresentationResolver, AnalyticsSchemaContractService, AnalyticsStatsRequestBuilderService, ApiConfigStorage, ApiEndpoint, BUILTIN_PAGE_LAYOUT_PRESETS, BUILTIN_PAGE_THEME_PRESETS, BUILTIN_SHELL_PRESETS, BUILTIN_WIDGET_SHELL_PRESETS, COMPONENT_METADATA_REGISTRY_I18N_CONFIG, COMPONENT_METADATA_REGISTRY_I18N_NAMESPACE, CONFIG_STORAGE, CONNECTION_STORAGE, ComponentAuthoringManifestProjectionError, ComponentKeyService, ComponentMetadataRegistry, ComponentRuntimeProfileService, CompositionRuntimeFacade, CompositionValidatorService, ConsoleLoggerSink, CrudOperationResolutionService, DEFAULT_DYNAMIC_FORM_SCHEMA_CACHE_MAX_ENTRIES, DEFAULT_DYNAMIC_FORM_SCHEMA_CACHE_TTL_MS, DEFAULT_FIELD_SELECTOR_CONTROL_TYPE_MAP, DEFAULT_JSON_LOGIC_OPERATORS, DEFAULT_SCHEMA_REQUEST_TIMEOUT_MS, DEFAULT_TABLE_CONFIG, DOMAIN_CATALOG_COMPONENT_CONTEXT_PACK, DOMAIN_CATALOG_CONTEXT_HINT_SCHEMA_VERSION, DYNAMIC_PAGE_AI_CAPABILITIES, DYNAMIC_PAGE_COMPONENT_CONTEXT_PACK, DYNAMIC_PAGE_CONFIG_EDITOR, DYNAMIC_PAGE_SHELL_EDITOR, DefaultLoadingRenderer, DeferredAsyncConfigStorage, DomainCatalogService, DomainKnowledgeService, DomainRuleService, DynamicFormService, DynamicWidgetLoaderDirective, DynamicWidgetPageComponent, EDITORIAL_ALLOWED_CONTENT_FORMATS, EDITORIAL_COMPLIANCE_PRESETS, EDITORIAL_EXTERNAL_LINK_REL, EDITORIAL_FORM_TEMPLATE_CATALOG, EDITORIAL_HTML_ENABLED, EDITORIAL_MARKDOWN_IMAGES_ENABLED, EDITORIAL_SOLUTION_CATALOG, EDITORIAL_SOLUTION_PRESETS, EDITORIAL_THEME_PRESETS, EDITORIAL_WIDGET_CONVENTION_INPUTS, EDITORIAL_WIDGET_TAG, EFFECTIVE_UI_LAYOUT_COMPOSITION_SCHEMA_VERSION, EMPLOYEE_ONBOARDING_EDITORIAL_SOLUTION, EMPLOYEE_ONBOARDING_EDITORIAL_TEMPLATE, EMPLOYEE_ONBOARDING_GUIDED_EDITORIAL_SOLUTION, EMPLOYEE_ONBOARDING_GUIDED_EDITORIAL_TEMPLATE, EVENT_REGISTRATION_EDITORIAL_SOLUTION, EVENT_REGISTRATION_EDITORIAL_TEMPLATE, EffectiveUiLayoutContractError, EffectiveUiLayoutHttpError, EffectiveUiLayoutService, EmptyStateCardComponent, EnterpriseRuntimeContextObsoleteError, EnterpriseRuntimeContextService, ErrorMessageService, FIELD_METADATA_CAPABILITIES, FIELD_SELECTOR_REGISTRY_BASE, FIELD_SELECTOR_REGISTRY_DISABLE_DEFAULTS, FIELD_SELECTOR_REGISTRY_OVERRIDES, FORM_HOOKS, FORM_HOOKS_PRESETS, FORM_HOOKS_WHITELIST, FORM_HOOK_RESOLVERS, FieldControlType, FieldDataType, FieldSelectorRegistry, FormHooksRegistry, GLOBAL_ACTION_CATALOG, GLOBAL_ACTION_HANDLERS, GLOBAL_ACTION_UI_SCHEMAS, GLOBAL_ANALYTICS_SERVICE, GLOBAL_API_CLIENT, GLOBAL_CONFIG, GLOBAL_DIALOG_SERVICE, GLOBAL_ROUTE_GUARD_RESOLVER, GLOBAL_SURFACE_SERVICE, GLOBAL_TOAST_SERVICE, GOVERNED_COLOR_PALETTE_OPTIONS, GenericCrudService, GlobalActionService, GlobalConfigService, GovernedColorPaletteService, INLINE_FILTER_ALIAS_TOKENS, INLINE_FILTER_CONTROL_TYPES, INLINE_FILTER_CONTROL_TYPE_SET, INLINE_FILTER_CONTROL_TYPE_VALUES, INLINE_FILTER_TOKEN_TO_BASE_CONTROL_TYPE, INLINE_FILTER_TOKEN_TO_CONTROL_TYPE, IconPickerService, IconPosition, IconSize, LOGGER_LEVEL_BY_ENV, LOGGER_LEVEL_PRIORITY, LoadingOrchestrator, LocalConnectionStorage, LocalStorageAsyncAdapter, LocalStorageCacheAdapter, LocalStorageConfigService, LoggerService, LoggerThrottleTracker, LoggerWarnOnceTracker, MemoryCacheAdapter, NestedPortCatalogService, NestedWidgetConfigAccessor, NumericFormat, OVERLAY_DECIDER_DEBUG, OVERLAY_DECISION_MATRIX, ObservabilityDashboardService, OverlayDeciderService, PRAXIS_ACTION_CONTROL_DEFAULTS, PRAXIS_ACTION_CONTROL_VARS, PRAXIS_COLLECTION_EXPORT_HTTP_OPTIONS, PRAXIS_COLLECTION_EXPORT_PROVIDER, PRAXIS_COLLECTION_SEARCH_DEFAULTS, PRAXIS_COLLECTION_SEARCH_VARS, PRAXIS_CORPORATE_SENSITIVE_KEYS, PRAXIS_DEFAULT_EXPORT_SECURITY_POLICY, PRAXIS_DEFAULT_OBSERVABILITY_ALERT_RULES, PRAXIS_DYNAMIC_PAGE_COMPONENT_METADATA, PRAXIS_EFFECTIVE_UI_LAYOUT_OPTIONS, PRAXIS_ENTERPRISE_RUNTIME_CONTEXT_OPTIONS, PRAXIS_ENTERPRISE_RUNTIME_CONTEXT_READY, PRAXIS_EXPORT_FORMULA_PREFIXES, PRAXIS_EXPORT_SECURITY_POLICY, PRAXIS_FOOTER_LINKS_METADATA, PRAXIS_GLOBAL_ACTION_CATALOG, PRAXIS_GLOBAL_CONFIG_BOOTSTRAP_OPTIONS, PRAXIS_GLOBAL_CONFIG_BOOTSTRAP_READY, PRAXIS_GLOBAL_CONFIG_TENANT_RESOLVER, PRAXIS_HERO_BANNER_METADATA, PRAXIS_I18N_CONFIG, PRAXIS_I18N_TRANSLATOR, PRAXIS_JSON_LOGIC_OPERATORS, PRAXIS_LAYER_SCALE_DEFAULTS, PRAXIS_LAYER_SCALE_VARS, PRAXIS_LEGAL_NOTICE_METADATA, PRAXIS_LOADING_CTX, PRAXIS_LOADING_RENDERER, PRAXIS_LOGGER_CONFIG, PRAXIS_LOGGER_SINKS, PRAXIS_OBSERVABILITY_DASHBOARD_OPTIONS, PRAXIS_QUERY_FILTER_EXPRESSION_SCHEMA_VERSION, PRAXIS_RELATED_RESOURCE_OUTLET_AUTHORING_MANIFEST, PRAXIS_RELATED_RESOURCE_OUTLET_COMPONENT_METADATA, PRAXIS_RELATED_RESOURCE_OUTLET_PORTS, PRAXIS_RICH_TEXT_BLOCK_METADATA, PRAXIS_TABLE_DETAIL_INLINE_NODE_RESOLVERS, PRAXIS_TABLE_DETAIL_INLINE_RENDERERS, PRAXIS_TABLE_DETAIL_RESOURCE_RESOLVER, PRAXIS_TELEMETRY_TRANSPORT, PRAXIS_THEME_SURFACE_DEFAULTS, PRAXIS_THEME_SURFACE_VARS, PRAXIS_USER_CONTEXT_SUMMARY_METADATA, PRIVACY_CONSENT_EDITORIAL_SOLUTION, PRIVACY_CONSENT_EDITORIAL_TEMPLATE, PraxisAppShellComponent, PraxisCollectionExportService, PraxisCore, PraxisFooterLinksComponent, PraxisGlobalErrorHandler, PraxisHeroBannerComponent, PraxisHttpCollectionExportProvider, PraxisI18nService, PraxisIconButtonComponent, PraxisIconDirective, PraxisIconPickerComponent, PraxisJsonLogicError, PraxisJsonLogicService, PraxisKeepAliveTabDirective, PraxisLayerScaleStyleService, PraxisLegalNoticeComponent, PraxisLoadingInterceptor, PraxisRelatedResourceOutletComponent, PraxisRelatedResourceOutletConfigEditorComponent, PraxisResourceIdentityComponent, PraxisRichTextBlockComponent, PraxisRuntimeComponentObservationRegistryService, PraxisSurfaceHostComponent, PraxisUserContextSummaryComponent, PraxisWidgetEventBus, RESOURCE_DISCOVERY_I18N_CONFIG, RESOURCE_DISCOVERY_I18N_NAMESPACE, RULE_PROPERTY_SCHEMA, RelatedResourceSurfaceResolverService, RemoteConfigStorage, ResourceActionOpenAdapterService, ResourceDiscoveryService, ResourceQuickConnectComponent, ResourceRecordOpenError, ResourceRecordOpenService, ResourceSurfaceOpenAdapterService, SCHEMA_METADATA_CLIENT_OPTIONS, SCHEMA_VIEWER_CONTEXT, SETTINGS_EDITOR_SECTIONS_HOSTED, SETTINGS_PANEL_BRIDGE, SETTINGS_PANEL_DATA, STEPPER_CONFIG_EDITOR, SURFACE_DRAWER_BRIDGE, SURFACE_DRAWER_CONTENT_DATA, SURFACE_DRAWER_REF, SURFACE_NAVIGATION_I18N_CONFIG, SURFACE_NAVIGATION_I18N_NAMESPACE, SURFACE_OPEN_I18N_CONFIG, SURFACE_OPEN_I18N_NAMESPACE, SURFACE_OPEN_PRESETS, SafeExpressionEvaluator, SchemaMetadataClient, SchemaNormalizerService, SchemaViewerComponent, SettingsSectionTabsComponent, SurfaceBindingRuntimeService, SurfaceNavigationError, SurfaceOpenActionEditorComponent, SurfaceOpenMaterializerService, SurfaceOutletRegistryService, TABLE_CONFIG_EDITOR, TableConfigService, TelemetryLoggerSink, TelemetryService, UiLayoutLifecycleContractError, UiLayoutLifecycleHttpError, UiLayoutLifecycleService, ValidationPattern, WidgetPageCompositionFactory, WidgetPageStateRuntimeService, WidgetShellComponent, applyLocalCustomizations$2 as applyLocalCustomizations, applyLocalCustomizations$1 as applyLocalFormCustomizations, assertPraxisCollectionExportArtifact, assertPraxisRuntimeComponentObservationSerializable, buildAngularValidators, buildApiUrl, buildBaseColumnFromDef, buildBaseFormField, buildFormConfigFromEditorialTemplate, buildHeaders, buildPageKey, buildPraxisActionControlCss, buildPraxisCollectionSearchCss, buildPraxisEffectDistinctKey, buildPraxisLayerScaleCss, buildPraxisThemeSurfaceCss, buildSchemaId, buildSchemaIdStorageKeySegment, buildValidatorsFromValidatorOptions, cancelIfCpfInvalidHook, canonicalJsonSha256, canonicalJsonStringify, clampRange, classifyEntityLookupResult, clonePraxisRuntimeComponentObservation, cloneTableConfig, cloneUiLayoutJsonDocument, cnpjAlphaValidator, collapseWhitespace, composeHeadersWithVersion, conditionalAsyncValidator, convertFormLayoutToConfig, createCorporateLoggerConfig, createCorporateObservabilityOptions, createCpfCnpjValidator, createDefaultFormConfig, createDefaultTableConfig, createDynamicRecord, createEmptyFormConfig, createEmptyRichContentDocument, createFieldLayoutItem, createPersistedPage, customAsyncValidatorFn, customValidatorFn, debounceAsyncValidator, deepMerge, domainKnowledgeTimelineToRichContentDocument, domainRuleTimelineToRichContentDocument, ensureIds, ensureNoConflictsHookFactory, ensurePageIds, escapePraxisExportCell, evaluateFieldAccess, extractNormalizedError, extractRecordId, fetchWithETag, fileTypeValidator, fillUndefined, findBuiltinWidgetShellPreset, formIdentityIssues, generateId, getDefaultFormHints, getEditorialCompliancePresetById, getEditorialFormTemplateById, getEditorialFormTemplateCatalog, getEditorialSolutionById, getEditorialSolutionCatalog, getEditorialSolutionPresetById, getEditorialThemePresetById, getEssentialConfig, getFieldMetadataCapabilities, getFormColumnFieldNames, getFormLayoutFieldNames, getGlobalActionCatalog, getGlobalActionPayloadActualType, getGlobalActionPayloadTypeIssue, getGlobalActionProviderEvidence, getGlobalActionUiSchema, getMissingGlobalActionPayloadKeys, getPraxisTableCellVisualizationConstraint, getPraxisTableCellVisualizationGuidance, getReferencedFieldMetadata, getRequiredGlobalActionPayloadKeys, getTextTransformer, getWidgetShellActionIdError, hasMeaningfulGlobalActionPayloadValue, hasPraxisCollectionExportArtifact, interpolatePraxisTranslation, isAllowedEditorialContentFormat, isAllowedEditorialHref, isCssTextTransform, isDomainRuleSnapshotProblemResponse, isDynamicDataRecord, isEditorialComponentMeta, isEntityLookupMultiplePayloadMode, isEntityLookupPayloadMode, isEntityLookupPayloadModeCompatible, isEntityLookupResultSelectable, isEntityLookupSinglePayloadMode, isFormLayoutItem, isGlobalActionProviderOperational, isGlobalActionRef, isInlineFilterControlType, isLookupDialogSize, isLookupFilterFieldType, isLookupFilterOperator, isPraxisI18nMessageDescriptor, isPraxisPresentationVisualizationTableSafe, isPraxisRuntimeGlobalActionEffect, isProgrammaticDateRangePreset, isRangeValidForFilter, isRequiredGlobalActionParamPayloadMissing, isRequiredGlobalActionPayloadMissing, isStaticDateRangePreset, isSurfaceNavigationError, isTableConfigV2, isValidFormConfig, isValidTableConfig, legacyCnpjValidator, legacyCpfValidator, logOnErrorHook, mapFieldDefinitionToMetadata, mapFieldDefinitionsToMetadata, markGlobalActionProviderOperational, matchFieldValidator, materializeFormLayoutFromMetadata, materializeGovernedPaletteColors, materializeGovernedPaletteEntries, materializeResourceIdentity, maxFileSizeValidator, mergeFieldMetadata, mergePraxisI18nConfigs, mergeTableConfigs, migrateFormLayoutRule, migrateLegacyCompositionLink, migrateLegacyCompositionLinks, minWordsValidator, nestedPortPathIdentity, normalizeCardMediaAspectRatio, normalizeControlTypeKey, normalizeControlTypeToken, normalizeEditorialLink, normalizeEnd, normalizeFieldAccessMetadata, normalizeFieldConstraints, normalizeFieldPresentation, normalizeFormConfig, normalizeFormLayoutItems, normalizeFormMetadata, normalizeGlobalActionRef$1 as normalizeGlobalActionRef, normalizeLayoutPolicy, normalizeLookupFilterRequest, normalizePath, normalizePraxisDataQueryContext, normalizePraxisEffectPolicy, normalizePraxisPresentationVisualization, normalizePraxisQueryFilterExpression, normalizePraxisQueryFilterNode, normalizeReactiveDeterminations, normalizeResourceAvailabilityReasonCode, normalizeResourceIdentityContract, normalizeStart, normalizeSurfaceOperationContext, normalizeUnknownError, normalizeWidgetEventPath, notifySuccessHook, parseJsonResponseOrEmpty, praxisLoadingInterceptorFn, prefillFromContextHook, prepareCanvasArrangement, projectComponentAuthoringManifest, projectGroupedCommandPartialRows, provideDefaultFormHooks, provideFieldSelectorRegistryBase, provideFieldSelectorRegistryOverride, provideFieldSelectorRegistryRuntime, provideFormHookPresets, provideFormHooks, provideGlobalActionCatalog, provideGlobalActionHandler, provideGlobalConfig, provideGlobalConfigReady, provideGlobalConfigSeed, provideGlobalConfigTenant, provideHookResolvers, provideHookWhitelist, provideOverlayDecisionMatrix, providePraxisAnalyticsGlobalActions, providePraxisCollectionExportProvider, providePraxisDynamicPageMetadata, providePraxisEnterpriseRuntimeContext, providePraxisFooterLinksMetadata, providePraxisGlobalActionCatalog, providePraxisGlobalActions, providePraxisGlobalConfigBootstrap, providePraxisHeroBannerMetadata, providePraxisHttpCollectionExportProvider, providePraxisHttpLoading, providePraxisI18n, providePraxisI18nConfig, providePraxisI18nTranslator, providePraxisIconDefaults, providePraxisJsonLogicOperator, providePraxisJsonLogicOperatorOverride, providePraxisLegalNoticeMetadata, providePraxisLoadingDefaults, providePraxisLogging, providePraxisRelatedResourceOutletMetadata, providePraxisRichTextBlockMetadata, providePraxisTelemetry, providePraxisToastGlobalActions, providePraxisUserContextSummaryMetadata, provideRemoteGlobalConfig, readPraxisExportValue, reconcileFilterConfig, reconcileFormConfig, reconcileTableConfig, registerPraxisRuntimeComponentObservation, removeDiacritics, renderPraxisPresentationVisualizationHtml, reportTelemetryHookFactory, requiredCheckedValidator, requiredPresenceValidator, resolveBuiltinPresets, resolveBuiltinWidgetShellAppearance, resolveColumnTypeFromFieldDefinition, resolveControlTypeAlias, resolveDataField, resolveDateRangeShortcutPreset, resolveDateRangeShortcutPresets, resolveDefaultValuePresentationFormat, resolveEntityLookupPayloadMode, resolveFieldPresentation, resolveGroupedCommandPartialRowSpans, resolveHidden, resolveInlineFilterControlType, resolveInlineFilterControlTypeToBaseControlType, resolveLoggerConfig, resolveObservabilityOptions, resolveOffset, resolveOrder, resolvePraxisCollectionExportItems, resolvePraxisExportFields, resolvePraxisExportScope, resolvePraxisFilterCriteria, resolvePraxisI18nDocument, resolvePraxisMicroVisualizationTone, resolveResourceAvailabilityReasonKey, resolveResourceIdentityContract, resolveSpan, resolveTextMaskFormat, resolveTextMaskFormatFromFieldDefinition, resolveValuePresentation, resolveValuePresentationLocale, resolveWidgetPageLayoutPreset, serializeEntityLookupValueForPayload, serializeOptionSourceFilterRequest, serializePraxisCollectionToCsv, serializePraxisCollectionToExcel, serializePraxisCollectionToJson, slugify, staticDateRangePresetToPreset, stripMasksHook, supportsConfigDocuments, supportsImplicitValuePresentation, syncWithServerMetadata, textSha256, toCamel, toCapitalize, toKebab, toPascal, toSentenceCase, toSnake, toTitleCase, translateResourceAvailabilityReason, translateResourceDiscoveryText, translateSurfaceNavigationRejected, translateSurfaceOpenFailed, translateUnavailableWorkflowMessage, trim, uiLayoutJsonIdentity, uniqueAsyncValidator, urlValidator, validateCanvasArrangement, validateGlobalActionRef, validateGlobalActionRefs, withFormConfigSections, withMessage, withPraxisHttpLoading };