@designcrowd/library.brand-page 6.0.6-undo-5 → 6.0.6-undo-6
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/lib/src/{CreatedOnBrandCrowd-de-de-BRaWY6nF.js → CreatedOnBrandCrowd-de-de-BfSQG4or.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-D0DuxW2v.js → CreatedOnBrandCrowd-en-us-CO07UsMZ.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-Du0Wjx0E.js → CreatedOnBrandCrowd-es-es-BElFyN72.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-Cz1OGFZT.js → CreatedOnBrandCrowd-fr-ca-DnTxsYIb.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-Cz1OGFZT.js → CreatedOnBrandCrowd-fr-fr-DnTxsYIb.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-TfMgQ231.js → CreatedOnBrandCrowd-pt-br-DSMhWz5Z.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-TfMgQ231.js → CreatedOnBrandCrowd-pt-pt-DSMhWz5Z.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-CIxWfXsX.js → CreatedOnDcom-de-de-C2469ycC.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-BQeFEYIF.js → CreatedOnDcom-en-us-Cvz7kq6J.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-Bu9Nr8t-.js → CreatedOnDcom-es-es-BvDTs1Y_.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-Dpsu-wxQ.js → CreatedOnDcom-fr-ca-vJ9zwwPI.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-CWGZAKa0.js → CreatedOnDcom-fr-fr-D90gm3ay.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-Ds8H3Kiq.js → CreatedOnDcom-pt-br-C1VGGfTF.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-kDx8VWIo.js → CreatedOnDcom-pt-pt-DGL1py4g.js} +1 -1
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-BJdIOaxe.js → lib-nGjOXase.js} +10145 -10140
- package/lib/src/shared/content-settings/store/content-settings-store.js +17 -0
- package/package.json +1 -1
- package/src/shared/content-settings/store/content-settings-store.test.ts +103 -0
- package/src/shared/content-settings/store/content-settings-store.ts +28 -0
|
@@ -14,6 +14,17 @@ const DEFAULT_TEXT_COLOR_DEPENDENT_TYPES = [
|
|
|
14
14
|
'profile',
|
|
15
15
|
'text',
|
|
16
16
|
];
|
|
17
|
+
/**
|
|
18
|
+
* True when the incoming value is byte-identical (as JSON, matching how
|
|
19
|
+
* history snapshots the model) to what the model already holds. Such
|
|
20
|
+
* re-writes must be dropped whole: the assignment would be a reactive
|
|
21
|
+
* no-op, but onContentChanged() still flips isModifiedAfterTemplateApplied
|
|
22
|
+
* — and when that flag was false, its flip is itself a model mutation, so
|
|
23
|
+
* undo history commits an entry in which nothing user-visible changed
|
|
24
|
+
* (phantom undo state, BP-5772: re-clicking the logo panel's Upload/Logo
|
|
25
|
+
* option, re-clicking an already-active display style).
|
|
26
|
+
*/
|
|
27
|
+
const isSameValue = (a, b) => a === b || JSON.stringify(a) === JSON.stringify(b);
|
|
17
28
|
export const CONTENT_SETTINGS_MODES = {
|
|
18
29
|
edit: 'edit',
|
|
19
30
|
add: 'add',
|
|
@@ -211,6 +222,9 @@ export const useUiContentSettingsStore = defineStore('uiContentSettings', {
|
|
|
211
222
|
if (!item) {
|
|
212
223
|
return;
|
|
213
224
|
}
|
|
225
|
+
if (isSameValue(item.settings.content[payload.name], payload.value)) {
|
|
226
|
+
return; // no-op write: don't validate, don't flip the modified flag
|
|
227
|
+
}
|
|
214
228
|
// Hydration write-backs (RTE canonical HTML on mount, lazy config
|
|
215
229
|
// hydration, stale-id clears) fold into the current undo head instead
|
|
216
230
|
// of pushing an entry. Arming here — same tick as the write — is what
|
|
@@ -232,6 +246,9 @@ export const useUiContentSettingsStore = defineStore('uiContentSettings', {
|
|
|
232
246
|
if (!item) {
|
|
233
247
|
return;
|
|
234
248
|
}
|
|
249
|
+
if (Object.keys(value ?? {}).every((k) => isSameValue(item.settings.content[k], value[k]))) {
|
|
250
|
+
return; // every field a no-op — see isSameValue
|
|
251
|
+
}
|
|
235
252
|
// Same hydration-write contract as updateItemValue.
|
|
236
253
|
if (isHydrationWrite) {
|
|
237
254
|
useHistoryStore().mergeNextMutation();
|
package/package.json
CHANGED
|
@@ -206,3 +206,106 @@ describe('content settings store — hydration writes arm the history merge', ()
|
|
|
206
206
|
expect(mergeNextMutationMock).not.toHaveBeenCalled();
|
|
207
207
|
});
|
|
208
208
|
});
|
|
209
|
+
|
|
210
|
+
// BP-5772: a value-identical re-write must be dropped whole. The assignment
|
|
211
|
+
// would be a reactive no-op, but onContentChanged() still flips
|
|
212
|
+
// isModifiedAfterTemplateApplied — and when that flag was false, its flip is
|
|
213
|
+
// itself a model mutation, so history commits an entry in which nothing
|
|
214
|
+
// user-visible changed. Live repro: with the logo visible, clicking the logo
|
|
215
|
+
// panel's "Upload" option re-emits { logoImage, logoTemplateImage,
|
|
216
|
+
// logoImageBase64Url, isLogoVisible } deep-equal to the model under fresh
|
|
217
|
+
// references (LogoSelectionField.onLogoVisibleChanged). Same class:
|
|
218
|
+
// re-clicking the already-active display style.
|
|
219
|
+
describe('content settings store — value-identical writes are dropped', () => {
|
|
220
|
+
const logoImage = () => ({
|
|
221
|
+
logoDraftToken: 'dae5cda0-602d-4c69-a26e-000000000001',
|
|
222
|
+
logoVariation: 0,
|
|
223
|
+
logoLastUpdate: '638000000000000000',
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const makeItem = () => ({
|
|
227
|
+
id: 'item-1',
|
|
228
|
+
type: 'logo',
|
|
229
|
+
settings: {
|
|
230
|
+
content: {
|
|
231
|
+
title: '<p>old</p>',
|
|
232
|
+
isLogoVisible: true,
|
|
233
|
+
logoImage: logoImage(),
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const setupStore = () => {
|
|
239
|
+
setActivePinia(createPinia());
|
|
240
|
+
|
|
241
|
+
const store = useUiContentSettingsStore();
|
|
242
|
+
|
|
243
|
+
store.items.push(makeItem() as never);
|
|
244
|
+
|
|
245
|
+
store.validateItem = vi.fn();
|
|
246
|
+
store.onContentChanged = vi.fn();
|
|
247
|
+
|
|
248
|
+
return store;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
beforeEach(() => {
|
|
252
|
+
mergeNextMutationMock.mockClear();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('updateMultipleItemValues drops the logo-panel Upload re-click payload', () => {
|
|
256
|
+
const store = setupStore();
|
|
257
|
+
|
|
258
|
+
// Deep-equal to the model but every reference is fresh — exactly what
|
|
259
|
+
// the Upload option click emits when visibility didn't change.
|
|
260
|
+
store.updateMultipleItemValues({
|
|
261
|
+
id: 'item-1',
|
|
262
|
+
name: 'logoImage',
|
|
263
|
+
value: { logoImage: logoImage(), isLogoVisible: true },
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
expect(store.onContentChanged).not.toHaveBeenCalled();
|
|
267
|
+
expect(store.validateItem).not.toHaveBeenCalled();
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test('updateItemValue drops an identical primitive', () => {
|
|
271
|
+
const store = setupStore();
|
|
272
|
+
|
|
273
|
+
store.updateItemValue({
|
|
274
|
+
id: 'item-1',
|
|
275
|
+
name: 'title',
|
|
276
|
+
value: '<p>old</p>',
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
expect(store.onContentChanged).not.toHaveBeenCalled();
|
|
280
|
+
expect(store.validateItem).not.toHaveBeenCalled();
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test('updateMultipleItemValues writes when any one field differs', () => {
|
|
284
|
+
const store = setupStore();
|
|
285
|
+
|
|
286
|
+
store.updateMultipleItemValues({
|
|
287
|
+
id: 'item-1',
|
|
288
|
+
name: 'logoImage',
|
|
289
|
+
value: { logoImage: logoImage(), isLogoVisible: false },
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
expect(store.onContentChanged).toHaveBeenCalledTimes(1);
|
|
293
|
+
expect(
|
|
294
|
+
(store.items[0].settings.content as { isLogoVisible?: boolean })
|
|
295
|
+
.isLogoVisible,
|
|
296
|
+
).toBe(false);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test('a dropped write never arms the history merge, even when hydration-flagged', () => {
|
|
300
|
+
const store = setupStore();
|
|
301
|
+
|
|
302
|
+
store.updateItemValue({
|
|
303
|
+
id: 'item-1',
|
|
304
|
+
name: 'title',
|
|
305
|
+
value: '<p>old</p>',
|
|
306
|
+
isHydrationWrite: true,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
expect(mergeNextMutationMock).not.toHaveBeenCalled();
|
|
310
|
+
});
|
|
311
|
+
});
|
|
@@ -60,6 +60,19 @@ const DEFAULT_TEXT_COLOR_DEPENDENT_TYPES: readonly ContentType[] = [
|
|
|
60
60
|
'text',
|
|
61
61
|
] as const;
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* True when the incoming value is byte-identical (as JSON, matching how
|
|
65
|
+
* history snapshots the model) to what the model already holds. Such
|
|
66
|
+
* re-writes must be dropped whole: the assignment would be a reactive
|
|
67
|
+
* no-op, but onContentChanged() still flips isModifiedAfterTemplateApplied
|
|
68
|
+
* — and when that flag was false, its flip is itself a model mutation, so
|
|
69
|
+
* undo history commits an entry in which nothing user-visible changed
|
|
70
|
+
* (phantom undo state, BP-5772: re-clicking the logo panel's Upload/Logo
|
|
71
|
+
* option, re-clicking an already-active display style).
|
|
72
|
+
*/
|
|
73
|
+
const isSameValue = (a: unknown, b: unknown): boolean =>
|
|
74
|
+
a === b || JSON.stringify(a) === JSON.stringify(b);
|
|
75
|
+
|
|
63
76
|
export const CONTENT_SETTINGS_MODES = {
|
|
64
77
|
edit: 'edit',
|
|
65
78
|
add: 'add',
|
|
@@ -390,6 +403,10 @@ export const useUiContentSettingsStore = defineStore('uiContentSettings', {
|
|
|
390
403
|
return;
|
|
391
404
|
}
|
|
392
405
|
|
|
406
|
+
if (isSameValue(item.settings.content[payload.name], payload.value)) {
|
|
407
|
+
return; // no-op write: don't validate, don't flip the modified flag
|
|
408
|
+
}
|
|
409
|
+
|
|
393
410
|
// Hydration write-backs (RTE canonical HTML on mount, lazy config
|
|
394
411
|
// hydration, stale-id clears) fold into the current undo head instead
|
|
395
412
|
// of pushing an entry. Arming here — same tick as the write — is what
|
|
@@ -421,6 +438,17 @@ export const useUiContentSettingsStore = defineStore('uiContentSettings', {
|
|
|
421
438
|
return;
|
|
422
439
|
}
|
|
423
440
|
|
|
441
|
+
if (
|
|
442
|
+
Object.keys(value ?? {}).every((k) =>
|
|
443
|
+
isSameValue(
|
|
444
|
+
item.settings.content[k],
|
|
445
|
+
(value as Record<string, unknown>)[k],
|
|
446
|
+
),
|
|
447
|
+
)
|
|
448
|
+
) {
|
|
449
|
+
return; // every field a no-op — see isSameValue
|
|
450
|
+
}
|
|
451
|
+
|
|
424
452
|
// Same hydration-write contract as updateItemValue.
|
|
425
453
|
if (isHydrationWrite) {
|
|
426
454
|
useHistoryStore().mergeNextMutation();
|