@praxisui/table 1.0.0-beta.60 → 1.0.0-beta.63

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.
@@ -2594,33 +2594,389 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2594
2594
  type: Output
2595
2595
  }] } });
2596
2596
 
2597
+ const DOCUMENT_KIND = 'praxis.table.editor';
2598
+ const DOCUMENT_VERSION = 1;
2599
+ const HORIZONTAL_SCROLL_VALUES = ['auto', 'wrap', 'none'];
2600
+ function createTableAuthoringDocument(source) {
2601
+ const doc = {
2602
+ kind: DOCUMENT_KIND,
2603
+ version: DOCUMENT_VERSION,
2604
+ config: asTableConfig(source?.config),
2605
+ };
2606
+ if (hasOwnProperty(source, 'bindings')) {
2607
+ doc.bindings = source?.bindings;
2608
+ }
2609
+ return normalizeTableAuthoringDocument(doc);
2610
+ }
2611
+ function parseLegacyOrTableDocument(raw) {
2612
+ const obj = asRecord(raw);
2613
+ if (obj.kind === DOCUMENT_KIND && obj.version === DOCUMENT_VERSION) {
2614
+ return normalizeTableAuthoringDocument({
2615
+ kind: DOCUMENT_KIND,
2616
+ version: DOCUMENT_VERSION,
2617
+ config: asTableConfig(obj.config),
2618
+ bindings: obj.bindings,
2619
+ });
2620
+ }
2621
+ if (looksLikeLegacyPayload(obj)) {
2622
+ return normalizeTableAuthoringDocument({
2623
+ kind: DOCUMENT_KIND,
2624
+ version: DOCUMENT_VERSION,
2625
+ config: extractLegacyTableConfig(obj),
2626
+ bindings: {
2627
+ resourcePath: normalizeResourcePath(obj.__resourcePath__),
2628
+ idField: normalizeIdField(obj.__idField__),
2629
+ horizontalScroll: normalizeHorizontalScroll(obj.__horizontalScroll__),
2630
+ },
2631
+ });
2632
+ }
2633
+ return createTableAuthoringDocument({ config: raw });
2634
+ }
2635
+ function normalizeTableAuthoringDocument(doc) {
2636
+ const normalized = {
2637
+ kind: DOCUMENT_KIND,
2638
+ version: DOCUMENT_VERSION,
2639
+ config: ensureAuthoringCompleteness(normalizeTableConfig(doc?.config)),
2640
+ };
2641
+ if (hasOwnProperty(doc, 'bindings')) {
2642
+ normalized.bindings = normalizeBindings(doc?.bindings);
2643
+ }
2644
+ return normalized;
2645
+ }
2646
+ function validateTableAuthoringDocument(doc, context) {
2647
+ const raw = asRecord(doc);
2648
+ const rawConfig = asRecord(raw.config);
2649
+ const rawBindings = asRecord(raw.bindings);
2650
+ const normalized = normalizeTableAuthoringDocument(doc);
2651
+ const diagnostics = [];
2652
+ if ('columns' in rawConfig && !Array.isArray(rawConfig.columns)) {
2653
+ diagnostics.push(errorDiagnostic('table.config.columns.invalid', 'config.columns must be an array', 'config.columns'));
2654
+ return diagnostics;
2655
+ }
2656
+ const rawColumns = Array.isArray(rawConfig.columns)
2657
+ ? rawConfig.columns
2658
+ : normalized.config.columns;
2659
+ rawColumns.forEach((column, index) => {
2660
+ if (!trimString(column?.field)) {
2661
+ diagnostics.push(errorDiagnostic('table.config.columns.field.required', 'Column field is required', `config.columns[${index}].field`));
2662
+ }
2663
+ });
2664
+ const bindings = normalized.bindings || {};
2665
+ if ('horizontalScroll' in rawBindings &&
2666
+ rawBindings.horizontalScroll !== undefined &&
2667
+ normalizeHorizontalScroll(rawBindings.horizontalScroll) === undefined) {
2668
+ diagnostics.push(errorDiagnostic('table.bindings.horizontalScroll.invalid', 'horizontalScroll must be auto, wrap or none', 'bindings.horizontalScroll'));
2669
+ }
2670
+ if (bindings.horizontalScroll &&
2671
+ !HORIZONTAL_SCROLL_VALUES.includes(bindings.horizontalScroll)) {
2672
+ diagnostics.push(errorDiagnostic('table.bindings.horizontalScroll.invalid', 'horizontalScroll must be auto, wrap or none', 'bindings.horizontalScroll'));
2673
+ }
2674
+ const effectiveMode = resolveEffectiveDataMode(normalized, context);
2675
+ if (effectiveMode === 'local') {
2676
+ if (normalized.config.behavior?.pagination?.strategy === 'server') {
2677
+ diagnostics.push(warnDiagnostic('table.behavior.pagination.strategy.invalid-for-local', 'Server pagination will be projected to client in local mode', 'config.behavior.pagination.strategy'));
2678
+ }
2679
+ if (normalized.config.behavior?.sorting?.strategy === 'server') {
2680
+ diagnostics.push(warnDiagnostic('table.behavior.sorting.strategy.invalid-for-local', 'Server sorting will be projected to client in local mode', 'config.behavior.sorting.strategy'));
2681
+ }
2682
+ }
2683
+ if (context?.server?.idField &&
2684
+ bindings.idField &&
2685
+ context.server.idField !== bindings.idField) {
2686
+ diagnostics.push(warnDiagnostic('table.bindings.idField.diverges-from-server', 'bindings.idField diverges from current server idField', 'bindings.idField'));
2687
+ }
2688
+ const configHash = trimString(normalized.config?.meta?.serverHash);
2689
+ if (context?.server?.schemaHash && !configHash) {
2690
+ diagnostics.push(infoDiagnostic('table.meta.serverHash.snapshot-missing', 'No persisted schema snapshot is available for reconciliation with the current server schema hash', 'config.meta.serverHash'));
2691
+ }
2692
+ if (context?.server?.schemaHash && configHash && context.server.schemaHash !== configHash) {
2693
+ diagnostics.push(warnDiagnostic('table.meta.serverHash.diverges-from-server', 'config.meta.serverHash snapshot diverges from current server schema hash', 'config.meta.serverHash'));
2694
+ }
2695
+ return diagnostics;
2696
+ }
2697
+ function toCanonicalTableConfig(doc, context) {
2698
+ const normalized = normalizeTableAuthoringDocument(doc);
2699
+ const cfg = cloneJson(normalized.config);
2700
+ const effectiveMode = resolveEffectiveDataMode(normalized, context);
2701
+ if (effectiveMode === 'local') {
2702
+ if (cfg.behavior?.pagination?.strategy === 'server') {
2703
+ cfg.behavior = {
2704
+ ...(cfg.behavior || {}),
2705
+ pagination: {
2706
+ ...(cfg.behavior?.pagination || {}),
2707
+ strategy: 'client',
2708
+ },
2709
+ };
2710
+ }
2711
+ if (cfg.behavior?.sorting?.strategy === 'server') {
2712
+ cfg.behavior = {
2713
+ ...(cfg.behavior || {}),
2714
+ sorting: {
2715
+ ...(cfg.behavior?.sorting || {}),
2716
+ strategy: 'client',
2717
+ },
2718
+ };
2719
+ }
2720
+ }
2721
+ return stripLegacyRuntimeMarkers(cfg);
2722
+ }
2723
+ function buildTableApplyPlan(doc, runtime, options) {
2724
+ const normalized = normalizeTableAuthoringDocument(doc);
2725
+ const hasBindings = hasOwnProperty(doc, 'bindings');
2726
+ const diagnostics = validateTableAuthoringDocument(normalized, {
2727
+ hasLocalDataInput: runtime?.hasLocalDataInput,
2728
+ localDataModeDefaultEnabled: runtime?.localDataModeDefaultEnabled,
2729
+ server: {
2730
+ idField: runtime?.server?.idField,
2731
+ schemaHash: runtime?.server?.schemaHash,
2732
+ },
2733
+ });
2734
+ const bindingsPatch = hasBindings
2735
+ ? normalizeBindings(normalized.bindings) || {}
2736
+ : undefined;
2737
+ const diff = deriveBindingsDiff(bindingsPatch, runtime?.currentBindings);
2738
+ return {
2739
+ canonicalConfig: toCanonicalTableConfig(normalized, {
2740
+ hasLocalDataInput: runtime?.hasLocalDataInput,
2741
+ localDataModeDefaultEnabled: runtime?.localDataModeDefaultEnabled,
2742
+ }),
2743
+ bindingsPatch,
2744
+ persistence: {
2745
+ saveConfig: options?.saveConfig === true,
2746
+ saveBindings: options?.saveBindings === true,
2747
+ },
2748
+ runtime: deriveRuntimePlan(bindingsPatch, diff),
2749
+ metadata: {
2750
+ attachServerMeta: options?.attachServerMeta === true,
2751
+ },
2752
+ diff,
2753
+ diagnostics,
2754
+ };
2755
+ }
2756
+ function serializeTableAuthoringDocument(doc) {
2757
+ const normalized = normalizeTableAuthoringDocument(doc);
2758
+ return stripUndefinedDeep({
2759
+ kind: DOCUMENT_KIND,
2760
+ version: DOCUMENT_VERSION,
2761
+ config: normalized.config,
2762
+ bindings: normalized.bindings,
2763
+ });
2764
+ }
2765
+ function normalizeTableConfig(config) {
2766
+ const normalized = cloneJson(config || { columns: [] });
2767
+ if (!Array.isArray(normalized.columns)) {
2768
+ normalized.columns = [];
2769
+ }
2770
+ normalized.columns.sort((left, right) => String(left?.field || '').localeCompare(String(right?.field || '')));
2771
+ if (normalized.behavior && typeof normalized.behavior === 'object') {
2772
+ Object.keys(normalized.behavior).forEach((key) => {
2773
+ const value = normalized.behavior[key];
2774
+ if (value &&
2775
+ typeof value === 'object' &&
2776
+ !Array.isArray(value) &&
2777
+ Object.keys(value).length === 0) {
2778
+ delete normalized.behavior[key];
2779
+ }
2780
+ });
2781
+ if (Object.keys(normalized.behavior).length === 0) {
2782
+ delete normalized.behavior;
2783
+ }
2784
+ }
2785
+ const settings = normalized.behavior?.filtering?.advancedFilters?.settings;
2786
+ if (settings && typeof settings === 'object' && !Array.isArray(settings)) {
2787
+ Object.keys(settings).forEach((key) => {
2788
+ if (settings[key] === undefined || settings[key] === null || settings[key] === '') {
2789
+ delete settings[key];
2790
+ }
2791
+ });
2792
+ }
2793
+ return normalized;
2794
+ }
2795
+ function ensureAuthoringCompleteness(config) {
2796
+ const next = cloneJson(config);
2797
+ const advanced = next.behavior?.filtering?.advancedFilters;
2798
+ if (advanced &&
2799
+ typeof advanced === 'object' &&
2800
+ !Object.prototype.hasOwnProperty.call(advanced, 'settings')) {
2801
+ advanced.settings = {};
2802
+ }
2803
+ return next;
2804
+ }
2805
+ function normalizeBindings(bindings) {
2806
+ if (!bindings || typeof bindings !== 'object')
2807
+ return undefined;
2808
+ const normalized = {
2809
+ resourcePath: normalizeResourcePath(bindings.resourcePath),
2810
+ idField: normalizeIdField(bindings.idField),
2811
+ horizontalScroll: normalizeHorizontalScroll(bindings.horizontalScroll),
2812
+ };
2813
+ return stripUndefinedShallow(normalized);
2814
+ }
2815
+ function resolveEffectiveDataMode(doc, context) {
2816
+ const resourcePath = trimString(doc.bindings?.resourcePath);
2817
+ if (resourcePath)
2818
+ return 'remote';
2819
+ const localModeFlag = readBoolean(doc.config?.behavior?.localDataMode?.enabled);
2820
+ const effectiveLocalFlag = localModeFlag ?? (context?.localDataModeDefaultEnabled === true);
2821
+ if (context?.hasLocalDataInput && effectiveLocalFlag) {
2822
+ return 'local';
2823
+ }
2824
+ return 'empty';
2825
+ }
2826
+ function deriveBindingsDiff(next, current) {
2827
+ if (next === undefined) {
2828
+ return {
2829
+ resourcePathChanged: false,
2830
+ idFieldChanged: false,
2831
+ horizontalScrollChanged: false,
2832
+ requiresDataRefresh: false,
2833
+ };
2834
+ }
2835
+ const normalizedNext = normalizeBindings(next) || {};
2836
+ const normalizedCurrent = normalizeBindings(current) || {};
2837
+ const resourcePathChanged = normalizeResourcePath(normalizedNext.resourcePath) !==
2838
+ normalizeResourcePath(normalizedCurrent.resourcePath);
2839
+ const idFieldChanged = normalizeIdField(normalizedNext.idField) !==
2840
+ normalizeIdField(normalizedCurrent.idField);
2841
+ const horizontalScrollChanged = normalizeHorizontalScroll(normalizedNext.horizontalScroll) !==
2842
+ normalizeHorizontalScroll(normalizedCurrent.horizontalScroll);
2843
+ return {
2844
+ resourcePathChanged,
2845
+ idFieldChanged,
2846
+ horizontalScrollChanged,
2847
+ requiresDataRefresh: resourcePathChanged,
2848
+ };
2849
+ }
2850
+ function deriveRuntimePlan(bindings, diff) {
2851
+ if (bindings === undefined) {
2852
+ return {
2853
+ reconnectDataSource: false,
2854
+ reloadSchema: false,
2855
+ fetchData: false,
2856
+ refreshLocalView: false,
2857
+ };
2858
+ }
2859
+ const resourcePath = trimString(bindings?.resourcePath);
2860
+ if (resourcePath) {
2861
+ return {
2862
+ reconnectDataSource: !!diff?.resourcePathChanged,
2863
+ reloadSchema: !!diff?.resourcePathChanged,
2864
+ fetchData: !!diff?.requiresDataRefresh,
2865
+ refreshLocalView: false,
2866
+ };
2867
+ }
2868
+ return {
2869
+ reconnectDataSource: !!diff?.resourcePathChanged,
2870
+ reloadSchema: false,
2871
+ fetchData: false,
2872
+ refreshLocalView: true,
2873
+ };
2874
+ }
2875
+ function extractLegacyTableConfig(obj) {
2876
+ const clone = { ...obj };
2877
+ delete clone.__resourcePath__;
2878
+ delete clone.__resourcePathIntent__;
2879
+ delete clone.__idField__;
2880
+ delete clone.__horizontalScroll__;
2881
+ return asTableConfig(clone);
2882
+ }
2883
+ function stripLegacyRuntimeMarkers(config) {
2884
+ const clone = cloneJson(config);
2885
+ delete clone.__resourcePath__;
2886
+ delete clone.__resourcePathIntent__;
2887
+ delete clone.__idField__;
2888
+ delete clone.__horizontalScroll__;
2889
+ return clone;
2890
+ }
2891
+ function looksLikeLegacyPayload(obj) {
2892
+ return ('__resourcePath__' in obj ||
2893
+ '__resourcePathIntent__' in obj ||
2894
+ '__idField__' in obj ||
2895
+ '__horizontalScroll__' in obj);
2896
+ }
2897
+ function asTableConfig(value) {
2898
+ if (value && typeof value === 'object') {
2899
+ return value;
2900
+ }
2901
+ return { columns: [] };
2902
+ }
2903
+ function asRecord(value) {
2904
+ return value && typeof value === 'object'
2905
+ ? value
2906
+ : {};
2907
+ }
2908
+ function hasOwnProperty(value, key) {
2909
+ return !!value && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, key);
2910
+ }
2911
+ function normalizeResourcePath(value) {
2912
+ if (value === undefined)
2913
+ return undefined;
2914
+ const trimmed = trimString(value);
2915
+ return trimmed || null;
2916
+ }
2917
+ function normalizeIdField(value) {
2918
+ const trimmed = trimString(value);
2919
+ return trimmed || undefined;
2920
+ }
2921
+ function normalizeHorizontalScroll(value) {
2922
+ const candidate = trimString(value);
2923
+ if (!candidate)
2924
+ return undefined;
2925
+ return HORIZONTAL_SCROLL_VALUES.includes(candidate) ? candidate : undefined;
2926
+ }
2927
+ function trimString(value) {
2928
+ return typeof value === 'string' ? value.trim() : '';
2929
+ }
2930
+ function readBoolean(value) {
2931
+ if (value === true || value === false)
2932
+ return value;
2933
+ return undefined;
2934
+ }
2935
+ function cloneJson(value) {
2936
+ return JSON.parse(JSON.stringify(value ?? null));
2937
+ }
2938
+ function stripUndefinedShallow(value) {
2939
+ const entries = Object.entries(value).filter(([, v]) => v !== undefined);
2940
+ if (!entries.length)
2941
+ return undefined;
2942
+ return Object.fromEntries(entries);
2943
+ }
2944
+ function stripUndefinedDeep(value) {
2945
+ return JSON.parse(JSON.stringify(value));
2946
+ }
2947
+ function errorDiagnostic(code, message, path) {
2948
+ return { level: 'error', code, message, path };
2949
+ }
2950
+ function warnDiagnostic(code, message, path) {
2951
+ return { level: 'warning', code, message, path };
2952
+ }
2953
+ function infoDiagnostic(code, message, path) {
2954
+ return { level: 'info', code, message, path };
2955
+ }
2956
+
2597
2957
  class JsonConfigEditorComponent {
2598
2958
  cdr;
2599
- config = null;
2600
- configChange = new EventEmitter();
2959
+ document = null;
2960
+ documentChange = new EventEmitter();
2601
2961
  validationChange = new EventEmitter();
2602
2962
  editorEvent = new EventEmitter();
2603
- // JSON editing state
2604
2963
  jsonText = '';
2605
2964
  isValidJson = true;
2606
2965
  jsonError = '';
2607
2966
  unknownTopKeys = [];
2608
- // Subjects para cleanup e debouncing
2609
2967
  destroy$ = new Subject();
2610
2968
  jsonTextChanges$ = new Subject();
2611
2969
  constructor(cdr) {
2612
2970
  this.cdr = cdr;
2613
- // Setup debounced JSON validation
2614
2971
  this.jsonTextChanges$
2615
2972
  .pipe(debounceTime(300), takeUntil(this.destroy$))
2616
- .subscribe(text => {
2973
+ .subscribe((text) => {
2617
2974
  this.validateJson(text);
2618
2975
  });
2619
2976
  }
2620
2977
  ngOnInit() {
2621
- // Inicializar texto JSON com a configuração recebida
2622
- if (this.config) {
2623
- this.jsonText = JSON.stringify(this.config, null, 2);
2978
+ if (this.document) {
2979
+ this.jsonText = JSON.stringify(serializeTableAuthoringDocument(this.document), null, 2);
2624
2980
  this.validateJson(this.jsonText);
2625
2981
  }
2626
2982
  }
@@ -2632,99 +2988,87 @@ class JsonConfigEditorComponent {
2632
2988
  this.jsonTextChanges$.next(text);
2633
2989
  }
2634
2990
  applyJsonChanges() {
2635
- if (!this.isValidJson) {
2991
+ if (!this.isValidJson)
2636
2992
  return;
2637
- }
2638
2993
  try {
2639
- const newConfig = JSON.parse(this.jsonText);
2640
- // Emitir a nova configuração
2641
- this.configChange.emit(newConfig);
2642
- // Emitir evento de aplicação
2994
+ const newDocument = parseLegacyOrTableDocument(JSON.parse(this.jsonText));
2995
+ const diagnostics = validateTableAuthoringDocument(newDocument);
2996
+ this.documentChange.emit(newDocument);
2643
2997
  this.editorEvent.emit({
2644
2998
  type: 'apply',
2645
2999
  payload: {
2646
- isValid: true,
2647
- config: newConfig
2648
- }
3000
+ isValid: !diagnostics.some((item) => item.level === 'error'),
3001
+ document: newDocument,
3002
+ diagnostics,
3003
+ },
2649
3004
  });
2650
3005
  }
2651
- catch (error) {
2652
- const errorResult = {
2653
- isValid: false,
2654
- error: 'Erro ao aplicar configuração JSON'
2655
- };
3006
+ catch {
2656
3007
  this.editorEvent.emit({
2657
3008
  type: 'apply',
2658
- payload: errorResult
3009
+ payload: {
3010
+ isValid: false,
3011
+ error: 'Erro ao aplicar documento JSON',
3012
+ },
2659
3013
  });
2660
3014
  }
2661
3015
  }
2662
3016
  formatJson() {
2663
- if (!this.isValidJson) {
3017
+ if (!this.isValidJson)
2664
3018
  return;
2665
- }
2666
3019
  try {
2667
- const parsed = JSON.parse(this.jsonText);
2668
- this.jsonText = JSON.stringify(parsed, null, 2);
2669
- // Emitir evento de formatação
3020
+ const document = parseLegacyOrTableDocument(JSON.parse(this.jsonText));
3021
+ const diagnostics = validateTableAuthoringDocument(document);
3022
+ this.jsonText = JSON.stringify(serializeTableAuthoringDocument(document), null, 2);
2670
3023
  this.editorEvent.emit({
2671
3024
  type: 'format',
2672
3025
  payload: {
2673
- isValid: true,
2674
- config: parsed
2675
- }
3026
+ isValid: !diagnostics.some((item) => item.level === 'error'),
3027
+ document,
3028
+ diagnostics,
3029
+ },
2676
3030
  });
2677
3031
  this.cdr.markForCheck();
2678
3032
  }
2679
- catch (error) {
3033
+ catch {
2680
3034
  this.editorEvent.emit({
2681
3035
  type: 'format',
2682
3036
  payload: {
2683
3037
  isValid: false,
2684
- error: 'Erro ao formatar JSON'
2685
- }
3038
+ error: 'Erro ao formatar JSON',
3039
+ },
2686
3040
  });
2687
3041
  }
2688
3042
  }
2689
- /**
2690
- * Método público para atualizar o JSON externamente
2691
- */
2692
- updateJsonFromConfig(config) {
2693
- this.jsonText = JSON.stringify(config, null, 2);
3043
+ updateJsonFromDocument(document) {
3044
+ this.jsonText = JSON.stringify(serializeTableAuthoringDocument(document), null, 2);
2694
3045
  this.validateJson(this.jsonText);
2695
3046
  }
2696
- /**
2697
- * Método público para obter a configuração atual validada
2698
- */
2699
- getCurrentConfig() {
2700
- if (!this.isValidJson) {
3047
+ getCurrentDocument() {
3048
+ if (!this.isValidJson)
2701
3049
  return null;
2702
- }
2703
3050
  try {
2704
- return JSON.parse(this.jsonText);
3051
+ return parseLegacyOrTableDocument(JSON.parse(this.jsonText));
2705
3052
  }
2706
3053
  catch {
2707
3054
  return null;
2708
3055
  }
2709
3056
  }
2710
- /**
2711
- * Método público para verificar se há alterações
2712
- */
2713
3057
  hasChanges() {
2714
- if (!this.config) {
3058
+ if (!this.document)
2715
3059
  return false;
2716
- }
2717
- const currentConfig = this.getCurrentConfig();
2718
- if (!currentConfig) {
3060
+ const currentDocument = this.getCurrentDocument();
3061
+ if (!currentDocument)
2719
3062
  return false;
2720
- }
2721
- return JSON.stringify(this.config) !== JSON.stringify(currentConfig);
3063
+ return JSON.stringify(serializeTableAuthoringDocument(this.document))
3064
+ !== JSON.stringify(serializeTableAuthoringDocument(currentDocument));
2722
3065
  }
2723
3066
  validateJson(text) {
2724
3067
  const result = {
2725
3068
  isValid: false,
2726
3069
  error: undefined,
2727
- config: undefined
3070
+ document: undefined,
3071
+ diagnostics: [],
2728
3072
  };
2729
3073
  if (!text.trim()) {
2730
3074
  result.error = 'JSON não pode estar vazio';
@@ -2733,32 +3077,19 @@ class JsonConfigEditorComponent {
2733
3077
  }
2734
3078
  try {
2735
3079
  const parsed = JSON.parse(text);
2736
- // Validação básica da estrutura TableConfig
2737
- if (typeof parsed !== 'object' || parsed === null) {
2738
- throw new Error('Configuração deve ser um objeto');
2739
- }
2740
- if (!Array.isArray(parsed.columns)) {
2741
- throw new Error('Campo "columns" deve ser um array');
2742
- }
2743
- // Validação adicional para campos críticos
2744
- if (parsed.gridOptions && typeof parsed.gridOptions !== 'object') {
2745
- throw new Error('Campo "gridOptions" deve ser um objeto');
2746
- }
2747
- if (parsed.toolbar && typeof parsed.toolbar !== 'object') {
2748
- throw new Error('Campo "toolbar" deve ser um objeto');
2749
- }
2750
- result.isValid = true;
2751
- result.config = parsed;
2752
- // Detect unknown top-level keys (non-blocking)
2753
- try {
2754
- const allowed = [
2755
- 'meta', 'columns', 'behavior', 'appearance', 'toolbar', 'actions', 'export', 'messages', 'localization', 'data', 'theme', 'performance', 'plugins', 'accessibility', 'rowConditionalStyles'
2756
- ];
2757
- const keys = Object.keys(parsed || {});
2758
- this.unknownTopKeys = keys.filter((k) => !allowed.includes(k));
2759
- }
2760
- catch {
2761
- this.unknownTopKeys = [];
3080
+ const document = parseLegacyOrTableDocument(parsed);
3081
+ const diagnostics = validateTableAuthoringDocument(document);
3082
+ const hasErrors = diagnostics.some((item) => item.level === 'error');
3083
+ result.isValid = !hasErrors;
3084
+ result.document = document;
3085
+ result.diagnostics = diagnostics;
3086
+ const allowed = ['kind', 'version', 'config', 'bindings'];
3087
+ const keys = Object.keys(parsed || {});
3088
+ this.unknownTopKeys = keys.filter((key) => !allowed.includes(key));
3089
+ if (hasErrors) {
3090
+ result.error =
3091
+ diagnostics.find((item) => item.level === 'error')?.message ||
3092
+ 'Documento de autoria inválido';
2762
3093
  }
2763
3094
  this.updateValidationState(result);
2764
3095
  }
@@ -2770,32 +3101,28 @@ class JsonConfigEditorComponent {
2770
3101
  updateValidationState(result) {
2771
3102
  this.isValidJson = result.isValid;
2772
3103
  this.jsonError = result.error || '';
2773
- // Emitir evento de validação
2774
3104
  this.validationChange.emit(result);
2775
- // Emitir evento genérico do editor
2776
3105
  this.editorEvent.emit({
2777
3106
  type: 'validation',
2778
- payload: result
3107
+ payload: result,
2779
3108
  });
2780
3109
  this.cdr.markForCheck();
2781
3110
  }
2782
3111
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: JsonConfigEditorComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
2783
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: JsonConfigEditorComponent, isStandalone: true, selector: "json-config-editor", inputs: { config: "config" }, outputs: { configChange: "configChange", validationChange: "validationChange", editorEvent: "editorEvent" }, ngImport: i0, template: `
3112
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: JsonConfigEditorComponent, isStandalone: true, selector: "json-config-editor", inputs: { document: "document" }, outputs: { documentChange: "documentChange", validationChange: "validationChange", editorEvent: "editorEvent" }, ngImport: i0, template: `
2784
3113
  <div class="json-config-editor">
2785
- <!-- Educational Card -->
2786
3114
  <mat-card class="educational-card">
2787
3115
  <mat-card-header>
2788
3116
  <mat-icon mat-card-avatar class="card-icon">data_object</mat-icon>
2789
3117
  <mat-card-title>Edição Avançada JSON</mat-card-title>
2790
3118
  </mat-card-header>
2791
3119
  <mat-card-content>
2792
- <p><strong>Para usuários avançados:</strong> Esta seção permite ajustar todas as configurações da tabela
3120
+ <p><strong>Para usuários avançados:</strong> Esta seção permite ajustar o documento canônico de autoria da tabela
2793
3121
  diretamente via JSON. <strong>Atenção:</strong> Alterações aqui podem sobrescrever configurações
2794
3122
  visuais nas outras abas.</p>
2795
3123
  </mat-card-content>
2796
3124
  </mat-card>
2797
3125
 
2798
- <!-- JSON Editor Section -->
2799
3126
  <div class="json-editor-section">
2800
3127
  <div class="json-editor-toolbar">
2801
3128
  <button mat-button (click)="formatJson()" [disabled]="!isValidJson">
@@ -2809,12 +3136,12 @@ class JsonConfigEditorComponent {
2809
3136
  </div>
2810
3137
 
2811
3138
  <mat-form-field appearance="outline" class="json-textarea-field">
2812
- <mat-label>Configuração JSON</mat-label>
3139
+ <mat-label>Documento JSON</mat-label>
2813
3140
  <textarea
2814
3141
  matInput
2815
3142
  [(ngModel)]="jsonText"
2816
3143
  (ngModelChange)="onJsonTextChange($event)"
2817
- placeholder="Edite a configuração JSON aqui..."
3144
+ placeholder="Edite o documento JSON aqui..."
2818
3145
  rows="20"
2819
3146
  spellcheck="false"
2820
3147
  class="json-textarea">
@@ -2838,23 +3165,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2838
3165
  MatIconModule,
2839
3166
  MatFormFieldModule,
2840
3167
  MatInputModule,
2841
- MatCardModule
3168
+ MatCardModule,
2842
3169
  ], template: `
2843
3170
  <div class="json-config-editor">
2844
- <!-- Educational Card -->
2845
3171
  <mat-card class="educational-card">
2846
3172
  <mat-card-header>
2847
3173
  <mat-icon mat-card-avatar class="card-icon">data_object</mat-icon>
2848
3174
  <mat-card-title>Edição Avançada JSON</mat-card-title>
2849
3175
  </mat-card-header>
2850
3176
  <mat-card-content>
2851
- <p><strong>Para usuários avançados:</strong> Esta seção permite ajustar todas as configurações da tabela
3177
+ <p><strong>Para usuários avançados:</strong> Esta seção permite ajustar o documento canônico de autoria da tabela
2852
3178
  diretamente via JSON. <strong>Atenção:</strong> Alterações aqui podem sobrescrever configurações
2853
3179
  visuais nas outras abas.</p>
2854
3180
  </mat-card-content>
2855
3181
  </mat-card>
2856
3182
 
2857
- <!-- JSON Editor Section -->
2858
3183
  <div class="json-editor-section">
2859
3184
  <div class="json-editor-toolbar">
2860
3185
  <button mat-button (click)="formatJson()" [disabled]="!isValidJson">
@@ -2868,12 +3193,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2868
3193
  </div>
2869
3194
 
2870
3195
  <mat-form-field appearance="outline" class="json-textarea-field">
2871
- <mat-label>Configuração JSON</mat-label>
3196
+ <mat-label>Documento JSON</mat-label>
2872
3197
  <textarea
2873
3198
  matInput
2874
3199
  [(ngModel)]="jsonText"
2875
3200
  (ngModelChange)="onJsonTextChange($event)"
2876
- placeholder="Edite a configuração JSON aqui..."
3201
+ placeholder="Edite o documento JSON aqui..."
2877
3202
  rows="20"
2878
3203
  spellcheck="false"
2879
3204
  class="json-textarea">
@@ -2887,9 +3212,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2887
3212
  </div>
2888
3213
  </div>
2889
3214
  `, styles: [".json-config-editor{display:flex;flex-direction:column;height:100%}.educational-card{margin-bottom:24px;background-color:var(--md-sys-color-surface-container-low);border-left:4px solid var(--md-sys-color-primary)}.educational-card .mat-mdc-card-header{padding-bottom:8px}.card-icon{background-color:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container);font-size:20px;width:40px;height:40px;display:flex;align-items:center;justify-content:center}.educational-card .mat-mdc-card-title{font-size:1.1rem;font-weight:500;color:var(--md-sys-color-on-surface)}.educational-card .mat-mdc-card-content{color:var(--md-sys-color-on-surface-variant);line-height:1.5}.json-editor-section{flex:1;display:flex;flex-direction:column}.json-editor-toolbar{display:flex;gap:12px;margin-bottom:16px;padding:12px;background-color:var(--md-sys-color-surface-container-low);border-radius:8px;border:1px solid var(--md-sys-color-outline-variant)}.json-textarea-field{width:100%;flex:1}.json-textarea{font-family:Monaco,Menlo,Ubuntu Mono,Consolas,monospace!important;font-size:13px!important;line-height:1.4!important;height:100%!important;min-height:300px!important;white-space:pre!important;overflow-wrap:normal!important;overflow-x:auto!important;resize:none!important}.valid-hint{color:var(--md-sys-color-primary)!important}@media(max-width:768px){.json-editor-toolbar{flex-direction:column;gap:8px}.json-textarea{font-size:12px!important;min-height:300px!important}}\n"] }]
2890
- }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { config: [{
3215
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { document: [{
2891
3216
  type: Input
2892
- }], configChange: [{
3217
+ }], documentChange: [{
2893
3218
  type: Output
2894
3219
  }], validationChange: [{
2895
3220
  type: Output
@@ -28289,6 +28614,7 @@ class PraxisTableConfigEditor {
28289
28614
  serverSchemaHash;
28290
28615
  idFieldDiverges = false;
28291
28616
  schemaHashDiverges = false;
28617
+ schemaSnapshotMissing = false;
28292
28618
  // V2 specific configs for advanced editing
28293
28619
  isV2Config = false;
28294
28620
  // Estado do componente
@@ -28367,10 +28693,11 @@ class PraxisTableConfigEditor {
28367
28693
  const hs = this.panelData?.horizontalScroll || 'auto';
28368
28694
  this.horizontalScroll = hs;
28369
28695
  this.initialHorizontalScroll = hs;
28370
- // Divergência: idField e serverHash
28696
+ // Divergência: idField efetivo e snapshot persistido de schema
28371
28697
  const cfgHash = config?.meta?.serverHash;
28372
28698
  this.idFieldDiverges = !!(this.serverIdField && this.idField && this.serverIdField !== this.idField);
28373
28699
  this.schemaHashDiverges = !!(this.serverSchemaHash && cfgHash && this.serverSchemaHash !== cfgHash);
28700
+ this.schemaSnapshotMissing = !!(this.serverSchemaHash && !cfgHash);
28374
28701
  // Validar configuração recebida
28375
28702
  if (!config || typeof config !== 'object') {
28376
28703
  console.error('PTABLE:config:invalid', config);
@@ -28414,9 +28741,17 @@ class PraxisTableConfigEditor {
28414
28741
  }
28415
28742
  }
28416
28743
  // Event handlers para o JsonConfigEditorComponent
28417
- onJsonConfigChange(newConfig) {
28418
- this.debugLog('[PraxisTableConfigEditor] onJsonConfigChange received', newConfig);
28419
- this.editedConfig = newConfig;
28744
+ onJsonConfigChange(newValue) {
28745
+ this.debugLog('[PraxisTableConfigEditor] onJsonConfigChange received', newValue);
28746
+ const newDocument = newValue?.kind === 'praxis.table.editor'
28747
+ ? newValue
28748
+ : createTableAuthoringDocument({ config: newValue });
28749
+ this.editedConfig = newDocument.config;
28750
+ if (Object.prototype.hasOwnProperty.call(newDocument, 'bindings')) {
28751
+ this.resourcePath = newDocument.bindings?.resourcePath ?? '';
28752
+ this.idField = newDocument.bindings?.idField || 'id';
28753
+ this.horizontalScroll = newDocument.bindings?.horizontalScroll || 'auto';
28754
+ }
28420
28755
  this.enforceEffectiveModeStrategyConstraints();
28421
28756
  // Update state directly
28422
28757
  this.updateConfigurationVersion();
@@ -28431,7 +28766,7 @@ class PraxisTableConfigEditor {
28431
28766
  onJsonEditorEvent(event) {
28432
28767
  switch (event.type) {
28433
28768
  case 'apply':
28434
- if (event.payload.isValid && event.payload.config) {
28769
+ if (event.payload.isValid && event.payload.document) {
28435
28770
  this.showSuccess('Configuração JSON aplicada com sucesso!');
28436
28771
  }
28437
28772
  else {
@@ -28448,6 +28783,16 @@ class PraxisTableConfigEditor {
28448
28783
  break;
28449
28784
  }
28450
28785
  }
28786
+ buildJsonAuthoringDocument() {
28787
+ return createTableAuthoringDocument({
28788
+ config: this.editedConfig,
28789
+ bindings: {
28790
+ resourcePath: (this.resourcePath || '').trim() || null,
28791
+ idField: (this.idField || '').trim() || undefined,
28792
+ horizontalScroll: this.horizontalScroll || 'auto',
28793
+ },
28794
+ });
28795
+ }
28451
28796
  // Event handlers para o ColumnsConfigEditorComponent
28452
28797
  onColumnsConfigChange(newConfig) {
28453
28798
  // Normalize and short-circuit when nothing effectively changed to avoid loops
@@ -28838,26 +29183,7 @@ class PraxisTableConfigEditor {
28838
29183
  getSettingsValue() {
28839
29184
  this.syncFilterSettingsFromEditor();
28840
29185
  this.enforceEffectiveModeStrategyConstraints();
28841
- const trimmed = (this.resourcePath || '').trim();
28842
- const idKey = (this.idField || '').trim();
28843
- const hs = this.horizontalScroll || 'auto';
28844
- const out = { ...this.editedConfig };
28845
- out.__resourcePathIntent__ = this.getResourcePathIntent();
28846
- if (trimmed)
28847
- out.__resourcePath__ = trimmed;
28848
- if (idKey)
28849
- out.__idField__ = idKey;
28850
- out.__horizontalScroll__ = hs;
28851
- return out;
28852
- }
28853
- getResourcePathIntent() {
28854
- const current = (this.resourcePath || '').trim();
28855
- const initial = (this.initialResourcePath || '').trim();
28856
- if (current === initial)
28857
- return 'unchanged';
28858
- if (!current)
28859
- return 'clear';
28860
- return 'set';
29186
+ return this.buildJsonAuthoringDocument();
28861
29187
  }
28862
29188
  syncFilterSettingsFromEditor() {
28863
29189
  const childCfg = this.filterSettingsEditor?.getSettingsValue?.();
@@ -28970,14 +29296,6 @@ class PraxisTableConfigEditor {
28970
29296
  if (!this.serverIdField)
28971
29297
  return;
28972
29298
  this.idField = this.serverIdField;
28973
- const meta = { ...(this.editedConfig.meta || {}) };
28974
- meta.idField = this.serverIdField;
28975
- meta.version = meta.version || '2.0.0';
28976
- const now = new Date().toISOString();
28977
- meta.updatedAt = now;
28978
- if (!meta.createdAt)
28979
- meta.createdAt = now;
28980
- this.editedConfig = { ...this.editedConfig, meta };
28981
29299
  this.idFieldDiverges = false;
28982
29300
  this.updateCanSaveState();
28983
29301
  this.showSuccess('Chave primária reconciliada com o servidor');
@@ -28986,15 +29304,8 @@ class PraxisTableConfigEditor {
28986
29304
  onAcceptServerHash() {
28987
29305
  if (!this.serverSchemaHash)
28988
29306
  return;
28989
- const meta = { ...(this.editedConfig.meta || {}) };
28990
- meta.serverHash = this.serverSchemaHash;
28991
- meta.version = meta.version || '2.0.0';
28992
- const now = new Date().toISOString();
28993
- meta.updatedAt = now;
28994
- if (!meta.createdAt)
28995
- meta.createdAt = now;
28996
- this.editedConfig = { ...this.editedConfig, meta };
28997
29307
  this.schemaHashDiverges = false;
29308
+ this.schemaSnapshotMissing = false;
28998
29309
  this.updateCanSaveState();
28999
29310
  this.showSuccess('Metadados de schema atualizados');
29000
29311
  }
@@ -29085,8 +29396,6 @@ class PraxisTableConfigEditor {
29085
29396
  // Recalcular estado de save após salvar overrides e config
29086
29397
  this.updateCanSaveState();
29087
29398
  const value = this.getSettingsValue();
29088
- // Importante: retornar o mesmo valor de getSettingsValue()
29089
- // para garantir que __resourcePath__ seja propagado pelo painel
29090
29399
  return value;
29091
29400
  }
29092
29401
  catch (error) {
@@ -29278,7 +29587,7 @@ class PraxisTableConfigEditor {
29278
29587
  this.behaviorFeedbackBurstTypes.clear();
29279
29588
  }
29280
29589
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisTableConfigEditor, deps: [{ token: i0.ChangeDetectorRef }, { token: i1.TableConfigService }], target: i0.ɵɵFactoryTarget.Component });
29281
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisTableConfigEditor, isStandalone: true, selector: "praxis-table-config-editor", providers: [TableConfigService], viewQueries: [{ propertyName: "filterSettingsEditor", first: true, predicate: ["tableFilterSettingsRef"], descendants: true }, { propertyName: "behaviorEditor", first: true, predicate: BehaviorConfigEditorComponent, descendants: true }, { propertyName: "crudEditorSetter", first: true, predicate: ["crudEditorRef"], descendants: true }], ngImport: i0, template: " <mat-tab-group class=\"config-tabs\" [(selectedIndex)]=\"activeSectionIndex\">\n <mat-tab *ngFor=\"let section of sections\">\n <ng-template mat-tab-label>\n @if (section.icon) { <mat-icon [praxisIcon]=\"section.icon\"></mat-icon> }\n <span [attr.data-testid]=\"'config-tab-' + section.id\">{{ section.label }}</span>\n </ng-template>\n <div class=\"tab-content\">\n <ng-container [ngSwitch]=\"section.id\">\n <div *ngSwitchCase=\"'connect'\" class=\"connect-grid\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Recurso</mat-label>\n <input matInput [(ngModel)]=\"resourcePath\" (ngModelChange)=\"onResourcePathChange($event)\" placeholder=\"ex.: employees\" />\n <mat-icon matSuffix>link</mat-icon>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Chave prim\u00E1ria</mat-label>\n <input matInput [(ngModel)]=\"idField\" (ngModelChange)=\"onIdFieldChange($event)\" placeholder=\"ex.: id, uuid, codigo\" />\n <mat-icon matSuffix>fingerprint</mat-icon>\n </mat-form-field>\n <p class=\"connect-note\">Defina o recurso da API e, se necess\u00E1rio, a chave prim\u00E1ria.</p>\n\n <!-- Diverg\u00EAncia: idField -->\n @if (idFieldDiverges) { <div class=\"alert alert--warning connect-alert\">\n <mat-icon class=\"alert__icon\">warning</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n A chave prim\u00E1ria no servidor diverge da configura\u00E7\u00E3o atual.\n <span class=\"connect-alert__meta\">(config: {{ idField || 'id' }} | servidor: {{ serverIdField || 'id' }})</span>\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onReconcileIdField()\">Reconciliar</button>\n </div> }\n\n <!-- Diverg\u00EAncia: serverHash -->\n @if (schemaHashDiverges) { <div class=\"alert alert--info connect-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n O schema no servidor foi atualizado desde a \u00FAltima sincroniza\u00E7\u00E3o.\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onAcceptServerHash()\">Atualizar metadados</button>\n </div> }\n </div>\n\n <div *ngSwitchCase=\"'overview'\" class=\"overview-grid\">\n <div class=\"overview-row\">\n <mat-form-field appearance=\"outline\" class=\"hs-field\">\n <mat-label>Scroll Horizontal</mat-label>\n <mat-select [(ngModel)]=\"horizontalScroll\" (ngModelChange)=\"onHorizontalScrollChange($event)\">\n <mat-option value=\"auto\">Auto (padr\u00E3o)</mat-option>\n <mat-option value=\"wrap\">Wrap (quebrar linhas)</mat-option>\n <mat-option value=\"none\">Host controla</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"help small\">Controla como a tabela lida com largura horizontal e barra de rolagem.</div>\n </div>\n @if (isEffectiveLocalMode()) { <div class=\"alert alert--info overview-mode-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text\">\n Modo local efetivo ativo: estrat\u00E9gias de pagina\u00E7\u00E3o e ordena\u00E7\u00E3o em servidor ficam bloqueadas e s\u00E3o salvas como cliente.\n </div>\n </div> }\n <behavior-config-editor\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n [forceClientStrategies]=\"isEffectiveLocalMode()\"\n (configChange)=\"onBehaviorConfigChange($event)\"\n (behaviorChange)=\"onBehaviorChange($event)\"\n ></behavior-config-editor>\n </div>\n\n <columns-config-editor\n *ngSwitchCase=\"'columns'\"\n [config]=\"editedConfig\"\n (configChange)=\"onColumnsConfigChange($event)\"\n (columnChange)=\"onColumnChange($event)\"\n ></columns-config-editor>\n\n <table-rules-editor\n *ngSwitchCase=\"'rules'\"\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n (configChange)=\"onRulesConfigChange($event)\"\n ></table-rules-editor>\n\n <header-appearance-editor\n *ngSwitchCase=\"'header'\"\n [config]=\"editedConfig\"\n (configChange)=\"onColumnsConfigChange($event)\"\n ></header-appearance-editor>\n\n <toolbar-actions-editor\n *ngSwitchCase=\"'toolbar'\"\n [config]=\"editedConfig\"\n (configChange)=\"onToolbarActionsConfigChange($event)\"\n (toolbarActionsChange)=\"onToolbarActionsChange($event)\"\n ></toolbar-actions-editor>\n\n <!-- Aba extra para integra\u00E7\u00F5es CRUD (vis\u00EDvel quando em contexto CRUD) -->\n <crud-integration-editor\n #crudEditorRef\n *ngSwitchCase=\"'crud'\"\n [tableId]=\"crudContext?.tableId || 'default'\"\n [crudContext]=\"crudContext\"\n [componentKeyId]=\"crudContext?.componentKeyId\"\n ></crud-integration-editor>\n\n <filter-settings\n #tableFilterSettingsRef\n *ngSwitchCase=\"'filters'\"\n [metadata]=\"getFilterSettingsMetas()\"\n [metadataSource]=\"isUsingFilterDtoMetas() ? 'filter-dto' : 'columns'\"\n [metadataLoading]=\"filterDtoMetasLoading\"\n [metadataErrorMsg]=\"filterDtoMetasErrorMsg\"\n [settings]=\"\n editedConfig.behavior?.filtering?.advancedFilters?.settings\n \"\n (settingsChange)=\"onFilterSettingsChange($event)\"\n ></filter-settings>\n\n <messages-localization-editor\n *ngSwitchCase=\"'messages'\"\n [config]=\"editedConfig\"\n (configChange)=\"onMessagesLocalizationConfigChange($event)\"\n (messagesLocalizationChange)=\"\n onMessagesLocalizationChange($event)\n \"\n ></messages-localization-editor>\n\n <confirm-dialog-appearance-editor\n *ngSwitchCase=\"'dialogs'\"\n [config]=\"editedConfig\"\n (configChange)=\"onJsonConfigChange($event)\"\n ></confirm-dialog-appearance-editor>\n\n\n <json-config-editor\n *ngSwitchCase=\"'json'\"\n [config]=\"editedConfig\"\n (configChange)=\"onJsonConfigChange($event)\"\n (editorEvent)=\"onJsonEditorEvent($event)\"\n ></json-config-editor>\n </ng-container>\n </div>\n </mat-tab>\n </mat-tab-group>\n@if (statusMessage) { <div class=\"config-editor-status\">\n <span\n class=\"status-text\"\n [class.error]=\"hasErrors\"\n [class.success]=\"hasSuccess\"\n >{{ statusMessage }}</span\n >\n </div> }\n", styles: ["@charset \"UTF-8\";.config-tabs{flex:1 1 auto;display:flex;flex-direction:column}.config-tabs .mat-mdc-tab{min-width:120px}.config-tabs .mat-mdc-tab-body-wrapper,.config-tabs .mat-mdc-tab-group-container{flex:1 1 auto;min-height:0}.config-tabs .mat-mdc-tab-body-content{height:100%;min-height:0;overflow:visible}.tab-content{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;padding:8px 8px 56px;box-sizing:border-box;overflow:visible}.overview-grid{display:grid;grid-template-columns:1fr;gap:12px}.overview-grid .overview-row{display:grid;grid-template-columns:280px 1fr;align-items:end;gap:12px}.overview-grid .overview-row .help.small{opacity:.75;font-size:12px}.overview-mode-alert{align-items:center}.connect-grid{display:grid;gap:12px;padding:8px;grid-template-columns:1fr 240px;align-items:start}.connect-note{grid-column:1/-1;margin:0;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.connect-alert{grid-column:1/-1;align-items:center}.connect-alert__text{flex:1}.connect-alert__meta{opacity:.85}.educational-card{margin-bottom:24px;background:var(--md-sys-color-surface-container-low);border-left:4px solid var(--md-sys-color-primary);flex-shrink:0}.config-editor-status{padding:12px 16px;margin-top:auto;border-top:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);flex-shrink:0;position:relative}.status-text{font-size:.875rem;line-height:1.2}.status-text.error{color:var(--md-sys-color-error)}.status-text.success{color:var(--md-sys-color-primary)}@media(max-width:768px){.tab-content{padding:8px}.config-editor-status{padding:12px 16px}.connect-grid{grid-template-columns:1fr}.overview-grid .overview-row{grid-template-columns:1fr;align-items:start}:host{display:flex;flex-direction:column;flex:1 1 auto;min-height:0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$2.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1$2.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i4.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i4.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i4.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: FormsModule }, { 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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { 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.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: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: JsonConfigEditorComponent, selector: "json-config-editor", inputs: ["config"], outputs: ["configChange", "validationChange", "editorEvent"] }, { kind: "component", type: ColumnsConfigEditorComponent, selector: "columns-config-editor", inputs: ["config"], outputs: ["configChange", "columnChange"] }, { kind: "component", type: BehaviorConfigEditorComponent, selector: "behavior-config-editor", inputs: ["config", "resourcePath", "forceClientStrategies"], outputs: ["configChange", "behaviorChange"] }, { kind: "component", type: HeaderAppearanceEditorComponent, selector: "header-appearance-editor", inputs: ["config"], outputs: ["configChange"] }, { kind: "component", type: ToolbarActionsEditorComponent, selector: "toolbar-actions-editor", inputs: ["config"], outputs: ["configChange", "toolbarActionsChange"] }, { kind: "component", type: MessagesLocalizationEditorComponent, selector: "messages-localization-editor", inputs: ["config"], outputs: ["configChange", "messagesLocalizationChange"] }, { kind: "component", type: FilterSettingsComponent, selector: "filter-settings", inputs: ["metadata", "metadataSource", "metadataLoading", "metadataErrorMsg", "settings", "configKey"], outputs: ["settingsChange"] }, { kind: "component", type: CrudIntegrationEditorComponent, selector: "crud-integration-editor", inputs: ["tableId", "crudContext", "componentKeyId"] }, { kind: "component", type: ConfirmDialogAppearanceEditorComponent, selector: "confirm-dialog-appearance-editor", inputs: ["config"], outputs: ["configChange"] }, { kind: "component", type: TableRulesEditorComponent, selector: "table-rules-editor", inputs: ["config", "resourcePath", "fields", "i18nRules", "dslFunctionRegistry", "debugLogs", "debugLevel"], outputs: ["configChange"] }] });
29590
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PraxisTableConfigEditor, isStandalone: true, selector: "praxis-table-config-editor", providers: [TableConfigService], viewQueries: [{ propertyName: "filterSettingsEditor", first: true, predicate: ["tableFilterSettingsRef"], descendants: true }, { propertyName: "behaviorEditor", first: true, predicate: BehaviorConfigEditorComponent, descendants: true }, { propertyName: "crudEditorSetter", first: true, predicate: ["crudEditorRef"], descendants: true }], ngImport: i0, template: " <mat-tab-group class=\"config-tabs\" [(selectedIndex)]=\"activeSectionIndex\">\n <mat-tab *ngFor=\"let section of sections\">\n <ng-template mat-tab-label>\n @if (section.icon) { <mat-icon [praxisIcon]=\"section.icon\"></mat-icon> }\n <span [attr.data-testid]=\"'config-tab-' + section.id\">{{ section.label }}</span>\n </ng-template>\n <div class=\"tab-content\">\n <ng-container [ngSwitch]=\"section.id\">\n <div *ngSwitchCase=\"'connect'\" class=\"connect-grid\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Recurso</mat-label>\n <input matInput [(ngModel)]=\"resourcePath\" (ngModelChange)=\"onResourcePathChange($event)\" placeholder=\"ex.: employees\" />\n <mat-icon matSuffix>link</mat-icon>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Chave prim\u00E1ria</mat-label>\n <input matInput [(ngModel)]=\"idField\" (ngModelChange)=\"onIdFieldChange($event)\" placeholder=\"ex.: id, uuid, codigo\" />\n <mat-icon matSuffix>fingerprint</mat-icon>\n </mat-form-field>\n <p class=\"connect-note\">Defina o recurso da API e, se necess\u00E1rio, a chave prim\u00E1ria.</p>\n\n <!-- Diverg\u00EAncia: idField -->\n @if (idFieldDiverges) { <div class=\"alert alert--warning connect-alert\">\n <mat-icon class=\"alert__icon\">warning</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n A chave prim\u00E1ria no servidor diverge da configura\u00E7\u00E3o atual.\n <span class=\"connect-alert__meta\">(config: {{ idField || 'id' }} | servidor: {{ serverIdField || 'id' }})</span>\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onReconcileIdField()\">Reconciliar</button>\n </div> }\n\n <!-- Diverg\u00EAncia: serverHash -->\n @if (schemaHashDiverges) { <div class=\"alert alert--info connect-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n O snapshot de schema salvo diverge do schema atual do servidor.\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onAcceptServerHash()\">Atualizar metadados</button>\n </div> }\n\n @if (!schemaHashDiverges && schemaSnapshotMissing) { <div class=\"alert alert--info connect-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n Existe schema atual no servidor, mas ainda n\u00E3o h\u00E1 snapshot persistido para reconcilia\u00E7\u00E3o.\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onAcceptServerHash()\">Salvar snapshot</button>\n </div> }\n </div>\n\n <div *ngSwitchCase=\"'overview'\" class=\"overview-grid\">\n <div class=\"overview-row\">\n <mat-form-field appearance=\"outline\" class=\"hs-field\">\n <mat-label>Scroll Horizontal</mat-label>\n <mat-select [(ngModel)]=\"horizontalScroll\" (ngModelChange)=\"onHorizontalScrollChange($event)\">\n <mat-option value=\"auto\">Auto (padr\u00E3o)</mat-option>\n <mat-option value=\"wrap\">Wrap (quebrar linhas)</mat-option>\n <mat-option value=\"none\">Host controla</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"help small\">Controla como a tabela lida com largura horizontal e barra de rolagem.</div>\n </div>\n @if (isEffectiveLocalMode()) { <div class=\"alert alert--info overview-mode-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text\">\n Modo local efetivo ativo: estrat\u00E9gias de pagina\u00E7\u00E3o e ordena\u00E7\u00E3o em servidor ficam bloqueadas e s\u00E3o salvas como cliente.\n </div>\n </div> }\n <behavior-config-editor\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n [forceClientStrategies]=\"isEffectiveLocalMode()\"\n (configChange)=\"onBehaviorConfigChange($event)\"\n (behaviorChange)=\"onBehaviorChange($event)\"\n ></behavior-config-editor>\n </div>\n\n <columns-config-editor\n *ngSwitchCase=\"'columns'\"\n [config]=\"editedConfig\"\n (configChange)=\"onColumnsConfigChange($event)\"\n (columnChange)=\"onColumnChange($event)\"\n ></columns-config-editor>\n\n <table-rules-editor\n *ngSwitchCase=\"'rules'\"\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n (configChange)=\"onRulesConfigChange($event)\"\n ></table-rules-editor>\n\n <header-appearance-editor\n *ngSwitchCase=\"'header'\"\n [config]=\"editedConfig\"\n (configChange)=\"onColumnsConfigChange($event)\"\n ></header-appearance-editor>\n\n <toolbar-actions-editor\n *ngSwitchCase=\"'toolbar'\"\n [config]=\"editedConfig\"\n (configChange)=\"onToolbarActionsConfigChange($event)\"\n (toolbarActionsChange)=\"onToolbarActionsChange($event)\"\n ></toolbar-actions-editor>\n\n <!-- Aba extra para integra\u00E7\u00F5es CRUD (vis\u00EDvel quando em contexto CRUD) -->\n <crud-integration-editor\n #crudEditorRef\n *ngSwitchCase=\"'crud'\"\n [tableId]=\"crudContext?.tableId || 'default'\"\n [crudContext]=\"crudContext\"\n [componentKeyId]=\"crudContext?.componentKeyId\"\n ></crud-integration-editor>\n\n <filter-settings\n #tableFilterSettingsRef\n *ngSwitchCase=\"'filters'\"\n [metadata]=\"getFilterSettingsMetas()\"\n [metadataSource]=\"isUsingFilterDtoMetas() ? 'filter-dto' : 'columns'\"\n [metadataLoading]=\"filterDtoMetasLoading\"\n [metadataErrorMsg]=\"filterDtoMetasErrorMsg\"\n [settings]=\"\n editedConfig.behavior?.filtering?.advancedFilters?.settings\n \"\n (settingsChange)=\"onFilterSettingsChange($event)\"\n ></filter-settings>\n\n <messages-localization-editor\n *ngSwitchCase=\"'messages'\"\n [config]=\"editedConfig\"\n (configChange)=\"onMessagesLocalizationConfigChange($event)\"\n (messagesLocalizationChange)=\"\n onMessagesLocalizationChange($event)\n \"\n ></messages-localization-editor>\n\n <confirm-dialog-appearance-editor\n *ngSwitchCase=\"'dialogs'\"\n [config]=\"editedConfig\"\n (configChange)=\"onJsonConfigChange($event)\"\n ></confirm-dialog-appearance-editor>\n\n\n <json-config-editor\n *ngSwitchCase=\"'json'\"\n [document]=\"buildJsonAuthoringDocument()\"\n (documentChange)=\"onJsonConfigChange($event)\"\n (validationChange)=\"onJsonValidationChange($event)\"\n (editorEvent)=\"onJsonEditorEvent($event)\"\n ></json-config-editor>\n </ng-container>\n </div>\n </mat-tab>\n </mat-tab-group>\n@if (statusMessage) { <div class=\"config-editor-status\">\n <span\n class=\"status-text\"\n [class.error]=\"hasErrors\"\n [class.success]=\"hasSuccess\"\n >{{ statusMessage }}</span\n >\n </div> }\n", styles: ["@charset \"UTF-8\";.config-tabs{flex:1 1 auto;display:flex;flex-direction:column}.config-tabs .mat-mdc-tab{min-width:120px}.config-tabs .mat-mdc-tab-body-wrapper,.config-tabs .mat-mdc-tab-group-container{flex:1 1 auto;min-height:0}.config-tabs .mat-mdc-tab-body-content{height:100%;min-height:0;overflow:visible}.tab-content{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;padding:8px 8px 56px;box-sizing:border-box;overflow:visible}.overview-grid{display:grid;grid-template-columns:1fr;gap:12px}.overview-grid .overview-row{display:grid;grid-template-columns:280px 1fr;align-items:end;gap:12px}.overview-grid .overview-row .help.small{opacity:.75;font-size:12px}.overview-mode-alert{align-items:center}.connect-grid{display:grid;gap:12px;padding:8px;grid-template-columns:1fr 240px;align-items:start}.connect-note{grid-column:1/-1;margin:0;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.connect-alert{grid-column:1/-1;align-items:center}.connect-alert__text{flex:1}.connect-alert__meta{opacity:.85}.educational-card{margin-bottom:24px;background:var(--md-sys-color-surface-container-low);border-left:4px solid var(--md-sys-color-primary);flex-shrink:0}.config-editor-status{padding:12px 16px;margin-top:auto;border-top:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);flex-shrink:0;position:relative}.status-text{font-size:.875rem;line-height:1.2}.status-text.error{color:var(--md-sys-color-error)}.status-text.success{color:var(--md-sys-color-primary)}@media(max-width:768px){.tab-content{padding:8px}.config-editor-status{padding:12px 16px}.connect-grid{grid-template-columns:1fr}.overview-grid .overview-row{grid-template-columns:1fr;align-items:start}:host{display:flex;flex-direction:column;flex:1 1 auto;min-height:0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$2.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1$2.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i4.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i4.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i4.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: FormsModule }, { 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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { 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.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: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: JsonConfigEditorComponent, selector: "json-config-editor", inputs: ["document"], outputs: ["documentChange", "validationChange", "editorEvent"] }, { kind: "component", type: ColumnsConfigEditorComponent, selector: "columns-config-editor", inputs: ["config"], outputs: ["configChange", "columnChange"] }, { kind: "component", type: BehaviorConfigEditorComponent, selector: "behavior-config-editor", inputs: ["config", "resourcePath", "forceClientStrategies"], outputs: ["configChange", "behaviorChange"] }, { kind: "component", type: HeaderAppearanceEditorComponent, selector: "header-appearance-editor", inputs: ["config"], outputs: ["configChange"] }, { kind: "component", type: ToolbarActionsEditorComponent, selector: "toolbar-actions-editor", inputs: ["config"], outputs: ["configChange", "toolbarActionsChange"] }, { kind: "component", type: MessagesLocalizationEditorComponent, selector: "messages-localization-editor", inputs: ["config"], outputs: ["configChange", "messagesLocalizationChange"] }, { kind: "component", type: FilterSettingsComponent, selector: "filter-settings", inputs: ["metadata", "metadataSource", "metadataLoading", "metadataErrorMsg", "settings", "configKey"], outputs: ["settingsChange"] }, { kind: "component", type: CrudIntegrationEditorComponent, selector: "crud-integration-editor", inputs: ["tableId", "crudContext", "componentKeyId"] }, { kind: "component", type: ConfirmDialogAppearanceEditorComponent, selector: "confirm-dialog-appearance-editor", inputs: ["config"], outputs: ["configChange"] }, { kind: "component", type: TableRulesEditorComponent, selector: "table-rules-editor", inputs: ["config", "resourcePath", "fields", "i18nRules", "dslFunctionRegistry", "debugLogs", "debugLevel"], outputs: ["configChange"] }] });
29282
29591
  }
29283
29592
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisTableConfigEditor, decorators: [{
29284
29593
  type: Component,
@@ -29301,7 +29610,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
29301
29610
  CrudIntegrationEditorComponent,
29302
29611
  ConfirmDialogAppearanceEditorComponent,
29303
29612
  TableRulesEditorComponent,
29304
- ], providers: [TableConfigService], template: " <mat-tab-group class=\"config-tabs\" [(selectedIndex)]=\"activeSectionIndex\">\n <mat-tab *ngFor=\"let section of sections\">\n <ng-template mat-tab-label>\n @if (section.icon) { <mat-icon [praxisIcon]=\"section.icon\"></mat-icon> }\n <span [attr.data-testid]=\"'config-tab-' + section.id\">{{ section.label }}</span>\n </ng-template>\n <div class=\"tab-content\">\n <ng-container [ngSwitch]=\"section.id\">\n <div *ngSwitchCase=\"'connect'\" class=\"connect-grid\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Recurso</mat-label>\n <input matInput [(ngModel)]=\"resourcePath\" (ngModelChange)=\"onResourcePathChange($event)\" placeholder=\"ex.: employees\" />\n <mat-icon matSuffix>link</mat-icon>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Chave prim\u00E1ria</mat-label>\n <input matInput [(ngModel)]=\"idField\" (ngModelChange)=\"onIdFieldChange($event)\" placeholder=\"ex.: id, uuid, codigo\" />\n <mat-icon matSuffix>fingerprint</mat-icon>\n </mat-form-field>\n <p class=\"connect-note\">Defina o recurso da API e, se necess\u00E1rio, a chave prim\u00E1ria.</p>\n\n <!-- Diverg\u00EAncia: idField -->\n @if (idFieldDiverges) { <div class=\"alert alert--warning connect-alert\">\n <mat-icon class=\"alert__icon\">warning</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n A chave prim\u00E1ria no servidor diverge da configura\u00E7\u00E3o atual.\n <span class=\"connect-alert__meta\">(config: {{ idField || 'id' }} | servidor: {{ serverIdField || 'id' }})</span>\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onReconcileIdField()\">Reconciliar</button>\n </div> }\n\n <!-- Diverg\u00EAncia: serverHash -->\n @if (schemaHashDiverges) { <div class=\"alert alert--info connect-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n O schema no servidor foi atualizado desde a \u00FAltima sincroniza\u00E7\u00E3o.\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onAcceptServerHash()\">Atualizar metadados</button>\n </div> }\n </div>\n\n <div *ngSwitchCase=\"'overview'\" class=\"overview-grid\">\n <div class=\"overview-row\">\n <mat-form-field appearance=\"outline\" class=\"hs-field\">\n <mat-label>Scroll Horizontal</mat-label>\n <mat-select [(ngModel)]=\"horizontalScroll\" (ngModelChange)=\"onHorizontalScrollChange($event)\">\n <mat-option value=\"auto\">Auto (padr\u00E3o)</mat-option>\n <mat-option value=\"wrap\">Wrap (quebrar linhas)</mat-option>\n <mat-option value=\"none\">Host controla</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"help small\">Controla como a tabela lida com largura horizontal e barra de rolagem.</div>\n </div>\n @if (isEffectiveLocalMode()) { <div class=\"alert alert--info overview-mode-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text\">\n Modo local efetivo ativo: estrat\u00E9gias de pagina\u00E7\u00E3o e ordena\u00E7\u00E3o em servidor ficam bloqueadas e s\u00E3o salvas como cliente.\n </div>\n </div> }\n <behavior-config-editor\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n [forceClientStrategies]=\"isEffectiveLocalMode()\"\n (configChange)=\"onBehaviorConfigChange($event)\"\n (behaviorChange)=\"onBehaviorChange($event)\"\n ></behavior-config-editor>\n </div>\n\n <columns-config-editor\n *ngSwitchCase=\"'columns'\"\n [config]=\"editedConfig\"\n (configChange)=\"onColumnsConfigChange($event)\"\n (columnChange)=\"onColumnChange($event)\"\n ></columns-config-editor>\n\n <table-rules-editor\n *ngSwitchCase=\"'rules'\"\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n (configChange)=\"onRulesConfigChange($event)\"\n ></table-rules-editor>\n\n <header-appearance-editor\n *ngSwitchCase=\"'header'\"\n [config]=\"editedConfig\"\n (configChange)=\"onColumnsConfigChange($event)\"\n ></header-appearance-editor>\n\n <toolbar-actions-editor\n *ngSwitchCase=\"'toolbar'\"\n [config]=\"editedConfig\"\n (configChange)=\"onToolbarActionsConfigChange($event)\"\n (toolbarActionsChange)=\"onToolbarActionsChange($event)\"\n ></toolbar-actions-editor>\n\n <!-- Aba extra para integra\u00E7\u00F5es CRUD (vis\u00EDvel quando em contexto CRUD) -->\n <crud-integration-editor\n #crudEditorRef\n *ngSwitchCase=\"'crud'\"\n [tableId]=\"crudContext?.tableId || 'default'\"\n [crudContext]=\"crudContext\"\n [componentKeyId]=\"crudContext?.componentKeyId\"\n ></crud-integration-editor>\n\n <filter-settings\n #tableFilterSettingsRef\n *ngSwitchCase=\"'filters'\"\n [metadata]=\"getFilterSettingsMetas()\"\n [metadataSource]=\"isUsingFilterDtoMetas() ? 'filter-dto' : 'columns'\"\n [metadataLoading]=\"filterDtoMetasLoading\"\n [metadataErrorMsg]=\"filterDtoMetasErrorMsg\"\n [settings]=\"\n editedConfig.behavior?.filtering?.advancedFilters?.settings\n \"\n (settingsChange)=\"onFilterSettingsChange($event)\"\n ></filter-settings>\n\n <messages-localization-editor\n *ngSwitchCase=\"'messages'\"\n [config]=\"editedConfig\"\n (configChange)=\"onMessagesLocalizationConfigChange($event)\"\n (messagesLocalizationChange)=\"\n onMessagesLocalizationChange($event)\n \"\n ></messages-localization-editor>\n\n <confirm-dialog-appearance-editor\n *ngSwitchCase=\"'dialogs'\"\n [config]=\"editedConfig\"\n (configChange)=\"onJsonConfigChange($event)\"\n ></confirm-dialog-appearance-editor>\n\n\n <json-config-editor\n *ngSwitchCase=\"'json'\"\n [config]=\"editedConfig\"\n (configChange)=\"onJsonConfigChange($event)\"\n (editorEvent)=\"onJsonEditorEvent($event)\"\n ></json-config-editor>\n </ng-container>\n </div>\n </mat-tab>\n </mat-tab-group>\n@if (statusMessage) { <div class=\"config-editor-status\">\n <span\n class=\"status-text\"\n [class.error]=\"hasErrors\"\n [class.success]=\"hasSuccess\"\n >{{ statusMessage }}</span\n >\n </div> }\n", styles: ["@charset \"UTF-8\";.config-tabs{flex:1 1 auto;display:flex;flex-direction:column}.config-tabs .mat-mdc-tab{min-width:120px}.config-tabs .mat-mdc-tab-body-wrapper,.config-tabs .mat-mdc-tab-group-container{flex:1 1 auto;min-height:0}.config-tabs .mat-mdc-tab-body-content{height:100%;min-height:0;overflow:visible}.tab-content{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;padding:8px 8px 56px;box-sizing:border-box;overflow:visible}.overview-grid{display:grid;grid-template-columns:1fr;gap:12px}.overview-grid .overview-row{display:grid;grid-template-columns:280px 1fr;align-items:end;gap:12px}.overview-grid .overview-row .help.small{opacity:.75;font-size:12px}.overview-mode-alert{align-items:center}.connect-grid{display:grid;gap:12px;padding:8px;grid-template-columns:1fr 240px;align-items:start}.connect-note{grid-column:1/-1;margin:0;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.connect-alert{grid-column:1/-1;align-items:center}.connect-alert__text{flex:1}.connect-alert__meta{opacity:.85}.educational-card{margin-bottom:24px;background:var(--md-sys-color-surface-container-low);border-left:4px solid var(--md-sys-color-primary);flex-shrink:0}.config-editor-status{padding:12px 16px;margin-top:auto;border-top:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);flex-shrink:0;position:relative}.status-text{font-size:.875rem;line-height:1.2}.status-text.error{color:var(--md-sys-color-error)}.status-text.success{color:var(--md-sys-color-primary)}@media(max-width:768px){.tab-content{padding:8px}.config-editor-status{padding:12px 16px}.connect-grid{grid-template-columns:1fr}.overview-grid .overview-row{grid-template-columns:1fr;align-items:start}:host{display:flex;flex-direction:column;flex:1 1 auto;min-height:0}}\n"] }]
29613
+ ], providers: [TableConfigService], template: " <mat-tab-group class=\"config-tabs\" [(selectedIndex)]=\"activeSectionIndex\">\n <mat-tab *ngFor=\"let section of sections\">\n <ng-template mat-tab-label>\n @if (section.icon) { <mat-icon [praxisIcon]=\"section.icon\"></mat-icon> }\n <span [attr.data-testid]=\"'config-tab-' + section.id\">{{ section.label }}</span>\n </ng-template>\n <div class=\"tab-content\">\n <ng-container [ngSwitch]=\"section.id\">\n <div *ngSwitchCase=\"'connect'\" class=\"connect-grid\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Recurso</mat-label>\n <input matInput [(ngModel)]=\"resourcePath\" (ngModelChange)=\"onResourcePathChange($event)\" placeholder=\"ex.: employees\" />\n <mat-icon matSuffix>link</mat-icon>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Chave prim\u00E1ria</mat-label>\n <input matInput [(ngModel)]=\"idField\" (ngModelChange)=\"onIdFieldChange($event)\" placeholder=\"ex.: id, uuid, codigo\" />\n <mat-icon matSuffix>fingerprint</mat-icon>\n </mat-form-field>\n <p class=\"connect-note\">Defina o recurso da API e, se necess\u00E1rio, a chave prim\u00E1ria.</p>\n\n <!-- Diverg\u00EAncia: idField -->\n @if (idFieldDiverges) { <div class=\"alert alert--warning connect-alert\">\n <mat-icon class=\"alert__icon\">warning</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n A chave prim\u00E1ria no servidor diverge da configura\u00E7\u00E3o atual.\n <span class=\"connect-alert__meta\">(config: {{ idField || 'id' }} | servidor: {{ serverIdField || 'id' }})</span>\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onReconcileIdField()\">Reconciliar</button>\n </div> }\n\n <!-- Diverg\u00EAncia: serverHash -->\n @if (schemaHashDiverges) { <div class=\"alert alert--info connect-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n O snapshot de schema salvo diverge do schema atual do servidor.\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onAcceptServerHash()\">Atualizar metadados</button>\n </div> }\n\n @if (!schemaHashDiverges && schemaSnapshotMissing) { <div class=\"alert alert--info connect-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n Existe schema atual no servidor, mas ainda n\u00E3o h\u00E1 snapshot persistido para reconcilia\u00E7\u00E3o.\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onAcceptServerHash()\">Salvar snapshot</button>\n </div> }\n </div>\n\n <div *ngSwitchCase=\"'overview'\" class=\"overview-grid\">\n <div class=\"overview-row\">\n <mat-form-field appearance=\"outline\" class=\"hs-field\">\n <mat-label>Scroll Horizontal</mat-label>\n <mat-select [(ngModel)]=\"horizontalScroll\" (ngModelChange)=\"onHorizontalScrollChange($event)\">\n <mat-option value=\"auto\">Auto (padr\u00E3o)</mat-option>\n <mat-option value=\"wrap\">Wrap (quebrar linhas)</mat-option>\n <mat-option value=\"none\">Host controla</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"help small\">Controla como a tabela lida com largura horizontal e barra de rolagem.</div>\n </div>\n @if (isEffectiveLocalMode()) { <div class=\"alert alert--info overview-mode-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text\">\n Modo local efetivo ativo: estrat\u00E9gias de pagina\u00E7\u00E3o e ordena\u00E7\u00E3o em servidor ficam bloqueadas e s\u00E3o salvas como cliente.\n </div>\n </div> }\n <behavior-config-editor\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n [forceClientStrategies]=\"isEffectiveLocalMode()\"\n (configChange)=\"onBehaviorConfigChange($event)\"\n (behaviorChange)=\"onBehaviorChange($event)\"\n ></behavior-config-editor>\n </div>\n\n <columns-config-editor\n *ngSwitchCase=\"'columns'\"\n [config]=\"editedConfig\"\n (configChange)=\"onColumnsConfigChange($event)\"\n (columnChange)=\"onColumnChange($event)\"\n ></columns-config-editor>\n\n <table-rules-editor\n *ngSwitchCase=\"'rules'\"\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n (configChange)=\"onRulesConfigChange($event)\"\n ></table-rules-editor>\n\n <header-appearance-editor\n *ngSwitchCase=\"'header'\"\n [config]=\"editedConfig\"\n (configChange)=\"onColumnsConfigChange($event)\"\n ></header-appearance-editor>\n\n <toolbar-actions-editor\n *ngSwitchCase=\"'toolbar'\"\n [config]=\"editedConfig\"\n (configChange)=\"onToolbarActionsConfigChange($event)\"\n (toolbarActionsChange)=\"onToolbarActionsChange($event)\"\n ></toolbar-actions-editor>\n\n <!-- Aba extra para integra\u00E7\u00F5es CRUD (vis\u00EDvel quando em contexto CRUD) -->\n <crud-integration-editor\n #crudEditorRef\n *ngSwitchCase=\"'crud'\"\n [tableId]=\"crudContext?.tableId || 'default'\"\n [crudContext]=\"crudContext\"\n [componentKeyId]=\"crudContext?.componentKeyId\"\n ></crud-integration-editor>\n\n <filter-settings\n #tableFilterSettingsRef\n *ngSwitchCase=\"'filters'\"\n [metadata]=\"getFilterSettingsMetas()\"\n [metadataSource]=\"isUsingFilterDtoMetas() ? 'filter-dto' : 'columns'\"\n [metadataLoading]=\"filterDtoMetasLoading\"\n [metadataErrorMsg]=\"filterDtoMetasErrorMsg\"\n [settings]=\"\n editedConfig.behavior?.filtering?.advancedFilters?.settings\n \"\n (settingsChange)=\"onFilterSettingsChange($event)\"\n ></filter-settings>\n\n <messages-localization-editor\n *ngSwitchCase=\"'messages'\"\n [config]=\"editedConfig\"\n (configChange)=\"onMessagesLocalizationConfigChange($event)\"\n (messagesLocalizationChange)=\"\n onMessagesLocalizationChange($event)\n \"\n ></messages-localization-editor>\n\n <confirm-dialog-appearance-editor\n *ngSwitchCase=\"'dialogs'\"\n [config]=\"editedConfig\"\n (configChange)=\"onJsonConfigChange($event)\"\n ></confirm-dialog-appearance-editor>\n\n\n <json-config-editor\n *ngSwitchCase=\"'json'\"\n [document]=\"buildJsonAuthoringDocument()\"\n (documentChange)=\"onJsonConfigChange($event)\"\n (validationChange)=\"onJsonValidationChange($event)\"\n (editorEvent)=\"onJsonEditorEvent($event)\"\n ></json-config-editor>\n </ng-container>\n </div>\n </mat-tab>\n </mat-tab-group>\n@if (statusMessage) { <div class=\"config-editor-status\">\n <span\n class=\"status-text\"\n [class.error]=\"hasErrors\"\n [class.success]=\"hasSuccess\"\n >{{ statusMessage }}</span\n >\n </div> }\n", styles: ["@charset \"UTF-8\";.config-tabs{flex:1 1 auto;display:flex;flex-direction:column}.config-tabs .mat-mdc-tab{min-width:120px}.config-tabs .mat-mdc-tab-body-wrapper,.config-tabs .mat-mdc-tab-group-container{flex:1 1 auto;min-height:0}.config-tabs .mat-mdc-tab-body-content{height:100%;min-height:0;overflow:visible}.tab-content{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;padding:8px 8px 56px;box-sizing:border-box;overflow:visible}.overview-grid{display:grid;grid-template-columns:1fr;gap:12px}.overview-grid .overview-row{display:grid;grid-template-columns:280px 1fr;align-items:end;gap:12px}.overview-grid .overview-row .help.small{opacity:.75;font-size:12px}.overview-mode-alert{align-items:center}.connect-grid{display:grid;gap:12px;padding:8px;grid-template-columns:1fr 240px;align-items:start}.connect-note{grid-column:1/-1;margin:0;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.connect-alert{grid-column:1/-1;align-items:center}.connect-alert__text{flex:1}.connect-alert__meta{opacity:.85}.educational-card{margin-bottom:24px;background:var(--md-sys-color-surface-container-low);border-left:4px solid var(--md-sys-color-primary);flex-shrink:0}.config-editor-status{padding:12px 16px;margin-top:auto;border-top:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);flex-shrink:0;position:relative}.status-text{font-size:.875rem;line-height:1.2}.status-text.error{color:var(--md-sys-color-error)}.status-text.success{color:var(--md-sys-color-primary)}@media(max-width:768px){.tab-content{padding:8px}.config-editor-status{padding:12px 16px}.connect-grid{grid-template-columns:1fr}.overview-grid .overview-row{grid-template-columns:1fr;align-items:start}:host{display:flex;flex-direction:column;flex:1 1 auto;min-height:0}}\n"] }]
29305
29614
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i1.TableConfigService }], propDecorators: { filterSettingsEditor: [{
29306
29615
  type: ViewChild,
29307
29616
  args: ['tableFilterSettingsRef']
@@ -29650,6 +29959,11 @@ class PraxisFilter {
29650
29959
  configKey = null;
29651
29960
  lastSchemaMeta;
29652
29961
  schemaLoadVersion = 0;
29962
+ storedSchemaMetaCache = new Map();
29963
+ schemaMetaIndexCache;
29964
+ schemaMetaIndexLoadPromise;
29965
+ verifySchemaInFlight = new Map();
29966
+ lastSchemaVerificationByContext = new Map();
29653
29967
  // Outdated state and persistence helpers
29654
29968
  schemaOutdated = false;
29655
29969
  anchorRef;
@@ -30075,13 +30389,17 @@ class PraxisFilter {
30075
30389
  this.applyProvidedFieldMetadata();
30076
30390
  }
30077
30391
  else {
30078
- // Lightweight server schema verification (ETag/If-None-Match). Will not download schema body when columns already exist.
30079
- this.verifyServerSchemaVersion().catch(() => { });
30080
- // Only load schema body when necessary for rendering (alwaysVisible fields at init).
30081
- if ((this.alwaysVisibleFields?.length || 0) > 0) {
30392
+ const requiresSchemaBodyAtInit = (this.alwaysVisibleFields?.length || 0) > 0 ||
30393
+ (this.selectedFieldIds?.length || 0) > 0;
30394
+ // Skip the lightweight verification when we already know a full schema load is
30395
+ // required for rendering. loadSchemaViaClient() will fetch and persist metadata.
30396
+ if (requiresSchemaBodyAtInit) {
30082
30397
  this.logFilterDebug('[PFILTER] ngOnInit: alwaysVisible present -> loadSchema');
30083
30398
  this.loadSchema();
30084
30399
  }
30400
+ else {
30401
+ this.verifyServerSchemaVersion().catch(() => { });
30402
+ }
30085
30403
  }
30086
30404
  }
30087
30405
  ngAfterViewInit() {
@@ -30209,13 +30527,17 @@ class PraxisFilter {
30209
30527
  }
30210
30528
  else {
30211
30529
  this.schemaMetas = undefined;
30212
- // Verify lightweight on path changes; load schema body only if needed for rendering
30213
- this.verifyServerSchemaVersion().catch(() => { });
30214
- if ((this.alwaysVisibleFields?.length || 0) > 0) {
30530
+ const requiresSchemaBodyForPathChange = (this.alwaysVisibleFields?.length || 0) > 0 ||
30531
+ (this.selectedFieldIds?.length || 0) > 0;
30532
+ if (requiresSchemaBodyForPathChange) {
30215
30533
  this.logFilterDebug('[PFILTER] ngOnChanges: alwaysVisible present -> loadSchema');
30216
30534
  if (!this.schemaLoading)
30217
30535
  this.loadSchema();
30218
30536
  }
30537
+ else {
30538
+ // Verify lightweight on path changes only when a full schema fetch is not already required.
30539
+ this.verifyServerSchemaVersion().catch(() => { });
30540
+ }
30219
30541
  }
30220
30542
  }
30221
30543
  if (changes['fieldMetadata']) {
@@ -30774,33 +31096,31 @@ class PraxisFilter {
30774
31096
  try {
30775
31097
  const keys = new Set();
30776
31098
  const legacyScopeKey = this.getSchemaMetaKey();
30777
- if (legacyScopeKey)
31099
+ if (legacyScopeKey) {
30778
31100
  keys.add(legacyScopeKey);
31101
+ this.storedSchemaMetaCache.delete(legacyScopeKey);
31102
+ }
30779
31103
  if (options?.context) {
30780
31104
  const contextualKey = this.getSchemaMetaKeyForContext(options.context);
30781
- if (contextualKey)
31105
+ if (contextualKey) {
30782
31106
  keys.add(contextualKey);
31107
+ this.storedSchemaMetaCache.delete(contextualKey);
31108
+ }
30783
31109
  }
30784
31110
  if (options?.includeAllScoped) {
30785
31111
  const indexKey = this.filterSchemaMetaIndexKey();
30786
31112
  if (indexKey) {
30787
- this.configStorage
30788
- .loadConfig(indexKey)
30789
- .pipe(take(1))
30790
- .subscribe({
30791
- next: (storedIds) => {
30792
- const ids = Array.isArray(storedIds) ? storedIds : [];
30793
- ids.forEach((schemaId) => {
30794
- const key = this.filterSchemaMetaKeyForSchemaId(schemaId);
30795
- if (key)
30796
- this.clearConfigStorageKey(key);
30797
- });
30798
- this.clearConfigStorageKey(indexKey);
30799
- },
30800
- error: () => {
30801
- this.clearConfigStorageKey(indexKey);
30802
- },
31113
+ this.schemaMetaIndexCache = undefined;
31114
+ this.schemaMetaIndexLoadPromise = undefined;
31115
+ const ids = Array.from(this.schemaMetaIndexCache ?? []);
31116
+ ids.forEach((schemaId) => {
31117
+ const key = this.filterSchemaMetaKeyForSchemaId(schemaId);
31118
+ if (key) {
31119
+ this.storedSchemaMetaCache.delete(key);
31120
+ this.clearConfigStorageKey(key);
31121
+ }
30803
31122
  });
31123
+ this.clearConfigStorageKey(indexKey);
30804
31124
  }
30805
31125
  }
30806
31126
  keys.forEach((key) => this.clearConfigStorageKey(key));
@@ -30812,6 +31132,11 @@ class PraxisFilter {
30812
31132
  clearConfigStorageKey(key) {
30813
31133
  if (!key)
30814
31134
  return;
31135
+ this.storedSchemaMetaCache.delete(key);
31136
+ if (key === this.filterSchemaMetaIndexKey()) {
31137
+ this.schemaMetaIndexCache = undefined;
31138
+ this.schemaMetaIndexLoadPromise = undefined;
31139
+ }
30815
31140
  try {
30816
31141
  this.configStorage.clearConfig(key).pipe(take(1)).subscribe();
30817
31142
  }
@@ -30822,35 +31147,79 @@ class PraxisFilter {
30822
31147
  loadStoredSchemaMeta(key) {
30823
31148
  if (!key)
30824
31149
  return Promise.resolve(undefined);
31150
+ if (this.storedSchemaMetaCache.has(key)) {
31151
+ const cached = this.storedSchemaMetaCache.get(key);
31152
+ return Promise.resolve(cached ?? undefined);
31153
+ }
30825
31154
  return firstValueFrom(this.configStorage.loadConfig(key))
30826
- .then((value) => value ?? undefined)
31155
+ .then((value) => {
31156
+ const next = value ?? null;
31157
+ this.storedSchemaMetaCache.set(key, next);
31158
+ return next ?? undefined;
31159
+ })
30827
31160
  .catch(() => undefined);
30828
31161
  }
30829
31162
  rememberSchemaMetaContext(schemaId) {
30830
31163
  const id = String(schemaId || '').trim();
30831
31164
  if (!id)
30832
31165
  return;
30833
- const indexKey = this.filterSchemaMetaIndexKey();
30834
- if (!indexKey)
31166
+ const index = this.schemaMetaIndexCache ?? new Set();
31167
+ index.add(id);
31168
+ this.schemaMetaIndexCache = index;
31169
+ }
31170
+ ensureSchemaMetaIndexLoaded(indexKey) {
31171
+ if (this.schemaMetaIndexCache) {
31172
+ return Promise.resolve(this.schemaMetaIndexCache);
31173
+ }
31174
+ if (this.schemaMetaIndexLoadPromise) {
31175
+ return this.schemaMetaIndexLoadPromise;
31176
+ }
31177
+ this.schemaMetaIndexLoadPromise = Promise.resolve(new Set()).then((next) => {
31178
+ this.schemaMetaIndexCache = next;
31179
+ this.schemaMetaIndexLoadPromise = undefined;
31180
+ return next;
31181
+ });
31182
+ return this.schemaMetaIndexLoadPromise;
31183
+ }
31184
+ saveStoredSchemaMeta(key, next, schemaId) {
31185
+ const current = this.storedSchemaMetaCache.get(key) ?? null;
31186
+ if (this.stableSerialize(this.normalizeStoredSchemaMetaForComparison(current)) === this.stableSerialize(this.normalizeStoredSchemaMetaForComparison(next))) {
31187
+ if (schemaId)
31188
+ this.rememberSchemaMetaContext(schemaId);
30835
31189
  return;
31190
+ }
31191
+ this.storedSchemaMetaCache.set(key, next);
31192
+ this.configStorage.saveConfig(key, next).pipe(take(1)).subscribe();
31193
+ if (schemaId)
31194
+ this.rememberSchemaMetaContext(schemaId);
31195
+ }
31196
+ stableSerialize(value) {
30836
31197
  try {
30837
- this.configStorage
30838
- .loadConfig(indexKey)
30839
- .pipe(take(1))
30840
- .subscribe((stored) => {
30841
- const unique = new Set((Array.isArray(stored) ? stored : [])
30842
- .map((item) => String(item || '').trim())
30843
- .filter((item) => !!item));
30844
- unique.add(id);
30845
- this.configStorage
30846
- .saveConfig(indexKey, Array.from(unique))
30847
- .pipe(take(1))
30848
- .subscribe();
31198
+ return JSON.stringify(value, (_key, nested) => {
31199
+ if (!nested || typeof nested !== 'object' || Array.isArray(nested)) {
31200
+ return nested;
31201
+ }
31202
+ const ordered = {};
31203
+ for (const key of Object.keys(nested).sort()) {
31204
+ ordered[key] = nested[key];
31205
+ }
31206
+ return ordered;
30849
31207
  });
30850
31208
  }
30851
31209
  catch {
30852
- // ignore index update failures
31210
+ return String(value);
31211
+ }
31212
+ }
31213
+ normalizeStoredSchemaMetaForComparison(value) {
31214
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
31215
+ return value;
30853
31216
  }
31217
+ const record = { ...value };
31218
+ delete record['lastVerifiedAt'];
31219
+ return record;
31220
+ }
31221
+ schemaVerificationContextKey(schemaId, context) {
31222
+ return `${schemaId}|${this.stableSerialize(context)}`;
30854
31223
  }
30855
31224
  getSchemaMetaKeyForContext(context) {
30856
31225
  try {
@@ -30899,21 +31268,16 @@ class PraxisFilter {
30899
31268
  if (!key)
30900
31269
  return;
30901
31270
  const nowIso = new Date().toISOString();
30902
- this.configStorage
30903
- .loadConfig(key)
30904
- .pipe(take(1))
30905
- .subscribe((prev) => {
30906
- const next = {
30907
- ...(prev || {}),
30908
- schemaId,
30909
- schemaContext: context,
30910
- lastVerifiedAt: nowIso,
30911
- };
30912
- if (serverHash)
30913
- next.serverHash = serverHash;
30914
- this.configStorage.saveConfig(key, next).pipe(take(1)).subscribe();
30915
- });
30916
- this.rememberSchemaMetaContext(schemaId);
31271
+ const prev = this.storedSchemaMetaCache.get(key) ?? null;
31272
+ const next = {
31273
+ ...(prev || {}),
31274
+ schemaId,
31275
+ schemaContext: context,
31276
+ lastVerifiedAt: nowIso,
31277
+ };
31278
+ if (serverHash)
31279
+ next.serverHash = serverHash;
31280
+ this.saveStoredSchemaMeta(key, next, schemaId);
30917
31281
  }
30918
31282
  catch {
30919
31283
  // no-op: schema metadata persistence is best effort
@@ -32212,6 +32576,31 @@ class PraxisFilter {
32212
32576
  const currentCtx = this.buildFilteredSchemaContext();
32213
32577
  const currentSchemaContext = this.toSchemaIdContext(currentCtx);
32214
32578
  const currentSchemaId = buildSchemaId(currentSchemaContext);
32579
+ const verifyContextKey = this.schemaVerificationContextKey(currentSchemaId, currentSchemaContext);
32580
+ const inFlight = this.verifySchemaInFlight.get(verifyContextKey);
32581
+ if (inFlight) {
32582
+ await inFlight;
32583
+ return;
32584
+ }
32585
+ const verifiedAt = this.lastSchemaVerificationByContext.get(verifyContextKey) || 0;
32586
+ if (verifiedAt && Date.now() - verifiedAt < 5000) {
32587
+ return;
32588
+ }
32589
+ const task = this.runSchemaVerification(currentCtx, currentSchemaContext, currentSchemaId, verifyContextKey);
32590
+ this.verifySchemaInFlight.set(verifyContextKey, task);
32591
+ try {
32592
+ await task;
32593
+ }
32594
+ finally {
32595
+ this.verifySchemaInFlight.delete(verifyContextKey);
32596
+ }
32597
+ }
32598
+ catch {
32599
+ // ignore verification errors; filter remains functional
32600
+ }
32601
+ }
32602
+ async runSchemaVerification(currentCtx, currentSchemaContext, currentSchemaId, verifyContextKey) {
32603
+ try {
32215
32604
  const scopedMetaKey = this.getSchemaMetaKeyForContext(currentSchemaContext) || this.getSchemaMetaKey();
32216
32605
  if (!scopedMetaKey)
32217
32606
  return;
@@ -32253,13 +32642,10 @@ class PraxisFilter {
32253
32642
  }
32254
32643
  if (res.status === 304) {
32255
32644
  try {
32256
- this.configStorage
32257
- .saveConfig(scopedMetaKey, metaToSave)
32258
- .pipe(take(1))
32259
- .subscribe();
32260
- this.rememberSchemaMetaContext(currentSchemaId);
32645
+ this.saveStoredSchemaMeta(scopedMetaKey, metaToSave, currentSchemaId);
32261
32646
  }
32262
32647
  catch { }
32648
+ this.lastSchemaVerificationByContext.set(verifyContextKey, Date.now());
32263
32649
  if (this.schemaOutdated)
32264
32650
  this.schemaOutdated = false;
32265
32651
  this.schemaStatusChange.emit({ outdated: false, serverHash: metaToSave.serverHash, lastVerifiedAt: metaToSave.lastVerifiedAt, formId: this.formId });
@@ -32269,13 +32655,10 @@ class PraxisFilter {
32269
32655
  const newHash = res.schemaHash;
32270
32656
  metaToSave.serverHash = newHash;
32271
32657
  try {
32272
- this.configStorage
32273
- .saveConfig(scopedMetaKey, metaToSave)
32274
- .pipe(take(1))
32275
- .subscribe();
32276
- this.rememberSchemaMetaContext(currentSchemaId);
32658
+ this.saveStoredSchemaMeta(scopedMetaKey, metaToSave, currentSchemaId);
32277
32659
  }
32278
32660
  catch { }
32661
+ this.lastSchemaVerificationByContext.set(verifyContextKey, Date.now());
32279
32662
  // Only mark outdated when there was a previous base (hash or schemaMetas) and we are in edit mode
32280
32663
  const hadBase = !!previousHash || !!this.schemaMetas?.length;
32281
32664
  this.schemaOutdated = !!(this.editModeEnabled && hadBase);
@@ -33521,6 +33904,7 @@ class PraxisTable {
33521
33904
  expansionDetailAbortByRowKey = new Map();
33522
33905
  expansionDetailActiveTabByRowKey = new Map();
33523
33906
  schemaFieldsSnapshot = [];
33907
+ runtimeSchemaMeta = {};
33524
33908
  schemaError = false;
33525
33909
  dataError = false;
33526
33910
  errorMessage = null;
@@ -34072,6 +34456,9 @@ class PraxisTable {
34072
34456
  this.selection.clear();
34073
34457
  }
34074
34458
  catch { }
34459
+ if (!(this.uncontrolledExpandedRowKeys instanceof Set)) {
34460
+ this.uncontrolledExpandedRowKeys = new Set();
34461
+ }
34075
34462
  this.uncontrolledExpandedRowKeys.clear();
34076
34463
  this.clearExpansionDetailRuntime();
34077
34464
  }
@@ -34408,7 +34795,7 @@ class PraxisTable {
34408
34795
  if (this.aiAdapter || this.aiAdapterLoadStarted)
34409
34796
  return;
34410
34797
  this.aiAdapterLoadStarted = true;
34411
- import('./praxisui-table-table-ai.adapter-DAmsfime.mjs')
34798
+ import('./praxisui-table-table-ai.adapter-C5rjLb8E.mjs')
34412
34799
  .then(({ TableAiAdapter }) => {
34413
34800
  this.aiAdapter = new TableAiAdapter(this);
34414
34801
  this.cdr.markForCheck();
@@ -34554,6 +34941,11 @@ class PraxisTable {
34554
34941
  catch { }
34555
34942
  // Apply global defaults as fallback only for missing properties
34556
34943
  this.ensureConfigDefaults();
34944
+ // ngOnInit is async and Angular does not await it before ngAfterContentInit.
34945
+ // Rebuild column runtime state here so hydrated persisted config can replace
34946
+ // any columns already derived from the initial @Input() config.
34947
+ this.setupColumns();
34948
+ this.applyDefaultSortIfNone();
34557
34949
  // Re-apply unsupported-feature guards after persisted config hydration.
34558
34950
  this.enforceUnsupportedFeatureGuards();
34559
34951
  this.syncHorizontalScrollFromAppearanceConfig();
@@ -34695,66 +35087,6 @@ class PraxisTable {
34695
35087
  inputsKey() {
34696
35088
  return this.storageKey('table-inputs:');
34697
35089
  }
34698
- normalizeResourcePathIntent(value) {
34699
- if (value === 'set' || value === 'clear' || value === 'unchanged') {
34700
- return value;
34701
- }
34702
- return null;
34703
- }
34704
- resolveResourcePathIntentFromEditor(cfg) {
34705
- const explicitIntent = this.normalizeResourcePathIntent(cfg?.__resourcePathIntent__);
34706
- if (explicitIntent)
34707
- return explicitIntent;
34708
- const hasLegacyPathMarker = Object.prototype.hasOwnProperty.call(cfg || {}, '__resourcePath__');
34709
- if (!hasLegacyPathMarker)
34710
- return 'unchanged';
34711
- const legacyPath = cfg?.__resourcePath__ || '';
34712
- return legacyPath.trim().length > 0 ? 'set' : 'unchanged';
34713
- }
34714
- applyResourcePathIntentFromEditor(cfg) {
34715
- const intent = this.resolveResourcePathIntentFromEditor(cfg);
34716
- const nextPath = (cfg?.__resourcePath__ || '').trim();
34717
- if (intent === 'clear') {
34718
- if (!this.hasExplicitResourcePath())
34719
- return false;
34720
- const previousPath = this.resourcePath;
34721
- this.disconnect();
34722
- this.debugLog('Editor resourcePath intent applied', {
34723
- intent,
34724
- from: (previousPath || '').trim(),
34725
- to: '',
34726
- });
34727
- return true;
34728
- }
34729
- if (intent === 'set') {
34730
- if (!nextPath)
34731
- return false;
34732
- if (nextPath === (this.resourcePath ?? '').trim())
34733
- return false;
34734
- this.resourcePath = nextPath;
34735
- this.reconcileDataModeTransition('editor-set-resourcePath');
34736
- this.clearSchemaFieldsSnapshot();
34737
- this.saveConnection('editor');
34738
- this.schemaError = false;
34739
- this.dataError = false;
34740
- this.errorMessage = null;
34741
- this.clearLocalScaffolding();
34742
- this.crudService.configure(this.resourcePath);
34743
- if ((this.config?.columns?.length ?? 0) > 0) {
34744
- this.verifyServerSchemaVersion().catch(() => { });
34745
- }
34746
- else {
34747
- this.loadSchema();
34748
- }
34749
- this.fetchData();
34750
- this.debugLog('Editor resourcePath intent applied', {
34751
- intent,
34752
- to: nextPath,
34753
- });
34754
- return true;
34755
- }
34756
- return false;
34757
- }
34758
35090
  disconnect() {
34759
35091
  try {
34760
35092
  const key = this.connectionKey();
@@ -36980,106 +37312,27 @@ class PraxisTable {
36980
37312
  content: { component: PraxisTableConfigEditor, inputs: panelInputs },
36981
37313
  });
36982
37314
  this.subscriptions.push(ref.applied$.subscribe((val) => {
36983
- const cfg = val;
36984
- if (!cfg)
37315
+ if (!val)
36985
37316
  return;
36986
- this.debugLog('[PraxisTable] Applied config', cfg);
36987
- const newIdField = cfg.__idField__ || '';
36988
- const newHs = cfg.__horizontalScroll__ || undefined;
36989
- this.applyResourcePathIntentFromEditor(cfg);
36990
- if (newIdField && newIdField !== this.idField) {
36991
- this.idField = newIdField;
36992
- }
36993
- if (newHs && newHs !== this.horizontalScroll) {
36994
- this.horizontalScroll = newHs;
36995
- this.horizontalScrollOverrideSource = 'editor';
36996
- const inputsKey = this.inputsKey();
36997
- if (inputsKey) {
36998
- this.persistHorizontalScrollInput(inputsKey, newHs, 'settings-applied');
36999
- }
37000
- }
37001
- // Persistir em memória no TableConfig.meta com idField resolvido (mesmo se usuário não alterou)
37002
- const metaApply = { ...(cfg.meta || {}) };
37003
- const resolvedIdApply = this.getIdField();
37004
- if (resolvedIdApply)
37005
- metaApply.idField = resolvedIdApply;
37006
- if (!metaApply.version)
37007
- metaApply.version = '2.0.0';
37008
- const nowApply = new Date().toISOString();
37009
- if (!metaApply.createdAt)
37010
- metaApply.createdAt = nowApply;
37011
- metaApply.updatedAt = nowApply;
37012
- const infoApply = this.crudService.getLastSchemaInfo?.();
37013
- if (infoApply?.schemaId)
37014
- metaApply.schemaId = infoApply.schemaId;
37015
- if (infoApply?.schemaHash)
37016
- metaApply.serverHash = infoApply.schemaHash;
37017
- cfg.meta = metaApply;
37018
- // Remove internal marker before saving/applying
37019
- if ('__resourcePath__' in cfg)
37020
- delete cfg.__resourcePath__;
37021
- if ('__resourcePathIntent__' in cfg)
37022
- delete cfg.__resourcePathIntent__;
37023
- if ('__idField__' in cfg)
37024
- delete cfg.__idField__;
37025
- if ('__horizontalScroll__' in cfg)
37026
- delete cfg.__horizontalScroll__;
37027
- this.applyTableConfig(cfg);
37317
+ const doc = parseLegacyOrTableDocument(val);
37318
+ this.debugLog('[PraxisTable] Applied config', doc);
37319
+ const plan = buildTableApplyPlan(doc, this.buildTableEditorRuntimeContext(), {
37320
+ saveConfig: false,
37321
+ saveBindings: true,
37322
+ attachServerMeta: true,
37323
+ });
37324
+ this.executeTableEditorApplyPlan(plan, doc, 'settings-applied');
37028
37325
  }), ref.saved$.subscribe((val) => {
37029
- const cfg = val;
37030
- if (!cfg)
37326
+ if (!val)
37031
37327
  return;
37032
- this.debugLog('[PraxisTable] Saved config', cfg);
37033
- const newIdField = cfg.__idField__ || '';
37034
- const newHs = cfg.__horizontalScroll__ || undefined;
37035
- this.applyResourcePathIntentFromEditor(cfg);
37036
- if (newIdField && newIdField !== this.idField) {
37037
- this.idField = newIdField;
37038
- }
37039
- if (newHs && newHs !== this.horizontalScroll) {
37040
- this.horizontalScroll = newHs;
37041
- this.horizontalScrollOverrideSource = 'editor';
37042
- const inputsKey = this.inputsKey();
37043
- if (inputsKey) {
37044
- this.persistHorizontalScrollInput(inputsKey, newHs, 'settings-saved');
37045
- }
37046
- }
37047
- // Persistir meta no JSON salvo (sempre definir idField resolvido)
37048
- const metaSave = { ...(cfg.meta || {}) };
37049
- if (!metaSave.version)
37050
- metaSave.version = '2.0.0';
37051
- const now = new Date().toISOString();
37052
- if (!metaSave.createdAt)
37053
- metaSave.createdAt = now;
37054
- metaSave.updatedAt = now;
37055
- const info = this.crudService.getLastSchemaInfo?.();
37056
- if (info?.schemaId)
37057
- metaSave.schemaId = info.schemaId;
37058
- if (info?.schemaHash)
37059
- metaSave.serverHash = info.schemaHash;
37060
- const resolvedIdSave = this.getIdField();
37061
- if (resolvedIdSave)
37062
- metaSave.idField = resolvedIdSave;
37063
- cfg.meta = metaSave;
37064
- if ('__resourcePath__' in cfg)
37065
- delete cfg.__resourcePath__;
37066
- if ('__resourcePathIntent__' in cfg)
37067
- delete cfg.__resourcePathIntent__;
37068
- if ('__idField__' in cfg)
37069
- delete cfg.__idField__;
37070
- if ('__horizontalScroll__' in cfg)
37071
- delete cfg.__horizontalScroll__;
37072
- const cfgKey = this.tableConfigKey();
37073
- if (cfgKey) {
37074
- this.saveConfigWithAck(cfgKey, cfg, 'table-settings-save', {
37075
- onSettled: (persisted) => {
37076
- if (!persisted) {
37077
- this.showPersistenceFailureFeedback('table-settings-save');
37078
- }
37079
- },
37080
- });
37081
- }
37082
- this.applyTableConfig(cfg);
37328
+ const doc = parseLegacyOrTableDocument(val);
37329
+ this.debugLog('[PraxisTable] Saved config', doc);
37330
+ const plan = buildTableApplyPlan(doc, this.buildTableEditorRuntimeContext(), {
37331
+ saveConfig: true,
37332
+ saveBindings: true,
37333
+ attachServerMeta: true,
37334
+ });
37335
+ this.executeTableEditorApplyPlan(plan, doc, 'settings-saved');
37083
37336
  }), ref.reset$.subscribe(() => {
37084
37337
  this.debugLog('[PraxisTable] Resetting to default config');
37085
37338
  this.resetPreferencesToDefaults({
@@ -37094,25 +37347,21 @@ class PraxisTable {
37094
37347
  }
37095
37348
  }
37096
37349
  applyTableConfig(cfg) {
37097
- const safeCfg = this.withFallbackAdvancedFilterSettings(cfg);
37098
- this.debugLog('[PraxisTable] Applying table config', safeCfg);
37099
- this.config = { ...safeCfg };
37350
+ this.debugLog('[PraxisTable] Applying table config', cfg);
37351
+ this.config = { ...cfg };
37352
+ this.syncRuntimeSchemaMetaFromConfig();
37100
37353
  this.ensureConfigDefaults();
37101
37354
  this.enforceUnsupportedFeatureGuards();
37102
37355
  this.syncHorizontalScrollFromAppearanceConfig();
37103
- const advancedFilters = safeCfg.behavior?.filtering?.advancedFilters;
37356
+ const advancedFilters = cfg.behavior?.filtering?.advancedFilters;
37104
37357
  const hasExplicitFilterSettings = !!advancedFilters &&
37105
37358
  Object.prototype.hasOwnProperty.call(advancedFilters, 'settings');
37106
37359
  const filterSettings = hasExplicitFilterSettings
37107
37360
  ? advancedFilters?.settings
37108
37361
  : undefined;
37109
37362
  this.debugLog('[PraxisTable] Applying filter settings', filterSettings);
37110
- const filterKey = this.filterConfigKey();
37111
- if (filterKey && hasExplicitFilterSettings) {
37112
- this.filterConfig.save(filterKey, (filterSettings || {}));
37113
- }
37114
- this.setShowToolbar(!!(safeCfg.toolbar?.visible ||
37115
- safeCfg.behavior?.filtering?.advancedFilters?.enabled), true);
37363
+ this.setShowToolbar(!!(cfg.toolbar?.visible ||
37364
+ cfg.behavior?.filtering?.advancedFilters?.enabled), true);
37116
37365
  this.syncInternalFilterSettings(filterSettings);
37117
37366
  this.debugLog('[PraxisTable] Toolbar visibility after apply', this.showToolbar);
37118
37367
  this.setupColumns();
@@ -37128,27 +37377,125 @@ class PraxisTable {
37128
37377
  }
37129
37378
  this.cdr.detectChanges();
37130
37379
  }
37131
- withFallbackAdvancedFilterSettings(cfg) {
37132
- const incomingAdvanced = cfg?.behavior?.filtering?.advancedFilters;
37133
- if (!incomingAdvanced)
37134
- return cfg;
37135
- if (Object.prototype.hasOwnProperty.call(incomingAdvanced, 'settings')) {
37136
- return cfg;
37380
+ buildTableEditorRuntimeContext() {
37381
+ const info = this.crudService.getLastSchemaInfo?.();
37382
+ return {
37383
+ hasLocalDataInput: this.hasLocalDataInput(),
37384
+ localDataModeDefaultEnabled: this.isLocalDataModeFeatureEnabled(),
37385
+ server: {
37386
+ idField: this.crudService.getResourceIdField?.(),
37387
+ schemaId: info?.schemaId,
37388
+ schemaHash: info?.schemaHash,
37389
+ },
37390
+ crud: {
37391
+ tableId: this.tableId,
37392
+ componentKeyId: this.componentKeyId() || undefined,
37393
+ },
37394
+ currentBindings: {
37395
+ resourcePath: (this.resourcePath || '').trim() || null,
37396
+ idField: this.getIdField(),
37397
+ horizontalScroll: this.horizontalScroll,
37398
+ },
37399
+ };
37400
+ }
37401
+ executeTableEditorApplyPlan(plan, doc, trigger) {
37402
+ if (plan.diagnostics.some((item) => item.level === 'error')) {
37403
+ this.warnLog('[PraxisTable] Editor document validation failed', {
37404
+ diagnostics: plan.diagnostics,
37405
+ });
37406
+ return;
37407
+ }
37408
+ const nextBindings = plan.bindingsPatch || {};
37409
+ const nextPath = (nextBindings.resourcePath || '').trim();
37410
+ const shouldReconnect = plan.runtime?.reconnectDataSource === true;
37411
+ const shouldReloadSchema = plan.runtime?.reloadSchema === true;
37412
+ const shouldFetchData = plan.runtime?.fetchData === true;
37413
+ const shouldRefreshLocalView = plan.runtime?.refreshLocalView === true;
37414
+ if (shouldReconnect) {
37415
+ if (nextPath) {
37416
+ this.resourcePath = nextPath;
37417
+ this.reconcileDataModeTransition('editor-set-resourcePath');
37418
+ this.clearSchemaFieldsSnapshot();
37419
+ this.saveConnection('editor');
37420
+ this.schemaError = false;
37421
+ this.dataError = false;
37422
+ this.errorMessage = null;
37423
+ this.clearLocalScaffolding();
37424
+ this.crudService.configure(this.resourcePath);
37425
+ }
37426
+ else {
37427
+ this.disconnect();
37428
+ }
37429
+ }
37430
+ if (nextBindings.idField && nextBindings.idField !== this.idField) {
37431
+ this.idField = nextBindings.idField;
37432
+ }
37433
+ const nextHs = nextBindings.horizontalScroll;
37434
+ if (nextHs && nextHs !== this.horizontalScroll) {
37435
+ this.horizontalScroll = nextHs;
37436
+ this.horizontalScrollOverrideSource = 'editor';
37437
+ const inputsKey = this.inputsKey();
37438
+ if (inputsKey && plan.persistence?.saveBindings) {
37439
+ this.persistHorizontalScrollInput(inputsKey, nextHs, trigger);
37440
+ }
37441
+ }
37442
+ const cfg = this.attachRuntimeMetadataToEditorConfig(plan.canonicalConfig, plan.metadata?.attachServerMeta === true, plan.persistence?.saveConfig === true);
37443
+ if (plan.persistence?.saveConfig) {
37444
+ const cfgKey = this.tableConfigKey();
37445
+ if (cfgKey) {
37446
+ this.saveConfigWithAck(cfgKey, cfg, 'table-settings-save', {
37447
+ onSettled: (persisted) => {
37448
+ if (!persisted) {
37449
+ this.showPersistenceFailureFeedback('table-settings-save');
37450
+ }
37451
+ },
37452
+ });
37453
+ }
37137
37454
  }
37138
- const previousSettings = this.config?.behavior?.filtering?.advancedFilters?.settings;
37139
- if (!previousSettings || typeof previousSettings !== 'object') {
37455
+ this.applyTableConfig(cfg);
37456
+ if (shouldReconnect && nextPath) {
37457
+ if (shouldReloadSchema) {
37458
+ if ((doc.config?.columns?.length ?? 0) > 0) {
37459
+ this.verifyServerSchemaVersion().catch(() => { });
37460
+ }
37461
+ else {
37462
+ this.loadSchema();
37463
+ }
37464
+ }
37465
+ else if (shouldFetchData) {
37466
+ this.fetchData();
37467
+ }
37468
+ }
37469
+ else if (shouldRefreshLocalView) {
37470
+ this.refreshLocalScaffolding('editor-apply-plan', {
37471
+ resetPageSizeOverride: true,
37472
+ });
37473
+ }
37474
+ }
37475
+ attachRuntimeMetadataToEditorConfig(cfg, attachServerMeta, touchPersistenceMeta) {
37476
+ if (!attachServerMeta && !touchPersistenceMeta) {
37140
37477
  return cfg;
37141
37478
  }
37142
- const cloned = JSON.parse(JSON.stringify(cfg));
37143
- const behavior = cloned.behavior || {};
37144
- const filtering = behavior.filtering || {};
37145
- filtering.advancedFilters = {
37146
- ...(filtering.advancedFilters || {}),
37147
- settings: JSON.parse(JSON.stringify(previousSettings)),
37148
- };
37149
- behavior.filtering = filtering;
37150
- cloned.behavior = behavior;
37151
- return cloned;
37479
+ const next = { ...cfg };
37480
+ const meta = { ...(next.meta || {}) };
37481
+ if (touchPersistenceMeta) {
37482
+ const now = new Date().toISOString();
37483
+ if (!meta.version)
37484
+ meta.version = '2.0.0';
37485
+ if (!meta.createdAt)
37486
+ meta.createdAt = now;
37487
+ meta.updatedAt = now;
37488
+ }
37489
+ if (attachServerMeta) {
37490
+ // Persist only a reconciliation snapshot. Runtime drift detection uses runtimeSchemaMeta.
37491
+ const info = this.crudService.getLastSchemaInfo?.();
37492
+ if (info?.schemaId)
37493
+ meta.schemaId = info.schemaId;
37494
+ if (info?.schemaHash)
37495
+ meta.serverHash = info.schemaHash;
37496
+ }
37497
+ next.meta = meta;
37498
+ return next;
37152
37499
  }
37153
37500
  syncInternalFilterSettings(settings) {
37154
37501
  const filter = this.internalFilter;
@@ -37333,6 +37680,9 @@ class PraxisTable {
37333
37680
  return safe;
37334
37681
  }
37335
37682
  setupColumns() {
37683
+ if (!(this.uncontrolledExpandedRowKeys instanceof Set)) {
37684
+ this.uncontrolledExpandedRowKeys = new Set();
37685
+ }
37336
37686
  const columns = this.sanitizeColumns(this.config.columns);
37337
37687
  this.clearEffectiveCellRendererCache();
37338
37688
  this.visibleColumns = columns
@@ -38157,7 +38507,7 @@ class PraxisTable {
38157
38507
  u.searchParams.set('operation', 'get');
38158
38508
  u.searchParams.set('schemaType', 'response');
38159
38509
  u.searchParams.set('includeInternalSchemas', 'false');
38160
- const serverHash = this.config?.meta?.serverHash;
38510
+ const serverHash = this.runtimeSchemaMeta.serverHash;
38161
38511
  const hadServerHash = !!serverHash;
38162
38512
  const tenant = (() => { try {
38163
38513
  return this.global.getTenant();
@@ -38173,39 +38523,30 @@ class PraxisTable {
38173
38523
  } })();
38174
38524
  const res = await fetchWithETag({ url: u.toString(), schemaHash: serverHash, tenant, locale });
38175
38525
  const nowIso = new Date().toISOString();
38176
- const meta = { ...(this.config?.meta || {}) };
38177
- if (!meta.version)
38178
- meta.version = '2.0.0';
38179
- if (!meta.createdAt)
38180
- meta.createdAt = nowIso;
38181
- meta.lastVerifiedAt = nowIso;
38182
38526
  if (res.status === 304) {
38183
- // Only update lastVerifiedAt and persist (keep using existing columns)
38184
- this.config.meta = meta;
38185
- const cfgKey = this.tableConfigKey();
38186
- if (cfgKey) {
38187
- this.saveConfigWithAck(cfgKey, this.config, 'schema-verification-304');
38188
- }
38527
+ this.runtimeSchemaMeta = {
38528
+ ...this.runtimeSchemaMeta,
38529
+ lastVerifiedAt: nowIso,
38530
+ };
38189
38531
  // Keep outdated false and emit status
38190
38532
  if (this.schemaOutdated) {
38191
38533
  this.schemaOutdated = false;
38192
38534
  }
38193
38535
  this.emitMetadataChange('verification');
38194
- this.emitSchemaStatus({ outdated: false, serverHash: this.config?.meta?.serverHash, lastVerifiedAt: meta.lastVerifiedAt, resourcePath: this.resourcePath }, 'verification-304');
38536
+ this.emitSchemaStatus({ outdated: false, serverHash: this.runtimeSchemaMeta.serverHash, lastVerifiedAt: this.runtimeSchemaMeta.lastVerifiedAt, resourcePath: this.resourcePath }, 'verification-304');
38195
38537
  return;
38196
38538
  }
38197
38539
  // status === 200: server hash changed or first verification without hash. Do not apply columns here.
38198
38540
  const newHash = res.schemaHash;
38199
- meta.serverHash = newHash;
38200
- this.config.meta = meta;
38541
+ this.runtimeSchemaMeta = {
38542
+ ...this.runtimeSchemaMeta,
38543
+ serverHash: newHash,
38544
+ lastVerifiedAt: nowIso,
38545
+ };
38201
38546
  const changed = !!(hadServerHash && newHash && newHash !== serverHash);
38202
38547
  this.schemaOutdated = this.editModeEnabled && changed; // only notify in edit mode when we can compare
38203
- const cfgKey = this.tableConfigKey();
38204
- if (cfgKey) {
38205
- this.saveConfigWithAck(cfgKey, this.config, 'schema-verification-200');
38206
- }
38207
38548
  this.emitMetadataChange('verification');
38208
- this.emitSchemaStatus({ outdated: this.schemaOutdated, serverHash: meta.serverHash, lastVerifiedAt: meta.lastVerifiedAt, resourcePath: this.resourcePath }, 'verification-200');
38549
+ this.emitSchemaStatus({ outdated: this.schemaOutdated, serverHash: this.runtimeSchemaMeta.serverHash, lastVerifiedAt: this.runtimeSchemaMeta.lastVerifiedAt, resourcePath: this.resourcePath }, 'verification-200');
38209
38550
  // Notifications only if edit mode
38210
38551
  if (this.schemaOutdated) {
38211
38552
  if (this._resolvedPrefs.autoOpenSettingsOnOutdated) {
@@ -38231,28 +38572,28 @@ class PraxisTable {
38231
38572
  next: (fields) => {
38232
38573
  this.schemaError = false;
38233
38574
  this.setSchemaFieldsSnapshot(fields);
38234
- // Se idField não foi definido via inputs/contexto, tentar derivar do schema (x-ui.resource.idField)
38575
+ // Se idField não foi definido via inputs/contexto, tentar derivar do schema/serviço.
38235
38576
  try {
38236
- const derived = this.idField || this.crudContext?.idField || this.config?.meta?.idField || this.crudService.getResourceIdField();
38577
+ const derived = this.idField || this.crudContext?.idField || this.crudService.getResourceIdField();
38237
38578
  if (derived && (!this.idField || this.idField === 'id')) {
38238
38579
  this.idField = derived;
38239
38580
  }
38240
- // Alertar divergência entre config.meta.idField e o idField vindo do serviço
38241
- const cfgId = this.config?.meta?.idField;
38581
+ // Alertar divergência entre o idField efetivo em uso e o idField vindo do serviço.
38582
+ const cfgId = this.idField;
38242
38583
  const svcId = this.crudService.getResourceIdField();
38243
- if (cfgId && svcId && cfgId !== svcId) {
38584
+ if (cfgId && cfgId !== 'id' && svcId && cfgId !== svcId) {
38244
38585
  try {
38245
- this.snackBar.open('Aviso: idField do TableConfig diverge do servidor', undefined, { duration: 4000 });
38586
+ this.snackBar.open('Aviso: idField efetivo diverge do servidor', undefined, { duration: 4000 });
38246
38587
  }
38247
38588
  catch { }
38248
38589
  this.warnLog('[PraxisTable] Divergência idField', { config: cfgId, server: svcId });
38249
38590
  }
38250
38591
  }
38251
38592
  catch { }
38252
- // Atualizar metadados de schema (schemaId/serverHash) e idField no TableConfig (em memória)
38593
+ // Atualizar metadados transitórios de schema em memória.
38253
38594
  try {
38254
38595
  const info = this.crudService.getLastSchemaInfo?.();
38255
- this.updateTableMetaFromServerInfo({ schemaId: info?.schemaId, schemaHash: info?.schemaHash }, { updateVerifiedAt: true, touchUpdatedAt: true });
38596
+ this.updateRuntimeSchemaMetaFromServerInfo({ schemaId: info?.schemaId, schemaHash: info?.schemaHash }, { updateVerifiedAt: true });
38256
38597
  }
38257
38598
  catch { }
38258
38599
  const existing = this.config?.columns ?? [];
@@ -38273,14 +38614,13 @@ class PraxisTable {
38273
38614
  }
38274
38615
  // Notificar hosts sobre atualização de metadados de schema ao concluir o loadSchema
38275
38616
  try {
38276
- const meta = this.config?.meta || {};
38277
38617
  // Ao carregar o schema com sucesso, consideramos que não há desatualização pendente
38278
38618
  this.schemaOutdated = false;
38279
38619
  this.emitMetadataChange('bootstrap');
38280
38620
  this.emitSchemaStatus({
38281
38621
  outdated: false,
38282
- serverHash: meta.serverHash,
38283
- lastVerifiedAt: meta.lastVerifiedAt,
38622
+ serverHash: this.runtimeSchemaMeta.serverHash,
38623
+ lastVerifiedAt: this.runtimeSchemaMeta.lastVerifiedAt,
38284
38624
  resourcePath: this.resourcePath,
38285
38625
  }, 'bootstrap');
38286
38626
  }
@@ -40931,14 +41271,14 @@ class PraxisTable {
40931
41271
  isOutdatedIgnored(serverHash) {
40932
41272
  if (!serverHash)
40933
41273
  return false;
40934
- if (this.config?.meta?.serverHash === serverHash)
41274
+ if (this.runtimeSchemaMeta.serverHash === serverHash)
40935
41275
  return this.schemaState.ignored;
40936
41276
  return false;
40937
41277
  }
40938
41278
  getOutdatedSnoozeUntil(serverHash) {
40939
41279
  if (!serverHash)
40940
41280
  return undefined;
40941
- if (this.config?.meta?.serverHash === serverHash)
41281
+ if (this.runtimeSchemaMeta.serverHash === serverHash)
40942
41282
  return this.schemaState.snoozeUntil;
40943
41283
  return undefined;
40944
41284
  }
@@ -40950,12 +41290,12 @@ class PraxisTable {
40950
41290
  return;
40951
41291
  if (until) {
40952
41292
  this.saveConfigWithAck(key, until.toISOString(), 'schema-outdated-snooze');
40953
- if (this.config?.meta?.serverHash === serverHash)
41293
+ if (this.runtimeSchemaMeta.serverHash === serverHash)
40954
41294
  this.schemaState.snoozeUntil = until.getTime();
40955
41295
  }
40956
41296
  else {
40957
41297
  this.clearConfigWithAck(key, 'schema-outdated-snooze-clear');
40958
- if (this.config?.meta?.serverHash === serverHash)
41298
+ if (this.runtimeSchemaMeta.serverHash === serverHash)
40959
41299
  this.schemaState.snoozeUntil = 0;
40960
41300
  }
40961
41301
  }
@@ -40967,12 +41307,12 @@ class PraxisTable {
40967
41307
  return;
40968
41308
  if (val) {
40969
41309
  this.saveConfigWithAck(key, true, 'schema-outdated-ignore');
40970
- if (this.config?.meta?.serverHash === serverHash)
41310
+ if (this.runtimeSchemaMeta.serverHash === serverHash)
40971
41311
  this.schemaState.ignored = true;
40972
41312
  }
40973
41313
  else {
40974
41314
  this.clearConfigWithAck(key, 'schema-outdated-ignore-clear');
40975
- if (this.config?.meta?.serverHash === serverHash)
41315
+ if (this.runtimeSchemaMeta.serverHash === serverHash)
40976
41316
  this.schemaState.ignored = false;
40977
41317
  }
40978
41318
  }
@@ -40983,13 +41323,13 @@ class PraxisTable {
40983
41323
  if (!key)
40984
41324
  return;
40985
41325
  this.saveConfigWithAck(key, true, 'schema-outdated-notified');
40986
- if (this.config?.meta?.serverHash === serverHash)
41326
+ if (this.runtimeSchemaMeta.serverHash === serverHash)
40987
41327
  this.schemaState.notified = true;
40988
41328
  }
40989
41329
  wasOutdatedNotified(serverHash) {
40990
41330
  if (!serverHash)
40991
41331
  return false;
40992
- if (this.config?.meta?.serverHash === serverHash)
41332
+ if (this.runtimeSchemaMeta.serverHash === serverHash)
40993
41333
  return this.schemaState.notified;
40994
41334
  return false;
40995
41335
  }
@@ -41044,7 +41384,7 @@ class PraxisTable {
41044
41384
  return;
41045
41385
  if (this.resolvedPrefs.notifyIfOutdated === 'none')
41046
41386
  return;
41047
- const serverHash = this.config?.meta?.serverHash;
41387
+ const serverHash = this.runtimeSchemaMeta.serverHash;
41048
41388
  if (!serverHash || !this.schemaOutdated)
41049
41389
  return;
41050
41390
  if (this.isOutdatedIgnored(serverHash))
@@ -41071,7 +41411,7 @@ class PraxisTable {
41071
41411
  catch { }
41072
41412
  }
41073
41413
  shouldShowOutdatedInline() {
41074
- const hash = this.config?.meta?.serverHash;
41414
+ const hash = this.runtimeSchemaMeta.serverHash;
41075
41415
  const inlineEnabled = this.resolvedPrefs.notifyIfOutdated === 'inline' || this.resolvedPrefs.notifyIfOutdated === 'both';
41076
41416
  return (inlineEnabled &&
41077
41417
  this.schemaOutdated &&
@@ -41084,7 +41424,7 @@ class PraxisTable {
41084
41424
  this.verifyServerSchemaVersion().catch(() => { });
41085
41425
  }
41086
41426
  onSnoozeOutdated() {
41087
- const hash = this.config?.meta?.serverHash;
41427
+ const hash = this.runtimeSchemaMeta.serverHash;
41088
41428
  if (hash) {
41089
41429
  // Default snooze 24h
41090
41430
  const until = new Date(Date.now() + (this.snoozeMs ?? 24 * 60 * 60 * 1000));
@@ -41093,33 +41433,27 @@ class PraxisTable {
41093
41433
  this.schemaOutdated = false;
41094
41434
  }
41095
41435
  onIgnoreOutdated() {
41096
- const hash = this.config?.meta?.serverHash;
41436
+ const hash = this.runtimeSchemaMeta.serverHash;
41097
41437
  if (hash) {
41098
41438
  this.setOutdatedIgnore(hash, true);
41099
41439
  }
41100
41440
  this.schemaOutdated = false;
41101
41441
  }
41102
- updateTableMetaFromServerInfo(info, opts = {}) {
41103
- const meta = { ...(this.config.meta || {}) };
41442
+ updateRuntimeSchemaMetaFromServerInfo(info, opts = {}) {
41443
+ const next = { ...this.runtimeSchemaMeta };
41104
41444
  if (info.schemaId)
41105
- meta.schemaId = info.schemaId;
41445
+ next.schemaId = info.schemaId;
41106
41446
  if (info.schemaHash)
41107
- meta.serverHash = info.schemaHash;
41447
+ next.serverHash = info.schemaHash;
41108
41448
  const now = new Date().toISOString();
41109
41449
  if (opts.updateVerifiedAt)
41110
- meta.lastVerifiedAt = now;
41111
- if (opts.touchUpdatedAt)
41112
- meta.updatedAt = now;
41113
- if (!meta.version)
41114
- meta.version = '2.0.0';
41115
- if (!meta.createdAt)
41116
- meta.createdAt = now;
41117
- this.config.meta = meta;
41450
+ next.lastVerifiedAt = now;
41451
+ this.runtimeSchemaMeta = next;
41118
41452
  }
41119
41453
  emitMetadataChange(trigger) {
41120
41454
  this.metadataChange.emit({
41121
41455
  trigger,
41122
- meta: this.config.meta,
41456
+ meta: this.getEffectiveMetadataSnapshot(),
41123
41457
  tableId: this.tableId
41124
41458
  });
41125
41459
  }
@@ -41135,7 +41469,32 @@ class PraxisTable {
41135
41469
  return this.getNestedPropertyValue(row, idField);
41136
41470
  }
41137
41471
  getIdField() {
41138
- return this.idField || this.config?.meta?.idField || 'id';
41472
+ return this.idField || 'id';
41473
+ }
41474
+ syncRuntimeSchemaMetaFromConfig() {
41475
+ const meta = (this.config?.meta || {});
41476
+ this.runtimeSchemaMeta = {
41477
+ schemaId: typeof meta.schemaId === 'string' && meta.schemaId.trim()
41478
+ ? meta.schemaId.trim()
41479
+ : undefined,
41480
+ serverHash: typeof meta.serverHash === 'string' && meta.serverHash.trim()
41481
+ ? meta.serverHash.trim()
41482
+ : undefined,
41483
+ lastVerifiedAt: typeof meta.lastVerifiedAt === 'string' && meta.lastVerifiedAt.trim()
41484
+ ? meta.lastVerifiedAt.trim()
41485
+ : undefined,
41486
+ };
41487
+ }
41488
+ getEffectiveMetadataSnapshot() {
41489
+ const baseMeta = { ...(this.config?.meta || {}) };
41490
+ if (this.runtimeSchemaMeta.schemaId)
41491
+ baseMeta.schemaId = this.runtimeSchemaMeta.schemaId;
41492
+ if (this.runtimeSchemaMeta.serverHash)
41493
+ baseMeta.serverHash = this.runtimeSchemaMeta.serverHash;
41494
+ if (this.runtimeSchemaMeta.lastVerifiedAt) {
41495
+ baseMeta.lastVerifiedAt = this.runtimeSchemaMeta.lastVerifiedAt;
41496
+ }
41497
+ return baseMeta;
41139
41498
  }
41140
41499
  getActionId(action) {
41141
41500
  return getActionId(action);
@@ -42172,7 +42531,7 @@ const TABLE_AI_CAPABILITIES = {
42172
42531
  { 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"' },
42173
42532
  { 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."' },
42174
42533
  { path: 'meta.tags', category: 'meta', valueKind: 'array', description: 'Tags para busca, presets e telemetria.', intentExamples: ['adicionar tag demo', 'marcar como financeiro'], example: '["financeiro","demo"]' },
42175
- { path: 'meta.idField', category: 'meta', valueKind: 'string', description: 'Campo usado como chave primária 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.' },
42534
+ { 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.' },
42176
42535
  { 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"' },
42177
42536
  // Columns — propriedades base
42178
42537
  { 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"' },
@@ -42850,5 +43209,5 @@ const TABLE_COMPONENT_AI_CAPABILITIES = {
42850
43209
  * Generated bundle index. Do not edit.
42851
43210
  */
42852
43211
 
42853
- export { BOOLEAN_PRESETS, BehaviorConfigEditorComponent, CURRENCY_PRESETS, ColumnsConfigEditorComponent, DATE_PRESETS, DataFormatterComponent, DataFormattingService, FORMULA_TEMPLATES, FilterConfigService, FilterSettingsComponent, FormulaGeneratorService, JsonConfigEditorComponent, MessagesLocalizationEditorComponent, NUMBER_PRESETS, PERCENTAGE_PRESETS, PRAXIS_TABLE_COMPONENT_METADATA, PraxisFilter, PraxisTable, PraxisTableConfigEditor, PraxisTableToolbar, STRING_PRESETS, TABLE_AI_CAPABILITIES, TABLE_COMPONENT_AI_CAPABILITIES, TASK_PRESETS, TableDefaultsProvider, TableRulesEditorComponent, ToolbarActionsEditorComponent, ValueMappingEditorComponent, VisualFormulaBuilderComponent, getActionId, getEnum, getTableCapabilities, hasJsonKey, jsonPathGet, jsonPathMatches, providePraxisTableMetadata, registerDateDslFunctions, registerJsonDslFunctions };
43212
+ export { BOOLEAN_PRESETS, BehaviorConfigEditorComponent, CURRENCY_PRESETS, ColumnsConfigEditorComponent, DATE_PRESETS, DataFormatterComponent, DataFormattingService, FORMULA_TEMPLATES, FilterConfigService, FilterSettingsComponent, FormulaGeneratorService, JsonConfigEditorComponent, MessagesLocalizationEditorComponent, NUMBER_PRESETS, PERCENTAGE_PRESETS, PRAXIS_TABLE_COMPONENT_METADATA, PraxisFilter, PraxisTable, PraxisTableConfigEditor, PraxisTableToolbar, STRING_PRESETS, TABLE_AI_CAPABILITIES, TABLE_COMPONENT_AI_CAPABILITIES, TASK_PRESETS, TableDefaultsProvider, TableRulesEditorComponent, ToolbarActionsEditorComponent, ValueMappingEditorComponent, VisualFormulaBuilderComponent, buildTableApplyPlan, createTableAuthoringDocument, getActionId, getEnum, getTableCapabilities, hasJsonKey, jsonPathGet, jsonPathMatches, normalizeTableAuthoringDocument, parseLegacyOrTableDocument, providePraxisTableMetadata, registerDateDslFunctions, registerJsonDslFunctions, serializeTableAuthoringDocument, toCanonicalTableConfig, validateTableAuthoringDocument };
42854
43213
  //# sourceMappingURL=praxisui-table.mjs.map