@praxisui/page-builder 9.0.68-rc.2 → 9.0.68-rc.3
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
CHANGED
|
@@ -81,7 +81,7 @@ through its host. Preset selection does not introduce collective repacking or ne
|
|
|
81
81
|
|
|
82
82
|
### Device composition
|
|
83
83
|
|
|
84
|
-
**Page settings → Responsiveness** opens on Mobile and shows one device at a time.
|
|
84
|
+
**Page settings → Adjustments → Responsiveness** opens on Mobile and shows one device at a time.
|
|
85
85
|
**Stack widgets** prepares a single-column variant in the base canvas's visual reading
|
|
86
86
|
order, with natural content height. It replaces positions and width bounds only for
|
|
87
87
|
that device, preserves hidden widgets and other overrides, and leaves the base and
|
|
@@ -97,7 +97,7 @@ precedence over grouping. The editor explains this distinction in the Grouping t
|
|
|
97
97
|
|
|
98
98
|
### Visual widget limits
|
|
99
99
|
|
|
100
|
-
Open **Page settings → Widget limits** in the default Dynamic Page editor to set
|
|
100
|
+
Open **Page settings → Adjustments → Widget limits** in the default Dynamic Page editor to set
|
|
101
101
|
minimum/maximum width (columns) and height (rows). Choose the base canvas or a
|
|
102
102
|
desktop, tablet or mobile override, without closing the editor. Blank base fields use Core defaults (minimum one,
|
|
103
103
|
maximum grid width/unbounded row span); blank device fields inherit base limits.
|
|
@@ -978,7 +978,9 @@ In **Page settings → Arrange**, choose a visual distribution. This is an
|
|
|
978
978
|
explicit arrangement of a complete existing base canvas, independent of layout preset
|
|
979
979
|
selection. The current geometric reading order assigns columns; no titles or business
|
|
980
980
|
roles are inferred. Heights, content, shell appearance, widget keys and composition links
|
|
981
|
-
are preserved.
|
|
981
|
+
are preserved. Indivisible columns are distributed by fractional remainder; ties favor
|
|
982
|
+
the larger weight, then the later lane. For example, four equal lanes over seven
|
|
983
|
+
columns produce `1 + 2 + 2 + 2`, not `1 + 1 + 1 + 4`.
|
|
982
984
|
|
|
983
985
|
Review the numbered before/after maps and widget names, then choose **Use arrangement
|
|
984
986
|
in draft** and the Settings Panel's **Apply**. Preparation does not dirty or emit the page.
|
|
@@ -1016,3 +1018,44 @@ Undo restores only `col`, `row`, `colSpan` and `rowSpan`. Additional gap, theme,
|
|
|
1016
1018
|
or limit edits remain in the draft. The owner-confirmed Apply receipt retains the undo
|
|
1017
1019
|
when the reviewed geometry matches, including when unrelated fields were edited before
|
|
1018
1020
|
Apply. Reset or replacement of the page still invalidates the transaction.
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
### Corporate adoption and validation boundaries
|
|
1024
|
+
|
|
1025
|
+
The template studio is a presentation and geometry editor. Its visual templates are
|
|
1026
|
+
Core theme presets; its layout templates describe composition roles. Neither gallery
|
|
1027
|
+
creates a governed business template, grants resource permissions, generates data
|
|
1028
|
+
connections, or implements a master-detail relationship by selecting a card.
|
|
1029
|
+
Governed template publication remains owned by Config Starter and its canonical
|
|
1030
|
+
references; host authoring eligibility, persistence identity and strong ETag handling
|
|
1031
|
+
remain required at their existing boundaries. Geometry locks are editing constraints,
|
|
1032
|
+
not an authorization mechanism.
|
|
1033
|
+
|
|
1034
|
+
Use the following acceptance scope when adopting the editor in a corporate host:
|
|
1035
|
+
|
|
1036
|
+
| Concern | Required evidence |
|
|
1037
|
+
| --- | --- |
|
|
1038
|
+
| Document integrity | Apply, Save and fresh reopen preserve widget inputs, keys, composition links, context, state, slot assignments and untouched device overrides. |
|
|
1039
|
+
| Concurrency and governance | The real host rejects stale ETags and unauthorized writes; local Apply or a passing geometry test does not prove either. |
|
|
1040
|
+
| Responsive authoring | Validate each intended device with its own locks, bounds, hidden widgets and child-content height. Base-arrangement admission does not certify every device editing operation. |
|
|
1041
|
+
| User experience | Check keyboard, desktop/narrow layout, locale and dark theme with representative business data. Automated accessibility checks do not replace task-based usability testing. |
|
|
1042
|
+
| Scale | Measure proposal generation and review rendering with the host's expected widget counts; small fixtures do not establish a performance envelope. |
|
|
1043
|
+
| Publication | Validate the actual npm train in the consumer. A local RC build is not a hosted-production smoke or approval to promote the stable channel. |
|
|
1044
|
+
|
|
1045
|
+
The public Landing Studio deliberately hides full page settings under its authoring
|
|
1046
|
+
policy. Its JSON/Visual compatibility tests do not demonstrate the template gallery;
|
|
1047
|
+
use the canonical Dynamic Page laboratory or a host that enables page settings for that
|
|
1048
|
+
journey. Enabling that surface is a host policy decision, not a package upgrade effect.
|
|
1049
|
+
|
|
1050
|
+
The source corrections following the 9.0.68-rc.2 review reject device stacking
|
|
1051
|
+
before changing the draft when it would alter effective position/size locks, including
|
|
1052
|
+
content height for size-locked widgets. Hidden untouched items remain unchanged. An
|
|
1053
|
+
explicit device override of a lock continues to follow the existing inheritance contract;
|
|
1054
|
+
this does not grant permission to change locks.
|
|
1055
|
+
|
|
1056
|
+
Context paths `ui` and `ui.shell`, when present, must be objects; `ui.shell.preset`
|
|
1057
|
+
must be text when present. Incompatible imported or edited values remain in the draft
|
|
1058
|
+
and block value production with a localized diagnostic, allowing correction without
|
|
1059
|
+
losing unrelated context. Arrangement choices track their draft baseline even when the
|
|
1060
|
+
selected distribution initially produces no changes, so later edits clear stale status.
|
|
1061
|
+
These fixes are not part of the already published 9.0.68-rc.2 package.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"generatedAt": "2026-09-
|
|
3
|
+
"generatedAt": "2026-09-10T02:26:52.714Z",
|
|
4
4
|
"packageName": "@praxisui/page-builder",
|
|
5
|
-
"packageVersion": "9.0.68-rc.
|
|
5
|
+
"packageVersion": "9.0.68-rc.3",
|
|
6
6
|
"sourceRegistry": "praxis-component-registry-ingestion",
|
|
7
7
|
"sourceRegistryVersion": "1.0.0",
|
|
8
8
|
"componentCount": 1,
|
|
@@ -816,6 +816,8 @@ const PRAXIS_PAGE_BUILDER_EN_US = {
|
|
|
816
816
|
'praxis.pageBuilder.editor.apply': 'Apply',
|
|
817
817
|
'praxis.pageBuilder.editor.errors.invalidJson': 'Invalid JSON',
|
|
818
818
|
'praxis.pageBuilder.editor.errors.canvasInvalid': 'Invalid canvas',
|
|
819
|
+
'praxis.pageBuilder.editor.devices.locked': 'Stacking would change a locked widget. Review its device locks first.',
|
|
820
|
+
'praxis.pageBuilder.editor.errors.contextStructure': 'Context ui and ui.shell must be objects; shell.preset must be text. Correct these fields without removing other context data.',
|
|
819
821
|
'praxis.pageBuilder.editor.errors.contextInvalid': 'Invalid context',
|
|
820
822
|
'praxis.pageBuilder.editor.errors.stateInvalid': 'Invalid state',
|
|
821
823
|
'praxis.pageBuilder.editor.errors.duplicateGroupIds': 'Duplicate group IDs',
|
|
@@ -1879,6 +1881,8 @@ const PRAXIS_PAGE_BUILDER_PT_BR = {
|
|
|
1879
1881
|
'praxis.pageBuilder.editor.apply': 'Aplicar',
|
|
1880
1882
|
'praxis.pageBuilder.editor.errors.invalidJson': 'JSON inválido',
|
|
1881
1883
|
'praxis.pageBuilder.editor.errors.canvasInvalid': 'Canvas inválido',
|
|
1884
|
+
'praxis.pageBuilder.editor.devices.locked': 'O empilhamento alteraria um widget bloqueado. Revise os bloqueios desse dispositivo primeiro.',
|
|
1885
|
+
'praxis.pageBuilder.editor.errors.contextStructure': 'No contexto, ui e ui.shell devem ser objetos; shell.preset deve ser texto. Corrija esses campos preservando os demais dados.',
|
|
1882
1886
|
'praxis.pageBuilder.editor.errors.contextInvalid': 'Contexto inválido',
|
|
1883
1887
|
'praxis.pageBuilder.editor.errors.stateInvalid': 'Estado inválido',
|
|
1884
1888
|
'praxis.pageBuilder.editor.errors.duplicateGroupIds': 'IDs de grupo duplicados',
|
|
@@ -13554,6 +13558,7 @@ class DynamicPageConfigEditorComponent {
|
|
|
13554
13558
|
focusArrangementFeedback = false;
|
|
13555
13559
|
workspaceTab = signal(0, ...(ngDevMode ? [{ debugName: "workspaceTab" }] : /* istanbul ignore next */ []));
|
|
13556
13560
|
arrangementSelection = signal('', ...(ngDevMode ? [{ debugName: "arrangementSelection" }] : /* istanbul ignore next */ []));
|
|
13561
|
+
arrangementSelectionSource = '';
|
|
13557
13562
|
// Transient authoring choices, expressed through the existing Core width contract.
|
|
13558
13563
|
arrangementChoices = [
|
|
13559
13564
|
{ id: 'stack', weights: [1] }, { id: 'compare', weights: [1, 1] },
|
|
@@ -13603,6 +13608,8 @@ class DynamicPageConfigEditorComponent {
|
|
|
13603
13608
|
previousArrangementItems;
|
|
13604
13609
|
prepareArrangement(id) {
|
|
13605
13610
|
this.arrangementPreview.set(null);
|
|
13611
|
+
this.arrangementSelection.set('');
|
|
13612
|
+
this.arrangementSelectionSource = '';
|
|
13606
13613
|
const page = this.buildCurrentPage(true);
|
|
13607
13614
|
if (!page)
|
|
13608
13615
|
return;
|
|
@@ -13616,6 +13623,7 @@ class DynamicPageConfigEditorComponent {
|
|
|
13616
13623
|
return;
|
|
13617
13624
|
}
|
|
13618
13625
|
this.arrangementSelection.set(id);
|
|
13626
|
+
this.arrangementSelectionSource = this.draftFingerprint();
|
|
13619
13627
|
this.arrangementMessage.set(proposal.changedKeys.length ? '' : this.tx('editor.arrange.unchanged', 'This arrangement already matches the page.'));
|
|
13620
13628
|
if (proposal.changedKeys.length)
|
|
13621
13629
|
this.arrangementPreview.set({ source: this.draftFingerprint(), before: page.canvas, canvas: proposal.canvas, keys: proposal.changedKeys });
|
|
@@ -13638,6 +13646,7 @@ class DynamicPageConfigEditorComponent {
|
|
|
13638
13646
|
this.arrangementSourcePage = this.clone(current);
|
|
13639
13647
|
this.previousArrangementItems = this.clone(current.canvas.items);
|
|
13640
13648
|
this.arrangedItems = preview.canvas.items;
|
|
13649
|
+
this.arrangementSelectionSource = this.draftFingerprint();
|
|
13641
13650
|
this.arrangementGeometry = this.geometryFingerprint(preview.canvas);
|
|
13642
13651
|
this.arrangementUndoAvailable.set(true);
|
|
13643
13652
|
this.arrangementPreview.set(null);
|
|
@@ -13762,6 +13771,13 @@ class DynamicPageConfigEditorComponent {
|
|
|
13762
13771
|
this.themePresetControl.setValue(theme.id); }
|
|
13763
13772
|
get hasCanvasWidgets() { return !!this.currentPageInput()?.widgets.length; }
|
|
13764
13773
|
stackDevice(device) {
|
|
13774
|
+
const previous = this.currentPageInput()?.deviceLayouts?.[device]?.canvas || {};
|
|
13775
|
+
if (this.stackViolatesLocks(device, this.stackedCanvas(device, previous))) {
|
|
13776
|
+
this.deviceError.set(this.tx('editor.devices.locked', 'Stacking would change a locked widget. Review its device locks first.'));
|
|
13777
|
+
this.focusEditorTab('devices');
|
|
13778
|
+
return;
|
|
13779
|
+
}
|
|
13780
|
+
this.deviceError.set(null);
|
|
13765
13781
|
this.hydrating = true;
|
|
13766
13782
|
this.stackedDevices.add(device);
|
|
13767
13783
|
this.deviceForms[device].patchValue({ columns: 1, autoRows: 'content' }, { emitEvent: false });
|
|
@@ -13776,6 +13792,23 @@ class DynamicPageConfigEditorComponent {
|
|
|
13776
13792
|
this.hydrating = false;
|
|
13777
13793
|
this.refreshPanelState();
|
|
13778
13794
|
}
|
|
13795
|
+
stackViolatesLocks(device, canvas) {
|
|
13796
|
+
const page = this.currentPageInput();
|
|
13797
|
+
if (!page?.canvas)
|
|
13798
|
+
return false;
|
|
13799
|
+
const overrides = page.deviceLayouts?.[device]?.canvas?.items || {};
|
|
13800
|
+
return Object.entries(canvas.items || {}).some(([key, after]) => {
|
|
13801
|
+
const base = page.canvas.items[key];
|
|
13802
|
+
if (!base)
|
|
13803
|
+
return false;
|
|
13804
|
+
const before = { ...base, ...overrides[key] };
|
|
13805
|
+
after = { ...base, ...after };
|
|
13806
|
+
const locks = { ...base.constraints, ...overrides[key]?.constraints };
|
|
13807
|
+
return !!((locks.lockPosition && (before.col !== after.col || before.row !== after.row))
|
|
13808
|
+
|| (locks.lockSize && (before.colSpan !== after.colSpan || before.rowSpan !== after.rowSpan
|
|
13809
|
+
|| JSON.stringify(before.contentSize) !== JSON.stringify(after.contentSize))));
|
|
13810
|
+
});
|
|
13811
|
+
}
|
|
13779
13812
|
stackedCanvas(device, previous) {
|
|
13780
13813
|
const page = this.currentPageInput() || { widgets: [] };
|
|
13781
13814
|
const source = this.completeCanvasItems(page);
|
|
@@ -13939,7 +13972,8 @@ class DynamicPageConfigEditorComponent {
|
|
|
13939
13972
|
buildCurrentPage(reportErrors) { const next = this.clone(this.currentPageInput() || { widgets: [] }); if (this.canvasColumnsControl.invalid)
|
|
13940
13973
|
return this.invalidate('canvas', this.canvasError, reportErrors, this.tx('editor.errors.canvasInvalid', 'Invalid canvas')); this.canvasError.set(null); const grouping = this.buildGrouping(reportErrors); if (grouping === null)
|
|
13941
13974
|
return null; this.groupingError.set(null); const context = this.parseRecordJson(this.contextControl.value, {}); if (context === null)
|
|
13942
|
-
return this.invalidate('context', this.contextError, reportErrors, this.tx('editor.errors.contextInvalid', 'Invalid context'));
|
|
13975
|
+
return this.invalidate('context', this.contextError, reportErrors, this.tx('editor.errors.contextInvalid', 'Invalid context')); if (!this.validShellContext(context))
|
|
13976
|
+
return this.invalidate('context', this.contextError, reportErrors, this.tx('editor.errors.contextStructure', 'Context ui and ui.shell must be objects; shell.preset must be text. Correct these fields without removing other context data.')); this.contextError.set(null); const state = this.parseRecordJson(this.stateControl.value, {}); if (state === null)
|
|
13943
13977
|
return this.invalidate('state', this.stateError, reportErrors, this.tx('editor.errors.stateInvalid', 'Invalid state')); this.stateError.set(null); const deviceLayouts = this.buildDeviceLayouts(reportErrors, next.deviceLayouts); if (deviceLayouts === null)
|
|
13944
13978
|
return null; next.canvas = this.buildCanvas(next); if (next.canvas && this.arrangedItems)
|
|
13945
13979
|
next.canvas = { ...next.canvas, items: this.clone(this.arrangedItems) }; next.layout = withPagePaddingInline(next.layout, this.paddingInlineControl.value); next.layoutPreset = this.layoutPresetControl.value.trim() || undefined; next.themePreset = this.themePresetControl.value.trim() || undefined; next.grouping = grouping.length ? grouping : undefined; next.deviceLayouts = Object.keys(deviceLayouts).length ? deviceLayouts : undefined; next.context = this.buildPageContext(next.context, context); next.state = Object.keys(state).length ? state : undefined; return this.applyCanvasLimits(next, reportErrors); }
|
|
@@ -14032,6 +14066,8 @@ class DynamicPageConfigEditorComponent {
|
|
|
14032
14066
|
delete baseVariant.groupingOverrides;
|
|
14033
14067
|
delete baseVariant.widgetOverrides;
|
|
14034
14068
|
const canvas = this.stackedDevices.has(device) ? this.stackedCanvas(device, baseCanvas) : { ...baseCanvas };
|
|
14069
|
+
if (this.stackedDevices.has(device) && this.stackViolatesLocks(device, canvas))
|
|
14070
|
+
return this.invalidate('devices', this.deviceError, reportErrors, this.tx('editor.devices.locked', 'Stacking would change a locked widget. Review its device locks first.'));
|
|
14035
14071
|
if (form.controls.columns.value != null)
|
|
14036
14072
|
canvas.columns = form.controls.columns.value;
|
|
14037
14073
|
else
|
|
@@ -14062,7 +14098,7 @@ class DynamicPageConfigEditorComponent {
|
|
|
14062
14098
|
const current = this.buildCurrentPage(false);
|
|
14063
14099
|
this.isValid$.next(!!current);
|
|
14064
14100
|
const fingerprint = this.draftFingerprint();
|
|
14065
|
-
if (this.
|
|
14101
|
+
if (this.arrangementSelection() && this.arrangementSelectionSource !== fingerprint) {
|
|
14066
14102
|
this.arrangementPreview.set(null);
|
|
14067
14103
|
this.arrangementSelection.set('');
|
|
14068
14104
|
this.focusArrangementFeedback = false;
|
|
@@ -14113,13 +14149,22 @@ class DynamicPageConfigEditorComponent {
|
|
|
14113
14149
|
return ''; return JSON.stringify(value, null, 2); }
|
|
14114
14150
|
stringifyGroupingWidgetKeys(seed) { if (!seed || seed.kind === 'tabs')
|
|
14115
14151
|
return ''; return seed.widgetKeys.join(', '); }
|
|
14116
|
-
extractShellPreset(context) {
|
|
14117
|
-
removeShellPresetFromContext(context) { const next = this.clone(context || {}); if (next.ui
|
|
14152
|
+
extractShellPreset(context) { const preset = context?.ui?.shell?.preset; return typeof preset === 'string' ? preset : ''; }
|
|
14153
|
+
removeShellPresetFromContext(context) { const next = this.clone(context || {}); if (this.isPlainObject(next.ui) && this.isPlainObject(next.ui.shell) && typeof next.ui.shell.preset === 'string') {
|
|
14118
14154
|
delete next.ui.shell.preset;
|
|
14119
14155
|
if (!Object.keys(next.ui.shell).length)
|
|
14120
14156
|
delete next.ui.shell;
|
|
14121
|
-
} if (next.ui && !Object.keys(next.ui).length)
|
|
14157
|
+
} if (this.isPlainObject(next.ui) && !Object.keys(next.ui).length)
|
|
14122
14158
|
delete next.ui; return next; }
|
|
14159
|
+
validShellContext(context) {
|
|
14160
|
+
if (context['ui'] === undefined)
|
|
14161
|
+
return true;
|
|
14162
|
+
if (!this.isPlainObject(context['ui']))
|
|
14163
|
+
return false;
|
|
14164
|
+
const shell = context['ui']['shell'];
|
|
14165
|
+
return shell === undefined || (this.isPlainObject(shell)
|
|
14166
|
+
&& (shell['preset'] === undefined || typeof shell['preset'] === 'string'));
|
|
14167
|
+
}
|
|
14123
14168
|
applyShellPresetToContext(context, shellPreset) { const next = this.clone(context || {}); if (shellPreset) {
|
|
14124
14169
|
next.ui = next.ui || {};
|
|
14125
14170
|
next.ui.shell = next.ui.shell || {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxisui/page-builder",
|
|
3
|
-
"version": "9.0.68-rc.
|
|
3
|
+
"version": "9.0.68-rc.3",
|
|
4
4
|
"description": "Page and widget builder utilities for Praxis UI (grid, dynamic widgets, editors).",
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"@angular/common": "^21.0.0",
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
"@angular/forms": "^21.0.0",
|
|
9
9
|
"@angular/cdk": "^21.0.0",
|
|
10
10
|
"@angular/material": "^21.0.0",
|
|
11
|
-
"@praxisui/ai": "^9.0.68-rc.
|
|
12
|
-
"@praxisui/core": "^9.0.68-rc.
|
|
13
|
-
"@praxisui/settings-panel": "^9.0.68-rc.
|
|
11
|
+
"@praxisui/ai": "^9.0.68-rc.3",
|
|
12
|
+
"@praxisui/core": "^9.0.68-rc.3",
|
|
13
|
+
"@praxisui/settings-panel": "^9.0.68-rc.3",
|
|
14
14
|
"rxjs": "~7.8.0"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
@@ -1128,6 +1128,7 @@ declare class DynamicPageConfigEditorComponent implements OnInit, SettingsValueP
|
|
|
1128
1128
|
private focusArrangementFeedback;
|
|
1129
1129
|
readonly workspaceTab: WritableSignal<number>;
|
|
1130
1130
|
readonly arrangementSelection: WritableSignal<string>;
|
|
1131
|
+
private arrangementSelectionSource;
|
|
1131
1132
|
readonly arrangementChoices: readonly [{
|
|
1132
1133
|
readonly id: "stack";
|
|
1133
1134
|
readonly weights: readonly [1];
|
|
@@ -1261,6 +1262,7 @@ declare class DynamicPageConfigEditorComponent implements OnInit, SettingsValueP
|
|
|
1261
1262
|
pinRecommendedThemePreset(): void;
|
|
1262
1263
|
get hasCanvasWidgets(): boolean;
|
|
1263
1264
|
stackDevice(device: DeviceKey): void;
|
|
1265
|
+
private stackViolatesLocks;
|
|
1264
1266
|
private stackedCanvas;
|
|
1265
1267
|
addGroupingNode(seed?: WidgetPageGroupingDefinition): void;
|
|
1266
1268
|
removeGroupingNode(index: number): void;
|
|
@@ -1312,6 +1314,7 @@ declare class DynamicPageConfigEditorComponent implements OnInit, SettingsValueP
|
|
|
1312
1314
|
private stringifyGroupingWidgetKeys;
|
|
1313
1315
|
private extractShellPreset;
|
|
1314
1316
|
private removeShellPresetFromContext;
|
|
1317
|
+
private validShellContext;
|
|
1315
1318
|
private applyShellPresetToContext;
|
|
1316
1319
|
private lookupPresetLabel;
|
|
1317
1320
|
private defaultCanvas;
|