@praxisui/table 8.0.0-beta.18 → 8.0.0-beta.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1770,3 +1770,30 @@ Apache-2.0 — consulte `LICENSE` na raiz do workspace para detalhes.
1770
1770
  **Parte do Praxis UI Workspace**
1771
1771
  **Versão**: 2.0.0 (Unified Architecture)
1772
1772
  **Compatibilidade**: Angular 18+
1773
+
1774
+ ## Discovery contextual em ações por linha
1775
+
1776
+ Use `actions.row.discovery.enabled=false` quando a tabela precisar operar em uma
1777
+ experiência corporativa estritamente curada pelo host. Nesse modo, o runtime
1778
+ mantém somente as ações declaradas em `actions.row.actions[]` e não adiciona
1779
+ ações descobertas dinamicamente por HATEOAS/capabilities.
1780
+
1781
+ ```ts
1782
+ actions: {
1783
+ row: {
1784
+ enabled: true,
1785
+ display: 'buttons',
1786
+ discovery: { enabled: false },
1787
+ actions: [
1788
+ { id: 'briefing', label: 'Briefing', action: 'inspect-surfaces' },
1789
+ { id: 'capabilities', label: 'Capabilities', action: 'inspect-actions' },
1790
+ ],
1791
+ },
1792
+ }
1793
+ ```
1794
+
1795
+ O campo controla apenas a descoberta contextual de row actions. Ele não desativa
1796
+ `behavior.expansion.detail.source.mode="hypermedia"`; quando a expansão
1797
+ hypermedia estiver ativa, o detail ainda pode resolver `surfaces` e `actions`
1798
+ sob demanda ao expandir a linha. O default continua habilitado quando
1799
+ `actions.row.discovery.enabled` é omitido.
@@ -7596,6 +7596,8 @@ const PRAXIS_TABLE_EDITOR_I18N_CONFIG = {
7596
7596
  'toolbar.row.width': 'Largura da coluna de ações',
7597
7597
  'toolbar.row.width.placeholder': 'ex.: 96px, 6rem',
7598
7598
  'toolbar.row.width.hint': 'Aplica no cabeçalho e nas células.',
7599
+ 'toolbar.row.discovery.enabled': 'Descoberta contextual',
7600
+ 'toolbar.row.discovery.hint': 'Quando habilitada, a tabela pode adicionar ações contextuais a partir de links HATEOAS e capabilities da linha.',
7599
7601
  'toolbar.row.sticky': 'Fixar coluna de ações',
7600
7602
  'toolbar.row.sticky.none': 'Nenhum',
7601
7603
  'toolbar.row.sticky.start': 'Início',
@@ -8628,6 +8630,8 @@ const PRAXIS_TABLE_EDITOR_I18N_CONFIG = {
8628
8630
  'toolbar.row.width': 'Actions column width',
8629
8631
  'toolbar.row.width.placeholder': 'e.g. 96px, 6rem',
8630
8632
  'toolbar.row.width.hint': 'Applies to header and cells.',
8633
+ 'toolbar.row.discovery.enabled': 'Contextual discovery',
8634
+ 'toolbar.row.discovery.hint': 'When enabled, the table can add contextual actions from row HATEOAS links and capabilities.',
8631
8635
  'toolbar.row.sticky': 'Pin actions column',
8632
8636
  'toolbar.row.sticky.none': 'None',
8633
8637
  'toolbar.row.sticky.start': 'Start',
@@ -17937,13 +17941,36 @@ class ToolbarActionsEditorComponent {
17937
17941
  ngOnChanges(changes) {
17938
17942
  if (changes['config'] && this.toolbarForm) {
17939
17943
  const toolbar = this.config.toolbar || {};
17944
+ const v2Config = this.config;
17945
+ const rowCfg = v2Config.actions?.row;
17946
+ const bulkCfg = v2Config.actions?.bulk;
17947
+ const beh = rowCfg?.behavior;
17948
+ const displayRaw = rowCfg?.display || 'menu';
17949
+ const displayUI = displayRaw === 'icons' && beh && beh.enabled !== false && beh.maxInline
17950
+ ? 'hybrid'
17951
+ : displayRaw;
17940
17952
  this.toolbarForm.patchValue({
17941
17953
  toolbarVisible: toolbar.visible || false,
17942
17954
  toolbarTitle: toolbar.title || '',
17943
17955
  toolbarSubtitle: toolbar.subtitle || '',
17944
17956
  toolbarActionsPosition: toolbar.actionsPosition || toolbar.position || 'top',
17945
17957
  toolbarActionsBackgroundColor: toolbar.actionsBackgroundColor || '',
17946
- toolbarHeight: toolbar.height || 64,
17958
+ toolbarHeight: toolbar.layout?.height || toolbar.height || 64,
17959
+ rowActionsEnabled: rowCfg?.enabled || false,
17960
+ rowActionsDisplay: displayUI,
17961
+ rowActionsTrigger: rowCfg?.trigger || 'always',
17962
+ maxVisibleRowActions: rowCfg?.maxVisibleActions || 3,
17963
+ rowActionsWidth: rowCfg?.width || '120px',
17964
+ rowActionsSticky: rowCfg?.sticky ?? null,
17965
+ rowActionsMenuIcon: rowCfg?.menuIcon || 'more_vert',
17966
+ rowActionsMenuButtonColor: rowCfg?.menuButtonColor || 'basic',
17967
+ rowActionsDiscoveryEnabled: rowCfg?.discovery?.enabled !== false,
17968
+ rowActionsHeaderLabel: rowCfg?.header?.label || '',
17969
+ rowActionsHeaderIcon: rowCfg?.header?.icon || '',
17970
+ rowActionsHeaderTooltip: rowCfg?.header?.tooltip || '',
17971
+ rowActionsHeaderAlign: rowCfg?.header?.align || 'end',
17972
+ bulkActionsEnabled: bulkCfg?.enabled || false,
17973
+ bulkActionsPosition: bulkCfg?.position || 'toolbar',
17947
17974
  }, { emitEvent: false });
17948
17975
  this.syncActionsFromConfig();
17949
17976
  }
@@ -17981,6 +18008,7 @@ class ToolbarActionsEditorComponent {
17981
18008
  rowActionsSticky: [null],
17982
18009
  rowActionsMenuIcon: ['more_vert'],
17983
18010
  rowActionsMenuButtonColor: ['basic'],
18011
+ rowActionsDiscoveryEnabled: [true],
17984
18012
  rowActionsHeaderLabel: [''],
17985
18013
  rowActionsHeaderIcon: [''],
17986
18014
  rowActionsHeaderTooltip: [''],
@@ -18025,6 +18053,7 @@ class ToolbarActionsEditorComponent {
18025
18053
  rowActionsSticky: rowCfg?.sticky ?? null,
18026
18054
  rowActionsMenuIcon: rowCfg?.menuIcon || 'more_vert',
18027
18055
  rowActionsMenuButtonColor: rowCfg?.menuButtonColor || 'basic',
18056
+ rowActionsDiscoveryEnabled: rowCfg?.discovery?.enabled !== false,
18028
18057
  rowActionsHeaderLabel: rowCfg?.header?.label || '',
18029
18058
  rowActionsHeaderIcon: rowCfg?.header?.icon || '',
18030
18059
  rowActionsHeaderTooltip: rowCfg?.header?.tooltip || '',
@@ -18238,6 +18267,9 @@ class ToolbarActionsEditorComponent {
18238
18267
  maxVisibleActions: values.maxVisibleRowActions,
18239
18268
  menuIcon: values.rowActionsMenuIcon || 'more_vert',
18240
18269
  menuButtonColor: values.rowActionsMenuButtonColor !== 'basic' ? values.rowActionsMenuButtonColor : undefined,
18270
+ discovery: {
18271
+ enabled: values.rowActionsDiscoveryEnabled !== false,
18272
+ },
18241
18273
  header: {
18242
18274
  label: values.rowActionsHeaderLabel || undefined,
18243
18275
  icon: values.rowActionsHeaderIcon || undefined,
@@ -19916,6 +19948,14 @@ class ToolbarActionsEditorComponent {
19916
19948
  </mat-select>
19917
19949
  </mat-form-field>
19918
19950
 
19951
+ <mat-slide-toggle
19952
+ formControlName="rowActionsDiscoveryEnabled"
19953
+ class="toggle-field"
19954
+ [matTooltip]="tx('toolbar.row.discovery.hint', 'When enabled, the table can add contextual actions from row HATEOAS links and capabilities.')"
19955
+ >
19956
+ {{ tx('toolbar.row.discovery.enabled', 'Contextual discovery') }}
19957
+ </mat-slide-toggle>
19958
+
19919
19959
  <mat-form-field appearance="outline">
19920
19960
  <mat-label>{{ tx('toolbar.row.maxVisible', 'Maximum visible actions') }}</mat-label>
19921
19961
  <input
@@ -21492,6 +21532,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
21492
21532
  </mat-select>
21493
21533
  </mat-form-field>
21494
21534
 
21535
+ <mat-slide-toggle
21536
+ formControlName="rowActionsDiscoveryEnabled"
21537
+ class="toggle-field"
21538
+ [matTooltip]="tx('toolbar.row.discovery.hint', 'When enabled, the table can add contextual actions from row HATEOAS links and capabilities.')"
21539
+ >
21540
+ {{ tx('toolbar.row.discovery.enabled', 'Contextual discovery') }}
21541
+ </mat-slide-toggle>
21542
+
21495
21543
  <mat-form-field appearance="outline">
21496
21544
  <mat-label>{{ tx('toolbar.row.maxVisible', 'Maximum visible actions') }}</mat-label>
21497
21545
  <input
@@ -41896,6 +41944,11 @@ class PraxisTable {
41896
41944
  const willAutoDelete = action === 'delete' && (this.autoDelete || bulk.autoDelete);
41897
41945
  const requiresConfirmation = !!bulk.requiresConfirmation || !!willAutoDelete;
41898
41946
  const executeBulk = async () => {
41947
+ const bulkExportFormat = this.resolveBulkExportFormat(action, safeActionConfig);
41948
+ if (bulkExportFormat) {
41949
+ await this.onExportAction({ format: bulkExportFormat });
41950
+ return;
41951
+ }
41899
41952
  if (willAutoDelete) {
41900
41953
  if (this.isLocalDataModeActive()) {
41901
41954
  this.debugLog('Local mode: skipping backend bulk delete and emitting bulkAction', { count: rows.length });
@@ -42043,12 +42096,98 @@ class PraxisTable {
42043
42096
  label: toolbarActionConfig?.label || action,
42044
42097
  });
42045
42098
  }
42099
+ resolveBulkExportFormat(action, actionConfig) {
42100
+ if (this.config?.export?.enabled !== true) {
42101
+ return null;
42102
+ }
42103
+ const actionKey = String(action || '').trim().toLowerCase();
42104
+ const looksLikeExportAction = this.isBulkExportActionKey(actionKey);
42105
+ const hasExplicitExportFormat = actionConfig?.exportFormat !== undefined
42106
+ || actionConfig?.format !== undefined
42107
+ || actionConfig?.params?.format !== undefined
42108
+ || actionConfig?.payload?.format !== undefined;
42109
+ if (!looksLikeExportAction && !hasExplicitExportFormat) {
42110
+ return null;
42111
+ }
42112
+ const configuredFormat = this.normalizeExportFormat(actionConfig?.exportFormat
42113
+ ?? actionConfig?.format
42114
+ ?? actionConfig?.params?.format
42115
+ ?? actionConfig?.payload?.format);
42116
+ if (hasExplicitExportFormat) {
42117
+ return configuredFormat && this.isExportFormatEnabled(configuredFormat)
42118
+ ? configuredFormat
42119
+ : null;
42120
+ }
42121
+ const formatFromAction = this.normalizeExportFormat(actionKey.includes('excel') || actionKey.includes('xlsx')
42122
+ ? 'excel'
42123
+ : actionKey.includes('pdf')
42124
+ ? 'pdf'
42125
+ : actionKey.includes('json')
42126
+ ? 'json'
42127
+ : actionKey.includes('csv')
42128
+ ? 'csv'
42129
+ : null);
42130
+ if (formatFromAction) {
42131
+ return this.isExportFormatEnabled(formatFromAction) ? formatFromAction : null;
42132
+ }
42133
+ if (looksLikeExportAction) {
42134
+ const defaultFormat = this.normalizeExportFormat(this.config?.export?.defaultFormat);
42135
+ if (defaultFormat && this.isExportFormatEnabled(defaultFormat)) {
42136
+ return defaultFormat;
42137
+ }
42138
+ return this.getFirstEnabledExportFormat();
42139
+ }
42140
+ return null;
42141
+ }
42142
+ isBulkExportActionKey(actionKey) {
42143
+ return /(^|[-_.:])export($|[-_.:])|export-selected|bulk-export/.test(actionKey);
42144
+ }
42145
+ normalizeExportFormat(format) {
42146
+ const value = String(format || '').trim().toLowerCase();
42147
+ if (value === 'xlsx')
42148
+ return 'excel';
42149
+ return value === 'excel'
42150
+ || value === 'pdf'
42151
+ || value === 'csv'
42152
+ || value === 'json'
42153
+ || value === 'print'
42154
+ ? value
42155
+ : null;
42156
+ }
42157
+ isExportFormatEnabled(format) {
42158
+ const formats = this.config?.export?.formats;
42159
+ if (!Array.isArray(formats) || formats.length === 0) {
42160
+ return true;
42161
+ }
42162
+ return formats
42163
+ .map((item) => this.normalizeExportFormat(item))
42164
+ .includes(format);
42165
+ }
42166
+ getFirstEnabledExportFormat() {
42167
+ const formats = this.config?.export?.formats;
42168
+ if (!Array.isArray(formats) || formats.length === 0) {
42169
+ return 'csv';
42170
+ }
42171
+ for (const item of formats) {
42172
+ const format = this.normalizeExportFormat(item);
42173
+ if (format)
42174
+ return format;
42175
+ }
42176
+ return null;
42177
+ }
42046
42178
  async onExportAction(event) {
42047
42179
  const format = String(event?.format || '').trim().toLowerCase();
42048
42180
  if (!format)
42049
42181
  return;
42050
42182
  if (this.exportBusy)
42051
42183
  return;
42184
+ if (this.shouldBlockSelectedExportWithoutSelection()) {
42185
+ try {
42186
+ this.snackBar.open(translateTableRuntimeText(this.i18n, 'table.export.noSelection', 'Selecione ao menos um registro para exportar.'), undefined, { duration: 3000 });
42187
+ }
42188
+ catch { }
42189
+ return;
42190
+ }
42052
42191
  this.exportBusy = true;
42053
42192
  this.exportStatusMessage = translateTableRuntimeText(this.i18n, 'table.export.processing', 'Exportando dados...');
42054
42193
  this.cdr.markForCheck();
@@ -42085,6 +42224,10 @@ class PraxisTable {
42085
42224
  this.cdr.markForCheck();
42086
42225
  }
42087
42226
  }
42227
+ shouldBlockSelectedExportWithoutSelection() {
42228
+ const configuredScope = (this.config?.export?.general?.scope || 'auto');
42229
+ return configuredScope === 'selected' && this.selection.selected.length === 0;
42230
+ }
42088
42231
  buildTableExportRequest(format) {
42089
42232
  const general = this.config?.export?.general;
42090
42233
  const scope = (general?.scope || 'auto');
@@ -44194,7 +44337,8 @@ class PraxisTable {
44194
44337
  return this.rowActionCatalogByHref.get(href) || null;
44195
44338
  }
44196
44339
  shouldUseRowDiscovery() {
44197
- if (this.config.actions?.row?.enabled === true) {
44340
+ const rowActionDiscoveryEnabled = this.config.actions?.row?.discovery?.enabled !== false;
44341
+ if (this.config.actions?.row?.enabled === true && rowActionDiscoveryEnabled) {
44198
44342
  return true;
44199
44343
  }
44200
44344
  const expansionMode = String((this.getExpansionConfig()?.detail?.source || {}).mode || '')
@@ -50601,7 +50745,13 @@ const PRAXIS_TABLE_AUTHORING_MANIFEST = {
50601
50745
  properties: {
50602
50746
  enabled: { type: 'boolean' },
50603
50747
  display: { enum: ['menu', 'buttons', 'icons'] },
50604
- trigger: { enum: ['hover', 'always', 'click'] }
50748
+ trigger: { enum: ['hover', 'always', 'click'] },
50749
+ discovery: {
50750
+ type: 'object',
50751
+ properties: {
50752
+ enabled: { type: 'boolean' }
50753
+ }
50754
+ }
50605
50755
  }
50606
50756
  },
50607
50757
  effects: [{ kind: 'merge-object', path: 'actions.row' }],
@@ -53253,6 +53403,7 @@ const TABLE_AI_CAPABILITIES = {
53253
53403
  { path: 'actions.row.enabled', category: 'actions', valueKind: 'boolean', description: 'Habilita coluna de ações por linha.', critical: true, intentExamples: ['ativar ações por linha', 'desligar coluna de ações'], safetyNotes: 'Ao desabilitar, verifique se não há ações críticas dependentes.' },
53254
53404
  { path: 'actions.row.display', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.rowActionDisplay, description: 'Modo de exibição das ações (menu/buttons/icons).', dependsOn: 'actions.row.enabled' },
53255
53405
  { path: 'actions.row.trigger', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.rowActionTrigger, description: 'Como as ações aparecem (hover/always/click).', dependsOn: 'actions.row.enabled' },
53406
+ { path: 'actions.row.discovery.enabled', category: 'actions', valueKind: 'boolean', description: 'Controla se a tabela enriquece ações por linha com discovery contextual de HATEOAS/capabilities.', dependsOn: 'actions.row.enabled', safetyNotes: 'Use false quando a experiência precisar mostrar apenas ações curadas pelo host.' },
53256
53407
  { path: 'actions.bulk.enabled', category: 'actions', valueKind: 'boolean', description: 'Habilita ações em lote.', critical: true, intentExamples: ['ativar ações em lote', 'permitir excluir em massa'], safetyNotes: 'Combine com selection.persistSelection e limites de maxSelections.' },
53257
53408
  { path: 'actions.bulk.position', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.bulkActionPosition, description: 'Posição das ações em lote.', dependsOn: 'actions.bulk.enabled' },
53258
53409
  { path: 'actions.context.enabled', category: 'actions', valueKind: 'boolean', description: 'Habilita menu de contexto (clique direito/long press).', critical: true, intentExamples: ['ativar menu de contexto'] },
package/index.d.ts CHANGED
@@ -1388,9 +1388,15 @@ declare class PraxisTable implements OnInit, OnChanges, AfterViewInit, AfterCont
1388
1388
  action: string;
1389
1389
  actionConfig?: any;
1390
1390
  }): Promise<void>;
1391
+ private resolveBulkExportFormat;
1392
+ private isBulkExportActionKey;
1393
+ private normalizeExportFormat;
1394
+ private isExportFormatEnabled;
1395
+ private getFirstEnabledExportFormat;
1391
1396
  onExportAction(event: {
1392
1397
  format: string;
1393
1398
  }): Promise<void>;
1399
+ private shouldBlockSelectedExportWithoutSelection;
1394
1400
  private buildTableExportRequest;
1395
1401
  private resolveEffectiveExportScope;
1396
1402
  private resolveTableExportLoadedItems;
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@praxisui/table",
3
- "version": "8.0.0-beta.18",
3
+ "version": "8.0.0-beta.19",
4
4
  "description": "Advanced data table for Angular (Praxis UI) with editing, filtering, sorting, virtualization, and settings panel integration.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^20.0.0",
7
7
  "@angular/core": "^20.0.0",
8
- "@praxisui/ai": "^8.0.0-beta.18",
9
- "@praxisui/core": "^8.0.0-beta.18",
10
- "@praxisui/dynamic-fields": "^8.0.0-beta.18",
11
- "@praxisui/dynamic-form": "^8.0.0-beta.18",
12
- "@praxisui/metadata-editor": "^8.0.0-beta.18",
13
- "@praxisui/rich-content": "^8.0.0-beta.18",
14
- "@praxisui/settings-panel": "^8.0.0-beta.18",
15
- "@praxisui/table-rule-builder": "^8.0.0-beta.18"
8
+ "@praxisui/ai": "^8.0.0-beta.19",
9
+ "@praxisui/core": "^8.0.0-beta.19",
10
+ "@praxisui/dynamic-fields": "^8.0.0-beta.19",
11
+ "@praxisui/dynamic-form": "^8.0.0-beta.19",
12
+ "@praxisui/metadata-editor": "^8.0.0-beta.19",
13
+ "@praxisui/rich-content": "^8.0.0-beta.19",
14
+ "@praxisui/settings-panel": "^8.0.0-beta.19",
15
+ "@praxisui/table-rule-builder": "^8.0.0-beta.19"
16
16
  },
17
17
  "dependencies": {
18
18
  "tslib": "^2.3.0"