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

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 ao menos um limite (mínimo ou máximo).',
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 at least one bound (min or max).',
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-D-PEKBru.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-LLzzZHSg.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
+ }
46352
46561
  this.onColumnPropertyChange();
46353
46562
  this.validateRenderer();
46354
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
+ }
46587
+ this.onColumnPropertyChange();
46588
+ this.validateRenderer();
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,37 @@ 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
+ if (!hasMin && !hasMax) {
47471
+ errs.push(this.tx('columns.renderer.validation.microVisualization.thresholdEmptyBounds', 'Micro-visualization: band #{index} must define at least min or max.').replace('{index}', String(idx + 1)));
47472
+ }
47473
+ else if (hasMin && hasMax && entry.min > entry.max) {
47474
+ errs.push(this.tx('columns.renderer.validation.microVisualization.thresholdInvalidRange', 'Micro-visualization: band #{index} has min ({min}) greater than max ({max}).')
47475
+ .replace('{index}', String(idx + 1))
47476
+ .replace('{min}', String(entry.min))
47477
+ .replace('{max}', String(entry.max)));
47478
+ }
47479
+ });
47480
+ }
47101
47481
  validateRenderer() {
47102
47482
  const errs = [];
47103
47483
  const col = this.selectedColumn;
47484
+ const overrides = Array.isArray(col?.conditionalRenderers) ? col.conditionalRenderers : [];
47485
+ for (const ov of overrides) {
47486
+ if (ov?.renderer?.type === 'microVisualization') {
47487
+ this.validateMicroVisualizationThresholds(ov.renderer.microVisualization?.visualization?.thresholds, errs);
47488
+ }
47489
+ }
47104
47490
  const r = col?.renderer || null;
47105
47491
  if (!r || !r.type) {
47106
- // no renderer -> valid
47107
- this.rendererValidationErrors = [];
47108
- this.rendererIsValid = true;
47492
+ // no renderer -> valid unless conditional override has threshold errors
47493
+ this.rendererValidationErrors = errs;
47494
+ this.rendererIsValid = errs.length === 0;
47109
47495
  this.cdr.markForCheck();
47110
47496
  return;
47111
47497
  }
@@ -47211,6 +47597,7 @@ class ColumnsConfigEditorComponent {
47211
47597
  if (visualization.kind === 'processFlow' && !hasItems) {
47212
47598
  errs.push('Microvisualização: process flow exige items ou itemsExpr.');
47213
47599
  }
47600
+ this.validateMicroVisualizationThresholds(visualization.thresholds, errs);
47214
47601
  }
47215
47602
  }
47216
47603
  if (r.type === 'avatar') {
@@ -49089,18 +49476,162 @@ class ColumnsConfigEditorComponent {
49089
49476
  }
49090
49477
  <mat-form-field appearance="outline">
49091
49478
  <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)">
49479
+ <mat-select [ngModel]="getRendererProp('microVisualization','visualization.tone') ?? ''" [ngModelOptions]="{standalone: true}" (ngModelChange)="setMicroVisualizationProp('tone', $event)">
49480
+ <mat-option [value]="''">{{ tx('columns.renderer.microVisualization.tone.auto', 'Automatic (by thresholds / semantic)') }}</mat-option>
49093
49481
  <mat-option value="neutral">{{ tx('columns.renderer.microVisualization.tone.neutral', 'Neutral') }}</mat-option>
49094
49482
  <mat-option value="info">{{ tx('columns.renderer.microVisualization.tone.info', 'Info') }}</mat-option>
49095
49483
  <mat-option value="success">{{ tx('columns.renderer.microVisualization.tone.success', 'Success') }}</mat-option>
49096
49484
  <mat-option value="warning">{{ tx('columns.renderer.microVisualization.tone.warning', 'Warning') }}</mat-option>
49097
49485
  <mat-option value="danger">{{ tx('columns.renderer.microVisualization.tone.danger', 'Danger') }}</mat-option>
49486
+ <mat-option value="critical">{{ tx('columns.renderer.microVisualization.tone.critical', 'Critical') }}</mat-option>
49098
49487
  </mat-select>
49099
49488
  </mat-form-field>
49100
49489
  <mat-form-field appearance="outline">
49101
49490
  <mat-label>{{ tx('columns.renderer.microVisualization.fallbackText', 'Accessible fallback text') }}</mat-label>
49102
49491
  <input matInput [value]="getRendererProp('microVisualization','visualization.fallbackText') || ''" (input)="setMicroVisualizationProp('fallbackText', $any($event.target).value)" />
49103
49492
  </mat-form-field>
49493
+ <div class="renderer-hint" style="grid-column: 1/-1; opacity:.78;">
49494
+ {{ tx('columns.renderer.microVisualization.tone.precedenceHint', 'In Automatic mode, color is resolved from threshold bands or value sign. A fixed tone overrides conditional bands.') }}
49495
+ </div>
49496
+ @if (isMicroVisualizationThresholdsSupported()) {
49497
+ <div
49498
+ data-testid="micro-viz-thresholds-section"
49499
+ style="grid-column: 1/-1; display: grid; gap: 10px; padding: 12px; border: 1px solid var(--md-sys-color-outline-variant); border-radius: 8px;"
49500
+ >
49501
+ <div style="display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap;">
49502
+ <div>
49503
+ <div style="font-weight: 600;">
49504
+ {{ tx('columns.renderer.microVisualization.thresholds.title', 'Color bands (thresholds)') }}
49505
+ </div>
49506
+ <div class="renderer-hint" style="opacity: .78;">
49507
+ {{ tx('columns.renderer.microVisualization.thresholds.description', 'Evaluates top-to-bottom (first matching band wins). Both min and max bounds are inclusive (min <= value <= max).') }}
49508
+ </div>
49509
+ </div>
49510
+ <div style="display: flex; gap: 8px; flex-wrap: wrap;">
49511
+ <button
49512
+ mat-stroked-button
49513
+ type="button"
49514
+ data-testid="micro-viz-add-threshold"
49515
+ (click)="addMicroVisualizationThreshold()"
49516
+ >
49517
+ <mat-icon>add</mat-icon>
49518
+ {{ tx('columns.renderer.microVisualization.thresholds.add', 'Add threshold') }}
49519
+ </button>
49520
+ <button
49521
+ mat-stroked-button
49522
+ type="button"
49523
+ data-testid="micro-viz-preset-thresholds"
49524
+ (click)="applyDefaultMicroVisualizationThresholds()"
49525
+ >
49526
+ <mat-icon>traffic</mat-icon>
49527
+ {{ tx('columns.renderer.microVisualization.thresholds.preset', 'Traffic-light preset (70+ / 40–69 / ≤39)') }}
49528
+ </button>
49529
+ </div>
49530
+ </div>
49531
+ @if (!getMicroVisualizationThresholds().length) {
49532
+ <div class="renderer-hint" style="opacity: .75;">
49533
+ {{ tx('columns.renderer.microVisualization.thresholds.empty', 'No threshold bands configured. Add bands or apply the traffic-light preset to color values automatically.') }}
49534
+ </div>
49535
+ }
49536
+ @for (threshold of getMicroVisualizationThresholds(); track $index) {
49537
+ <div
49538
+ data-testid="micro-viz-threshold-row"
49539
+ style="display: grid; grid-template-columns: minmax(110px, 1fr) minmax(110px, 1fr) minmax(140px, 1fr) auto; gap: 8px; align-items: center;"
49540
+ >
49541
+ <mat-form-field appearance="outline">
49542
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.min', 'Min (inclusive)') }}</mat-label>
49543
+ <input
49544
+ matInput
49545
+ type="number"
49546
+ data-testid="micro-viz-threshold-min"
49547
+ [value]="threshold.min ?? ''"
49548
+ (input)="setMicroVisualizationThresholdNumber($index, 'min', $any($event.target).value)"
49549
+ />
49550
+ </mat-form-field>
49551
+ <mat-form-field appearance="outline">
49552
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.max', 'Max (inclusive)') }}</mat-label>
49553
+ <input
49554
+ matInput
49555
+ type="number"
49556
+ data-testid="micro-viz-threshold-max"
49557
+ [value]="threshold.max ?? ''"
49558
+ (input)="setMicroVisualizationThresholdNumber($index, 'max', $any($event.target).value)"
49559
+ />
49560
+ </mat-form-field>
49561
+ <mat-form-field appearance="outline">
49562
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.tone', 'Band tone') }}</mat-label>
49563
+ <mat-select
49564
+ data-testid="micro-viz-threshold-tone"
49565
+ [ngModel]="threshold.tone || 'info'"
49566
+ [ngModelOptions]="{standalone: true}"
49567
+ (ngModelChange)="setMicroVisualizationThresholdTone($index, $event)"
49568
+ >
49569
+ <mat-option value="success">{{ tx('columns.renderer.microVisualization.tone.success', 'Success') }}</mat-option>
49570
+ <mat-option value="warning">{{ tx('columns.renderer.microVisualization.tone.warning', 'Warning') }}</mat-option>
49571
+ <mat-option value="danger">{{ tx('columns.renderer.microVisualization.tone.danger', 'Danger') }}</mat-option>
49572
+ <mat-option value="critical">{{ tx('columns.renderer.microVisualization.tone.critical', 'Critical') }}</mat-option>
49573
+ <mat-option value="info">{{ tx('columns.renderer.microVisualization.tone.info', 'Info') }}</mat-option>
49574
+ <mat-option value="neutral">{{ tx('columns.renderer.microVisualization.tone.neutral', 'Neutral') }}</mat-option>
49575
+ </mat-select>
49576
+ </mat-form-field>
49577
+ <div style="display: flex; gap: 4px; align-items: center;">
49578
+ <button
49579
+ praxisIconButton="arrow_upward"
49580
+ size="dense"
49581
+ type="button"
49582
+ data-testid="micro-viz-threshold-up"
49583
+ [disabled]="$index === 0"
49584
+ [matTooltip]="tx('columns.renderer.microVisualization.thresholds.moveUp', 'Move threshold up')"
49585
+ [attr.aria-label]="tx('columns.renderer.microVisualization.thresholds.moveUp', 'Move threshold up')"
49586
+ (click)="moveMicroVisualizationThreshold($index, -1)"
49587
+ ></button>
49588
+ <button
49589
+ praxisIconButton="arrow_downward"
49590
+ size="dense"
49591
+ type="button"
49592
+ data-testid="micro-viz-threshold-down"
49593
+ [disabled]="$index === getMicroVisualizationThresholds().length - 1"
49594
+ [matTooltip]="tx('columns.renderer.microVisualization.thresholds.moveDown', 'Move threshold down')"
49595
+ [attr.aria-label]="tx('columns.renderer.microVisualization.thresholds.moveDown', 'Move threshold down')"
49596
+ (click)="moveMicroVisualizationThreshold($index, 1)"
49597
+ ></button>
49598
+ <button
49599
+ praxisIconButton="delete_outline"
49600
+ size="dense"
49601
+ type="button"
49602
+ data-testid="micro-viz-threshold-remove"
49603
+ [matTooltip]="tx('columns.renderer.microVisualization.thresholds.remove', 'Remove threshold')"
49604
+ [attr.aria-label]="tx('columns.renderer.microVisualization.thresholds.remove', 'Remove threshold')"
49605
+ (click)="removeMicroVisualizationThreshold($index)"
49606
+ ></button>
49607
+ </div>
49608
+ </div>
49609
+ }
49610
+ @if (isMicroVisualizationValueKind()) {
49611
+ <mat-form-field appearance="outline">
49612
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.sampleValues', 'Preview sample values (comma-separated)') }}</mat-label>
49613
+ <input
49614
+ matInput
49615
+ data-testid="micro-viz-sample-values-input"
49616
+ [value]="microVisualizationSampleValuesText"
49617
+ (input)="setMicroVisualizationSampleValuesText($any($event.target).value)"
49618
+ />
49619
+ </mat-form-field>
49620
+ <div
49621
+ data-testid="micro-viz-sample-preview"
49622
+ style="display: flex; gap: 8px; flex-wrap: wrap; align-items: center;"
49623
+ >
49624
+ @for (sampleVal of getMicroVisualizationSampleValues(); track $index) {
49625
+ <span
49626
+ class="pfx-micro-viz-cell"
49627
+ [attr.data-sample-value]="sampleVal"
49628
+ [innerHTML]="getPreviewMicroVisualizationHtmlForValue(sampleVal)"
49629
+ ></span>
49630
+ }
49631
+ </div>
49632
+ }
49633
+ </div>
49634
+ }
49104
49635
  <div class="renderer-hint" style="grid-column: 1/-1; opacity:.78;">
49105
49636
  {{ 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
49637
  </div>
@@ -50001,14 +50532,79 @@ class ColumnsConfigEditorComponent {
50001
50532
  }
50002
50533
  <mat-form-field appearance="outline">
50003
50534
  <mat-label>{{ tx('columns.renderer.microVisualization.tone', 'Tone') }}</mat-label>
50004
- <mat-select [ngModel]="getOverrideMicroVisualizationProp('tone') || 'info'" [ngModelOptions]="{standalone:true}" (ngModelChange)="setOverrideMicroVisualizationProp('tone', $event)">
50535
+ <mat-select [ngModel]="getOverrideMicroVisualizationProp('tone') || ''" [ngModelOptions]="{standalone:true}" (ngModelChange)="setOverrideMicroVisualizationProp('tone', $event || undefined)">
50536
+ <mat-option value="">{{ tx('columns.renderer.microVisualization.tone.auto', 'Automatic (thresholds / default)') }}</mat-option>
50005
50537
  <mat-option value="neutral">{{ tx('columns.renderer.microVisualization.tone.neutral', 'Neutral') }}</mat-option>
50006
50538
  <mat-option value="info">{{ tx('columns.renderer.microVisualization.tone.info', 'Info') }}</mat-option>
50007
50539
  <mat-option value="success">{{ tx('columns.renderer.microVisualization.tone.success', 'Success') }}</mat-option>
50008
50540
  <mat-option value="warning">{{ tx('columns.renderer.microVisualization.tone.warning', 'Warning') }}</mat-option>
50009
50541
  <mat-option value="danger">{{ tx('columns.renderer.microVisualization.tone.danger', 'Danger') }}</mat-option>
50542
+ <mat-option value="critical">{{ tx('columns.renderer.microVisualization.tone.critical', 'Critical') }}</mat-option>
50010
50543
  </mat-select>
50544
+ <mat-hint>{{ tx('columns.renderer.microVisualization.tone.precedenceHint', 'Fixed tone overrides thresholds. Choose Automatic to let color bands control the tone.') }}</mat-hint>
50011
50545
  </mat-form-field>
50546
+ @if (isOverrideMicroVisualizationThresholdsSupported()) {
50547
+ <div class="micro-viz-thresholds-section" data-testid="override-micro-viz-thresholds-section">
50548
+ <div class="compose-header">
50549
+ <div>
50550
+ <div class="section-subtitle">{{ tx('columns.renderer.microVisualization.thresholds.title', 'Color bands (thresholds)') }}</div>
50551
+ <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>
50552
+ </div>
50553
+ <div style="display:flex; gap:8px; flex-wrap:wrap;">
50554
+ <button mat-stroked-button type="button" data-testid="override-micro-viz-preset-thresholds" (click)="applyDefaultOverrideMicroVisualizationThresholds()">
50555
+ <mat-icon>palette</mat-icon>
50556
+ {{ tx('columns.renderer.microVisualization.thresholds.applyPreset', 'Traffic-light preset') }}
50557
+ </button>
50558
+ <button mat-stroked-button type="button" data-testid="override-micro-viz-add-threshold" (click)="addOverrideMicroVisualizationThreshold()">
50559
+ <mat-icon>add</mat-icon>
50560
+ {{ tx('columns.renderer.microVisualization.thresholds.add', 'Add band') }}
50561
+ </button>
50562
+ </div>
50563
+ </div>
50564
+ @if (getOverrideMicroVisualizationProp('tone') && getOverrideMicroVisualizationThresholds().length > 0) {
50565
+ <div class="field-hint micro-viz-guidance is-conditional">
50566
+ {{ 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.') }}
50567
+ </div>
50568
+ }
50569
+ @if (getOverrideMicroVisualizationThresholds().length === 0) {
50570
+ <div class="field-hint">{{ tx('columns.renderer.microVisualization.thresholds.empty', 'No color bands configured.') }}</div>
50571
+ }
50572
+ @for (threshold of getOverrideMicroVisualizationThresholds(); track $index; let idx = $index) {
50573
+ <div class="form-row three-columns micro-viz-threshold-row" [attr.data-testid]="'override-micro-viz-threshold-row-' + idx">
50574
+ <mat-form-field appearance="outline">
50575
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.min', 'Min') }}</mat-label>
50576
+ <input matInput type="number" [value]="threshold.min ?? ''" (input)="setOverrideMicroVisualizationThresholdNumber(idx, 'min', $any($event.target).value)" />
50577
+ </mat-form-field>
50578
+ <mat-form-field appearance="outline">
50579
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.max', 'Max') }}</mat-label>
50580
+ <input matInput type="number" [value]="threshold.max ?? ''" (input)="setOverrideMicroVisualizationThresholdNumber(idx, 'max', $any($event.target).value)" />
50581
+ </mat-form-field>
50582
+ <div style="display:flex; gap:6px; align-items:flex-start;">
50583
+ <mat-form-field appearance="outline" style="flex:1;">
50584
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.tone', 'Band tone') }}</mat-label>
50585
+ <mat-select [ngModel]="threshold.tone || 'neutral'" [ngModelOptions]="{standalone:true}" (ngModelChange)="setOverrideMicroVisualizationThresholdTone(idx, $event)">
50586
+ <mat-option value="neutral">{{ tx('columns.renderer.microVisualization.tone.neutral', 'Neutral') }}</mat-option>
50587
+ <mat-option value="info">{{ tx('columns.renderer.microVisualization.tone.info', 'Info') }}</mat-option>
50588
+ <mat-option value="success">{{ tx('columns.renderer.microVisualization.tone.success', 'Success') }}</mat-option>
50589
+ <mat-option value="warning">{{ tx('columns.renderer.microVisualization.tone.warning', 'Warning') }}</mat-option>
50590
+ <mat-option value="danger">{{ tx('columns.renderer.microVisualization.tone.danger', 'Danger') }}</mat-option>
50591
+ <mat-option value="critical">{{ tx('columns.renderer.microVisualization.tone.critical', 'Critical') }}</mat-option>
50592
+ </mat-select>
50593
+ </mat-form-field>
50594
+ <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')">
50595
+ <mat-icon>arrow_upward</mat-icon>
50596
+ </button>
50597
+ <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')">
50598
+ <mat-icon>arrow_downward</mat-icon>
50599
+ </button>
50600
+ <button mat-icon-button type="button" color="warn" (click)="removeOverrideMicroVisualizationThreshold(idx)" [attr.aria-label]="tx('columns.renderer.microVisualization.thresholds.remove', 'Remove band')">
50601
+ <mat-icon>delete</mat-icon>
50602
+ </button>
50603
+ </div>
50604
+ </div>
50605
+ }
50606
+ </div>
50607
+ }
50012
50608
  <mat-form-field appearance="outline">
50013
50609
  <mat-label>{{ tx('columns.renderer.microVisualization.fallbackText', 'Accessible fallback text') }}</mat-label>
50014
50610
  <input matInput [value]="getOverrideMicroVisualizationProp('fallbackText') || ''" (input)="setOverrideMicroVisualizationProp('fallbackText', $any($event.target).value)" />
@@ -50024,7 +50620,7 @@ class ColumnsConfigEditorComponent {
50024
50620
  </div>
50025
50621
  </div>
50026
50622
  </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 });
50623
+ `, 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
50624
  }
50029
50625
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ColumnsConfigEditorComponent, decorators: [{
50030
50626
  type: Component,
@@ -51115,18 +51711,162 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
51115
51711
  }
51116
51712
  <mat-form-field appearance="outline">
51117
51713
  <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)">
51714
+ <mat-select [ngModel]="getRendererProp('microVisualization','visualization.tone') ?? ''" [ngModelOptions]="{standalone: true}" (ngModelChange)="setMicroVisualizationProp('tone', $event)">
51715
+ <mat-option [value]="''">{{ tx('columns.renderer.microVisualization.tone.auto', 'Automatic (by thresholds / semantic)') }}</mat-option>
51119
51716
  <mat-option value="neutral">{{ tx('columns.renderer.microVisualization.tone.neutral', 'Neutral') }}</mat-option>
51120
51717
  <mat-option value="info">{{ tx('columns.renderer.microVisualization.tone.info', 'Info') }}</mat-option>
51121
51718
  <mat-option value="success">{{ tx('columns.renderer.microVisualization.tone.success', 'Success') }}</mat-option>
51122
51719
  <mat-option value="warning">{{ tx('columns.renderer.microVisualization.tone.warning', 'Warning') }}</mat-option>
51123
51720
  <mat-option value="danger">{{ tx('columns.renderer.microVisualization.tone.danger', 'Danger') }}</mat-option>
51721
+ <mat-option value="critical">{{ tx('columns.renderer.microVisualization.tone.critical', 'Critical') }}</mat-option>
51124
51722
  </mat-select>
51125
51723
  </mat-form-field>
51126
51724
  <mat-form-field appearance="outline">
51127
51725
  <mat-label>{{ tx('columns.renderer.microVisualization.fallbackText', 'Accessible fallback text') }}</mat-label>
51128
51726
  <input matInput [value]="getRendererProp('microVisualization','visualization.fallbackText') || ''" (input)="setMicroVisualizationProp('fallbackText', $any($event.target).value)" />
51129
51727
  </mat-form-field>
51728
+ <div class="renderer-hint" style="grid-column: 1/-1; opacity:.78;">
51729
+ {{ tx('columns.renderer.microVisualization.tone.precedenceHint', 'In Automatic mode, color is resolved from threshold bands or value sign. A fixed tone overrides conditional bands.') }}
51730
+ </div>
51731
+ @if (isMicroVisualizationThresholdsSupported()) {
51732
+ <div
51733
+ data-testid="micro-viz-thresholds-section"
51734
+ style="grid-column: 1/-1; display: grid; gap: 10px; padding: 12px; border: 1px solid var(--md-sys-color-outline-variant); border-radius: 8px;"
51735
+ >
51736
+ <div style="display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap;">
51737
+ <div>
51738
+ <div style="font-weight: 600;">
51739
+ {{ tx('columns.renderer.microVisualization.thresholds.title', 'Color bands (thresholds)') }}
51740
+ </div>
51741
+ <div class="renderer-hint" style="opacity: .78;">
51742
+ {{ tx('columns.renderer.microVisualization.thresholds.description', 'Evaluates top-to-bottom (first matching band wins). Both min and max bounds are inclusive (min <= value <= max).') }}
51743
+ </div>
51744
+ </div>
51745
+ <div style="display: flex; gap: 8px; flex-wrap: wrap;">
51746
+ <button
51747
+ mat-stroked-button
51748
+ type="button"
51749
+ data-testid="micro-viz-add-threshold"
51750
+ (click)="addMicroVisualizationThreshold()"
51751
+ >
51752
+ <mat-icon>add</mat-icon>
51753
+ {{ tx('columns.renderer.microVisualization.thresholds.add', 'Add threshold') }}
51754
+ </button>
51755
+ <button
51756
+ mat-stroked-button
51757
+ type="button"
51758
+ data-testid="micro-viz-preset-thresholds"
51759
+ (click)="applyDefaultMicroVisualizationThresholds()"
51760
+ >
51761
+ <mat-icon>traffic</mat-icon>
51762
+ {{ tx('columns.renderer.microVisualization.thresholds.preset', 'Traffic-light preset (70+ / 40–69 / ≤39)') }}
51763
+ </button>
51764
+ </div>
51765
+ </div>
51766
+ @if (!getMicroVisualizationThresholds().length) {
51767
+ <div class="renderer-hint" style="opacity: .75;">
51768
+ {{ tx('columns.renderer.microVisualization.thresholds.empty', 'No threshold bands configured. Add bands or apply the traffic-light preset to color values automatically.') }}
51769
+ </div>
51770
+ }
51771
+ @for (threshold of getMicroVisualizationThresholds(); track $index) {
51772
+ <div
51773
+ data-testid="micro-viz-threshold-row"
51774
+ style="display: grid; grid-template-columns: minmax(110px, 1fr) minmax(110px, 1fr) minmax(140px, 1fr) auto; gap: 8px; align-items: center;"
51775
+ >
51776
+ <mat-form-field appearance="outline">
51777
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.min', 'Min (inclusive)') }}</mat-label>
51778
+ <input
51779
+ matInput
51780
+ type="number"
51781
+ data-testid="micro-viz-threshold-min"
51782
+ [value]="threshold.min ?? ''"
51783
+ (input)="setMicroVisualizationThresholdNumber($index, 'min', $any($event.target).value)"
51784
+ />
51785
+ </mat-form-field>
51786
+ <mat-form-field appearance="outline">
51787
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.max', 'Max (inclusive)') }}</mat-label>
51788
+ <input
51789
+ matInput
51790
+ type="number"
51791
+ data-testid="micro-viz-threshold-max"
51792
+ [value]="threshold.max ?? ''"
51793
+ (input)="setMicroVisualizationThresholdNumber($index, 'max', $any($event.target).value)"
51794
+ />
51795
+ </mat-form-field>
51796
+ <mat-form-field appearance="outline">
51797
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.tone', 'Band tone') }}</mat-label>
51798
+ <mat-select
51799
+ data-testid="micro-viz-threshold-tone"
51800
+ [ngModel]="threshold.tone || 'info'"
51801
+ [ngModelOptions]="{standalone: true}"
51802
+ (ngModelChange)="setMicroVisualizationThresholdTone($index, $event)"
51803
+ >
51804
+ <mat-option value="success">{{ tx('columns.renderer.microVisualization.tone.success', 'Success') }}</mat-option>
51805
+ <mat-option value="warning">{{ tx('columns.renderer.microVisualization.tone.warning', 'Warning') }}</mat-option>
51806
+ <mat-option value="danger">{{ tx('columns.renderer.microVisualization.tone.danger', 'Danger') }}</mat-option>
51807
+ <mat-option value="critical">{{ tx('columns.renderer.microVisualization.tone.critical', 'Critical') }}</mat-option>
51808
+ <mat-option value="info">{{ tx('columns.renderer.microVisualization.tone.info', 'Info') }}</mat-option>
51809
+ <mat-option value="neutral">{{ tx('columns.renderer.microVisualization.tone.neutral', 'Neutral') }}</mat-option>
51810
+ </mat-select>
51811
+ </mat-form-field>
51812
+ <div style="display: flex; gap: 4px; align-items: center;">
51813
+ <button
51814
+ praxisIconButton="arrow_upward"
51815
+ size="dense"
51816
+ type="button"
51817
+ data-testid="micro-viz-threshold-up"
51818
+ [disabled]="$index === 0"
51819
+ [matTooltip]="tx('columns.renderer.microVisualization.thresholds.moveUp', 'Move threshold up')"
51820
+ [attr.aria-label]="tx('columns.renderer.microVisualization.thresholds.moveUp', 'Move threshold up')"
51821
+ (click)="moveMicroVisualizationThreshold($index, -1)"
51822
+ ></button>
51823
+ <button
51824
+ praxisIconButton="arrow_downward"
51825
+ size="dense"
51826
+ type="button"
51827
+ data-testid="micro-viz-threshold-down"
51828
+ [disabled]="$index === getMicroVisualizationThresholds().length - 1"
51829
+ [matTooltip]="tx('columns.renderer.microVisualization.thresholds.moveDown', 'Move threshold down')"
51830
+ [attr.aria-label]="tx('columns.renderer.microVisualization.thresholds.moveDown', 'Move threshold down')"
51831
+ (click)="moveMicroVisualizationThreshold($index, 1)"
51832
+ ></button>
51833
+ <button
51834
+ praxisIconButton="delete_outline"
51835
+ size="dense"
51836
+ type="button"
51837
+ data-testid="micro-viz-threshold-remove"
51838
+ [matTooltip]="tx('columns.renderer.microVisualization.thresholds.remove', 'Remove threshold')"
51839
+ [attr.aria-label]="tx('columns.renderer.microVisualization.thresholds.remove', 'Remove threshold')"
51840
+ (click)="removeMicroVisualizationThreshold($index)"
51841
+ ></button>
51842
+ </div>
51843
+ </div>
51844
+ }
51845
+ @if (isMicroVisualizationValueKind()) {
51846
+ <mat-form-field appearance="outline">
51847
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.sampleValues', 'Preview sample values (comma-separated)') }}</mat-label>
51848
+ <input
51849
+ matInput
51850
+ data-testid="micro-viz-sample-values-input"
51851
+ [value]="microVisualizationSampleValuesText"
51852
+ (input)="setMicroVisualizationSampleValuesText($any($event.target).value)"
51853
+ />
51854
+ </mat-form-field>
51855
+ <div
51856
+ data-testid="micro-viz-sample-preview"
51857
+ style="display: flex; gap: 8px; flex-wrap: wrap; align-items: center;"
51858
+ >
51859
+ @for (sampleVal of getMicroVisualizationSampleValues(); track $index) {
51860
+ <span
51861
+ class="pfx-micro-viz-cell"
51862
+ [attr.data-sample-value]="sampleVal"
51863
+ [innerHTML]="getPreviewMicroVisualizationHtmlForValue(sampleVal)"
51864
+ ></span>
51865
+ }
51866
+ </div>
51867
+ }
51868
+ </div>
51869
+ }
51130
51870
  <div class="renderer-hint" style="grid-column: 1/-1; opacity:.78;">
51131
51871
  {{ 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
51872
  </div>
@@ -52027,14 +52767,79 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
52027
52767
  }
52028
52768
  <mat-form-field appearance="outline">
52029
52769
  <mat-label>{{ tx('columns.renderer.microVisualization.tone', 'Tone') }}</mat-label>
52030
- <mat-select [ngModel]="getOverrideMicroVisualizationProp('tone') || 'info'" [ngModelOptions]="{standalone:true}" (ngModelChange)="setOverrideMicroVisualizationProp('tone', $event)">
52770
+ <mat-select [ngModel]="getOverrideMicroVisualizationProp('tone') || ''" [ngModelOptions]="{standalone:true}" (ngModelChange)="setOverrideMicroVisualizationProp('tone', $event || undefined)">
52771
+ <mat-option value="">{{ tx('columns.renderer.microVisualization.tone.auto', 'Automatic (thresholds / default)') }}</mat-option>
52031
52772
  <mat-option value="neutral">{{ tx('columns.renderer.microVisualization.tone.neutral', 'Neutral') }}</mat-option>
52032
52773
  <mat-option value="info">{{ tx('columns.renderer.microVisualization.tone.info', 'Info') }}</mat-option>
52033
52774
  <mat-option value="success">{{ tx('columns.renderer.microVisualization.tone.success', 'Success') }}</mat-option>
52034
52775
  <mat-option value="warning">{{ tx('columns.renderer.microVisualization.tone.warning', 'Warning') }}</mat-option>
52035
52776
  <mat-option value="danger">{{ tx('columns.renderer.microVisualization.tone.danger', 'Danger') }}</mat-option>
52777
+ <mat-option value="critical">{{ tx('columns.renderer.microVisualization.tone.critical', 'Critical') }}</mat-option>
52036
52778
  </mat-select>
52779
+ <mat-hint>{{ tx('columns.renderer.microVisualization.tone.precedenceHint', 'Fixed tone overrides thresholds. Choose Automatic to let color bands control the tone.') }}</mat-hint>
52037
52780
  </mat-form-field>
52781
+ @if (isOverrideMicroVisualizationThresholdsSupported()) {
52782
+ <div class="micro-viz-thresholds-section" data-testid="override-micro-viz-thresholds-section">
52783
+ <div class="compose-header">
52784
+ <div>
52785
+ <div class="section-subtitle">{{ tx('columns.renderer.microVisualization.thresholds.title', 'Color bands (thresholds)') }}</div>
52786
+ <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>
52787
+ </div>
52788
+ <div style="display:flex; gap:8px; flex-wrap:wrap;">
52789
+ <button mat-stroked-button type="button" data-testid="override-micro-viz-preset-thresholds" (click)="applyDefaultOverrideMicroVisualizationThresholds()">
52790
+ <mat-icon>palette</mat-icon>
52791
+ {{ tx('columns.renderer.microVisualization.thresholds.applyPreset', 'Traffic-light preset') }}
52792
+ </button>
52793
+ <button mat-stroked-button type="button" data-testid="override-micro-viz-add-threshold" (click)="addOverrideMicroVisualizationThreshold()">
52794
+ <mat-icon>add</mat-icon>
52795
+ {{ tx('columns.renderer.microVisualization.thresholds.add', 'Add band') }}
52796
+ </button>
52797
+ </div>
52798
+ </div>
52799
+ @if (getOverrideMicroVisualizationProp('tone') && getOverrideMicroVisualizationThresholds().length > 0) {
52800
+ <div class="field-hint micro-viz-guidance is-conditional">
52801
+ {{ 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.') }}
52802
+ </div>
52803
+ }
52804
+ @if (getOverrideMicroVisualizationThresholds().length === 0) {
52805
+ <div class="field-hint">{{ tx('columns.renderer.microVisualization.thresholds.empty', 'No color bands configured.') }}</div>
52806
+ }
52807
+ @for (threshold of getOverrideMicroVisualizationThresholds(); track $index; let idx = $index) {
52808
+ <div class="form-row three-columns micro-viz-threshold-row" [attr.data-testid]="'override-micro-viz-threshold-row-' + idx">
52809
+ <mat-form-field appearance="outline">
52810
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.min', 'Min') }}</mat-label>
52811
+ <input matInput type="number" [value]="threshold.min ?? ''" (input)="setOverrideMicroVisualizationThresholdNumber(idx, 'min', $any($event.target).value)" />
52812
+ </mat-form-field>
52813
+ <mat-form-field appearance="outline">
52814
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.max', 'Max') }}</mat-label>
52815
+ <input matInput type="number" [value]="threshold.max ?? ''" (input)="setOverrideMicroVisualizationThresholdNumber(idx, 'max', $any($event.target).value)" />
52816
+ </mat-form-field>
52817
+ <div style="display:flex; gap:6px; align-items:flex-start;">
52818
+ <mat-form-field appearance="outline" style="flex:1;">
52819
+ <mat-label>{{ tx('columns.renderer.microVisualization.thresholds.tone', 'Band tone') }}</mat-label>
52820
+ <mat-select [ngModel]="threshold.tone || 'neutral'" [ngModelOptions]="{standalone:true}" (ngModelChange)="setOverrideMicroVisualizationThresholdTone(idx, $event)">
52821
+ <mat-option value="neutral">{{ tx('columns.renderer.microVisualization.tone.neutral', 'Neutral') }}</mat-option>
52822
+ <mat-option value="info">{{ tx('columns.renderer.microVisualization.tone.info', 'Info') }}</mat-option>
52823
+ <mat-option value="success">{{ tx('columns.renderer.microVisualization.tone.success', 'Success') }}</mat-option>
52824
+ <mat-option value="warning">{{ tx('columns.renderer.microVisualization.tone.warning', 'Warning') }}</mat-option>
52825
+ <mat-option value="danger">{{ tx('columns.renderer.microVisualization.tone.danger', 'Danger') }}</mat-option>
52826
+ <mat-option value="critical">{{ tx('columns.renderer.microVisualization.tone.critical', 'Critical') }}</mat-option>
52827
+ </mat-select>
52828
+ </mat-form-field>
52829
+ <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')">
52830
+ <mat-icon>arrow_upward</mat-icon>
52831
+ </button>
52832
+ <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')">
52833
+ <mat-icon>arrow_downward</mat-icon>
52834
+ </button>
52835
+ <button mat-icon-button type="button" color="warn" (click)="removeOverrideMicroVisualizationThreshold(idx)" [attr.aria-label]="tx('columns.renderer.microVisualization.thresholds.remove', 'Remove band')">
52836
+ <mat-icon>delete</mat-icon>
52837
+ </button>
52838
+ </div>
52839
+ </div>
52840
+ }
52841
+ </div>
52842
+ }
52038
52843
  <mat-form-field appearance="outline">
52039
52844
  <mat-label>{{ tx('columns.renderer.microVisualization.fallbackText', 'Accessible fallback text') }}</mat-label>
52040
52845
  <input matInput [value]="getOverrideMicroVisualizationProp('fallbackText') || ''" (input)="setOverrideMicroVisualizationProp('fallbackText', $any($event.target).value)" />