@praxisui/core 10.0.0-rc.25 → 10.0.0-rc.27

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.
@@ -2,8 +2,8 @@ import * as i0 from '@angular/core';
2
2
  import { Component, InjectionToken, Injectable, inject, Inject, Optional, SkipSelf, makeEnvironmentProviders, signal, computed, APP_INITIALIZER, 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
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, timer, switchMap as switchMap$1, catchError as catchError$1, forkJoin, isObservable, combineLatest, merge, Subscription, defaultIfEmpty, timeout } from 'rxjs';
6
- import { switchMap, map, take, catchError, concatMap, tap, shareReplay, takeUntil, toArray, finalize, takeWhile, filter } from 'rxjs/operators';
5
+ import { of, defer, throwError, from, EMPTY, BehaviorSubject, firstValueFrom, Subject, finalize as finalize$1, shareReplay as shareReplay$1, map as map$1, Observable, timer, switchMap as switchMap$1, catchError as catchError$1, forkJoin, isObservable, combineLatest, merge, Subscription, defaultIfEmpty, timeout as timeout$1 } from 'rxjs';
6
+ import { switchMap, map, take, catchError, concatMap, tap, shareReplay, takeUntil, toArray, finalize, exhaustMap, filter, timeout } from 'rxjs/operators';
7
7
  import * as i1$3 from '@angular/common';
8
8
  import { Location, DOCUMENT, NgTemplateOutlet, CommonModule, NgComponentOutlet } from '@angular/common';
9
9
  import { Router, ActivatedRoute, RouterLink, RouterLinkActive } from '@angular/router';
@@ -15529,6 +15529,9 @@ class BulkSyncSession {
15529
15529
  }, ...(ngDevMode ? [{ debugName: "current" }] : /* istanbul ignore next */ []));
15530
15530
  confirmationKey = null;
15531
15531
  refreshInFlight = false;
15532
+ refreshGeneration = 0;
15533
+ refreshSignal;
15534
+ resultsGeneration = 0;
15532
15535
  ownedContract;
15533
15536
  options;
15534
15537
  /** Return detached wire data while retaining the original technical error for UX mapping. */
@@ -15607,17 +15610,25 @@ class BulkSyncSession {
15607
15610
  throw error;
15608
15611
  }
15609
15612
  }
15610
- async refreshExecution() {
15613
+ /** One cancellable consultation; the caller owns its deadline. POST custody is unchanged. */
15614
+ async refreshExecution(signal) {
15615
+ this.requireReadLifetime(signal);
15611
15616
  const current = this.current();
15612
15617
  if (!current.execution)
15613
15618
  throw new Error('No accepted execution is known to this session.');
15614
15619
  if (isBulkExecutionTerminal(current.execution.status))
15615
15620
  return structuredClone(current.execution);
15616
- if (this.refreshInFlight)
15621
+ if (this.refreshInFlight && !this.refreshSignal?.aborted) {
15617
15622
  throw new Error('An execution readback is already in flight.');
15623
+ }
15624
+ const generation = ++this.refreshGeneration;
15618
15625
  this.refreshInFlight = true;
15626
+ this.refreshSignal = signal;
15619
15627
  try {
15620
- const execution = await firstValueFrom(this.client.readExecution(this.ownedContract, current.execution.executionId, this.options));
15628
+ const execution = await this.readValue(() => this.client.readExecution(this.ownedContract, current.execution.executionId, this.options), signal);
15629
+ this.requireReadLifetime(signal);
15630
+ if (generation !== this.refreshGeneration)
15631
+ throw this.readAborted();
15621
15632
  if (execution.proposalId !== current.execution.proposalId) {
15622
15633
  throw new Error('Execution readback changed the proposal identity.');
15623
15634
  }
@@ -15626,25 +15637,63 @@ class BulkSyncSession {
15626
15637
  return structuredClone(execution);
15627
15638
  }
15628
15639
  catch (error) {
15629
- this.fail(error);
15640
+ if (generation === this.refreshGeneration && !signal?.aborted)
15641
+ this.fail(error);
15630
15642
  throw error;
15631
15643
  }
15632
15644
  finally {
15633
- this.refreshInFlight = false;
15645
+ if (generation === this.refreshGeneration) {
15646
+ this.refreshInFlight = false;
15647
+ this.refreshSignal = undefined;
15648
+ }
15634
15649
  }
15635
15650
  }
15636
- async readResults(size = 200, after) {
15651
+ /** Cancels only this results GET; an abort never discards the accepted execution. */
15652
+ async readResults(size = 200, after, signal) {
15653
+ this.requireReadLifetime(signal);
15654
+ const generation = ++this.resultsGeneration;
15637
15655
  const execution = this.current().execution;
15638
15656
  if (!execution)
15639
15657
  throw new Error('Results require an accepted execution.');
15640
15658
  try {
15641
- return await firstValueFrom(this.client.readResults(this.ownedContract, execution.executionId, size, after, this.options));
15659
+ return await this.readValue(() => this.client.readResults(this.ownedContract, execution.executionId, size, after, this.options), signal);
15642
15660
  }
15643
15661
  catch (error) {
15644
- this.current.set({ ...this.current(), error });
15662
+ if (generation === this.resultsGeneration && !signal?.aborted) {
15663
+ this.current.set({ ...this.current(), error });
15664
+ }
15645
15665
  throw error;
15646
15666
  }
15647
15667
  }
15668
+ readAborted() {
15669
+ return new DOMException('Bulk observation was cancelled.', 'AbortError');
15670
+ }
15671
+ requireReadLifetime(signal) {
15672
+ if (signal?.aborted)
15673
+ throw this.readAborted();
15674
+ }
15675
+ /** Cancellation owns the HTTP subscription, not just its Promise observer. */
15676
+ async readValue(read, signal) {
15677
+ this.requireReadLifetime(signal);
15678
+ const value = await firstValueFrom(new Observable(subscriber => {
15679
+ if (signal?.aborted) {
15680
+ subscriber.error(this.readAborted());
15681
+ return;
15682
+ }
15683
+ const abort = () => subscriber.error(this.readAborted());
15684
+ signal?.addEventListener('abort', abort, { once: true });
15685
+ // defer converts a synchronous client failure into an Observable failure,
15686
+ // so the listener is removed by the same teardown on every path.
15687
+ const subscription = defer(read).subscribe(subscriber);
15688
+ return () => {
15689
+ subscription.unsubscribe();
15690
+ signal?.removeEventListener('abort', abort);
15691
+ };
15692
+ }));
15693
+ // Abort may occur after HTTP next but before this Promise continuation.
15694
+ this.requireReadLifetime(signal);
15695
+ return value;
15696
+ }
15648
15697
  fail(error) {
15649
15698
  this.current.set({ ...this.current(), phase: 'error', error });
15650
15699
  }
@@ -15832,21 +15881,18 @@ class PraxisBulkClientService {
15832
15881
  * Polling utilitario para aguardar a conclusao terminal de uma execucao assincrona.
15833
15882
  */
15834
15883
  waitForExecution(executionOp, executionId, pollIntervalMs = 1000, timeoutMs = 60000, options) {
15835
- const startTime = Date.now();
15836
- return timer(0, pollIntervalMs).pipe(switchMap(() => this.getExecution(executionOp, executionId, options)), takeWhile((exec) => {
15837
- const isTerminal = this.isTerminalStatus(exec.status);
15838
- if (Date.now() - startTime > timeoutMs && !isTerminal) {
15839
- throw new Error(`Bulk execution ${executionId} timed out after ${timeoutMs}ms.`);
15840
- }
15841
- return !isTerminal;
15842
- }, true), filter((exec) => this.isTerminalStatus(exec.status)), take(1));
15884
+ return timer(0, pollIntervalMs).pipe(
15885
+ // A slow useful GET owns its slot; interval ticks never abort it or queue reads.
15886
+ exhaustMap(() => this.getExecution(executionOp, executionId, options)), filter((exec) => this.isTerminalStatus(exec.status)), take(1),
15887
+ // Only terminal snapshots cross the filter. This deadline therefore starts
15888
+ // per subscription and cannot be renewed by pending responses or a NEVER GET.
15889
+ timeout({
15890
+ first: timeoutMs,
15891
+ with: () => throwError(() => new Error(`Bulk execution ${executionId} timed out after ${timeoutMs}ms.`)),
15892
+ }));
15843
15893
  }
15844
15894
  isTerminalStatus(status) {
15845
- return (status === 'COMPLETED' ||
15846
- status === 'COMPLETED_WITH_ERRORS' ||
15847
- status === 'CANCELLED' ||
15848
- status === 'STOPPED' ||
15849
- status === 'RECONCILIATION_REQUIRED');
15895
+ return isBulkExecutionTerminal(status);
15850
15896
  }
15851
15897
  resolveUrl(path, options) {
15852
15898
  return this.discovery.resolveHref(path, options);
@@ -27007,15 +27053,30 @@ function removeValueAtPath(target, path) {
27007
27053
  if (!segments.length) {
27008
27054
  return;
27009
27055
  }
27056
+ const parents = [];
27010
27057
  let current = target;
27011
27058
  for (const segment of segments.slice(0, -1)) {
27012
27059
  const next = current[segment];
27013
27060
  if (!next || typeof next !== 'object' || Array.isArray(next)) {
27014
27061
  return;
27015
27062
  }
27063
+ parents.push({ parent: current, key: segment });
27016
27064
  current = next;
27017
27065
  }
27018
27066
  delete current[segments[segments.length - 1]];
27067
+ for (let i = parents.length - 1; i >= 0; i -= 1) {
27068
+ const { parent, key } = parents[i];
27069
+ const child = parent[key];
27070
+ if (child &&
27071
+ typeof child === 'object' &&
27072
+ !Array.isArray(child) &&
27073
+ Object.keys(child).length === 0) {
27074
+ delete parent[key];
27075
+ }
27076
+ else {
27077
+ break;
27078
+ }
27079
+ }
27019
27080
  }
27020
27081
  function normalizeEndpoint(endpoint) {
27021
27082
  if (endpoint.kind === 'component-port') {
@@ -37087,6 +37148,9 @@ class DynamicWidgetLoaderDirective {
37087
37148
  if (key && (key === 'expr' || key.endsWith('Expr'))) {
37088
37149
  return true;
37089
37150
  }
37151
+ if (path.includes('componentInputs')) {
37152
+ return true;
37153
+ }
37090
37154
  for (let index = 0; index < path.length - 1; index++) {
37091
37155
  if ((path[index] === 'action' || path[index] === 'globalAction')
37092
37156
  && path[index + 1] === 'payload') {
@@ -39273,7 +39337,10 @@ class WidgetPageCompositionFactory {
39273
39337
  }
39274
39338
  const isStructured = typeof state === 'object'
39275
39339
  && !Array.isArray(state)
39276
- && ('values' in state || 'schema' in state || 'derived' in state);
39340
+ && ('values' in state ||
39341
+ 'schema' in state ||
39342
+ 'derived' in state ||
39343
+ 'transientValues' in state);
39277
39344
  if (isStructured) {
39278
39345
  const structured = state;
39279
39346
  return {
@@ -39632,7 +39699,10 @@ class WidgetPageStateRuntimeService {
39632
39699
  return { values: {} };
39633
39700
  const isStructured = typeof state === 'object'
39634
39701
  && !Array.isArray(state)
39635
- && ('values' in state || 'schema' in state || 'derived' in state);
39702
+ && ('values' in state ||
39703
+ 'schema' in state ||
39704
+ 'derived' in state ||
39705
+ 'transientValues' in state);
39636
39706
  if (isStructured) {
39637
39707
  const structured = state;
39638
39708
  const normalized = {
@@ -39737,6 +39807,13 @@ class WidgetPageStateRuntimeService {
39737
39807
  computeDerivedNode(node, env, diagnostics, key) {
39738
39808
  const compute = node.compute;
39739
39809
  if (!compute) {
39810
+ if (this.isPlainObject(node) &&
39811
+ Object.keys(node).length > 0 &&
39812
+ !('dependsOn' in node) &&
39813
+ !('description' in node) &&
39814
+ !('cache' in node)) {
39815
+ return this.evaluateDerivedJsonLogic(node, env, diagnostics, key);
39816
+ }
39740
39817
  diagnostics.push(`Derived state '${key}' is missing compute definition.`);
39741
39818
  return undefined;
39742
39819
  }
@@ -39995,8 +40072,10 @@ class WidgetPageStateRuntimeService {
39995
40072
  }
39996
40073
  evaluateDerivedJsonLogic(expression, env, diagnostics, key) {
39997
40074
  try {
40075
+ const clonedState = this.clone(env.state) || {};
39998
40076
  return this.jsonLogic.evaluate(expression, {
39999
- state: this.clone(env.state) || {},
40077
+ ...clonedState,
40078
+ state: clonedState,
40000
40079
  context: this.clone(env.context) || {},
40001
40080
  }, this.derivedValueRuleOptions);
40002
40081
  }
@@ -40123,24 +40202,54 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
40123
40202
  }], ctorParameters: () => [{ type: PraxisJsonLogicService }] });
40124
40203
 
40125
40204
  const DEFAULT_JSON_LOGIC$2 = new PraxisJsonLogicService(null);
40205
+ const CANONICAL_OPERATION_SUFFIXES = {
40206
+ statsGroupBy: 'stats/group-by',
40207
+ 'stats/group-by': 'stats/group-by',
40208
+ statsTimeseries: 'stats/timeseries',
40209
+ 'stats/timeseries': 'stats/timeseries',
40210
+ statsDistribution: 'stats/distribution',
40211
+ 'stats/distribution': 'stats/distribution',
40212
+ statsComparison: 'stats/comparison',
40213
+ 'stats/comparison': 'stats/comparison',
40214
+ filter: 'filter',
40215
+ };
40126
40216
  class WidgetPageDataSourceRuntimeService {
40127
40217
  http = inject(HttpClient, { optional: true });
40128
40218
  apiUrlConfig = inject(API_URL, { optional: true });
40129
40219
  jsonLogic = inject(PraxisJsonLogicService, { optional: true }) || DEFAULT_JSON_LOGIC$2;
40130
40220
  connections = inject(ConnectionManagerService);
40221
+ latestSnapshots = new Map();
40222
+ getLatestSnapshot(id) {
40223
+ return this.latestSnapshots.get(id);
40224
+ }
40131
40225
  /**
40132
40226
  * Constrói a URL canônica completa para o resourcePath declarado no dataSource.
40227
+ * Quando `endpointKey` identifica uma operação canônica (`statsGroupBy`, `statsTimeseries`,
40228
+ * `statsDistribution`, `statsComparison`, `filter`) e não um host customizado em `API_URL`,
40229
+ * anexa o sufixo da operação ao `resourcePath` caso ainda não esteja presente.
40133
40230
  */
40134
40231
  resolveUrl(resourcePath, endpointKey) {
40135
40232
  const trimmed = (resourcePath || '').trim();
40136
40233
  if (!trimmed)
40137
40234
  return '';
40235
+ const key = (endpointKey || 'default').trim() || 'default';
40236
+ const hasCustomEndpointHost = Boolean(this.apiUrlConfig && key in this.apiUrlConfig);
40237
+ const operationSuffix = !hasCustomEndpointHost ? CANONICAL_OPERATION_SUFFIXES[key] : undefined;
40238
+ const appendOperationSuffix = (target) => {
40239
+ if (!operationSuffix)
40240
+ return target;
40241
+ const normalizedTarget = target.replace(/\/+$/, '');
40242
+ if (normalizedTarget === operationSuffix ||
40243
+ normalizedTarget.endsWith(`/${operationSuffix}`)) {
40244
+ return normalizedTarget;
40245
+ }
40246
+ return `${normalizedTarget}/${operationSuffix}`;
40247
+ };
40138
40248
  if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
40139
- return trimmed;
40249
+ return appendOperationSuffix(trimmed);
40140
40250
  }
40141
40251
  let base = '';
40142
40252
  if (this.apiUrlConfig) {
40143
- const key = (endpointKey || 'default').trim() || 'default';
40144
40253
  const entry = this.apiUrlConfig[key] ||
40145
40254
  this.apiUrlConfig['default'] ||
40146
40255
  Object.values(this.apiUrlConfig)[0];
@@ -40148,11 +40257,11 @@ class WidgetPageDataSourceRuntimeService {
40148
40257
  base = buildApiUrl(entry);
40149
40258
  }
40150
40259
  }
40260
+ const cleanPath = appendOperationSuffix(trimmed.replace(/^\/+/, ''));
40151
40261
  if (!base) {
40152
- return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
40262
+ return cleanPath.startsWith('/') ? cleanPath : `/${cleanPath}`;
40153
40263
  }
40154
40264
  const cleanBase = base.replace(/\/+$/, '');
40155
- const cleanPath = trimmed.replace(/^\/+/, '');
40156
40265
  return `${cleanBase}/${cleanPath}`;
40157
40266
  }
40158
40267
  /**
@@ -40192,7 +40301,9 @@ class WidgetPageDataSourceRuntimeService {
40192
40301
  return of(dataSource.fallbackValue ?? null);
40193
40302
  }
40194
40303
  const method = dataSource.method ??
40195
- (dataSource.kind === 'stats-query' || dataSource.resourcePath.includes('/stats/')
40304
+ (dataSource.kind === 'stats-query' ||
40305
+ dataSource.resourcePath.includes('/stats/') ||
40306
+ url.includes('/stats/')
40196
40307
  ? 'POST'
40197
40308
  : 'GET');
40198
40309
  let request$;
@@ -40279,6 +40390,7 @@ class WidgetPageDataSourceRuntimeService {
40279
40390
  hasSucceededOnce = true;
40280
40391
  skippedWhileHidden = false;
40281
40392
  const now = new Date().toISOString();
40393
+ this.latestSnapshots.set(ds.id, { data: this.clone(value), lastUpdated: now });
40282
40394
  onDiagnostic?.({
40283
40395
  dataSourceId: ds.id,
40284
40396
  status: 'success',
@@ -40292,10 +40404,15 @@ class WidgetPageDataSourceRuntimeService {
40292
40404
  });
40293
40405
  return value;
40294
40406
  }), catchError((err) => {
40407
+ const errorMsg = err?.message || String(err);
40408
+ this.latestSnapshots.set(ds.id, {
40409
+ ...(this.latestSnapshots.get(ds.id) || {}),
40410
+ error: errorMsg,
40411
+ });
40295
40412
  onDiagnostic?.({
40296
40413
  dataSourceId: ds.id,
40297
40414
  status: 'error',
40298
- error: err?.message || String(err),
40415
+ error: errorMsg,
40299
40416
  });
40300
40417
  if (!hasSucceededOnce &&
40301
40418
  ds.errorPolicy !== 'silent' &&
@@ -40353,10 +40470,26 @@ class WidgetPageDataSourceRuntimeService {
40353
40470
  destroy$.complete();
40354
40471
  };
40355
40472
  }
40473
+ /**
40474
+ * Avalia uma transformação declarativa contra uma amostra ou resposta bruta,
40475
+ * preservando `false`, `0` e `null` e reportando erros explícitos sem convertê-los em `0` silencioso.
40476
+ */
40477
+ previewTransform(rawSample, dataSource) {
40478
+ try {
40479
+ const value = this.transformPayload(rawSample, dataSource, true);
40480
+ return { ok: true, value };
40481
+ }
40482
+ catch (error) {
40483
+ return {
40484
+ ok: false,
40485
+ error: error instanceof Error ? error.message : String(error ?? 'Transform evaluation failed'),
40486
+ };
40487
+ }
40488
+ }
40356
40489
  /**
40357
40490
  * Transforma a resposta bruta do backend aplicando envelope canônico ou JsonLogic.
40358
40491
  */
40359
- transformPayload(raw, dataSource) {
40492
+ transformPayload(raw, dataSource, strictErrors = false) {
40360
40493
  if (raw == null)
40361
40494
  return raw;
40362
40495
  let payload = raw;
@@ -40378,9 +40511,13 @@ class WidgetPageDataSourceRuntimeService {
40378
40511
  const kind = transformObj['kind'];
40379
40512
  if (kind === 'path' && typeof transformObj['path'] === 'string') {
40380
40513
  const extracted = this.connections.extractByPath(payload, transformObj['path']);
40381
- return extracted !== undefined
40514
+ const resolved = extracted !== undefined
40382
40515
  ? extracted
40383
40516
  : this.connections.extractByPath(raw, transformObj['path']);
40517
+ if (resolved === undefined && strictErrors) {
40518
+ throw new Error(`Transform path '${transformObj['path']}' resolved to undefined.`);
40519
+ }
40520
+ return resolved;
40384
40521
  }
40385
40522
  if (kind === 'group-by-map') {
40386
40523
  const rawBuckets = raw?.buckets ??
@@ -40389,6 +40526,14 @@ class WidgetPageDataSourceRuntimeService {
40389
40526
  const buckets = Array.isArray(rawBuckets) ? rawBuckets : [];
40390
40527
  const mappings = transformObj['mappings'] || {};
40391
40528
  const totalField = transformObj['totalField'];
40529
+ const weightedAverageField = transformObj['weightedAverageField'];
40530
+ const weightedAverageDecimals = typeof transformObj['weightedAverageDecimals'] === 'number' &&
40531
+ Number.isFinite(transformObj['weightedAverageDecimals'])
40532
+ ? Math.max(0, Math.min(10, Math.trunc(transformObj['weightedAverageDecimals'])))
40533
+ : 2;
40534
+ const ratios = transformObj['ratios'] && typeof transformObj['ratios'] === 'object'
40535
+ ? transformObj['ratios']
40536
+ : undefined;
40392
40537
  const result = {};
40393
40538
  for (const targetField of Object.values(mappings)) {
40394
40539
  if (typeof targetField === 'string' && targetField.trim()) {
@@ -40396,11 +40541,25 @@ class WidgetPageDataSourceRuntimeService {
40396
40541
  }
40397
40542
  }
40398
40543
  let total = 0;
40544
+ let weightedSum = 0;
40545
+ let totalWeight = 0;
40399
40546
  for (const bucket of buckets) {
40400
- const keyStr = String(bucket?.key ?? '');
40401
- const val = Number(bucket?.value ?? bucket?.count ?? 0);
40547
+ const hasExplicitKey = bucket?.key !== undefined;
40548
+ const keyStr = String(hasExplicitKey ? bucket.key : (bucket?.value ?? ''));
40549
+ const val = hasExplicitKey
40550
+ ? Number(bucket?.value ?? bucket?.count ?? 0)
40551
+ : Number(bucket?.count ?? bucket?.value ?? 0);
40402
40552
  const safeVal = Number.isFinite(val) ? val : 0;
40403
40553
  total += safeVal;
40554
+ const rawWeight = bucket?.count !== undefined ? Number(bucket.count) : 1;
40555
+ const safeWeight = Number.isFinite(rawWeight) && rawWeight > 0 ? rawWeight : 0;
40556
+ const bucketMetric = hasExplicitKey
40557
+ ? Number(bucket?.value ?? bucket?.key ?? 0)
40558
+ : Number(bucket?.value ?? 0);
40559
+ if (Number.isFinite(bucketMetric) && safeWeight > 0) {
40560
+ weightedSum += bucketMetric * safeWeight;
40561
+ totalWeight += safeWeight;
40562
+ }
40404
40563
  const mappedField = mappings[keyStr];
40405
40564
  if (mappedField) {
40406
40565
  result[mappedField] = (result[mappedField] || 0) + safeVal;
@@ -40409,6 +40568,48 @@ class WidgetPageDataSourceRuntimeService {
40409
40568
  if (totalField) {
40410
40569
  result[totalField] = total;
40411
40570
  }
40571
+ if (weightedAverageField && weightedAverageField.trim()) {
40572
+ if (totalWeight > 0) {
40573
+ const avg = weightedSum / totalWeight;
40574
+ const factor = Math.pow(10, weightedAverageDecimals);
40575
+ result[weightedAverageField.trim()] =
40576
+ Math.round((avg + Number.EPSILON) * factor) / factor;
40577
+ }
40578
+ else {
40579
+ result[weightedAverageField.trim()] = 0;
40580
+ }
40581
+ }
40582
+ if (ratios) {
40583
+ for (const [ratioKey, ratioDef] of Object.entries(ratios)) {
40584
+ if (!ratioKey.trim() || !ratioDef || typeof ratioDef !== 'object')
40585
+ continue;
40586
+ const rawNum = ratioDef.numerator ?? ratioDef.numeratorField;
40587
+ const rawDen = ratioDef.denominator ?? ratioDef.denominatorField;
40588
+ const numField = typeof rawNum === 'string' ? rawNum.trim() : '';
40589
+ const denField = typeof rawDen === 'string' ? rawDen.trim() : '';
40590
+ if (!numField || !denField)
40591
+ continue;
40592
+ const numVal = Number(result[numField] ?? 0);
40593
+ const denVal = Number(result[denField] ?? 0);
40594
+ const scale = typeof ratioDef.scale === 'number' && Number.isFinite(ratioDef.scale)
40595
+ ? ratioDef.scale
40596
+ : 100;
40597
+ const decimals = typeof ratioDef.decimals === 'number' && Number.isFinite(ratioDef.decimals)
40598
+ ? Math.max(0, Math.min(10, Math.trunc(ratioDef.decimals)))
40599
+ : 1;
40600
+ if (Number.isFinite(numVal) && Number.isFinite(denVal) && denVal > 0) {
40601
+ const rawRatio = (numVal / denVal) * scale;
40602
+ const factor = Math.pow(10, decimals);
40603
+ result[ratioKey.trim()] =
40604
+ decimals === 0
40605
+ ? Math.round(rawRatio)
40606
+ : Math.round((rawRatio + Number.EPSILON) * factor) / factor;
40607
+ }
40608
+ else {
40609
+ result[ratioKey.trim()] = 0;
40610
+ }
40611
+ }
40612
+ }
40412
40613
  return result;
40413
40614
  }
40414
40615
  if (kind === 'timeseries-last') {
@@ -40436,6 +40637,9 @@ class WidgetPageDataSourceRuntimeService {
40436
40637
  }
40437
40638
  return result;
40438
40639
  }
40640
+ if (strictErrors) {
40641
+ throw new Error(`Unsupported transform kind "${String(kind ?? '')}".`);
40642
+ }
40439
40643
  }
40440
40644
  try {
40441
40645
  return this.jsonLogic.evaluate(transform, {
@@ -40444,7 +40648,10 @@ class WidgetPageDataSourceRuntimeService {
40444
40648
  result: payload,
40445
40649
  });
40446
40650
  }
40447
- catch {
40651
+ catch (error) {
40652
+ if (strictErrors) {
40653
+ throw error;
40654
+ }
40448
40655
  return payload;
40449
40656
  }
40450
40657
  }
@@ -43839,6 +44046,8 @@ const DYNAMIC_WIDGET_PAGE_I18N_CONFIG = {
43839
44046
  'size.widthError': 'Informe um número inteiro entre 1 e {{columns}}. O espaço livre também pode limitar a largura.',
43840
44047
  'size.heightPixelsHelp': 'Altura do conteúdo em pixels. O cabeçalho e o espaçamento podem aumentar a altura total.',
43841
44048
  'size.heightRowsHelp': 'Quantidade de linhas que o widget ocupa.',
44049
+ 'size.responsiveSummaryContent': 'Grade de {{columns}} colunas com altura em pixels ajustada ao conteúdo. Em telas menores sem layout próprio, os widgets são reorganizados automaticamente em coluna única.',
44050
+ 'size.responsiveSummaryRows': 'Grade de {{columns}} colunas com altura em faixas da grade. Em telas menores sem layout próprio, os widgets são reorganizados automaticamente em coluna única.',
43842
44051
  'size.anchorHelp': 'Ao editar largura ou altura, este canto fica no lugar e o lado oposto se move.',
43843
44052
  'size.title': 'Tamanho…',
43844
44053
  'size.fill': 'Preencher espaço livre',
@@ -44001,6 +44210,8 @@ const DYNAMIC_WIDGET_PAGE_I18N_CONFIG = {
44001
44210
  'size.widthError': 'Enter a whole number between 1 and {{columns}}. Free space may also limit the width.',
44002
44211
  'size.heightPixelsHelp': 'Content height in pixels. The header and spacing may increase the total height.',
44003
44212
  'size.heightRowsHelp': 'Number of rows occupied by the widget.',
44213
+ 'size.responsiveSummaryContent': '{{columns}}-column grid with content height in pixels. On smaller screens without a dedicated layout, widgets automatically reflow into a single column.',
44214
+ 'size.responsiveSummaryRows': '{{columns}}-column grid with row-span height. On smaller screens without a dedicated layout, widgets automatically reflow into a single column.',
44004
44215
  'size.anchorHelp': 'When editing width or height, this corner stays in place and the opposite side moves.',
44005
44216
  'size.title': 'Size…',
44006
44217
  'size.fill': 'Fill free space',
@@ -44194,6 +44405,7 @@ class WidgetSettingsEditorComponent {
44194
44405
  return;
44195
44406
  const changed = id !== this.currentSection();
44196
44407
  this.selectedSection.set(id);
44408
+ this.data.sectionChanged?.(id);
44197
44409
  if (id.startsWith('widget:'))
44198
44410
  this.shellProvider?.selectSettingsSection?.(id.slice('widget:'.length));
44199
44411
  if (changed)
@@ -44204,6 +44416,10 @@ class WidgetSettingsEditorComponent {
44204
44416
  } }, { injector: this.injector });
44205
44417
  }
44206
44418
  ngAfterViewInit() {
44419
+ this.data.registerTabController?.((tab) => {
44420
+ const targetId = tab === 'size' ? 'size' : (this.sections().find(s => s.id !== 'size')?.id ?? 'widget');
44421
+ this.selectSection(targetId);
44422
+ });
44207
44423
  // Let the eager tab contents initialize before observing their providers.
44208
44424
  queueMicrotask(() => {
44209
44425
  if (this.destroyRef.destroyed)
@@ -44572,7 +44788,13 @@ class CanvasSizeEditorComponent {
44572
44788
  showDelay: 500, hideDelay: 0, touchendHideDelay: 1500, disableTooltipInteractivity: true,
44573
44789
  } }], usesOnChanges: true, ngImport: i0, template: `
44574
44790
  <section class="size-editor" [formGroup]="form" [attr.aria-label]="t('size.title')">
44575
- <div class="context"><strong>{{ widgetLabel() }}</strong><span>{{ scopeLabel() }}</span></div>
44791
+ <div class="context">
44792
+ <strong>{{ widgetLabel() }}</strong>
44793
+ <span>{{ scopeLabel() }}</span>
44794
+ <p class="hint" data-testid="canvas-size-responsive-summary">
44795
+ {{ t(contentRows() ? 'size.responsiveSummaryContent' : 'size.responsiveSummaryRows', { columns: columns() }) }}
44796
+ </p>
44797
+ </div>
44576
44798
  <div class="dimensions">
44577
44799
  <mat-form-field appearance="outline" subscriptSizing="dynamic">
44578
44800
  <mat-label>{{ t('size.width') }}</mat-label>
@@ -44696,7 +44918,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
44696
44918
  showDelay: 500, hideDelay: 0, touchendHideDelay: 1500, disableTooltipInteractivity: true,
44697
44919
  } }], changeDetection: ChangeDetectionStrategy.OnPush, imports: [ReactiveFormsModule, MatButtonModule, MatFormFieldModule, MatInputModule, MatRadioModule, MatTooltipModule], template: `
44698
44920
  <section class="size-editor" [formGroup]="form" [attr.aria-label]="t('size.title')">
44699
- <div class="context"><strong>{{ widgetLabel() }}</strong><span>{{ scopeLabel() }}</span></div>
44921
+ <div class="context">
44922
+ <strong>{{ widgetLabel() }}</strong>
44923
+ <span>{{ scopeLabel() }}</span>
44924
+ <p class="hint" data-testid="canvas-size-responsive-summary">
44925
+ {{ t(contentRows() ? 'size.responsiveSummaryContent' : 'size.responsiveSummaryRows', { columns: columns() }) }}
44926
+ </p>
44927
+ </div>
44700
44928
  <div class="dimensions">
44701
44929
  <mat-form-field appearance="outline" subscriptSizing="dynamic">
44702
44930
  <mat-label>{{ t('size.width') }}</mat-label>
@@ -45911,6 +46139,9 @@ class DynamicWidgetPageComponent {
45911
46139
  injector = inject(Injector);
45912
46140
  settingsSessions = new Map();
45913
46141
  widgetSettingsSession;
46142
+ activeSettingsPanelSession;
46143
+ activeSettingsFocusReturnTarget = null;
46144
+ pendingContextNavigationFocusTarget;
45914
46145
  settingsIdentityRevision = 0;
45915
46146
  acceptedPageIdentity;
45916
46147
  storage = inject(ASYNC_CONFIG_STORAGE);
@@ -46079,13 +46310,15 @@ class DynamicWidgetPageComponent {
46079
46310
  // Controlled hosts return pageChange through [page]. That acknowledgement
46080
46311
  // must not dispose the open settings provider or rebuild its size template.
46081
46312
  // Any other input change or different document still invalidates the editor.
46082
- if (this.widgetSettingsKey && changes['page'] && Object.keys(changes).length === 1
46083
- && this.authoredPageDefinition
46084
- && this.sameJsonStructure(this.parsePage(this.page), this.authoredPageDefinition))
46313
+ if (this.widgetSettingsKey &&
46314
+ changes['page'] &&
46315
+ Object.keys(changes).length === 1 &&
46316
+ this.isAcknowledgedCanonicalPage(incomingPage)) {
46085
46317
  return;
46086
- const acknowledgedPage = changes['page'] && Object.keys(changes).length === 1
46087
- && this.authoredPageDefinition
46088
- && this.sameJsonStructure(this.parsePage(this.page), this.authoredPageDefinition);
46318
+ }
46319
+ const acknowledgedPage = changes['page'] &&
46320
+ Object.keys(changes).length === 1 &&
46321
+ this.isAcknowledgedCanonicalPage(incomingPage);
46089
46322
  if (!acknowledgedPage && (changes['page'] || changes['context'] || changes['pageIdentity']
46090
46323
  || changes['componentInstanceId'] || changes['enableCustomization'] || changes['authoringCapabilities'])) {
46091
46324
  this.settingsIdentityRevision++;
@@ -46112,6 +46345,8 @@ class DynamicWidgetPageComponent {
46112
46345
  if (changes['page'] ||
46113
46346
  changes['context'] ||
46114
46347
  changes['pageIdentity']) {
46348
+ const previousPageDefinition = this.pageDefinition;
46349
+ const previousPageRuntime = this.pageRuntime;
46115
46350
  const parsed = this.parsePage(this.page);
46116
46351
  if (changes['page'] || !this.authoredPageDefinition) {
46117
46352
  this.authoredPageDefinition = undefined;
@@ -46122,7 +46357,17 @@ class DynamicWidgetPageComponent {
46122
46357
  })
46123
46358
  : undefined;
46124
46359
  }
46125
- const resolvedPage = parsed ? this.resolvePagePresets(parsed) : parsed;
46360
+ const shouldPreserveEphemeralState = !this.isHydrating &&
46361
+ !changes['page']?.isFirstChange() &&
46362
+ !changes['pageIdentity'] &&
46363
+ !changes['componentInstanceId'] &&
46364
+ this.samePageContextIdentity(this.pageDefinition, parsed);
46365
+ const runtimeSeedPage = shouldPreserveEphemeralState && parsed
46366
+ ? this.preserveEphemeralRuntimeState(parsed)
46367
+ : parsed;
46368
+ const resolvedPage = runtimeSeedPage
46369
+ ? this.resolvePagePresets(runtimeSeedPage)
46370
+ : runtimeSeedPage;
46126
46371
  this.assertNoLegacyConnections(resolvedPage);
46127
46372
  this.widgetDiagnostics = {};
46128
46373
  this.widgetDiagnosticsChange.emit({});
@@ -46142,6 +46387,10 @@ class DynamicWidgetPageComponent {
46142
46387
  }
46143
46388
  this.pageRuntime = this.buildStateRuntime(this.pageDefinition?.state, this.pageDefinition?.context);
46144
46389
  this.pageState = this.pageRuntime.state;
46390
+ const resetNonPersistentPaths = this.collectResetNonPersistentStatePaths(previousPageDefinition, previousPageRuntime, this.pageDefinition, this.pageRuntime);
46391
+ if (resetNonPersistentPaths.length > 0) {
46392
+ this.clearMountedSourceSelectionsForNonPersistentState([previousPageDefinition, this.pageDefinition], resetNonPersistentPaths);
46393
+ }
46145
46394
  const bootstrapSnapshot = this.bootstrapCompositionAdapter(this.pageDefinition);
46146
46395
  const rawWidgets = this.pageDefinition?.widgets || [];
46147
46396
  let widgets = this.applyEditShellActions(rawWidgets);
@@ -48093,6 +48342,10 @@ class DynamicWidgetPageComponent {
48093
48342
  return session;
48094
48343
  }
48095
48344
  releaseSettingsSession(session) {
48345
+ if (this.activeSettingsPanelSession === session) {
48346
+ this.activeSettingsPanelSession = undefined;
48347
+ this.activeSettingsFocusReturnTarget = null;
48348
+ }
48096
48349
  this.settingsSessions.delete(session);
48097
48350
  if (!session.destroyed)
48098
48351
  session.destroy();
@@ -48103,6 +48356,7 @@ class DynamicWidgetPageComponent {
48103
48356
  this.releaseSettingsSession(session);
48104
48357
  }
48105
48358
  if (key === undefined) {
48359
+ this.pendingContextNavigationFocusTarget = undefined;
48106
48360
  this.configEditorContextRequests.clear();
48107
48361
  this.pendingWidgetComponentSettings.set(new Set());
48108
48362
  }
@@ -48111,10 +48365,102 @@ class DynamicWidgetPageComponent {
48111
48365
  this.setWidgetComponentSettingsPending(key, false);
48112
48366
  }
48113
48367
  }
48368
+ widgetSettingsSelectTab;
48369
+ buildWidgetSettingsContextNavigation(key, activeMode) {
48370
+ const page = this.ensurePageDefinition();
48371
+ const targets = (page.widgets || []).map((widget) => ({
48372
+ id: widget.key,
48373
+ label: this.widgetContextLabel(widget),
48374
+ }));
48375
+ const canContent = this.canOpenWidgetComponentSettings(key);
48376
+ const canWidget = this.canOpenWidgetShellSettings();
48377
+ const canSize = !!this.resolveSizeEditorTarget(key) ||
48378
+ (this.canCustomizeCanvas() && this.isCanvasMode() && !!this.currentCanvasItem(key));
48379
+ const nav = {
48380
+ targets,
48381
+ activeTargetId: key,
48382
+ modes: [
48383
+ {
48384
+ id: 'content',
48385
+ label: this.componentSettingsLabel(),
48386
+ icon: 'tune',
48387
+ disabled: !canContent,
48388
+ },
48389
+ {
48390
+ id: 'widget',
48391
+ label: this.widgetSettingsLabel(),
48392
+ icon: 'settings',
48393
+ disabled: !canWidget,
48394
+ editorGroup: 'widget-shell',
48395
+ },
48396
+ {
48397
+ id: 'size',
48398
+ label: this.sizeEditorLabel(),
48399
+ icon: 'straighten',
48400
+ disabled: !canSize,
48401
+ editorGroup: 'widget-shell',
48402
+ },
48403
+ ],
48404
+ activeModeId: activeMode,
48405
+ onSelectTarget: (targetKey) => {
48406
+ if (!targetKey || targetKey === key || this.destroyed)
48407
+ return;
48408
+ const currentMode = nav.activeModeId || activeMode;
48409
+ this.navigateToWidgetSettingsContext(targetKey, currentMode);
48410
+ },
48411
+ onSelectMode: (modeId) => {
48412
+ if (!modeId || modeId === nav.activeModeId || this.destroyed)
48413
+ return;
48414
+ if ((modeId === 'widget' || modeId === 'size') &&
48415
+ this.widgetSettingsKey === key &&
48416
+ this.widgetSettingsSession &&
48417
+ !this.widgetSettingsSession.destroyed &&
48418
+ this.widgetSettingsSelectTab) {
48419
+ nav.activeModeId = modeId;
48420
+ this.widgetSettingsSelectTab(modeId);
48421
+ return;
48422
+ }
48423
+ if (modeId === 'content' || modeId === 'widget' || modeId === 'size') {
48424
+ this.navigateToWidgetSettingsContext(key, modeId);
48425
+ }
48426
+ },
48427
+ };
48428
+ return nav;
48429
+ }
48430
+ navigateToWidgetSettingsContext(targetKey, requestedMode) {
48431
+ const canContent = this.canOpenWidgetComponentSettings(targetKey);
48432
+ const canWidget = this.canOpenWidgetShellSettings();
48433
+ const canSize = !!this.resolveSizeEditorTarget(targetKey) ||
48434
+ (this.canCustomizeCanvas() && this.isCanvasMode() && !!this.currentCanvasItem(targetKey));
48435
+ let mode = requestedMode;
48436
+ if (mode === 'content' && !canContent) {
48437
+ mode = canWidget ? 'widget' : canSize ? 'size' : 'content';
48438
+ }
48439
+ else if (mode === 'widget' && !canWidget) {
48440
+ mode = canSize ? 'size' : canContent ? 'content' : 'widget';
48441
+ }
48442
+ else if (mode === 'size' && !canSize) {
48443
+ mode = canWidget ? 'widget' : canContent ? 'content' : 'size';
48444
+ }
48445
+ this.pendingContextNavigationFocusTarget = {
48446
+ target: this.activeSettingsFocusReturnTarget,
48447
+ };
48448
+ if (mode === 'content') {
48449
+ void this.openWidgetComponentSettings(targetKey);
48450
+ }
48451
+ else {
48452
+ this.openWidgetSettingsPanel(targetKey, mode);
48453
+ }
48454
+ }
48455
+ resolveSettingsFocusReturnTarget() {
48456
+ const pending = this.pendingContextNavigationFocusTarget;
48457
+ this.pendingContextNavigationFocusTarget = undefined;
48458
+ return pending ? pending.target : this.captureFocusReturnTarget();
48459
+ }
48114
48460
  openWidgetSettingsPanel(key, initialTab) {
48115
48461
  if (this.destroyed || !this.settingsPanel)
48116
48462
  return;
48117
- const focusReturnTarget = this.captureFocusReturnTarget();
48463
+ const focusReturnTarget = this.resolveSettingsFocusReturnTarget();
48118
48464
  const page = this.ensurePageDefinition();
48119
48465
  const widget = page.widgets.find(item => item.key === key);
48120
48466
  if (!widget)
@@ -48124,6 +48470,11 @@ class DynamicWidgetPageComponent {
48124
48470
  return;
48125
48471
  const session = this.createSettingsSession(key);
48126
48472
  const owner = session.get(DestroyRef);
48473
+ const contextNavigation = this.buildWidgetSettingsContextNavigation(key, initialTab);
48474
+ const previousActiveSession = this.activeSettingsPanelSession;
48475
+ const previousFocusTarget = this.activeSettingsFocusReturnTarget;
48476
+ this.activeSettingsPanelSession = session;
48477
+ this.activeSettingsFocusReturnTarget = focusReturnTarget;
48127
48478
  let ref;
48128
48479
  try {
48129
48480
  ref = this.settingsPanel.open({
@@ -48131,14 +48482,19 @@ class DynamicWidgetPageComponent {
48131
48482
  id: `dynamic-page-widget:${key}`, title: this.t('controls.cardSettings', 'Configurar card'),
48132
48483
  previewTarget: () => Array.from(this.pageCanvasHost?.nativeElement.children || [])
48133
48484
  .find(element => element.getAttribute('data-widget-key') === key) ?? null,
48485
+ contextNavigation,
48134
48486
  content: { component: WidgetSettingsEditorComponent, inputs: () => {
48135
48487
  const page = this.ensurePageDefinition();
48136
48488
  const widget = page.widgets.find(item => item.key === key);
48137
48489
  const uiShell = this.mergedContext['ui']?.shell || {};
48490
+ this.activeSettingsPanelSession = session;
48491
+ this.activeSettingsFocusReturnTarget = focusReturnTarget;
48138
48492
  this.closeSizeEditor(false);
48493
+ this.selectWidget(key);
48139
48494
  this.prepareSizeEditor(key);
48140
48495
  this.widgetSettingsKey = key;
48141
48496
  this.widgetSettingsSession = session;
48497
+ this.widgetSettingsSelectTab = undefined;
48142
48498
  const hasSize = this.sizeEditorSeed()?.key === key;
48143
48499
  return {
48144
48500
  shellComponent,
@@ -48153,11 +48509,21 @@ class DynamicWidgetPageComponent {
48153
48509
  appliedValues$: this.widgetSettingsAppliedValues.asObservable(),
48154
48510
  widgetChanged: (value, state) => this.previewSettingsShell(key, value, state),
48155
48511
  validate: (value) => this.validateWidgetSettings(key, value),
48512
+ sectionChanged: (sectionId) => {
48513
+ contextNavigation.activeModeId = sectionId === 'size' ? 'size' : 'widget';
48514
+ },
48515
+ registerTabController: (selectTab) => {
48516
+ this.widgetSettingsSelectTab = selectTab;
48517
+ },
48156
48518
  };
48157
48519
  } },
48158
48520
  });
48159
48521
  }
48160
48522
  catch (error) {
48523
+ if (this.activeSettingsPanelSession === session) {
48524
+ this.activeSettingsPanelSession = previousActiveSession;
48525
+ this.activeSettingsFocusReturnTarget = previousFocusTarget;
48526
+ }
48161
48527
  if (this.widgetSettingsSession === session)
48162
48528
  this.closeSizeEditor(false);
48163
48529
  else
@@ -48167,13 +48533,27 @@ class DynamicWidgetPageComponent {
48167
48533
  ref.applied$.pipe(takeUntilDestroyed(owner)).subscribe(value => this.commitWidgetSettings(key, value, false));
48168
48534
  ref.saved$.pipe(takeUntilDestroyed(owner)).subscribe(value => this.commitWidgetSettings(key, value, true));
48169
48535
  ref.closed$?.pipe(take(1), takeUntilDestroyed(owner)).subscribe(() => {
48170
- const active = this.widgetSettingsSession === session;
48171
- if (active)
48536
+ const wasActiveWidgetSettings = this.widgetSettingsSession === session;
48537
+ const wasActivePanelSession = this.activeSettingsPanelSession === session;
48538
+ if (wasActivePanelSession &&
48539
+ !wasActiveWidgetSettings &&
48540
+ previousActiveSession &&
48541
+ !previousActiveSession.destroyed) {
48542
+ this.activeSettingsPanelSession = previousActiveSession;
48543
+ this.activeSettingsFocusReturnTarget = previousFocusTarget;
48544
+ this.releaseSettingsSession(session);
48545
+ return;
48546
+ }
48547
+ if (wasActiveWidgetSettings) {
48548
+ this.widgetSettingsSelectTab = undefined;
48172
48549
  this.closeSizeEditor(false);
48173
- else
48550
+ }
48551
+ else {
48174
48552
  this.releaseSettingsSession(session);
48175
- if (active)
48553
+ }
48554
+ if (wasActivePanelSession) {
48176
48555
  this.restoreFocus(focusReturnTarget);
48556
+ }
48177
48557
  });
48178
48558
  }
48179
48559
  sizeEditorLayoutItems() {
@@ -48310,6 +48690,7 @@ class DynamicWidgetPageComponent {
48310
48690
  } });
48311
48691
  }
48312
48692
  async openWidgetComponentSettings(key) {
48693
+ const focusReturnTarget = this.resolveSettingsFocusReturnTarget();
48313
48694
  if (this.destroyed || !this.canOpenWidgetComponentSettings(key))
48314
48695
  return;
48315
48696
  if (!this.settingsPanel) {
@@ -48331,7 +48712,6 @@ class DynamicWidgetPageComponent {
48331
48712
  const materializedInputs = metadata.configEditor?.contextResolver
48332
48713
  ? this.readMaterializedInputsForWidget(metadata, key)
48333
48714
  : undefined;
48334
- const focusReturnTarget = this.captureFocusReturnTarget();
48335
48715
  const session = this.createSettingsSession(key);
48336
48716
  const owner = session.get(DestroyRef);
48337
48717
  let opened = false;
@@ -48350,12 +48730,22 @@ class DynamicWidgetPageComponent {
48350
48730
  }
48351
48731
  const appliedValues = new Subject();
48352
48732
  owner.onDestroy(() => appliedValues.complete());
48733
+ const contextNavigation = this.buildWidgetSettingsContextNavigation(key, 'content');
48734
+ const previousActiveSession = this.activeSettingsPanelSession;
48735
+ const previousFocusTarget = this.activeSettingsFocusReturnTarget;
48736
+ const previousSelectedWidgetKey = this.selectedWidgetKeyState();
48737
+ this.activeSettingsPanelSession = session;
48738
+ this.activeSettingsFocusReturnTarget = focusReturnTarget;
48739
+ this.selectWidget(key);
48353
48740
  const ref = this.settingsPanel.open({
48354
48741
  owner,
48355
48742
  id: `dynamic-page-component:${key}`,
48356
48743
  title: metadata.configEditor?.title ||
48357
48744
  metadata.friendlyName ||
48358
48745
  this.t('controls.componentSettings', 'Configurar conteudo'),
48746
+ previewTarget: () => Array.from(this.pageCanvasHost?.nativeElement.children || [])
48747
+ .find(element => element.getAttribute('data-widget-key') === key) ?? null,
48748
+ contextNavigation,
48359
48749
  content: {
48360
48750
  component: editor,
48361
48751
  inputs: {
@@ -48376,8 +48766,22 @@ class DynamicWidgetPageComponent {
48376
48766
  });
48377
48767
  ref.saved$.pipe(takeUntilDestroyed(owner)).subscribe((result) => this.applyWidgetComponentInputs(key, result, true));
48378
48768
  ref.closed$?.pipe(take(1), takeUntilDestroyed(owner)).subscribe(() => {
48769
+ const wasActivePanelSession = this.activeSettingsPanelSession === session;
48770
+ if (wasActivePanelSession &&
48771
+ previousActiveSession &&
48772
+ !previousActiveSession.destroyed) {
48773
+ this.activeSettingsPanelSession = previousActiveSession;
48774
+ this.activeSettingsFocusReturnTarget = previousFocusTarget;
48775
+ if (previousSelectedWidgetKey) {
48776
+ this.selectWidget(previousSelectedWidgetKey);
48777
+ }
48778
+ this.releaseSettingsSession(session);
48779
+ return;
48780
+ }
48379
48781
  this.releaseSettingsSession(session);
48380
- this.restoreFocus(focusReturnTarget);
48782
+ if (wasActivePanelSession) {
48783
+ this.restoreFocus(focusReturnTarget);
48784
+ }
48381
48785
  });
48382
48786
  }
48383
48787
  finally {
@@ -48413,6 +48817,8 @@ class DynamicWidgetPageComponent {
48413
48817
  restoreFocus(target) {
48414
48818
  queueMicrotask(() => {
48415
48819
  if (!this.destroyed
48820
+ && !this.activeSettingsPanelSession
48821
+ && this.pendingWidgetComponentSettings().size === 0
48416
48822
  && target?.isConnected
48417
48823
  && !target.hasAttribute('disabled')
48418
48824
  && target.getAttribute('aria-disabled') !== 'true') {
@@ -48722,6 +49128,7 @@ class DynamicWidgetPageComponent {
48722
49128
  return (endpoint.kind === 'component-port' && endpoint.ref.widget === widgetKey);
48723
49129
  }
48724
49130
  openPageSettings() {
49131
+ const focusReturnTarget = this.resolveSettingsFocusReturnTarget();
48725
49132
  if (this.destroyed || !this.canInvokePageSettings())
48726
49133
  return;
48727
49134
  if (!this.settingsPanel)
@@ -48736,6 +49143,10 @@ class DynamicWidgetPageComponent {
48736
49143
  const owner = session.get(DestroyRef);
48737
49144
  const appliedValues = new Subject();
48738
49145
  owner.onDestroy(() => appliedValues.complete());
49146
+ const previousActiveSession = this.activeSettingsPanelSession;
49147
+ const previousFocusTarget = this.activeSettingsFocusReturnTarget;
49148
+ this.activeSettingsPanelSession = session;
49149
+ this.activeSettingsFocusReturnTarget = focusReturnTarget;
48739
49150
  const ref = this.settingsPanel.open({
48740
49151
  owner,
48741
49152
  id: 'dynamic-page-settings',
@@ -48765,7 +49176,21 @@ class DynamicWidgetPageComponent {
48765
49176
  appliedValues.next(accepted);
48766
49177
  });
48767
49178
  ref.saved$.pipe(takeUntilDestroyed(owner)).subscribe((result) => this.applyPageLayout(result, true));
48768
- ref.closed$?.pipe(take(1), takeUntilDestroyed(owner)).subscribe(() => this.releaseSettingsSession(session));
49179
+ ref.closed$?.pipe(take(1), takeUntilDestroyed(owner)).subscribe(() => {
49180
+ const wasActivePanelSession = this.activeSettingsPanelSession === session;
49181
+ if (wasActivePanelSession &&
49182
+ previousActiveSession &&
49183
+ !previousActiveSession.destroyed) {
49184
+ this.activeSettingsPanelSession = previousActiveSession;
49185
+ this.activeSettingsFocusReturnTarget = previousFocusTarget;
49186
+ this.releaseSettingsSession(session);
49187
+ return;
49188
+ }
49189
+ this.releaseSettingsSession(session);
49190
+ if (wasActivePanelSession) {
49191
+ this.restoreFocus(focusReturnTarget);
49192
+ }
49193
+ });
48769
49194
  }
48770
49195
  canInvokePageSettings() {
48771
49196
  return this.isAuthoringCapabilityEnabled('pageSettings')
@@ -48898,11 +49323,227 @@ class DynamicWidgetPageComponent {
48898
49323
  }, persist);
48899
49324
  }
48900
49325
  applyAuthoringPageUpdate(next, persist = true, runtime, syncCompositionAdapter = true, skipStateWidgetProjection = false, emitUnchangedCanonicalPage = true) {
48901
- this.applyPageUpdate(next, persist, runtime, syncCompositionAdapter, skipStateWidgetProjection, emitUnchangedCanonicalPage);
49326
+ const runtimeSeed = !runtime && !this.isHydrating
49327
+ ? (this.preserveEphemeralRuntimeState(next) ?? next)
49328
+ : next;
49329
+ this.applyPageUpdate(runtimeSeed, persist, runtime, syncCompositionAdapter, skipStateWidgetProjection, emitUnchangedCanonicalPage);
48902
49330
  if (this.authoredPageDefinition) {
48903
49331
  this.pageAuthoringChange.emit(this.clonePageDefinition(this.authoredPageDefinition));
48904
49332
  }
48905
49333
  }
49334
+ isAcknowledgedCanonicalPage(incomingPage) {
49335
+ if (!incomingPage || !this.authoredPageDefinition) {
49336
+ return false;
49337
+ }
49338
+ if (this.sameJsonStructure(incomingPage, this.authoredPageDefinition)) {
49339
+ return true;
49340
+ }
49341
+ if (this.hasExplicitNonPersistentStateOverride(incomingPage)) {
49342
+ return false;
49343
+ }
49344
+ const normalizedIncoming = this.toPersistedCanonicalPage({
49345
+ ...incomingPage,
49346
+ state: this.stateRuntime.normalizeState(incomingPage.state),
49347
+ });
49348
+ return this.sameJsonStructure(normalizedIncoming, this.authoredPageDefinition);
49349
+ }
49350
+ hasExplicitNonPersistentStateOverride(page) {
49351
+ const schema = page?.state?.schema || {};
49352
+ const values = page?.state?.values;
49353
+ if (!values || typeof values !== 'object') {
49354
+ return false;
49355
+ }
49356
+ for (const rawPath of Object.keys(schema)) {
49357
+ const entry = schema[rawPath];
49358
+ if (entry?.persist !== false) {
49359
+ continue;
49360
+ }
49361
+ const path = rawPath.trim();
49362
+ if (!path) {
49363
+ continue;
49364
+ }
49365
+ const value = this.conn.extractByPath(values, path);
49366
+ if (value !== undefined && !this.sameJsonStructure(value, entry.initial)) {
49367
+ return true;
49368
+ }
49369
+ }
49370
+ return false;
49371
+ }
49372
+ collectResetNonPersistentStatePaths(previousPage, previousRuntime, nextPage, nextRuntime) {
49373
+ if (!previousPage || !previousRuntime || !nextRuntime) {
49374
+ return [];
49375
+ }
49376
+ const previousSchema = previousPage.state?.schema ?? {};
49377
+ const nextSchema = nextPage?.state?.schema ?? {};
49378
+ const candidatePaths = new Set([
49379
+ ...Object.keys(previousSchema).filter((path) => previousSchema[path]?.persist === false),
49380
+ ...Object.keys(nextSchema).filter((path) => nextSchema[path]?.persist === false),
49381
+ ]);
49382
+ const resetPaths = [];
49383
+ for (const rawPath of candidatePaths) {
49384
+ const path = rawPath.trim();
49385
+ if (!path) {
49386
+ continue;
49387
+ }
49388
+ const prevEntry = previousSchema[rawPath] ?? nextSchema[rawPath];
49389
+ const nextEntry = nextSchema[rawPath] ?? previousSchema[rawPath];
49390
+ const prevValue = this.conn.extractByPath(previousRuntime.primaryValues, path);
49391
+ const hadLiveSelection = prevValue !== undefined &&
49392
+ !this.sameJsonStructure(prevValue, prevEntry?.initial);
49393
+ if (!hadLiveSelection) {
49394
+ continue;
49395
+ }
49396
+ const nextValue = this.conn.extractByPath(nextRuntime.primaryValues, path);
49397
+ const isNowReset = nextValue === undefined ||
49398
+ this.sameJsonStructure(nextValue, nextEntry?.initial);
49399
+ if (isNowReset) {
49400
+ resetPaths.push(path);
49401
+ }
49402
+ }
49403
+ return [...new Set(resetPaths)];
49404
+ }
49405
+ samePageContextIdentity(left, right) {
49406
+ if (!left || !right) {
49407
+ return false;
49408
+ }
49409
+ const leftPageId = left.context?.['pageId'] ?? null;
49410
+ const rightPageId = right.context?.['pageId'] ?? null;
49411
+ if (leftPageId !== rightPageId) {
49412
+ return false;
49413
+ }
49414
+ if (leftPageId !== null) {
49415
+ return true;
49416
+ }
49417
+ const leftKeys = new Set((left.widgets || []).map((w) => w.key));
49418
+ const rightKeys = (right.widgets || []).map((w) => w.key);
49419
+ if (leftKeys.size === 0 && rightKeys.length === 0) {
49420
+ return true;
49421
+ }
49422
+ return rightKeys.some((key) => leftKeys.has(key));
49423
+ }
49424
+ preserveEphemeralRuntimeState(incomingPage) {
49425
+ if (!incomingPage || !this.pageRuntime || this.isHydrating) {
49426
+ return incomingPage;
49427
+ }
49428
+ const schema = incomingPage.state?.schema || {};
49429
+ const nonPersistentPaths = Object.keys(schema)
49430
+ .filter((path) => schema[path]?.persist === false)
49431
+ .map((path) => path.trim())
49432
+ .filter(Boolean);
49433
+ if (!nonPersistentPaths.length) {
49434
+ return incomingPage;
49435
+ }
49436
+ const incomingWidgets = new Set((incomingPage.widgets || []).map((widget) => widget.key));
49437
+ const incomingLinks = incomingPage.composition?.links || [];
49438
+ const previousLinks = this.pageDefinition?.composition?.links || incomingLinks;
49439
+ let mergedValues = this.cloneStateValues(incomingPage.state?.values || {});
49440
+ let preservedAny = false;
49441
+ for (const path of nonPersistentPaths) {
49442
+ const schemaEntry = schema[path];
49443
+ const incomingValue = this.conn.extractByPath(mergedValues, path);
49444
+ const hasExplicitIncomingOverride = incomingValue !== undefined &&
49445
+ !this.sameJsonStructure(incomingValue, schemaEntry?.initial);
49446
+ if (hasExplicitIncomingOverride) {
49447
+ continue;
49448
+ }
49449
+ const previousWriterWidgets = previousLinks.filter((link) => link.from.kind === 'component-port' &&
49450
+ link.to.kind === 'state' &&
49451
+ this.isSameOrNestedStatePath(link.to.ref.path, path));
49452
+ if (previousWriterWidgets.length > 0) {
49453
+ const hasActiveWriter = incomingLinks.some((link) => link.from.kind === 'component-port' &&
49454
+ link.to.kind === 'state' &&
49455
+ this.isSameOrNestedStatePath(link.to.ref.path, path) &&
49456
+ incomingWidgets.has(link.from.ref.widget));
49457
+ if (!hasActiveWriter) {
49458
+ continue;
49459
+ }
49460
+ }
49461
+ const liveValue = this.conn.extractByPath(this.pageRuntime.primaryValues, path);
49462
+ if (liveValue === undefined) {
49463
+ continue;
49464
+ }
49465
+ mergedValues = this.conn.setValueAtPath(mergedValues, path, this.cloneStateValues(liveValue));
49466
+ preservedAny = true;
49467
+ }
49468
+ if (!preservedAny) {
49469
+ return incomingPage;
49470
+ }
49471
+ return {
49472
+ ...incomingPage,
49473
+ state: {
49474
+ ...(incomingPage.state || {}),
49475
+ values: mergedValues,
49476
+ },
49477
+ };
49478
+ }
49479
+ isSameOrNestedStatePath(candidate, base) {
49480
+ const normalizedCandidate = String(candidate || '').trim();
49481
+ const normalizedBase = String(base || '').trim();
49482
+ if (!normalizedCandidate || !normalizedBase) {
49483
+ return false;
49484
+ }
49485
+ return (normalizedCandidate === normalizedBase ||
49486
+ normalizedCandidate.startsWith(`${normalizedBase}.`) ||
49487
+ normalizedBase.startsWith(`${normalizedCandidate}.`));
49488
+ }
49489
+ clearMountedSourceSelectionsForNonPersistentState(pages, resetPaths) {
49490
+ if (!this.widgetLoaders) {
49491
+ return;
49492
+ }
49493
+ const matchesResetPath = (path) => !resetPaths || resetPaths.some((resetPath) => this.isSameOrNestedStatePath(path, resetPath));
49494
+ const sourceWidgets = new Set();
49495
+ for (const page of pages) {
49496
+ if (!page) {
49497
+ continue;
49498
+ }
49499
+ const schema = page.state?.schema || {};
49500
+ const nonPersistentPaths = Object.keys(schema)
49501
+ .filter((path) => schema[path]?.persist === false)
49502
+ .map((path) => path.trim())
49503
+ .filter((path) => path && matchesResetPath(path));
49504
+ if (!nonPersistentPaths.length) {
49505
+ continue;
49506
+ }
49507
+ const links = page.composition?.links || [];
49508
+ for (const link of links) {
49509
+ const fromRef = link.from?.ref;
49510
+ const toRef = link.to?.ref;
49511
+ const toPath = typeof toRef?.path === 'string' ? toRef.path : '';
49512
+ if (link.from?.kind === 'component-port' &&
49513
+ link.to?.kind === 'state' &&
49514
+ typeof fromRef?.widget === 'string' &&
49515
+ toPath &&
49516
+ matchesResetPath(toPath) &&
49517
+ nonPersistentPaths.some((path) => this.isSameOrNestedStatePath(toPath, path))) {
49518
+ sourceWidgets.add(fromRef.widget);
49519
+ }
49520
+ }
49521
+ for (const widget of page.widgets || []) {
49522
+ const outputs = widget.definition?.outputs;
49523
+ if (!outputs || typeof outputs !== 'object') {
49524
+ continue;
49525
+ }
49526
+ for (const rawActions of Object.values(outputs)) {
49527
+ const actions = Array.isArray(rawActions) ? rawActions : [rawActions];
49528
+ for (const action of actions) {
49529
+ const candidate = action;
49530
+ if (candidate?.type === 'setState' &&
49531
+ typeof candidate.path === 'string' &&
49532
+ matchesResetPath(candidate.path) &&
49533
+ nonPersistentPaths.some((path) => this.isSameOrNestedStatePath(candidate.path, path))) {
49534
+ sourceWidgets.add(widget.key);
49535
+ }
49536
+ }
49537
+ }
49538
+ }
49539
+ }
49540
+ for (const widgetKey of sourceWidgets) {
49541
+ const loader = this.findWidgetLoader(widgetKey);
49542
+ if (loader && typeof loader.clearSelection === 'function') {
49543
+ loader.clearSelection();
49544
+ }
49545
+ }
49546
+ }
48906
49547
  applyPageUpdate(next, persist = true, runtime, syncCompositionAdapter = true, skipStateWidgetProjection = false, emitUnchangedCanonicalPage = true) {
48907
49548
  this.assertNoLegacyConnections(next);
48908
49549
  if (this.getDataSourcesSignature(next.dataSources) !==
@@ -48927,7 +49568,7 @@ class DynamicWidgetPageComponent {
48927
49568
  state: {
48928
49569
  primaryValues: this.cloneStateValues(effectiveRuntime.primaryValues),
48929
49570
  derivedValues: this.cloneStateValues(effectiveRuntime.derivedValues),
48930
- transientValues: this.cloneStateValues(this.compositionRuntime.getSnapshot().state.transientValues),
49571
+ transientValues: {},
48931
49572
  changedPaths: [],
48932
49573
  diagnostics: [],
48933
49574
  },
@@ -49011,7 +49652,7 @@ class DynamicWidgetPageComponent {
49011
49652
  state: {
49012
49653
  primaryValues: this.cloneStateValues(nextRuntime.primaryValues),
49013
49654
  derivedValues: this.cloneStateValues(nextRuntime.derivedValues),
49014
- transientValues: this.cloneStateValues(this.compositionRuntime.getSnapshot().state.transientValues),
49655
+ transientValues: {},
49015
49656
  changedPaths: updates.map((u) => u.targetStatePath),
49016
49657
  diagnostics: [],
49017
49658
  },
@@ -52085,6 +52726,7 @@ class DynamicWidgetPageComponent {
52085
52726
  dataSources: stored.dataSources ?? baseline.dataSources,
52086
52727
  }
52087
52728
  : (stored ?? defaultPage);
52729
+ this.clearMountedSourceSelectionsForNonPersistentState([loaded]);
52088
52730
  this.authoredPageDefinition = this.clonePageDefinition(loaded);
52089
52731
  this.applyPageUpdate(loaded, false);
52090
52732
  }
@@ -52159,6 +52801,7 @@ class DynamicWidgetPageComponent {
52159
52801
  if (mutation.revision === this.persistenceRevision) {
52160
52802
  this.isHydrating = true;
52161
52803
  try {
52804
+ this.clearMountedSourceSelectionsForNonPersistentState([mutation.defaultPage]);
52162
52805
  this.authoredPageDefinition = this.clonePageDefinition(mutation.defaultPage);
52163
52806
  this.applyPageUpdate(mutation.defaultPage, false);
52164
52807
  }
@@ -53544,7 +54187,42 @@ class PraxisSurfaceHostComponent {
53544
54187
  loader.renderNow();
53545
54188
  }
53546
54189
  enrichWidget(widget, widgetKey) {
53547
- if (!widget || widget.id !== 'praxis-rich-content') {
54190
+ if (!widget) {
54191
+ return widget;
54192
+ }
54193
+ if (widget.id === 'praxis-crud') {
54194
+ const explicitContext = widget.inputs?.['context'] &&
54195
+ typeof widget.inputs['context'] === 'object' &&
54196
+ !Array.isArray(widget.inputs['context'])
54197
+ ? widget.inputs['context']
54198
+ : {};
54199
+ const hostContext = this.context &&
54200
+ typeof this.context === 'object' &&
54201
+ !Array.isArray(this.context)
54202
+ ? this.context
54203
+ : {};
54204
+ const mergedContext = {
54205
+ ...hostContext,
54206
+ ...explicitContext,
54207
+ };
54208
+ if ((!mergedContext['surfaceRuntime'] ||
54209
+ typeof mergedContext['surfaceRuntime'] !== 'object') &&
54210
+ hostContext['surfaceRuntime'] &&
54211
+ typeof hostContext['surfaceRuntime'] === 'object') {
54212
+ mergedContext['surfaceRuntime'] = hostContext['surfaceRuntime'];
54213
+ }
54214
+ if (Object.keys(mergedContext).length === 0) {
54215
+ return widget;
54216
+ }
54217
+ return {
54218
+ ...widget,
54219
+ inputs: {
54220
+ ...(widget.inputs || {}),
54221
+ context: mergedContext,
54222
+ },
54223
+ };
54224
+ }
54225
+ if (widget.id !== 'praxis-rich-content') {
53548
54226
  return widget;
53549
54227
  }
53550
54228
  return {
@@ -56193,7 +56871,7 @@ class ResourceQuickConnectComponent {
56193
56871
  this.catalogError = false;
56194
56872
  this.cdr?.markForCheck();
56195
56873
  try {
56196
- const catalog = await firstValueFrom(this.discovery.getSchemaCatalog().pipe(timeout(30000)));
56874
+ const catalog = await firstValueFrom(this.discovery.getSchemaCatalog().pipe(timeout$1(30000)));
56197
56875
  const resources = new Map();
56198
56876
  for (const endpoint of catalog.endpoints ?? []) {
56199
56877
  // Ground only declared resource-level capability endpoints; never infer user intent.
@@ -56807,12 +57485,13 @@ function reconcileFormConfig(layout, serverDefs) {
56807
57485
  }
56808
57486
  }
56809
57487
  // Orphans: preserve explicit local fields; hide legacy schema fields that no
56810
- // longer exist on the server.
57488
+ // longer exist on the server. Retain their authored metadata/layout, but do
57489
+ // not submit or validate an invisible control outside the current schema.
56811
57490
  for (const [name, localField] of localByName) {
56812
57491
  if (!serverByName.has(name)) {
56813
57492
  merged.push(isLocalField(localField)
56814
57493
  ? localField
56815
- : { ...localField, formHidden: true });
57494
+ : { ...localField, formHidden: true, disabled: true, submitPolicy: 'omit' });
56816
57495
  }
56817
57496
  }
56818
57497
  // Preserve layout (sections/rows/columns) order, only affect fieldMetadata
@@ -57526,6 +58205,7 @@ class PraxisAppShellComponent {
57526
58205
  }
57527
58206
  showSearch = true;
57528
58207
  searchPlaceholder = 'Buscar no sistema ou executar comando...';
58208
+ clearSearchOnBlur = false;
57529
58209
  _footerStatus = signal(undefined, ...(ngDevMode ? [{ debugName: "_footerStatus" }] : /* istanbul ignore next */ []));
57530
58210
  set footerStatus(val) {
57531
58211
  this._footerStatus.set(val);
@@ -57589,11 +58269,15 @@ class PraxisAppShellComponent {
57589
58269
  this.searchQuery.set(value);
57590
58270
  this.searchChange.emit(value);
57591
58271
  }
58272
+ onSearchBlur() {
58273
+ if (this.clearSearchOnBlur) {
58274
+ this.searchQuery.set('');
58275
+ }
58276
+ }
57592
58277
  onSearchEnter() {
57593
58278
  const query = this.searchQuery().trim();
57594
- if (query) {
57595
- this.searchSubmit.emit(query);
57596
- }
58279
+ this.searchSubmit.emit(query);
58280
+ this.searchQuery.set('');
57597
58281
  }
57598
58282
  onUserClick() {
57599
58283
  if (this.user) {
@@ -57615,7 +58299,7 @@ class PraxisAppShellComponent {
57615
58299
  return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
57616
58300
  }
57617
58301
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisAppShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
57618
- 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", showCustomizationToggle: "showCustomizationToggle", isDarkTheme: "isDarkTheme", showSearch: "showSearch", searchPlaceholder: "searchPlaceholder", footerStatus: "footerStatus", isAnyLoading: "isAnyLoading", collapsed: "collapsed" }, outputs: { themeToggle: "themeToggle", customizationToggle: "customizationToggle", searchSubmit: "searchSubmit", searchChange: "searchChange", userProfileClick: "userProfileClick", collapsedChange: "collapsedChange", navItemClick: "navItemClick" }, host: { listeners: { "keydown.escape": "handleEscape()" } }, ngImport: i0, template: `
58302
+ 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", showCustomizationToggle: "showCustomizationToggle", isDarkTheme: "isDarkTheme", showSearch: "showSearch", searchPlaceholder: "searchPlaceholder", clearSearchOnBlur: "clearSearchOnBlur", footerStatus: "footerStatus", isAnyLoading: "isAnyLoading", collapsed: "collapsed" }, outputs: { themeToggle: "themeToggle", customizationToggle: "customizationToggle", searchSubmit: "searchSubmit", searchChange: "searchChange", userProfileClick: "userProfileClick", collapsedChange: "collapsedChange", navItemClick: "navItemClick" }, host: { listeners: { "keydown.escape": "handleEscape()" } }, ngImport: i0, template: `
57619
58303
  <div
57620
58304
  class="praxis-shell"
57621
58305
  [class.praxis-shell--dark]="isDarkTheme"
@@ -57812,6 +58496,7 @@ class PraxisAppShellComponent {
57812
58496
  [value]="searchQuery()"
57813
58497
  (input)="onSearchInput($any($event.target).value)"
57814
58498
  (keydown.enter)="onSearchEnter()"
58499
+ (blur)="onSearchBlur()"
57815
58500
  aria-label="Buscar no sistema"
57816
58501
  />
57817
58502
  </div>
@@ -58154,6 +58839,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
58154
58839
  [value]="searchQuery()"
58155
58840
  (input)="onSearchInput($any($event.target).value)"
58156
58841
  (keydown.enter)="onSearchEnter()"
58842
+ (blur)="onSearchBlur()"
58157
58843
  aria-label="Buscar no sistema"
58158
58844
  />
58159
58845
  </div>
@@ -58312,6 +58998,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
58312
58998
  type: Input
58313
58999
  }], searchPlaceholder: [{
58314
59000
  type: Input
59001
+ }], clearSearchOnBlur: [{
59002
+ type: Input
58315
59003
  }], footerStatus: [{
58316
59004
  type: Input
58317
59005
  }], isAnyLoading: [{