@praxisui/table 8.0.0-beta.1 → 8.0.0-beta.11

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.
@@ -33,7 +33,7 @@ import * as i12 from '@angular/cdk/drag-drop';
33
33
  import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
34
34
  import { Subject, debounceTime, takeUntil, of, firstValueFrom, BehaviorSubject, take as take$1 } from 'rxjs';
35
35
  import * as i1 from '@praxisui/core';
36
- import { LoggerService, createCorporateLoggerConfig, ConsoleLoggerSink, PraxisJsonLogicService, PraxisIconDirective, PraxisI18nService, isTableConfigV2, providePraxisI18nConfig, GLOBAL_ACTION_SPEC_CATALOG, SURFACE_OPEN_I18N_NAMESPACE, getGlobalActionUiSchema, SurfaceOpenActionEditorComponent, SURFACE_OPEN_I18N_CONFIG, INLINE_FILTER_CONTROL_TYPES, INLINE_FILTER_CONTROL_TYPE_VALUES, FieldControlType, normalizeControlTypeToken, ASYNC_CONFIG_STORAGE, resolveControlTypeAlias, mapFieldDefinitionsToMetadata, PraxisJsonLogicError, GenericCrudService, TableConfigService, createDefaultTableConfig, fillUndefined, deepMerge, INLINE_FILTER_ALIAS_TOKENS, GlobalConfigService, buildSchemaId, MemoryCacheAdapter, LocalStorageCacheAdapter, SchemaMetadataClient, resolveInlineFilterControlType, fetchWithETag, ComponentMetadataRegistry, ResourceQuickConnectComponent, translateResourceAvailabilityReason, PRAXIS_LOADING_CTX, translateResourceDiscoveryText, supportsImplicitValuePresentation, resolveValuePresentation, translateUnavailableWorkflowMessage, CONNECTION_STORAGE, PRAXIS_LOADING_RENDERER, EmptyStateCardComponent, RESOURCE_DISCOVERY_I18N_CONFIG, AnalyticsStatsRequestBuilderService, buildApiUrl, normalizePraxisDataQueryContext, API_URL } from '@praxisui/core';
36
+ import { LoggerService, createCorporateLoggerConfig, ConsoleLoggerSink, PraxisJsonLogicService, PraxisIconDirective, PraxisI18nService, isTableConfigV2, providePraxisI18nConfig, PRAXIS_GLOBAL_ACTION_CATALOG, SURFACE_OPEN_I18N_NAMESPACE, isRequiredGlobalActionPayloadMissing, getGlobalActionUiSchema, getRequiredGlobalActionPayloadKeys, hasMeaningfulGlobalActionPayloadValue, SurfaceOpenActionEditorComponent, SURFACE_OPEN_I18N_CONFIG, INLINE_FILTER_CONTROL_TYPES, INLINE_FILTER_CONTROL_TYPE_VALUES, FieldControlType, normalizeControlTypeToken, ASYNC_CONFIG_STORAGE, resolveControlTypeAlias, mapFieldDefinitionsToMetadata, PraxisJsonLogicError, GLOBAL_ACTION_CATALOG, GenericCrudService, validateGlobalActionRefs, getGlobalActionCatalog, TableConfigService, createDefaultTableConfig, fillUndefined, deepMerge, INLINE_FILTER_ALIAS_TOKENS, GlobalConfigService, buildSchemaId, MemoryCacheAdapter, LocalStorageCacheAdapter, SchemaMetadataClient, resolveInlineFilterControlType, fetchWithETag, ComponentMetadataRegistry, ResourceQuickConnectComponent, translateResourceAvailabilityReason, PRAXIS_LOADING_CTX, translateResourceDiscoveryText, supportsImplicitValuePresentation, resolveValuePresentation, translateUnavailableWorkflowMessage, CONNECTION_STORAGE, PRAXIS_LOADING_RENDERER, EmptyStateCardComponent, RESOURCE_DISCOVERY_I18N_CONFIG, AnalyticsStatsRequestBuilderService, buildApiUrl, normalizePraxisDataQueryContext, API_URL } from '@praxisui/core';
37
37
  import { PraxisRichContent } from '@praxisui/rich-content';
38
38
  import * as i2 from '@angular/material/toolbar';
39
39
  import { MatToolbarModule } from '@angular/material/toolbar';
@@ -1794,13 +1794,20 @@ function parseLegacyOrTableDocument(raw) {
1794
1794
  });
1795
1795
  }
1796
1796
  if (looksLikeLegacyPayload(obj)) {
1797
+ const legacyConfig = extractLegacyTableConfig(obj);
1798
+ const legacyIdField = normalizeIdField(obj.__idField__);
1799
+ if (legacyIdField) {
1800
+ legacyConfig.meta = {
1801
+ ...(legacyConfig.meta || {}),
1802
+ idField: legacyIdField,
1803
+ };
1804
+ }
1797
1805
  return normalizeTableAuthoringDocument({
1798
1806
  kind: DOCUMENT_KIND,
1799
1807
  version: DOCUMENT_VERSION,
1800
- config: extractLegacyTableConfig(obj),
1808
+ config: legacyConfig,
1801
1809
  bindings: {
1802
1810
  resourcePath: normalizeResourcePath(obj.__resourcePath__),
1803
- idField: normalizeIdField(obj.__idField__),
1804
1811
  horizontalScroll: normalizeHorizontalScroll(obj.__horizontalScroll__),
1805
1812
  },
1806
1813
  });
@@ -1808,10 +1815,21 @@ function parseLegacyOrTableDocument(raw) {
1808
1815
  return createTableAuthoringDocument({ config: raw });
1809
1816
  }
1810
1817
  function normalizeTableAuthoringDocument(doc) {
1818
+ const config = ensureAuthoringCompleteness(normalizeTableConfig(doc?.config));
1819
+ const legacyBindingIdField = normalizeIdField(doc?.bindings?.idField);
1820
+ const legacyRuntimeIdField = normalizeIdField(doc?.config?.__idField__);
1821
+ const canonicalIdField = normalizeIdField(config?.meta?.idField);
1822
+ const migratedIdField = canonicalIdField || legacyBindingIdField || legacyRuntimeIdField;
1823
+ if (migratedIdField && !canonicalIdField) {
1824
+ config.meta = {
1825
+ ...(config.meta || {}),
1826
+ idField: migratedIdField,
1827
+ };
1828
+ }
1811
1829
  const normalized = {
1812
1830
  kind: DOCUMENT_KIND,
1813
1831
  version: DOCUMENT_VERSION,
1814
- config: ensureAuthoringCompleteness(normalizeTableConfig(doc?.config)),
1832
+ config,
1815
1833
  };
1816
1834
  if (hasOwnProperty(doc, 'bindings')) {
1817
1835
  normalized.bindings = normalizeBindings(doc?.bindings);
@@ -1837,6 +1855,7 @@ function validateTableAuthoringDocument(doc, context) {
1837
1855
  }
1838
1856
  });
1839
1857
  const bindings = normalized.bindings || {};
1858
+ const metaIdField = trimString(normalized.config?.meta?.idField);
1840
1859
  if ('horizontalScroll' in rawBindings &&
1841
1860
  rawBindings.horizontalScroll !== undefined &&
1842
1861
  normalizeHorizontalScroll(rawBindings.horizontalScroll) === undefined) {
@@ -1856,9 +1875,9 @@ function validateTableAuthoringDocument(doc, context) {
1856
1875
  }
1857
1876
  }
1858
1877
  if (context?.server?.idField &&
1859
- bindings.idField &&
1860
- context.server.idField !== bindings.idField) {
1861
- diagnostics.push(warnDiagnostic('table.bindings.idField.diverges-from-server', 'bindings.idField diverges from current server idField', 'bindings.idField'));
1878
+ metaIdField &&
1879
+ context.server.idField !== metaIdField) {
1880
+ diagnostics.push(warnDiagnostic('table.meta.idField.diverges-from-server', 'config.meta.idField diverges from current server idField', 'config.meta.idField'));
1862
1881
  }
1863
1882
  const configHash = trimString(normalized.config?.meta?.serverHash);
1864
1883
  if (context?.server?.schemaHash && !configHash) {
@@ -1982,7 +2001,6 @@ function normalizeBindings(bindings) {
1982
2001
  return undefined;
1983
2002
  const normalized = {
1984
2003
  resourcePath: normalizeResourcePath(bindings.resourcePath),
1985
- idField: normalizeIdField(bindings.idField),
1986
2004
  horizontalScroll: normalizeHorizontalScroll(bindings.horizontalScroll),
1987
2005
  };
1988
2006
  return stripUndefinedShallow(normalized);
@@ -2002,7 +2020,6 @@ function deriveBindingsDiff(next, current) {
2002
2020
  if (next === undefined) {
2003
2021
  return {
2004
2022
  resourcePathChanged: false,
2005
- idFieldChanged: false,
2006
2023
  horizontalScrollChanged: false,
2007
2024
  requiresDataRefresh: false,
2008
2025
  };
@@ -2011,13 +2028,10 @@ function deriveBindingsDiff(next, current) {
2011
2028
  const normalizedCurrent = normalizeBindings(current) || {};
2012
2029
  const resourcePathChanged = normalizeResourcePath(normalizedNext.resourcePath) !==
2013
2030
  normalizeResourcePath(normalizedCurrent.resourcePath);
2014
- const idFieldChanged = normalizeIdField(normalizedNext.idField) !==
2015
- normalizeIdField(normalizedCurrent.idField);
2016
2031
  const horizontalScrollChanged = normalizeHorizontalScroll(normalizedNext.horizontalScroll) !==
2017
2032
  normalizeHorizontalScroll(normalizedCurrent.horizontalScroll);
2018
2033
  return {
2019
2034
  resourcePathChanged,
2020
- idFieldChanged,
2021
2035
  horizontalScrollChanged,
2022
2036
  requiresDataRefresh: resourcePathChanged,
2023
2037
  };
@@ -7434,6 +7448,7 @@ const PRAXIS_TABLE_EDITOR_I18N_CONFIG = {
7434
7448
  'toolbar.global.section.essential': 'Essencial',
7435
7449
  'toolbar.global.section.dialogOptional': 'Diálogo (opcional)',
7436
7450
  'toolbar.global.validation.validJson': 'Informe um JSON válido.',
7451
+ 'toolbar.global.validation.requiredField': 'Campo obrigatório.',
7437
7452
  'toolbar.section.title': 'Barra de ferramentas',
7438
7453
  'toolbar.section.description': 'Configure a barra superior da tabela',
7439
7454
  'toolbar.visible': 'Mostrar barra de ferramentas',
@@ -8460,6 +8475,7 @@ const PRAXIS_TABLE_EDITOR_I18N_CONFIG = {
8460
8475
  'toolbar.global.section.essential': 'Essential',
8461
8476
  'toolbar.global.section.dialogOptional': 'Dialog (optional)',
8462
8477
  'toolbar.global.validation.validJson': 'Enter valid JSON.',
8478
+ 'toolbar.global.validation.requiredField': 'Required field.',
8463
8479
  'toolbar.section.title': 'Toolbar',
8464
8480
  'toolbar.section.description': 'Configure the top bar of the table',
8465
8481
  'toolbar.visible': 'Show toolbar',
@@ -16072,6 +16088,57 @@ class BehaviorConfigEditorComponent {
16072
16088
  </div>
16073
16089
  </mat-expansion-panel> }
16074
16090
 
16091
+ <!-- Grouping (V2 only) -->
16092
+ @if (isV2) { <mat-expansion-panel>
16093
+ <mat-expansion-panel-header>
16094
+ <mat-panel-title>
16095
+ <mat-icon class="section-icon">group_work</mat-icon>
16096
+ {{ tx('behavior.grouping.title', 'Grouping') }}
16097
+ </mat-panel-title>
16098
+ <mat-panel-description>
16099
+ {{ tx('behavior.grouping.description', 'Group data by specific fields') }}
16100
+ </mat-panel-description>
16101
+ </mat-expansion-panel-header>
16102
+
16103
+ <div class="config-section">
16104
+ <div class="config-fields">
16105
+ <mat-slide-toggle
16106
+ formControlName="groupingEnabled"
16107
+ class="toggle-field"
16108
+ >
16109
+ {{ tx('behavior.grouping.enabled', 'Enable data grouping') }}
16110
+ </mat-slide-toggle>
16111
+
16112
+ @if (behaviorForm.get('groupingEnabled')?.value) {
16113
+ <mat-form-field appearance="outline">
16114
+ <mat-label>{{ tx('behavior.grouping.fields', 'Fields to group by') }}</mat-label>
16115
+ <input
16116
+ matInput
16117
+ formControlName="groupingFields"
16118
+ placeholder="field1, field2"
16119
+ />
16120
+ <button
16121
+ mat-icon-button
16122
+ matSuffix
16123
+ type="button"
16124
+ class="help-icon-button"
16125
+ [matTooltip]="tx('behavior.grouping.fieldsHint', 'Comma-separated field names.')"
16126
+ >
16127
+ <mat-icon>help_outline</mat-icon>
16128
+ </button>
16129
+ </mat-form-field>
16130
+
16131
+ <mat-slide-toggle
16132
+ formControlName="groupingExpanded"
16133
+ class="toggle-field"
16134
+ >
16135
+ {{ tx('behavior.grouping.expanded', 'Start groups expanded') }}
16136
+ </mat-slide-toggle>
16137
+ }
16138
+ </div>
16139
+ </div>
16140
+ </mat-expansion-panel> }
16141
+
16075
16142
  <!-- Interaction (V2 only) -->
16076
16143
  @if (isV2) { <mat-expansion-panel>
16077
16144
  <mat-expansion-panel-header>
@@ -17064,6 +17131,57 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
17064
17131
  </div>
17065
17132
  </mat-expansion-panel> }
17066
17133
 
17134
+ <!-- Grouping (V2 only) -->
17135
+ @if (isV2) { <mat-expansion-panel>
17136
+ <mat-expansion-panel-header>
17137
+ <mat-panel-title>
17138
+ <mat-icon class="section-icon">group_work</mat-icon>
17139
+ {{ tx('behavior.grouping.title', 'Grouping') }}
17140
+ </mat-panel-title>
17141
+ <mat-panel-description>
17142
+ {{ tx('behavior.grouping.description', 'Group data by specific fields') }}
17143
+ </mat-panel-description>
17144
+ </mat-expansion-panel-header>
17145
+
17146
+ <div class="config-section">
17147
+ <div class="config-fields">
17148
+ <mat-slide-toggle
17149
+ formControlName="groupingEnabled"
17150
+ class="toggle-field"
17151
+ >
17152
+ {{ tx('behavior.grouping.enabled', 'Enable data grouping') }}
17153
+ </mat-slide-toggle>
17154
+
17155
+ @if (behaviorForm.get('groupingEnabled')?.value) {
17156
+ <mat-form-field appearance="outline">
17157
+ <mat-label>{{ tx('behavior.grouping.fields', 'Fields to group by') }}</mat-label>
17158
+ <input
17159
+ matInput
17160
+ formControlName="groupingFields"
17161
+ placeholder="field1, field2"
17162
+ />
17163
+ <button
17164
+ mat-icon-button
17165
+ matSuffix
17166
+ type="button"
17167
+ class="help-icon-button"
17168
+ [matTooltip]="tx('behavior.grouping.fieldsHint', 'Comma-separated field names.')"
17169
+ >
17170
+ <mat-icon>help_outline</mat-icon>
17171
+ </button>
17172
+ </mat-form-field>
17173
+
17174
+ <mat-slide-toggle
17175
+ formControlName="groupingExpanded"
17176
+ class="toggle-field"
17177
+ >
17178
+ {{ tx('behavior.grouping.expanded', 'Start groups expanded') }}
17179
+ </mat-slide-toggle>
17180
+ }
17181
+ </div>
17182
+ </div>
17183
+ </mat-expansion-panel> }
17184
+
17067
17185
  <!-- Interaction (V2 only) -->
17068
17186
  @if (isV2) { <mat-expansion-panel>
17069
17187
  <mat-expansion-panel-header>
@@ -17667,7 +17785,20 @@ class ToolbarActionsEditorComponent {
17667
17785
  editingToolbarActionIndex = null;
17668
17786
  editingRowActionIndex = null;
17669
17787
  editingBulkActionIndex = null;
17670
- globalActionCatalog = GLOBAL_ACTION_SPEC_CATALOG;
17788
+ i18n = inject(PraxisI18nService);
17789
+ globalActionCatalog = PRAXIS_GLOBAL_ACTION_CATALOG.map((entry) => ({
17790
+ ...entry,
17791
+ param: entry.payloadSchema
17792
+ ? {
17793
+ label: this.tx('toolbar.action.param.defaultLabel', 'Payload'),
17794
+ placeholder: entry.payloadSchema.example
17795
+ ? JSON.stringify(entry.payloadSchema.example)
17796
+ : '{ }',
17797
+ hint: this.tx('toolbar.global.payload.hint', 'Use JSON when the global app action requires structured payload.'),
17798
+ required: !!entry.payloadSchema.required?.length,
17799
+ }
17800
+ : undefined,
17801
+ }));
17671
17802
  get internalActionCatalog() {
17672
17803
  return [
17673
17804
  {
@@ -17707,7 +17838,6 @@ class ToolbarActionsEditorComponent {
17707
17838
  actionFieldDrafts = new Map();
17708
17839
  actionFieldErrors = new Map();
17709
17840
  warnedInvalidShortcutScopes = new Set();
17710
- i18n = inject(PraxisI18nService);
17711
17841
  destroy$ = new Subject();
17712
17842
  constructor(fb, iconPicker) {
17713
17843
  this.fb = fb;
@@ -18094,19 +18224,26 @@ class ToolbarActionsEditorComponent {
18094
18224
  // =============================================================
18095
18225
  // Global actions catalog helpers
18096
18226
  // =============================================================
18097
- getActionSelectValue(value) {
18098
- const parsed = this.parseActionValue(value);
18099
- if (this.isInternalAction(parsed.id))
18100
- return parsed.id;
18101
- return parsed.isGlobal ? parsed.id : this.customActionValue;
18227
+ getActionSelectValue(action) {
18228
+ const localAction = String(action?.action || '').trim();
18229
+ if (this.isInternalAction(localAction))
18230
+ return localAction;
18231
+ return action?.globalAction?.actionId || this.customActionValue;
18232
+ }
18233
+ getActionParam(action) {
18234
+ const payload = action?.globalAction?.payload;
18235
+ if (payload === undefined || payload === null)
18236
+ return '';
18237
+ return typeof payload === 'string' ? payload : this.stringifyJson(payload);
18102
18238
  }
18103
- getActionParam(value) {
18104
- const parsed = this.parseActionValue(value);
18105
- return parsed.isGlobal ? parsed.param : '';
18239
+ isRequiredGlobalActionParamMissing(action, spec) {
18240
+ return isRequiredGlobalActionPayloadMissing(action.globalAction, spec);
18106
18241
  }
18107
- getActionCustomValue(value) {
18108
- const parsed = this.parseActionValue(value);
18109
- return parsed.isGlobal || this.isInternalAction(parsed.id) ? '' : parsed.raw;
18242
+ getActionCustomValue(action) {
18243
+ const localAction = String(action?.action || '').trim();
18244
+ return action?.globalAction || this.isInternalAction(localAction)
18245
+ ? ''
18246
+ : localAction;
18110
18247
  }
18111
18248
  isInternalAction(value) {
18112
18249
  const raw = String(value || '').trim().toLowerCase();
@@ -18117,36 +18254,51 @@ class ToolbarActionsEditorComponent {
18117
18254
  }
18118
18255
  onActionSelectChange(action, value, kind) {
18119
18256
  if (value === this.customActionValue) {
18120
- const parsed = this.parseActionValue(action.action);
18121
- if (parsed.isGlobal) {
18257
+ action.globalAction = undefined;
18258
+ if (this.isInternalAction(action.action))
18122
18259
  action.action = '';
18123
- }
18124
18260
  this.touchActionList(kind);
18125
18261
  return;
18126
18262
  }
18127
18263
  if (this.isInternalAction(value)) {
18128
18264
  action.action = value;
18265
+ action.globalAction = undefined;
18129
18266
  this.touchActionList(kind);
18130
18267
  return;
18131
18268
  }
18132
- const parsed = this.parseActionValue(action.action);
18133
- const param = parsed.isGlobal && parsed.id === value ? parsed.param : '';
18134
- action.action = param ? `${value}:${param}` : value;
18269
+ const payload = action.globalAction?.actionId === value
18270
+ ? action.globalAction.payload
18271
+ : undefined;
18272
+ action.globalAction =
18273
+ payload === undefined ? { actionId: value } : { actionId: value, payload };
18274
+ action.action = '';
18135
18275
  this.touchActionList(kind);
18136
18276
  }
18137
18277
  onActionParamChange(action, value, kind) {
18138
- const parsed = this.parseActionValue(action.action);
18139
- const id = parsed.isGlobal ? parsed.id : '';
18278
+ const id = action.globalAction?.actionId;
18140
18279
  if (!id) {
18141
18280
  action.action = value;
18281
+ this.touchActionList(kind);
18282
+ return;
18142
18283
  }
18143
- else {
18144
- action.action = value ? `${id}:${value}` : id;
18284
+ const trimmed = String(value || '').trim();
18285
+ if (!trimmed) {
18286
+ action.globalAction = { actionId: id };
18287
+ this.touchActionList(kind);
18288
+ return;
18145
18289
  }
18290
+ try {
18291
+ action.globalAction = { actionId: id, payload: JSON.parse(trimmed) };
18292
+ }
18293
+ catch {
18294
+ action.globalAction = { actionId: id, payload: trimmed };
18295
+ }
18296
+ action.action = '';
18146
18297
  this.touchActionList(kind);
18147
18298
  }
18148
18299
  onActionCustomChange(action, value, kind) {
18149
18300
  action.action = value;
18301
+ action.globalAction = undefined;
18150
18302
  this.touchActionList(kind);
18151
18303
  }
18152
18304
  touchActionList(kind) {
@@ -18160,50 +18312,23 @@ class ToolbarActionsEditorComponent {
18160
18312
  }
18161
18313
  this.updateBulkActions();
18162
18314
  }
18163
- parseActionValue(value) {
18164
- const raw = String(value || '').trim();
18165
- if (!raw) {
18166
- return { id: this.customActionValue, param: '', isGlobal: false, raw: '' };
18167
- }
18168
- if (this.isInternalAction(raw)) {
18169
- return { id: raw, param: '', isGlobal: false, raw };
18170
- }
18171
- const [prefix, ...rest] = raw.split(':');
18172
- const candidate = this.globalActionCatalog.find((item) => item.id === prefix);
18173
- if (candidate) {
18174
- return {
18175
- id: candidate.id,
18176
- param: rest.join(':'),
18177
- isGlobal: true,
18178
- raw,
18179
- };
18180
- }
18181
- const exact = this.globalActionCatalog.find((item) => item.id === raw);
18182
- if (exact) {
18183
- return { id: exact.id, param: '', isGlobal: true, raw };
18184
- }
18185
- return { id: this.customActionValue, param: '', isGlobal: false, raw };
18186
- }
18187
18315
  isGlobalActionId(id) {
18188
18316
  if (!id)
18189
18317
  return false;
18190
18318
  return this.globalActionCatalog.some((item) => item.id === id);
18191
18319
  }
18192
18320
  getGlobalActionSchema(action) {
18193
- const parsed = this.parseActionValue(action.action);
18194
- if (!parsed.isGlobal)
18195
- return undefined;
18196
- return getGlobalActionUiSchema(parsed.id);
18321
+ return getGlobalActionUiSchema(action.globalAction?.actionId);
18197
18322
  }
18198
18323
  getSurfaceOpenActionPayload(action) {
18199
- const info = this.getActionParamInfo(action.action);
18200
- const payload = info.isJson && info.json && typeof info.json === 'object' && !Array.isArray(info.json)
18201
- ? info.json
18202
- : undefined;
18203
- return this.normalizeSurfaceOpenPayload(payload);
18324
+ return this.normalizeSurfaceOpenPayload(action.globalAction?.payload);
18204
18325
  }
18205
18326
  onSurfaceOpenActionPayloadChange(action, payload, kind) {
18206
- action.action = `surface.open:${JSON.stringify(this.normalizeSurfaceOpenPayload(payload))}`;
18327
+ action.globalAction = {
18328
+ actionId: 'surface.open',
18329
+ payload: this.normalizeSurfaceOpenPayload(payload),
18330
+ };
18331
+ action.action = '';
18207
18332
  this.touchActionList(kind);
18208
18333
  }
18209
18334
  shouldShowGlobalActionField(action, field) {
@@ -18215,6 +18340,18 @@ class ToolbarActionsEditorComponent {
18215
18340
  const values = this.collectGlobalActionFieldValues(action, schema.fields);
18216
18341
  return String(values[field.dependsOnKey] ?? '') === String(field.dependsOnValue ?? '');
18217
18342
  }
18343
+ isGlobalActionFieldRequired(action, field) {
18344
+ const actionId = action.globalAction?.actionId;
18345
+ const spec = this.globalActionCatalog.find((item) => item.id === actionId);
18346
+ return getRequiredGlobalActionPayloadKeys(actionId, spec).includes(field.key);
18347
+ }
18348
+ isGlobalActionFieldMissing(action, field) {
18349
+ if (!this.isGlobalActionFieldRequired(action, field))
18350
+ return false;
18351
+ if (!this.shouldShowGlobalActionField(action, field))
18352
+ return false;
18353
+ return !hasMeaningfulGlobalActionPayloadValue(this.getGlobalActionFieldValue(action, field));
18354
+ }
18218
18355
  hasActionFieldError(action, key) {
18219
18356
  const errors = this.actionFieldErrors.get(this.getActionDraftKey(action));
18220
18357
  return !!errors?.[key];
@@ -18229,36 +18366,16 @@ class ToolbarActionsEditorComponent {
18229
18366
  if (draft !== undefined)
18230
18367
  return draft;
18231
18368
  }
18232
- const info = this.getActionParamInfo(action.action);
18233
- const raw = info.param || '';
18234
- const json = info.json || {};
18235
- const hasJson = info.isJson;
18369
+ const json = action.globalAction?.payload || {};
18236
18370
  switch (field.key) {
18237
18371
  case 'message':
18238
- return json.message ?? json.text ?? (hasJson ? '' : raw);
18372
+ return json.message ?? json.text ?? '';
18239
18373
  case 'url':
18240
- if (json.url)
18241
- return json.url;
18242
- if (!hasJson && info.id === 'apiCall') {
18243
- const parsed = this.parseMethodAndUrl(raw);
18244
- return parsed.url || '';
18245
- }
18246
- return hasJson ? '' : raw;
18247
- case 'method': {
18248
- if (json.method)
18249
- return String(json.method);
18250
- if (!hasJson) {
18251
- const parsed = this.parseMethodAndUrl(raw);
18252
- return parsed.method || '';
18253
- }
18254
- return '';
18255
- }
18256
- case 'bodySource':
18257
- return json.bodySource ?? '';
18374
+ return json.url ?? '';
18375
+ case 'params':
18258
18376
  case 'headers':
18259
- return this.stringifyJson(json.headers);
18260
18377
  case 'body':
18261
- return this.stringifyJson(json.body);
18378
+ return this.stringifyJson(json[field.key]);
18262
18379
  case 'contentData':
18263
18380
  return this.stringifyJson(json.content?.data);
18264
18381
  case 'styles':
@@ -18294,17 +18411,14 @@ class ToolbarActionsEditorComponent {
18294
18411
  case 'target':
18295
18412
  return json.target ?? '';
18296
18413
  case 'text':
18297
- return json.text ?? (hasJson ? '' : raw);
18298
- case 'key':
18299
- case 'field':
18300
- return json[field.key] ?? (hasJson ? '' : raw);
18414
+ return json.text ?? '';
18301
18415
  default:
18302
18416
  return json[field.key] ?? '';
18303
18417
  }
18304
18418
  }
18305
18419
  onGlobalActionFieldChange(action, field, value, kind) {
18306
- const selectedId = this.getActionSelectValue(action.action);
18307
- if (!this.isGlobalActionId(selectedId))
18420
+ const selectedId = action.globalAction?.actionId;
18421
+ if (!selectedId || !this.isGlobalActionId(selectedId))
18308
18422
  return;
18309
18423
  const schema = getGlobalActionUiSchema(selectedId);
18310
18424
  if (!schema)
@@ -18331,43 +18445,13 @@ class ToolbarActionsEditorComponent {
18331
18445
  }
18332
18446
  const values = this.collectGlobalActionFieldValues(action, schema.fields);
18333
18447
  values[field.key] = value;
18334
- const info = this.getActionParamInfo(action.action);
18335
- const param = this.serializeGlobalActionParam(selectedId, values, info.json);
18336
- action.action = param ? `${selectedId}:${param}` : selectedId;
18448
+ const payload = this.buildGlobalActionPayload(values, action.globalAction?.payload);
18449
+ action.globalAction = Object.keys(payload).length
18450
+ ? { actionId: selectedId, payload }
18451
+ : { actionId: selectedId };
18452
+ action.action = '';
18337
18453
  this.touchActionList(kind);
18338
18454
  }
18339
- getActionParamInfo(value) {
18340
- const parsed = this.parseActionValue(value);
18341
- const param = parsed.param || '';
18342
- const trimmed = param.trim();
18343
- const looksJson = (trimmed.startsWith('{') && trimmed.endsWith('}')) ||
18344
- (trimmed.startsWith('[') && trimmed.endsWith(']'));
18345
- if (looksJson) {
18346
- try {
18347
- const json = JSON.parse(trimmed);
18348
- if (json && typeof json === 'object') {
18349
- return { ...parsed, param, isJson: true, json };
18350
- }
18351
- }
18352
- catch {
18353
- // ignore
18354
- }
18355
- }
18356
- return { ...parsed, param, isJson: false, json: undefined };
18357
- }
18358
- parseMethodAndUrl(raw) {
18359
- const text = String(raw || '').trim();
18360
- if (!text)
18361
- return {};
18362
- const idx = text.indexOf(':');
18363
- if (idx > 0) {
18364
- return {
18365
- method: text.slice(0, idx).toUpperCase(),
18366
- url: text.slice(idx + 1),
18367
- };
18368
- }
18369
- return { url: text };
18370
- }
18371
18455
  stringifyJson(value) {
18372
18456
  if (value === undefined || value === null || value === '')
18373
18457
  return '';
@@ -18430,60 +18514,6 @@ class ToolbarActionsEditorComponent {
18430
18514
  });
18431
18515
  return values;
18432
18516
  }
18433
- serializeGlobalActionParam(actionId, values, existingJson) {
18434
- const payload = this.buildGlobalActionPayload(values, existingJson);
18435
- const preferJson = !!(existingJson && Object.keys(existingJson).length);
18436
- const primary = this.buildPrimaryParam(actionId, values, payload, preferJson);
18437
- if (primary)
18438
- return primary;
18439
- if (!payload || Object.keys(payload).length === 0)
18440
- return '';
18441
- return JSON.stringify(payload);
18442
- }
18443
- buildPrimaryParam(actionId, values, payload, preferJson) {
18444
- if (preferJson)
18445
- return '';
18446
- const onlyPayload = payload ? Object.keys(payload).length : 0;
18447
- const text = (value) => String(value || '').trim();
18448
- if (actionId === 'navigate' || actionId === 'openUrl') {
18449
- const url = text(values.url);
18450
- if (url && onlyPayload === 1)
18451
- return url;
18452
- }
18453
- if (actionId === 'showAlert' || actionId === 'confirm' || actionId === 'alert' || actionId === 'prompt') {
18454
- const message = text(values.message);
18455
- if (message && onlyPayload === 1)
18456
- return message;
18457
- }
18458
- if (actionId === 'download') {
18459
- const url = text(values.url);
18460
- if (url && onlyPayload === 1)
18461
- return url;
18462
- }
18463
- if (actionId === 'copyToClipboard') {
18464
- const copyText = text(values.text);
18465
- if (copyText && onlyPayload === 1)
18466
- return copyText;
18467
- }
18468
- if (actionId === 'saveFormDraft' || actionId === 'loadFormDraft' || actionId === 'clearFormDraft') {
18469
- const key = text(values.key);
18470
- if (key && onlyPayload === 1)
18471
- return key;
18472
- }
18473
- if (actionId === 'refreshOptions' || actionId === 'loadDependentData') {
18474
- const field = text(values.field);
18475
- if (field && onlyPayload === 1)
18476
- return field;
18477
- }
18478
- if (actionId === 'apiCall') {
18479
- const method = text(values.method);
18480
- const url = text(values.url);
18481
- if (url && onlyPayload <= 2) {
18482
- return method ? `${method}:${url}` : url;
18483
- }
18484
- }
18485
- return '';
18486
- }
18487
18517
  buildGlobalActionPayload(values, existingJson) {
18488
18518
  const payload = {};
18489
18519
  const hasText = (value) => {
@@ -18961,7 +18991,7 @@ class ToolbarActionsEditorComponent {
18961
18991
  console.warn('[ToolbarActionsEditor] shortcutScope invalido; aplicando fallback para "toolbar".', { actionId, shortcutScope: scope });
18962
18992
  }
18963
18993
  getActionDraftKey(action) {
18964
- const base = action.id || action.action || 'action';
18994
+ const base = action.id || action.globalAction?.actionId || action.action || 'action';
18965
18995
  const pos = action.position || '';
18966
18996
  const appearance = action.appearance || '';
18967
18997
  return `${base}|${pos}|${appearance}`;
@@ -19051,10 +19081,10 @@ class ToolbarActionsEditorComponent {
19051
19081
  <div class="action-global-grid">
19052
19082
  @for (field of schema.fields; track field.key) {
19053
19083
  @if (shouldShowGlobalActionField(action, field)) {
19054
- @if (schema.id === 'showAlert' && field.key === 'message') {
19084
+ @if (schema.id === 'dialog.alert' && field.key === 'message') {
19055
19085
  <div class="action-section-title">{{ tx('toolbar.global.section.essential', 'Essential') }}</div>
19056
19086
  }
19057
- @if (schema.id === 'showAlert' && field.key === 'title') {
19087
+ @if (schema.id === 'dialog.alert' && field.key === 'title') {
19058
19088
  <div class="action-section-title">{{ tx('toolbar.global.section.dialogOptional', 'Dialog (optional)') }}</div>
19059
19089
  }
19060
19090
  @if (field.type === 'toggle') {
@@ -19085,6 +19115,7 @@ class ToolbarActionsEditorComponent {
19085
19115
  (ngModelChange)="onGlobalActionFieldChange(action, field, $event, kind)"
19086
19116
  [ngModelOptions]="{ standalone: true }"
19087
19117
  [placeholder]="field.placeholder || ''"
19118
+ [required]="isGlobalActionFieldRequired(action, field)"
19088
19119
  ></textarea>
19089
19120
  @if (field.hint) {
19090
19121
  <button
@@ -19097,6 +19128,9 @@ class ToolbarActionsEditorComponent {
19097
19128
  <mat-icon>help_outline</mat-icon>
19098
19129
  </button>
19099
19130
  }
19131
+ @if (isGlobalActionFieldMissing(action, field)) {
19132
+ <mat-error aria-live="polite">{{ tx('toolbar.global.validation.requiredField', 'Campo obrigatório.') }}</mat-error>
19133
+ }
19100
19134
  </mat-form-field>
19101
19135
  } @else if (field.type === 'json') {
19102
19136
  <mat-form-field appearance="outline">
@@ -19108,6 +19142,7 @@ class ToolbarActionsEditorComponent {
19108
19142
  (ngModelChange)="onGlobalActionFieldChange(action, field, $event, kind)"
19109
19143
  [ngModelOptions]="{ standalone: true }"
19110
19144
  [placeholder]="field.placeholder || '{ }'"
19145
+ [required]="isGlobalActionFieldRequired(action, field)"
19111
19146
  ></textarea>
19112
19147
  <button
19113
19148
  mat-icon-button
@@ -19120,6 +19155,8 @@ class ToolbarActionsEditorComponent {
19120
19155
  </button>
19121
19156
  @if (hasActionFieldError(action, field.key)) {
19122
19157
  <mat-error>{{ getActionFieldError(action, field.key) }}</mat-error>
19158
+ } @else if (isGlobalActionFieldMissing(action, field)) {
19159
+ <mat-error aria-live="polite">{{ tx('toolbar.global.validation.requiredField', 'Campo obrigatório.') }}</mat-error>
19123
19160
  }
19124
19161
  </mat-form-field>
19125
19162
  } @else if (field.type === 'select') {
@@ -19129,6 +19166,7 @@ class ToolbarActionsEditorComponent {
19129
19166
  [ngModel]="getGlobalActionFieldValue(action, field)"
19130
19167
  (ngModelChange)="onGlobalActionFieldChange(action, field, $event, kind)"
19131
19168
  [ngModelOptions]="{ standalone: true }"
19169
+ [required]="isGlobalActionFieldRequired(action, field)"
19132
19170
  >
19133
19171
  @for (opt of field.options || []; track opt.value) {
19134
19172
  <mat-option [value]="opt.value">{{ opt.label }}</mat-option>
@@ -19145,6 +19183,9 @@ class ToolbarActionsEditorComponent {
19145
19183
  <mat-icon>help_outline</mat-icon>
19146
19184
  </button>
19147
19185
  }
19186
+ @if (isGlobalActionFieldMissing(action, field)) {
19187
+ <mat-error aria-live="polite">{{ tx('toolbar.global.validation.requiredField', 'Campo obrigatório.') }}</mat-error>
19188
+ }
19148
19189
  </mat-form-field>
19149
19190
  } @else {
19150
19191
  <mat-form-field appearance="outline">
@@ -19156,6 +19197,7 @@ class ToolbarActionsEditorComponent {
19156
19197
  (ngModelChange)="onGlobalActionFieldChange(action, field, $event, kind)"
19157
19198
  [ngModelOptions]="{ standalone: true }"
19158
19199
  [placeholder]="field.placeholder || ''"
19200
+ [required]="isGlobalActionFieldRequired(action, field)"
19159
19201
  />
19160
19202
  @if (field.hint) {
19161
19203
  <button
@@ -19168,6 +19210,9 @@ class ToolbarActionsEditorComponent {
19168
19210
  <mat-icon>help_outline</mat-icon>
19169
19211
  </button>
19170
19212
  }
19213
+ @if (isGlobalActionFieldMissing(action, field)) {
19214
+ <mat-error aria-live="polite">{{ tx('toolbar.global.validation.requiredField', 'Campo obrigatório.') }}</mat-error>
19215
+ }
19171
19216
  </mat-form-field>
19172
19217
  }
19173
19218
  }
@@ -19175,6 +19220,34 @@ class ToolbarActionsEditorComponent {
19175
19220
  </div>
19176
19221
  }
19177
19222
  </div>
19223
+ } @else {
19224
+ @if (getActionSpecById(action.globalAction?.actionId || ''); as actionSpec) {
19225
+ @if (actionSpec.param) {
19226
+ <mat-form-field appearance="outline">
19227
+ <mat-label>{{ actionSpec.param.label || tx('toolbar.action.param.defaultLabel', 'Parameter') }}</mat-label>
19228
+ <input
19229
+ matInput
19230
+ [ngModel]="getActionParam(action)"
19231
+ (ngModelChange)="onActionParamChange(action, $event, kind)"
19232
+ [ngModelOptions]="{ standalone: true }"
19233
+ [placeholder]="actionSpec.param.placeholder || ''"
19234
+ [required]="!!actionSpec.param.required"
19235
+ />
19236
+ <button
19237
+ mat-icon-button
19238
+ matSuffix
19239
+ type="button"
19240
+ class="help-icon-button"
19241
+ [matTooltip]="getGlobalActionSpecHint(actionSpec)"
19242
+ >
19243
+ <mat-icon>help_outline</mat-icon>
19244
+ </button>
19245
+ @if (isRequiredGlobalActionParamMissing(action, actionSpec)) {
19246
+ <mat-error>{{ tx('toolbar.action.param.required', 'Required parameter.') }}</mat-error>
19247
+ }
19248
+ </mat-form-field>
19249
+ }
19250
+ }
19178
19251
  }
19179
19252
  </ng-template>
19180
19253
  <form [formGroup]="toolbarForm">
@@ -19558,7 +19631,7 @@ class ToolbarActionsEditorComponent {
19558
19631
  <mat-form-field appearance="outline">
19559
19632
  <mat-label>{{ tx('toolbar.action.action', 'Action') }}</mat-label>
19560
19633
  <mat-select
19561
- [ngModel]="getActionSelectValue(action.action)"
19634
+ [ngModel]="getActionSelectValue(action)"
19562
19635
  (ngModelChange)="onActionSelectChange(action, $event, 'toolbar')"
19563
19636
  [ngModelOptions]="{ standalone: true }"
19564
19637
  >
@@ -19585,12 +19658,12 @@ class ToolbarActionsEditorComponent {
19585
19658
  </button>
19586
19659
  </mat-form-field>
19587
19660
 
19588
- @if (getActionSelectValue(action.action) === customActionValue) {
19661
+ @if (getActionSelectValue(action) === customActionValue) {
19589
19662
  <mat-form-field appearance="outline">
19590
19663
  <mat-label>{{ tx('toolbar.action.customField', 'Custom action') }}</mat-label>
19591
19664
  <input
19592
19665
  matInput
19593
- [ngModel]="getActionCustomValue(action.action)"
19666
+ [ngModel]="getActionCustomValue(action)"
19594
19667
  (ngModelChange)="onActionCustomChange(action, $event, 'toolbar')"
19595
19668
  [ngModelOptions]="{ standalone: true }"
19596
19669
  [placeholder]="tx('toolbar.action.customField.placeholder', 'Ex: create, edit, delete, custom_action')"
@@ -19606,19 +19679,19 @@ class ToolbarActionsEditorComponent {
19606
19679
  </button>
19607
19680
  </mat-form-field>
19608
19681
  } @else {
19609
- @if (isGlobalActionId(getActionSelectValue(action.action))) {
19682
+ @if (isGlobalActionId(getActionSelectValue(action))) {
19610
19683
  <ng-container
19611
19684
  [ngTemplateOutlet]="globalActionFields"
19612
19685
  [ngTemplateOutletContext]="{ $implicit: action, kind: 'toolbar' }"
19613
19686
  ></ng-container>
19614
19687
  } @else {
19615
- @if (getActionSpecById(getActionSelectValue(action.action)); as actionSpec) {
19688
+ @if (getActionSpecById(getActionSelectValue(action)); as actionSpec) {
19616
19689
  @if (actionSpec.param) {
19617
19690
  <mat-form-field appearance="outline">
19618
19691
  <mat-label>{{ actionSpec.param.label || tx('toolbar.action.param.defaultLabel', 'Parameter') }}</mat-label>
19619
19692
  <input
19620
19693
  matInput
19621
- [ngModel]="getActionParam(action.action)"
19694
+ [ngModel]="getActionParam(action)"
19622
19695
  (ngModelChange)="onActionParamChange(action, $event, 'toolbar')"
19623
19696
  [ngModelOptions]="{ standalone: true }"
19624
19697
  [placeholder]="actionSpec.param.placeholder || ''"
@@ -19632,7 +19705,7 @@ class ToolbarActionsEditorComponent {
19632
19705
  >
19633
19706
  <mat-icon>help_outline</mat-icon>
19634
19707
  </button>
19635
- @if (actionSpec.param.required && !getActionParam(action.action)) {
19708
+ @if (isRequiredGlobalActionParamMissing(action, actionSpec)) {
19636
19709
  <mat-error>{{ tx('toolbar.action.param.required', 'Required parameter.') }}</mat-error>
19637
19710
  }
19638
19711
  </mat-form-field>
@@ -20032,7 +20105,7 @@ class ToolbarActionsEditorComponent {
20032
20105
  <mat-form-field appearance="outline">
20033
20106
  <mat-label>{{ tx('toolbar.action.action', 'Action') }}</mat-label>
20034
20107
  <mat-select
20035
- [ngModel]="getActionSelectValue(action.action)"
20108
+ [ngModel]="getActionSelectValue(action)"
20036
20109
  (ngModelChange)="onActionSelectChange(action, $event, 'row')"
20037
20110
  [ngModelOptions]="{ standalone: true }"
20038
20111
  >
@@ -20059,31 +20132,31 @@ class ToolbarActionsEditorComponent {
20059
20132
  </button>
20060
20133
  </mat-form-field>
20061
20134
 
20062
- @if (getActionSelectValue(action.action) === customActionValue) {
20135
+ @if (getActionSelectValue(action) === customActionValue) {
20063
20136
  <mat-form-field appearance="outline">
20064
20137
  <mat-label>{{ tx('toolbar.action.customField', 'Custom action') }}</mat-label>
20065
20138
  <input
20066
20139
  matInput
20067
- [ngModel]="getActionCustomValue(action.action)"
20140
+ [ngModel]="getActionCustomValue(action)"
20068
20141
  (ngModelChange)="onActionCustomChange(action, $event, 'row')"
20069
20142
  [ngModelOptions]="{ standalone: true }"
20070
20143
  [placeholder]="tx('toolbar.row.customPlaceholder', 'Ex: view, edit, delete, custom_action')"
20071
20144
  />
20072
20145
  </mat-form-field>
20073
20146
  } @else {
20074
- @if (isGlobalActionId(getActionSelectValue(action.action))) {
20147
+ @if (isGlobalActionId(getActionSelectValue(action))) {
20075
20148
  <ng-container
20076
20149
  [ngTemplateOutlet]="globalActionFields"
20077
20150
  [ngTemplateOutletContext]="{ $implicit: action, kind: 'row' }"
20078
20151
  ></ng-container>
20079
20152
  } @else {
20080
- @if (getActionSpecById(getActionSelectValue(action.action)); as actionSpec) {
20153
+ @if (getActionSpecById(getActionSelectValue(action)); as actionSpec) {
20081
20154
  @if (actionSpec.param) {
20082
20155
  <mat-form-field appearance="outline">
20083
20156
  <mat-label>{{ actionSpec.param.label || tx('toolbar.action.param.defaultLabel', 'Parameter') }}</mat-label>
20084
20157
  <input
20085
20158
  matInput
20086
- [ngModel]="getActionParam(action.action)"
20159
+ [ngModel]="getActionParam(action)"
20087
20160
  (ngModelChange)="onActionParamChange(action, $event, 'row')"
20088
20161
  [ngModelOptions]="{ standalone: true }"
20089
20162
  [placeholder]="actionSpec.param.placeholder || ''"
@@ -20097,7 +20170,7 @@ class ToolbarActionsEditorComponent {
20097
20170
  >
20098
20171
  <mat-icon>help_outline</mat-icon>
20099
20172
  </button>
20100
- @if (actionSpec.param.required && !getActionParam(action.action)) {
20173
+ @if (isRequiredGlobalActionParamMissing(action, actionSpec)) {
20101
20174
  <mat-error>{{ tx('toolbar.action.param.required', 'Required parameter.') }}</mat-error>
20102
20175
  }
20103
20176
  </mat-form-field>
@@ -20302,7 +20375,7 @@ class ToolbarActionsEditorComponent {
20302
20375
  <mat-form-field appearance="outline">
20303
20376
  <mat-label>{{ tx('toolbar.action.action', 'Action') }}</mat-label>
20304
20377
  <mat-select
20305
- [ngModel]="getActionSelectValue(action.action)"
20378
+ [ngModel]="getActionSelectValue(action)"
20306
20379
  (ngModelChange)="onActionSelectChange(action, $event, 'bulk')"
20307
20380
  [ngModelOptions]="{ standalone: true }"
20308
20381
  >
@@ -20329,31 +20402,31 @@ class ToolbarActionsEditorComponent {
20329
20402
  </button>
20330
20403
  </mat-form-field>
20331
20404
 
20332
- @if (getActionSelectValue(action.action) === customActionValue) {
20405
+ @if (getActionSelectValue(action) === customActionValue) {
20333
20406
  <mat-form-field appearance="outline">
20334
20407
  <mat-label>{{ tx('toolbar.action.customField', 'Custom action') }}</mat-label>
20335
20408
  <input
20336
20409
  matInput
20337
- [ngModel]="getActionCustomValue(action.action)"
20410
+ [ngModel]="getActionCustomValue(action)"
20338
20411
  (ngModelChange)="onActionCustomChange(action, $event, 'bulk')"
20339
20412
  [ngModelOptions]="{ standalone: true }"
20340
20413
  [placeholder]="tx('toolbar.bulk.customPlaceholder', 'Ex: delete, export, custom_action')"
20341
20414
  />
20342
20415
  </mat-form-field>
20343
20416
  } @else {
20344
- @if (isGlobalActionId(getActionSelectValue(action.action))) {
20417
+ @if (isGlobalActionId(getActionSelectValue(action))) {
20345
20418
  <ng-container
20346
20419
  [ngTemplateOutlet]="globalActionFields"
20347
20420
  [ngTemplateOutletContext]="{ $implicit: action, kind: 'bulk' }"
20348
20421
  ></ng-container>
20349
20422
  } @else {
20350
- @if (getActionSpecById(getActionSelectValue(action.action)); as actionSpec) {
20423
+ @if (getActionSpecById(getActionSelectValue(action)); as actionSpec) {
20351
20424
  @if (actionSpec.param) {
20352
20425
  <mat-form-field appearance="outline">
20353
20426
  <mat-label>{{ actionSpec.param.label || tx('toolbar.action.param.defaultLabel', 'Parameter') }}</mat-label>
20354
20427
  <input
20355
20428
  matInput
20356
- [ngModel]="getActionParam(action.action)"
20429
+ [ngModel]="getActionParam(action)"
20357
20430
  (ngModelChange)="onActionParamChange(action, $event, 'bulk')"
20358
20431
  [ngModelOptions]="{ standalone: true }"
20359
20432
  [placeholder]="actionSpec.param.placeholder || ''"
@@ -20367,7 +20440,7 @@ class ToolbarActionsEditorComponent {
20367
20440
  >
20368
20441
  <mat-icon>help_outline</mat-icon>
20369
20442
  </button>
20370
- @if (actionSpec.param.required && !getActionParam(action.action)) {
20443
+ @if (isRequiredGlobalActionParamMissing(action, actionSpec)) {
20371
20444
  <mat-error>{{ tx('toolbar.action.param.required', 'Required parameter.') }}</mat-error>
20372
20445
  }
20373
20446
  </mat-form-field>
@@ -20536,7 +20609,7 @@ class ToolbarActionsEditorComponent {
20536
20609
  </mat-expansion-panel> }
20537
20610
  </form>
20538
20611
  </div>
20539
- `, isInline: true, styles: [":host{--tae-primary: var(--md-sys-color-primary);--tae-surface: var(--md-sys-color-surface);--tae-surface-low: var(--md-sys-color-surface-container-low);--tae-surface-variant: var(--md-sys-color-surface-variant);--tae-outline-variant: var(--md-sys-color-outline-variant);--tae-on-surface: var(--md-sys-color-on-surface);--tae-on-surface-variant: var(--md-sys-color-on-surface-variant)}.toolbar-actions-container{width:100%;padding:8px}.config-section{padding:16px}.section-hint{color:var(--tae-on-surface-variant);font-size:12px;margin-bottom:12px}.config-fields{display:flex;flex-direction:column;gap:16px;margin-top:16px}.nested-fields{display:flex;flex-direction:column;gap:12px;margin-left:24px;margin-top:12px;padding:16px;background-color:var(--tae-surface-variant);border-radius:8px}.action-section-title{width:100%;margin:12px 0 4px;font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--tae-on-surface-variant)}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}.action-global-fields .mat-mdc-form-field-icon-suffix{align-self:center}.toggle-field{display:flex;align-items:center;gap:8px}.section-icon{margin-right:8px;color:var(--tae-primary)}.subsection{margin-top:24px;padding:16px;border:1px solid var(--tae-outline-variant);border-radius:8px}.subsection h4{margin:0 0 16px;color:var(--tae-on-surface);font-weight:500}.actions-header{margin:16px 0;display:flex;justify-content:flex-start}.actions-list{display:flex;flex-direction:column;gap:8px;min-height:60px}.action-item{border:1px solid var(--tae-outline-variant);border-radius:8px;background-color:var(--tae-surface);overflow:hidden}.action-header{display:flex;align-items:center;padding:12px 16px;background-color:var(--tae-surface-low);cursor:move}.drag-handle{margin-right:8px;color:var(--tae-on-surface-variant);cursor:grab}.drag-handle:active{cursor:grabbing}.action-label{flex:1;margin-left:8px;font-weight:500}.action-controls{display:flex;gap:4px}.action-details{padding:16px;border-top:1px solid var(--tae-outline-variant);background-color:var(--tae-surface)}.action-form{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:16px}.action-global-fields{grid-column:1 / -1;padding:12px 14px;border:1px dashed var(--tae-outline-variant);border-radius:10px;background:var(--md-sys-color-surface-container)}.action-global-title{font-size:12px;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--tae-on-surface-variant);margin-bottom:10px}.action-global-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px 16px}mat-form-field{width:100%}mat-expansion-panel{margin-bottom:8px;border-radius:8px;overflow:hidden}mat-expansion-panel-header{min-height:56px}mat-panel-description{color:var(--tae-on-surface-variant)}.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:var(--md-sys-elevation-level3)}.cdk-drag-placeholder{opacity:0}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.actions-list.cdk-drop-list-dragging .action-item:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { 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],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: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i6.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i6.MatLabel, selector: "mat-label" }, { kind: "directive", type: i6.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i6.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "directive", type: i6.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i7.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: MatSelectModule }, { kind: "component", type: i5$1.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: i5$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i5$1.MatOptgroup, selector: "mat-optgroup", inputs: ["label", "disabled"], exportAs: ["matOptgroup"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i6$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: MatExpansionModule }, { kind: "component", type: i7$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i7$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i7$1.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i7$1.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { 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: MatDividerModule }, { 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: "ngmodule", type: MatListModule }, { kind: "ngmodule", type: MatChipsModule }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i17.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i17.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i17.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i12.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: i12.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: i12.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: SurfaceOpenActionEditorComponent, selector: "praxis-surface-open-action-editor", inputs: ["value", "hostKind"], outputs: ["valueChange"] }] });
20612
+ `, isInline: true, styles: [":host{--tae-primary: var(--md-sys-color-primary);--tae-surface: var(--md-sys-color-surface);--tae-surface-low: var(--md-sys-color-surface-container-low);--tae-surface-variant: var(--md-sys-color-surface-variant);--tae-outline-variant: var(--md-sys-color-outline-variant);--tae-on-surface: var(--md-sys-color-on-surface);--tae-on-surface-variant: var(--md-sys-color-on-surface-variant)}.toolbar-actions-container{width:100%;padding:8px}.config-section{padding:16px}.section-hint{color:var(--tae-on-surface-variant);font-size:12px;margin-bottom:12px}.config-fields{display:flex;flex-direction:column;gap:16px;margin-top:16px}.nested-fields{display:flex;flex-direction:column;gap:12px;margin-left:24px;margin-top:12px;padding:16px;background-color:var(--tae-surface-variant);border-radius:8px}.action-section-title{width:100%;margin:12px 0 4px;font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--tae-on-surface-variant)}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}.action-field-error{color:var(--md-sys-color-error, #ba1a1a);font-weight:500}.action-global-fields .mat-mdc-form-field-icon-suffix{align-self:center}.toggle-field{display:flex;align-items:center;gap:8px}.section-icon{margin-right:8px;color:var(--tae-primary)}.subsection{margin-top:24px;padding:16px;border:1px solid var(--tae-outline-variant);border-radius:8px}.subsection h4{margin:0 0 16px;color:var(--tae-on-surface);font-weight:500}.actions-header{margin:16px 0;display:flex;justify-content:flex-start}.actions-list{display:flex;flex-direction:column;gap:8px;min-height:60px}.action-item{border:1px solid var(--tae-outline-variant);border-radius:8px;background-color:var(--tae-surface);overflow:hidden}.action-header{display:flex;align-items:center;padding:12px 16px;background-color:var(--tae-surface-low);cursor:move}.drag-handle{margin-right:8px;color:var(--tae-on-surface-variant);cursor:grab}.drag-handle:active{cursor:grabbing}.action-label{flex:1;margin-left:8px;font-weight:500}.action-controls{display:flex;gap:4px}.action-details{padding:16px;border-top:1px solid var(--tae-outline-variant);background-color:var(--tae-surface)}.action-form{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:16px}.action-global-fields{grid-column:1 / -1;padding:12px 14px;border:1px dashed var(--tae-outline-variant);border-radius:10px;background:var(--md-sys-color-surface-container)}.action-global-title{font-size:12px;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--tae-on-surface-variant);margin-bottom:10px}.action-global-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px 16px}mat-form-field{width:100%}mat-expansion-panel{margin-bottom:8px;border-radius:8px;overflow:hidden}mat-expansion-panel-header{min-height:56px}mat-panel-description{color:var(--tae-on-surface-variant)}.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:var(--md-sys-elevation-level3)}.cdk-drag-placeholder{opacity:0}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.actions-list.cdk-drop-list-dragging .action-item:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { 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],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { 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: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i6.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i6.MatLabel, selector: "mat-label" }, { kind: "directive", type: i6.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i6.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "directive", type: i6.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i7.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: MatSelectModule }, { kind: "component", type: i5$1.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: i5$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i5$1.MatOptgroup, selector: "mat-optgroup", inputs: ["label", "disabled"], exportAs: ["matOptgroup"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i6$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: MatExpansionModule }, { kind: "component", type: i7$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i7$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i7$1.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i7$1.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { 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: MatDividerModule }, { 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: "ngmodule", type: MatListModule }, { kind: "ngmodule", type: MatChipsModule }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i17.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i17.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i17.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i12.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: i12.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: i12.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: SurfaceOpenActionEditorComponent, selector: "praxis-surface-open-action-editor", inputs: ["value", "hostKind"], outputs: ["valueChange"] }] });
20540
20613
  }
20541
20614
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ToolbarActionsEditorComponent, decorators: [{
20542
20615
  type: Component,
@@ -20578,10 +20651,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
20578
20651
  <div class="action-global-grid">
20579
20652
  @for (field of schema.fields; track field.key) {
20580
20653
  @if (shouldShowGlobalActionField(action, field)) {
20581
- @if (schema.id === 'showAlert' && field.key === 'message') {
20654
+ @if (schema.id === 'dialog.alert' && field.key === 'message') {
20582
20655
  <div class="action-section-title">{{ tx('toolbar.global.section.essential', 'Essential') }}</div>
20583
20656
  }
20584
- @if (schema.id === 'showAlert' && field.key === 'title') {
20657
+ @if (schema.id === 'dialog.alert' && field.key === 'title') {
20585
20658
  <div class="action-section-title">{{ tx('toolbar.global.section.dialogOptional', 'Dialog (optional)') }}</div>
20586
20659
  }
20587
20660
  @if (field.type === 'toggle') {
@@ -20612,6 +20685,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
20612
20685
  (ngModelChange)="onGlobalActionFieldChange(action, field, $event, kind)"
20613
20686
  [ngModelOptions]="{ standalone: true }"
20614
20687
  [placeholder]="field.placeholder || ''"
20688
+ [required]="isGlobalActionFieldRequired(action, field)"
20615
20689
  ></textarea>
20616
20690
  @if (field.hint) {
20617
20691
  <button
@@ -20624,6 +20698,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
20624
20698
  <mat-icon>help_outline</mat-icon>
20625
20699
  </button>
20626
20700
  }
20701
+ @if (isGlobalActionFieldMissing(action, field)) {
20702
+ <mat-error aria-live="polite">{{ tx('toolbar.global.validation.requiredField', 'Campo obrigatório.') }}</mat-error>
20703
+ }
20627
20704
  </mat-form-field>
20628
20705
  } @else if (field.type === 'json') {
20629
20706
  <mat-form-field appearance="outline">
@@ -20635,6 +20712,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
20635
20712
  (ngModelChange)="onGlobalActionFieldChange(action, field, $event, kind)"
20636
20713
  [ngModelOptions]="{ standalone: true }"
20637
20714
  [placeholder]="field.placeholder || '{ }'"
20715
+ [required]="isGlobalActionFieldRequired(action, field)"
20638
20716
  ></textarea>
20639
20717
  <button
20640
20718
  mat-icon-button
@@ -20647,6 +20725,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
20647
20725
  </button>
20648
20726
  @if (hasActionFieldError(action, field.key)) {
20649
20727
  <mat-error>{{ getActionFieldError(action, field.key) }}</mat-error>
20728
+ } @else if (isGlobalActionFieldMissing(action, field)) {
20729
+ <mat-error aria-live="polite">{{ tx('toolbar.global.validation.requiredField', 'Campo obrigatório.') }}</mat-error>
20650
20730
  }
20651
20731
  </mat-form-field>
20652
20732
  } @else if (field.type === 'select') {
@@ -20656,6 +20736,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
20656
20736
  [ngModel]="getGlobalActionFieldValue(action, field)"
20657
20737
  (ngModelChange)="onGlobalActionFieldChange(action, field, $event, kind)"
20658
20738
  [ngModelOptions]="{ standalone: true }"
20739
+ [required]="isGlobalActionFieldRequired(action, field)"
20659
20740
  >
20660
20741
  @for (opt of field.options || []; track opt.value) {
20661
20742
  <mat-option [value]="opt.value">{{ opt.label }}</mat-option>
@@ -20672,6 +20753,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
20672
20753
  <mat-icon>help_outline</mat-icon>
20673
20754
  </button>
20674
20755
  }
20756
+ @if (isGlobalActionFieldMissing(action, field)) {
20757
+ <mat-error aria-live="polite">{{ tx('toolbar.global.validation.requiredField', 'Campo obrigatório.') }}</mat-error>
20758
+ }
20675
20759
  </mat-form-field>
20676
20760
  } @else {
20677
20761
  <mat-form-field appearance="outline">
@@ -20683,6 +20767,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
20683
20767
  (ngModelChange)="onGlobalActionFieldChange(action, field, $event, kind)"
20684
20768
  [ngModelOptions]="{ standalone: true }"
20685
20769
  [placeholder]="field.placeholder || ''"
20770
+ [required]="isGlobalActionFieldRequired(action, field)"
20686
20771
  />
20687
20772
  @if (field.hint) {
20688
20773
  <button
@@ -20695,6 +20780,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
20695
20780
  <mat-icon>help_outline</mat-icon>
20696
20781
  </button>
20697
20782
  }
20783
+ @if (isGlobalActionFieldMissing(action, field)) {
20784
+ <mat-error aria-live="polite">{{ tx('toolbar.global.validation.requiredField', 'Campo obrigatório.') }}</mat-error>
20785
+ }
20698
20786
  </mat-form-field>
20699
20787
  }
20700
20788
  }
@@ -20702,6 +20790,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
20702
20790
  </div>
20703
20791
  }
20704
20792
  </div>
20793
+ } @else {
20794
+ @if (getActionSpecById(action.globalAction?.actionId || ''); as actionSpec) {
20795
+ @if (actionSpec.param) {
20796
+ <mat-form-field appearance="outline">
20797
+ <mat-label>{{ actionSpec.param.label || tx('toolbar.action.param.defaultLabel', 'Parameter') }}</mat-label>
20798
+ <input
20799
+ matInput
20800
+ [ngModel]="getActionParam(action)"
20801
+ (ngModelChange)="onActionParamChange(action, $event, kind)"
20802
+ [ngModelOptions]="{ standalone: true }"
20803
+ [placeholder]="actionSpec.param.placeholder || ''"
20804
+ [required]="!!actionSpec.param.required"
20805
+ />
20806
+ <button
20807
+ mat-icon-button
20808
+ matSuffix
20809
+ type="button"
20810
+ class="help-icon-button"
20811
+ [matTooltip]="getGlobalActionSpecHint(actionSpec)"
20812
+ >
20813
+ <mat-icon>help_outline</mat-icon>
20814
+ </button>
20815
+ @if (isRequiredGlobalActionParamMissing(action, actionSpec)) {
20816
+ <mat-error>{{ tx('toolbar.action.param.required', 'Required parameter.') }}</mat-error>
20817
+ }
20818
+ </mat-form-field>
20819
+ }
20820
+ }
20705
20821
  }
20706
20822
  </ng-template>
20707
20823
  <form [formGroup]="toolbarForm">
@@ -21085,7 +21201,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
21085
21201
  <mat-form-field appearance="outline">
21086
21202
  <mat-label>{{ tx('toolbar.action.action', 'Action') }}</mat-label>
21087
21203
  <mat-select
21088
- [ngModel]="getActionSelectValue(action.action)"
21204
+ [ngModel]="getActionSelectValue(action)"
21089
21205
  (ngModelChange)="onActionSelectChange(action, $event, 'toolbar')"
21090
21206
  [ngModelOptions]="{ standalone: true }"
21091
21207
  >
@@ -21112,12 +21228,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
21112
21228
  </button>
21113
21229
  </mat-form-field>
21114
21230
 
21115
- @if (getActionSelectValue(action.action) === customActionValue) {
21231
+ @if (getActionSelectValue(action) === customActionValue) {
21116
21232
  <mat-form-field appearance="outline">
21117
21233
  <mat-label>{{ tx('toolbar.action.customField', 'Custom action') }}</mat-label>
21118
21234
  <input
21119
21235
  matInput
21120
- [ngModel]="getActionCustomValue(action.action)"
21236
+ [ngModel]="getActionCustomValue(action)"
21121
21237
  (ngModelChange)="onActionCustomChange(action, $event, 'toolbar')"
21122
21238
  [ngModelOptions]="{ standalone: true }"
21123
21239
  [placeholder]="tx('toolbar.action.customField.placeholder', 'Ex: create, edit, delete, custom_action')"
@@ -21133,19 +21249,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
21133
21249
  </button>
21134
21250
  </mat-form-field>
21135
21251
  } @else {
21136
- @if (isGlobalActionId(getActionSelectValue(action.action))) {
21252
+ @if (isGlobalActionId(getActionSelectValue(action))) {
21137
21253
  <ng-container
21138
21254
  [ngTemplateOutlet]="globalActionFields"
21139
21255
  [ngTemplateOutletContext]="{ $implicit: action, kind: 'toolbar' }"
21140
21256
  ></ng-container>
21141
21257
  } @else {
21142
- @if (getActionSpecById(getActionSelectValue(action.action)); as actionSpec) {
21258
+ @if (getActionSpecById(getActionSelectValue(action)); as actionSpec) {
21143
21259
  @if (actionSpec.param) {
21144
21260
  <mat-form-field appearance="outline">
21145
21261
  <mat-label>{{ actionSpec.param.label || tx('toolbar.action.param.defaultLabel', 'Parameter') }}</mat-label>
21146
21262
  <input
21147
21263
  matInput
21148
- [ngModel]="getActionParam(action.action)"
21264
+ [ngModel]="getActionParam(action)"
21149
21265
  (ngModelChange)="onActionParamChange(action, $event, 'toolbar')"
21150
21266
  [ngModelOptions]="{ standalone: true }"
21151
21267
  [placeholder]="actionSpec.param.placeholder || ''"
@@ -21159,7 +21275,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
21159
21275
  >
21160
21276
  <mat-icon>help_outline</mat-icon>
21161
21277
  </button>
21162
- @if (actionSpec.param.required && !getActionParam(action.action)) {
21278
+ @if (isRequiredGlobalActionParamMissing(action, actionSpec)) {
21163
21279
  <mat-error>{{ tx('toolbar.action.param.required', 'Required parameter.') }}</mat-error>
21164
21280
  }
21165
21281
  </mat-form-field>
@@ -21559,7 +21675,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
21559
21675
  <mat-form-field appearance="outline">
21560
21676
  <mat-label>{{ tx('toolbar.action.action', 'Action') }}</mat-label>
21561
21677
  <mat-select
21562
- [ngModel]="getActionSelectValue(action.action)"
21678
+ [ngModel]="getActionSelectValue(action)"
21563
21679
  (ngModelChange)="onActionSelectChange(action, $event, 'row')"
21564
21680
  [ngModelOptions]="{ standalone: true }"
21565
21681
  >
@@ -21586,31 +21702,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
21586
21702
  </button>
21587
21703
  </mat-form-field>
21588
21704
 
21589
- @if (getActionSelectValue(action.action) === customActionValue) {
21705
+ @if (getActionSelectValue(action) === customActionValue) {
21590
21706
  <mat-form-field appearance="outline">
21591
21707
  <mat-label>{{ tx('toolbar.action.customField', 'Custom action') }}</mat-label>
21592
21708
  <input
21593
21709
  matInput
21594
- [ngModel]="getActionCustomValue(action.action)"
21710
+ [ngModel]="getActionCustomValue(action)"
21595
21711
  (ngModelChange)="onActionCustomChange(action, $event, 'row')"
21596
21712
  [ngModelOptions]="{ standalone: true }"
21597
21713
  [placeholder]="tx('toolbar.row.customPlaceholder', 'Ex: view, edit, delete, custom_action')"
21598
21714
  />
21599
21715
  </mat-form-field>
21600
21716
  } @else {
21601
- @if (isGlobalActionId(getActionSelectValue(action.action))) {
21717
+ @if (isGlobalActionId(getActionSelectValue(action))) {
21602
21718
  <ng-container
21603
21719
  [ngTemplateOutlet]="globalActionFields"
21604
21720
  [ngTemplateOutletContext]="{ $implicit: action, kind: 'row' }"
21605
21721
  ></ng-container>
21606
21722
  } @else {
21607
- @if (getActionSpecById(getActionSelectValue(action.action)); as actionSpec) {
21723
+ @if (getActionSpecById(getActionSelectValue(action)); as actionSpec) {
21608
21724
  @if (actionSpec.param) {
21609
21725
  <mat-form-field appearance="outline">
21610
21726
  <mat-label>{{ actionSpec.param.label || tx('toolbar.action.param.defaultLabel', 'Parameter') }}</mat-label>
21611
21727
  <input
21612
21728
  matInput
21613
- [ngModel]="getActionParam(action.action)"
21729
+ [ngModel]="getActionParam(action)"
21614
21730
  (ngModelChange)="onActionParamChange(action, $event, 'row')"
21615
21731
  [ngModelOptions]="{ standalone: true }"
21616
21732
  [placeholder]="actionSpec.param.placeholder || ''"
@@ -21624,7 +21740,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
21624
21740
  >
21625
21741
  <mat-icon>help_outline</mat-icon>
21626
21742
  </button>
21627
- @if (actionSpec.param.required && !getActionParam(action.action)) {
21743
+ @if (isRequiredGlobalActionParamMissing(action, actionSpec)) {
21628
21744
  <mat-error>{{ tx('toolbar.action.param.required', 'Required parameter.') }}</mat-error>
21629
21745
  }
21630
21746
  </mat-form-field>
@@ -21829,7 +21945,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
21829
21945
  <mat-form-field appearance="outline">
21830
21946
  <mat-label>{{ tx('toolbar.action.action', 'Action') }}</mat-label>
21831
21947
  <mat-select
21832
- [ngModel]="getActionSelectValue(action.action)"
21948
+ [ngModel]="getActionSelectValue(action)"
21833
21949
  (ngModelChange)="onActionSelectChange(action, $event, 'bulk')"
21834
21950
  [ngModelOptions]="{ standalone: true }"
21835
21951
  >
@@ -21856,31 +21972,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
21856
21972
  </button>
21857
21973
  </mat-form-field>
21858
21974
 
21859
- @if (getActionSelectValue(action.action) === customActionValue) {
21975
+ @if (getActionSelectValue(action) === customActionValue) {
21860
21976
  <mat-form-field appearance="outline">
21861
21977
  <mat-label>{{ tx('toolbar.action.customField', 'Custom action') }}</mat-label>
21862
21978
  <input
21863
21979
  matInput
21864
- [ngModel]="getActionCustomValue(action.action)"
21980
+ [ngModel]="getActionCustomValue(action)"
21865
21981
  (ngModelChange)="onActionCustomChange(action, $event, 'bulk')"
21866
21982
  [ngModelOptions]="{ standalone: true }"
21867
21983
  [placeholder]="tx('toolbar.bulk.customPlaceholder', 'Ex: delete, export, custom_action')"
21868
21984
  />
21869
21985
  </mat-form-field>
21870
21986
  } @else {
21871
- @if (isGlobalActionId(getActionSelectValue(action.action))) {
21987
+ @if (isGlobalActionId(getActionSelectValue(action))) {
21872
21988
  <ng-container
21873
21989
  [ngTemplateOutlet]="globalActionFields"
21874
21990
  [ngTemplateOutletContext]="{ $implicit: action, kind: 'bulk' }"
21875
21991
  ></ng-container>
21876
21992
  } @else {
21877
- @if (getActionSpecById(getActionSelectValue(action.action)); as actionSpec) {
21993
+ @if (getActionSpecById(getActionSelectValue(action)); as actionSpec) {
21878
21994
  @if (actionSpec.param) {
21879
21995
  <mat-form-field appearance="outline">
21880
21996
  <mat-label>{{ actionSpec.param.label || tx('toolbar.action.param.defaultLabel', 'Parameter') }}</mat-label>
21881
21997
  <input
21882
21998
  matInput
21883
- [ngModel]="getActionParam(action.action)"
21999
+ [ngModel]="getActionParam(action)"
21884
22000
  (ngModelChange)="onActionParamChange(action, $event, 'bulk')"
21885
22001
  [ngModelOptions]="{ standalone: true }"
21886
22002
  [placeholder]="actionSpec.param.placeholder || ''"
@@ -21894,7 +22010,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
21894
22010
  >
21895
22011
  <mat-icon>help_outline</mat-icon>
21896
22012
  </button>
21897
- @if (actionSpec.param.required && !getActionParam(action.action)) {
22013
+ @if (isRequiredGlobalActionParamMissing(action, actionSpec)) {
21898
22014
  <mat-error>{{ tx('toolbar.action.param.required', 'Required parameter.') }}</mat-error>
21899
22015
  }
21900
22016
  </mat-form-field>
@@ -22063,7 +22179,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
22063
22179
  </mat-expansion-panel> }
22064
22180
  </form>
22065
22181
  </div>
22066
- `, styles: [":host{--tae-primary: var(--md-sys-color-primary);--tae-surface: var(--md-sys-color-surface);--tae-surface-low: var(--md-sys-color-surface-container-low);--tae-surface-variant: var(--md-sys-color-surface-variant);--tae-outline-variant: var(--md-sys-color-outline-variant);--tae-on-surface: var(--md-sys-color-on-surface);--tae-on-surface-variant: var(--md-sys-color-on-surface-variant)}.toolbar-actions-container{width:100%;padding:8px}.config-section{padding:16px}.section-hint{color:var(--tae-on-surface-variant);font-size:12px;margin-bottom:12px}.config-fields{display:flex;flex-direction:column;gap:16px;margin-top:16px}.nested-fields{display:flex;flex-direction:column;gap:12px;margin-left:24px;margin-top:12px;padding:16px;background-color:var(--tae-surface-variant);border-radius:8px}.action-section-title{width:100%;margin:12px 0 4px;font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--tae-on-surface-variant)}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}.action-global-fields .mat-mdc-form-field-icon-suffix{align-self:center}.toggle-field{display:flex;align-items:center;gap:8px}.section-icon{margin-right:8px;color:var(--tae-primary)}.subsection{margin-top:24px;padding:16px;border:1px solid var(--tae-outline-variant);border-radius:8px}.subsection h4{margin:0 0 16px;color:var(--tae-on-surface);font-weight:500}.actions-header{margin:16px 0;display:flex;justify-content:flex-start}.actions-list{display:flex;flex-direction:column;gap:8px;min-height:60px}.action-item{border:1px solid var(--tae-outline-variant);border-radius:8px;background-color:var(--tae-surface);overflow:hidden}.action-header{display:flex;align-items:center;padding:12px 16px;background-color:var(--tae-surface-low);cursor:move}.drag-handle{margin-right:8px;color:var(--tae-on-surface-variant);cursor:grab}.drag-handle:active{cursor:grabbing}.action-label{flex:1;margin-left:8px;font-weight:500}.action-controls{display:flex;gap:4px}.action-details{padding:16px;border-top:1px solid var(--tae-outline-variant);background-color:var(--tae-surface)}.action-form{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:16px}.action-global-fields{grid-column:1 / -1;padding:12px 14px;border:1px dashed var(--tae-outline-variant);border-radius:10px;background:var(--md-sys-color-surface-container)}.action-global-title{font-size:12px;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--tae-on-surface-variant);margin-bottom:10px}.action-global-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px 16px}mat-form-field{width:100%}mat-expansion-panel{margin-bottom:8px;border-radius:8px;overflow:hidden}mat-expansion-panel-header{min-height:56px}mat-panel-description{color:var(--tae-on-surface-variant)}.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:var(--md-sys-elevation-level3)}.cdk-drag-placeholder{opacity:0}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.actions-list.cdk-drop-list-dragging .action-item:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}\n"] }]
22182
+ `, styles: [":host{--tae-primary: var(--md-sys-color-primary);--tae-surface: var(--md-sys-color-surface);--tae-surface-low: var(--md-sys-color-surface-container-low);--tae-surface-variant: var(--md-sys-color-surface-variant);--tae-outline-variant: var(--md-sys-color-outline-variant);--tae-on-surface: var(--md-sys-color-on-surface);--tae-on-surface-variant: var(--md-sys-color-on-surface-variant)}.toolbar-actions-container{width:100%;padding:8px}.config-section{padding:16px}.section-hint{color:var(--tae-on-surface-variant);font-size:12px;margin-bottom:12px}.config-fields{display:flex;flex-direction:column;gap:16px;margin-top:16px}.nested-fields{display:flex;flex-direction:column;gap:12px;margin-left:24px;margin-top:12px;padding:16px;background-color:var(--tae-surface-variant);border-radius:8px}.action-section-title{width:100%;margin:12px 0 4px;font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--tae-on-surface-variant)}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}.action-field-error{color:var(--md-sys-color-error, #ba1a1a);font-weight:500}.action-global-fields .mat-mdc-form-field-icon-suffix{align-self:center}.toggle-field{display:flex;align-items:center;gap:8px}.section-icon{margin-right:8px;color:var(--tae-primary)}.subsection{margin-top:24px;padding:16px;border:1px solid var(--tae-outline-variant);border-radius:8px}.subsection h4{margin:0 0 16px;color:var(--tae-on-surface);font-weight:500}.actions-header{margin:16px 0;display:flex;justify-content:flex-start}.actions-list{display:flex;flex-direction:column;gap:8px;min-height:60px}.action-item{border:1px solid var(--tae-outline-variant);border-radius:8px;background-color:var(--tae-surface);overflow:hidden}.action-header{display:flex;align-items:center;padding:12px 16px;background-color:var(--tae-surface-low);cursor:move}.drag-handle{margin-right:8px;color:var(--tae-on-surface-variant);cursor:grab}.drag-handle:active{cursor:grabbing}.action-label{flex:1;margin-left:8px;font-weight:500}.action-controls{display:flex;gap:4px}.action-details{padding:16px;border-top:1px solid var(--tae-outline-variant);background-color:var(--tae-surface)}.action-form{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:16px}.action-global-fields{grid-column:1 / -1;padding:12px 14px;border:1px dashed var(--tae-outline-variant);border-radius:10px;background:var(--md-sys-color-surface-container)}.action-global-title{font-size:12px;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--tae-on-surface-variant);margin-bottom:10px}.action-global-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px 16px}mat-form-field{width:100%}mat-expansion-panel{margin-bottom:8px;border-radius:8px;overflow:hidden}mat-expansion-panel-header{min-height:56px}mat-panel-description{color:var(--tae-on-surface-variant)}.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:var(--md-sys-elevation-level3)}.cdk-drag-placeholder{opacity:0}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.actions-list.cdk-drop-list-dragging .action-item:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}\n"] }]
22067
22183
  }], ctorParameters: () => [{ type: i1$1.FormBuilder }, { type: i1.IconPickerService }], propDecorators: { config: [{
22068
22184
  type: Input
22069
22185
  }], configChange: [{
@@ -23722,7 +23838,9 @@ const CONTROL_TYPE_LABELS = {
23722
23838
  [INLINE_SEARCHABLE_SELECT_CONTROL_TYPE$1]: 'Select buscável inline',
23723
23839
  [String(FieldControlType.ASYNC_SELECT).toLowerCase()]: 'Select assíncrono',
23724
23840
  [INLINE_ASYNC_SELECT_CONTROL_TYPE$1]: 'Select assíncrono inline',
23841
+ [String(FieldControlType.ENTITY_LOOKUP).toLowerCase()]: 'Lookup de entidade',
23725
23842
  [INLINE_ENTITY_LOOKUP_CONTROL_TYPE$1]: 'Lookup entidade inline',
23843
+ [normalizeControlTypeToken(INLINE_ENTITY_LOOKUP_CONTROL_TYPE$1)]: 'Lookup entidade inline',
23726
23844
  [String(FieldControlType.AUTO_COMPLETE).toLowerCase()]: 'Autocomplete',
23727
23845
  [INLINE_AUTOCOMPLETE_CONTROL_TYPE$1]: 'Autocomplete inline',
23728
23846
  [INLINE_NUMBER_CONTROL_TYPE$1]: 'Numérico inline',
@@ -24485,6 +24603,7 @@ class FilterSettingsComponent {
24485
24603
  const token = normalizeControlTypeToken(base);
24486
24604
  const searchableSelectToken = normalizeControlTypeToken(FieldControlType.SEARCHABLE_SELECT);
24487
24605
  const asyncSelectToken = normalizeControlTypeToken(FieldControlType.ASYNC_SELECT);
24606
+ const entityLookupToken = normalizeControlTypeToken(FieldControlType.ENTITY_LOOKUP);
24488
24607
  const autoCompleteToken = normalizeControlTypeToken(FieldControlType.AUTO_COMPLETE);
24489
24608
  const numericTextBoxToken = normalizeControlTypeToken(FieldControlType.NUMERIC_TEXT_BOX);
24490
24609
  const currencyToken = normalizeControlTypeToken(FieldControlType.CURRENCY_INPUT);
@@ -24590,6 +24709,9 @@ class FilterSettingsComponent {
24590
24709
  ? INLINE_ASYNC_SELECT_CONTROL_TYPE$1
24591
24710
  : String(FieldControlType.ASYNC_SELECT).toLowerCase();
24592
24711
  }
24712
+ if (token === entityLookupToken || token === 'entitylookup') {
24713
+ return INLINE_ENTITY_LOOKUP_CONTROL_TYPE$1;
24714
+ }
24593
24715
  if (token === autoCompleteToken || token === 'autocomplete') {
24594
24716
  return this.form.controls.useInlineSearchableSelectVariant.value
24595
24717
  ? INLINE_AUTOCOMPLETE_CONTROL_TYPE$1
@@ -30729,6 +30851,8 @@ class PraxisTableConfigEditor {
30729
30851
  cdr;
30730
30852
  configService;
30731
30853
  i18n = inject(PraxisI18nService);
30854
+ globalActionCatalogSource = inject(GLOBAL_ACTION_CATALOG, { optional: true }) ?? [];
30855
+ globalActionCatalog = this.buildGlobalActionCatalog(this.globalActionCatalogSource);
30732
30856
  filterSettingsEditor;
30733
30857
  behaviorEditor;
30734
30858
  rulesEditor;
@@ -30764,7 +30888,7 @@ class PraxisTableConfigEditor {
30764
30888
  // resourcePath para conexão rápida (fora do TableConfig)
30765
30889
  resourcePath = '';
30766
30890
  initialResourcePath = '';
30767
- // idField (fora do TableConfig) usado para resolver a chave primária no componente de Tabela standalone
30891
+ // idField editável; persistência canônica em config.meta.idField.
30768
30892
  idField = 'id';
30769
30893
  initialIdField = 'id';
30770
30894
  // horizontalScroll (fora do TableConfig) – comportamento do componente
@@ -30799,6 +30923,7 @@ class PraxisTableConfigEditor {
30799
30923
  hasLocalDataInput = false;
30800
30924
  filterDtoLoadSeq = 0;
30801
30925
  lastKnownFilterSettings = {};
30926
+ globalActionValidationIssues = [];
30802
30927
  panelData = inject(SETTINGS_PANEL_DATA, { optional: true });
30803
30928
  crudService = inject(GenericCrudService, {
30804
30929
  optional: true,
@@ -30849,11 +30974,11 @@ class PraxisTableConfigEditor {
30849
30974
  // Dados do servidor para divergência
30850
30975
  this.serverIdField = this.panelData?.serverIdField || undefined;
30851
30976
  this.serverSchemaHash = this.panelData?.serverSchemaInfo?.schemaHash || this.panelData?.serverHash || undefined;
30852
- // Inicializar resourcePath/idField (prioriza valor direto; fallback para crudContext/config.meta)
30977
+ // Inicializar resourcePath/idField (prioriza config.meta; fallbacks são apenas contexto runtime)
30853
30978
  this.resourcePath = this.panelData?.resourcePath || this.crudContext?.resourcePath || '';
30854
30979
  this.initialResourcePath = this.resourcePath;
30855
30980
  this.hasLocalDataInput = this.panelData?.hasLocalDataInput === true;
30856
- this.idField = this.panelData?.idField || this.crudContext?.idField || config?.meta?.idField || 'id';
30981
+ this.idField = config?.meta?.idField || this.panelData?.idField || this.crudContext?.idField || 'id';
30857
30982
  this.initialIdField = this.idField;
30858
30983
  // horizontalScroll recebido do host (quando disponível)
30859
30984
  const hs = this.panelData?.horizontalScroll || 'auto';
@@ -30913,9 +31038,9 @@ class PraxisTableConfigEditor {
30913
31038
  ? newValue
30914
31039
  : createTableAuthoringDocument({ config: newValue });
30915
31040
  this.editedConfig = newDocument.config;
31041
+ this.idField = newDocument.config?.meta?.idField || 'id';
30916
31042
  if (Object.prototype.hasOwnProperty.call(newDocument, 'bindings')) {
30917
31043
  this.resourcePath = newDocument.bindings?.resourcePath ?? '';
30918
- this.idField = newDocument.bindings?.idField || 'id';
30919
31044
  this.horizontalScroll = newDocument.bindings?.horizontalScroll || 'auto';
30920
31045
  }
30921
31046
  this.enforceEffectiveModeStrategyConstraints();
@@ -30950,11 +31075,11 @@ class PraxisTableConfigEditor {
30950
31075
  }
30951
31076
  }
30952
31077
  buildJsonAuthoringDocument() {
31078
+ const config = this.withCanonicalIdField(this.editedConfig);
30953
31079
  return createTableAuthoringDocument({
30954
- config: this.editedConfig,
31080
+ config,
30955
31081
  bindings: {
30956
31082
  resourcePath: (this.resourcePath || '').trim() || null,
30957
- idField: (this.idField || '').trim() || undefined,
30958
31083
  horizontalScroll: this.horizontalScroll || 'auto',
30959
31084
  },
30960
31085
  });
@@ -31148,7 +31273,8 @@ class PraxisTableConfigEditor {
31148
31273
  horizontalScrollDirty ||
31149
31274
  !this.deepEqual(normalizedOriginal, normalizedEdited) ||
31150
31275
  this.hasCrudOverridesDirty;
31151
- const isValid = this.isValidJson;
31276
+ this.globalActionValidationIssues = this.collectGlobalActionValidationIssues(this.editedConfig);
31277
+ const isValid = this.isValidJson && this.globalActionValidationIssues.length === 0;
31152
31278
  const canSave = hasChanges && isValid;
31153
31279
  this.debugLog('[PraxisTableConfigEditor] updateCanSaveState', {
31154
31280
  hasChanges,
@@ -31176,6 +31302,32 @@ class PraxisTableConfigEditor {
31176
31302
  // isBusy$ será atualizado em operações específicas
31177
31303
  this.cdr.markForCheck();
31178
31304
  }
31305
+ collectGlobalActionValidationIssues(config) {
31306
+ return validateGlobalActionRefs(this.collectGlobalActionValidationTargets(config));
31307
+ }
31308
+ collectGlobalActionValidationTargets(config) {
31309
+ const targets = [];
31310
+ const add = (ref, path) => {
31311
+ if (!ref)
31312
+ return;
31313
+ targets.push({
31314
+ ref,
31315
+ path,
31316
+ catalogEntry: this.findGlobalActionCatalogEntry(ref.actionId),
31317
+ });
31318
+ };
31319
+ config.toolbar?.actions?.forEach((action, index) => add(action?.globalAction, `toolbar.actions[${index}].globalAction`));
31320
+ config.actions?.row?.actions?.forEach((action, index) => add(action?.globalAction, `actions.row.actions[${index}].globalAction`));
31321
+ config.actions?.bulk?.actions?.forEach((action, index) => add(action?.globalAction, `actions.bulk.actions[${index}].globalAction`));
31322
+ return targets;
31323
+ }
31324
+ findGlobalActionCatalogEntry(actionId) {
31325
+ return this.globalActionCatalog.find((entry) => entry.id === actionId);
31326
+ }
31327
+ buildGlobalActionCatalog(source) {
31328
+ const injected = getGlobalActionCatalog(source);
31329
+ return injected.length ? injected : PRAXIS_GLOBAL_ACTION_CATALOG;
31330
+ }
31179
31331
  subscribeCrudEditorChanges(ref) {
31180
31332
  try {
31181
31333
  const markDirty = () => {
@@ -31229,6 +31381,23 @@ class PraxisTableConfigEditor {
31229
31381
  * Normaliza uma configuração de tabela para comparação consistente
31230
31382
  * Remove propriedades undefined, ordena arrays e objetos de forma consistente
31231
31383
  */
31384
+ withCanonicalIdField(config) {
31385
+ const next = JSON.parse(JSON.stringify(config || { columns: [] }));
31386
+ const idField = (this.idField || '').trim();
31387
+ if (idField) {
31388
+ next.meta = {
31389
+ ...(next.meta || {}),
31390
+ idField,
31391
+ };
31392
+ }
31393
+ else if (next.meta && typeof next.meta === 'object') {
31394
+ delete next.meta.idField;
31395
+ if (Object.keys(next.meta).length === 0) {
31396
+ delete next.meta;
31397
+ }
31398
+ }
31399
+ return next;
31400
+ }
31232
31401
  normalizeTableConfig(config) {
31233
31402
  if (!config) {
31234
31403
  return { columns: [] };
@@ -31449,6 +31618,7 @@ class PraxisTableConfigEditor {
31449
31618
  }
31450
31619
  onIdFieldChange(val) {
31451
31620
  this.idField = (val || '').trim();
31621
+ this.editedConfig = this.withCanonicalIdField(this.editedConfig);
31452
31622
  const idDirty = (this.idField || 'id').trim() !== (this.initialIdField || 'id').trim();
31453
31623
  this.idFieldDiverges = !!(this.serverIdField && this.idField && this.serverIdField !== this.idField);
31454
31624
  const normalizedOriginal = this.normalizeTableConfig(this.originalConfig);
@@ -31473,6 +31643,7 @@ class PraxisTableConfigEditor {
31473
31643
  if (!this.serverIdField)
31474
31644
  return;
31475
31645
  this.idField = this.serverIdField;
31646
+ this.editedConfig = this.withCanonicalIdField(this.editedConfig);
31476
31647
  this.idFieldDiverges = false;
31477
31648
  this.updateCanSaveState();
31478
31649
  this.showSuccess('Chave primária reconciliada com o servidor');
@@ -33847,7 +34018,10 @@ class PraxisFilter {
33847
34018
  normalizedControlType === 'autocomplete' ||
33848
34019
  normalizedControlType === 'auto-complete' ||
33849
34020
  controlTypeToken === 'autocomplete';
33850
- const isEntityLookup = normalizedControlType === INLINE_ENTITY_LOOKUP_CONTROL_TYPE ||
34021
+ const isEntityLookup = controlType === FieldControlType.ENTITY_LOOKUP ||
34022
+ normalizedControlType === 'entitylookup' ||
34023
+ controlTypeToken === 'entitylookup' ||
34024
+ controlTypeToken === normalizeControlTypeToken(INLINE_ENTITY_LOOKUP_CONTROL_TYPE) ||
33851
34025
  meta?.entityLookup === true;
33852
34026
  const isNumeric = controlType === FieldControlType.NUMERIC_TEXT_BOX ||
33853
34027
  normalizedControlType === 'numerictextbox' ||
@@ -36521,6 +36695,7 @@ class PraxisTable {
36521
36695
  resourceDiscovery;
36522
36696
  componentKeys;
36523
36697
  loadingOrchestrator;
36698
+ globalActions;
36524
36699
  loadingRenderer;
36525
36700
  route;
36526
36701
  logger;
@@ -36581,6 +36756,7 @@ class PraxisTable {
36581
36756
  metadataChange = new EventEmitter();
36582
36757
  loadingStateChange = new EventEmitter();
36583
36758
  collectionLinksChange = new EventEmitter();
36759
+ selectionChange = new EventEmitter();
36584
36760
  latestLoadingState = null;
36585
36761
  paginator;
36586
36762
  sort;
@@ -37623,7 +37799,7 @@ class PraxisTable {
37623
37799
  return true;
37624
37800
  return (config.toolbar?.actions?.length ?? 0) > 0;
37625
37801
  }
37626
- constructor(cdr, settingsPanel, crudService, tableDefaultsProvider, filterConfig, formattingService, pxDialog, snackBar, asyncConfigStorage, connectionStorage, hostRef, global, resourceDiscovery, componentKeys, loadingOrchestrator, loadingRenderer, route, logger) {
37802
+ constructor(cdr, settingsPanel, crudService, tableDefaultsProvider, filterConfig, formattingService, pxDialog, snackBar, asyncConfigStorage, connectionStorage, hostRef, global, resourceDiscovery, componentKeys, loadingOrchestrator, globalActions, loadingRenderer, route, logger) {
37627
37803
  this.cdr = cdr;
37628
37804
  this.settingsPanel = settingsPanel;
37629
37805
  this.crudService = crudService;
@@ -37639,6 +37815,7 @@ class PraxisTable {
37639
37815
  this.resourceDiscovery = resourceDiscovery;
37640
37816
  this.componentKeys = componentKeys;
37641
37817
  this.loadingOrchestrator = loadingOrchestrator;
37818
+ this.globalActions = globalActions;
37642
37819
  this.loadingRenderer = loadingRenderer;
37643
37820
  this.route = route;
37644
37821
  this.logger = logger;
@@ -37673,7 +37850,7 @@ class PraxisTable {
37673
37850
  if (this.aiAdapter || this.aiAdapterLoadStarted)
37674
37851
  return;
37675
37852
  this.aiAdapterLoadStarted = true;
37676
- import('./praxisui-table-table-ai.adapter-DxjDaQqy.mjs')
37853
+ import('./praxisui-table-table-ai.adapter-fS74fZ7o.mjs')
37677
37854
  .then(({ TableAiAdapter }) => {
37678
37855
  this.aiAdapter = new TableAiAdapter(this);
37679
37856
  this.initializeAiAssistantController();
@@ -41103,7 +41280,7 @@ class PraxisTable {
41103
41280
  action,
41104
41281
  rowId: row?.id,
41105
41282
  }, { actionId: action, throttleKey: `praxis-table:row-action:emit:${action}` });
41106
- this.emitRowActionEvent(action, row, resolvedRuntimeOptions);
41283
+ void this.dispatchRowAction(action, row, resolvedRuntimeOptions);
41107
41284
  }
41108
41285
  }
41109
41286
  resolveRowActionRuntimeOptions(runtimeOptions, fallbackConfig) {
@@ -41224,7 +41401,7 @@ class PraxisTable {
41224
41401
  }
41225
41402
  else {
41226
41403
  this.debugLog('[PraxisTable] onRowAction: confirmed, emitting rowAction', { action, rowId: row?.id });
41227
- this.emitRowActionEvent(action, row, runtimeOptions);
41404
+ void this.dispatchRowAction(action, row, runtimeOptions);
41228
41405
  }
41229
41406
  }
41230
41407
  else {
@@ -41324,6 +41501,22 @@ class PraxisTable {
41324
41501
  },
41325
41502
  });
41326
41503
  }
41504
+ async dispatchRowAction(action, row, runtimeOptions, extra) {
41505
+ const actionConfig = runtimeOptions?.actionConfig;
41506
+ if (actionConfig?.globalAction) {
41507
+ await this.executeConfiguredGlobalAction(actionConfig, {
41508
+ sourceId: this.tableId || this.componentInstanceId || 'praxis-table',
41509
+ output: 'rowAction',
41510
+ payload: runtimeOptions?.payload ?? row,
41511
+ runtime: { row },
41512
+ meta: { actionId: action, actionConfig },
41513
+ });
41514
+ if (!this.shouldEmitLocalForGlobalAction(actionConfig)) {
41515
+ return;
41516
+ }
41517
+ }
41518
+ this.emitRowActionEvent(action, row, runtimeOptions, extra);
41519
+ }
41327
41520
  cloneForEmit(value) {
41328
41521
  if (value === undefined || value === null)
41329
41522
  return value;
@@ -41465,6 +41658,25 @@ class PraxisTable {
41465
41658
  });
41466
41659
  }
41467
41660
  }
41661
+ async executeConfiguredGlobalAction(actionConfig, context) {
41662
+ const ref = actionConfig?.globalAction;
41663
+ if (!ref)
41664
+ return false;
41665
+ const result = await this.globalActions.executeRef(ref, context);
41666
+ if (!result.success) {
41667
+ this.warnLog('[PraxisTable] global action execution failed', { actionId: ref.actionId, error: result.error }, { actionId: ref.actionId });
41668
+ this.showActionFeedbackMessage('errors', ref.actionId, {
41669
+ label: actionConfig.label || ref.meta?.label || ref.actionId,
41670
+ error: result.error,
41671
+ fallback: result.error || 'Erro ao executar acao global',
41672
+ duration: 3000,
41673
+ });
41674
+ }
41675
+ return true;
41676
+ }
41677
+ shouldEmitLocalForGlobalAction(actionConfig) {
41678
+ return actionConfig?.emitLocal === true || actionConfig?.globalAction?.meta?.emitLocal === true;
41679
+ }
41468
41680
  resolveBulkValidationMessage(kind, context) {
41469
41681
  const templates = this.config?.messages?.actions?.validation?.bulk || {};
41470
41682
  const defaults = {
@@ -41582,7 +41794,7 @@ class PraxisTable {
41582
41794
  }
41583
41795
  });
41584
41796
  }
41585
- onToolbarAction(event) {
41797
+ async onToolbarAction(event) {
41586
41798
  this.debugLogWithMeta('[PraxisTable] onToolbarAction received', event, { actionId: event?.action || 'toolbar', throttleKey: 'praxis-table:toolbar-action:received' });
41587
41799
  const action = String(event?.action || '').trim();
41588
41800
  if (!action)
@@ -41597,7 +41809,7 @@ class PraxisTable {
41597
41809
  const actionLabel = safeActionConfig?.label || bulk?.label || action;
41598
41810
  const willAutoDelete = action === 'delete' && (this.autoDelete || bulk.autoDelete);
41599
41811
  const requiresConfirmation = !!bulk.requiresConfirmation || !!willAutoDelete;
41600
- const executeBulk = () => {
41812
+ const executeBulk = async () => {
41601
41813
  if (willAutoDelete) {
41602
41814
  if (this.isLocalDataModeActive()) {
41603
41815
  this.debugLog('Local mode: skipping backend bulk delete and emitting bulkAction', { count: rows.length });
@@ -41690,6 +41902,18 @@ class PraxisTable {
41690
41902
  action,
41691
41903
  selected: rows.length,
41692
41904
  }, { actionId: action, throttleKey: `praxis-table:toolbar-bulk-emit:${action}` });
41905
+ if (safeActionConfig?.globalAction) {
41906
+ await this.executeConfiguredGlobalAction(safeActionConfig, {
41907
+ sourceId: this.tableId || this.componentInstanceId || 'praxis-table',
41908
+ output: 'bulkAction',
41909
+ payload: rows,
41910
+ runtime: { selection: rows },
41911
+ meta: { actionId: action, actionConfig: safeActionConfig },
41912
+ });
41913
+ if (!this.shouldEmitLocalForGlobalAction(safeActionConfig)) {
41914
+ return;
41915
+ }
41916
+ }
41693
41917
  this.emitEventWithActionFeedback(this.bulkAction, {
41694
41918
  action,
41695
41919
  rows: this.cloneForEmit(rows),
@@ -41702,10 +41926,10 @@ class PraxisTable {
41702
41926
  });
41703
41927
  };
41704
41928
  if (requiresConfirmation) {
41705
- this.showBulkConfirmDialog(action, bulk, rows.length, executeBulk);
41929
+ this.showBulkConfirmDialog(action, bulk, rows.length, () => void executeBulk());
41706
41930
  return;
41707
41931
  }
41708
- executeBulk();
41932
+ await executeBulk();
41709
41933
  return;
41710
41934
  }
41711
41935
  const toolbarActionConfig = event.actionConfig ||
@@ -41717,6 +41941,17 @@ class PraxisTable {
41717
41941
  : {}),
41718
41942
  };
41719
41943
  this.debugLogWithMeta('[PraxisTable] onToolbarAction: emitting toolbarAction', payload, { actionId: action, throttleKey: `praxis-table:toolbar-action-emit:${action}` });
41944
+ if (toolbarActionConfig?.globalAction) {
41945
+ await this.executeConfiguredGlobalAction(toolbarActionConfig, {
41946
+ sourceId: this.tableId || this.componentInstanceId || 'praxis-table',
41947
+ output: 'toolbarAction',
41948
+ payload,
41949
+ meta: { actionId: action, actionConfig: toolbarActionConfig },
41950
+ });
41951
+ if (!this.shouldEmitLocalForGlobalAction(toolbarActionConfig)) {
41952
+ return;
41953
+ }
41954
+ }
41720
41955
  this.emitEventWithActionFeedback(this.toolbarAction, payload, {
41721
41956
  actionId: action,
41722
41957
  label: toolbarActionConfig?.label || action,
@@ -42002,7 +42237,6 @@ class PraxisTable {
42002
42237
  },
42003
42238
  currentBindings: {
42004
42239
  resourcePath: (this.resourcePath || '').trim() || null,
42005
- idField: this.getIdField(),
42006
42240
  horizontalScroll: this.horizontalScroll,
42007
42241
  },
42008
42242
  };
@@ -42036,9 +42270,6 @@ class PraxisTable {
42036
42270
  this.disconnect();
42037
42271
  }
42038
42272
  }
42039
- if (nextBindings.idField && nextBindings.idField !== this.idField) {
42040
- this.idField = nextBindings.idField;
42041
- }
42042
42273
  const nextHs = nextBindings.horizontalScroll;
42043
42274
  if (nextHs && nextHs !== this.horizontalScroll) {
42044
42275
  this.horizontalScroll = nextHs;
@@ -46945,7 +47176,15 @@ class PraxisTable {
46945
47176
  return normalized ? normalized : null;
46946
47177
  }
46947
47178
  getIdField() {
46948
- return this.idField || 'id';
47179
+ return this.resolveConfigMetaIdField() || this.idField || 'id';
47180
+ }
47181
+ resolveConfigMetaIdField() {
47182
+ const raw = this.config?.meta?.idField;
47183
+ if (typeof raw !== 'string') {
47184
+ return null;
47185
+ }
47186
+ const normalized = raw.trim();
47187
+ return normalized ? normalized : null;
46949
47188
  }
46950
47189
  syncRuntimeSchemaMetaFromConfig() {
46951
47190
  const meta = (this.config?.meta || {});
@@ -47052,6 +47291,7 @@ class PraxisTable {
47052
47291
  this.isAllSelected() ?
47053
47292
  this.selection.clear() :
47054
47293
  this.dataSource.data.forEach(row => this.selection.select(row));
47294
+ this.emitSelectionChange('select-all-toggle');
47055
47295
  }
47056
47296
  isAllSelected() {
47057
47297
  const numSelected = this.selection.selected.length;
@@ -47069,13 +47309,26 @@ class PraxisTable {
47069
47309
  if (isSingleSelection) {
47070
47310
  if (allowDeselect && alreadySelected) {
47071
47311
  this.selection.deselect(row);
47312
+ this.emitSelectionChange('row-toggle', row);
47072
47313
  return;
47073
47314
  }
47074
47315
  this.selection.clear();
47075
47316
  this.selection.select(row);
47317
+ this.emitSelectionChange('row-select', row);
47076
47318
  return;
47077
47319
  }
47078
47320
  this.selection.toggle(row);
47321
+ this.emitSelectionChange('row-toggle', row);
47322
+ }
47323
+ emitSelectionChange(trigger, row) {
47324
+ const selectedRows = this.selection.selected.slice();
47325
+ this.selectionChange.emit({
47326
+ trigger,
47327
+ row,
47328
+ selectedRows,
47329
+ selectedCount: selectedRows.length,
47330
+ tableId: this.tableId,
47331
+ });
47079
47332
  }
47080
47333
  getRowActionsConfig() {
47081
47334
  return this.config.actions?.row || {};
@@ -47259,12 +47512,22 @@ class PraxisTable {
47259
47512
  : configuredActionsRef
47260
47513
  .map((action) => this.decorateConfiguredRowAction(action, row))
47261
47514
  .filter((action) => !!action);
47262
- if (!actionCatalog) {
47263
- return configuredActions;
47515
+ if (canReuseConfiguredActionsDirectly) {
47516
+ return configuredActionsRef;
47264
47517
  }
47265
47518
  const normalizedConfiguredIds = new Set(configuredActions.map((action) => this.normalizeActionId(action)).filter(Boolean));
47519
+ const discoveredSurfaces = this.getDiscoveredRowSurfaceActionsFromSnapshot(capabilitySnapshot, normalizedConfiguredIds);
47520
+ discoveredSurfaces.forEach((action) => {
47521
+ const normalizedId = this.normalizeActionId(action);
47522
+ if (normalizedId) {
47523
+ normalizedConfiguredIds.add(normalizedId);
47524
+ }
47525
+ });
47526
+ if (!actionCatalog) {
47527
+ return [...configuredActions, ...discoveredSurfaces];
47528
+ }
47266
47529
  const discoveredActions = this.getDiscoveredRowWorkflowActionsFromCatalog(actionCatalog, normalizedConfiguredIds);
47267
- return [...configuredActions, ...discoveredActions];
47530
+ return [...configuredActions, ...discoveredSurfaces, ...discoveredActions];
47268
47531
  }
47269
47532
  splitVisibleRowActionsFromVisible(visible, display = this.getRowActionsDisplay()) {
47270
47533
  if (!visible.length) {
@@ -47305,6 +47568,25 @@ class PraxisTable {
47305
47568
  }
47306
47569
  return action;
47307
47570
  }
47571
+ const capabilitySnapshot = this.getCachedRowCapabilitySnapshot(row);
47572
+ const discoveredSurface = this.findItemSurface(capabilitySnapshot?.surfaces, normalizedId);
47573
+ if (discoveredSurface) {
47574
+ return {
47575
+ ...action,
47576
+ label: action.label || discoveredSurface.title || action.action,
47577
+ tooltip: action.tooltip
47578
+ || discoveredSurface.description
47579
+ || this.getUnavailableSurfaceMessage(discoveredSurface),
47580
+ color: action.color
47581
+ || (discoveredSurface.availability?.allowed === false
47582
+ ? 'warn'
47583
+ : this.getDiscoveredSurfaceColor(discoveredSurface)),
47584
+ disabled: typeof action.disabled === 'boolean'
47585
+ ? action.disabled
47586
+ : discoveredSurface.availability?.allowed === false,
47587
+ __praxisDiscoveredSurface: discoveredSurface,
47588
+ };
47589
+ }
47308
47590
  const hasResolvedCatalog = this.hasResolvedRowActionCatalog(row);
47309
47591
  const catalog = this.getCachedRowActionCatalog(row);
47310
47592
  if (!hasResolvedCatalog || !catalog) {
@@ -47335,6 +47617,25 @@ class PraxisTable {
47335
47617
  }
47336
47618
  return this.getDiscoveredRowWorkflowActionsFromCatalog(catalog, normalizedConfiguredIds);
47337
47619
  }
47620
+ getDiscoveredRowSurfaceActionsFromSnapshot(snapshot, normalizedConfiguredIds) {
47621
+ return (snapshot?.surfaces || [])
47622
+ .filter((surface) => surface.scope === 'ITEM')
47623
+ .filter((surface) => this.isRowSurfaceAction(surface))
47624
+ .sort((left, right) => (left.order ?? 0) - (right.order ?? 0))
47625
+ .filter((surface) => !normalizedConfiguredIds.has(this.normalizeActionId(surface)))
47626
+ .map((surface) => ({
47627
+ id: surface.id,
47628
+ action: surface.id,
47629
+ label: surface.title || surface.id,
47630
+ icon: this.getDiscoveredSurfaceIcon(surface),
47631
+ color: surface.availability?.allowed === false
47632
+ ? 'warn'
47633
+ : this.getDiscoveredSurfaceColor(surface),
47634
+ tooltip: surface.description || this.getUnavailableSurfaceMessage(surface),
47635
+ disabled: surface.availability?.allowed === false,
47636
+ __praxisDiscoveredSurface: surface,
47637
+ }));
47638
+ }
47338
47639
  getDiscoveredRowWorkflowActionsFromCatalog(catalog, normalizedConfiguredIds) {
47339
47640
  return (catalog.actions || [])
47340
47641
  .filter((action) => action.scope === 'ITEM')
@@ -47372,12 +47673,41 @@ class PraxisTable {
47372
47673
  }
47373
47674
  return (actions.find((action) => action.scope === 'ITEM' && this.normalizeActionId(action) === normalizedId) || null);
47374
47675
  }
47676
+ findItemSurface(surfaces, normalizedId) {
47677
+ if (!Array.isArray(surfaces) || !surfaces.length) {
47678
+ return null;
47679
+ }
47680
+ return (surfaces.find((surface) => surface.scope === 'ITEM'
47681
+ && this.isRowSurfaceAction(surface)
47682
+ && this.normalizeActionId(surface) === normalizedId) || null);
47683
+ }
47684
+ isRowSurfaceAction(surface) {
47685
+ return this.isWritableRowSurface(surface) || this.isReadableRowSurface(surface);
47686
+ }
47687
+ isWritableRowSurface(surface) {
47688
+ return surface.kind === 'FORM' || surface.kind === 'PARTIAL_FORM';
47689
+ }
47690
+ isReadableRowSurface(surface) {
47691
+ return surface.kind === 'VIEW' || surface.kind === 'READ_PROJECTION';
47692
+ }
47693
+ getDiscoveredSurfaceIcon(surface) {
47694
+ return this.isReadableRowSurface(surface) ? 'visibility' : 'edit_note';
47695
+ }
47696
+ getDiscoveredSurfaceColor(surface) {
47697
+ return this.isReadableRowSurface(surface) ? 'accent' : 'primary';
47698
+ }
47375
47699
  getUnavailableWorkflowMessage(action) {
47376
47700
  if (action.availability?.allowed !== false) {
47377
47701
  return undefined;
47378
47702
  }
47379
47703
  return translateUnavailableWorkflowMessage(this.i18n, action.availability);
47380
47704
  }
47705
+ getUnavailableSurfaceMessage(surface) {
47706
+ if (surface.availability?.allowed !== false) {
47707
+ return undefined;
47708
+ }
47709
+ return translateUnavailableWorkflowMessage(this.i18n, surface.availability);
47710
+ }
47381
47711
  normalizeActionId(action) {
47382
47712
  return String(action?.action || action?.id || '').trim().toLowerCase();
47383
47713
  }
@@ -47405,6 +47735,9 @@ class PraxisTable {
47405
47735
  if (action.__praxisDiscoveredAction?.availability?.allowed === false) {
47406
47736
  return true;
47407
47737
  }
47738
+ if (action.__praxisDiscoveredSurface?.availability?.allowed === false) {
47739
+ return true;
47740
+ }
47408
47741
  if (typeof action.disabled === 'boolean')
47409
47742
  return action.disabled;
47410
47743
  const disabledWhen = action.disabledWhen;
@@ -47426,10 +47759,15 @@ class PraxisTable {
47426
47759
  actionConfig: action.__praxisDiscoveredAction,
47427
47760
  }, action);
47428
47761
  }
47762
+ if (action.__praxisDiscoveredSurface) {
47763
+ return this.resolveRowActionRuntimeOptions({
47764
+ actionConfig: action.__praxisDiscoveredSurface,
47765
+ }, action);
47766
+ }
47429
47767
  return this.resolveRowActionRuntimeOptions(undefined, action);
47430
47768
  }
47431
47769
  isWorkflowRowAction(action) {
47432
- return !!action?.__praxisDiscoveredAction;
47770
+ return !!action?.__praxisDiscoveredAction || !!action?.__praxisDiscoveredSurface;
47433
47771
  }
47434
47772
  isBlockedWorkflowRowAction(action, row) {
47435
47773
  return this.isWorkflowRowAction(action) && this.isActionDisabled(action, row);
@@ -47440,7 +47778,9 @@ class PraxisTable {
47440
47778
  }
47441
47779
  const disabledReason = action.__praxisDiscoveredAction
47442
47780
  ? this.getUnavailableWorkflowMessage(action.__praxisDiscoveredAction)
47443
- : undefined;
47781
+ : action.__praxisDiscoveredSurface
47782
+ ? this.getUnavailableSurfaceMessage(action.__praxisDiscoveredSurface)
47783
+ : undefined;
47444
47784
  const tooltip = String(disabledReason
47445
47785
  || action.tooltip
47446
47786
  || action.label
@@ -47489,8 +47829,8 @@ class PraxisTable {
47489
47829
  this.removeViewportChangeListeners?.();
47490
47830
  this.removeViewportChangeListeners = null;
47491
47831
  }
47492
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisTable, deps: [{ token: i0.ChangeDetectorRef }, { token: i3$1.SettingsPanelService }, { token: i1.GenericCrudService }, { token: TableDefaultsProvider }, { token: FilterConfigService }, { token: DataFormattingService }, { token: i6$3.PraxisDialog }, { token: i2$2.MatSnackBar }, { token: ASYNC_CONFIG_STORAGE }, { token: CONNECTION_STORAGE }, { token: i0.ElementRef }, { token: i1.GlobalConfigService }, { token: i1.ResourceDiscoveryService }, { token: i1.ComponentKeyService }, { token: i1.LoadingOrchestrator }, { token: PRAXIS_LOADING_RENDERER, optional: true }, { token: i6$2.ActivatedRoute, optional: true }, { token: i1.LoggerService, optional: true }], target: i0.ɵɵFactoryTarget.Component });
47493
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisTable, isStandalone: true, selector: "praxis-table", inputs: { config: "config", resourcePath: "resourcePath", data: "data", tableId: "tableId", componentInstanceId: "componentInstanceId", title: "title", subtitle: "subtitle", icon: "icon", autoDelete: "autoDelete", notifyIfOutdated: "notifyIfOutdated", snoozeMs: "snoozeMs", autoOpenSettingsOnOutdated: "autoOpenSettingsOnOutdated", crudContext: "crudContext", filterCriteria: "filterCriteria", queryContext: "queryContext", enableCustomization: ["enableCustomization", "enableCustomization", booleanAttribute], dense: "dense" }, outputs: { rowClick: "rowClick", rowDoubleClick: "rowDoubleClick", rowExpansionChange: "rowExpansionChange", rowAction: "rowAction", toolbarAction: "toolbarAction", bulkAction: "bulkAction", columnReorder: "columnReorder", columnReorderAttempt: "columnReorderAttempt", beforeDelete: "beforeDelete", afterDelete: "afterDelete", deleteError: "deleteError", beforeBulkDelete: "beforeBulkDelete", afterBulkDelete: "afterBulkDelete", bulkDeleteError: "bulkDeleteError", schemaStatusChange: "schemaStatusChange", metadataChange: "metadataChange", loadingStateChange: "loadingStateChange", collectionLinksChange: "collectionLinksChange" }, host: { properties: { "class.density-compact": "this.hostDensityCompactClass", "class.density-comfortable": "this.hostDensityComfortableClass", "class.density-spacious": "this.hostDensitySpaciousClass", "class.row-borders": "this.hostRowBordersClass", "class.col-borders": "this.hostColumnBordersClass" } }, providers: [
47832
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisTable, deps: [{ token: i0.ChangeDetectorRef }, { token: i3$1.SettingsPanelService }, { token: i1.GenericCrudService }, { token: TableDefaultsProvider }, { token: FilterConfigService }, { token: DataFormattingService }, { token: i6$3.PraxisDialog }, { token: i2$2.MatSnackBar }, { token: ASYNC_CONFIG_STORAGE }, { token: CONNECTION_STORAGE }, { token: i0.ElementRef }, { token: i1.GlobalConfigService }, { token: i1.ResourceDiscoveryService }, { token: i1.ComponentKeyService }, { token: i1.LoadingOrchestrator }, { token: i1.GlobalActionService }, { token: PRAXIS_LOADING_RENDERER, optional: true }, { token: i6$2.ActivatedRoute, optional: true }, { token: i1.LoggerService, optional: true }], target: i0.ɵɵFactoryTarget.Component });
47833
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisTable, isStandalone: true, selector: "praxis-table", inputs: { config: "config", resourcePath: "resourcePath", data: "data", tableId: "tableId", componentInstanceId: "componentInstanceId", title: "title", subtitle: "subtitle", icon: "icon", autoDelete: "autoDelete", notifyIfOutdated: "notifyIfOutdated", snoozeMs: "snoozeMs", autoOpenSettingsOnOutdated: "autoOpenSettingsOnOutdated", crudContext: "crudContext", filterCriteria: "filterCriteria", queryContext: "queryContext", enableCustomization: ["enableCustomization", "enableCustomization", booleanAttribute], dense: "dense" }, outputs: { rowClick: "rowClick", rowDoubleClick: "rowDoubleClick", rowExpansionChange: "rowExpansionChange", rowAction: "rowAction", toolbarAction: "toolbarAction", bulkAction: "bulkAction", columnReorder: "columnReorder", columnReorderAttempt: "columnReorderAttempt", beforeDelete: "beforeDelete", afterDelete: "afterDelete", deleteError: "deleteError", beforeBulkDelete: "beforeBulkDelete", afterBulkDelete: "afterBulkDelete", bulkDeleteError: "bulkDeleteError", schemaStatusChange: "schemaStatusChange", metadataChange: "metadataChange", loadingStateChange: "loadingStateChange", collectionLinksChange: "collectionLinksChange", selectionChange: "selectionChange" }, host: { properties: { "class.density-compact": "this.hostDensityCompactClass", "class.density-comfortable": "this.hostDensityComfortableClass", "class.density-spacious": "this.hostDensitySpaciousClass", "class.row-borders": "this.hostRowBordersClass", "class.col-borders": "this.hostColumnBordersClass" } }, providers: [
47494
47834
  providePraxisI18nConfig(RESOURCE_DISCOVERY_I18N_CONFIG),
47495
47835
  providePraxisI18nConfig(PRAXIS_TABLE_RUNTIME_I18N_CONFIG),
47496
47836
  MatPaginatorIntl,
@@ -47540,7 +47880,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
47540
47880
  }] }, { type: undefined, decorators: [{
47541
47881
  type: Inject,
47542
47882
  args: [CONNECTION_STORAGE]
47543
- }] }, { type: i0.ElementRef }, { type: i1.GlobalConfigService }, { type: i1.ResourceDiscoveryService }, { type: i1.ComponentKeyService }, { type: i1.LoadingOrchestrator }, { type: undefined, decorators: [{
47883
+ }] }, { type: i0.ElementRef }, { type: i1.GlobalConfigService }, { type: i1.ResourceDiscoveryService }, { type: i1.ComponentKeyService }, { type: i1.LoadingOrchestrator }, { type: i1.GlobalActionService }, { type: undefined, decorators: [{
47544
47884
  type: Optional
47545
47885
  }, {
47546
47886
  type: Inject,
@@ -47616,6 +47956,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
47616
47956
  type: Output
47617
47957
  }], collectionLinksChange: [{
47618
47958
  type: Output
47959
+ }], selectionChange: [{
47960
+ type: Output
47619
47961
  }], paginator: [{
47620
47962
  type: ViewChild,
47621
47963
  args: [MatPaginator]
@@ -48126,6 +48468,2028 @@ function normalizeGap(gap) {
48126
48468
  }
48127
48469
  }
48128
48470
 
48471
+ const COLUMN_TYPES = ['string', 'number', 'date', 'boolean', 'currency', 'percentage', 'custom'];
48472
+ const FORMAT_PRESETS$1 = [
48473
+ 'shortDate',
48474
+ 'mediumDate',
48475
+ 'longDate',
48476
+ 'fullDate',
48477
+ 'MMM/yyyy',
48478
+ 'shortTime',
48479
+ 'short',
48480
+ 'yyyy-MM-dd',
48481
+ 'dd/MM/yyyy',
48482
+ 'yyyy-MM-dd HH:mm',
48483
+ '1.0-0',
48484
+ '1.1-1',
48485
+ '1.2-2',
48486
+ '1.0-3',
48487
+ '1.0-0|nosep',
48488
+ 'BRL|symbol|2',
48489
+ 'USD|symbol|2',
48490
+ 'EUR|symbol|2',
48491
+ 'BRL|code|2',
48492
+ 'USD|code|0',
48493
+ 'BRL|symbol|2|nosep',
48494
+ 'EUR|symbol|2|nosep',
48495
+ '1.0-0|x100',
48496
+ '1.1-1|x100',
48497
+ '1.2-2|x100',
48498
+ 'none',
48499
+ 'uppercase',
48500
+ 'lowercase',
48501
+ 'titlecase',
48502
+ 'capitalize',
48503
+ 'uppercase|truncate|50|...',
48504
+ 'true-false',
48505
+ 'yes-no',
48506
+ 'active-inactive',
48507
+ 'on-off',
48508
+ 'enabled-disabled',
48509
+ 'custom|Sim|Nao'
48510
+ ];
48511
+ const RENDERER_TYPES = ['icon', 'image', 'badge', 'link', 'button', 'chip', 'progress', 'avatar', 'toggle', 'menu', 'rating', 'html', 'compose'];
48512
+ const EXPORT_FORMATS = ['excel', 'pdf', 'csv', 'json', 'print'];
48513
+ const LOCALIZATION_DIRECTIONS = ['ltr', 'rtl'];
48514
+ const LIVE_REGIONS = ['polite', 'assertive'];
48515
+ const TABLE_LOADING_STRATEGIES = ['eager', 'lazy', 'on-demand'];
48516
+ const tableExpansionSchema = {
48517
+ type: 'object',
48518
+ minProperties: 1,
48519
+ properties: {
48520
+ enabled: { type: 'boolean' },
48521
+ contractVersion: { type: 'string' },
48522
+ state: {
48523
+ type: 'object',
48524
+ properties: { mode: { enum: ['controlled', 'uncontrolled'] } }
48525
+ },
48526
+ interaction: {
48527
+ type: 'object',
48528
+ properties: {
48529
+ trigger: { enum: ['icon', 'row', 'both'] },
48530
+ toggleIcon: {
48531
+ type: 'object',
48532
+ properties: {
48533
+ collapsed: { type: 'string' },
48534
+ expanded: { type: 'string' },
48535
+ ariaLabelCollapsed: { type: 'string' },
48536
+ ariaLabelExpanded: { type: 'string' }
48537
+ }
48538
+ },
48539
+ motion: {
48540
+ type: 'object',
48541
+ properties: {
48542
+ preset: { enum: ['none', 'subtle-slide', 'accordion', 'fade-scale'] },
48543
+ durationMs: { type: 'number', minimum: 0 },
48544
+ easing: { enum: ['standard', 'emphasized', 'decelerate'] }
48545
+ }
48546
+ },
48547
+ keyboard: {
48548
+ type: 'object',
48549
+ properties: { profile: { enum: ['disclosure', 'grid'] } }
48550
+ }
48551
+ }
48552
+ },
48553
+ limits: {
48554
+ type: 'object',
48555
+ properties: {
48556
+ allowMultiple: { type: 'boolean' },
48557
+ maxExpandedRows: { type: 'number', minimum: 1 },
48558
+ onOverflow: { enum: ['collapseOldest', 'denyNew', 'collapseAll'] }
48559
+ }
48560
+ },
48561
+ collapseOn: {
48562
+ type: 'object',
48563
+ properties: {
48564
+ sortChange: { type: 'boolean' },
48565
+ pageChange: { type: 'boolean' },
48566
+ filterChange: { type: 'boolean' },
48567
+ dataRefresh: { type: 'boolean' }
48568
+ }
48569
+ },
48570
+ detail: {
48571
+ type: 'object',
48572
+ properties: {
48573
+ schemaContract: {
48574
+ type: 'object',
48575
+ properties: {
48576
+ kind: { type: 'string' },
48577
+ version: { type: 'string' }
48578
+ }
48579
+ },
48580
+ source: {
48581
+ type: 'object',
48582
+ properties: {
48583
+ mode: { enum: ['inline', 'resource', 'resourcePath', 'hypermedia'] },
48584
+ inlineSchema: { type: 'object' },
48585
+ resource: {
48586
+ type: 'object',
48587
+ properties: {
48588
+ kind: { enum: ['ui-composition', 'form-schema', 'table-schema', 'dashboard-schema'] },
48589
+ id: { type: 'string' },
48590
+ version: { type: 'string' }
48591
+ }
48592
+ },
48593
+ resourcePath: {
48594
+ type: 'object',
48595
+ properties: {
48596
+ path: { type: 'string' },
48597
+ method: { enum: ['GET', 'POST'] },
48598
+ paramsMap: { type: 'object' }
48599
+ }
48600
+ },
48601
+ resourceAllowList: { type: 'array', items: { type: 'string' } },
48602
+ hypermedia: { type: 'object' }
48603
+ }
48604
+ },
48605
+ rendering: {
48606
+ type: 'object',
48607
+ properties: {
48608
+ strategy: { enum: ['registry'] },
48609
+ registryId: { type: 'string' },
48610
+ hostLayout: { enum: ['auto', 'stack', 'tabs'] },
48611
+ fallbackNodePolicy: { enum: ['failClosed', 'renderPlaceholder'] }
48612
+ }
48613
+ },
48614
+ height: {
48615
+ type: 'object',
48616
+ properties: {
48617
+ mode: { enum: ['fixed', 'dynamic'] },
48618
+ px: { type: 'number', minimum: 0 }
48619
+ }
48620
+ }
48621
+ }
48622
+ },
48623
+ virtualization: {
48624
+ type: 'object',
48625
+ properties: { policy: { enum: ['fixed-height-only', 'allow-dynamic-under-flag'] } }
48626
+ },
48627
+ persistence: {
48628
+ type: 'object',
48629
+ properties: {
48630
+ enabled: { type: 'boolean' },
48631
+ storageKeyStrategy: {
48632
+ type: 'object',
48633
+ properties: { namespace: { type: 'string' } }
48634
+ }
48635
+ }
48636
+ },
48637
+ security: {
48638
+ type: 'object',
48639
+ properties: {
48640
+ eventExposureDefault: {
48641
+ type: 'object',
48642
+ properties: {
48643
+ rowId: { enum: ['redacted', 'hashed', 'raw'] },
48644
+ expandedKeys: { enum: ['none', 'hashed', 'raw'] }
48645
+ }
48646
+ }
48647
+ }
48648
+ },
48649
+ deepLink: {
48650
+ type: 'object',
48651
+ properties: {
48652
+ enabled: { type: 'boolean' },
48653
+ encoding: { enum: ['csv'] },
48654
+ parsing: {
48655
+ type: 'object',
48656
+ properties: { duplicateParams: { enum: ['firstWins', 'reject'] } }
48657
+ },
48658
+ integrity: {
48659
+ type: 'object',
48660
+ properties: { mode: { enum: ['opaqueToken'] } }
48661
+ },
48662
+ privacy: {
48663
+ type: 'object',
48664
+ properties: { mode: { enum: ['denyByDefault', 'allowByDefault'] } }
48665
+ }
48666
+ }
48667
+ }
48668
+ }
48669
+ };
48670
+ const tableExportSchema = {
48671
+ type: 'object',
48672
+ minProperties: 1,
48673
+ properties: {
48674
+ enabled: { type: 'boolean' },
48675
+ formats: { type: 'array', items: { enum: EXPORT_FORMATS } },
48676
+ general: {
48677
+ type: 'object',
48678
+ properties: {
48679
+ includeHeaders: { type: 'boolean' },
48680
+ respectFilters: { type: 'boolean' },
48681
+ maxRows: { type: 'number', minimum: 1 }
48682
+ }
48683
+ },
48684
+ excel: {
48685
+ type: 'object',
48686
+ properties: { autoFitColumns: { type: 'boolean' } }
48687
+ },
48688
+ pdf: {
48689
+ type: 'object',
48690
+ properties: {
48691
+ orientation: { enum: ['portrait', 'landscape'] },
48692
+ pageSize: { enum: ['A4', 'A3', 'Letter', 'Legal'] }
48693
+ }
48694
+ },
48695
+ csv: {
48696
+ type: 'object',
48697
+ properties: { delimiter: { enum: [',', ';', '|', '\\t'] } }
48698
+ }
48699
+ }
48700
+ };
48701
+ const tableFilteringSchema = {
48702
+ type: 'object',
48703
+ minProperties: 1,
48704
+ properties: {
48705
+ enabled: { type: 'boolean' },
48706
+ strategy: { enum: ['client', 'server'] },
48707
+ columnFilters: {
48708
+ type: 'object',
48709
+ properties: {
48710
+ enabled: { type: 'boolean' },
48711
+ defaultType: { enum: ['text', 'select', 'date', 'number', 'boolean'] },
48712
+ position: { enum: ['header', 'subheader', 'sidebar'] }
48713
+ }
48714
+ },
48715
+ advancedFilters: {
48716
+ type: 'object',
48717
+ properties: {
48718
+ enabled: { type: 'boolean' },
48719
+ settings: {
48720
+ type: 'object',
48721
+ properties: { mode: { enum: ['filter'] } }
48722
+ }
48723
+ }
48724
+ },
48725
+ debounceTime: { type: 'number', minimum: 0 }
48726
+ }
48727
+ };
48728
+ const tableInteractionSchema = {
48729
+ type: 'object',
48730
+ minProperties: 1,
48731
+ properties: {
48732
+ rowClick: {
48733
+ type: 'object',
48734
+ properties: {
48735
+ enabled: { type: 'boolean' },
48736
+ action: { type: 'string' }
48737
+ }
48738
+ },
48739
+ keyboard: {
48740
+ type: 'object',
48741
+ properties: { enabled: { type: 'boolean' } }
48742
+ },
48743
+ hover: {
48744
+ type: 'object',
48745
+ properties: {
48746
+ enabled: { type: 'boolean' },
48747
+ highlightRow: { type: 'boolean' }
48748
+ }
48749
+ }
48750
+ }
48751
+ };
48752
+ const tableAppearanceSchema = {
48753
+ type: 'object',
48754
+ minProperties: 1,
48755
+ properties: {
48756
+ density: { enum: ['compact', 'comfortable', 'spacious'] },
48757
+ borders: {
48758
+ type: 'object',
48759
+ properties: {
48760
+ showRowBorders: { type: 'boolean' },
48761
+ showColumnBorders: { type: 'boolean' },
48762
+ showOuterBorder: { type: 'boolean' },
48763
+ style: { enum: ['solid', 'dashed', 'dotted'] }
48764
+ }
48765
+ },
48766
+ colors: {
48767
+ type: 'object',
48768
+ properties: {
48769
+ alternateRowBackground: { type: 'string' },
48770
+ hoverBackground: { type: 'string' }
48771
+ }
48772
+ },
48773
+ typography: {
48774
+ type: 'object',
48775
+ properties: {
48776
+ fontSize: { type: 'string' },
48777
+ headerFontWeight: { type: 'string' }
48778
+ }
48779
+ },
48780
+ spacing: {
48781
+ type: 'object',
48782
+ properties: { cellPadding: { type: 'string' } }
48783
+ },
48784
+ responsive: {
48785
+ type: 'object',
48786
+ properties: {
48787
+ mobile: {
48788
+ type: 'object',
48789
+ properties: {
48790
+ horizontalScroll: { type: 'boolean' },
48791
+ cardMode: { type: 'boolean' }
48792
+ }
48793
+ },
48794
+ breakpoints: {
48795
+ type: 'object',
48796
+ properties: { mobile: { type: 'number', minimum: 0 } }
48797
+ }
48798
+ }
48799
+ },
48800
+ animations: {
48801
+ type: 'object',
48802
+ properties: { enabled: { type: 'boolean' } }
48803
+ },
48804
+ elevation: {
48805
+ type: 'object',
48806
+ properties: { level: { type: 'number', minimum: 0 } }
48807
+ }
48808
+ }
48809
+ };
48810
+ const tableToolbarSchema = {
48811
+ type: 'object',
48812
+ minProperties: 1,
48813
+ properties: {
48814
+ visible: { type: 'boolean' },
48815
+ position: { enum: ['top', 'bottom', 'both'] },
48816
+ actionsPosition: { enum: ['top', 'bottom', 'both'] },
48817
+ actionsBackgroundColor: { type: 'string' },
48818
+ title: { type: 'string' },
48819
+ subtitle: { type: 'string' },
48820
+ layout: {
48821
+ type: 'object',
48822
+ properties: { alignment: { enum: ['start', 'center', 'end', 'space-between'] } }
48823
+ },
48824
+ filters: {
48825
+ type: 'object',
48826
+ properties: { enabled: { type: 'boolean' } }
48827
+ },
48828
+ settingsMenu: {
48829
+ type: 'object',
48830
+ properties: { enabled: { type: 'boolean' } }
48831
+ }
48832
+ }
48833
+ };
48834
+ const tableMessagesSchema = {
48835
+ type: 'object',
48836
+ minProperties: 1,
48837
+ properties: {
48838
+ states: {
48839
+ type: 'object',
48840
+ properties: {
48841
+ empty: { type: 'string' },
48842
+ loading: { type: 'string' }
48843
+ }
48844
+ },
48845
+ actions: {
48846
+ type: 'object',
48847
+ properties: {
48848
+ confirmations: {
48849
+ type: 'object',
48850
+ properties: { delete: { type: 'string' } }
48851
+ }
48852
+ }
48853
+ }
48854
+ }
48855
+ };
48856
+ const tableLocalizationSchema = {
48857
+ type: 'object',
48858
+ minProperties: 1,
48859
+ properties: {
48860
+ locale: { type: 'string' },
48861
+ direction: { enum: LOCALIZATION_DIRECTIONS },
48862
+ dateTime: {
48863
+ type: 'object',
48864
+ properties: {
48865
+ dateFormat: { type: 'string' },
48866
+ timeFormat: { type: 'string' },
48867
+ dateTimeFormat: { type: 'string' },
48868
+ firstDayOfWeek: { enum: [0, 1, 2, 3, 4, 5, 6] },
48869
+ relativeTime: { type: 'boolean' },
48870
+ monthNames: { type: 'array', items: { type: 'string' } },
48871
+ dayNames: { type: 'array', items: { type: 'string' } }
48872
+ }
48873
+ },
48874
+ number: {
48875
+ type: 'object',
48876
+ properties: {
48877
+ decimalSeparator: { type: 'string' },
48878
+ thousandsSeparator: { type: 'string' },
48879
+ defaultPrecision: { type: 'number', minimum: 0 },
48880
+ negativeSign: { type: 'string' },
48881
+ negativeSignPosition: { enum: ['before', 'after'] }
48882
+ }
48883
+ },
48884
+ currency: {
48885
+ type: 'object',
48886
+ properties: {
48887
+ code: { type: 'string' },
48888
+ symbol: { type: 'string' },
48889
+ position: { enum: ['before', 'after'] },
48890
+ spacing: { type: 'boolean' },
48891
+ precision: { type: 'number', minimum: 0 }
48892
+ }
48893
+ },
48894
+ formatting: {
48895
+ type: 'object',
48896
+ properties: {
48897
+ percentageFormat: { type: 'string' },
48898
+ fileSizeFormat: { enum: ['binary', 'decimal'] }
48899
+ }
48900
+ }
48901
+ }
48902
+ };
48903
+ const tablePerformanceSchema = {
48904
+ type: 'object',
48905
+ minProperties: 1,
48906
+ properties: {
48907
+ virtualization: {
48908
+ type: 'object',
48909
+ properties: {
48910
+ enabled: { type: 'boolean' },
48911
+ itemHeight: { type: 'number', minimum: 1 }
48912
+ }
48913
+ },
48914
+ debounce: {
48915
+ type: 'object',
48916
+ properties: {
48917
+ search: { type: 'number', minimum: 0 },
48918
+ filter: { type: 'number', minimum: 0 }
48919
+ }
48920
+ },
48921
+ lazyLoading: {
48922
+ type: 'object',
48923
+ properties: { images: { type: 'boolean' } }
48924
+ }
48925
+ }
48926
+ };
48927
+ const rendererSchema = {
48928
+ type: 'object',
48929
+ required: ['type'],
48930
+ properties: {
48931
+ type: { enum: RENDERER_TYPES },
48932
+ typeExpr: { type: 'string' },
48933
+ button: {
48934
+ type: 'object',
48935
+ required: ['action'],
48936
+ properties: {
48937
+ label: { type: 'string' },
48938
+ labelField: { type: 'string' },
48939
+ icon: { type: 'string' },
48940
+ color: { enum: ['primary', 'accent', 'warn'] },
48941
+ variant: { enum: ['filled', 'outlined', 'text'] },
48942
+ size: { enum: ['sm', 'md', 'lg'] },
48943
+ ariaLabel: { type: 'string' },
48944
+ disabledCondition: { type: 'object', description: 'AST Json Logic' },
48945
+ action: {
48946
+ type: 'object',
48947
+ required: ['id'],
48948
+ properties: {
48949
+ id: { type: 'string' },
48950
+ payloadExpr: { type: 'string' }
48951
+ }
48952
+ }
48953
+ }
48954
+ },
48955
+ menu: {
48956
+ type: 'object',
48957
+ required: ['itemsExpr'],
48958
+ properties: {
48959
+ itemsExpr: { type: 'string' },
48960
+ ariaLabel: { type: 'string' }
48961
+ }
48962
+ },
48963
+ link: {
48964
+ type: 'object',
48965
+ anyOf: [{ required: ['href'] }, { required: ['hrefField'] }],
48966
+ properties: {
48967
+ text: { type: 'string' },
48968
+ textField: { type: 'string' },
48969
+ href: { type: 'string' },
48970
+ hrefField: { type: 'string' },
48971
+ target: { enum: ['_blank', '_self', '_parent', '_top'] },
48972
+ rel: { type: 'string' }
48973
+ }
48974
+ },
48975
+ progress: {
48976
+ type: 'object',
48977
+ required: ['valueExpr'],
48978
+ properties: {
48979
+ valueExpr: { type: 'string' },
48980
+ color: { type: 'string' },
48981
+ showLabel: { type: 'boolean' }
48982
+ }
48983
+ },
48984
+ badge: {
48985
+ type: 'object',
48986
+ anyOf: [{ required: ['text'] }, { required: ['textField'] }],
48987
+ properties: {
48988
+ text: { type: 'string' },
48989
+ textField: { type: 'string' },
48990
+ color: { type: 'string' },
48991
+ variant: { enum: ['filled', 'outlined', 'soft'] },
48992
+ icon: { type: 'string' }
48993
+ }
48994
+ },
48995
+ chip: {
48996
+ type: 'object',
48997
+ anyOf: [{ required: ['text'] }, { required: ['textField'] }],
48998
+ properties: {
48999
+ text: { type: 'string' },
49000
+ textField: { type: 'string' },
49001
+ color: { type: 'string' },
49002
+ variant: { enum: ['filled', 'outlined'] },
49003
+ icon: { type: 'string' }
49004
+ }
49005
+ },
49006
+ icon: {
49007
+ type: 'object',
49008
+ anyOf: [{ required: ['name'] }, { required: ['nameField'] }],
49009
+ properties: {
49010
+ name: { type: 'string' },
49011
+ nameField: { type: 'string' },
49012
+ color: { type: 'string' },
49013
+ size: { type: 'number' },
49014
+ ariaLabel: { type: 'string' }
49015
+ }
49016
+ },
49017
+ image: {
49018
+ type: 'object',
49019
+ anyOf: [{ required: ['src'] }, { required: ['srcField'] }],
49020
+ properties: {
49021
+ src: { type: 'string' },
49022
+ srcField: { type: 'string' },
49023
+ alt: { type: 'string' },
49024
+ altField: { type: 'string' },
49025
+ width: { type: 'number' },
49026
+ height: { type: 'number' },
49027
+ shape: { enum: ['square', 'rounded', 'circle'] },
49028
+ fit: { enum: ['cover', 'contain'] },
49029
+ lazy: { type: 'boolean' }
49030
+ }
49031
+ },
49032
+ avatar: {
49033
+ type: 'object',
49034
+ anyOf: [{ required: ['src'] }, { required: ['srcField'] }, { required: ['initialsField'] }, { required: ['initialsExpr'] }],
49035
+ properties: {
49036
+ src: { type: 'string' },
49037
+ srcField: { type: 'string' },
49038
+ alt: { type: 'string' },
49039
+ altField: { type: 'string' },
49040
+ initialsField: { type: 'string' },
49041
+ initialsExpr: { type: 'string' },
49042
+ shape: { enum: ['circle', 'square', 'rounded'] },
49043
+ size: { type: 'number' }
49044
+ }
49045
+ },
49046
+ toggle: {
49047
+ type: 'object',
49048
+ required: ['stateExpr', 'action'],
49049
+ properties: {
49050
+ stateExpr: { type: 'string' },
49051
+ disabledCondition: { type: 'object', description: 'AST Json Logic' },
49052
+ ariaLabel: { type: 'string' },
49053
+ action: {
49054
+ type: 'object',
49055
+ required: ['id'],
49056
+ properties: {
49057
+ id: { type: 'string' },
49058
+ payloadExpr: { type: 'string' }
49059
+ }
49060
+ }
49061
+ }
49062
+ },
49063
+ rating: {
49064
+ type: 'object',
49065
+ required: ['valueExpr'],
49066
+ properties: {
49067
+ valueExpr: { type: 'string' },
49068
+ max: { type: 'number' },
49069
+ color: { type: 'string' },
49070
+ outlineColor: { type: 'string' },
49071
+ size: { enum: ['small', 'medium', 'large'] },
49072
+ ariaLabel: { type: 'string' }
49073
+ }
49074
+ },
49075
+ html: {
49076
+ type: 'object',
49077
+ required: ['template'],
49078
+ properties: {
49079
+ template: { type: 'string' },
49080
+ sanitize: { enum: ['strict', 'basic'] },
49081
+ emptyFallback: { type: 'string' }
49082
+ }
49083
+ },
49084
+ compose: {
49085
+ type: 'object',
49086
+ required: ['items'],
49087
+ properties: {
49088
+ items: {
49089
+ type: 'array',
49090
+ minItems: 1,
49091
+ items: {
49092
+ type: 'object',
49093
+ required: ['type'],
49094
+ properties: { type: { enum: RENDERER_TYPES.filter(type => type !== 'compose') } }
49095
+ }
49096
+ },
49097
+ layout: {
49098
+ type: 'object',
49099
+ properties: {
49100
+ gap: { type: 'number' },
49101
+ direction: { enum: ['row', 'column'] },
49102
+ align: { enum: ['start', 'center', 'end'] },
49103
+ wrap: { type: 'boolean' },
49104
+ ellipsis: { type: 'boolean' }
49105
+ }
49106
+ }
49107
+ }
49108
+ }
49109
+ },
49110
+ allOf: [
49111
+ { if: { properties: { type: { const: 'button' } } }, then: { required: ['button'] } },
49112
+ { if: { properties: { type: { const: 'menu' } } }, then: { required: ['menu'] } },
49113
+ { if: { properties: { type: { const: 'link' } } }, then: { required: ['link'] } },
49114
+ { if: { properties: { type: { const: 'progress' } } }, then: { required: ['progress'] } },
49115
+ { if: { properties: { type: { const: 'badge' } } }, then: { required: ['badge'] } },
49116
+ { if: { properties: { type: { const: 'chip' } } }, then: { required: ['chip'] } },
49117
+ { if: { properties: { type: { const: 'icon' } } }, then: { required: ['icon'] } },
49118
+ { if: { properties: { type: { const: 'image' } } }, then: { required: ['image'] } },
49119
+ { if: { properties: { type: { const: 'avatar' } } }, then: { required: ['avatar'] } },
49120
+ { if: { properties: { type: { const: 'toggle' } } }, then: { required: ['toggle'] } },
49121
+ { if: { properties: { type: { const: 'rating' } } }, then: { required: ['rating'] } },
49122
+ { if: { properties: { type: { const: 'html' } } }, then: { required: ['html'] } },
49123
+ { if: { properties: { type: { const: 'compose' } } }, then: { required: ['compose'] } }
49124
+ ]
49125
+ };
49126
+ /**
49127
+ * Manifesto de authoring canônico para o componente praxis-table.
49128
+ * Este arquivo define o contrato executável para que agentes de IA editem tabelas.
49129
+ *
49130
+ * @version 2.0.0
49131
+ * @status COMPLIANT - Alinhado com o contrato v2 e TableConfig canônico.
49132
+ */
49133
+ const PRAXIS_TABLE_AUTHORING_MANIFEST = {
49134
+ schemaVersion: '1.0.0',
49135
+ componentId: 'praxis-table',
49136
+ ownerPackage: '@praxisui/table',
49137
+ configSchemaId: 'TableConfig',
49138
+ manifestVersion: '2.0.0',
49139
+ runtimeInputs: [
49140
+ { name: 'config', type: 'TableConfig', description: 'Configuração completa da tabela' },
49141
+ { name: 'data', type: 'any[]', description: 'Dados a serem exibidos (modo client-side)' },
49142
+ { name: 'resourcePath', type: 'string', description: 'Caminho do recurso (API base)' },
49143
+ { name: 'queryContext', type: 'PraxisDataQueryContext', description: 'Contexto de consulta declarativo' },
49144
+ { name: 'title', type: 'string', description: 'Título da tabela' },
49145
+ { name: 'subtitle', type: 'string', description: 'Subtítulo da tabela' },
49146
+ { name: 'enableCustomization', type: 'boolean', description: 'Habilita modo de edição' }
49147
+ ],
49148
+ editableTargets: [
49149
+ { kind: 'column', resolver: 'column-by-field', description: 'Colunas base da tabela' },
49150
+ { kind: 'computedColumn', resolver: 'column-by-field', description: 'Colunas calculadas' },
49151
+ { kind: 'renderer', resolver: 'renderer-in-column', description: 'Renderizador de célula' },
49152
+ { kind: 'conditionalRenderer', resolver: 'conditional-renderer-in-column', description: 'Renderizadores condicionais' },
49153
+ { kind: 'rowAction', resolver: 'action-in-row-config', description: 'Ações por linha' },
49154
+ { kind: 'toolbarAction', resolver: 'action-in-toolbar-config', description: 'Ações na barra de ferramentas' },
49155
+ { kind: 'toolbar', resolver: 'toolbar-config', description: 'Configuração da barra de ferramentas' },
49156
+ { kind: 'filter', resolver: 'filter-config', description: 'Configuração de filtragem' },
49157
+ { kind: 'grouping', resolver: 'grouping-config', description: 'Configuração de agrupamento' },
49158
+ { kind: 'selection', resolver: 'selection-config', description: 'Configuração de seleção' },
49159
+ { kind: 'export', resolver: 'export-config', description: 'Configuração de exportação' },
49160
+ { kind: 'appearance', resolver: 'appearance-config', description: 'Configurações visuais' },
49161
+ { kind: 'expansion', resolver: 'expansion-config', description: 'Configuração de detalhe/expansão' },
49162
+ { kind: 'rule', resolver: 'style-rule-in-column-or-row', description: 'Regras de estilo ou comportamento' },
49163
+ { kind: 'meta', resolver: 'table-meta-config', description: 'Metadados da configuracao' },
49164
+ { kind: 'bulkAction', resolver: 'action-in-bulk-config', description: 'Acoes em lote' },
49165
+ { kind: 'contextAction', resolver: 'action-in-context-config', description: 'Acoes de menu contextual' },
49166
+ { kind: 'pagination', resolver: 'pagination-config', description: 'Configuracao de paginacao' },
49167
+ { kind: 'sorting', resolver: 'sorting-config', description: 'Configuracao de ordenacao' },
49168
+ { kind: 'interaction', resolver: 'interaction-config', description: 'Configuracao de interacao' },
49169
+ { kind: 'loading', resolver: 'loading-config', description: 'Estados de carregamento' },
49170
+ { kind: 'emptyState', resolver: 'empty-state-config', description: 'Estado vazio' },
49171
+ { kind: 'resizing', resolver: 'resizing-config', description: 'Redimensionamento de colunas' },
49172
+ { kind: 'dragging', resolver: 'dragging-config', description: 'Reorganizacao por drag and drop' },
49173
+ { kind: 'editing', resolver: 'editing-config', description: 'Modo de edicao' },
49174
+ { kind: 'messages', resolver: 'messages-config', description: 'Mensagens da tabela' },
49175
+ { kind: 'localization', resolver: 'localization-config', description: 'Localizacao e formatos regionais' },
49176
+ { kind: 'performance', resolver: 'performance-config', description: 'Configuracoes de performance' },
49177
+ { kind: 'data', resolver: 'data-config', description: 'Configuracoes de dados' },
49178
+ { kind: 'accessibility', resolver: 'accessibility-config', description: 'Acessibilidade' }
49179
+ ],
49180
+ operations: [
49181
+ // --- COLUMN OPERATIONS ---
49182
+ {
49183
+ operationId: 'column.add',
49184
+ title: 'Adicionar coluna',
49185
+ scope: 'global',
49186
+ targetKind: 'column',
49187
+ target: { kind: 'column', resolver: 'column-by-field', ambiguityPolicy: 'fail', required: false },
49188
+ inputSchema: {
49189
+ type: 'object',
49190
+ required: ['field', 'header'],
49191
+ properties: {
49192
+ field: { type: 'string' },
49193
+ header: { type: 'string' },
49194
+ type: { enum: COLUMN_TYPES, default: 'string' }
49195
+ }
49196
+ },
49197
+ effects: [{ kind: 'append-unique', path: 'columns[]', key: 'field' }],
49198
+ validators: ['column-field-unique'],
49199
+ affectedPaths: ['columns[]'],
49200
+ submissionImpact: false,
49201
+ preconditions: ['config-initialized']
49202
+ },
49203
+ {
49204
+ operationId: 'column.remove',
49205
+ title: 'Remover coluna',
49206
+ scope: 'column',
49207
+ targetKind: 'column',
49208
+ target: { kind: 'column', resolver: 'column-by-field', ambiguityPolicy: 'fail', required: true },
49209
+ inputSchema: { type: 'object', properties: {} },
49210
+ effects: [{ kind: 'remove-by-key', path: 'columns[]', key: 'field' }],
49211
+ destructive: true,
49212
+ requiresConfirmation: true,
49213
+ validators: ['destructive-removal-confirmation'],
49214
+ affectedPaths: ['columns[]'],
49215
+ submissionImpact: false,
49216
+ preconditions: ['config-initialized', 'target-exists']
49217
+ },
49218
+ {
49219
+ operationId: 'column.header.set',
49220
+ title: 'Definir cabeçalho',
49221
+ scope: 'column',
49222
+ targetKind: 'column',
49223
+ target: { kind: 'column', resolver: 'column-by-field', ambiguityPolicy: 'fail', required: true },
49224
+ inputSchema: {
49225
+ type: 'object',
49226
+ required: ['header'],
49227
+ properties: { header: { type: 'string', minLength: 1 } }
49228
+ },
49229
+ effects: [{ kind: 'merge-by-key', path: 'columns[]', key: 'field' }],
49230
+ validators: ['target-column-exists'],
49231
+ affectedPaths: ['columns[].header'],
49232
+ submissionImpact: false,
49233
+ preconditions: ['config-initialized', 'target-exists']
49234
+ },
49235
+ {
49236
+ operationId: 'column.type.set',
49237
+ title: 'Definir tipo de dado',
49238
+ scope: 'column',
49239
+ targetKind: 'column',
49240
+ target: { kind: 'column', resolver: 'column-by-field', ambiguityPolicy: 'fail', required: true },
49241
+ inputSchema: {
49242
+ type: 'object',
49243
+ required: ['type'],
49244
+ properties: {
49245
+ type: { enum: COLUMN_TYPES }
49246
+ }
49247
+ },
49248
+ effects: [{ kind: 'merge-by-key', path: 'columns[]', key: 'field' }],
49249
+ validators: ['target-column-exists'],
49250
+ affectedPaths: ['columns[].type'],
49251
+ submissionImpact: false,
49252
+ preconditions: ['config-initialized', 'target-exists']
49253
+ },
49254
+ {
49255
+ operationId: 'column.format.set',
49256
+ title: 'Definir formato',
49257
+ scope: 'column',
49258
+ targetKind: 'column',
49259
+ target: { kind: 'column', resolver: 'column-by-field', ambiguityPolicy: 'fail', required: true },
49260
+ inputSchema: {
49261
+ type: 'object',
49262
+ required: ['format'],
49263
+ properties: {
49264
+ format: {
49265
+ type: 'string',
49266
+ enum: FORMAT_PRESETS$1,
49267
+ description: 'Preset canônico do DataFormattingService.'
49268
+ }
49269
+ }
49270
+ },
49271
+ effects: [{ kind: 'merge-by-key', path: 'columns[]', key: 'field' }],
49272
+ validators: ['target-column-exists', 'format-preset-supported'],
49273
+ affectedPaths: ['columns[].format'],
49274
+ submissionImpact: false,
49275
+ preconditions: ['config-initialized', 'target-exists']
49276
+ },
49277
+ {
49278
+ operationId: 'column.width.set',
49279
+ title: 'Definir largura da coluna',
49280
+ scope: 'column',
49281
+ targetKind: 'column',
49282
+ target: { kind: 'column', resolver: 'column-by-field', ambiguityPolicy: 'fail', required: true },
49283
+ inputSchema: {
49284
+ type: 'object',
49285
+ required: ['width'],
49286
+ properties: { width: { type: 'string', pattern: '^(auto|\\d+(px|%|rem|em))$' } }
49287
+ },
49288
+ effects: [{ kind: 'merge-by-key', path: 'columns[]', key: 'field' }],
49289
+ validators: ['target-column-exists', 'column-width-valid'],
49290
+ affectedPaths: ['columns[].width'],
49291
+ submissionImpact: false,
49292
+ preconditions: ['config-initialized', 'target-exists']
49293
+ },
49294
+ {
49295
+ operationId: 'column.align.set',
49296
+ title: 'Definir alinhamento da coluna',
49297
+ scope: 'column',
49298
+ targetKind: 'column',
49299
+ target: { kind: 'column', resolver: 'column-by-field', ambiguityPolicy: 'fail', required: true },
49300
+ inputSchema: {
49301
+ type: 'object',
49302
+ required: ['align'],
49303
+ properties: { align: { enum: ['left', 'center', 'right'] } }
49304
+ },
49305
+ effects: [{ kind: 'merge-by-key', path: 'columns[]', key: 'field' }],
49306
+ validators: ['target-column-exists'],
49307
+ affectedPaths: ['columns[].align'],
49308
+ submissionImpact: false,
49309
+ preconditions: ['config-initialized', 'target-exists']
49310
+ },
49311
+ {
49312
+ operationId: 'column.sticky.set',
49313
+ title: 'Definir sticky da coluna',
49314
+ scope: 'column',
49315
+ targetKind: 'column',
49316
+ target: { kind: 'column', resolver: 'column-by-field', ambiguityPolicy: 'fail', required: true },
49317
+ inputSchema: {
49318
+ type: 'object',
49319
+ required: ['sticky'],
49320
+ properties: { sticky: { enum: ['start', 'end', true, false] } }
49321
+ },
49322
+ effects: [{ kind: 'merge-by-key', path: 'columns[]', key: 'field' }],
49323
+ validators: ['target-column-exists'],
49324
+ affectedPaths: ['columns[].sticky'],
49325
+ submissionImpact: false,
49326
+ preconditions: ['config-initialized', 'target-exists']
49327
+ },
49328
+ {
49329
+ operationId: 'column.sortable.set',
49330
+ title: 'Definir ordenação da coluna',
49331
+ scope: 'column',
49332
+ targetKind: 'column',
49333
+ target: { kind: 'column', resolver: 'column-by-field', ambiguityPolicy: 'fail', required: true },
49334
+ inputSchema: {
49335
+ type: 'object',
49336
+ required: ['sortable'],
49337
+ properties: { sortable: { type: 'boolean' } }
49338
+ },
49339
+ effects: [{ kind: 'merge-by-key', path: 'columns[]', key: 'field' }],
49340
+ validators: ['target-column-exists'],
49341
+ affectedPaths: ['columns[].sortable'],
49342
+ submissionImpact: false,
49343
+ preconditions: ['config-initialized', 'target-exists']
49344
+ },
49345
+ {
49346
+ operationId: 'column.filterable.set',
49347
+ title: 'Definir filtro da coluna',
49348
+ scope: 'column',
49349
+ targetKind: 'column',
49350
+ target: { kind: 'column', resolver: 'column-by-field', ambiguityPolicy: 'fail', required: true },
49351
+ inputSchema: {
49352
+ type: 'object',
49353
+ required: ['filterable'],
49354
+ properties: { filterable: { type: 'boolean' } }
49355
+ },
49356
+ effects: [{ kind: 'merge-by-key', path: 'columns[]', key: 'field' }],
49357
+ validators: ['target-column-exists'],
49358
+ affectedPaths: ['columns[].filterable'],
49359
+ submissionImpact: false,
49360
+ preconditions: ['config-initialized', 'target-exists']
49361
+ },
49362
+ {
49363
+ operationId: 'column.style.set',
49364
+ title: 'Definir estilo da célula',
49365
+ scope: 'column',
49366
+ targetKind: 'column',
49367
+ target: { kind: 'column', resolver: 'column-by-field', ambiguityPolicy: 'fail', required: true },
49368
+ inputSchema: {
49369
+ type: 'object',
49370
+ required: ['style'],
49371
+ properties: { style: { type: 'string' } }
49372
+ },
49373
+ effects: [{ kind: 'merge-by-key', path: 'columns[]', key: 'field' }],
49374
+ validators: ['target-column-exists', 'css-style-safe'],
49375
+ affectedPaths: ['columns[].style'],
49376
+ submissionImpact: false,
49377
+ preconditions: ['config-initialized', 'target-exists']
49378
+ },
49379
+ {
49380
+ operationId: 'column.headerStyle.set',
49381
+ title: 'Definir estilo do cabeçalho',
49382
+ scope: 'column',
49383
+ targetKind: 'column',
49384
+ target: { kind: 'column', resolver: 'column-by-field', ambiguityPolicy: 'fail', required: true },
49385
+ inputSchema: {
49386
+ type: 'object',
49387
+ required: ['headerStyle'],
49388
+ properties: { headerStyle: { type: 'string' } }
49389
+ },
49390
+ effects: [{ kind: 'merge-by-key', path: 'columns[]', key: 'field' }],
49391
+ validators: ['target-column-exists', 'css-style-safe'],
49392
+ affectedPaths: ['columns[].headerStyle'],
49393
+ submissionImpact: false,
49394
+ preconditions: ['config-initialized', 'target-exists']
49395
+ },
49396
+ {
49397
+ operationId: 'column.valueMapping.set',
49398
+ title: 'Definir mapeamento de valores',
49399
+ scope: 'column',
49400
+ targetKind: 'column',
49401
+ target: { kind: 'column', resolver: 'column-by-field', ambiguityPolicy: 'fail', required: true },
49402
+ inputSchema: {
49403
+ type: 'object',
49404
+ required: ['valueMapping'],
49405
+ properties: { valueMapping: { type: 'object', minProperties: 1 } }
49406
+ },
49407
+ effects: [{ kind: 'merge-by-key', path: 'columns[]', key: 'field' }],
49408
+ validators: ['target-column-exists', 'value-mapping-valid'],
49409
+ affectedPaths: ['columns[].valueMapping'],
49410
+ submissionImpact: false,
49411
+ preconditions: ['config-initialized', 'target-exists']
49412
+ },
49413
+ {
49414
+ operationId: 'column.order.set',
49415
+ title: 'Definir ordem visual',
49416
+ scope: 'column',
49417
+ targetKind: 'column',
49418
+ target: { kind: 'column', resolver: 'column-by-field', ambiguityPolicy: 'fail', required: true },
49419
+ inputSchema: {
49420
+ type: 'object',
49421
+ required: ['order'],
49422
+ properties: { order: { type: 'number', minimum: 0 } }
49423
+ },
49424
+ effects: [{ kind: 'merge-by-key', path: 'columns[]', key: 'field' }],
49425
+ validators: ['target-column-exists'],
49426
+ affectedPaths: ['columns[].order'],
49427
+ submissionImpact: false,
49428
+ preconditions: ['config-initialized', 'target-exists']
49429
+ },
49430
+ {
49431
+ operationId: 'column.visibility.set',
49432
+ title: 'Definir visibilidade',
49433
+ scope: 'column',
49434
+ targetKind: 'column',
49435
+ target: { kind: 'column', resolver: 'column-by-field', ambiguityPolicy: 'fail', required: true },
49436
+ inputSchema: {
49437
+ type: 'object',
49438
+ required: ['visible'],
49439
+ properties: { visible: { type: 'boolean' } }
49440
+ },
49441
+ effects: [{ kind: 'merge-by-key', path: 'columns[]', key: 'field' }],
49442
+ validators: ['target-column-exists'],
49443
+ affectedPaths: ['columns[].visible'],
49444
+ submissionImpact: false,
49445
+ preconditions: ['config-initialized', 'target-exists']
49446
+ },
49447
+ {
49448
+ operationId: 'column.computed.add',
49449
+ title: 'Adicionar coluna calculada',
49450
+ scope: 'global',
49451
+ targetKind: 'computedColumn',
49452
+ target: { kind: 'computedColumn', resolver: 'column-by-field', ambiguityPolicy: 'fail', required: false },
49453
+ inputSchema: {
49454
+ type: 'object',
49455
+ required: ['field', 'header', 'expression'],
49456
+ properties: {
49457
+ field: { type: 'string' },
49458
+ header: { type: 'string' },
49459
+ expression: { type: 'object', description: 'AST Json Logic' },
49460
+ outputType: { enum: COLUMN_TYPES }
49461
+ }
49462
+ },
49463
+ effects: [{ kind: 'append-unique', path: 'columns[]', key: 'field' }],
49464
+ validators: ['column-field-unique', 'computed-expression-valid'],
49465
+ affectedPaths: ['columns[]'],
49466
+ submissionImpact: false,
49467
+ preconditions: ['config-initialized']
49468
+ },
49469
+ {
49470
+ operationId: 'column.renderer.set',
49471
+ title: 'Definir renderizador',
49472
+ scope: 'column',
49473
+ targetKind: 'renderer',
49474
+ target: { kind: 'renderer', resolver: 'renderer-in-column', ambiguityPolicy: 'fail', required: true },
49475
+ inputSchema: rendererSchema,
49476
+ effects: [{ kind: 'merge-object', path: 'columns[].renderer' }],
49477
+ validators: ['target-column-exists', 'renderer-type-supported', 'renderer-config-match', 'editor-round-trip-preserve'],
49478
+ affectedPaths: ['columns[].renderer'],
49479
+ submissionImpact: false,
49480
+ preconditions: ['config-initialized', 'target-exists']
49481
+ },
49482
+ {
49483
+ operationId: 'column.conditionalRenderer.add',
49484
+ title: 'Adicionar renderizador condicional',
49485
+ scope: 'column',
49486
+ targetKind: 'conditionalRenderer',
49487
+ target: { kind: 'conditionalRenderer', resolver: 'conditional-renderer-in-column', ambiguityPolicy: 'fail', required: true },
49488
+ inputSchema: {
49489
+ type: 'object',
49490
+ required: ['id', 'condition', 'renderer'],
49491
+ properties: {
49492
+ id: { type: 'string', description: 'Identificador estável do renderer condicional (usado para deduplicação)' },
49493
+ condition: { type: 'object', description: 'AST Json Logic' },
49494
+ renderer: rendererSchema,
49495
+ enabled: { type: 'boolean' },
49496
+ description: { type: 'string' }
49497
+ }
49498
+ },
49499
+ effects: [{ kind: 'append-unique', path: 'columns[].conditionalRenderers[]', key: 'id' }],
49500
+ validators: ['target-column-exists', 'computed-expression-valid', 'renderer-config-match'],
49501
+ affectedPaths: ['columns[].conditionalRenderers[]'],
49502
+ submissionImpact: false,
49503
+ preconditions: ['config-initialized', 'target-exists']
49504
+ },
49505
+ {
49506
+ operationId: 'column.conditionalStyle.add',
49507
+ title: 'Adicionar regra de estilo condicional na coluna',
49508
+ scope: 'column',
49509
+ targetKind: 'rule',
49510
+ target: { kind: 'rule', resolver: 'style-rule-in-column-or-row', ambiguityPolicy: 'fail', required: true },
49511
+ inputSchema: {
49512
+ type: 'object',
49513
+ required: ['id', 'condition'],
49514
+ properties: {
49515
+ id: { type: 'string', description: 'Identificador estável da regra de estilo da coluna' },
49516
+ condition: { type: 'object', description: 'AST Json Logic' },
49517
+ cssClass: { type: 'string' },
49518
+ style: { type: 'object' },
49519
+ description: { type: 'string' }
49520
+ },
49521
+ anyOf: [{ required: ['cssClass'] }, { required: ['style'] }]
49522
+ },
49523
+ effects: [{ kind: 'append-unique', path: 'columns[].conditionalStyles[]', key: 'id' }],
49524
+ validators: ['target-column-exists', 'computed-expression-valid', 'conditional-style-valid'],
49525
+ affectedPaths: ['columns[].conditionalStyles[]'],
49526
+ submissionImpact: false,
49527
+ preconditions: ['config-initialized', 'target-exists']
49528
+ },
49529
+ // --- ROW & STYLE OPERATIONS ---
49530
+ {
49531
+ operationId: 'row.styleRule.add',
49532
+ title: 'Adicionar regra de estilo de linha',
49533
+ scope: 'global',
49534
+ targetKind: 'rule',
49535
+ target: { kind: 'rule', resolver: 'style-rule-in-column-or-row', ambiguityPolicy: 'fail', required: false },
49536
+ inputSchema: {
49537
+ type: 'object',
49538
+ required: ['id', 'condition', 'style'],
49539
+ properties: {
49540
+ id: { type: 'string', description: 'Identificador estável da regra (usado para deduplicação via append-unique)' },
49541
+ condition: { type: 'object', description: 'AST Json Logic' },
49542
+ style: { type: 'object', description: 'Estilo CSS (Record<string, string>)' },
49543
+ cssClass: { type: 'string' },
49544
+ description: { type: 'string' }
49545
+ }
49546
+ },
49547
+ effects: [{ kind: 'append-unique', path: 'rowConditionalStyles[]', key: 'id' }],
49548
+ validators: ['computed-expression-valid'],
49549
+ affectedPaths: ['rowConditionalStyles[]'],
49550
+ submissionImpact: false,
49551
+ preconditions: ['config-initialized']
49552
+ },
49553
+ // --- TOOLBAR & ACTIONS ---
49554
+ {
49555
+ operationId: 'toolbar.visibility.set',
49556
+ title: 'Definir visibilidade da toolbar',
49557
+ scope: 'global',
49558
+ targetKind: 'toolbar',
49559
+ target: { kind: 'toolbar', resolver: 'toolbar-config', ambiguityPolicy: 'fail', required: false },
49560
+ inputSchema: {
49561
+ type: 'object',
49562
+ required: ['visible'],
49563
+ properties: { visible: { type: 'boolean' } }
49564
+ },
49565
+ effects: [{ kind: 'merge-object', path: 'toolbar' }],
49566
+ affectedPaths: ['toolbar.visible'],
49567
+ submissionImpact: false,
49568
+ preconditions: ['config-initialized']
49569
+ },
49570
+ {
49571
+ operationId: 'toolbar.action.add',
49572
+ title: 'Adicionar ação na toolbar',
49573
+ scope: 'global',
49574
+ targetKind: 'toolbarAction',
49575
+ target: { kind: 'toolbarAction', resolver: 'action-in-toolbar-config', ambiguityPolicy: 'fail', required: false },
49576
+ inputSchema: {
49577
+ type: 'object',
49578
+ required: ['id', 'label', 'action'],
49579
+ properties: {
49580
+ id: { type: 'string' },
49581
+ label: { type: 'string' },
49582
+ action: { type: 'string' },
49583
+ icon: { type: 'string' },
49584
+ type: { enum: ['button', 'icon', 'menu'], default: 'button' }
49585
+ }
49586
+ },
49587
+ effects: [{ kind: 'append-unique', path: 'toolbar.actions[]', key: 'id' }],
49588
+ validators: ['toolbar-action-id-unique'],
49589
+ affectedPaths: ['toolbar.actions[]'],
49590
+ submissionImpact: false,
49591
+ preconditions: ['config-initialized']
49592
+ },
49593
+ {
49594
+ operationId: 'rowAction.add',
49595
+ title: 'Adicionar ação de linha',
49596
+ scope: 'global',
49597
+ targetKind: 'rowAction',
49598
+ target: { kind: 'rowAction', resolver: 'action-in-row-config', ambiguityPolicy: 'fail', required: false },
49599
+ inputSchema: {
49600
+ type: 'object',
49601
+ required: ['id', 'label', 'action'],
49602
+ properties: {
49603
+ id: { type: 'string' },
49604
+ label: { type: 'string' },
49605
+ action: { type: 'string' },
49606
+ icon: { type: 'string' },
49607
+ color: { type: 'string' }
49608
+ }
49609
+ },
49610
+ effects: [{ kind: 'append-unique', path: 'actions.row.actions[]', key: 'id' }],
49611
+ validators: ['row-action-id-unique'],
49612
+ affectedPaths: ['actions.row.actions[]'],
49613
+ submissionImpact: false,
49614
+ preconditions: ['config-initialized']
49615
+ },
49616
+ // --- BEHAVIOR & PERFORMANCE ---
49617
+ {
49618
+ operationId: 'export.enabled.set',
49619
+ title: 'Habilitar exportação',
49620
+ scope: 'global',
49621
+ targetKind: 'export',
49622
+ target: { kind: 'export', resolver: 'export-config', ambiguityPolicy: 'fail', required: false },
49623
+ inputSchema: {
49624
+ type: 'object',
49625
+ required: ['enabled'],
49626
+ properties: {
49627
+ enabled: { type: 'boolean' },
49628
+ formats: { type: 'array', items: { enum: ['excel', 'pdf', 'csv', 'json'] } }
49629
+ }
49630
+ },
49631
+ effects: [{ kind: 'merge-object', path: 'export' }],
49632
+ affectedPaths: ['export.enabled', 'export.formats'],
49633
+ submissionImpact: false,
49634
+ preconditions: ['config-initialized']
49635
+ },
49636
+ {
49637
+ operationId: 'filter.advanced.configure',
49638
+ title: 'Configurar filtros avançados',
49639
+ scope: 'global',
49640
+ targetKind: 'filter',
49641
+ target: { kind: 'filter', resolver: 'filter-config', ambiguityPolicy: 'fail', required: false },
49642
+ inputSchema: {
49643
+ type: 'object',
49644
+ required: ['enabled'],
49645
+ properties: {
49646
+ enabled: { type: 'boolean' },
49647
+ showAdvanced: { type: 'boolean' },
49648
+ allowSaveTags: { type: 'boolean' }
49649
+ }
49650
+ },
49651
+ effects: [
49652
+ { kind: 'merge-object', path: 'behavior.filtering.advancedFilters' }
49653
+ ],
49654
+ affectedPaths: ['behavior.filtering.advancedFilters.enabled', 'behavior.filtering.advancedFilters.settings'],
49655
+ submissionImpact: false,
49656
+ preconditions: ['config-initialized']
49657
+ },
49658
+ {
49659
+ operationId: 'grouping.set',
49660
+ title: 'Definir agrupamento',
49661
+ scope: 'global',
49662
+ targetKind: 'grouping',
49663
+ target: { kind: 'grouping', resolver: 'grouping-config', ambiguityPolicy: 'fail', required: false },
49664
+ inputSchema: {
49665
+ type: 'object',
49666
+ required: ['enabled'],
49667
+ properties: {
49668
+ enabled: { type: 'boolean' },
49669
+ fields: { type: 'array', items: { type: 'string' } },
49670
+ expanded: { type: 'boolean' }
49671
+ }
49672
+ },
49673
+ effects: [{ kind: 'merge-object', path: 'behavior.grouping' }],
49674
+ validators: ['grouping-fields-exist'],
49675
+ affectedPaths: ['behavior.grouping'],
49676
+ submissionImpact: false,
49677
+ preconditions: ['config-initialized']
49678
+ },
49679
+ {
49680
+ operationId: 'selection.mode.set',
49681
+ title: 'Definir modo de seleção',
49682
+ scope: 'global',
49683
+ targetKind: 'selection',
49684
+ target: { kind: 'selection', resolver: 'selection-config', ambiguityPolicy: 'fail', required: false },
49685
+ inputSchema: {
49686
+ type: 'object',
49687
+ required: ['enabled', 'type'],
49688
+ properties: {
49689
+ enabled: { type: 'boolean' },
49690
+ type: { enum: ['single', 'multiple'] },
49691
+ mode: { enum: ['checkbox', 'row', 'both'] }
49692
+ }
49693
+ },
49694
+ effects: [{ kind: 'merge-object', path: 'behavior.selection' }],
49695
+ affectedPaths: ['behavior.selection.enabled', 'behavior.selection.type', 'behavior.selection.mode'],
49696
+ submissionImpact: false,
49697
+ preconditions: ['config-initialized']
49698
+ },
49699
+ {
49700
+ operationId: 'appearance.density.set',
49701
+ title: 'Definir densidade',
49702
+ scope: 'global',
49703
+ targetKind: 'appearance',
49704
+ target: { kind: 'appearance', resolver: 'appearance-config', ambiguityPolicy: 'fail', required: false },
49705
+ inputSchema: {
49706
+ type: 'object',
49707
+ required: ['density'],
49708
+ properties: {
49709
+ density: { enum: ['compact', 'comfortable', 'spacious'] }
49710
+ }
49711
+ },
49712
+ effects: [{ kind: 'merge-object', path: 'appearance' }],
49713
+ affectedPaths: ['appearance.density'],
49714
+ submissionImpact: false,
49715
+ preconditions: ['config-initialized']
49716
+ },
49717
+ {
49718
+ operationId: 'appearance.borders.set',
49719
+ title: 'Definir bordas',
49720
+ scope: 'global',
49721
+ targetKind: 'appearance',
49722
+ target: { kind: 'appearance', resolver: 'appearance-config', ambiguityPolicy: 'fail', required: false },
49723
+ inputSchema: {
49724
+ type: 'object',
49725
+ required: ['showRowBorders', 'showColumnBorders'],
49726
+ properties: {
49727
+ showRowBorders: { type: 'boolean' },
49728
+ showColumnBorders: { type: 'boolean' },
49729
+ showOuterBorder: { type: 'boolean' }
49730
+ }
49731
+ },
49732
+ effects: [{ kind: 'merge-object', path: 'appearance.borders' }],
49733
+ affectedPaths: ['appearance.borders.showRowBorders', 'appearance.borders.showColumnBorders', 'appearance.borders.showOuterBorder'],
49734
+ submissionImpact: false,
49735
+ preconditions: ['config-initialized']
49736
+ },
49737
+ {
49738
+ operationId: 'virtualization.enabled.set',
49739
+ title: 'Habilitar virtualização',
49740
+ scope: 'global',
49741
+ targetKind: 'appearance',
49742
+ target: { kind: 'appearance', resolver: 'appearance-config', ambiguityPolicy: 'fail', required: false },
49743
+ inputSchema: {
49744
+ type: 'object',
49745
+ required: ['enabled'],
49746
+ properties: {
49747
+ enabled: { type: 'boolean' },
49748
+ itemHeight: { type: 'number', default: 48 }
49749
+ }
49750
+ },
49751
+ effects: [
49752
+ { kind: 'merge-object', path: 'behavior.virtualization' },
49753
+ { kind: 'merge-object', path: 'performance.virtualization' }
49754
+ ],
49755
+ affectedPaths: ['behavior.virtualization', 'performance.virtualization'],
49756
+ submissionImpact: false,
49757
+ preconditions: ['config-initialized']
49758
+ },
49759
+ {
49760
+ operationId: 'expansion.configure',
49761
+ title: 'Configurar expansão de linha',
49762
+ scope: 'global',
49763
+ targetKind: 'expansion',
49764
+ target: { kind: 'expansion', resolver: 'expansion-config', ambiguityPolicy: 'fail', required: false },
49765
+ inputSchema: tableExpansionSchema,
49766
+ effects: [{ kind: 'merge-object', path: 'behavior.expansion' }],
49767
+ validators: ['remote-resource-binding-safe'],
49768
+ affectedPaths: ['behavior.expansion'],
49769
+ submissionImpact: false,
49770
+ preconditions: ['config-initialized']
49771
+ },
49772
+ {
49773
+ operationId: 'meta.configure',
49774
+ title: 'Configurar metadados',
49775
+ scope: 'global',
49776
+ targetKind: 'meta',
49777
+ target: { kind: 'meta', resolver: 'table-meta-config', ambiguityPolicy: 'fail', required: false },
49778
+ inputSchema: {
49779
+ type: 'object',
49780
+ minProperties: 1,
49781
+ properties: {
49782
+ name: { type: 'string' },
49783
+ description: { type: 'string' },
49784
+ tags: { type: 'array', items: { type: 'string' } },
49785
+ schemaId: { type: 'string' }
49786
+ }
49787
+ },
49788
+ effects: [{ kind: 'merge-object', path: 'meta' }],
49789
+ affectedPaths: ['meta'],
49790
+ submissionImpact: false,
49791
+ preconditions: ['config-initialized']
49792
+ },
49793
+ {
49794
+ operationId: 'meta.idField.set',
49795
+ title: 'Definir campo de identidade',
49796
+ scope: 'global',
49797
+ targetKind: 'meta',
49798
+ target: { kind: 'meta', resolver: 'table-meta-config', ambiguityPolicy: 'fail', required: false },
49799
+ inputSchema: {
49800
+ type: 'object',
49801
+ required: ['idField'],
49802
+ properties: { idField: { type: 'string', minLength: 1 } }
49803
+ },
49804
+ effects: [{ kind: 'set-value', path: 'meta.idField' }],
49805
+ requiresConfirmation: true,
49806
+ affectedPaths: ['meta.idField'],
49807
+ submissionImpact: false,
49808
+ preconditions: ['config-initialized']
49809
+ },
49810
+ {
49811
+ operationId: 'column.resizable.set',
49812
+ title: 'Definir redimensionamento da coluna',
49813
+ scope: 'column',
49814
+ targetKind: 'column',
49815
+ target: { kind: 'column', resolver: 'column-by-field', ambiguityPolicy: 'fail', required: true },
49816
+ inputSchema: {
49817
+ type: 'object',
49818
+ required: ['resizable'],
49819
+ properties: { resizable: { type: 'boolean' } }
49820
+ },
49821
+ effects: [{ kind: 'merge-by-key', path: 'columns[]', key: 'field' }],
49822
+ validators: ['target-column-exists'],
49823
+ affectedPaths: ['columns[].resizable'],
49824
+ submissionImpact: false,
49825
+ preconditions: ['config-initialized', 'target-exists']
49826
+ },
49827
+ {
49828
+ operationId: 'column.sortField.set',
49829
+ title: 'Definir campo de ordenacao da coluna',
49830
+ scope: 'column',
49831
+ targetKind: 'column',
49832
+ target: { kind: 'column', resolver: 'column-by-field', ambiguityPolicy: 'fail', required: true },
49833
+ inputSchema: {
49834
+ type: 'object',
49835
+ required: ['sortField'],
49836
+ properties: { sortField: { type: 'string', minLength: 1 } }
49837
+ },
49838
+ effects: [{ kind: 'merge-by-key', path: 'columns[]', key: 'field' }],
49839
+ validators: ['target-column-exists'],
49840
+ affectedPaths: ['columns[].sortField'],
49841
+ submissionImpact: false,
49842
+ preconditions: ['config-initialized', 'target-exists']
49843
+ },
49844
+ {
49845
+ operationId: 'column.computed.configure',
49846
+ title: 'Configurar coluna calculada',
49847
+ scope: 'column',
49848
+ targetKind: 'computedColumn',
49849
+ target: { kind: 'computedColumn', resolver: 'column-by-field', ambiguityPolicy: 'fail', required: true },
49850
+ inputSchema: {
49851
+ type: 'object',
49852
+ required: ['expression'],
49853
+ properties: {
49854
+ expression: { type: 'object', description: 'AST Json Logic' },
49855
+ outputType: { enum: COLUMN_TYPES },
49856
+ format: { type: 'string', enum: FORMAT_PRESETS$1 },
49857
+ dependencies: { type: 'array', items: { type: 'string' } }
49858
+ }
49859
+ },
49860
+ effects: [{ kind: 'merge-by-key', path: 'columns[]', key: 'field' }],
49861
+ validators: ['target-column-exists', 'computed-expression-valid', 'format-preset-supported'],
49862
+ affectedPaths: ['columns[].computed'],
49863
+ submissionImpact: false,
49864
+ preconditions: ['config-initialized', 'target-exists']
49865
+ },
49866
+ {
49867
+ operationId: 'behavior.pagination.configure',
49868
+ title: 'Configurar paginacao',
49869
+ scope: 'global',
49870
+ targetKind: 'pagination',
49871
+ target: { kind: 'pagination', resolver: 'pagination-config', ambiguityPolicy: 'fail', required: false },
49872
+ inputSchema: {
49873
+ type: 'object',
49874
+ minProperties: 1,
49875
+ properties: {
49876
+ enabled: { type: 'boolean' },
49877
+ strategy: { enum: ['client', 'server'] },
49878
+ pageSize: { type: 'number', minimum: 1 },
49879
+ pageSizeOptions: { type: 'array', items: { type: 'number', minimum: 1 } },
49880
+ position: { enum: ['top', 'bottom', 'both'] },
49881
+ style: { enum: ['default', 'simple', 'advanced', 'compact'] },
49882
+ advanced: {
49883
+ type: 'object',
49884
+ properties: { maxPageSize: { type: 'number', minimum: 1 } }
49885
+ }
49886
+ }
49887
+ },
49888
+ effects: [{ kind: 'merge-object', path: 'behavior.pagination' }],
49889
+ affectedPaths: ['behavior.pagination'],
49890
+ submissionImpact: false,
49891
+ preconditions: ['config-initialized']
49892
+ },
49893
+ {
49894
+ operationId: 'behavior.sorting.configure',
49895
+ title: 'Configurar ordenacao',
49896
+ scope: 'global',
49897
+ targetKind: 'sorting',
49898
+ target: { kind: 'sorting', resolver: 'sorting-config', ambiguityPolicy: 'fail', required: false },
49899
+ inputSchema: {
49900
+ type: 'object',
49901
+ minProperties: 1,
49902
+ properties: {
49903
+ enabled: { type: 'boolean' },
49904
+ strategy: { enum: ['client', 'server'] },
49905
+ defaultSort: { type: 'object' },
49906
+ allowClearSort: { type: 'boolean' }
49907
+ }
49908
+ },
49909
+ effects: [{ kind: 'merge-object', path: 'behavior.sorting' }],
49910
+ affectedPaths: ['behavior.sorting'],
49911
+ submissionImpact: false,
49912
+ preconditions: ['config-initialized']
49913
+ },
49914
+ {
49915
+ operationId: 'behavior.filtering.configure',
49916
+ title: 'Configurar filtragem',
49917
+ scope: 'global',
49918
+ targetKind: 'filter',
49919
+ target: { kind: 'filter', resolver: 'filter-config', ambiguityPolicy: 'fail', required: false },
49920
+ inputSchema: tableFilteringSchema,
49921
+ effects: [{ kind: 'merge-object', path: 'behavior.filtering' }],
49922
+ affectedPaths: ['behavior.filtering'],
49923
+ submissionImpact: false,
49924
+ preconditions: ['config-initialized']
49925
+ },
49926
+ {
49927
+ operationId: 'behavior.selection.configure',
49928
+ title: 'Configurar selecao',
49929
+ scope: 'global',
49930
+ targetKind: 'selection',
49931
+ target: { kind: 'selection', resolver: 'selection-config', ambiguityPolicy: 'fail', required: false },
49932
+ inputSchema: {
49933
+ type: 'object',
49934
+ minProperties: 1,
49935
+ properties: {
49936
+ enabled: { type: 'boolean' },
49937
+ type: { enum: ['single', 'multiple'] },
49938
+ mode: { enum: ['checkbox', 'row', 'both'] },
49939
+ allowSelectAll: { type: 'boolean' },
49940
+ persistSelection: { type: 'boolean' },
49941
+ maxSelections: { type: 'number', minimum: 1 }
49942
+ }
49943
+ },
49944
+ effects: [{ kind: 'merge-object', path: 'behavior.selection' }],
49945
+ affectedPaths: ['behavior.selection'],
49946
+ submissionImpact: false,
49947
+ preconditions: ['config-initialized']
49948
+ },
49949
+ {
49950
+ operationId: 'behavior.interaction.configure',
49951
+ title: 'Configurar interacao',
49952
+ scope: 'global',
49953
+ targetKind: 'interaction',
49954
+ target: { kind: 'interaction', resolver: 'interaction-config', ambiguityPolicy: 'fail', required: false },
49955
+ inputSchema: tableInteractionSchema,
49956
+ effects: [{ kind: 'merge-object', path: 'behavior.interaction' }],
49957
+ affectedPaths: ['behavior.interaction'],
49958
+ submissionImpact: false,
49959
+ preconditions: ['config-initialized']
49960
+ },
49961
+ {
49962
+ operationId: 'behavior.loading.configure',
49963
+ title: 'Configurar loading',
49964
+ scope: 'global',
49965
+ targetKind: 'loading',
49966
+ target: { kind: 'loading', resolver: 'loading-config', ambiguityPolicy: 'fail', required: false },
49967
+ inputSchema: {
49968
+ type: 'object',
49969
+ minProperties: 1,
49970
+ properties: {
49971
+ type: { enum: ['spinner', 'skeleton', 'progress', 'custom'] },
49972
+ position: { enum: ['overlay', 'inline', 'replace'] },
49973
+ text: { type: 'string' },
49974
+ delay: { type: 'number', minimum: 0 }
49975
+ }
49976
+ },
49977
+ effects: [{ kind: 'merge-object', path: 'behavior.loading' }],
49978
+ affectedPaths: ['behavior.loading'],
49979
+ submissionImpact: false,
49980
+ preconditions: ['config-initialized']
49981
+ },
49982
+ {
49983
+ operationId: 'behavior.emptyState.configure',
49984
+ title: 'Configurar estado vazio',
49985
+ scope: 'global',
49986
+ targetKind: 'emptyState',
49987
+ target: { kind: 'emptyState', resolver: 'empty-state-config', ambiguityPolicy: 'fail', required: false },
49988
+ inputSchema: {
49989
+ type: 'object',
49990
+ required: ['message'],
49991
+ properties: { message: { type: 'string' } }
49992
+ },
49993
+ effects: [{ kind: 'merge-object', path: 'behavior.emptyState' }],
49994
+ affectedPaths: ['behavior.emptyState'],
49995
+ submissionImpact: false,
49996
+ preconditions: ['config-initialized']
49997
+ },
49998
+ {
49999
+ operationId: 'behavior.resizing.configure',
50000
+ title: 'Configurar redimensionamento',
50001
+ scope: 'global',
50002
+ targetKind: 'resizing',
50003
+ target: { kind: 'resizing', resolver: 'resizing-config', ambiguityPolicy: 'fail', required: false },
50004
+ inputSchema: {
50005
+ type: 'object',
50006
+ minProperties: 1,
50007
+ properties: {
50008
+ enabled: { type: 'boolean' },
50009
+ minColumnWidth: { type: 'number', minimum: 1 }
50010
+ }
50011
+ },
50012
+ effects: [{ kind: 'merge-object', path: 'behavior.resizing' }],
50013
+ affectedPaths: ['behavior.resizing'],
50014
+ submissionImpact: false,
50015
+ preconditions: ['config-initialized']
50016
+ },
50017
+ {
50018
+ operationId: 'behavior.dragging.configure',
50019
+ title: 'Configurar drag and drop',
50020
+ scope: 'global',
50021
+ targetKind: 'dragging',
50022
+ target: { kind: 'dragging', resolver: 'dragging-config', ambiguityPolicy: 'fail', required: false },
50023
+ inputSchema: {
50024
+ type: 'object',
50025
+ required: ['columns'],
50026
+ properties: { columns: { type: 'boolean' } }
50027
+ },
50028
+ effects: [{ kind: 'merge-object', path: 'behavior.dragging' }],
50029
+ affectedPaths: ['behavior.dragging'],
50030
+ submissionImpact: false,
50031
+ preconditions: ['config-initialized']
50032
+ },
50033
+ {
50034
+ operationId: 'behavior.editing.configure',
50035
+ title: 'Configurar edicao',
50036
+ scope: 'global',
50037
+ targetKind: 'editing',
50038
+ target: { kind: 'editing', resolver: 'editing-config', ambiguityPolicy: 'fail', required: false },
50039
+ inputSchema: {
50040
+ type: 'object',
50041
+ required: ['mode'],
50042
+ properties: { mode: { type: 'string' } }
50043
+ },
50044
+ effects: [{ kind: 'merge-object', path: 'behavior.editing' }],
50045
+ affectedPaths: ['behavior.editing'],
50046
+ submissionImpact: false,
50047
+ preconditions: ['config-initialized']
50048
+ },
50049
+ {
50050
+ operationId: 'toolbar.configure',
50051
+ title: 'Configurar toolbar',
50052
+ scope: 'global',
50053
+ targetKind: 'toolbar',
50054
+ target: { kind: 'toolbar', resolver: 'toolbar-config', ambiguityPolicy: 'fail', required: false },
50055
+ inputSchema: tableToolbarSchema,
50056
+ effects: [{ kind: 'merge-object', path: 'toolbar' }],
50057
+ validators: ['css-style-safe'],
50058
+ affectedPaths: ['toolbar'],
50059
+ submissionImpact: false,
50060
+ preconditions: ['config-initialized']
50061
+ },
50062
+ {
50063
+ operationId: 'actions.row.configure',
50064
+ title: 'Configurar acoes de linha',
50065
+ scope: 'global',
50066
+ targetKind: 'rowAction',
50067
+ target: { kind: 'rowAction', resolver: 'action-in-row-config', ambiguityPolicy: 'fail', required: false },
50068
+ inputSchema: {
50069
+ type: 'object',
50070
+ minProperties: 1,
50071
+ properties: {
50072
+ enabled: { type: 'boolean' },
50073
+ display: { enum: ['menu', 'buttons', 'icons'] },
50074
+ trigger: { enum: ['hover', 'always', 'click'] }
50075
+ }
50076
+ },
50077
+ effects: [{ kind: 'merge-object', path: 'actions.row' }],
50078
+ affectedPaths: ['actions.row'],
50079
+ submissionImpact: false,
50080
+ preconditions: ['config-initialized']
50081
+ },
50082
+ {
50083
+ operationId: 'actions.bulk.configure',
50084
+ title: 'Configurar acoes em lote',
50085
+ scope: 'global',
50086
+ targetKind: 'bulkAction',
50087
+ target: { kind: 'bulkAction', resolver: 'action-in-bulk-config', ambiguityPolicy: 'fail', required: false },
50088
+ inputSchema: {
50089
+ type: 'object',
50090
+ minProperties: 1,
50091
+ properties: {
50092
+ enabled: { type: 'boolean' },
50093
+ position: { enum: ['toolbar', 'floating', 'both'] }
50094
+ }
50095
+ },
50096
+ effects: [{ kind: 'merge-object', path: 'actions.bulk' }],
50097
+ requiresConfirmation: true,
50098
+ affectedPaths: ['actions.bulk'],
50099
+ submissionImpact: false,
50100
+ preconditions: ['config-initialized']
50101
+ },
50102
+ {
50103
+ operationId: 'actions.context.configure',
50104
+ title: 'Configurar menu contextual',
50105
+ scope: 'global',
50106
+ targetKind: 'contextAction',
50107
+ target: { kind: 'contextAction', resolver: 'action-in-context-config', ambiguityPolicy: 'fail', required: false },
50108
+ inputSchema: {
50109
+ type: 'object',
50110
+ minProperties: 1,
50111
+ properties: {
50112
+ enabled: { type: 'boolean' },
50113
+ trigger: { enum: ['right-click', 'long-press', 'both'] }
50114
+ }
50115
+ },
50116
+ effects: [{ kind: 'merge-object', path: 'actions.context' }],
50117
+ requiresConfirmation: true,
50118
+ affectedPaths: ['actions.context'],
50119
+ submissionImpact: false,
50120
+ preconditions: ['config-initialized']
50121
+ },
50122
+ {
50123
+ operationId: 'actions.confirmations.configure',
50124
+ title: 'Configurar confirmacoes',
50125
+ scope: 'global',
50126
+ targetKind: 'contextAction',
50127
+ target: { kind: 'contextAction', resolver: 'action-in-context-config', ambiguityPolicy: 'fail', required: false },
50128
+ inputSchema: {
50129
+ type: 'object',
50130
+ required: ['default'],
50131
+ properties: { default: { type: 'object' } }
50132
+ },
50133
+ effects: [{ kind: 'merge-object', path: 'actions.confirmations' }],
50134
+ requiresConfirmation: true,
50135
+ affectedPaths: ['actions.confirmations.default'],
50136
+ submissionImpact: false,
50137
+ preconditions: ['config-initialized']
50138
+ },
50139
+ {
50140
+ operationId: 'export.configure',
50141
+ title: 'Configurar exportacao',
50142
+ scope: 'global',
50143
+ targetKind: 'export',
50144
+ target: { kind: 'export', resolver: 'export-config', ambiguityPolicy: 'fail', required: false },
50145
+ inputSchema: tableExportSchema,
50146
+ effects: [{ kind: 'merge-object', path: 'export' }],
50147
+ requiresConfirmation: true,
50148
+ affectedPaths: ['export'],
50149
+ submissionImpact: false,
50150
+ preconditions: ['config-initialized']
50151
+ },
50152
+ {
50153
+ operationId: 'appearance.configure',
50154
+ title: 'Configurar aparencia',
50155
+ scope: 'global',
50156
+ targetKind: 'appearance',
50157
+ target: { kind: 'appearance', resolver: 'appearance-config', ambiguityPolicy: 'fail', required: false },
50158
+ inputSchema: tableAppearanceSchema,
50159
+ effects: [{ kind: 'merge-object', path: 'appearance' }],
50160
+ validators: ['css-style-safe'],
50161
+ affectedPaths: ['appearance'],
50162
+ submissionImpact: false,
50163
+ preconditions: ['config-initialized']
50164
+ },
50165
+ {
50166
+ operationId: 'messages.configure',
50167
+ title: 'Configurar mensagens',
50168
+ scope: 'global',
50169
+ targetKind: 'messages',
50170
+ target: { kind: 'messages', resolver: 'messages-config', ambiguityPolicy: 'fail', required: false },
50171
+ inputSchema: tableMessagesSchema,
50172
+ effects: [{ kind: 'merge-object', path: 'messages' }],
50173
+ affectedPaths: ['messages'],
50174
+ submissionImpact: false,
50175
+ preconditions: ['config-initialized']
50176
+ },
50177
+ {
50178
+ operationId: 'localization.configure',
50179
+ title: 'Configurar localizacao',
50180
+ scope: 'global',
50181
+ targetKind: 'localization',
50182
+ target: { kind: 'localization', resolver: 'localization-config', ambiguityPolicy: 'fail', required: false },
50183
+ inputSchema: tableLocalizationSchema,
50184
+ effects: [{ kind: 'merge-object', path: 'localization' }],
50185
+ affectedPaths: ['localization'],
50186
+ submissionImpact: false,
50187
+ preconditions: ['config-initialized']
50188
+ },
50189
+ {
50190
+ operationId: 'performance.configure',
50191
+ title: 'Configurar performance',
50192
+ scope: 'global',
50193
+ targetKind: 'performance',
50194
+ target: { kind: 'performance', resolver: 'performance-config', ambiguityPolicy: 'fail', required: false },
50195
+ inputSchema: tablePerformanceSchema,
50196
+ effects: [{ kind: 'merge-object', path: 'performance' }],
50197
+ affectedPaths: ['performance'],
50198
+ submissionImpact: false,
50199
+ preconditions: ['config-initialized']
50200
+ },
50201
+ {
50202
+ operationId: 'data.loadingStrategy.set',
50203
+ title: 'Definir estrategia de carregamento',
50204
+ scope: 'global',
50205
+ targetKind: 'data',
50206
+ target: { kind: 'data', resolver: 'data-config', ambiguityPolicy: 'fail', required: false },
50207
+ inputSchema: {
50208
+ type: 'object',
50209
+ required: ['loadingStrategy'],
50210
+ properties: { loadingStrategy: { enum: TABLE_LOADING_STRATEGIES } }
50211
+ },
50212
+ effects: [{ kind: 'set-value', path: 'data.loadingStrategy' }],
50213
+ affectedPaths: ['data.loadingStrategy'],
50214
+ submissionImpact: false,
50215
+ preconditions: ['config-initialized']
50216
+ },
50217
+ {
50218
+ operationId: 'accessibility.configure',
50219
+ title: 'Configurar acessibilidade',
50220
+ scope: 'global',
50221
+ targetKind: 'accessibility',
50222
+ target: { kind: 'accessibility', resolver: 'accessibility-config', ambiguityPolicy: 'fail', required: false },
50223
+ inputSchema: {
50224
+ type: 'object',
50225
+ minProperties: 1,
50226
+ properties: {
50227
+ enabled: { type: 'boolean' },
50228
+ highContrast: { type: 'boolean' },
50229
+ reduceMotion: { type: 'boolean' },
50230
+ announcements: {
50231
+ type: 'object',
50232
+ properties: { liveRegion: { enum: LIVE_REGIONS } }
50233
+ },
50234
+ keyboard: {
50235
+ type: 'object',
50236
+ properties: { shortcuts: { type: 'boolean' } }
50237
+ }
50238
+ }
50239
+ },
50240
+ effects: [{ kind: 'merge-object', path: 'accessibility' }],
50241
+ requiresConfirmation: true,
50242
+ affectedPaths: ['accessibility'],
50243
+ submissionImpact: false,
50244
+ preconditions: ['config-initialized']
50245
+ }
50246
+ ],
50247
+ validators: [
50248
+ {
50249
+ validatorId: 'target-column-exists',
50250
+ level: 'error',
50251
+ code: 'TB001',
50252
+ description: 'A coluna alvo deve existir na configuração.'
50253
+ },
50254
+ {
50255
+ validatorId: 'column-field-unique',
50256
+ level: 'error',
50257
+ code: 'TB002',
50258
+ description: 'O identificador técnico da coluna (field) deve ser único.'
50259
+ },
50260
+ {
50261
+ validatorId: 'renderer-type-supported',
50262
+ level: 'error',
50263
+ code: 'TB003',
50264
+ description: 'O tipo de renderizador deve ser suportado pelo componente.'
50265
+ },
50266
+ {
50267
+ validatorId: 'renderer-config-match',
50268
+ level: 'error',
50269
+ code: 'TB004',
50270
+ description: 'A configuração do renderizador deve corresponder ao tipo selecionado.'
50271
+ },
50272
+ {
50273
+ validatorId: 'grouping-fields-exist',
50274
+ level: 'error',
50275
+ code: 'TB005',
50276
+ description: 'Os campos de agrupamento devem existir na definição de colunas.'
50277
+ },
50278
+ {
50279
+ validatorId: 'destructive-removal-confirmation',
50280
+ level: 'error',
50281
+ code: 'TB006',
50282
+ description: 'Remoções destrutivas de colunas ou ações exigem confirmação explícita.'
50283
+ },
50284
+ {
50285
+ validatorId: 'computed-expression-valid',
50286
+ level: 'error',
50287
+ code: 'TB007',
50288
+ description: 'A expressão de cálculo deve ser um AST Json Logic válido.'
50289
+ },
50290
+ {
50291
+ validatorId: 'remote-resource-binding-safe',
50292
+ level: 'error',
50293
+ code: 'TB008',
50294
+ description: 'A ligação com recurso remoto não deve conter URLs absolutas não permitidas.'
50295
+ },
50296
+ {
50297
+ validatorId: 'editor-round-trip-preserve',
50298
+ level: 'error',
50299
+ code: 'TB009',
50300
+ description: 'A edição não deve corromper propriedades críticas necessárias para o round-trip do editor.'
50301
+ },
50302
+ {
50303
+ validatorId: 'toolbar-action-id-unique',
50304
+ level: 'error',
50305
+ code: 'TB010',
50306
+ description: 'O id da ação de toolbar deve ser único em toolbar.actions[]. O efeito append-unique usa este id como chave de deduplicação.'
50307
+ },
50308
+ {
50309
+ validatorId: 'row-action-id-unique',
50310
+ level: 'error',
50311
+ code: 'TB011',
50312
+ description: 'O id da row action deve ser único em actions.row.actions[]. O efeito append-unique usa este id como chave de deduplicação.'
50313
+ },
50314
+ {
50315
+ validatorId: 'format-preset-supported',
50316
+ level: 'error',
50317
+ code: 'TB012',
50318
+ description: 'O formato deve usar um preset canonico publicado em table-ai-capabilities.ts.'
50319
+ },
50320
+ {
50321
+ validatorId: 'column-width-valid',
50322
+ level: 'error',
50323
+ code: 'TB013',
50324
+ description: 'A largura deve ser auto ou uma medida simples em px, %, rem ou em.'
50325
+ },
50326
+ {
50327
+ validatorId: 'css-style-safe',
50328
+ level: 'error',
50329
+ code: 'TB014',
50330
+ description: 'Estilos inline devem permanecer em CSS simples e seguro, sem scripts, URLs perigosas ou expressoes dinamicas.'
50331
+ },
50332
+ {
50333
+ validatorId: 'value-mapping-valid',
50334
+ level: 'error',
50335
+ code: 'TB015',
50336
+ description: 'O mapeamento de valores deve ser um objeto simples com chaves estaveis e labels serializaveis.'
50337
+ },
50338
+ {
50339
+ validatorId: 'conditional-style-valid',
50340
+ level: 'error',
50341
+ code: 'TB016',
50342
+ description: 'Regras condicionais de coluna devem ter id estavel, condition Json Logic e ao menos cssClass ou style.'
50343
+ }
50344
+ ],
50345
+ roundTripRequirements: [
50346
+ 'preserve-column-order',
50347
+ 'normalize-enum-values',
50348
+ 'maintain-renderer-precedence',
50349
+ 'preserve-meta-ids'
50350
+ ],
50351
+ examples: [
50352
+ {
50353
+ id: 'add-schema-column',
50354
+ request: 'Adicionar coluna para o campo email com o título Contato',
50355
+ operationId: 'column.add',
50356
+ params: { field: 'email', header: 'Contato', type: 'string' },
50357
+ isPositive: true
50358
+ },
50359
+ {
50360
+ id: 'add-computed-column',
50361
+ request: 'Criar coluna de idade baseada na data de nascimento',
50362
+ operationId: 'column.computed.add',
50363
+ params: {
50364
+ field: 'idade',
50365
+ header: 'Idade',
50366
+ expression: { "yearsSince": [{ "var": "dataNascimento" }] },
50367
+ outputType: 'number'
50368
+ },
50369
+ isPositive: true
50370
+ },
50371
+ {
50372
+ id: 'add-conditional-badge',
50373
+ request: 'Mostrar badge vermelho na coluna status se o valor for "atrasado"',
50374
+ operationId: 'column.conditionalRenderer.add',
50375
+ target: 'status',
50376
+ params: {
50377
+ id: 'badge-atrasado',
50378
+ condition: { "==": [{ "var": "status" }, "atrasado"] },
50379
+ renderer: { type: 'badge', badge: { color: 'warn', variant: 'filled' } }
50380
+ },
50381
+ isPositive: true
50382
+ },
50383
+ {
50384
+ id: 'enable-export',
50385
+ request: 'Habilitar exportação para Excel e PDF',
50386
+ operationId: 'export.enabled.set',
50387
+ params: { enabled: true, formats: ['excel', 'pdf'] },
50388
+ isPositive: true
50389
+ },
50390
+ {
50391
+ id: 'add-row-action',
50392
+ request: 'Adicionar botão de excluir na linha',
50393
+ operationId: 'rowAction.add',
50394
+ params: { id: 'delete', label: 'Excluir', action: 'delete', icon: 'delete', color: 'warn' },
50395
+ isPositive: true
50396
+ },
50397
+ {
50398
+ id: 'group-by-field',
50399
+ request: 'Agrupar a tabela por departamento',
50400
+ operationId: 'grouping.set',
50401
+ params: { enabled: true, fields: ['departamento'] },
50402
+ isPositive: true
50403
+ },
50404
+ {
50405
+ id: 'reject-invalid-renderer',
50406
+ request: 'Configurar imagem para um renderizador do tipo ícone',
50407
+ operationId: 'column.renderer.set',
50408
+ target: 'avatar',
50409
+ params: { type: 'icon', image: { src: '...' } },
50410
+ isPositive: false
50411
+ },
50412
+ {
50413
+ id: 'set-button-renderer',
50414
+ request: 'Transformar a coluna acoes em botao que executa abrir-detalhe',
50415
+ operationId: 'column.renderer.set',
50416
+ target: 'acoes',
50417
+ params: { type: 'button', button: { label: 'Abrir', action: { id: 'abrir-detalhe' } } },
50418
+ isPositive: true
50419
+ },
50420
+ {
50421
+ id: 'add-column-conditional-style',
50422
+ request: 'Destacar a coluna status quando o valor estiver bloqueado',
50423
+ operationId: 'column.conditionalStyle.add',
50424
+ target: 'status',
50425
+ params: {
50426
+ id: 'status-bloqueado',
50427
+ condition: { "==": [{ "var": "status" }, "bloqueado"] },
50428
+ cssClass: 'status-bloqueado'
50429
+ },
50430
+ isPositive: true
50431
+ },
50432
+ {
50433
+ id: 'set-currency-format-preset',
50434
+ request: 'Formatar a coluna total como moeda brasileira',
50435
+ operationId: 'column.format.set',
50436
+ target: 'total',
50437
+ params: { format: 'BRL|symbol|2' },
50438
+ isPositive: true
50439
+ },
50440
+ {
50441
+ id: 'set-basic-column-width',
50442
+ request: 'Definir a largura da coluna nome para 240px',
50443
+ operationId: 'column.width.set',
50444
+ target: 'nome',
50445
+ params: { width: '240px' },
50446
+ isPositive: true
50447
+ },
50448
+ {
50449
+ id: 'configure-server-pagination',
50450
+ request: 'Usar paginacao no servidor com 50 linhas por pagina',
50451
+ operationId: 'behavior.pagination.configure',
50452
+ params: { enabled: true, strategy: 'server', pageSize: 50, position: 'bottom' },
50453
+ isPositive: true
50454
+ },
50455
+ {
50456
+ id: 'configure-default-sorting',
50457
+ request: 'Ordenar inicialmente por data de criacao decrescente',
50458
+ operationId: 'behavior.sorting.configure',
50459
+ params: { enabled: true, strategy: 'server', defaultSort: { field: 'createdAt', direction: 'desc' } },
50460
+ isPositive: true
50461
+ },
50462
+ {
50463
+ id: 'configure-bulk-actions',
50464
+ request: 'Habilitar acoes em lote na toolbar',
50465
+ operationId: 'actions.bulk.configure',
50466
+ params: { enabled: true, position: 'toolbar' },
50467
+ isPositive: true
50468
+ },
50469
+ {
50470
+ id: 'configure-localization',
50471
+ request: 'Configurar a tabela para portugues do Brasil',
50472
+ operationId: 'localization.configure',
50473
+ params: { locale: 'pt-BR', direction: 'ltr', dateTime: { dateFormat: 'dd/MM/yyyy' } },
50474
+ isPositive: true
50475
+ },
50476
+ {
50477
+ id: 'configure-accessibility',
50478
+ request: 'Ativar alto contraste e reduzir animacoes',
50479
+ operationId: 'accessibility.configure',
50480
+ params: { enabled: true, highContrast: true, reduceMotion: true, announcements: { liveRegion: 'polite' } },
50481
+ isPositive: true
50482
+ },
50483
+ {
50484
+ id: 'require-removal-confirmation',
50485
+ request: 'Remover a coluna nome',
50486
+ operationId: 'column.remove',
50487
+ target: 'nome',
50488
+ isPositive: true
50489
+ }
50490
+ ]
50491
+ };
50492
+
48129
50493
  class PraxisTableInlineAuthoringEditorComponent {
48130
50494
  i18n;
48131
50495
  config = { columns: [] };
@@ -49563,6 +51927,12 @@ const PRAXIS_TABLE_COMPONENT_METADATA = {
49563
51927
  label: 'Mudança de expansão',
49564
51928
  description: 'Emitido ao expandir/colapsar detalhes com contrato discriminado por política de exposição de identificadores',
49565
51929
  },
51930
+ {
51931
+ name: 'selectionChange',
51932
+ type: '{ trigger: string; row?: any; selectedRows: any[]; selectedCount: number; tableId?: string }',
51933
+ label: 'Selecao alterada',
51934
+ description: 'Emitido quando a selecao de linhas muda por interacao do usuario',
51935
+ },
49566
51936
  {
49567
51937
  name: 'rowAction',
49568
51938
  type: '{ action: string; row: any }',
@@ -49847,7 +52217,7 @@ const RENDERER_COMPOSE_ITEM_TYPES = [
49847
52217
  ];
49848
52218
  const ENUMS = {
49849
52219
  align: ['left', 'center', 'right'],
49850
- sticky: ['start', 'end'],
52220
+ sticky: ['start', 'end', true, false],
49851
52221
  columnTypes: ['string', 'number', 'date', 'boolean', 'currency', 'percentage', 'custom'],
49852
52222
  rendererTypes: ['icon', 'image', 'badge', 'link', 'button', 'chip', 'progress', 'avatar', 'toggle', 'menu', 'rating', 'html', 'compose'],
49853
52223
  rendererButtonVariant: ['filled', 'outlined', 'text'],
@@ -49935,7 +52305,7 @@ const TABLE_AI_CAPABILITIES = {
49935
52305
  { path: 'meta.name', category: 'meta', valueKind: 'string', description: 'Nome amigável da configuração para exibição em tooltips ou relatórios.', intentExamples: ['renomear tabela', 'chamar de Funcionários'], example: '"Funcionários ativos"' },
49936
52306
  { path: 'meta.description', category: 'meta', valueKind: 'string', description: 'Descrição breve do propósito da tabela.', intentExamples: ['documentar tabela', 'anotar uso em relatórios'], example: '"Visão dos funcionários ativos com filtros por status."' },
49937
52307
  { path: 'meta.tags', category: 'meta', valueKind: 'array', description: 'Tags para busca, presets e telemetria.', intentExamples: ['adicionar tag demo', 'marcar como financeiro'], example: '["financeiro","demo"]' },
49938
- { path: 'bindings.idField', category: 'bindings', valueKind: 'string', description: 'Campo usado como chave primária operacional da tabela.', critical: true, intentExamples: ['usar employeeId como chave', 'trocar id da linha'], example: '"employeeId"', safetyNotes: 'Trocar idField sem migrar dados pode quebrar seleção e ações por linha.' },
52308
+ { path: 'meta.idField', category: 'meta', valueKind: 'string', description: 'Campo usado como chave primária operacional da tabela.', critical: true, intentExamples: ['usar employeeId como chave', 'trocar id da linha'], example: '"employeeId"', safetyNotes: 'Trocar idField sem migrar dados pode quebrar seleção e ações por linha.' },
49939
52309
  { path: 'meta.schemaId', category: 'meta', valueKind: 'string', description: 'Identidade do schema de origem para reconciliação.', intentExamples: ['registrar schema', 'alinhar com API X'], example: '"path:/employees#response"' },
49940
52310
  // Columns — propriedades base
49941
52311
  { path: 'columns[].field', category: 'columns', valueKind: 'string', description: 'Identificador técnico no dataset. Deve ser único.', critical: true, intentExamples: ['apontar campo source', 'usar field id'], example: '"status"' },
@@ -49944,7 +52314,7 @@ const TABLE_AI_CAPABILITIES = {
49944
52314
  { path: 'columns[].order', category: 'columns', valueKind: 'number', description: 'Ordem visual. Menor número = mais à esquerda.', intentExamples: ['mover para o início', 'trocar ordem', 'colocar depois de'], example: '0' },
49945
52315
  { path: 'columns[].width', category: 'columns', valueKind: 'string', description: 'Largura da coluna (ex.: 100px, 10%, auto).', intentExamples: ['aumentar largura', 'coluna mais estreita'], example: '"140px"' },
49946
52316
  { path: 'columns[].align', category: 'columns', valueKind: 'enum', allowedValues: ENUMS.align, description: 'Alinhamento do conteúdo e cabeçalho.', intentExamples: ['centralizar coluna', 'alinhar à direita'] },
49947
- { path: 'columns[].sticky', category: 'columns', valueKind: 'enum', allowedValues: ENUMS.sticky, description: 'Fixa a coluna no início (start) ou fim (end) da rolagem.', intentExamples: ['congelar coluna', 'fixar à esquerda'], safetyNotes: 'Para sticky booleano simples, use start/end como referência e valide suporte do tema.' },
52317
+ { path: 'columns[].sticky', category: 'columns', valueKind: 'enum', allowedValues: ENUMS.sticky, description: 'Fixa a coluna no início (start/true) ou fim (end) da rolagem; false desativa sticky.', intentExamples: ['congelar coluna', 'fixar à esquerda'], safetyNotes: 'Use start/end para expressar direção quando a coluna fixa precisar de semântica explícita.' },
49948
52318
  { path: 'columns[].sortable', category: 'columns', valueKind: 'boolean', description: 'Habilita ordenação nesta coluna específica.', intentExamples: ['permitir ordenar por', 'bloquear ordenação'] },
49949
52319
  { path: 'columns[].filterable', category: 'columns', valueKind: 'boolean', description: 'Habilita filtro nesta coluna específica.', intentExamples: ['filtrar por', 'adicionar filtro em'] },
49950
52320
  { path: 'columns[].resizable', category: 'columns', valueKind: 'boolean', description: 'Permite redimensionar a largura desta coluna.' },
@@ -51418,4 +53788,4 @@ function isSupportedJsonLogicComputedOperator(operator) {
51418
53788
  * Generated bundle index. Do not edit.
51419
53789
  */
51420
53790
 
51421
- export { AnalyticsTableConfigAdapterService, AnalyticsTableContractService, AnalyticsTableStatsApiService, BOOLEAN_PRESETS, BehaviorConfigEditorComponent, CURRENCY_PRESETS, ColumnsConfigEditorComponent, DATE_PRESETS, DataFormatterComponent, DataFormattingService, FORMULA_TEMPLATES, FilterConfigService, FilterSettingsComponent, FormulaGeneratorService, JsonConfigEditorComponent, MessagesLocalizationEditorComponent, NUMBER_PRESETS, PERCENTAGE_PRESETS, PRAXIS_FILTER_COMPONENT_METADATA, PRAXIS_TABLE_COMPONENT_METADATA, PraxisFilter, PraxisTable, PraxisTableConfigEditor, PraxisTableInlineAuthoringEditorComponent, PraxisTableToolbar, STRING_PRESETS, TABLE_AI_CAPABILITIES, TABLE_COMPONENT_AI_CAPABILITIES, TABLE_COMPONENT_EDIT_PLAN_ALLOWED_CHANGE_KINDS, TABLE_COMPONENT_EDIT_PLAN_BATCH_KIND, TABLE_COMPONENT_EDIT_PLAN_EXPECTED_PATHS, TABLE_COMPONENT_EDIT_PLAN_JSON_SCHEMA, TABLE_COMPONENT_EDIT_PLAN_KIND, TABLE_COMPONENT_EDIT_PLAN_VERSION, TASK_PRESETS, TableDefaultsProvider, TableRulesEditorComponent, ToolbarActionsEditorComponent, ValueMappingEditorComponent, VisualFormulaBuilderComponent, buildTableApplyPlan, coerceTableComponentEditPlan, coerceTableComponentEditPlans, compileTableComponentEditPlan, compileTableComponentEditPlans, createTableAuthoringDocument, getActionId, getEnum, getTableCapabilities, getTableComponentEditPlanCapabilities, isTableRendererSupportedByRichContentP0, mapTableRendererToRichContentP0, normalizeTableAuthoringDocument, parseLegacyOrTableDocument, providePraxisFilterMetadata, providePraxisTableMetadata, serializeTableAuthoringDocument, toCanonicalTableConfig, validateTableAuthoringDocument };
53791
+ export { AnalyticsTableConfigAdapterService, AnalyticsTableContractService, AnalyticsTableStatsApiService, BOOLEAN_PRESETS, BehaviorConfigEditorComponent, CURRENCY_PRESETS, ColumnsConfigEditorComponent, DATE_PRESETS, DataFormatterComponent, DataFormattingService, FORMULA_TEMPLATES, FilterConfigService, FilterSettingsComponent, FormulaGeneratorService, JsonConfigEditorComponent, MessagesLocalizationEditorComponent, NUMBER_PRESETS, PERCENTAGE_PRESETS, PRAXIS_FILTER_COMPONENT_METADATA, PRAXIS_TABLE_AUTHORING_MANIFEST, PRAXIS_TABLE_COMPONENT_METADATA, PraxisFilter, PraxisTable, PraxisTableConfigEditor, PraxisTableInlineAuthoringEditorComponent, PraxisTableToolbar, STRING_PRESETS, TABLE_AI_CAPABILITIES, TABLE_COMPONENT_AI_CAPABILITIES, TABLE_COMPONENT_EDIT_PLAN_ALLOWED_CHANGE_KINDS, TABLE_COMPONENT_EDIT_PLAN_BATCH_KIND, TABLE_COMPONENT_EDIT_PLAN_EXPECTED_PATHS, TABLE_COMPONENT_EDIT_PLAN_JSON_SCHEMA, TABLE_COMPONENT_EDIT_PLAN_KIND, TABLE_COMPONENT_EDIT_PLAN_VERSION, TASK_PRESETS, TableDefaultsProvider, TableRulesEditorComponent, ToolbarActionsEditorComponent, ValueMappingEditorComponent, VisualFormulaBuilderComponent, buildTableApplyPlan, coerceTableComponentEditPlan, coerceTableComponentEditPlans, compileTableComponentEditPlan, compileTableComponentEditPlans, createTableAuthoringDocument, getActionId, getEnum, getTableCapabilities, getTableComponentEditPlanCapabilities, isTableRendererSupportedByRichContentP0, mapTableRendererToRichContentP0, normalizeTableAuthoringDocument, parseLegacyOrTableDocument, providePraxisFilterMetadata, providePraxisTableMetadata, serializeTableAuthoringDocument, toCanonicalTableConfig, validateTableAuthoringDocument };