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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +39 -5
  2. package/docs/DSL-Extensions-Guide.md +23 -0
  3. package/docs/adr/2026-03-dynamic-filter-cross-lib-coupling.md +107 -0
  4. package/docs/adr/2026-03-filter-drawer-adapter-light-entrypoint.md +105 -0
  5. package/docs/adr/2026-03-table-editor-idfield-decision.md +85 -0
  6. package/docs/column-resize-reorder-implementation-plan.md +338 -0
  7. package/docs/column-resize-reorder-review-prompt.md +34 -0
  8. package/docs/dynamic-filter-architecture-overview.md +207 -0
  9. package/docs/dynamic-filter-backend-contract-cheatsheet.md +167 -0
  10. package/docs/dynamic-filter-editor-settings-guide.md +229 -0
  11. package/docs/dynamic-filter-host-integration-guide.md +217 -0
  12. package/docs/dynamic-filter-payload-contract.md +331 -0
  13. package/docs/dynamic-filter-range-filters-guide.md +289 -0
  14. package/docs/dynamic-filter-troubleshooting-guide.md +220 -0
  15. package/docs/dynamic-inline-filter-catalog.md +147 -0
  16. package/docs/e2e-column-drag-playwright.md +62 -0
  17. package/docs/expandable-rows-enterprise-big-leagues-plan.md +1080 -0
  18. package/docs/json-logic-operators-and-helpers.md +57 -0
  19. package/docs/local-data-mode-precedence.md +12 -0
  20. package/docs/local-data-pre-implementation-baseline.md +22 -0
  21. package/docs/local-data-preimplementation-go-no-go.md +39 -0
  22. package/docs/local-data-support-implementation-plan.md +524 -0
  23. package/docs/local-data-support-pr-package.md +66 -0
  24. package/docs/localization-persistence-merge.md +22 -0
  25. package/docs/performance-hardening-v2-implementation-plan.md +479 -0
  26. package/docs/playground-scenario-curation-plan.md +482 -0
  27. package/docs/playground-scenario-second-opinion-prompt.md +121 -0
  28. package/docs/playground-scenario-second-opinion-review.md +234 -0
  29. package/docs/release-notes-p1-hardening.md +76 -0
  30. package/docs/table-authoring-document-completeness-checklist.md +120 -0
  31. package/docs/table-editor-capability-review-prompt.md +349 -0
  32. package/docs/visual-rules-editor-transition.md +29 -0
  33. package/fesm2022/praxisui-table.mjs +301 -14
  34. package/index.d.ts +13 -0
  35. package/package.json +15 -9
  36. package/src/lib/praxis-table.json-api.md +1315 -0
@@ -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, 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, PraxisCollectionExportService, ResourceQuickConnectComponent, translateResourceAvailabilityReason, assertPraxisCollectionExportArtifact, 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, serializeEntityLookupValueForPayload, ComponentMetadataRegistry, PraxisCollectionExportService, ResourceQuickConnectComponent, translateResourceAvailabilityReason, assertPraxisCollectionExportArtifact, 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';
@@ -35857,7 +35857,7 @@ class PraxisFilter {
35857
35857
  cleanFilterPayload(source) {
35858
35858
  const cleaned = {};
35859
35859
  Object.keys(source || {}).forEach((key) => {
35860
- const value = source[key];
35860
+ const value = this.normalizeFilterFieldValue(key, source[key]);
35861
35861
  if (!this.isEffectivelyEmptyFilterValue(value)) {
35862
35862
  cleaned[key] = value;
35863
35863
  }
@@ -35865,11 +35865,43 @@ class PraxisFilter {
35865
35865
  return cleaned;
35866
35866
  }
35867
35867
  upsertDtoValue(key, value) {
35868
- if (this.isEffectivelyEmptyFilterValue(value)) {
35868
+ const normalizedValue = this.normalizeFilterFieldValue(key, value);
35869
+ if (this.isEffectivelyEmptyFilterValue(normalizedValue)) {
35869
35870
  delete this.dto[key];
35870
35871
  return;
35871
35872
  }
35872
- this.dto[key] = value;
35873
+ this.dto[key] = normalizedValue;
35874
+ }
35875
+ normalizeFilterFieldValue(key, value) {
35876
+ const meta = this.getFilterFieldMetaByName(key);
35877
+ if (!this.isEntityLookupFilterField(meta)) {
35878
+ return value;
35879
+ }
35880
+ return serializeEntityLookupValueForPayload(value, {
35881
+ payloadMode: meta?.payloadMode,
35882
+ multiple: meta?.multiple === true,
35883
+ entityType: String(meta?.optionSource?.entityKey || '').trim() || undefined,
35884
+ });
35885
+ }
35886
+ getFilterFieldMetaByName(fieldName) {
35887
+ const normalizedFieldName = String(fieldName || '').trim();
35888
+ if (!normalizedFieldName)
35889
+ return null;
35890
+ return (this.schemaMetas?.find((meta) => meta.name === normalizedFieldName) ||
35891
+ (this.fieldMetadata || []).find((meta) => meta.name === normalizedFieldName) ||
35892
+ this.getEditableFieldMeta(normalizedFieldName)) ?? null;
35893
+ }
35894
+ isEntityLookupFilterField(meta) {
35895
+ if (!meta)
35896
+ return false;
35897
+ const controlType = meta.controlType;
35898
+ const normalizedControlType = String(controlType || '').toLowerCase();
35899
+ const controlTypeToken = normalizeControlTypeToken(controlType);
35900
+ return (controlType === FieldControlType.ENTITY_LOOKUP ||
35901
+ normalizedControlType === 'entitylookup' ||
35902
+ controlTypeToken === 'entitylookup' ||
35903
+ controlTypeToken === normalizeControlTypeToken(INLINE_ENTITY_LOOKUP_CONTROL_TYPE) ||
35904
+ meta?.entityLookup === true);
35873
35905
  }
35874
35906
  isEffectivelyEmptyFilterValue(value) {
35875
35907
  if (value === null || value === undefined)
@@ -41638,6 +41670,13 @@ class PraxisTable {
41638
41670
  const payload = Object.prototype.hasOwnProperty.call(runtimeOptions || {}, 'payload')
41639
41671
  ? runtimeOptions?.payload
41640
41672
  : { row };
41673
+ const resolvedGlobalAction = this.resolveConfiguredGlobalActionRef(actionConfig.globalAction, {
41674
+ sourceId: this.tableId || this.componentInstanceId || 'praxis-table',
41675
+ output: 'rowAction',
41676
+ payload,
41677
+ runtime: { row },
41678
+ meta: { actionId: action, actionConfig },
41679
+ });
41641
41680
  await this.executeConfiguredGlobalAction(actionConfig, {
41642
41681
  sourceId: this.tableId || this.componentInstanceId || 'praxis-table',
41643
41682
  output: 'rowAction',
@@ -41796,7 +41835,7 @@ class PraxisTable {
41796
41835
  const ref = actionConfig?.globalAction;
41797
41836
  if (!ref)
41798
41837
  return false;
41799
- const result = await this.globalActions.executeRef(ref, context);
41838
+ const result = await this.globalActions.executeRef(this.resolveConfiguredGlobalActionRef(ref, context) || ref, context);
41800
41839
  if (!result.success) {
41801
41840
  this.warnLog('[PraxisTable] global action execution failed', { actionId: ref.actionId, error: result.error }, { actionId: ref.actionId });
41802
41841
  this.showActionFeedbackMessage('errors', ref.actionId, {
@@ -41808,6 +41847,70 @@ class PraxisTable {
41808
41847
  }
41809
41848
  return true;
41810
41849
  }
41850
+ resolveConfiguredGlobalActionRef(ref, context) {
41851
+ if (!ref || ref.payloadExpr !== undefined || !Object.prototype.hasOwnProperty.call(ref, 'payload')) {
41852
+ return ref;
41853
+ }
41854
+ return {
41855
+ ...ref,
41856
+ payload: this.resolveGlobalActionPayload(ref.payload, context),
41857
+ };
41858
+ }
41859
+ resolveGlobalActionPayload(raw, context) {
41860
+ const root = this.buildGlobalActionTemplateContext(context);
41861
+ return this.resolveGlobalActionTemplateValue(raw, root);
41862
+ }
41863
+ buildGlobalActionTemplateContext(context) {
41864
+ return {
41865
+ context,
41866
+ payload: context?.payload,
41867
+ runtime: context?.runtime,
41868
+ meta: context?.meta,
41869
+ pageContext: context?.pageContext,
41870
+ row: context?.runtime?.row,
41871
+ item: context?.runtime?.item,
41872
+ selection: context?.runtime?.selection,
41873
+ formData: context?.runtime?.formData,
41874
+ value: context?.runtime?.value,
41875
+ state: context?.runtime?.state,
41876
+ actionConfig: context?.meta?.actionConfig,
41877
+ actionId: context?.meta?.actionId,
41878
+ };
41879
+ }
41880
+ resolveGlobalActionTemplateValue(raw, ctx, key) {
41881
+ if (this.isDeferredTemplateExpressionKey(key) && typeof raw === 'string') {
41882
+ return raw;
41883
+ }
41884
+ if (typeof raw === 'string') {
41885
+ return this.interpolateGlobalActionTemplate(raw, ctx);
41886
+ }
41887
+ if (Array.isArray(raw)) {
41888
+ return raw.map((value) => this.resolveGlobalActionTemplateValue(value, ctx, key));
41889
+ }
41890
+ if (raw && typeof raw === 'object') {
41891
+ const out = {};
41892
+ for (const [childKey, value] of Object.entries(raw)) {
41893
+ out[childKey] = this.resolveGlobalActionTemplateValue(value, ctx, childKey);
41894
+ }
41895
+ return out;
41896
+ }
41897
+ return raw;
41898
+ }
41899
+ isDeferredTemplateExpressionKey(key) {
41900
+ if (!key)
41901
+ return false;
41902
+ return key === 'expr' || key.endsWith('Expr');
41903
+ }
41904
+ interpolateGlobalActionTemplate(template, ctx) {
41905
+ const single = String(template || '').match(/^\$\{([^}]+)\}$/);
41906
+ if (single) {
41907
+ return this.getNestedPropertyValue(ctx, single[1].trim());
41908
+ }
41909
+ return String(template || '').replace(/\$\{([^}]+)\}/g, (_match, path) => {
41910
+ const value = this.getNestedPropertyValue(ctx, String(path).trim());
41911
+ return value == null ? '' : String(value);
41912
+ });
41913
+ }
41811
41914
  shouldEmitLocalForGlobalAction(actionConfig) {
41812
41915
  return actionConfig?.emitLocal === true || actionConfig?.globalAction?.meta?.emitLocal === true;
41813
41916
  }
@@ -43808,7 +43911,7 @@ class PraxisTable {
43808
43911
  // Only verify when we already have columns configured (bootstrap handled by loadSchema)
43809
43912
  if ((this.config?.columns?.length ?? 0) === 0)
43810
43913
  return;
43811
- // Build request to /schemas/filtered for grid (path=/.../all, operation=get, schemaType=response)
43914
+ // Build request to /schemas/filtered for grid using the governed search/list surface.
43812
43915
  const baseUrl = this.crudService.getSchemasFilteredBaseUrl();
43813
43916
  let derived = '';
43814
43917
  try {
@@ -43818,8 +43921,8 @@ class PraxisTable {
43818
43921
  return;
43819
43922
  }
43820
43923
  const u = new URL(baseUrl);
43821
- u.searchParams.set('path', `${derived}/all`);
43822
- u.searchParams.set('operation', 'get');
43924
+ u.searchParams.set('path', `${derived}/filter`);
43925
+ u.searchParams.set('operation', 'post');
43823
43926
  u.searchParams.set('schemaType', 'response');
43824
43927
  u.searchParams.set('includeInternalSchemas', 'false');
43825
43928
  const serverHash = this.runtimeSchemaMeta.serverHash;
@@ -44850,7 +44953,68 @@ class PraxisTable {
44850
44953
  this.errorLog('PTABLE:format:error', { error, value, column });
44851
44954
  }
44852
44955
  }
44853
- return value;
44956
+ return this.normalizeReadonlyEntityLookupCellValue(value);
44957
+ }
44958
+ normalizeReadonlyEntityLookupCellValue(value) {
44959
+ if (Array.isArray(value)) {
44960
+ const normalizedItems = value
44961
+ .map((item) => this.toEntityLookupDisplayToken(item))
44962
+ .filter((item) => !!item);
44963
+ const hasStructuredEntries = value.some((item) => this.isEntityLookupDisplayCandidate(item));
44964
+ if (hasStructuredEntries && normalizedItems.length > 0) {
44965
+ return normalizedItems.join(', ');
44966
+ }
44967
+ return value;
44968
+ }
44969
+ const normalized = this.toEntityLookupDisplayToken(value);
44970
+ return normalized ?? value;
44971
+ }
44972
+ toEntityLookupDisplayToken(value) {
44973
+ if (!this.isEntityLookupDisplayCandidate(value)) {
44974
+ return null;
44975
+ }
44976
+ const record = value;
44977
+ const extra = record['extra'] && typeof record['extra'] === 'object' && !Array.isArray(record['extra'])
44978
+ ? record['extra']
44979
+ : null;
44980
+ const code = this.toEntityLookupDisplayPrimitive(record['code'] ?? extra?.['code'] ?? record['resourceKey'] ?? record['externalId']);
44981
+ const label = this.toEntityLookupDisplayPrimitive(record['label']
44982
+ ?? record['displayName']
44983
+ ?? record['name']
44984
+ ?? record['title']
44985
+ ?? extra?.['label']);
44986
+ const id = this.toEntityLookupDisplayPrimitive(record['id']);
44987
+ if (code && label && code !== label) {
44988
+ return `${code} · ${label}`;
44989
+ }
44990
+ return label || code || id || null;
44991
+ }
44992
+ isEntityLookupDisplayCandidate(value) {
44993
+ if (!value || typeof value !== 'object' || Array.isArray(value) || value instanceof Date) {
44994
+ return false;
44995
+ }
44996
+ const record = value;
44997
+ return (Object.prototype.hasOwnProperty.call(record, 'label') ||
44998
+ Object.prototype.hasOwnProperty.call(record, 'displayName') ||
44999
+ Object.prototype.hasOwnProperty.call(record, 'name') ||
45000
+ Object.prototype.hasOwnProperty.call(record, 'title') ||
45001
+ Object.prototype.hasOwnProperty.call(record, 'id') ||
45002
+ Object.prototype.hasOwnProperty.call(record, 'type') ||
45003
+ Object.prototype.hasOwnProperty.call(record, 'code') ||
45004
+ Object.prototype.hasOwnProperty.call(record, 'extra'));
45005
+ }
45006
+ toEntityLookupDisplayPrimitive(value) {
45007
+ if (value === null || value === undefined) {
45008
+ return null;
45009
+ }
45010
+ if (typeof value === 'string') {
45011
+ const trimmed = value.trim();
45012
+ return trimmed ? trimmed : null;
45013
+ }
45014
+ if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
45015
+ return String(value);
45016
+ }
45017
+ return null;
44854
45018
  }
44855
45019
  resolveImplicitFormat(column) {
44856
45020
  if (!column?.type || !supportsImplicitValuePresentation(column.type)) {
@@ -49336,7 +49500,11 @@ const tableFilteringSchema = {
49336
49500
  enabled: { type: 'boolean' },
49337
49501
  settings: {
49338
49502
  type: 'object',
49339
- properties: { mode: { enum: ['filter'] } }
49503
+ properties: {
49504
+ mode: { enum: ['filter'] },
49505
+ showAdvanced: { type: 'boolean' },
49506
+ allowSaveTags: { type: 'boolean' }
49507
+ }
49340
49508
  }
49341
49509
  }
49342
49510
  },
@@ -50200,7 +50368,26 @@ const PRAXIS_TABLE_AUTHORING_MANIFEST = {
50200
50368
  label: { type: 'string' },
50201
50369
  action: { type: 'string' },
50202
50370
  icon: { type: 'string' },
50203
- type: { enum: ['button', 'icon', 'menu'], default: 'button' }
50371
+ type: { enum: ['button', 'icon', 'menu'], default: 'button' },
50372
+ globalAction: {
50373
+ type: 'object',
50374
+ properties: {
50375
+ actionId: {
50376
+ type: 'string',
50377
+ enum: [
50378
+ 'toast.success',
50379
+ 'toast.error',
50380
+ 'dialog.alert',
50381
+ 'dialog.open',
50382
+ 'navigation.openExternal',
50383
+ 'navigation.openRoute',
50384
+ 'api.post',
50385
+ 'api.patch',
50386
+ ],
50387
+ },
50388
+ payload: { type: 'object' },
50389
+ },
50390
+ },
50204
50391
  }
50205
50392
  },
50206
50393
  effects: [{ kind: 'append-unique', path: 'toolbar.actions[]', key: 'id' }],
@@ -50223,7 +50410,26 @@ const PRAXIS_TABLE_AUTHORING_MANIFEST = {
50223
50410
  label: { type: 'string' },
50224
50411
  action: { type: 'string' },
50225
50412
  icon: { type: 'string' },
50226
- color: { type: 'string' }
50413
+ color: { type: 'string' },
50414
+ globalAction: {
50415
+ type: 'object',
50416
+ properties: {
50417
+ actionId: {
50418
+ type: 'string',
50419
+ enum: [
50420
+ 'toast.success',
50421
+ 'toast.error',
50422
+ 'dialog.alert',
50423
+ 'dialog.open',
50424
+ 'navigation.openExternal',
50425
+ 'navigation.openRoute',
50426
+ 'api.post',
50427
+ 'api.patch',
50428
+ ],
50429
+ },
50430
+ payload: { type: 'object' },
50431
+ },
50432
+ },
50227
50433
  }
50228
50434
  },
50229
50435
  effects: [{ kind: 'append-unique', path: 'actions.row.actions[]', key: 'id' }],
@@ -50232,6 +50438,48 @@ const PRAXIS_TABLE_AUTHORING_MANIFEST = {
50232
50438
  submissionImpact: 'config-only',
50233
50439
  preconditions: ['config-initialized']
50234
50440
  },
50441
+ {
50442
+ operationId: 'bulkAction.add',
50443
+ title: 'Adicionar ação em lote',
50444
+ scope: 'global',
50445
+ targetKind: 'bulkAction',
50446
+ target: { kind: 'bulkAction', resolver: 'action-in-bulk-config', ambiguityPolicy: 'fail', required: false },
50447
+ inputSchema: {
50448
+ type: 'object',
50449
+ required: ['id', 'label', 'action'],
50450
+ properties: {
50451
+ id: { type: 'string' },
50452
+ label: { type: 'string' },
50453
+ action: { type: 'string' },
50454
+ icon: { type: 'string' },
50455
+ color: { type: 'string' },
50456
+ globalAction: {
50457
+ type: 'object',
50458
+ properties: {
50459
+ actionId: {
50460
+ type: 'string',
50461
+ enum: [
50462
+ 'toast.success',
50463
+ 'toast.error',
50464
+ 'dialog.alert',
50465
+ 'dialog.open',
50466
+ 'navigation.openExternal',
50467
+ 'navigation.openRoute',
50468
+ 'api.post',
50469
+ 'api.patch',
50470
+ ],
50471
+ },
50472
+ payload: { type: 'object' },
50473
+ },
50474
+ },
50475
+ }
50476
+ },
50477
+ effects: [{ kind: 'append-unique', path: 'actions.bulk.actions[]', key: 'id' }],
50478
+ validators: ['bulk-action-id-unique'],
50479
+ affectedPaths: ['actions.bulk.actions[]'],
50480
+ submissionImpact: 'config-only',
50481
+ preconditions: ['config-initialized']
50482
+ },
50235
50483
  // --- BEHAVIOR & PERFORMANCE ---
50236
50484
  {
50237
50485
  operationId: 'export.enabled.set',
@@ -50264,8 +50512,16 @@ const PRAXIS_TABLE_AUTHORING_MANIFEST = {
50264
50512
  required: ['enabled'],
50265
50513
  properties: {
50266
50514
  enabled: { type: 'boolean' },
50267
- showAdvanced: { type: 'boolean' },
50268
- allowSaveTags: { type: 'boolean' }
50515
+ queryBuilder: { type: 'boolean' },
50516
+ savePresets: { type: 'boolean' },
50517
+ settings: {
50518
+ type: 'object',
50519
+ properties: {
50520
+ mode: { enum: ['filter'] },
50521
+ showAdvanced: { type: 'boolean' },
50522
+ allowSaveTags: { type: 'boolean' }
50523
+ }
50524
+ }
50269
50525
  }
50270
50526
  },
50271
50527
  effects: [
@@ -51001,6 +51257,12 @@ const PRAXIS_TABLE_AUTHORING_MANIFEST = {
51001
51257
  code: 'TB011',
51002
51258
  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.'
51003
51259
  },
51260
+ {
51261
+ validatorId: 'bulk-action-id-unique',
51262
+ level: 'error',
51263
+ code: 'TB017',
51264
+ description: 'O id da ação em lote deve ser único em actions.bulk.actions[]. O efeito append-unique usa este id como chave de deduplicação.'
51265
+ },
51004
51266
  {
51005
51267
  validatorId: 'format-preset-supported',
51006
51268
  level: 'error',
@@ -51084,6 +51346,25 @@ const PRAXIS_TABLE_AUTHORING_MANIFEST = {
51084
51346
  params: { id: 'delete', label: 'Excluir', action: 'delete', icon: 'delete', color: 'warn' },
51085
51347
  isPositive: true
51086
51348
  },
51349
+ {
51350
+ id: 'add-row-action-open-route',
51351
+ request: 'Adicionar botão de linha para abrir detalhe com navegação interna',
51352
+ operationId: 'rowAction.add',
51353
+ params: {
51354
+ id: 'details',
51355
+ label: 'Detalhes',
51356
+ action: 'navigation.openRoute',
51357
+ icon: 'open_in_new',
51358
+ globalAction: {
51359
+ actionId: 'navigation.openRoute',
51360
+ payload: {
51361
+ path: '/clientes/detalhe',
51362
+ query: { id: '${row.id}' }
51363
+ }
51364
+ }
51365
+ },
51366
+ isPositive: true
51367
+ },
51087
51368
  {
51088
51369
  id: 'group-by-field',
51089
51370
  request: 'Agrupar a tabela por departamento',
@@ -53404,11 +53685,17 @@ const TABLE_AI_CAPABILITIES = {
53404
53685
  { path: 'actions.row.display', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.rowActionDisplay, description: 'Modo de exibição das ações (menu/buttons/icons).', dependsOn: 'actions.row.enabled' },
53405
53686
  { path: 'actions.row.trigger', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.rowActionTrigger, description: 'Como as ações aparecem (hover/always/click).', dependsOn: 'actions.row.enabled' },
53406
53687
  { path: 'actions.row.discovery.enabled', category: 'actions', valueKind: 'boolean', description: 'Controla se a tabela enriquece ações por linha com discovery contextual de HATEOAS/capabilities.', dependsOn: 'actions.row.enabled', safetyNotes: 'Use false quando a experiência precisar mostrar apenas ações curadas pelo host.' },
53688
+ { path: 'actions.row.actions[].globalAction.[actionId]', category: 'actions', valueKind: 'string', description: 'Identificador canônico de global action para a ação por linha (ex.: "navigation.openRoute", "surface.open", "toast.success").', dependsOn: 'actions.row.enabled', intentExamples: ['navegar para detalhe interno ao clicar na linha', 'abrir drawer com surface.open', 'mostrar toast de sucesso'] },
53689
+ { path: 'actions.row.actions[].globalAction.payload', category: 'actions', valueKind: 'object', description: 'Payload estruturado da global action por linha. Para `navigation.openRoute`, templates como `${row.id}` são resolvidos antes da navegação; para ações como `surface.open`, `payload.*` continua sendo o envelope canônico do evento consumido pelo destino.', dependsOn: 'actions.row.actions[].globalAction.[actionId]', intentExamples: ['abrir detalhe com query.id da linha', 'passar state.selectedId usando a linha atual'], example: '{ "path": "/clientes/detalhe", "query": { "id": "${row.id}" }, "state": { "selectedId": "${row.id}" } }' },
53407
53690
  { path: 'actions.bulk.enabled', category: 'actions', valueKind: 'boolean', description: 'Habilita ações em lote.', critical: true, intentExamples: ['ativar ações em lote', 'permitir excluir em massa'], safetyNotes: 'Combine com selection.persistSelection e limites de maxSelections.' },
53408
53691
  { path: 'actions.bulk.position', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.bulkActionPosition, description: 'Posição das ações em lote.', dependsOn: 'actions.bulk.enabled' },
53692
+ { path: 'actions.bulk.actions[].globalAction.[actionId]', category: 'actions', valueKind: 'string', description: 'Identificador canônico de global action para a ação em lote.', dependsOn: 'actions.bulk.enabled', intentExamples: ['disparar ação global com a seleção atual', 'abrir surface corporativa a partir do bulk action'] },
53693
+ { path: 'actions.bulk.actions[].globalAction.payload', category: 'actions', valueKind: 'object', description: 'Payload estruturado da global action em lote. Deve permanecer alinhado ao schema da action escolhida e ao contexto da seleção atual.', dependsOn: 'actions.bulk.actions[].globalAction.[actionId]' },
53409
53694
  { path: 'actions.context.enabled', category: 'actions', valueKind: 'boolean', description: 'Habilita menu de contexto (clique direito/long press).', critical: true, intentExamples: ['ativar menu de contexto'] },
53410
53695
  { path: 'actions.context.trigger', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.contextTrigger, description: 'Trigger para abrir o menu de contexto.', dependsOn: 'actions.context.enabled' },
53411
53696
  { path: 'actions.confirmations.default', category: 'actions', valueKind: 'object', description: 'Texto padrão de confirmação para ações perigosas.', critical: true, example: '{ title: "Confirmar exclusão", message: "Deseja remover o item?", confirmText: "Remover" }' },
53697
+ { path: 'toolbar.actions[].globalAction.[actionId]', category: 'toolbar', valueKind: 'string', description: 'Identificador canônico de global action para ações da toolbar (ex.: "navigation.openRoute", "surface.open", "dialog.open").', dependsOn: 'toolbar.visible', intentExamples: ['abrir rota interna pela toolbar', 'abrir surface pela toolbar'] },
53698
+ { path: 'toolbar.actions[].globalAction.payload', category: 'toolbar', valueKind: 'object', description: 'Payload estruturado da global action da toolbar. Para `surface.open`, preserve a semântica canônica do catálogo compartilhado: `payload.*` representa o envelope do evento e `runtime.*` referencia estado exposto diretamente pelo host.', dependsOn: 'toolbar.actions[].globalAction.[actionId]' },
53412
53699
  // Export
53413
53700
  { path: 'export.enabled', category: 'export', valueKind: 'boolean', description: 'Habilita funcionalidades de exportação.', critical: true, intentExamples: ['permitir exportar', 'desativar exportação'], safetyNotes: 'Avaliar políticas de dados sensíveis antes de habilitar.' },
53414
53701
  { path: 'export.formats', category: 'export', valueKind: 'array', description: 'Formatos permitidos.', allowedValues: ENUMS.exportFormat, example: '["csv","excel"]', intentExamples: ['exportar para excel', 'download em csv'] },
package/index.d.ts CHANGED
@@ -744,6 +744,9 @@ declare class PraxisFilter implements OnInit, OnChanges, AfterViewInit, OnDestro
744
744
  private persistTags;
745
745
  private cleanFilterPayload;
746
746
  private upsertDtoValue;
747
+ private normalizeFilterFieldValue;
748
+ private getFilterFieldMetaByName;
749
+ private isEntityLookupFilterField;
747
750
  private isEffectivelyEmptyFilterValue;
748
751
  private syncFormsToDto;
749
752
  private updateDisplayedTags;
@@ -1380,6 +1383,12 @@ declare class PraxisTable implements OnInit, OnChanges, AfterViewInit, AfterCont
1380
1383
  private showActionFeedbackMessage;
1381
1384
  private emitEventWithActionFeedback;
1382
1385
  private executeConfiguredGlobalAction;
1386
+ private resolveConfiguredGlobalActionRef;
1387
+ private resolveGlobalActionPayload;
1388
+ private buildGlobalActionTemplateContext;
1389
+ private resolveGlobalActionTemplateValue;
1390
+ private isDeferredTemplateExpressionKey;
1391
+ private interpolateGlobalActionTemplate;
1383
1392
  private shouldEmitLocalForGlobalAction;
1384
1393
  private resolveBulkValidationMessage;
1385
1394
  private hasValidBulkSelectionCount;
@@ -1554,6 +1563,10 @@ declare class PraxisTable implements OnInit, OnChanges, AfterViewInit, AfterCont
1554
1563
  * 3. format (data formatting like dates, numbers, currency)
1555
1564
  */
1556
1565
  getCellValue(rowData: any, column: ColumnDefinition): any;
1566
+ private normalizeReadonlyEntityLookupCellValue;
1567
+ private toEntityLookupDisplayToken;
1568
+ private isEntityLookupDisplayCandidate;
1569
+ private toEntityLookupDisplayPrimitive;
1557
1570
  private resolveImplicitFormat;
1558
1571
  getCellClasses(rowData: any, column: ColumnDefinition): string[] | undefined;
1559
1572
  getCellNgStyle(rowData: any, column: ColumnDefinition): Record<string, string> | undefined;
package/package.json CHANGED
@@ -1,18 +1,24 @@
1
1
  {
2
2
  "name": "@praxisui/table",
3
- "version": "8.0.0-beta.19",
3
+ "version": "8.0.0-beta.20",
4
4
  "description": "Advanced data table for Angular (Praxis UI) with editing, filtering, sorting, virtualization, and settings panel integration.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^20.0.0",
7
7
  "@angular/core": "^20.0.0",
8
- "@praxisui/ai": "^8.0.0-beta.19",
9
- "@praxisui/core": "^8.0.0-beta.19",
10
- "@praxisui/dynamic-fields": "^8.0.0-beta.19",
11
- "@praxisui/dynamic-form": "^8.0.0-beta.19",
12
- "@praxisui/metadata-editor": "^8.0.0-beta.19",
13
- "@praxisui/rich-content": "^8.0.0-beta.19",
14
- "@praxisui/settings-panel": "^8.0.0-beta.19",
15
- "@praxisui/table-rule-builder": "^8.0.0-beta.19"
8
+ "@praxisui/ai": "^8.0.0-beta.20",
9
+ "@praxisui/core": "^8.0.0-beta.20",
10
+ "@praxisui/dynamic-fields": "^8.0.0-beta.20",
11
+ "@praxisui/dynamic-form": "^8.0.0-beta.20",
12
+ "@praxisui/metadata-editor": "^8.0.0-beta.20",
13
+ "@praxisui/rich-content": "^8.0.0-beta.20",
14
+ "@praxisui/settings-panel": "^8.0.0-beta.20",
15
+ "@praxisui/table-rule-builder": "^8.0.0-beta.20",
16
+ "@angular/cdk": "^20.0.0",
17
+ "@angular/forms": "^20.0.0",
18
+ "@angular/material": "^20.0.0",
19
+ "@angular/router": "^20.0.0",
20
+ "@praxisui/dialog": "^8.0.0-beta.20",
21
+ "rxjs": "~7.8.0"
16
22
  },
17
23
  "dependencies": {
18
24
  "tslib": "^2.3.0"