@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.
- package/README.md +4 -0
- package/ai/component-registry.json +387 -151
- package/docs/governed-page-widget-adapter.md +55 -0
- package/fesm2022/praxisui-table-authoring.mjs +467 -27
- package/fesm2022/{praxisui-table-filter-form-dialog-host.component-CN4pADPh.mjs → praxisui-table-filter-form-dialog-host.component-DlGomgO3.mjs} +2 -2
- package/fesm2022/{praxisui-table-praxisui-table-WJvWsf3B.mjs → praxisui-table-praxisui-table-C6NaFjJJ.mjs} +2932 -585
- package/fesm2022/{praxisui-table-table-agentic-authoring-turn-flow-CNFpSoP9.mjs → praxisui-table-table-agentic-authoring-turn-flow-CB4ne1nl.mjs} +1 -1
- package/fesm2022/{praxisui-table-table-ai.adapter-CGb2n3wx.mjs → praxisui-table-table-ai.adapter-DPC3MX0Y.mjs} +6 -5
- package/fesm2022/praxisui-table.mjs +1 -1
- package/package.json +10 -10
- package/src/lib/praxis-table.json-api.md +8 -0
- package/types/praxisui-table-authoring.d.ts +30 -2
- package/types/praxisui-table.d.ts +124 -8
|
@@ -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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
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 (
|
|
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 = '
|
|
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 };
|