@praxisui/table 10.0.0-rc.19 → 10.0.0-rc.21

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.
@@ -9025,6 +9025,48 @@ const PRAXIS_TABLE_EDITOR_I18N_CONFIG = {
9025
9025
  'columns.renderer.microVisualization.guidance.horizontalSpace': 'Condicional: visualizações de tendência precisam de largura horizontal para manter a legibilidade; confira a largura configurada da coluna.',
9026
9026
  'columns.renderer.microVisualization.guidance.rowDensity': 'Condicional: comparison aumenta a altura da linha. Use apenas quando densidade não for um requisito principal.',
9027
9027
  'columns.renderer.microVisualization.guidance.stepLabels': 'Condicional: processFlow exige etapas reconhecíveis. Prefira o detalhe quando os rótulos forem essenciais.',
9028
+ 'columns.renderer.microVisualization.kind': 'Visualização',
9029
+ 'columns.renderer.microVisualization.kind.delta': 'Delta',
9030
+ 'columns.renderer.microVisualization.kind.bullet': 'Bullet (meta)',
9031
+ 'columns.renderer.microVisualization.kind.radial': 'Radial',
9032
+ 'columns.renderer.microVisualization.kind.harveyBall': 'Harvey ball',
9033
+ 'columns.renderer.microVisualization.kind.line': 'Linha',
9034
+ 'columns.renderer.microVisualization.kind.area': 'Área',
9035
+ 'columns.renderer.microVisualization.kind.column': 'Colunas',
9036
+ 'columns.renderer.microVisualization.kind.comparison': 'Comparação',
9037
+ 'columns.renderer.microVisualization.kind.stackedBar': 'Barra empilhada',
9038
+ 'columns.renderer.microVisualization.kind.processFlow': 'Fluxo de processo',
9039
+ 'columns.renderer.microVisualization.valueExpression': 'Expressão de valor',
9040
+ 'columns.renderer.microVisualization.pointsExpression': 'Expressão de pontos',
9041
+ 'columns.renderer.microVisualization.segmentsExpression': 'Expressão de segmentos',
9042
+ 'columns.renderer.microVisualization.itemsExpression': 'Expressão de itens',
9043
+ 'columns.renderer.microVisualization.totalExpression': 'Expressão de total',
9044
+ 'columns.renderer.microVisualization.targetExpression': 'Expressão de meta',
9045
+ 'columns.renderer.microVisualization.tone': 'Tom (cor)',
9046
+ 'columns.renderer.microVisualization.tone.auto': 'Automático (por faixas / semântico)',
9047
+ 'columns.renderer.microVisualization.tone.neutral': 'Neutro',
9048
+ 'columns.renderer.microVisualization.tone.info': 'Informativo',
9049
+ 'columns.renderer.microVisualization.tone.success': 'Sucesso',
9050
+ 'columns.renderer.microVisualization.tone.warning': 'Alerta',
9051
+ 'columns.renderer.microVisualization.tone.danger': 'Perigo',
9052
+ 'columns.renderer.microVisualization.tone.critical': 'Crítico',
9053
+ 'columns.renderer.microVisualization.tone.precedenceHint': 'No modo Automático, a cor vem das faixas de cor (thresholds) ou do sinal do valor. Um tom fixo substitui as faixas condicionais.',
9054
+ 'columns.renderer.microVisualization.fallbackText': 'Texto alternativo acessível',
9055
+ 'columns.renderer.microVisualization.hint': 'Use valor para indicadores, pontos para tendências, segmentos para barras empilhadas e itens para fluxos de processo. A tabela mantém o visual compacto e expõe o texto alternativo para tecnologias assistivas.',
9056
+ 'columns.renderer.microVisualization.thresholds.title': 'Faixas de cor (thresholds)',
9057
+ 'columns.renderer.microVisualization.thresholds.description': 'Avalia de cima para baixo (a primeira faixa compatível vence). Limites min e max são inclusivos (min <= valor <= max).',
9058
+ 'columns.renderer.microVisualization.thresholds.add': 'Adicionar faixa',
9059
+ 'columns.renderer.microVisualization.thresholds.preset': 'Preset semáforo (70+ / 40–69 / ≤39)',
9060
+ 'columns.renderer.microVisualization.thresholds.min': 'Mínimo (inclusivo)',
9061
+ 'columns.renderer.microVisualization.thresholds.max': 'Máximo (inclusivo)',
9062
+ 'columns.renderer.microVisualization.thresholds.tone': 'Tom da faixa',
9063
+ 'columns.renderer.microVisualization.thresholds.moveUp': 'Mover faixa para cima',
9064
+ 'columns.renderer.microVisualization.thresholds.moveDown': 'Mover faixa para baixo',
9065
+ 'columns.renderer.microVisualization.thresholds.remove': 'Remover faixa',
9066
+ 'columns.renderer.microVisualization.thresholds.empty': 'Nenhuma faixa configurada. Adicione faixas ou aplique o preset semáforo para colorir automaticamente por valor.',
9067
+ 'columns.renderer.microVisualization.thresholds.sampleValues': 'Valores de teste da prévia (separados por vírgula)',
9068
+ 'columns.renderer.microVisualization.validation.thresholdEmptyBounds': 'Microvisualização: a faixa {index} precisa definir uma condição (min, max, value ou equals).',
9069
+ 'columns.renderer.microVisualization.validation.thresholdMinGreaterThanMax': 'Microvisualização: na faixa {index}, o mínimo ({min}) não pode ser maior que o máximo ({max}).',
9028
9070
  },
9029
9071
  'en-US': {
9030
9072
  'filter.presentation.tab': 'Presentation',
@@ -10357,6 +10399,48 @@ const PRAXIS_TABLE_EDITOR_I18N_CONFIG = {
10357
10399
  'columns.renderer.microVisualization.guidance.horizontalSpace': 'Conditional: trend visualizations need horizontal space to remain legible; check the configured column width.',
10358
10400
  'columns.renderer.microVisualization.guidance.rowDensity': 'Conditional: comparison increases row height. Use it only when density is not a primary requirement.',
10359
10401
  'columns.renderer.microVisualization.guidance.stepLabels': 'Conditional: processFlow needs recognizable steps. Prefer the detail view when labels are essential.',
10402
+ 'columns.renderer.microVisualization.kind': 'Visualization',
10403
+ 'columns.renderer.microVisualization.kind.delta': 'Delta',
10404
+ 'columns.renderer.microVisualization.kind.bullet': 'Bullet',
10405
+ 'columns.renderer.microVisualization.kind.radial': 'Radial',
10406
+ 'columns.renderer.microVisualization.kind.harveyBall': 'Harvey ball',
10407
+ 'columns.renderer.microVisualization.kind.line': 'Line',
10408
+ 'columns.renderer.microVisualization.kind.area': 'Area',
10409
+ 'columns.renderer.microVisualization.kind.column': 'Column',
10410
+ 'columns.renderer.microVisualization.kind.comparison': 'Comparison',
10411
+ 'columns.renderer.microVisualization.kind.stackedBar': 'Stacked bar',
10412
+ 'columns.renderer.microVisualization.kind.processFlow': 'Process flow',
10413
+ 'columns.renderer.microVisualization.valueExpression': 'Value expression',
10414
+ 'columns.renderer.microVisualization.pointsExpression': 'Points expression',
10415
+ 'columns.renderer.microVisualization.segmentsExpression': 'Segments expression',
10416
+ 'columns.renderer.microVisualization.itemsExpression': 'Items expression',
10417
+ 'columns.renderer.microVisualization.totalExpression': 'Total expression',
10418
+ 'columns.renderer.microVisualization.targetExpression': 'Target expression',
10419
+ 'columns.renderer.microVisualization.tone': 'Tone',
10420
+ 'columns.renderer.microVisualization.tone.auto': 'Automatic (by thresholds / semantic)',
10421
+ 'columns.renderer.microVisualization.tone.neutral': 'Neutral',
10422
+ 'columns.renderer.microVisualization.tone.info': 'Info',
10423
+ 'columns.renderer.microVisualization.tone.success': 'Success',
10424
+ 'columns.renderer.microVisualization.tone.warning': 'Warning',
10425
+ 'columns.renderer.microVisualization.tone.danger': 'Danger',
10426
+ 'columns.renderer.microVisualization.tone.critical': 'Critical',
10427
+ 'columns.renderer.microVisualization.tone.precedenceHint': 'In Automatic mode, color is resolved from threshold bands or value sign. A fixed tone overrides conditional bands.',
10428
+ 'columns.renderer.microVisualization.fallbackText': 'Accessible fallback text',
10429
+ 'columns.renderer.microVisualization.hint': 'Use a value for indicators, points for trends, segments for stacked bars, and items for process flows. The table keeps the visual compact and exposes fallback text to assistive technology.',
10430
+ 'columns.renderer.microVisualization.thresholds.title': 'Color bands (thresholds)',
10431
+ 'columns.renderer.microVisualization.thresholds.description': 'Evaluates top-to-bottom (first matching band wins). Both min and max bounds are inclusive (min <= value <= max).',
10432
+ 'columns.renderer.microVisualization.thresholds.add': 'Add threshold',
10433
+ 'columns.renderer.microVisualization.thresholds.preset': 'Traffic-light preset (70+ / 40–69 / ≤39)',
10434
+ 'columns.renderer.microVisualization.thresholds.min': 'Min (inclusive)',
10435
+ 'columns.renderer.microVisualization.thresholds.max': 'Max (inclusive)',
10436
+ 'columns.renderer.microVisualization.thresholds.tone': 'Band tone',
10437
+ 'columns.renderer.microVisualization.thresholds.moveUp': 'Move threshold up',
10438
+ 'columns.renderer.microVisualization.thresholds.moveDown': 'Move threshold down',
10439
+ 'columns.renderer.microVisualization.thresholds.remove': 'Remove threshold',
10440
+ 'columns.renderer.microVisualization.thresholds.empty': 'No threshold bands configured. Add bands or apply the traffic-light preset to color values automatically.',
10441
+ 'columns.renderer.microVisualization.thresholds.sampleValues': 'Preview sample values (comma-separated)',
10442
+ 'columns.renderer.microVisualization.validation.thresholdEmptyBounds': 'Micro visualization: threshold {index} must define a condition (min, max, value or equals).',
10443
+ 'columns.renderer.microVisualization.validation.thresholdMinGreaterThanMax': 'Micro visualization: in threshold {index}, min ({min}) cannot be greater than max ({max}).',
10360
10444
  },
10361
10445
  },
10362
10446
  },
@@ -23293,7 +23377,7 @@ class PraxisTable {
23293
23377
  if (this.aiAdapter || this.aiAdapterLoadStarted)
23294
23378
  return;
23295
23379
  this.aiAdapterLoadStarted = true;
23296
- import('./praxisui-table-table-ai.adapter-ndYth7DX.mjs')
23380
+ import('./praxisui-table-table-ai.adapter-Dwq3cX1k.mjs')
23297
23381
  .then(({ TableAiAdapter }) => {
23298
23382
  if (!this.isAiAssistantEnabled()) {
23299
23383
  this.aiAssistantOpenAfterAdapterLoad = false;
@@ -24065,7 +24149,7 @@ class PraxisTable {
24065
24149
  initializeAiAssistantController() {
24066
24150
  if (!this.aiAdapter || this.aiAssistantController)
24067
24151
  return;
24068
- import('./praxisui-table-table-agentic-authoring-turn-flow-BQjoJKN_.mjs')
24152
+ import('./praxisui-table-table-agentic-authoring-turn-flow-DPJpzgFT.mjs')
24069
24153
  .then(({ TableAgenticAuthoringTurnFlow }) => {
24070
24154
  if (this.aiAssistantController || !this.aiAdapter)
24071
24155
  return;
@@ -45381,11 +45465,10 @@ class ColumnsConfigEditorComponent {
45381
45465
  ov.renderer.type = val;
45382
45466
  if (val === 'microVisualization') {
45383
45467
  ov.renderer.microVisualization = ov.renderer.microVisualization || {};
45384
- ov.renderer.microVisualization.visualization = {
45385
- ...this.createDefaultMicroVisualization(),
45386
- ...(ov.renderer.microVisualization.visualization || {}),
45387
- surface: 'table-cell',
45388
- };
45468
+ const existing = ov.renderer.microVisualization.visualization;
45469
+ ov.renderer.microVisualization.visualization = existing && typeof existing === 'object'
45470
+ ? { ...existing, surface: 'table-cell' }
45471
+ : { ...this.createDefaultMicroVisualization(), surface: 'table-cell' };
45389
45472
  }
45390
45473
  this.onColumnPropertyChange();
45391
45474
  }
@@ -45399,20 +45482,32 @@ class ColumnsConfigEditorComponent {
45399
45482
  return undefined;
45400
45483
  }
45401
45484
  }
45402
- setOverrideMicroVisualizationProp(path, value) {
45485
+ ensureOverrideMicroVisualizationObject() {
45403
45486
  const override = this.getConditionalOverrides()[this.condOverridesSelectedIndex];
45404
45487
  if (!override)
45405
- return;
45488
+ return null;
45406
45489
  override.renderer = override.renderer || {};
45407
45490
  override.renderer.type = 'microVisualization';
45408
45491
  override.renderer.microVisualization = override.renderer.microVisualization || {};
45409
- override.renderer.microVisualization.visualization = {
45410
- ...this.createDefaultMicroVisualization(),
45411
- ...(override.renderer.microVisualization.visualization || {}),
45412
- surface: 'table-cell',
45413
- };
45414
- override.renderer.microVisualization.visualization[path] = value;
45492
+ const existing = override.renderer.microVisualization.visualization;
45493
+ override.renderer.microVisualization.visualization =
45494
+ existing && typeof existing === 'object'
45495
+ ? { ...existing, surface: 'table-cell' }
45496
+ : { ...this.createDefaultMicroVisualization(), surface: 'table-cell' };
45497
+ return override.renderer.microVisualization.visualization;
45498
+ }
45499
+ setOverrideMicroVisualizationProp(path, value) {
45500
+ const visualization = this.ensureOverrideMicroVisualizationObject();
45501
+ if (!visualization)
45502
+ return;
45503
+ if (value === undefined || value === null || (path === 'tone' && value === '')) {
45504
+ delete visualization[path];
45505
+ }
45506
+ else {
45507
+ visualization[path] = value;
45508
+ }
45415
45509
  this.onColumnPropertyChange();
45510
+ this.validateRenderer();
45416
45511
  }
45417
45512
  getOverrideMicroVisualizationKind() {
45418
45513
  const kind = this.getOverrideMicroVisualizationProp('kind');
@@ -45442,6 +45537,105 @@ class ColumnsConfigEditorComponent {
45442
45537
  isOverrideMicroVisualizationTotalKind() {
45443
45538
  return ['bullet', 'radial', 'harveyBall'].includes(this.getOverrideMicroVisualizationKind());
45444
45539
  }
45540
+ isOverrideMicroVisualizationThresholdsSupported() {
45541
+ return ['delta', 'bullet', 'radial', 'harveyBall', 'line', 'area', 'column', 'comparison'].includes(this.getOverrideMicroVisualizationKind());
45542
+ }
45543
+ getOverrideMicroVisualizationThresholds() {
45544
+ const thresholds = this.getOverrideMicroVisualizationProp('thresholds');
45545
+ return Array.isArray(thresholds) ? thresholds : [];
45546
+ }
45547
+ addOverrideMicroVisualizationThreshold() {
45548
+ const visualization = this.ensureOverrideMicroVisualizationObject();
45549
+ if (!visualization)
45550
+ return;
45551
+ const current = Array.isArray(visualization['thresholds']) ? [...visualization['thresholds']] : [];
45552
+ const lastMax = current.length ? current[current.length - 1]?.max : undefined;
45553
+ const nextMin = typeof lastMax === 'number' && Number.isFinite(lastMax) ? lastMax + 1 : 0;
45554
+ current.push({
45555
+ min: nextMin,
45556
+ max: nextMin + 30,
45557
+ tone: 'warning',
45558
+ });
45559
+ visualization['thresholds'] = current;
45560
+ if (visualization['tone'] === 'info') {
45561
+ delete visualization['tone'];
45562
+ }
45563
+ this.onColumnPropertyChange();
45564
+ this.validateRenderer();
45565
+ }
45566
+ applyDefaultOverrideMicroVisualizationThresholds() {
45567
+ const visualization = this.ensureOverrideMicroVisualizationObject();
45568
+ if (!visualization)
45569
+ return;
45570
+ visualization['thresholds'] = [
45571
+ { min: 0, max: 39, tone: 'danger' },
45572
+ { min: 40, max: 69, tone: 'warning' },
45573
+ { min: 70, max: 100, tone: 'success' },
45574
+ ];
45575
+ delete visualization['tone'];
45576
+ this.onColumnPropertyChange();
45577
+ this.validateRenderer();
45578
+ }
45579
+ setOverrideMicroVisualizationThresholdNumber(index, bound, raw) {
45580
+ const visualization = this.ensureOverrideMicroVisualizationObject();
45581
+ if (!visualization || !Array.isArray(visualization['thresholds']) || !visualization['thresholds'][index])
45582
+ return;
45583
+ const list = visualization['thresholds'].map((item) => ({ ...item }));
45584
+ const trimmed = String(raw ?? '').trim();
45585
+ if (!trimmed) {
45586
+ delete list[index][bound];
45587
+ }
45588
+ else {
45589
+ const parsed = Number(trimmed);
45590
+ if (Number.isFinite(parsed)) {
45591
+ list[index][bound] = parsed;
45592
+ }
45593
+ }
45594
+ visualization['thresholds'] = list;
45595
+ this.onColumnPropertyChange();
45596
+ this.validateRenderer();
45597
+ }
45598
+ setOverrideMicroVisualizationThresholdTone(index, tone) {
45599
+ const visualization = this.ensureOverrideMicroVisualizationObject();
45600
+ if (!visualization || !Array.isArray(visualization['thresholds']) || !visualization['thresholds'][index])
45601
+ return;
45602
+ const list = visualization['thresholds'].map((item) => ({ ...item }));
45603
+ list[index].tone = tone;
45604
+ visualization['thresholds'] = list;
45605
+ this.onColumnPropertyChange();
45606
+ this.validateRenderer();
45607
+ }
45608
+ moveOverrideMicroVisualizationThreshold(index, delta) {
45609
+ const visualization = this.ensureOverrideMicroVisualizationObject();
45610
+ if (!visualization || !Array.isArray(visualization['thresholds']))
45611
+ return;
45612
+ const target = index + delta;
45613
+ const list = [...visualization['thresholds']];
45614
+ if (index < 0 || index >= list.length || target < 0 || target >= list.length)
45615
+ return;
45616
+ const [moved] = list.splice(index, 1);
45617
+ list.splice(target, 0, moved);
45618
+ visualization['thresholds'] = list;
45619
+ this.onColumnPropertyChange();
45620
+ this.validateRenderer();
45621
+ }
45622
+ removeOverrideMicroVisualizationThreshold(index) {
45623
+ const visualization = this.ensureOverrideMicroVisualizationObject();
45624
+ if (!visualization || !Array.isArray(visualization['thresholds']))
45625
+ return;
45626
+ const list = [...visualization['thresholds']];
45627
+ if (index < 0 || index >= list.length)
45628
+ return;
45629
+ list.splice(index, 1);
45630
+ if (list.length > 0) {
45631
+ visualization['thresholds'] = list;
45632
+ }
45633
+ else {
45634
+ delete visualization['thresholds'];
45635
+ }
45636
+ this.onColumnPropertyChange();
45637
+ this.validateRenderer();
45638
+ }
45445
45639
  getOverrideMicroVisualizationGuidanceText() {
45446
45640
  const constraint = this.getOverrideMicroVisualizationConstraint();
45447
45641
  if (this.getOverrideMicroVisualizationGuidance() === 'recommended') {
@@ -45489,11 +45683,15 @@ class ColumnsConfigEditorComponent {
45489
45683
  }
45490
45684
  // Renderer apply/preview state
45491
45685
  rendererValidationErrors = [];
45686
+ get validationErrors() {
45687
+ return this.rendererValidationErrors;
45688
+ }
45492
45689
  rendererImportWarnings = [];
45493
45690
  rendererIsValid = true;
45494
45691
  rendererPreviewEnabled = true;
45495
45692
  rendererAppliedSnapshot = null;
45496
45693
  previewRows = [];
45694
+ microVisualizationSampleValuesText = '15, 40, 69, 70, 98';
45497
45695
  refreshRendererFieldOptions() {
45498
45696
  const options = this.buildRendererFieldOptions();
45499
45697
  const filtered = this.onlyVisibleRendererFields ? options.filter((o) => o.isVisible) : options;
@@ -46336,22 +46534,175 @@ class ColumnsConfigEditorComponent {
46336
46534
  fallbackText: this.selectedColumn?.header || field,
46337
46535
  };
46338
46536
  }
46339
- setMicroVisualizationProp(path, value) {
46537
+ ensureMicroVisualizationObject() {
46340
46538
  if (!this.selectedColumn)
46341
- return;
46539
+ return null;
46342
46540
  const column = this.selectedColumn;
46343
46541
  column.renderer = column.renderer || { type: 'microVisualization' };
46344
46542
  column.renderer.type = 'microVisualization';
46345
46543
  column.renderer.microVisualization = column.renderer.microVisualization || {};
46346
- column.renderer.microVisualization.visualization = {
46347
- ...this.createDefaultMicroVisualization(),
46348
- ...(column.renderer.microVisualization.visualization || {}),
46349
- surface: 'table-cell',
46350
- };
46351
- column.renderer.microVisualization.visualization[path] = value;
46544
+ const existing = column.renderer.microVisualization.visualization;
46545
+ column.renderer.microVisualization.visualization =
46546
+ existing && typeof existing === 'object'
46547
+ ? { ...existing, surface: 'table-cell' }
46548
+ : { ...this.createDefaultMicroVisualization(), surface: 'table-cell' };
46549
+ return column.renderer.microVisualization.visualization;
46550
+ }
46551
+ setMicroVisualizationProp(path, value) {
46552
+ const visualization = this.ensureMicroVisualizationObject();
46553
+ if (!visualization)
46554
+ return;
46555
+ if (value === undefined || value === null || (path === 'tone' && value === '')) {
46556
+ delete visualization[path];
46557
+ }
46558
+ else {
46559
+ visualization[path] = value;
46560
+ }
46561
+ this.onColumnPropertyChange();
46562
+ this.validateRenderer();
46563
+ }
46564
+ isMicroVisualizationThresholdsSupported() {
46565
+ return ['delta', 'bullet', 'radial', 'harveyBall', 'line', 'area', 'column', 'comparison'].includes(this.getMicroVisualizationKind());
46566
+ }
46567
+ getMicroVisualizationThresholds() {
46568
+ const thresholds = this.getRendererProp('microVisualization', 'visualization.thresholds');
46569
+ return Array.isArray(thresholds) ? thresholds : [];
46570
+ }
46571
+ addMicroVisualizationThreshold() {
46572
+ const visualization = this.ensureMicroVisualizationObject();
46573
+ if (!visualization)
46574
+ return;
46575
+ const current = Array.isArray(visualization['thresholds']) ? [...visualization['thresholds']] : [];
46576
+ const lastMax = current.length ? current[current.length - 1]?.max : undefined;
46577
+ const nextMin = typeof lastMax === 'number' && Number.isFinite(lastMax) ? lastMax + 1 : 0;
46578
+ current.push({
46579
+ min: nextMin,
46580
+ max: nextMin + 30,
46581
+ tone: 'warning',
46582
+ });
46583
+ visualization['thresholds'] = current;
46584
+ if (visualization['tone'] === 'info') {
46585
+ delete visualization['tone'];
46586
+ }
46352
46587
  this.onColumnPropertyChange();
46353
46588
  this.validateRenderer();
46354
46589
  }
46590
+ applyDefaultMicroVisualizationThresholds() {
46591
+ const visualization = this.ensureMicroVisualizationObject();
46592
+ if (!visualization)
46593
+ return;
46594
+ visualization['thresholds'] = [
46595
+ { min: 0, max: 39, tone: 'danger' },
46596
+ { min: 40, max: 69, tone: 'warning' },
46597
+ { min: 70, max: 100, tone: 'success' },
46598
+ ];
46599
+ delete visualization['tone'];
46600
+ this.onColumnPropertyChange();
46601
+ this.validateRenderer();
46602
+ }
46603
+ setMicroVisualizationThresholdNumber(index, bound, raw) {
46604
+ const visualization = this.ensureMicroVisualizationObject();
46605
+ if (!visualization || !Array.isArray(visualization['thresholds']) || !visualization['thresholds'][index])
46606
+ return;
46607
+ const list = visualization['thresholds'].map((item) => ({ ...item }));
46608
+ const trimmed = String(raw ?? '').trim();
46609
+ if (!trimmed) {
46610
+ delete list[index][bound];
46611
+ }
46612
+ else {
46613
+ const parsed = Number(trimmed);
46614
+ if (Number.isFinite(parsed)) {
46615
+ list[index][bound] = parsed;
46616
+ }
46617
+ }
46618
+ visualization['thresholds'] = list;
46619
+ this.onColumnPropertyChange();
46620
+ this.validateRenderer();
46621
+ }
46622
+ setMicroVisualizationThresholdTone(index, tone) {
46623
+ const visualization = this.ensureMicroVisualizationObject();
46624
+ if (!visualization || !Array.isArray(visualization['thresholds']) || !visualization['thresholds'][index])
46625
+ return;
46626
+ const list = visualization['thresholds'].map((item) => ({ ...item }));
46627
+ list[index].tone = tone;
46628
+ visualization['thresholds'] = list;
46629
+ this.onColumnPropertyChange();
46630
+ this.validateRenderer();
46631
+ }
46632
+ moveMicroVisualizationThreshold(index, delta) {
46633
+ const visualization = this.ensureMicroVisualizationObject();
46634
+ if (!visualization || !Array.isArray(visualization['thresholds']))
46635
+ return;
46636
+ const target = index + delta;
46637
+ const list = [...visualization['thresholds']];
46638
+ if (index < 0 || index >= list.length || target < 0 || target >= list.length)
46639
+ return;
46640
+ const [moved] = list.splice(index, 1);
46641
+ list.splice(target, 0, moved);
46642
+ visualization['thresholds'] = list;
46643
+ this.onColumnPropertyChange();
46644
+ this.validateRenderer();
46645
+ }
46646
+ removeMicroVisualizationThreshold(index) {
46647
+ const visualization = this.ensureMicroVisualizationObject();
46648
+ if (!visualization || !Array.isArray(visualization['thresholds']))
46649
+ return;
46650
+ const list = [...visualization['thresholds']];
46651
+ if (index < 0 || index >= list.length)
46652
+ return;
46653
+ list.splice(index, 1);
46654
+ if (list.length > 0) {
46655
+ visualization['thresholds'] = list;
46656
+ }
46657
+ else {
46658
+ delete visualization['thresholds'];
46659
+ }
46660
+ this.onColumnPropertyChange();
46661
+ this.validateRenderer();
46662
+ }
46663
+ setMicroVisualizationSampleValuesText(raw) {
46664
+ this.microVisualizationSampleValuesText = raw;
46665
+ this.cdr.markForCheck();
46666
+ }
46667
+ getMicroVisualizationSampleValues() {
46668
+ const parsed = String(this.microVisualizationSampleValuesText || '')
46669
+ .split(',')
46670
+ .map((part) => Number(part.trim()))
46671
+ .filter((num) => Number.isFinite(num));
46672
+ return parsed.length > 0 ? parsed.slice(0, 8) : [15, 40, 69, 70, 98];
46673
+ }
46674
+ getPreviewMicroVisualizationHtmlForValue(sampleValue) {
46675
+ const field = this.selectedColumn?.field || 'value';
46676
+ const baseRow = this.previewRows?.[0] || {};
46677
+ const sampleRow = {
46678
+ ...baseRow,
46679
+ [field]: sampleValue,
46680
+ value: sampleValue,
46681
+ total: typeof baseRow?.total === 'number' ? baseRow.total : 100,
46682
+ target: typeof baseRow?.target === 'number' ? baseRow.target : 80,
46683
+ baseline: typeof baseRow?.baseline === 'number' ? baseRow.baseline : 50,
46684
+ };
46685
+ const visualization = this.selectedColumn?.renderer?.microVisualization?.visualization;
46686
+ if (!visualization)
46687
+ return '';
46688
+ const resolved = { ...visualization };
46689
+ const expressions = [
46690
+ ['value', 'valueExpr'], ['valueSuffix', 'valueSuffixExpr'], ['total', 'totalExpr'], ['target', 'targetExpr'], ['baseline', 'baselineExpr'],
46691
+ ['points', 'pointsExpr'], ['segments', 'segmentsExpr'], ['thresholds', 'thresholdsExpr'], ['items', 'itemsExpr'],
46692
+ ['tone', 'toneExpr'], ['ariaLabel', 'ariaLabelExpr'], ['fallbackText', 'fallbackTextExpr'],
46693
+ ];
46694
+ for (const [property, expression] of expressions) {
46695
+ if (!resolved[expression])
46696
+ continue;
46697
+ const evaluated = this.evalMicroVisualizationExpression(resolved[expression], sampleRow);
46698
+ if (evaluated !== undefined)
46699
+ resolved[property] = evaluated;
46700
+ }
46701
+ if (typeof resolved['value'] !== 'number' || !Number.isFinite(resolved['value'])) {
46702
+ resolved['value'] = sampleValue;
46703
+ }
46704
+ return renderPraxisPresentationVisualizationHtml(resolved);
46705
+ }
46355
46706
  // =============================
46356
46707
  // Renderer: validation/apply/import/export/preview
46357
46708
  // =============================
@@ -46952,6 +47303,18 @@ class ColumnsConfigEditorComponent {
46952
47303
  if (evaluated !== undefined)
46953
47304
  resolved[property] = evaluated;
46954
47305
  }
47306
+ if (['delta', 'bullet', 'radial', 'harveyBall'].includes(resolved['kind']) &&
47307
+ (typeof resolved['value'] !== 'number' || !Number.isFinite(resolved['value']))) {
47308
+ const parsedValue = Number(resolved['value']);
47309
+ if (Number.isFinite(parsedValue) && String(resolved['value'] ?? '').trim() !== '') {
47310
+ resolved['value'] = parsedValue;
47311
+ }
47312
+ else {
47313
+ const sampleValues = this.getMicroVisualizationSampleValues();
47314
+ const rowIdx = Math.max(0, this.previewRows.indexOf(row));
47315
+ resolved['value'] = sampleValues[rowIdx % sampleValues.length] ?? 70;
47316
+ }
47317
+ }
46955
47318
  return renderPraxisPresentationVisualizationHtml(resolved);
46956
47319
  }
46957
47320
  getMicroVisualizationGuidance() {
@@ -47098,14 +47461,39 @@ class ColumnsConfigEditorComponent {
47098
47461
  getPreviewHtml(row) { const ht = this.selectedColumn?.renderer?.html; if (!ht?.template)
47099
47462
  return String(ht?.emptyFallback || ''); return String(ht.template).replace(/\[\[([A-Za-z0-9_\.]+)\]\]/g, (_m, g1) => { const v = this.getNested(row, g1); return String(v ?? ''); }); }
47100
47463
  trackByIdx = (_, item) => this.previewRows.indexOf(item);
47464
+ validateMicroVisualizationThresholds(thresholds, errs) {
47465
+ if (!Array.isArray(thresholds))
47466
+ return;
47467
+ thresholds.forEach((entry, idx) => {
47468
+ const hasMin = typeof entry?.min === 'number' && Number.isFinite(entry.min);
47469
+ const hasMax = typeof entry?.max === 'number' && Number.isFinite(entry.max);
47470
+ const hasValue = typeof entry?.value === 'number' && Number.isFinite(entry.value);
47471
+ const hasEquals = entry?.equals !== undefined;
47472
+ if (!hasMin && !hasMax && !hasValue && !hasEquals) {
47473
+ errs.push(this.tx('columns.renderer.microVisualization.validation.thresholdEmptyBounds', 'Micro visualization: threshold {index} must define a condition (min, max, value or equals).').replace('{index}', String(idx + 1)));
47474
+ }
47475
+ else if (hasMin && hasMax && entry.min > entry.max) {
47476
+ errs.push(this.tx('columns.renderer.microVisualization.validation.thresholdMinGreaterThanMax', 'Micro visualization: in threshold {index}, min ({min}) cannot be greater than max ({max}).')
47477
+ .replace('{index}', String(idx + 1))
47478
+ .replace('{min}', String(entry.min))
47479
+ .replace('{max}', String(entry.max)));
47480
+ }
47481
+ });
47482
+ }
47101
47483
  validateRenderer() {
47102
47484
  const errs = [];
47103
47485
  const col = this.selectedColumn;
47486
+ const overrides = Array.isArray(col?.conditionalRenderers) ? col.conditionalRenderers : [];
47487
+ for (const ov of overrides) {
47488
+ if (ov?.renderer?.type === 'microVisualization') {
47489
+ this.validateMicroVisualizationThresholds(ov.renderer.microVisualization?.visualization?.thresholds, errs);
47490
+ }
47491
+ }
47104
47492
  const r = col?.renderer || null;
47105
47493
  if (!r || !r.type) {
47106
- // no renderer -> valid
47107
- this.rendererValidationErrors = [];
47108
- this.rendererIsValid = true;
47494
+ // no renderer -> valid unless conditional override has threshold errors
47495
+ this.rendererValidationErrors = errs;
47496
+ this.rendererIsValid = errs.length === 0;
47109
47497
  this.cdr.markForCheck();
47110
47498
  return;
47111
47499
  }
@@ -47211,6 +47599,7 @@ class ColumnsConfigEditorComponent {
47211
47599
  if (visualization.kind === 'processFlow' && !hasItems) {
47212
47600
  errs.push('Microvisualização: process flow exige items ou itemsExpr.');
47213
47601
  }
47602
+ this.validateMicroVisualizationThresholds(visualization.thresholds, errs);
47214
47603
  }
47215
47604
  }
47216
47605
  if (r.type === 'avatar') {
@@ -49089,18 +49478,162 @@ class ColumnsConfigEditorComponent {
49089
49478
  }
49090
49479
  <mat-form-field appearance="outline">
49091
49480
  <mat-label>{{ tx('columns.renderer.microVisualization.tone', 'Tone') }}</mat-label>
49092
- <mat-select [ngModel]="getRendererProp('microVisualization','visualization.tone') || 'info'" [ngModelOptions]="{standalone: true}" (ngModelChange)="setMicroVisualizationProp('tone', $event)">
49481
+ <mat-select [ngModel]="getRendererProp('microVisualization','visualization.tone') ?? ''" [ngModelOptions]="{standalone: true}" (ngModelChange)="setMicroVisualizationProp('tone', $event)">
49482
+ <mat-option [value]="''">{{ tx('columns.renderer.microVisualization.tone.auto', 'Automatic (by thresholds / semantic)') }}</mat-option>
49093
49483
  <mat-option value="neutral">{{ tx('columns.renderer.microVisualization.tone.neutral', 'Neutral') }}</mat-option>
49094
49484
  <mat-option value="info">{{ tx('columns.renderer.microVisualization.tone.info', 'Info') }}</mat-option>
49095
49485
  <mat-option value="success">{{ tx('columns.renderer.microVisualization.tone.success', 'Success') }}</mat-option>
49096
49486
  <mat-option value="warning">{{ tx('columns.renderer.microVisualization.tone.warning', 'Warning') }}</mat-option>
49097
49487
  <mat-option value="danger">{{ tx('columns.renderer.microVisualization.tone.danger', 'Danger') }}</mat-option>
49488
+ <mat-option value="critical">{{ tx('columns.renderer.microVisualization.tone.critical', 'Critical') }}</mat-option>
49098
49489
  </mat-select>
49099
49490
  </mat-form-field>
49100
49491
  <mat-form-field appearance="outline">
49101
49492
  <mat-label>{{ tx('columns.renderer.microVisualization.fallbackText', 'Accessible fallback text') }}</mat-label>
49102
49493
  <input matInput [value]="getRendererProp('microVisualization','visualization.fallbackText') || ''" (input)="setMicroVisualizationProp('fallbackText', $any($event.target).value)" />
49103
49494
  </mat-form-field>
49495
+ <div class="renderer-hint" style="grid-column: 1/-1; opacity:.78;">
49496
+ {{ tx('columns.renderer.microVisualization.tone.precedenceHint', 'In Automatic mode, color is resolved from threshold bands or value sign. A fixed tone overrides conditional bands.') }}
49497
+ </div>
49498
+ @if (isMicroVisualizationThresholdsSupported()) {
49499
+ <div
49500
+ data-testid="micro-viz-thresholds-section"
49501
+ style="grid-column: 1/-1; display: grid; gap: 10px; padding: 12px; border: 1px solid var(--md-sys-color-outline-variant); border-radius: 8px;"
49502
+ >
49503
+ <div style="display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap;">
49504
+ <div>
49505
+ <div style="font-weight: 600;">
49506
+ {{ tx('columns.renderer.microVisualization.thresholds.title', 'Color bands (thresholds)') }}
49507
+ </div>
49508
+ <div class="renderer-hint" style="opacity: .78;">
49509
+ {{ tx('columns.renderer.microVisualization.thresholds.description', 'Evaluates top-to-bottom (first matching band wins). Both min and max bounds are inclusive (min <= value <= max).') }}
49510
+ </div>
49511
+ </div>
49512
+ <div style="display: flex; gap: 8px; flex-wrap: wrap;">
49513
+ <button
49514
+ mat-stroked-button
49515
+ type="button"
49516
+ data-testid="micro-viz-add-threshold"
49517
+ (click)="addMicroVisualizationThreshold()"
49518
+ >
49519
+ <mat-icon>add</mat-icon>
49520
+ {{ tx('columns.renderer.microVisualization.thresholds.add', 'Add threshold') }}
49521
+ </button>
49522
+ <button
49523
+ mat-stroked-button
49524
+ type="button"
49525
+ data-testid="micro-viz-preset-thresholds"
49526
+ (click)="applyDefaultMicroVisualizationThresholds()"
49527
+ >
49528
+ <mat-icon>traffic</mat-icon>
49529
+ {{ tx('columns.renderer.microVisualization.thresholds.preset', 'Traffic-light preset (70+ / 40–69 / ≤39)') }}
49530
+ </button>
49531
+ </div>
49532
+ </div>
49533
+ @if (!getMicroVisualizationThresholds().length) {
49534
+ <div class="renderer-hint" style="opacity: .75;">
49535
+ {{ tx('columns.renderer.microVisualization.thresholds.empty', 'No threshold bands configured. Add bands or apply the traffic-light preset to color values automatically.') }}
49536
+ </div>
49537
+ }
49538
+ @for (threshold of getMicroVisualizationThresholds(); track $index) {
49539
+ <div
49540
+ data-testid="micro-viz-threshold-row"
49541
+ style="display: grid; grid-template-columns: minmax(110px, 1fr) minmax(110px, 1fr) minmax(140px, 1fr) auto; gap: 8px; align-items: center;"
49542
+ >
49543
+ <mat-form-field appearance="outline">
49544
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.min', 'Min (inclusive)') }}</mat-label>
49545
+ <input
49546
+ matInput
49547
+ type="number"
49548
+ data-testid="micro-viz-threshold-min"
49549
+ [value]="threshold.min ?? ''"
49550
+ (input)="setMicroVisualizationThresholdNumber($index, 'min', $any($event.target).value)"
49551
+ />
49552
+ </mat-form-field>
49553
+ <mat-form-field appearance="outline">
49554
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.max', 'Max (inclusive)') }}</mat-label>
49555
+ <input
49556
+ matInput
49557
+ type="number"
49558
+ data-testid="micro-viz-threshold-max"
49559
+ [value]="threshold.max ?? ''"
49560
+ (input)="setMicroVisualizationThresholdNumber($index, 'max', $any($event.target).value)"
49561
+ />
49562
+ </mat-form-field>
49563
+ <mat-form-field appearance="outline">
49564
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.tone', 'Band tone') }}</mat-label>
49565
+ <mat-select
49566
+ data-testid="micro-viz-threshold-tone"
49567
+ [ngModel]="threshold.tone || 'info'"
49568
+ [ngModelOptions]="{standalone: true}"
49569
+ (ngModelChange)="setMicroVisualizationThresholdTone($index, $event)"
49570
+ >
49571
+ <mat-option value="success">{{ tx('columns.renderer.microVisualization.tone.success', 'Success') }}</mat-option>
49572
+ <mat-option value="warning">{{ tx('columns.renderer.microVisualization.tone.warning', 'Warning') }}</mat-option>
49573
+ <mat-option value="danger">{{ tx('columns.renderer.microVisualization.tone.danger', 'Danger') }}</mat-option>
49574
+ <mat-option value="critical">{{ tx('columns.renderer.microVisualization.tone.critical', 'Critical') }}</mat-option>
49575
+ <mat-option value="info">{{ tx('columns.renderer.microVisualization.tone.info', 'Info') }}</mat-option>
49576
+ <mat-option value="neutral">{{ tx('columns.renderer.microVisualization.tone.neutral', 'Neutral') }}</mat-option>
49577
+ </mat-select>
49578
+ </mat-form-field>
49579
+ <div style="display: flex; gap: 4px; align-items: center;">
49580
+ <button
49581
+ praxisIconButton="arrow_upward"
49582
+ size="dense"
49583
+ type="button"
49584
+ data-testid="micro-viz-threshold-up"
49585
+ [disabled]="$index === 0"
49586
+ [matTooltip]="tx('columns.renderer.microVisualization.thresholds.moveUp', 'Move threshold up')"
49587
+ [attr.aria-label]="tx('columns.renderer.microVisualization.thresholds.moveUp', 'Move threshold up')"
49588
+ (click)="moveMicroVisualizationThreshold($index, -1)"
49589
+ ></button>
49590
+ <button
49591
+ praxisIconButton="arrow_downward"
49592
+ size="dense"
49593
+ type="button"
49594
+ data-testid="micro-viz-threshold-down"
49595
+ [disabled]="$index === getMicroVisualizationThresholds().length - 1"
49596
+ [matTooltip]="tx('columns.renderer.microVisualization.thresholds.moveDown', 'Move threshold down')"
49597
+ [attr.aria-label]="tx('columns.renderer.microVisualization.thresholds.moveDown', 'Move threshold down')"
49598
+ (click)="moveMicroVisualizationThreshold($index, 1)"
49599
+ ></button>
49600
+ <button
49601
+ praxisIconButton="delete_outline"
49602
+ size="dense"
49603
+ type="button"
49604
+ data-testid="micro-viz-threshold-remove"
49605
+ [matTooltip]="tx('columns.renderer.microVisualization.thresholds.remove', 'Remove threshold')"
49606
+ [attr.aria-label]="tx('columns.renderer.microVisualization.thresholds.remove', 'Remove threshold')"
49607
+ (click)="removeMicroVisualizationThreshold($index)"
49608
+ ></button>
49609
+ </div>
49610
+ </div>
49611
+ }
49612
+ @if (isMicroVisualizationValueKind()) {
49613
+ <mat-form-field appearance="outline">
49614
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.sampleValues', 'Preview sample values (comma-separated)') }}</mat-label>
49615
+ <input
49616
+ matInput
49617
+ data-testid="micro-viz-sample-values-input"
49618
+ [value]="microVisualizationSampleValuesText"
49619
+ (input)="setMicroVisualizationSampleValuesText($any($event.target).value)"
49620
+ />
49621
+ </mat-form-field>
49622
+ <div
49623
+ data-testid="micro-viz-sample-preview"
49624
+ style="display: flex; gap: 8px; flex-wrap: wrap; align-items: center;"
49625
+ >
49626
+ @for (sampleVal of getMicroVisualizationSampleValues(); track $index) {
49627
+ <span
49628
+ class="pfx-micro-viz-cell"
49629
+ [attr.data-sample-value]="sampleVal"
49630
+ [innerHTML]="getPreviewMicroVisualizationHtmlForValue(sampleVal)"
49631
+ ></span>
49632
+ }
49633
+ </div>
49634
+ }
49635
+ </div>
49636
+ }
49104
49637
  <div class="renderer-hint" style="grid-column: 1/-1; opacity:.78;">
49105
49638
  {{ tx('columns.renderer.microVisualization.hint', 'Use a value for indicators, points for trends, segments for stacked bars, and items for process flows. The table keeps the visual compact and exposes fallback text to assistive technology.') }}
49106
49639
  </div>
@@ -50001,14 +50534,79 @@ class ColumnsConfigEditorComponent {
50001
50534
  }
50002
50535
  <mat-form-field appearance="outline">
50003
50536
  <mat-label>{{ tx('columns.renderer.microVisualization.tone', 'Tone') }}</mat-label>
50004
- <mat-select [ngModel]="getOverrideMicroVisualizationProp('tone') || 'info'" [ngModelOptions]="{standalone:true}" (ngModelChange)="setOverrideMicroVisualizationProp('tone', $event)">
50537
+ <mat-select [ngModel]="getOverrideMicroVisualizationProp('tone') || ''" [ngModelOptions]="{standalone:true}" (ngModelChange)="setOverrideMicroVisualizationProp('tone', $event || undefined)">
50538
+ <mat-option value="">{{ tx('columns.renderer.microVisualization.tone.auto', 'Automatic (thresholds / default)') }}</mat-option>
50005
50539
  <mat-option value="neutral">{{ tx('columns.renderer.microVisualization.tone.neutral', 'Neutral') }}</mat-option>
50006
50540
  <mat-option value="info">{{ tx('columns.renderer.microVisualization.tone.info', 'Info') }}</mat-option>
50007
50541
  <mat-option value="success">{{ tx('columns.renderer.microVisualization.tone.success', 'Success') }}</mat-option>
50008
50542
  <mat-option value="warning">{{ tx('columns.renderer.microVisualization.tone.warning', 'Warning') }}</mat-option>
50009
50543
  <mat-option value="danger">{{ tx('columns.renderer.microVisualization.tone.danger', 'Danger') }}</mat-option>
50544
+ <mat-option value="critical">{{ tx('columns.renderer.microVisualization.tone.critical', 'Critical') }}</mat-option>
50010
50545
  </mat-select>
50546
+ <mat-hint>{{ tx('columns.renderer.microVisualization.tone.precedenceHint', 'Fixed tone overrides thresholds. Choose Automatic to let color bands control the tone.') }}</mat-hint>
50011
50547
  </mat-form-field>
50548
+ @if (isOverrideMicroVisualizationThresholdsSupported()) {
50549
+ <div class="micro-viz-thresholds-section" data-testid="override-micro-viz-thresholds-section">
50550
+ <div class="compose-header">
50551
+ <div>
50552
+ <div class="section-subtitle">{{ tx('columns.renderer.microVisualization.thresholds.title', 'Color bands (thresholds)') }}</div>
50553
+ <div class="field-hint">{{ tx('columns.renderer.microVisualization.thresholds.hint', 'First matching band wins (min <= value <= max). Leave min or max empty for open-ended ranges.') }}</div>
50554
+ </div>
50555
+ <div style="display:flex; gap:8px; flex-wrap:wrap;">
50556
+ <button mat-stroked-button type="button" data-testid="override-micro-viz-preset-thresholds" (click)="applyDefaultOverrideMicroVisualizationThresholds()">
50557
+ <mat-icon>palette</mat-icon>
50558
+ {{ tx('columns.renderer.microVisualization.thresholds.applyPreset', 'Traffic-light preset') }}
50559
+ </button>
50560
+ <button mat-stroked-button type="button" data-testid="override-micro-viz-add-threshold" (click)="addOverrideMicroVisualizationThreshold()">
50561
+ <mat-icon>add</mat-icon>
50562
+ {{ tx('columns.renderer.microVisualization.thresholds.add', 'Add band') }}
50563
+ </button>
50564
+ </div>
50565
+ </div>
50566
+ @if (getOverrideMicroVisualizationProp('tone') && getOverrideMicroVisualizationThresholds().length > 0) {
50567
+ <div class="field-hint micro-viz-guidance is-conditional">
50568
+ {{ tx('columns.renderer.microVisualization.thresholds.overriddenByToneWarning', 'A fixed tone is selected above and will override these color bands. Switch Tone to Automatic so thresholds take effect.') }}
50569
+ </div>
50570
+ }
50571
+ @if (getOverrideMicroVisualizationThresholds().length === 0) {
50572
+ <div class="field-hint">{{ tx('columns.renderer.microVisualization.thresholds.empty', 'No color bands configured.') }}</div>
50573
+ }
50574
+ @for (threshold of getOverrideMicroVisualizationThresholds(); track $index; let idx = $index) {
50575
+ <div class="form-row three-columns micro-viz-threshold-row" [attr.data-testid]="'override-micro-viz-threshold-row-' + idx">
50576
+ <mat-form-field appearance="outline">
50577
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.min', 'Min') }}</mat-label>
50578
+ <input matInput type="number" [value]="threshold.min ?? ''" (input)="setOverrideMicroVisualizationThresholdNumber(idx, 'min', $any($event.target).value)" />
50579
+ </mat-form-field>
50580
+ <mat-form-field appearance="outline">
50581
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.max', 'Max') }}</mat-label>
50582
+ <input matInput type="number" [value]="threshold.max ?? ''" (input)="setOverrideMicroVisualizationThresholdNumber(idx, 'max', $any($event.target).value)" />
50583
+ </mat-form-field>
50584
+ <div style="display:flex; gap:6px; align-items:flex-start;">
50585
+ <mat-form-field appearance="outline" style="flex:1;">
50586
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.tone', 'Band tone') }}</mat-label>
50587
+ <mat-select [ngModel]="threshold.tone || 'neutral'" [ngModelOptions]="{standalone:true}" (ngModelChange)="setOverrideMicroVisualizationThresholdTone(idx, $event)">
50588
+ <mat-option value="neutral">{{ tx('columns.renderer.microVisualization.tone.neutral', 'Neutral') }}</mat-option>
50589
+ <mat-option value="info">{{ tx('columns.renderer.microVisualization.tone.info', 'Info') }}</mat-option>
50590
+ <mat-option value="success">{{ tx('columns.renderer.microVisualization.tone.success', 'Success') }}</mat-option>
50591
+ <mat-option value="warning">{{ tx('columns.renderer.microVisualization.tone.warning', 'Warning') }}</mat-option>
50592
+ <mat-option value="danger">{{ tx('columns.renderer.microVisualization.tone.danger', 'Danger') }}</mat-option>
50593
+ <mat-option value="critical">{{ tx('columns.renderer.microVisualization.tone.critical', 'Critical') }}</mat-option>
50594
+ </mat-select>
50595
+ </mat-form-field>
50596
+ <button mat-icon-button type="button" (click)="moveOverrideMicroVisualizationThreshold(idx, -1)" [disabled]="idx === 0" [attr.aria-label]="tx('columns.renderer.microVisualization.thresholds.moveUp', 'Move band up')">
50597
+ <mat-icon>arrow_upward</mat-icon>
50598
+ </button>
50599
+ <button mat-icon-button type="button" (click)="moveOverrideMicroVisualizationThreshold(idx, 1)" [disabled]="idx === getOverrideMicroVisualizationThresholds().length - 1" [attr.aria-label]="tx('columns.renderer.microVisualization.thresholds.moveDown', 'Move band down')">
50600
+ <mat-icon>arrow_downward</mat-icon>
50601
+ </button>
50602
+ <button mat-icon-button type="button" color="warn" (click)="removeOverrideMicroVisualizationThreshold(idx)" [attr.aria-label]="tx('columns.renderer.microVisualization.thresholds.remove', 'Remove band')">
50603
+ <mat-icon>delete</mat-icon>
50604
+ </button>
50605
+ </div>
50606
+ </div>
50607
+ }
50608
+ </div>
50609
+ }
50012
50610
  <mat-form-field appearance="outline">
50013
50611
  <mat-label>{{ tx('columns.renderer.microVisualization.fallbackText', 'Accessible fallback text') }}</mat-label>
50014
50612
  <input matInput [value]="getOverrideMicroVisualizationProp('fallbackText') || ''" (input)="setOverrideMicroVisualizationProp('fallbackText', $any($event.target).value)" />
@@ -50024,7 +50622,7 @@ class ColumnsConfigEditorComponent {
50024
50622
  </div>
50025
50623
  </div>
50026
50624
  </div>
50027
- `, isInline: true, styles: [":host{display:block;min-width:0;container-type:inline-size;container-name:table-columns-editor}.columns-config-editor{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;gap:16px}.educational-card{background:var(--md-sys-color-surface-container-low);border-left:4px solid var(--md-sys-color-primary);margin-bottom:12px}.inline-icon{font-size:18px;width:18px;height:18px;color:var(--md-sys-color-primary)}.help-icon-button{vertical-align:middle}.global-config-accordion{margin-bottom:16px}.global-config-panel{background:var(--md-sys-color-surface-container);border-left:4px solid var(--md-sys-color-secondary)}.config-panel-content{padding:16px 20px 20px}.config-layout{display:flex;flex-wrap:wrap;gap:24px;align-items:flex-start}.config-group{display:flex;flex-direction:column;min-width:0}.config-group:not(.features-group){flex:0 0 auto}.features-group{flex:1 1 300px;min-width:300px}.control-section{display:flex;flex-direction:column;gap:8px}.control-label{font-size:.875rem;font-weight:500;color:var(--md-sys-color-on-surface);margin:0}.control-status{font-weight:400;color:var(--md-sys-color-on-surface-variant);font-size:.75rem;margin-left:4px}.visibility-toggle-group,.alignment-toggle-group{display:flex}.visibility-toggle-group .mat-button-toggle{min-width:90px;height:40px}.alignment-toggle-group .mat-button-toggle{min-width:56px;width:auto;height:40px;padding:0 8px}.features-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin-top:4px}.feature-checkbox{justify-self:start}.master-detail-layout{min-width:0;display:flex;flex:1;gap:16px;min-height:0}.master-panel{flex:0 0 350px;display:flex;flex-direction:column;background:var(--md-sys-color-surface-container-low);border-radius:8px;overflow:hidden}.list-header{display:flex;justify-content:space-between;align-items:center;padding:16px;background:var(--md-sys-color-surface-container);border-bottom:1px solid var(--md-sys-color-outline-variant)}.list-header h3{margin:0;font-size:1.1rem;font-weight:500}.add-column-fab{transform:scale(.7)}.columns-list-container{flex:1;overflow-y:auto}.columns-list{padding:0}.column-item{border-bottom:1px solid var(--md-sys-color-outline-variant);cursor:pointer;transition:background-color .2s}.column-item:hover{background:var(--md-sys-color-surface-container)}.column-item.selected{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.column-item-content{display:flex;justify-content:space-between;align-items:center;width:100%;padding:12px 16px}.column-main-info{flex:1;display:flex;flex-direction:column;gap:4px}.column-header{display:flex;align-items:center;gap:8px;width:100%}.drag-handle{cursor:grab;color:var(--md-sys-color-on-surface-variant);font-size:18px;flex-shrink:0}.column-text-info{flex:1;display:flex;flex-direction:column;gap:2px;min-width:0}.column-title{font-weight:600;font-size:.9rem;color:var(--md-sys-color-on-surface);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.column-field{font-size:.75rem;color:var(--md-sys-color-on-surface-variant);font-family:monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.column-indicators{display:flex;align-items:center;gap:6px;flex-shrink:0}.column-type-icon,.mapping-indicator-icon,.renderer-indicator-icon{font-size:16px;color:var(--md-sys-color-on-surface-variant)}.visibility-checkbox{transform:scale(.9)}.remove-button{color:var(--md-sys-color-error);opacity:.7;transition:opacity .2s}.remove-button:hover{opacity:1}.detail-panel{min-width:0;flex:1;display:flex;flex-direction:column;background:var(--md-sys-color-surface-container-low);border-radius:8px;overflow:hidden}.no-selection-message{display:flex;flex-direction:column;align-items:center;justify-content:center;flex:1;text-align:center;padding:48px;color:var(--md-sys-color-on-surface-variant)}.no-selection-message mat-icon{font-size:48px;width:48px;height:48px;margin-bottom:16px}.editor-header{padding:16px 24px;background:var(--md-sys-color-surface-container);border-bottom:1px solid var(--md-sys-color-outline-variant)}.editor-title{margin:0;font-size:1.1rem;font-weight:500;color:var(--md-sys-color-on-surface)}.column-form{flex:1;padding:24px;overflow:visible}.form-row{display:flex;gap:16px;margin-bottom:24px;align-items:flex-start}.field-input,.header-input,.width-input,.data-type-input{flex:1}.header-input{flex:2}.alignment-group,.sticky-group{display:flex;flex-direction:column;gap:8px}.control-label{font-size:.875rem;font-weight:500;color:var(--md-sys-color-on-surface)}.renderer-grid,.renderer-config{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(220px,100%),1fr));gap:12px}.compose-preset-panel{grid-column:1/-1;display:grid;gap:12px;padding:12px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface-container-low)}.compose-preset-copy,.compose-editor-heading{display:grid;gap:4px}.compose-preset-copy strong,.compose-editor-heading strong{color:var(--md-sys-color-on-surface);font-weight:600}.compose-preset-actions,.compose-item-actions{display:flex;flex-wrap:wrap;gap:8px}.compose-preset-actions button,.compose-item-actions button{min-height:36px;white-space:normal;text-align:left;line-height:1.2}.compose-preset-actions--inline{margin:0 0 12px}.compose-editor-heading{margin-bottom:12px}.renderer-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:8px}.cond-ov-item{min-width:0;flex-wrap:wrap}.cond-ov-item>span{min-width:0;overflow-wrap:anywhere}.cond-ov-item>.spacer{margin-left:auto}.renderer-errors{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container);border:1px solid var(--md-sys-color-error);padding:8px 12px;border-radius:6px;font-size:.85rem}.renderer-preview{margin-top:8px;padding:8px 12px;background:var(--md-sys-color-surface-container-low);border:1px solid var(--md-sys-color-outline-variant);border-radius:6px}.renderer-preview-list{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.renderer-preview-list .pfx-compose{display:inline-flex;gap:6px;align-items:center}.pfx-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 6px;border-radius:10px;font-size:12px;line-height:1}.pfx-badge-icon{font-size:14px;width:14px;height:14px}.pfx-badge--plain{border:1px solid transparent;color:inherit;background:transparent}.pfx-badge--outlined{border:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface);background:transparent}.pfx-badge--filled-primary{background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary)}.pfx-badge--filled-accent{background:var(--md-sys-color-secondary);color:var(--md-sys-color-on-secondary)}.pfx-badge--filled-warn,.pfx-badge--filled-danger{background:var(--md-sys-color-error);color:var(--md-sys-color-on-error)}.pfx-badge--filled-success{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container)}.pfx-badge--filled-info{background:var(--md-sys-color-tertiary);color:var(--md-sys-color-on-tertiary)}.pfx-badge--filled-basic{background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);border:1px solid var(--md-sys-color-outline-variant)}.pfx-badge--soft-primary{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.pfx-badge--soft-accent{background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container)}.pfx-badge--soft-warn,.pfx-badge--soft-danger{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container)}.pfx-badge--soft-success,.pfx-badge--soft-info{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container)}.pfx-badge--soft-basic{background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);border:1px solid var(--md-sys-color-outline-variant)}.pfx-cell-image{display:inline-block;border-radius:4px;object-fit:cover}.pfx-link{color:var(--md-sys-color-primary);text-decoration:underline;cursor:pointer}.pfx-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:10px;font-size:12px;border:1px solid transparent}.pfx-chip--plain{background:transparent;border-color:transparent;color:var(--md-sys-color-on-surface)}.pfx-chip--outlined{border-color:var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}.pfx-chip--filled-primary{background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary)}.pfx-chip--filled-accent{background:var(--md-sys-color-secondary);color:var(--md-sys-color-on-secondary)}.pfx-chip--filled-warn,.pfx-chip--filled-danger{background:var(--md-sys-color-error);color:var(--md-sys-color-on-error)}.pfx-chip--filled-success{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container)}.pfx-chip--filled-info{background:var(--md-sys-color-tertiary);color:var(--md-sys-color-on-tertiary)}.pfx-chip--filled-basic{background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);border-color:var(--md-sys-color-outline-variant)}.pfx-chip--soft-primary{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.pfx-chip--soft-accent{background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container)}.pfx-chip--soft-warn,.pfx-chip--soft-danger{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container)}.pfx-chip--soft-success,.pfx-chip--soft-info{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container)}.pfx-chip--soft-basic{background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);border-color:var(--md-sys-color-outline-variant)}.pfx-progress{position:relative;width:120px;height:8px;background:var(--md-sys-color-surface-container-highest);border-radius:4px;overflow:hidden}.pfx-progress-bar{height:100%;background:var(--md-sys-color-primary)}.pfx-avatar{display:inline-flex;align-items:center;justify-content:center;background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface);border-radius:4px;overflow:hidden;font-weight:600;width:32px;height:32px}.pfx-avatar.shape-rounded{border-radius:8px}.pfx-avatar.shape-circle{border-radius:999px}.pfx-avatar--initials{text-transform:uppercase;font-size:12px}@container table-columns-editor (max-width: 800px){.column-form{padding:12px}.cond-ov-item{align-items:flex-start}.cond-ov-item>.spacer{display:none}.master-panel{flex:0 0 220px;min-width:0}.column-item{height:auto;min-height:96px}.column-item-content{display:grid;grid-template-columns:18px minmax(0,1fr) auto;padding:10px 0;gap:4px}.column-main-info,.column-header{display:contents}.drag-handle{grid-row:1;grid-column:1}.column-text-info{grid-row:1;grid-column:2/4}.column-title{white-space:normal;overflow-wrap:anywhere}.column-indicators{grid-row:2;grid-column:2;gap:2px}.remove-button{grid-row:2;grid-column:3}.detail-panel{min-width:0}.form-row{flex-direction:column;gap:12px}.config-layout{flex-direction:column;gap:20px}.features-group{min-width:auto}.features-grid{grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:8px}.visibility-toggle-group .mat-button-toggle{min-width:80px;font-size:.875rem}}@container table-columns-editor (max-width: 600px){.master-detail-layout{flex-direction:column}.master-panel{flex:0 0 auto;width:100%;max-height:240px}}@container table-columns-editor (max-width: 480px){.visibility-toggle-group .mat-button-toggle{min-width:70px;font-size:.8rem}.alignment-toggle-group .mat-button-toggle{min-width:48px;padding:0 6px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i10$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i10$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i3.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i9.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i9.MatCardContent, selector: "mat-card-content" }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i7$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatButtonToggleModule }, { kind: "directive", type: i10$2.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i10$2.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i11$1.MatList, selector: "mat-list", exportAs: ["matList"] }, { kind: "component", type: i11$1.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "component", type: i8.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i10.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i6.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i6.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i6.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i6.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i6.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "ngmodule", type: MatBadgeModule }, { kind: "directive", type: i14.MatBadge, selector: "[matBadge]", inputs: ["matBadgeColor", "matBadgeOverlap", "matBadgeDisabled", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i5$1.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i13.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i13.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i13.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: VisualFormulaBuilderComponent, selector: "visual-formula-builder", inputs: ["availableDataSchema", "currentFormula"], outputs: ["formulaChange", "generatedExpressionChange"] }, { kind: "component", type: ValueMappingEditorComponent, selector: "value-mapping-editor", inputs: ["currentMapping", "keyInputType", "labelKey", "labelValue"], outputs: ["mappingChange"] }, { kind: "component", type: DataFormatterComponent, selector: "data-formatter", inputs: ["columnType", "currentFormat"], outputs: ["formatChange"] }, { kind: "component", type: TableRatingCellComponent, selector: "praxis-table-rating", inputs: ["itemsCount", "value", "size", "ratingColor", "outlineColor", "ariaLabel"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
50625
+ `, isInline: true, styles: [":host{display:block;min-width:0;container-type:inline-size;container-name:table-columns-editor}.columns-config-editor{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;gap:16px}.educational-card{background:var(--md-sys-color-surface-container-low);border-left:4px solid var(--md-sys-color-primary);margin-bottom:12px}.inline-icon{font-size:18px;width:18px;height:18px;color:var(--md-sys-color-primary)}.help-icon-button{vertical-align:middle}.global-config-accordion{margin-bottom:16px}.global-config-panel{background:var(--md-sys-color-surface-container);border-left:4px solid var(--md-sys-color-secondary)}.config-panel-content{padding:16px 20px 20px}.config-layout{display:flex;flex-wrap:wrap;gap:24px;align-items:flex-start}.config-group{display:flex;flex-direction:column;min-width:0}.config-group:not(.features-group){flex:0 0 auto}.features-group{flex:1 1 300px;min-width:300px}.control-section{display:flex;flex-direction:column;gap:8px}.control-label{font-size:.875rem;font-weight:500;color:var(--md-sys-color-on-surface);margin:0}.control-status{font-weight:400;color:var(--md-sys-color-on-surface-variant);font-size:.75rem;margin-left:4px}.visibility-toggle-group,.alignment-toggle-group{display:flex}.visibility-toggle-group .mat-button-toggle{min-width:90px;height:40px}.alignment-toggle-group .mat-button-toggle{min-width:56px;width:auto;height:40px;padding:0 8px}.features-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin-top:4px}.feature-checkbox{justify-self:start}.master-detail-layout{min-width:0;display:flex;flex:1;gap:16px;min-height:0}.master-panel{flex:0 0 350px;display:flex;flex-direction:column;background:var(--md-sys-color-surface-container-low);border-radius:8px;overflow:hidden}.list-header{display:flex;justify-content:space-between;align-items:center;padding:16px;background:var(--md-sys-color-surface-container);border-bottom:1px solid var(--md-sys-color-outline-variant)}.list-header h3{margin:0;font-size:1.1rem;font-weight:500}.add-column-fab{transform:scale(.7)}.columns-list-container{flex:1;overflow-y:auto}.columns-list{padding:0}.column-item{border-bottom:1px solid var(--md-sys-color-outline-variant);cursor:pointer;transition:background-color .2s}.column-item:hover{background:var(--md-sys-color-surface-container)}.column-item.selected{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.column-item-content{display:flex;justify-content:space-between;align-items:center;width:100%;padding:12px 16px}.column-main-info{flex:1;display:flex;flex-direction:column;gap:4px}.column-header{display:flex;align-items:center;gap:8px;width:100%}.drag-handle{cursor:grab;color:var(--md-sys-color-on-surface-variant);font-size:18px;flex-shrink:0}.column-text-info{flex:1;display:flex;flex-direction:column;gap:2px;min-width:0}.column-title{font-weight:600;font-size:.9rem;color:var(--md-sys-color-on-surface);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.column-field{font-size:.75rem;color:var(--md-sys-color-on-surface-variant);font-family:monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.column-indicators{display:flex;align-items:center;gap:6px;flex-shrink:0}.column-type-icon,.mapping-indicator-icon,.renderer-indicator-icon{font-size:16px;color:var(--md-sys-color-on-surface-variant)}.visibility-checkbox{transform:scale(.9)}.remove-button{color:var(--md-sys-color-error);opacity:.7;transition:opacity .2s}.remove-button:hover{opacity:1}.detail-panel{min-width:0;flex:1;display:flex;flex-direction:column;background:var(--md-sys-color-surface-container-low);border-radius:8px;overflow:hidden}.no-selection-message{display:flex;flex-direction:column;align-items:center;justify-content:center;flex:1;text-align:center;padding:48px;color:var(--md-sys-color-on-surface-variant)}.no-selection-message mat-icon{font-size:48px;width:48px;height:48px;margin-bottom:16px}.editor-header{padding:16px 24px;background:var(--md-sys-color-surface-container);border-bottom:1px solid var(--md-sys-color-outline-variant)}.editor-title{margin:0;font-size:1.1rem;font-weight:500;color:var(--md-sys-color-on-surface)}.column-form{flex:1;padding:24px;overflow:visible}.form-row{display:flex;gap:16px;margin-bottom:24px;align-items:flex-start}.field-input,.header-input,.width-input,.data-type-input{flex:1}.header-input{flex:2}.alignment-group,.sticky-group{display:flex;flex-direction:column;gap:8px}.control-label{font-size:.875rem;font-weight:500;color:var(--md-sys-color-on-surface)}.renderer-grid,.renderer-config{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(220px,100%),1fr));gap:12px}.compose-preset-panel{grid-column:1/-1;display:grid;gap:12px;padding:12px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface-container-low)}.compose-preset-copy,.compose-editor-heading{display:grid;gap:4px}.compose-preset-copy strong,.compose-editor-heading strong{color:var(--md-sys-color-on-surface);font-weight:600}.compose-preset-actions,.compose-item-actions{display:flex;flex-wrap:wrap;gap:8px}.compose-preset-actions button,.compose-item-actions button{min-height:36px;white-space:normal;text-align:left;line-height:1.2}.compose-preset-actions--inline{margin:0 0 12px}.compose-editor-heading{margin-bottom:12px}.renderer-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:8px}.cond-ov-item{min-width:0;flex-wrap:wrap}.cond-ov-item>span{min-width:0;overflow-wrap:anywhere}.cond-ov-item>.spacer{margin-left:auto}.renderer-errors{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container);border:1px solid var(--md-sys-color-error);padding:8px 12px;border-radius:6px;font-size:.85rem}.renderer-preview{margin-top:8px;padding:8px 12px;background:var(--md-sys-color-surface-container-low);border:1px solid var(--md-sys-color-outline-variant);border-radius:6px}.renderer-preview-list{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.renderer-preview-list .pfx-compose{display:inline-flex;gap:6px;align-items:center}.pfx-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 6px;border-radius:10px;font-size:12px;line-height:1}.pfx-badge-icon{font-size:14px;width:14px;height:14px}.pfx-badge--plain{border:1px solid transparent;color:inherit;background:transparent}.pfx-badge--outlined{border:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface);background:transparent}.pfx-badge--filled-primary{background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary)}.pfx-badge--filled-accent{background:var(--md-sys-color-secondary);color:var(--md-sys-color-on-secondary)}.pfx-badge--filled-warn,.pfx-badge--filled-danger{background:var(--md-sys-color-error);color:var(--md-sys-color-on-error)}.pfx-badge--filled-success{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container)}.pfx-badge--filled-info{background:var(--md-sys-color-tertiary);color:var(--md-sys-color-on-tertiary)}.pfx-badge--filled-basic{background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);border:1px solid var(--md-sys-color-outline-variant)}.pfx-badge--soft-primary{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.pfx-badge--soft-accent{background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container)}.pfx-badge--soft-warn,.pfx-badge--soft-danger{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container)}.pfx-badge--soft-success,.pfx-badge--soft-info{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container)}.pfx-badge--soft-basic{background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);border:1px solid var(--md-sys-color-outline-variant)}.pfx-cell-image{display:inline-block;border-radius:4px;object-fit:cover}.pfx-link{color:var(--md-sys-color-primary);text-decoration:underline;cursor:pointer}.pfx-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:10px;font-size:12px;border:1px solid transparent}.pfx-chip--plain{background:transparent;border-color:transparent;color:var(--md-sys-color-on-surface)}.pfx-chip--outlined{border-color:var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}.pfx-chip--filled-primary{background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary)}.pfx-chip--filled-accent{background:var(--md-sys-color-secondary);color:var(--md-sys-color-on-secondary)}.pfx-chip--filled-warn,.pfx-chip--filled-danger{background:var(--md-sys-color-error);color:var(--md-sys-color-on-error)}.pfx-chip--filled-success{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container)}.pfx-chip--filled-info{background:var(--md-sys-color-tertiary);color:var(--md-sys-color-on-tertiary)}.pfx-chip--filled-basic{background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);border-color:var(--md-sys-color-outline-variant)}.pfx-chip--soft-primary{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.pfx-chip--soft-accent{background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container)}.pfx-chip--soft-warn,.pfx-chip--soft-danger{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container)}.pfx-chip--soft-success,.pfx-chip--soft-info{background:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container)}.pfx-chip--soft-basic{background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);border-color:var(--md-sys-color-outline-variant)}.pfx-progress{position:relative;width:120px;height:8px;background:var(--md-sys-color-surface-container-highest);border-radius:4px;overflow:hidden}.pfx-progress-bar{height:100%;background:var(--md-sys-color-primary)}.pfx-avatar{display:inline-flex;align-items:center;justify-content:center;background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface);border-radius:4px;overflow:hidden;font-weight:600;width:32px;height:32px}.pfx-avatar.shape-rounded{border-radius:8px}.pfx-avatar.shape-circle{border-radius:999px}.pfx-avatar--initials{text-transform:uppercase;font-size:12px}@container table-columns-editor (max-width: 800px){.column-form{padding:12px}.cond-ov-item{align-items:flex-start}.cond-ov-item>.spacer{display:none}.master-panel{flex:0 0 220px;min-width:0}.column-item{height:auto;min-height:96px}.column-item-content{display:grid;grid-template-columns:18px minmax(0,1fr) auto;padding:10px 0;gap:4px}.column-main-info,.column-header{display:contents}.drag-handle{grid-row:1;grid-column:1}.column-text-info{grid-row:1;grid-column:2/4}.column-title{white-space:normal;overflow-wrap:anywhere}.column-indicators{grid-row:2;grid-column:2;gap:2px}.remove-button{grid-row:2;grid-column:3}.detail-panel{min-width:0}.form-row{flex-direction:column;gap:12px}.config-layout{flex-direction:column;gap:20px}.features-group{min-width:auto}.features-grid{grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:8px}.visibility-toggle-group .mat-button-toggle{min-width:80px;font-size:.875rem}}@container table-columns-editor (max-width: 600px){.master-detail-layout{flex-direction:column}.master-panel{flex:0 0 auto;width:100%;max-height:240px}}@container table-columns-editor (max-width: 480px){.visibility-toggle-group .mat-button-toggle{min-width:70px;font-size:.8rem}.alignment-toggle-group .mat-button-toggle{min-width:48px;padding:0 6px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i10$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i10$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i3.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$1.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i2$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i9.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i9.MatCardContent, selector: "mat-card-content" }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i7$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatButtonToggleModule }, { kind: "directive", type: i10$2.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i10$2.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i4.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i11$1.MatList, selector: "mat-list", exportAs: ["matList"] }, { kind: "component", type: i11$1.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "component", type: i8.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i10.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i6.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i6.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i6.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i6.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i6.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "ngmodule", type: MatBadgeModule }, { kind: "directive", type: i14.MatBadge, selector: "[matBadge]", inputs: ["matBadgeColor", "matBadgeOverlap", "matBadgeDisabled", "matBadgePosition", "matBadge", "matBadgeDescription", "matBadgeSize", "matBadgeHidden"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i5$1.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i13.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i13.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i13.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: VisualFormulaBuilderComponent, selector: "visual-formula-builder", inputs: ["availableDataSchema", "currentFormula"], outputs: ["formulaChange", "generatedExpressionChange"] }, { kind: "component", type: ValueMappingEditorComponent, selector: "value-mapping-editor", inputs: ["currentMapping", "keyInputType", "labelKey", "labelValue"], outputs: ["mappingChange"] }, { kind: "component", type: DataFormatterComponent, selector: "data-formatter", inputs: ["columnType", "currentFormat"], outputs: ["formatChange"] }, { kind: "component", type: TableRatingCellComponent, selector: "praxis-table-rating", inputs: ["itemsCount", "value", "size", "ratingColor", "outlineColor", "ariaLabel"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
50028
50626
  }
50029
50627
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ColumnsConfigEditorComponent, decorators: [{
50030
50628
  type: Component,
@@ -51115,18 +51713,162 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
51115
51713
  }
51116
51714
  <mat-form-field appearance="outline">
51117
51715
  <mat-label>{{ tx('columns.renderer.microVisualization.tone', 'Tone') }}</mat-label>
51118
- <mat-select [ngModel]="getRendererProp('microVisualization','visualization.tone') || 'info'" [ngModelOptions]="{standalone: true}" (ngModelChange)="setMicroVisualizationProp('tone', $event)">
51716
+ <mat-select [ngModel]="getRendererProp('microVisualization','visualization.tone') ?? ''" [ngModelOptions]="{standalone: true}" (ngModelChange)="setMicroVisualizationProp('tone', $event)">
51717
+ <mat-option [value]="''">{{ tx('columns.renderer.microVisualization.tone.auto', 'Automatic (by thresholds / semantic)') }}</mat-option>
51119
51718
  <mat-option value="neutral">{{ tx('columns.renderer.microVisualization.tone.neutral', 'Neutral') }}</mat-option>
51120
51719
  <mat-option value="info">{{ tx('columns.renderer.microVisualization.tone.info', 'Info') }}</mat-option>
51121
51720
  <mat-option value="success">{{ tx('columns.renderer.microVisualization.tone.success', 'Success') }}</mat-option>
51122
51721
  <mat-option value="warning">{{ tx('columns.renderer.microVisualization.tone.warning', 'Warning') }}</mat-option>
51123
51722
  <mat-option value="danger">{{ tx('columns.renderer.microVisualization.tone.danger', 'Danger') }}</mat-option>
51723
+ <mat-option value="critical">{{ tx('columns.renderer.microVisualization.tone.critical', 'Critical') }}</mat-option>
51124
51724
  </mat-select>
51125
51725
  </mat-form-field>
51126
51726
  <mat-form-field appearance="outline">
51127
51727
  <mat-label>{{ tx('columns.renderer.microVisualization.fallbackText', 'Accessible fallback text') }}</mat-label>
51128
51728
  <input matInput [value]="getRendererProp('microVisualization','visualization.fallbackText') || ''" (input)="setMicroVisualizationProp('fallbackText', $any($event.target).value)" />
51129
51729
  </mat-form-field>
51730
+ <div class="renderer-hint" style="grid-column: 1/-1; opacity:.78;">
51731
+ {{ tx('columns.renderer.microVisualization.tone.precedenceHint', 'In Automatic mode, color is resolved from threshold bands or value sign. A fixed tone overrides conditional bands.') }}
51732
+ </div>
51733
+ @if (isMicroVisualizationThresholdsSupported()) {
51734
+ <div
51735
+ data-testid="micro-viz-thresholds-section"
51736
+ style="grid-column: 1/-1; display: grid; gap: 10px; padding: 12px; border: 1px solid var(--md-sys-color-outline-variant); border-radius: 8px;"
51737
+ >
51738
+ <div style="display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap;">
51739
+ <div>
51740
+ <div style="font-weight: 600;">
51741
+ {{ tx('columns.renderer.microVisualization.thresholds.title', 'Color bands (thresholds)') }}
51742
+ </div>
51743
+ <div class="renderer-hint" style="opacity: .78;">
51744
+ {{ tx('columns.renderer.microVisualization.thresholds.description', 'Evaluates top-to-bottom (first matching band wins). Both min and max bounds are inclusive (min <= value <= max).') }}
51745
+ </div>
51746
+ </div>
51747
+ <div style="display: flex; gap: 8px; flex-wrap: wrap;">
51748
+ <button
51749
+ mat-stroked-button
51750
+ type="button"
51751
+ data-testid="micro-viz-add-threshold"
51752
+ (click)="addMicroVisualizationThreshold()"
51753
+ >
51754
+ <mat-icon>add</mat-icon>
51755
+ {{ tx('columns.renderer.microVisualization.thresholds.add', 'Add threshold') }}
51756
+ </button>
51757
+ <button
51758
+ mat-stroked-button
51759
+ type="button"
51760
+ data-testid="micro-viz-preset-thresholds"
51761
+ (click)="applyDefaultMicroVisualizationThresholds()"
51762
+ >
51763
+ <mat-icon>traffic</mat-icon>
51764
+ {{ tx('columns.renderer.microVisualization.thresholds.preset', 'Traffic-light preset (70+ / 40–69 / ≤39)') }}
51765
+ </button>
51766
+ </div>
51767
+ </div>
51768
+ @if (!getMicroVisualizationThresholds().length) {
51769
+ <div class="renderer-hint" style="opacity: .75;">
51770
+ {{ tx('columns.renderer.microVisualization.thresholds.empty', 'No threshold bands configured. Add bands or apply the traffic-light preset to color values automatically.') }}
51771
+ </div>
51772
+ }
51773
+ @for (threshold of getMicroVisualizationThresholds(); track $index) {
51774
+ <div
51775
+ data-testid="micro-viz-threshold-row"
51776
+ style="display: grid; grid-template-columns: minmax(110px, 1fr) minmax(110px, 1fr) minmax(140px, 1fr) auto; gap: 8px; align-items: center;"
51777
+ >
51778
+ <mat-form-field appearance="outline">
51779
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.min', 'Min (inclusive)') }}</mat-label>
51780
+ <input
51781
+ matInput
51782
+ type="number"
51783
+ data-testid="micro-viz-threshold-min"
51784
+ [value]="threshold.min ?? ''"
51785
+ (input)="setMicroVisualizationThresholdNumber($index, 'min', $any($event.target).value)"
51786
+ />
51787
+ </mat-form-field>
51788
+ <mat-form-field appearance="outline">
51789
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.max', 'Max (inclusive)') }}</mat-label>
51790
+ <input
51791
+ matInput
51792
+ type="number"
51793
+ data-testid="micro-viz-threshold-max"
51794
+ [value]="threshold.max ?? ''"
51795
+ (input)="setMicroVisualizationThresholdNumber($index, 'max', $any($event.target).value)"
51796
+ />
51797
+ </mat-form-field>
51798
+ <mat-form-field appearance="outline">
51799
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.tone', 'Band tone') }}</mat-label>
51800
+ <mat-select
51801
+ data-testid="micro-viz-threshold-tone"
51802
+ [ngModel]="threshold.tone || 'info'"
51803
+ [ngModelOptions]="{standalone: true}"
51804
+ (ngModelChange)="setMicroVisualizationThresholdTone($index, $event)"
51805
+ >
51806
+ <mat-option value="success">{{ tx('columns.renderer.microVisualization.tone.success', 'Success') }}</mat-option>
51807
+ <mat-option value="warning">{{ tx('columns.renderer.microVisualization.tone.warning', 'Warning') }}</mat-option>
51808
+ <mat-option value="danger">{{ tx('columns.renderer.microVisualization.tone.danger', 'Danger') }}</mat-option>
51809
+ <mat-option value="critical">{{ tx('columns.renderer.microVisualization.tone.critical', 'Critical') }}</mat-option>
51810
+ <mat-option value="info">{{ tx('columns.renderer.microVisualization.tone.info', 'Info') }}</mat-option>
51811
+ <mat-option value="neutral">{{ tx('columns.renderer.microVisualization.tone.neutral', 'Neutral') }}</mat-option>
51812
+ </mat-select>
51813
+ </mat-form-field>
51814
+ <div style="display: flex; gap: 4px; align-items: center;">
51815
+ <button
51816
+ praxisIconButton="arrow_upward"
51817
+ size="dense"
51818
+ type="button"
51819
+ data-testid="micro-viz-threshold-up"
51820
+ [disabled]="$index === 0"
51821
+ [matTooltip]="tx('columns.renderer.microVisualization.thresholds.moveUp', 'Move threshold up')"
51822
+ [attr.aria-label]="tx('columns.renderer.microVisualization.thresholds.moveUp', 'Move threshold up')"
51823
+ (click)="moveMicroVisualizationThreshold($index, -1)"
51824
+ ></button>
51825
+ <button
51826
+ praxisIconButton="arrow_downward"
51827
+ size="dense"
51828
+ type="button"
51829
+ data-testid="micro-viz-threshold-down"
51830
+ [disabled]="$index === getMicroVisualizationThresholds().length - 1"
51831
+ [matTooltip]="tx('columns.renderer.microVisualization.thresholds.moveDown', 'Move threshold down')"
51832
+ [attr.aria-label]="tx('columns.renderer.microVisualization.thresholds.moveDown', 'Move threshold down')"
51833
+ (click)="moveMicroVisualizationThreshold($index, 1)"
51834
+ ></button>
51835
+ <button
51836
+ praxisIconButton="delete_outline"
51837
+ size="dense"
51838
+ type="button"
51839
+ data-testid="micro-viz-threshold-remove"
51840
+ [matTooltip]="tx('columns.renderer.microVisualization.thresholds.remove', 'Remove threshold')"
51841
+ [attr.aria-label]="tx('columns.renderer.microVisualization.thresholds.remove', 'Remove threshold')"
51842
+ (click)="removeMicroVisualizationThreshold($index)"
51843
+ ></button>
51844
+ </div>
51845
+ </div>
51846
+ }
51847
+ @if (isMicroVisualizationValueKind()) {
51848
+ <mat-form-field appearance="outline">
51849
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.sampleValues', 'Preview sample values (comma-separated)') }}</mat-label>
51850
+ <input
51851
+ matInput
51852
+ data-testid="micro-viz-sample-values-input"
51853
+ [value]="microVisualizationSampleValuesText"
51854
+ (input)="setMicroVisualizationSampleValuesText($any($event.target).value)"
51855
+ />
51856
+ </mat-form-field>
51857
+ <div
51858
+ data-testid="micro-viz-sample-preview"
51859
+ style="display: flex; gap: 8px; flex-wrap: wrap; align-items: center;"
51860
+ >
51861
+ @for (sampleVal of getMicroVisualizationSampleValues(); track $index) {
51862
+ <span
51863
+ class="pfx-micro-viz-cell"
51864
+ [attr.data-sample-value]="sampleVal"
51865
+ [innerHTML]="getPreviewMicroVisualizationHtmlForValue(sampleVal)"
51866
+ ></span>
51867
+ }
51868
+ </div>
51869
+ }
51870
+ </div>
51871
+ }
51130
51872
  <div class="renderer-hint" style="grid-column: 1/-1; opacity:.78;">
51131
51873
  {{ tx('columns.renderer.microVisualization.hint', 'Use a value for indicators, points for trends, segments for stacked bars, and items for process flows. The table keeps the visual compact and exposes fallback text to assistive technology.') }}
51132
51874
  </div>
@@ -52027,14 +52769,79 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
52027
52769
  }
52028
52770
  <mat-form-field appearance="outline">
52029
52771
  <mat-label>{{ tx('columns.renderer.microVisualization.tone', 'Tone') }}</mat-label>
52030
- <mat-select [ngModel]="getOverrideMicroVisualizationProp('tone') || 'info'" [ngModelOptions]="{standalone:true}" (ngModelChange)="setOverrideMicroVisualizationProp('tone', $event)">
52772
+ <mat-select [ngModel]="getOverrideMicroVisualizationProp('tone') || ''" [ngModelOptions]="{standalone:true}" (ngModelChange)="setOverrideMicroVisualizationProp('tone', $event || undefined)">
52773
+ <mat-option value="">{{ tx('columns.renderer.microVisualization.tone.auto', 'Automatic (thresholds / default)') }}</mat-option>
52031
52774
  <mat-option value="neutral">{{ tx('columns.renderer.microVisualization.tone.neutral', 'Neutral') }}</mat-option>
52032
52775
  <mat-option value="info">{{ tx('columns.renderer.microVisualization.tone.info', 'Info') }}</mat-option>
52033
52776
  <mat-option value="success">{{ tx('columns.renderer.microVisualization.tone.success', 'Success') }}</mat-option>
52034
52777
  <mat-option value="warning">{{ tx('columns.renderer.microVisualization.tone.warning', 'Warning') }}</mat-option>
52035
52778
  <mat-option value="danger">{{ tx('columns.renderer.microVisualization.tone.danger', 'Danger') }}</mat-option>
52779
+ <mat-option value="critical">{{ tx('columns.renderer.microVisualization.tone.critical', 'Critical') }}</mat-option>
52036
52780
  </mat-select>
52781
+ <mat-hint>{{ tx('columns.renderer.microVisualization.tone.precedenceHint', 'Fixed tone overrides thresholds. Choose Automatic to let color bands control the tone.') }}</mat-hint>
52037
52782
  </mat-form-field>
52783
+ @if (isOverrideMicroVisualizationThresholdsSupported()) {
52784
+ <div class="micro-viz-thresholds-section" data-testid="override-micro-viz-thresholds-section">
52785
+ <div class="compose-header">
52786
+ <div>
52787
+ <div class="section-subtitle">{{ tx('columns.renderer.microVisualization.thresholds.title', 'Color bands (thresholds)') }}</div>
52788
+ <div class="field-hint">{{ tx('columns.renderer.microVisualization.thresholds.hint', 'First matching band wins (min <= value <= max). Leave min or max empty for open-ended ranges.') }}</div>
52789
+ </div>
52790
+ <div style="display:flex; gap:8px; flex-wrap:wrap;">
52791
+ <button mat-stroked-button type="button" data-testid="override-micro-viz-preset-thresholds" (click)="applyDefaultOverrideMicroVisualizationThresholds()">
52792
+ <mat-icon>palette</mat-icon>
52793
+ {{ tx('columns.renderer.microVisualization.thresholds.applyPreset', 'Traffic-light preset') }}
52794
+ </button>
52795
+ <button mat-stroked-button type="button" data-testid="override-micro-viz-add-threshold" (click)="addOverrideMicroVisualizationThreshold()">
52796
+ <mat-icon>add</mat-icon>
52797
+ {{ tx('columns.renderer.microVisualization.thresholds.add', 'Add band') }}
52798
+ </button>
52799
+ </div>
52800
+ </div>
52801
+ @if (getOverrideMicroVisualizationProp('tone') && getOverrideMicroVisualizationThresholds().length > 0) {
52802
+ <div class="field-hint micro-viz-guidance is-conditional">
52803
+ {{ tx('columns.renderer.microVisualization.thresholds.overriddenByToneWarning', 'A fixed tone is selected above and will override these color bands. Switch Tone to Automatic so thresholds take effect.') }}
52804
+ </div>
52805
+ }
52806
+ @if (getOverrideMicroVisualizationThresholds().length === 0) {
52807
+ <div class="field-hint">{{ tx('columns.renderer.microVisualization.thresholds.empty', 'No color bands configured.') }}</div>
52808
+ }
52809
+ @for (threshold of getOverrideMicroVisualizationThresholds(); track $index; let idx = $index) {
52810
+ <div class="form-row three-columns micro-viz-threshold-row" [attr.data-testid]="'override-micro-viz-threshold-row-' + idx">
52811
+ <mat-form-field appearance="outline">
52812
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.min', 'Min') }}</mat-label>
52813
+ <input matInput type="number" [value]="threshold.min ?? ''" (input)="setOverrideMicroVisualizationThresholdNumber(idx, 'min', $any($event.target).value)" />
52814
+ </mat-form-field>
52815
+ <mat-form-field appearance="outline">
52816
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.max', 'Max') }}</mat-label>
52817
+ <input matInput type="number" [value]="threshold.max ?? ''" (input)="setOverrideMicroVisualizationThresholdNumber(idx, 'max', $any($event.target).value)" />
52818
+ </mat-form-field>
52819
+ <div style="display:flex; gap:6px; align-items:flex-start;">
52820
+ <mat-form-field appearance="outline" style="flex:1;">
52821
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.tone', 'Band tone') }}</mat-label>
52822
+ <mat-select [ngModel]="threshold.tone || 'neutral'" [ngModelOptions]="{standalone:true}" (ngModelChange)="setOverrideMicroVisualizationThresholdTone(idx, $event)">
52823
+ <mat-option value="neutral">{{ tx('columns.renderer.microVisualization.tone.neutral', 'Neutral') }}</mat-option>
52824
+ <mat-option value="info">{{ tx('columns.renderer.microVisualization.tone.info', 'Info') }}</mat-option>
52825
+ <mat-option value="success">{{ tx('columns.renderer.microVisualization.tone.success', 'Success') }}</mat-option>
52826
+ <mat-option value="warning">{{ tx('columns.renderer.microVisualization.tone.warning', 'Warning') }}</mat-option>
52827
+ <mat-option value="danger">{{ tx('columns.renderer.microVisualization.tone.danger', 'Danger') }}</mat-option>
52828
+ <mat-option value="critical">{{ tx('columns.renderer.microVisualization.tone.critical', 'Critical') }}</mat-option>
52829
+ </mat-select>
52830
+ </mat-form-field>
52831
+ <button mat-icon-button type="button" (click)="moveOverrideMicroVisualizationThreshold(idx, -1)" [disabled]="idx === 0" [attr.aria-label]="tx('columns.renderer.microVisualization.thresholds.moveUp', 'Move band up')">
52832
+ <mat-icon>arrow_upward</mat-icon>
52833
+ </button>
52834
+ <button mat-icon-button type="button" (click)="moveOverrideMicroVisualizationThreshold(idx, 1)" [disabled]="idx === getOverrideMicroVisualizationThresholds().length - 1" [attr.aria-label]="tx('columns.renderer.microVisualization.thresholds.moveDown', 'Move band down')">
52835
+ <mat-icon>arrow_downward</mat-icon>
52836
+ </button>
52837
+ <button mat-icon-button type="button" color="warn" (click)="removeOverrideMicroVisualizationThreshold(idx)" [attr.aria-label]="tx('columns.renderer.microVisualization.thresholds.remove', 'Remove band')">
52838
+ <mat-icon>delete</mat-icon>
52839
+ </button>
52840
+ </div>
52841
+ </div>
52842
+ }
52843
+ </div>
52844
+ }
52038
52845
  <mat-form-field appearance="outline">
52039
52846
  <mat-label>{{ tx('columns.renderer.microVisualization.fallbackText', 'Accessible fallback text') }}</mat-label>
52040
52847
  <input matInput [value]="getOverrideMicroVisualizationProp('fallbackText') || ''" (input)="setOverrideMicroVisualizationProp('fallbackText', $any($event.target).value)" />