@praxisui/table 10.0.0-rc.37 → 10.0.0-rc.39

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.
@@ -1,5 +1,6 @@
1
1
  import { validateRichContentDocument, isSafeRichContentClassName } from '@praxisui/rich-content';
2
2
  import { resolveColumnTypeFromFieldDefinition, deepMerge, isPraxisPresentationVisualizationTableSafe, resolveTextMaskFormatFromFieldDefinition } from '@praxisui/core/schema';
3
+ import { uiLayoutJsonIdentity, cloneBoundedUiLayoutJsonDocument, derivePageChildRuntimeId } from '@praxisui/core';
3
4
 
4
5
  const ORIGIN = '__praxisAuthoredDisabled';
5
6
  const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
@@ -32,18 +33,28 @@ function identity(action) {
32
33
  }
33
34
  /** Projects only canonical action slots, never arbitrary business payloads or callback values. */
34
35
  function projectTableActionAuthoring(config, authored, report) {
36
+ const hasAuthoredBaseline = authored !== undefined && authored !== null;
35
37
  function actions(value, original, path) {
36
38
  if (!Array.isArray(value))
37
39
  return value;
38
40
  const originals = Array.isArray(original) ? original.filter(isAction) : [];
39
- return value.map((item, index) => {
40
- if (!isAction(item))
41
- return item;
41
+ const projected = [];
42
+ value.forEach((item, index) => {
43
+ if (!isAction(item)) {
44
+ projected.push(item);
45
+ return;
46
+ }
42
47
  const key = identity(item);
43
48
  const matches = key ? originals.filter(candidate => identity(candidate) === key) : [];
44
49
  // No label matching, positional matching or guessing across duplicate identities.
45
50
  const source = matches.length === 1 && value.filter(candidate => isAction(candidate) && identity(candidate) === key).length === 1
46
51
  ? matches[0] : undefined;
52
+ const isRuntimeSynthesizedOnly = Boolean(item['__praxisDiscoveredAction']) ||
53
+ Boolean(item['__praxisBulkSyncAction']) ||
54
+ (hasAuthoredBaseline && Boolean(item['__praxisCollectionCreateAction']));
55
+ if (isRuntimeSynthesizedOnly && !source) {
56
+ return;
57
+ }
47
58
  const result = { ...item };
48
59
  if (hasRuntimeState(item)) {
49
60
  const origin = source && !hasRuntimeState(source) ? authoredDisabled(source) : authoredDisabled(item);
@@ -63,8 +74,9 @@ function projectTableActionAuthoring(config, authored, report) {
63
74
  }
64
75
  if (hasOwn(item, 'children'))
65
76
  result['children'] = actions(item['children'], source?.['children'], `${path}[${index}].children`);
66
- return result;
77
+ projected.push(result);
67
78
  });
79
+ return projected;
68
80
  }
69
81
  const result = { ...config };
70
82
  if (config?.toolbar)
@@ -1241,69 +1253,113 @@ class TableSchemaColumnProjector {
1241
1253
  }
1242
1254
  inferFieldTypeFromFieldName(fieldName) {
1243
1255
  const lowercaseName = fieldName.toLowerCase();
1244
- // Date/time patterns - more specific patterns first
1256
+ // Date/time patterns - more specific patterns first (EN + PT-BR)
1245
1257
  if (lowercaseName.endsWith('date') ||
1246
1258
  lowercaseName.endsWith('time') ||
1247
1259
  lowercaseName.endsWith('at') ||
1248
1260
  lowercaseName.startsWith('date') ||
1261
+ lowercaseName.startsWith('data') ||
1262
+ lowercaseName.startsWith('dt_') ||
1263
+ lowercaseName.startsWith('dt') ||
1264
+ lowercaseName.endsWith('_em') ||
1249
1265
  lowercaseName === 'created' ||
1250
1266
  lowercaseName === 'updated' ||
1251
1267
  lowercaseName === 'modified' ||
1268
+ lowercaseName === 'nascimento' ||
1269
+ lowercaseName === 'admissao' ||
1270
+ lowercaseName === 'demissao' ||
1271
+ lowercaseName === 'vencimento' ||
1252
1272
  lowercaseName.includes('timestamp')) {
1253
1273
  return 'date';
1254
1274
  }
1255
- // Boolean patterns - most specific first to avoid conflicts
1275
+ // Boolean patterns - most specific first to avoid conflicts (EN + PT-BR)
1256
1276
  if (lowercaseName.startsWith('is_') ||
1257
1277
  lowercaseName.startsWith('has_') ||
1258
1278
  lowercaseName.startsWith('can_') ||
1259
1279
  lowercaseName.startsWith('should_') ||
1280
+ lowercaseName.startsWith('fl_') ||
1281
+ lowercaseName.startsWith('ind_') ||
1260
1282
  lowercaseName === 'active' ||
1283
+ lowercaseName === 'ativo' ||
1284
+ lowercaseName === 'inativo' ||
1261
1285
  lowercaseName === 'enabled' ||
1286
+ lowercaseName === 'habilitado' ||
1262
1287
  lowercaseName === 'visible' ||
1288
+ lowercaseName === 'visivel' ||
1263
1289
  lowercaseName === 'deleted' ||
1290
+ lowercaseName === 'excluido' ||
1264
1291
  lowercaseName === 'archived' ||
1292
+ lowercaseName === 'arquivado' ||
1265
1293
  lowercaseName.endsWith('_flag') ||
1266
1294
  lowercaseName.endsWith('_enabled') ||
1267
- lowercaseName.endsWith('_active')) {
1295
+ lowercaseName.endsWith('_active') ||
1296
+ lowercaseName.endsWith('_ativo')) {
1268
1297
  return 'boolean';
1269
1298
  }
1270
- // Currency/money patterns - exclude common false positives
1299
+ // Currency/money patterns - exclude common false positives (EN + PT-BR)
1271
1300
  if ((lowercaseName.includes('price') ||
1301
+ lowercaseName.includes('preco') ||
1272
1302
  lowercaseName.includes('amount') ||
1273
1303
  lowercaseName.includes('cost') ||
1304
+ lowercaseName.includes('custo') ||
1274
1305
  lowercaseName.includes('salary') ||
1306
+ lowercaseName.includes('salario') ||
1307
+ lowercaseName.includes('remuneracao') ||
1308
+ lowercaseName.includes('orcamento') ||
1309
+ lowercaseName.includes('indenizacao') ||
1310
+ lowercaseName.includes('multa') ||
1311
+ lowercaseName.includes('desconto') ||
1312
+ lowercaseName.includes('liquido') ||
1313
+ lowercaseName.includes('bruto') ||
1275
1314
  lowercaseName.includes('wage') ||
1276
1315
  lowercaseName.includes('fee') ||
1277
- (lowercaseName.includes('value') &&
1316
+ ((lowercaseName.includes('value') || lowercaseName.includes('valor') || lowercaseName.startsWith('vl_')) &&
1278
1317
  !lowercaseName.includes('id') &&
1279
1318
  !lowercaseName.includes('key'))) &&
1280
1319
  !lowercaseName.includes('count') &&
1281
- !lowercaseName.includes('type')) {
1320
+ !lowercaseName.includes('type') &&
1321
+ !lowercaseName.includes('tipo')) {
1282
1322
  return 'currency';
1283
1323
  }
1284
- // Percentage patterns - be more specific
1324
+ // Percentage patterns - be more specific (EN + PT-BR)
1285
1325
  if (lowercaseName.includes('percent') ||
1326
+ lowercaseName.includes('percentual') ||
1327
+ lowercaseName.includes('aliquota') ||
1328
+ lowercaseName.includes('margem') ||
1329
+ lowercaseName.startsWith('pct_') ||
1286
1330
  lowercaseName.endsWith('_rate') ||
1287
1331
  lowercaseName.endsWith('_ratio') ||
1288
1332
  lowercaseName.endsWith('_pct') ||
1289
- (lowercaseName.includes('rate') &&
1333
+ lowercaseName.endsWith('_taxa') ||
1334
+ ((lowercaseName.includes('rate') || lowercaseName.includes('taxa')) &&
1290
1335
  !lowercaseName.includes('created') &&
1291
1336
  !lowercaseName.includes('updated')) ||
1292
1337
  lowercaseName.endsWith('_score')) {
1293
1338
  return 'percentage';
1294
1339
  }
1295
- // Number patterns - exclude common false positives like string IDs
1340
+ // Number patterns - exclude common false positives like string IDs (EN + PT-BR)
1296
1341
  if ((lowercaseName.endsWith('_id') && lowercaseName !== 'id') || // composite IDs might be strings
1297
1342
  lowercaseName === 'id' ||
1298
1343
  lowercaseName.includes('count') ||
1299
1344
  lowercaseName.includes('quantity') ||
1345
+ lowercaseName.includes('quantidade') ||
1346
+ lowercaseName.includes('qtd') ||
1300
1347
  lowercaseName.includes('number') ||
1348
+ lowercaseName.includes('numero') ||
1301
1349
  lowercaseName.includes('total') ||
1302
1350
  lowercaseName.includes('sum') ||
1351
+ lowercaseName.includes('soma') ||
1303
1352
  lowercaseName.includes('age') ||
1353
+ lowercaseName.includes('idade') ||
1304
1354
  lowercaseName.includes('weight') ||
1355
+ lowercaseName.includes('peso') ||
1305
1356
  lowercaseName.includes('height') ||
1357
+ lowercaseName.includes('altura') ||
1306
1358
  lowercaseName.includes('size') ||
1359
+ lowercaseName.includes('tamanho') ||
1360
+ lowercaseName.includes('pontuacao') ||
1361
+ lowercaseName.includes('score') ||
1362
+ lowercaseName.includes('nota') ||
1307
1363
  lowercaseName.endsWith('_num') ||
1308
1364
  lowercaseName.endsWith('_number')) {
1309
1365
  return 'number';
@@ -1319,12 +1375,13 @@ class TableSchemaColumnProjector {
1319
1375
  const valuePresentationType = this.normalizePresentationToken(field.valuePresentation?.type);
1320
1376
  const displayFormat = this.firstNonEmptyString(field.displayFormat, field.extraProperties?.displayFormat);
1321
1377
  const valueMapping = this.resolveSchemaValueMapping(field);
1322
- if (valueMapping) {
1378
+ if (valueMapping && (!col.valueMapping || Object.keys(col.valueMapping).length === 0)) {
1323
1379
  col.valueMapping = valueMapping;
1324
1380
  }
1325
1381
  const presentation = field.presentation;
1326
1382
  if (presentation?.presenter === 'microVisualization' &&
1327
- isPraxisPresentationVisualizationTableSafe(presentation?.visualization)) {
1383
+ isPraxisPresentationVisualizationTableSafe(presentation?.visualization) &&
1384
+ !col.renderer?.type) {
1328
1385
  col.renderer = {
1329
1386
  ...(col.renderer || {}),
1330
1387
  type: 'microVisualization',
@@ -1335,8 +1392,11 @@ class TableSchemaColumnProjector {
1335
1392
  }
1336
1393
  this.applySchemaSemanticCellPresentation(field, col, presentation);
1337
1394
  if (numericFormat === 'currency' || controlType === 'currency' || col.type === 'currency') {
1395
+ const wasCurrency = col.type === 'currency';
1338
1396
  col.type = 'currency';
1339
- col.format = this.resolveSchemaCurrencyFormat(field);
1397
+ if (!col.format || (!wasCurrency && !String(col.format).includes('|'))) {
1398
+ col.format = this.resolveSchemaCurrencyFormat(field);
1399
+ }
1340
1400
  return;
1341
1401
  }
1342
1402
  if (numericFormat === 'date' ||
@@ -1349,13 +1409,15 @@ class TableSchemaColumnProjector {
1349
1409
  controlType === 'date' ||
1350
1410
  controlType === 'datetime') {
1351
1411
  col.type = 'date';
1352
- col.format = displayFormat || (numericFormat === 'datetime' ||
1353
- valuePresentationType === 'datetime' ||
1354
- controlType === 'datetimepicker' ||
1355
- controlType === 'datetimelocal' ||
1356
- controlType === 'datetime'
1357
- ? 'dd/MM/yyyy HH:mm'
1358
- : 'dd/MM/yyyy');
1412
+ if (!col.format) {
1413
+ col.format = displayFormat || (numericFormat === 'datetime' ||
1414
+ valuePresentationType === 'datetime' ||
1415
+ controlType === 'datetimepicker' ||
1416
+ controlType === 'datetimelocal' ||
1417
+ controlType === 'datetime'
1418
+ ? 'dd/MM/yyyy HH:mm'
1419
+ : 'dd/MM/yyyy');
1420
+ }
1359
1421
  return;
1360
1422
  }
1361
1423
  if (col.type === 'boolean' && !col.format) {
@@ -1364,7 +1426,10 @@ class TableSchemaColumnProjector {
1364
1426
  const schemaDataType = this.normalizePresentationToken(field.type);
1365
1427
  const schemaFormat = this.normalizePresentationToken(field.format);
1366
1428
  if (col.type === 'number' && !col.format) {
1367
- if (schemaDataType === 'integer' ||
1429
+ if (displayFormat) {
1430
+ col.format = displayFormat;
1431
+ }
1432
+ else if (schemaDataType === 'integer' ||
1368
1433
  schemaFormat === 'integer' ||
1369
1434
  schemaFormat === 'int32' ||
1370
1435
  schemaFormat === 'int64' ||
@@ -1408,10 +1473,12 @@ class TableSchemaColumnProjector {
1408
1473
  size: 32,
1409
1474
  },
1410
1475
  };
1411
- // Sensible defaults for avatar column
1476
+ // Sensible defaults for avatar column (84px prevents header truncation when centered with sort/resize gutters; not sortable by default)
1412
1477
  col.align = col.align || 'center';
1478
+ if (col.sortable === undefined)
1479
+ col.sortable = false;
1413
1480
  if (!col.width)
1414
- col.width = '56px';
1481
+ col.width = '84px';
1415
1482
  return;
1416
1483
  }
1417
1484
  }
@@ -1726,8 +1793,381 @@ function deriveTableEditorDocument(input) {
1726
1793
  });
1727
1794
  }
1728
1795
 
1796
+ /** Shared presentation ordering; hidden columns must not shift visible fallback positions. */
1797
+ function orderTableColumns(columns) {
1798
+ if (!columns.some(column => Number.isFinite(column.order)))
1799
+ return [...columns];
1800
+ let visibleIndex = 0;
1801
+ return columns.map((column, originalIndex) => {
1802
+ const fallback = column.visible === false ? originalIndex : visibleIndex++;
1803
+ return { column, originalIndex, explicit: Number.isFinite(column.order), order: Number.isFinite(column.order) ? Number(column.order) : fallback };
1804
+ }).sort((left, right) => left.order - right.order
1805
+ || Number(right.explicit) - Number(left.explicit)
1806
+ || left.originalIndex - right.originalIndex).map(entry => entry.column);
1807
+ }
1808
+
1809
+ /** Preserve authored values; runtime/schema defaults remain transient unless explicitly edited. */
1810
+ function applyTableAuthoringChanges(authored, baseline, edited) {
1811
+ return projectChanges(authored, baseline, edited, true);
1812
+ }
1813
+ function projectChanges(authored, baseline, edited, tableConfigRoot) {
1814
+ if (sameAuthoringValue(baseline, edited))
1815
+ return authored;
1816
+ if (!edited || typeof edited !== 'object' || Array.isArray(edited))
1817
+ return edited;
1818
+ const original = authored && typeof authored === 'object' ? authored : {};
1819
+ const before = baseline && typeof baseline === 'object' ? baseline : {};
1820
+ const after = edited;
1821
+ const result = { ...original };
1822
+ for (const key of new Set([...Object.keys(before), ...Object.keys(after)])) {
1823
+ if (sameAuthoringValue(before[key], after[key]))
1824
+ continue;
1825
+ if (!Object.hasOwn(after, key))
1826
+ delete result[key];
1827
+ else
1828
+ Object.defineProperty(result, key, {
1829
+ value: projectChanges(Object.hasOwn(original, key) ? original[key] : undefined, Object.hasOwn(before, key) ? before[key] : undefined, after[key], false),
1830
+ enumerable: true, configurable: true, writable: true,
1831
+ });
1832
+ }
1833
+ if (tableConfigRoot && isRecord(original['columnProjection']) && original['columnProjection']['source'] === 'schema'
1834
+ && isRecord(result['columnProjection']) && result['columnProjection']['source'] === 'schema'
1835
+ && Array.isArray(after['columns']) && JSON.stringify(before['columns']) !== JSON.stringify(after['columns'])) {
1836
+ result['columns'] = [];
1837
+ result['columnProjection'] = projectSchemaColumnChanges(result['columnProjection'], before['columns'], after['columns'], isRecord(before['columnProjection']) ? before['columnProjection'] : original['columnProjection'], after['columnProjection']);
1838
+ }
1839
+ return result;
1840
+ }
1841
+ /** Write the existing compact schema projection, never a partial columns snapshot. */
1842
+ function projectSchemaColumnChanges(authored, baseline, edited, baselineProjection, editedProjection) {
1843
+ if (!isRecord(authored) || authored['source'] !== 'schema') {
1844
+ throw new Error('Table schema authorship requires its canonical columnProjection.');
1845
+ }
1846
+ const projection = structuredClone(authored);
1847
+ const overrides = Object.assign(Object.create(null), isRecord(projection['overrides']) ? projection['overrides'] : {});
1848
+ const additions = columnsByField(Array.isArray(projection['additions']) ? projection['additions'] : []);
1849
+ const before = columnsByField(Array.isArray(baseline) ? baseline : []);
1850
+ const after = columnsByField(edited);
1851
+ const nextAdditions = [...additions].filter(([field]) => !before.has(field) && !after.has(field))
1852
+ .map(([, column]) => structuredClone(column));
1853
+ for (const [field, column] of after) {
1854
+ if (additions.has(field) || !before.has(field)) {
1855
+ nextAdditions.push(structuredClone(column));
1856
+ continue;
1857
+ }
1858
+ const previousOverride = isRecord(overrides[field]) ? overrides[field] : {};
1859
+ if (Object.hasOwn(previousOverride, 'field')) {
1860
+ throw new Error('Table schema overrides cannot replace canonical field identity.');
1861
+ }
1862
+ const projected = projectChanges({ field, ...previousOverride }, before.get(field), column, false);
1863
+ const override = isRecord(projected) ? { ...projected } : {};
1864
+ // A direct edit of native intent is stronger evidence than a rematerialized snapshot.
1865
+ const baselineOverrides = isRecord(baselineProjection) && isRecord(baselineProjection['overrides'])
1866
+ ? baselineProjection['overrides'] : {};
1867
+ const editedOverrides = isRecord(editedProjection) && isRecord(editedProjection['overrides'])
1868
+ ? editedProjection['overrides'] : {};
1869
+ const oldIntent = isRecord(baselineOverrides[field]) ? baselineOverrides[field] : {};
1870
+ const newIntent = isRecord(editedOverrides[field]) ? editedOverrides[field] : {};
1871
+ if (isRecord(editedProjection)) {
1872
+ for (const key of new Set([...Object.keys(oldIntent), ...Object.keys(newIntent)])) {
1873
+ if (!changedNativeValue(oldIntent, newIntent, key))
1874
+ continue;
1875
+ if (Object.hasOwn(previousOverride, key))
1876
+ override[key] = structuredClone(previousOverride[key]);
1877
+ else
1878
+ delete override[key];
1879
+ }
1880
+ }
1881
+ delete override['field'];
1882
+ delete override['order']; // Ordering has a dedicated projection field.
1883
+ if (Object.keys(override).length)
1884
+ overrides[field] = override;
1885
+ else
1886
+ delete overrides[field];
1887
+ }
1888
+ // Missing fields do not erase their overrides; membership and intent are separate.
1889
+ const previousOrder = orderedFields(before, additions);
1890
+ const nextOrder = orderedFields(after, additions).filter(field => before.has(field));
1891
+ if (!changedNativeValue(baselineProjection, editedProjection, 'include')
1892
+ && previousOrder.some(field => !nextOrder.includes(field))) {
1893
+ const include = Array.isArray(projection['include']) ? projection['include'] : [];
1894
+ projection['include'] = [...nextOrder, ...include.filter(field => !before.has(field))];
1895
+ }
1896
+ if (!changedNativeValue(baselineProjection, editedProjection, 'order')
1897
+ && JSON.stringify(previousOrder) !== JSON.stringify(nextOrder)) {
1898
+ const order = Array.isArray(projection['order']) ? projection['order'] : [];
1899
+ projection['order'] = [...nextOrder, ...order.filter(field => !before.has(field) && !nextOrder.includes(field))];
1900
+ }
1901
+ if (Object.keys(overrides).length)
1902
+ projection['overrides'] = overrides;
1903
+ else
1904
+ delete projection['overrides'];
1905
+ if (nextAdditions.length)
1906
+ projection['additions'] = nextAdditions;
1907
+ else
1908
+ delete projection['additions'];
1909
+ return projection;
1910
+ }
1911
+ function changedNativeValue(before, after, key) {
1912
+ if (!isRecord(after))
1913
+ return false;
1914
+ const previous = isRecord(before) ? before : {};
1915
+ return Object.hasOwn(previous, key) !== Object.hasOwn(after, key)
1916
+ || !sameAuthoringValue(previous[key], after[key]);
1917
+ }
1918
+ function orderedFields(columns, additions) {
1919
+ return orderTableColumns([...columns].map(([field, column]) => ({
1920
+ field, order: typeof column['order'] === 'number' ? column['order'] : undefined,
1921
+ visible: typeof column['visible'] === 'boolean' ? column['visible'] : undefined,
1922
+ }))).map(column => column.field).filter(field => !additions.has(field));
1923
+ }
1924
+ function columnsByField(columns) {
1925
+ const result = new Map();
1926
+ for (const column of columns) {
1927
+ if (!isRecord(column) || typeof column['field'] !== 'string' || !column['field'].trim()) {
1928
+ throw new Error('Table authoring requires a non-empty column field identity.');
1929
+ }
1930
+ const identity = column['field'].trim();
1931
+ if (result.has(identity))
1932
+ throw new Error('Table authoring requires unique column field identities.');
1933
+ result.set(identity, column);
1934
+ }
1935
+ return result;
1936
+ }
1937
+ function isRecord(value) {
1938
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
1939
+ }
1940
+ /** Object insertion order is not authored intent; undefined is an internal absence sentinel. */
1941
+ function sameAuthoringValue(left, right) {
1942
+ if (left === undefined || right === undefined)
1943
+ return left === right;
1944
+ try {
1945
+ return uiLayoutJsonIdentity(left) === uiLayoutJsonIdentity(right);
1946
+ }
1947
+ catch {
1948
+ if (Array.isArray(left) || Array.isArray(right))
1949
+ return Array.isArray(left) && Array.isArray(right)
1950
+ && left.length === right.length && left.every((value, index) => sameAuthoringValue(value, right[index]));
1951
+ if (!isRecord(left) || !isRecord(right))
1952
+ return left === right;
1953
+ const keys = Object.keys(left).filter(key => left[key] !== undefined);
1954
+ const otherKeys = Object.keys(right).filter(key => right[key] !== undefined);
1955
+ return keys.length === otherKeys.length && keys.every(key => Object.prototype.hasOwnProperty.call(right, key)
1956
+ && sameAuthoringValue(left[key], right[key]));
1957
+ }
1958
+ }
1959
+
1960
+ const BINDINGS = ['resourcePath', 'horizontalScroll'];
1961
+ const INPUTS = ['config', ...BINDINGS];
1962
+ /** Raw compact schema authorship only; native normalization remains an editor/runtime projection. */
1963
+ function decodeTablePageInputs(value) {
1964
+ let inputs;
1965
+ try {
1966
+ inputs = cloneBoundedUiLayoutJsonDocument(value);
1967
+ }
1968
+ catch {
1969
+ return { document: null, diagnostics: [problem('table.page.json.invalid', '/definition/inputs')] };
1970
+ }
1971
+ const diagnostics = [];
1972
+ extras(inputs, INPUTS, '/definition/inputs', diagnostics);
1973
+ const config = inputs['config'];
1974
+ if (!record(config) || !Array.isArray(config['columns']) || config['columns'].length !== 0
1975
+ || !record(config['columnProjection']) || config['columnProjection']['source'] !== 'schema')
1976
+ diagnostics.push(problem('table.page.config.schema-required', '/definition/inputs/config'));
1977
+ if (record(config))
1978
+ nulls(config, '/definition/inputs/config', diagnostics);
1979
+ if (Object.prototype.hasOwnProperty.call(inputs, 'resourcePath') && inputs['resourcePath'] !== null && (typeof inputs['resourcePath'] !== 'string' || [...inputs['resourcePath']].length > 1024))
1980
+ diagnostics.push(problem('table.page.resourcePath.invalid', '/definition/inputs/resourcePath'));
1981
+ if (Object.prototype.hasOwnProperty.call(inputs, 'horizontalScroll') && !['auto', 'wrap', 'none'].includes(inputs['horizontalScroll']))
1982
+ diagnostics.push(problem('table.page.horizontalScroll.invalid', '/definition/inputs/horizontalScroll'));
1983
+ const bindings = Object.fromEntries(BINDINGS.filter(key => Object.prototype.hasOwnProperty.call(inputs, key)).map(key => [key, inputs[key]]));
1984
+ const document = { kind: 'praxis.table.editor', version: 1, config, ...(Object.keys(bindings).length ? { bindings } : {}) };
1985
+ if (!diagnostics.length) {
1986
+ try {
1987
+ diagnostics.push(...validateTableAuthoringDocument(document).filter(d => d.level === 'error').map(d => ({ ...d, path: nativePath(d.path) })));
1988
+ }
1989
+ catch {
1990
+ diagnostics.push(problem('table.page.native.invalid', '/definition/inputs/config'));
1991
+ }
1992
+ }
1993
+ if (!diagnostics.length) {
1994
+ try {
1995
+ cloneBoundedUiLayoutJsonDocument(document);
1996
+ }
1997
+ catch {
1998
+ diagnostics.push(problem('table.page.json.invalid', '/definition/inputs'));
1999
+ }
2000
+ }
2001
+ return { document: diagnostics.length ? null : document, diagnostics };
2002
+ }
2003
+ /** Core prefixes these widget-relative JSON Pointers; this callback never resolves a source/latest. */
2004
+ function validateTablePageWidget(widget) {
2005
+ if (widget.definition.id !== 'praxis-table')
2006
+ return [{ code: 'table.page.component.invalid', path: '/definition/id' }];
2007
+ return decodeTablePageInputs(widget.definition.inputs).diagnostics.map(d => ({ code: d.code, path: d.path ?? '/definition/inputs' }));
2008
+ }
2009
+ function encodeTablePageInputs(value) {
2010
+ let document;
2011
+ try {
2012
+ document = cloneBoundedUiLayoutJsonDocument(value);
2013
+ }
2014
+ catch {
2015
+ return { inputs: null, diagnostics: [problem('table.page.json.invalid', '/')] };
2016
+ }
2017
+ const diagnostics = [];
2018
+ extras(document, ['kind', 'version', 'config', 'bindings'], '', diagnostics);
2019
+ if (document['kind'] !== 'praxis.table.editor' || document['version'] !== 1)
2020
+ diagnostics.push(problem('table.page.native.invalid', '/'));
2021
+ const inputs = { config: document['config'] };
2022
+ if (Object.prototype.hasOwnProperty.call(document, 'bindings')) {
2023
+ const bindings = document['bindings'];
2024
+ if (!record(bindings) || !Object.keys(bindings).length)
2025
+ diagnostics.push(problem('table.page.envelope.unrepresentable', '/bindings'));
2026
+ else {
2027
+ extras(bindings, BINDINGS, '/bindings', diagnostics);
2028
+ Object.assign(inputs, bindings);
2029
+ }
2030
+ }
2031
+ const decoded = diagnostics.length ? null : decodeTablePageInputs(inputs);
2032
+ return { inputs: diagnostics.length || decoded?.diagnostics.length ? null : inputs, diagnostics: [...diagnostics, ...(decoded?.diagnostics ?? [])] };
2033
+ }
2034
+ /** No-change values in a materialized native editor cannot author defaults or erase absent keys. */
2035
+ function projectTablePageEditorChanges(authoredInputs, editorBaseline, editorValue) {
2036
+ const original = decodeTablePageInputs(authoredInputs);
2037
+ if (!original.document)
2038
+ return { inputs: null, diagnostics: original.diagnostics };
2039
+ let baseline;
2040
+ let edited;
2041
+ try {
2042
+ baseline = cloneBoundedUiLayoutJsonDocument(editorBaseline);
2043
+ edited = cloneBoundedUiLayoutJsonDocument(editorValue);
2044
+ }
2045
+ catch {
2046
+ return { inputs: null, diagnostics: [problem('table.page.json.invalid', '/')] };
2047
+ }
2048
+ const authored = original.document;
2049
+ // Native config delta has schema-column projection semantics; envelope delta is plain JSON.
2050
+ let candidate;
2051
+ try {
2052
+ candidate = applyTableAuthoringChanges(authored, baseline, edited);
2053
+ candidate['config'] = applyTableAuthoringChanges(authored['config'], baseline['config'], edited['config']);
2054
+ }
2055
+ catch {
2056
+ return { inputs: null, diagnostics: [problem('table.page.editor.invalid', '/definition/inputs/config')] };
2057
+ }
2058
+ const before = { ...authored };
2059
+ const after = { ...candidate };
2060
+ delete before['config'];
2061
+ delete after['config'];
2062
+ if (uiLayoutJsonIdentity(before) !== uiLayoutJsonIdentity(after))
2063
+ return { inputs: null, diagnostics: [problem('table.page.seed.replacement-required', '/definition/inputs')] };
2064
+ return encodeTablePageInputs(candidate);
2065
+ }
2066
+ function extras(source, allowed, path, diagnostics) {
2067
+ for (const key of Object.keys(source))
2068
+ if (!allowed.includes(key))
2069
+ diagnostics.push(problem('table.page.profile.unsupported', path + '/' + pointer(key)));
2070
+ }
2071
+ function nulls(value, path, diagnostics) {
2072
+ if (value === null)
2073
+ diagnostics.push(problem('table.page.config.null', path));
2074
+ else if (Array.isArray(value))
2075
+ value.forEach((item, index) => nulls(item, path + '/' + index, diagnostics));
2076
+ else if (record(value))
2077
+ Object.entries(value).forEach(([key, item]) => nulls(item, path + '/' + pointer(key), diagnostics));
2078
+ }
2079
+ function pointer(key) { return key.replace(/~/g, '~0').replace(/\//g, '~1'); }
2080
+ function record(value) { return value !== null && typeof value === 'object' && !Array.isArray(value); }
2081
+ function problem(code, path) { return { level: 'error', code, path, message: code }; }
2082
+ /** Derive execution-only identity and disable independent preferences. Never persist this projection. */
2083
+ function materializeTablePageRuntimeInputs(value, pageId, widgetKey, options) {
2084
+ const decoded = decodeTablePageInputs(value);
2085
+ if (!decoded.document)
2086
+ return { inputs: null, diagnostics: decoded.diagnostics };
2087
+ let id;
2088
+ try {
2089
+ id = derivePageChildRuntimeId(pageId, widgetKey);
2090
+ }
2091
+ catch {
2092
+ return { inputs: null, diagnostics: [problem('table.page.identity.invalid', '/key')] };
2093
+ }
2094
+ const cloned = cloneBoundedUiLayoutJsonDocument(value);
2095
+ if (options?.preview && record(cloned['config'])) {
2096
+ const previewConfig = { ...cloned['config'] };
2097
+ if (record(previewConfig['actions'])) {
2098
+ const actions = { ...previewConfig['actions'] };
2099
+ if (record(actions['row'])) {
2100
+ actions['row'] = {
2101
+ ...actions['row'],
2102
+ edit: false,
2103
+ delete: false,
2104
+ duplicate: false,
2105
+ custom: [],
2106
+ };
2107
+ }
2108
+ if (record(actions['toolbar'])) {
2109
+ actions['toolbar'] = {
2110
+ ...actions['toolbar'],
2111
+ add: false,
2112
+ create: false,
2113
+ delete: false,
2114
+ import: false,
2115
+ custom: [],
2116
+ };
2117
+ }
2118
+ if (record(actions['bulk'])) {
2119
+ actions['bulk'] = {
2120
+ ...actions['bulk'],
2121
+ enabled: false,
2122
+ actions: [],
2123
+ };
2124
+ }
2125
+ previewConfig['actions'] = actions;
2126
+ }
2127
+ if (record(previewConfig['editing'])) {
2128
+ previewConfig['editing'] = {
2129
+ ...previewConfig['editing'],
2130
+ enabled: false,
2131
+ };
2132
+ }
2133
+ cloned['config'] = previewConfig;
2134
+ }
2135
+ return {
2136
+ inputs: {
2137
+ ...cloned,
2138
+ tableId: id,
2139
+ componentInstanceId: id,
2140
+ configPersistenceStrategy: 'volatile',
2141
+ enableCustomization: false,
2142
+ },
2143
+ diagnostics: [],
2144
+ };
2145
+ }
2146
+ const TABLE_GOVERNED_PAGE_PREVIEW_ADAPTER = {
2147
+ widgetDefinitionId: 'praxis-table',
2148
+ certifiedForInteractivePreview: true,
2149
+ validate: validateTablePageWidget,
2150
+ materializePreviewInputs: (widget, pageId) => {
2151
+ const materialized = materializeTablePageRuntimeInputs(widget.definition.inputs, pageId, widget.key, { preview: true });
2152
+ if (!materialized.inputs) {
2153
+ throw new Error(materialized.diagnostics[0]?.code ?? 'table.page.preview.invalid');
2154
+ }
2155
+ return materialized.inputs;
2156
+ },
2157
+ };
2158
+ function nativePath(value) {
2159
+ // The native dot-path protocol does not escape dynamic field identities. Its
2160
+ // overrides ancestor is precise; splitting that field would invent a pointer.
2161
+ if (value?.startsWith('config.columnProjection.overrides.'))
2162
+ return '/definition/inputs/config/columnProjection/overrides';
2163
+ const parts = (value ?? 'config').replace(/\[(\d+)\]/g, '.$1').split('.');
2164
+ if (parts[0] === 'bindings')
2165
+ parts.shift();
2166
+ return '/definition/inputs/' + parts.map(pointer).join('/');
2167
+ }
2168
+
1729
2169
  /**
1730
2170
  * Generated bundle index. Do not edit.
1731
2171
  */
1732
2172
 
1733
- export { buildTableApplyPlan, createTableAuthoringDocument, deriveTableEditorDocument, normalizeTableAuthoringDocument, parseLegacyOrTableDocument, serializeTableAuthoringDocument, toCanonicalTableConfig, validateTableAuthoringDocument };
2173
+ export { TABLE_GOVERNED_PAGE_PREVIEW_ADAPTER, buildTableApplyPlan, createTableAuthoringDocument, decodeTablePageInputs, deriveTableEditorDocument, encodeTablePageInputs, materializeTablePageRuntimeInputs, normalizeTableAuthoringDocument, parseLegacyOrTableDocument, projectTablePageEditorChanges, serializeTableAuthoringDocument, toCanonicalTableConfig, validateTableAuthoringDocument, validateTablePageWidget };