@designcrowd/library.brand-page 5.19.1-id-based-items-1 → 5.19.1

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.
Files changed (47) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-0PcGqy0N.js → CreatedOnBrandCrowd-de-de-BmeMc9oz.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-DB8wcFMI.js → CreatedOnBrandCrowd-en-us-CQNdm_6P.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-DVWQ243C.js → CreatedOnBrandCrowd-es-es-BrmHWeAM.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-D-M_L0Du.js → CreatedOnBrandCrowd-fr-ca-CaIyjTXl.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-D-M_L0Du.js → CreatedOnBrandCrowd-fr-fr-CaIyjTXl.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-CNpzknxU.js → CreatedOnBrandCrowd-pt-br-BClE0Nd9.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-CNpzknxU.js → CreatedOnBrandCrowd-pt-pt-BClE0Nd9.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-DMJoD5wE.js → CreatedOnDcom-de-de-CkrdlPqK.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-B-F3Pmjv.js → CreatedOnDcom-en-us-D33J9qky.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-DmLk-cA2.js → CreatedOnDcom-es-es-BsmInngw.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-ntnsCzvA.js → CreatedOnDcom-fr-ca-DOXlsRea.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-DFcO0gHL.js → CreatedOnDcom-fr-fr-SfFYLtyn.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-CtuJoVXW.js → CreatedOnDcom-pt-br-DcWeuZUy.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-B7shZVVn.js → CreatedOnDcom-pt-pt-DZsZZ4iX.js} +1 -1
  15. package/lib/src/brand-page/components/image-gallery/masonry/MasonryGallery.mixin.d.ts +1 -1
  16. package/lib/src/brand-page/models/index.d.ts +0 -12
  17. package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +0 -18
  18. package/lib/src/index.mjs +1 -1
  19. package/lib/src/{lib-BQmJBVbS.js → lib-C5vGkjfs.js} +17561 -17370
  20. package/lib/src/metadata-builder/builders/footer-builder.js +44 -34
  21. package/lib/src/metadata-builder/builders/section-builders/image-gallery-builder.js +68 -57
  22. package/lib/src/metadata-builder/builders/section-builders/images-section-builder.js +93 -58
  23. package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.js +99 -66
  24. package/lib/src/metadata-builder/builders/section-builders/social-links-builder.js +17 -57
  25. package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.js +85 -51
  26. package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +74 -49
  27. package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +4 -13
  28. package/lib/src/metadata-builder/parsers/section-tool-arguments.d.ts +48 -66
  29. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +0 -2
  30. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +0 -2
  31. package/package.json +1 -1
  32. package/src/brand-page/components/image-gallery/masonry/MasonryGallery.mixin.ts +1 -2
  33. package/src/brand-page/models/index.ts +0 -12
  34. package/src/metadata-builder/builders/footer-builder.test.ts +0 -298
  35. package/src/metadata-builder/builders/footer-builder.ts +65 -56
  36. package/src/metadata-builder/builders/section-builders/image-gallery-builder.ts +88 -62
  37. package/src/metadata-builder/builders/section-builders/images-section-builder.ts +129 -66
  38. package/src/metadata-builder/builders/section-builders/shapes-section-builder.ts +160 -105
  39. package/src/metadata-builder/builders/section-builders/social-links-builder.ts +20 -65
  40. package/src/metadata-builder/builders/section-builders/testimonial-section-builder.ts +146 -83
  41. package/src/metadata-builder/builders/section-builders/text-section-builder.ts +122 -70
  42. package/src/metadata-builder/parsers/ai-toolcall-parser.ts +4 -13
  43. package/src/metadata-builder/parsers/section-tool-arguments.ts +48 -66
  44. package/lib/src/metadata-builder/builders/item-id-utils.d.ts +0 -41
  45. package/lib/src/metadata-builder/builders/item-id-utils.js +0 -68
  46. package/src/metadata-builder/builders/item-id-utils.test.ts +0 -118
  47. package/src/metadata-builder/builders/item-id-utils.ts +0 -85
@@ -142,15 +142,10 @@ export interface ProfileSectionArguments {
142
142
  }
143
143
 
144
144
  export interface TextSectionItem {
145
- /** Stable id of the existing item to update or move. Omit to ADD a new item. */
146
- item_id?: string;
147
- /**
148
- * Id of an existing item to position this item before (insert for new items,
149
- * move for existing ones). Use "__end__" for the end of the list. Omit to
150
- * leave an existing item in place / append a new item. Mirrors the
151
- * section-level move_before_content_id.
152
- */
153
- move_before_item_id?: string;
145
+ /** 0-based index of existing item to update in-place */
146
+ item_index?: number;
147
+ /** 0-based position to insert a new item at */
148
+ insert_at?: number;
154
149
  text?: RichTextField;
155
150
  text_style?: TextStyleArguments;
156
151
  }
@@ -164,8 +159,10 @@ export interface TextSectionArguments {
164
159
  /** Multiple text items (BrandSite) */
165
160
  items?: TextSectionItem[];
166
161
  _replace_items?: boolean;
167
- /** Stable ids of items to remove. Order-independent (no index shifting). */
168
- _remove_item_ids?: string[];
162
+ /** 0-based indices of items to remove */
163
+ _remove_item_indices?: number[];
164
+ /** New order as array of current 0-based indices */
165
+ _reorder_items?: number[];
169
166
  background_color?: string | null;
170
167
  }
171
168
 
@@ -175,15 +172,10 @@ export interface ImageSectionArguments {
175
172
  }
176
173
 
177
174
  export interface ImageGalleryItem {
178
- /** Stable id of the existing item to update or move. Omit to ADD a new item. */
179
- item_id?: string;
180
- /**
181
- * Id of an existing item to position this item before (insert for new items,
182
- * move for existing ones). Use "__end__" for the end of the list. Omit to
183
- * leave an existing item in place / append a new item. Mirrors the
184
- * section-level move_before_content_id.
185
- */
186
- move_before_item_id?: string;
175
+ /** 0-based index of existing item to update in-place */
176
+ item_index?: number;
177
+ /** 0-based position to insert a new item at */
178
+ insert_at?: number;
187
179
  image_url?: string;
188
180
  caption?: string;
189
181
  }
@@ -193,8 +185,10 @@ export interface ImageGallerySectionArguments {
193
185
  gallery_type?: 'grid' | 'carousel' | 'masonry';
194
186
  is_full_width?: boolean;
195
187
  _replace_items?: boolean;
196
- /** Stable ids of items to remove. Order-independent (no index shifting). */
197
- _remove_item_ids?: string[];
188
+ /** 0-based indices of items to remove */
189
+ _remove_item_indices?: number[];
190
+ /** New order as array of current 0-based indices */
191
+ _reorder_items?: number[];
198
192
  }
199
193
 
200
194
  export interface GeneralLinkSectionArguments {
@@ -262,15 +256,10 @@ export interface TiktokSectionArguments {
262
256
  }
263
257
 
264
258
  export interface SocialLink {
265
- /** Stable id of the existing item to update or move. Omit to ADD a new item. */
266
- item_id?: string;
267
- /**
268
- * Id of an existing item to position this item before (insert for new items,
269
- * move for existing ones). Use "__end__" for the end of the list. Omit to
270
- * leave an existing item in place / append a new item. Mirrors the
271
- * section-level move_before_content_id.
272
- */
273
- move_before_item_id?: string;
259
+ /** 0-based index of existing item to update in-place */
260
+ item_index?: number;
261
+ /** 0-based position to insert a new item at */
262
+ insert_at?: number;
274
263
  platform?:
275
264
  | 'facebook'
276
265
  | 'x'
@@ -291,8 +280,10 @@ export interface SocialLinksSectionArguments {
291
280
  /** Style for the icon container background (used when icon_style is 'square' or 'circle'). */
292
281
  icon_background_style?: TextStyleArguments;
293
282
  _replace_items?: boolean;
294
- /** Stable ids of items to remove. Order-independent (no index shifting). */
295
- _remove_item_ids?: string[];
283
+ /** 0-based indices of items to remove */
284
+ _remove_item_indices?: number[];
285
+ /** New order as array of current 0-based indices */
286
+ _reorder_items?: number[];
296
287
  }
297
288
 
298
289
  export interface MailingListSectionArguments {
@@ -380,15 +371,10 @@ export interface AboutSectionArguments {
380
371
  }
381
372
 
382
373
  export interface ImagesSectionItem {
383
- /** Stable id of the existing item to update or move. Omit to ADD a new item. */
384
- item_id?: string;
385
- /**
386
- * Id of an existing item to position this item before (insert for new items,
387
- * move for existing ones). Use "__end__" for the end of the list. Omit to
388
- * leave an existing item in place / append a new item. Mirrors the
389
- * section-level move_before_content_id.
390
- */
391
- move_before_item_id?: string;
374
+ /** 0-based index of existing item to update in-place */
375
+ item_index?: number;
376
+ /** 0-based position to insert a new item at */
377
+ insert_at?: number;
392
378
  image_url?: string;
393
379
  heading?: string;
394
380
  text?: string;
@@ -407,8 +393,10 @@ export interface ImagesSectionArguments {
407
393
  | 'center-card-aligned-image'
408
394
  | 'alternate-image';
409
395
  _replace_items?: boolean;
410
- /** Stable ids of items to remove. Order-independent (no index shifting). */
411
- _remove_item_ids?: string[];
396
+ /** 0-based indices of items to remove */
397
+ _remove_item_indices?: number[];
398
+ /** New order as array of current 0-based indices */
399
+ _reorder_items?: number[];
412
400
  title_style?: TextStyleArguments;
413
401
  description_style?: TextStyleArguments;
414
402
  heading_style?: TextStyleArguments;
@@ -421,15 +409,10 @@ export interface ImagesSectionArguments {
421
409
  }
422
410
 
423
411
  export interface TestimonialItem {
424
- /** Stable id of the existing item to update or move. Omit to ADD a new item. */
425
- item_id?: string;
426
- /**
427
- * Id of an existing item to position this item before (insert for new items,
428
- * move for existing ones). Use "__end__" for the end of the list. Omit to
429
- * leave an existing item in place / append a new item. Mirrors the
430
- * section-level move_before_content_id.
431
- */
432
- move_before_item_id?: string;
412
+ /** 0-based index of existing item to update in-place */
413
+ item_index?: number;
414
+ /** 0-based position to insert a new item at */
415
+ insert_at?: number;
433
416
  testimonial?: string;
434
417
  name?: string;
435
418
  position?: string;
@@ -443,8 +426,10 @@ export interface TestimonialSectionArguments {
443
426
  items?: TestimonialItem[];
444
427
  display_style?: 'centered' | 'big-image' | 'columns';
445
428
  _replace_items?: boolean;
446
- /** Stable ids of items to remove. Order-independent (no index shifting). */
447
- _remove_item_ids?: string[];
429
+ /** 0-based indices of items to remove */
430
+ _remove_item_indices?: number[];
431
+ /** New order as array of current 0-based indices */
432
+ _reorder_items?: number[];
448
433
  title_style?: TextStyleArguments;
449
434
  testimonial_style?: TextStyleArguments;
450
435
  name_style?: TextStyleArguments;
@@ -454,15 +439,10 @@ export interface TestimonialSectionArguments {
454
439
  }
455
440
 
456
441
  export interface ShapesSectionItem {
457
- /** Stable id of the existing item to update or move. Omit to ADD a new item. */
458
- item_id?: string;
459
- /**
460
- * Id of an existing item to position this item before (insert for new items,
461
- * move for existing ones). Use "__end__" for the end of the list. Omit to
462
- * leave an existing item in place / append a new item. Mirrors the
463
- * section-level move_before_content_id.
464
- */
465
- move_before_item_id?: string;
442
+ /** 0-based index of existing item to update in-place */
443
+ item_index?: number;
444
+ /** 0-based position to insert a new item at */
445
+ insert_at?: number;
466
446
  icon_image_url?: string;
467
447
  shape_url?: string;
468
448
  color_theme?: string;
@@ -482,8 +462,10 @@ export interface ShapesSectionArguments {
482
462
  | 'center-card-aligned-image'
483
463
  | 'alternate-image';
484
464
  _replace_items?: boolean;
485
- /** Stable ids of items to remove. Order-independent (no index shifting). */
486
- _remove_item_ids?: string[];
465
+ /** 0-based indices of items to remove */
466
+ _remove_item_indices?: number[];
467
+ /** New order as array of current 0-based indices */
468
+ _reorder_items?: number[];
487
469
  title_style?: TextStyleArguments;
488
470
  description_style?: TextStyleArguments;
489
471
  heading_style?: TextStyleArguments;
@@ -1,41 +0,0 @@
1
- /**
2
- * Sentinel anchor meaning "the end of the list", mirroring the section-level
3
- * `move_before_content_id: "__end__"` convention used in base-section-builder.
4
- */
5
- export declare const END_ITEM_ANCHOR = "__end__";
6
- /** Generate a stable item id. Mirrors section-level `uuid.v4()` id generation. */
7
- export declare const generateItemId: () => string;
8
- /**
9
- * Backfill ids onto any items missing one. Per BP-5603 this runs in the
10
- * metadata builder when a section is processed, so the persisted metadata
11
- * returned to the client gains stable ids that subsequent AI tool calls can
12
- * reference. Mutates in place.
13
- */
14
- export declare const ensureItemIds: <T extends {
15
- id?: string | undefined;
16
- }>(items: T[] | undefined) => void;
17
- /**
18
- * Remove items whose id appears in `ids`. Order-independent (no descending
19
- * index sort needed) — the whole point of the id-based migration. Items
20
- * without an id are never removed by id.
21
- */
22
- export declare const removeItemsByIds: <T extends {
23
- id?: string | undefined;
24
- }>(items: T[] | undefined, ids: string[] | undefined) => T[];
25
- /**
26
- * Position `item` within `items` relative to an anchor id — the item-level
27
- * mirror of base-section-builder's `moveContent`:
28
- *
29
- * - `moveBeforeItemId === undefined` → leave an existing item where it is;
30
- * append a new (not-yet-present) item to the end.
31
- * - `moveBeforeItemId === "__end__"` → move/insert at the very end.
32
- * - anchor id found → insert immediately before it.
33
- * - anchor id not found → fall through to the end (same
34
- * forgiving behaviour as `moveContent`).
35
- *
36
- * Mutates `items` in place. `item` may be an existing element of `items`
37
- * (a move) or a brand-new object (an insert).
38
- */
39
- export declare const repositionItemByAnchor: <T extends {
40
- id?: string | undefined;
41
- }>(items: T[], item: T, moveBeforeItemId: string | undefined) => void;
@@ -1,68 +0,0 @@
1
- import { v4 as uuidv4 } from 'uuid';
2
- /**
3
- * Sentinel anchor meaning "the end of the list", mirroring the section-level
4
- * `move_before_content_id: "__end__"` convention used in base-section-builder.
5
- */
6
- export const END_ITEM_ANCHOR = '__end__';
7
- /** Generate a stable item id. Mirrors section-level `uuid.v4()` id generation. */
8
- export const generateItemId = () => uuidv4();
9
- /**
10
- * Backfill ids onto any items missing one. Per BP-5603 this runs in the
11
- * metadata builder when a section is processed, so the persisted metadata
12
- * returned to the client gains stable ids that subsequent AI tool calls can
13
- * reference. Mutates in place.
14
- */
15
- export const ensureItemIds = (items) => {
16
- if (!items)
17
- return;
18
- for (const item of items) {
19
- if (!item.id)
20
- item.id = generateItemId();
21
- }
22
- };
23
- /**
24
- * Remove items whose id appears in `ids`. Order-independent (no descending
25
- * index sort needed) — the whole point of the id-based migration. Items
26
- * without an id are never removed by id.
27
- */
28
- export const removeItemsByIds = (items, ids) => {
29
- if (!items?.length)
30
- return items ?? [];
31
- if (!ids?.length)
32
- return items;
33
- const toRemove = new Set(ids);
34
- return items.filter((item) => !item.id || !toRemove.has(item.id));
35
- };
36
- /**
37
- * Position `item` within `items` relative to an anchor id — the item-level
38
- * mirror of base-section-builder's `moveContent`:
39
- *
40
- * - `moveBeforeItemId === undefined` → leave an existing item where it is;
41
- * append a new (not-yet-present) item to the end.
42
- * - `moveBeforeItemId === "__end__"` → move/insert at the very end.
43
- * - anchor id found → insert immediately before it.
44
- * - anchor id not found → fall through to the end (same
45
- * forgiving behaviour as `moveContent`).
46
- *
47
- * Mutates `items` in place. `item` may be an existing element of `items`
48
- * (a move) or a brand-new object (an insert).
49
- */
50
- export const repositionItemByAnchor = (items, item, moveBeforeItemId) => {
51
- const currentIndex = items.indexOf(item);
52
- if (moveBeforeItemId === undefined) {
53
- if (currentIndex === -1)
54
- items.push(item);
55
- return;
56
- }
57
- if (currentIndex !== -1)
58
- items.splice(currentIndex, 1);
59
- const anchorIndex = moveBeforeItemId === END_ITEM_ANCHOR
60
- ? -1
61
- : items.findIndex((i) => i.id === moveBeforeItemId);
62
- if (anchorIndex !== -1) {
63
- items.splice(anchorIndex, 0, item);
64
- }
65
- else {
66
- items.push(item);
67
- }
68
- };
@@ -1,118 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import {
3
- END_ITEM_ANCHOR,
4
- ensureItemIds,
5
- generateItemId,
6
- removeItemsByIds,
7
- repositionItemByAnchor,
8
- } from './item-id-utils';
9
-
10
- type Item = { id?: string; label: string };
11
-
12
- const items = (...labels: string[]): Item[] =>
13
- labels.map((label) => ({ id: label, label }));
14
-
15
- const labels = (list: Item[]): string[] => list.map((i) => i.label);
16
-
17
- describe('generateItemId', () => {
18
- it('returns unique uuid-shaped ids', () => {
19
- const a = generateItemId();
20
- const b = generateItemId();
21
-
22
- expect(a).not.toBe(b);
23
- expect(a).toMatch(
24
- /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
25
- );
26
- });
27
- });
28
-
29
- describe('ensureItemIds', () => {
30
- it('backfills only the items missing an id, preserving existing ones', () => {
31
- const list: Item[] = [
32
- { id: 'keep', label: 'a' },
33
- { label: 'b' },
34
- { id: '', label: 'c' },
35
- ];
36
-
37
- ensureItemIds(list);
38
-
39
- expect(list[0].id).toBe('keep');
40
- expect(list[1].id).toBeTruthy();
41
- expect(list[2].id).toBeTruthy();
42
- expect(new Set(list.map((i) => i.id)).size).toBe(3);
43
- });
44
-
45
- it('is a no-op for undefined', () => {
46
- expect(() => ensureItemIds(undefined)).not.toThrow();
47
- });
48
- });
49
-
50
- describe('removeItemsByIds', () => {
51
- it('removes by id regardless of order (no index shifting)', () => {
52
- const result = removeItemsByIds(items('a', 'b', 'c', 'd'), ['c', 'a']);
53
-
54
- expect(labels(result)).toEqual(['b', 'd']);
55
- });
56
-
57
- it('returns the list unchanged when no ids given', () => {
58
- const list = items('a', 'b');
59
-
60
- expect(removeItemsByIds(list, undefined)).toBe(list);
61
- expect(removeItemsByIds(list, [])).toBe(list);
62
- });
63
-
64
- it('returns [] for undefined items', () => {
65
- expect(removeItemsByIds(undefined, ['x'])).toEqual([]);
66
- });
67
- });
68
-
69
- describe('repositionItemByAnchor', () => {
70
- it('appends a new item when no anchor is given', () => {
71
- const list = items('a', 'b');
72
-
73
- repositionItemByAnchor(list, { id: 'new', label: 'new' }, undefined);
74
-
75
- expect(labels(list)).toEqual(['a', 'b', 'new']);
76
- });
77
-
78
- it('leaves an existing item in place when no anchor is given', () => {
79
- const list = items('a', 'b', 'c');
80
-
81
- repositionItemByAnchor(list, list[1], undefined);
82
-
83
- expect(labels(list)).toEqual(['a', 'b', 'c']);
84
- });
85
-
86
- it('inserts a new item before the anchor id', () => {
87
- const list = items('a', 'b', 'c');
88
-
89
- repositionItemByAnchor(list, { id: 'new', label: 'new' }, 'b');
90
-
91
- expect(labels(list)).toEqual(['a', 'new', 'b', 'c']);
92
- });
93
-
94
- it('moves an existing item before the anchor id', () => {
95
- const list = items('a', 'b', 'c', 'd');
96
-
97
- // move 'd' before 'b'
98
- repositionItemByAnchor(list, list[3], 'b');
99
-
100
- expect(labels(list)).toEqual(['a', 'd', 'b', 'c']);
101
- });
102
-
103
- it('moves an existing item to the end with the __end__ sentinel', () => {
104
- const list = items('a', 'b', 'c');
105
-
106
- repositionItemByAnchor(list, list[0], END_ITEM_ANCHOR);
107
-
108
- expect(labels(list)).toEqual(['b', 'c', 'a']);
109
- });
110
-
111
- it('falls through to the end when the anchor id is not found', () => {
112
- const list = items('a', 'b');
113
-
114
- repositionItemByAnchor(list, { id: 'new', label: 'new' }, 'missing');
115
-
116
- expect(labels(list)).toEqual(['a', 'b', 'new']);
117
- });
118
- });
@@ -1,85 +0,0 @@
1
- import { v4 as uuidv4 } from 'uuid';
2
-
3
- /**
4
- * Sentinel anchor meaning "the end of the list", mirroring the section-level
5
- * `move_before_content_id: "__end__"` convention used in base-section-builder.
6
- */
7
- export const END_ITEM_ANCHOR = '__end__';
8
-
9
- /** Generate a stable item id. Mirrors section-level `uuid.v4()` id generation. */
10
- export const generateItemId = (): string => uuidv4();
11
-
12
- /**
13
- * Backfill ids onto any items missing one. Per BP-5603 this runs in the
14
- * metadata builder when a section is processed, so the persisted metadata
15
- * returned to the client gains stable ids that subsequent AI tool calls can
16
- * reference. Mutates in place.
17
- */
18
- export const ensureItemIds = <T extends { id?: string }>(
19
- items: T[] | undefined,
20
- ): void => {
21
- if (!items) return;
22
-
23
- for (const item of items) {
24
- if (!item.id) item.id = generateItemId();
25
- }
26
- };
27
-
28
- /**
29
- * Remove items whose id appears in `ids`. Order-independent (no descending
30
- * index sort needed) — the whole point of the id-based migration. Items
31
- * without an id are never removed by id.
32
- */
33
- export const removeItemsByIds = <T extends { id?: string }>(
34
- items: T[] | undefined,
35
- ids: string[] | undefined,
36
- ): T[] => {
37
- if (!items?.length) return items ?? [];
38
-
39
- if (!ids?.length) return items;
40
-
41
- const toRemove = new Set(ids);
42
-
43
- return items.filter((item) => !item.id || !toRemove.has(item.id));
44
- };
45
-
46
- /**
47
- * Position `item` within `items` relative to an anchor id — the item-level
48
- * mirror of base-section-builder's `moveContent`:
49
- *
50
- * - `moveBeforeItemId === undefined` → leave an existing item where it is;
51
- * append a new (not-yet-present) item to the end.
52
- * - `moveBeforeItemId === "__end__"` → move/insert at the very end.
53
- * - anchor id found → insert immediately before it.
54
- * - anchor id not found → fall through to the end (same
55
- * forgiving behaviour as `moveContent`).
56
- *
57
- * Mutates `items` in place. `item` may be an existing element of `items`
58
- * (a move) or a brand-new object (an insert).
59
- */
60
- export const repositionItemByAnchor = <T extends { id?: string }>(
61
- items: T[],
62
- item: T,
63
- moveBeforeItemId: string | undefined,
64
- ): void => {
65
- const currentIndex = items.indexOf(item);
66
-
67
- if (moveBeforeItemId === undefined) {
68
- if (currentIndex === -1) items.push(item);
69
-
70
- return;
71
- }
72
-
73
- if (currentIndex !== -1) items.splice(currentIndex, 1);
74
-
75
- const anchorIndex =
76
- moveBeforeItemId === END_ITEM_ANCHOR
77
- ? -1
78
- : items.findIndex((i) => i.id === moveBeforeItemId);
79
-
80
- if (anchorIndex !== -1) {
81
- items.splice(anchorIndex, 0, item);
82
- } else {
83
- items.push(item);
84
- }
85
- };