Skip to content

Commit 0166416

Browse files
zJaaalclaude
andauthored
fix(template-builder): fix drag regression in template builder (#35277)
## Summary - Reverts the `identify` track function — removes the method entirely and uses `row.id` / `box.id` directly in `@for` track expressions. The previous `${id}-${index}` approach caused Angular to treat widgets by position rather than identity, desyncing GridStack's DOM during column-between-row drags and leaving the source row empty. - Bumps gridstack `8.1.1` → `8.4.0` - Guards against `undefined` `oldChild` in `updateOldRows` when a column has moved to a new X position after a drag ## Related Closes #33073 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes how Angular tracks grid items and updates GridStack dependency, which can affect drag/drop rendering and layout state synchronization. > > **Overview** > Fixes a template-builder drag regression by switching `@for` tracking to stable `row.id`/`box.id` (and removing the custom `identify()` key generator) so GridStack DOM/widget identity stays consistent during cross-row column drags. > > Hardens `updateOldRows` merging to avoid crashing/losing state when a column’s previous X position can’t be found (`oldChild?.id ?? newChild.id`), and bumps `gridstack` from `8.1.1` to `8.4.0`. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit ab89c85. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Sonnet 4.6 <[email protected]>
1 parent f57f4c9 commit 0166416

5 files changed

Lines changed: 11 additions & 56 deletions

File tree

core-web/libs/template-builder/src/lib/components/template-builder/store/template-builder.store.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ export class DotTemplateBuilderStore extends ComponentStore<DotTemplateBuilderSt
423423

424424
return {
425425
...newChild, // We want the data from the backend
426-
id: oldChild.id, // But We do not want to lose the id, because this is the way GridStack knows that nothing changed
426+
id: oldChild?.id ?? newChild.id, // But We do not want to lose the id, because this is the way GridStack knows that nothing changed
427427
containers: newChild.containers
428428
};
429429
})

core-web/libs/template-builder/src/lib/components/template-builder/template-builder.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
[containerMap]="vm.containerMap" />
6262
}
6363
<div class="grid-stack flex-1">
64-
@for (row of vm.rows; track identify($index, row)) {
64+
@for (row of vm.rows; track row.id) {
6565
<dotcms-template-builder-row
6666
[attr.gs-id]="row.id"
6767
[attr.gs-x]="row.x"
@@ -76,7 +76,7 @@
7676
#rowElement
7777
data-testId="row">
7878
<div class="grid-stack-item-content grid-stack">
79-
@for (box of row.subGridOpts?.children; track identify($index, box)) {
79+
@for (box of row.subGridOpts?.children; track box.id) {
8080
<div
8181
[attr.gs-id]="box.id"
8282
[attr.gs-auto]="true"

core-web/libs/template-builder/src/lib/components/template-builder/template-builder.component.ts

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -384,26 +384,6 @@ export class TemplateBuilderComponent implements OnDestroy, OnChanges, OnInit {
384384
this.destroy$.complete();
385385
}
386386

387-
/**
388-
* @description This method is used to identify items by id
389-
*
390-
* @param {number} index
391-
* @param {GridStackWidget} w
392-
* @return {*}
393-
* @memberof TemplateBuilderComponent
394-
*/
395-
identify(index: number, w: GridStackWidget): string {
396-
// Ensure we always return a unique string
397-
// Combine ID with index to prevent Angular 20 NG0955 errors about duplicate keys
398-
// This handles cases where the same ID might appear in different rows
399-
const id = w?.id;
400-
if (id != null && id !== '') {
401-
return `${String(id)}-${index}`;
402-
}
403-
// Fallback to index if ID is not available
404-
return `item-${index}`;
405-
}
406-
407387
/**
408388
* @description This method maintains the GridStack Model in sync with the store when you delete a column
409389
*

core-web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@
116116
"execa": "9.6.0",
117117
"font-awesome": "4.7.0",
118118
"fs-extra": "11.3.2",
119-
"gridstack": "8.1.1",
119+
"gridstack": "8.4.0",
120120
"htmldiff-js": "1.0.5",
121121
"inquirer": "13.0.1",
122122
"jstat": "1.9.6",

core-web/yarn.lock

Lines changed: 7 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13696,10 +13696,10 @@ graphemer@^1.4.0:
1369613696
resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
1369713697
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
1369813698

13699-
gridstack@8.1.1:
13700-
version "8.1.1"
13701-
resolved "https://registry.npmjs.org/gridstack/-/gridstack-8.1.1.tgz#acdc910346989d2016e03763b0de55b0de0d3224"
13702-
integrity sha512-Ya7EdwAh72iug8A51hSjH8FI0v83zxPKjo9zIT5iw5Dvh3BUbgRl2hrrmGAkDfbPlfK5lgeEsQNx08UK8iNAQw==
13699+
gridstack@8.4.0:
13700+
version "8.4.0"
13701+
resolved "https://registry.npmjs.org/gridstack/-/gridstack-8.4.0.tgz#7af49159f9dc144c89a2c56246e1710406f75fcf"
13702+
integrity sha512-qLJuJrBy9bbG3hI+h2cEhiuZ51J3MyEMmv5AXg7MCFiBeG8A4HyIUytueqtD/oZcA3Pccq2Xoj7GrwpmKOS3ig==
1370313703

1370413704
gzip-size@^6.0.0:
1370513705
version "6.0.0"
@@ -20336,16 +20336,7 @@ string-length@^4.0.2:
2033620336
char-regex "^1.0.2"
2033720337
strip-ansi "^6.0.0"
2033820338

20339-
"string-width-cjs@npm:string-width@^4.2.0":
20340-
version "4.2.3"
20341-
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
20342-
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
20343-
dependencies:
20344-
emoji-regex "^8.0.0"
20345-
is-fullwidth-code-point "^3.0.0"
20346-
strip-ansi "^6.0.1"
20347-
20348-
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
20339+
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
2034920340
version "4.2.3"
2035020341
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
2035120342
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -20462,14 +20453,7 @@ string_decoder@~1.1.1:
2046220453
dependencies:
2046320454
safe-buffer "~5.1.0"
2046420455

20465-
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
20466-
version "6.0.1"
20467-
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
20468-
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
20469-
dependencies:
20470-
ansi-regex "^5.0.1"
20471-
20472-
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
20456+
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
2047320457
version "6.0.1"
2047420458
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
2047520459
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -22140,7 +22124,7 @@ wordwrap@^1.0.0:
2214022124
resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
2214122125
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
2214222126

22143-
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
22127+
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
2214422128
version "7.0.0"
2214522129
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
2214622130
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -22158,15 +22142,6 @@ wrap-ansi@^6.2.0:
2215822142
string-width "^4.1.0"
2215922143
strip-ansi "^6.0.0"
2216022144

22161-
wrap-ansi@^7.0.0:
22162-
version "7.0.0"
22163-
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
22164-
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
22165-
dependencies:
22166-
ansi-styles "^4.0.0"
22167-
string-width "^4.1.0"
22168-
strip-ansi "^6.0.0"
22169-
2217022145
wrap-ansi@^8.1.0:
2217122146
version "8.1.0"
2217222147
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"

0 commit comments

Comments
 (0)