@salesforce/webapp-experimental 1.68.0 → 1.69.0

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.
@@ -70,6 +70,17 @@
70
70
  }
71
71
  return parseSourceFileAttribute(source);
72
72
  }
73
+ function findElementsBySourceLocation(location) {
74
+ const results = [];
75
+ const elements = document.querySelectorAll("[data-source-file]");
76
+ for (const el of elements) {
77
+ const elSource = getSourceFromDataAttributes(el);
78
+ if (elSource && elSource.fileName === location.fileName && elSource.lineNumber === location.lineNumber && elSource.columnNumber === location.columnNumber) {
79
+ results.push(el);
80
+ }
81
+ }
82
+ return results;
83
+ }
73
84
  function getLabelFromSource(element) {
74
85
  if (!element) {
75
86
  return "";
@@ -233,28 +244,6 @@
233
244
  }
234
245
  return true;
235
246
  }
236
- /**
237
- * Find all same elements that share the same data-source-file value.
238
- * These are elements rendered by the same component at the same source location.
239
- * @param element - The reference element
240
- * @returns Array of same elements (excludes the element itself)
241
- */
242
- findSameElements(element) {
243
- const sourceFile = element.getAttribute("data-source-file");
244
- if (!sourceFile) {
245
- return [];
246
- }
247
- const all = document.querySelectorAll(
248
- `[data-source-file="${CSS.escape(sourceFile)}"]`
249
- );
250
- const sameElements = [];
251
- for (const el of all) {
252
- if (el !== element) {
253
- sameElements.push(el);
254
- }
255
- }
256
- return sameElements;
257
- }
258
247
  /**
259
248
  * Find the nearest highlightable element by walking up the DOM tree
260
249
  * @param target - The target element
@@ -370,35 +359,24 @@
370
359
  __publicField(this, "styleManager");
371
360
  __publicField(this, "editableManager");
372
361
  __publicField(this, "communicationManager");
373
- __publicField(this, "currentHighlighted");
374
- __publicField(this, "currentHighlightedSameElements");
362
+ __publicField(this, "currentHighlightedElements");
375
363
  __publicField(this, "selectedElement");
376
- __publicField(this, "selectedSameElements");
364
+ __publicField(this, "selectedElements");
377
365
  this.isInteractionsActive = isInteractionsActive;
378
366
  this.componentMatcher = componentMatcher;
379
367
  this.styleManager = styleManager;
380
368
  this.editableManager = editableManager;
381
369
  this.communicationManager = communicationManager;
382
- this.currentHighlighted = null;
383
- this.currentHighlightedSameElements = [];
370
+ this.currentHighlightedElements = [];
384
371
  this.selectedElement = null;
385
- this.selectedSameElements = [];
372
+ this.selectedElements = [];
386
373
  this.handleMouseOver = this.handleMouseOver.bind(this);
387
374
  this.handleMouseLeave = this.handleMouseLeave.bind(this);
388
375
  this.handleClick = this.handleClick.bind(this);
389
376
  }
390
- /**
391
- * Find the nearest highlightable element by walking up the DOM tree
392
- * @param target - The target element
393
- * @returns The highlightable element or null
394
- */
395
377
  _findHighlightableElement(target) {
396
378
  return this.componentMatcher.findHighlightableElement(target);
397
379
  }
398
- /**
399
- * Handle mouseover event
400
- * @param e - The mouseover event
401
- */
402
380
  handleMouseOver(e) {
403
381
  if (!this.isInteractionsActive()) {
404
382
  return;
@@ -412,41 +390,26 @@
412
390
  if (!element) {
413
391
  return;
414
392
  }
415
- if (this.currentHighlighted === element || this.selectedElement && this.selectedElement === element) {
393
+ if (this.currentHighlightedElements.includes(element) || this.selectedElement && this.selectedElement === element) {
416
394
  return;
417
395
  }
418
- if (this.currentHighlighted && !this.currentHighlighted.classList.contains("design-mode-selected")) {
419
- this.styleManager.unhighlightElements([
420
- this.currentHighlighted,
421
- ...this.currentHighlightedSameElements
422
- ]);
423
- this.currentHighlightedSameElements = [];
396
+ if (this.currentHighlightedElements.length > 0 && !this.currentHighlightedElements[0].classList.contains("design-mode-selected")) {
397
+ this.styleManager.unhighlightElements(this.currentHighlightedElements);
398
+ this.currentHighlightedElements = [];
424
399
  }
425
- const sameElements = this.componentMatcher.findSameElements(element);
426
- this.styleManager.highlightElements([element, ...sameElements]);
427
- this.currentHighlighted = element;
428
- this.currentHighlightedSameElements = sameElements;
400
+ const allElements = findElementsBySourceLocation(getSourceFromDataAttributes(element));
401
+ this.styleManager.highlightElements(allElements);
402
+ this.currentHighlightedElements = allElements;
429
403
  }
430
- /**
431
- * Handle mouseleave event
432
- */
433
404
  handleMouseLeave() {
434
405
  if (!this.isInteractionsActive()) {
435
406
  return;
436
407
  }
437
- if (this.currentHighlighted && !this.currentHighlighted.classList.contains("design-mode-selected")) {
438
- this.styleManager.unhighlightElements([
439
- this.currentHighlighted,
440
- ...this.currentHighlightedSameElements
441
- ]);
442
- this.currentHighlighted = null;
443
- this.currentHighlightedSameElements = [];
408
+ if (this.currentHighlightedElements.length > 0 && !this.currentHighlightedElements[0].classList.contains("design-mode-selected")) {
409
+ this.styleManager.unhighlightElements(this.currentHighlightedElements);
410
+ this.currentHighlightedElements = [];
444
411
  }
445
412
  }
446
- /**
447
- * Handle click event
448
- * @param e - The click event
449
- */
450
413
  handleClick(e) {
451
414
  if (!this.isInteractionsActive()) {
452
415
  return;
@@ -465,57 +428,35 @@
465
428
  return;
466
429
  }
467
430
  if (this.selectedElement) {
468
- this.styleManager.deselectElements([this.selectedElement, ...this.selectedSameElements]);
431
+ this.styleManager.deselectElements(this.selectedElements);
469
432
  this.editableManager.removeEditable(this.selectedElement);
470
433
  }
471
- if (this.currentHighlighted) {
472
- this.styleManager.unhighlightElements([
473
- this.currentHighlighted,
474
- ...this.currentHighlightedSameElements
475
- ]);
476
- this.currentHighlighted = null;
477
- this.currentHighlightedSameElements = [];
434
+ if (this.currentHighlightedElements.length > 0) {
435
+ this.styleManager.unhighlightElements(this.currentHighlightedElements);
436
+ this.currentHighlightedElements = [];
478
437
  }
479
438
  this.selectedElement = element;
480
- const sameElements = this.componentMatcher.findSameElements(element);
481
- this.selectedSameElements = sameElements;
482
- this.styleManager.selectElements([element, ...sameElements]);
439
+ const allElements = findElementsBySourceLocation(getSourceFromDataAttributes(element));
440
+ this.selectedElements = allElements;
441
+ this.styleManager.selectElements(allElements);
483
442
  this.editableManager.makeEditableIfText(element);
484
443
  this.communicationManager.notifyComponentSelected(element);
485
444
  }
486
- /**
487
- * Clear all highlights and selections
488
- */
489
445
  clearAll() {
490
- if (this.currentHighlighted) {
491
- this.styleManager.unhighlightElements([
492
- this.currentHighlighted,
493
- ...this.currentHighlightedSameElements
494
- ]);
495
- this.currentHighlighted = null;
496
- this.currentHighlightedSameElements = [];
446
+ if (this.currentHighlightedElements.length > 0) {
447
+ this.styleManager.unhighlightElements(this.currentHighlightedElements);
448
+ this.currentHighlightedElements = [];
497
449
  }
498
450
  if (this.selectedElement) {
499
- this.styleManager.deselectElements([this.selectedElement, ...this.selectedSameElements]);
451
+ this.styleManager.deselectElements(this.selectedElements);
500
452
  this.editableManager.removeEditable(this.selectedElement);
501
453
  this.selectedElement = null;
502
- this.selectedSameElements = [];
454
+ this.selectedElements = [];
503
455
  }
504
456
  }
505
- /**
506
- * Get the currently selected element
507
- * @returns The selected element
508
- */
509
457
  getSelectedElement() {
510
458
  return this.selectedElement;
511
459
  }
512
- /**
513
- * Get the same elements of the currently selected element
514
- * @returns Array of same elements
515
- */
516
- getSelectedSameElements() {
517
- return this.selectedSameElements;
518
- }
519
460
  };
520
461
 
521
462
  // src/design/interactions/styleManager.ts
@@ -700,17 +641,20 @@
700
641
  console.log("Design Mode Interactions disabled");
701
642
  }
702
643
  /**
703
- * Apply style changes to the selected element
704
- * @param property - CSS property name
705
- * @param value - CSS property value
644
+ * Apply a style change to all elements at the given source location.
645
+ * When sourceLocation is provided (undo/redo), it is used directly.
646
+ * Otherwise the source location is read from the currently selected element.
706
647
  */
707
- applyStyleChange(property, value) {
708
- const selectedElement = this.eventHandlers.getSelectedElement();
709
- if (selectedElement) {
710
- selectedElement.style[property] = value;
711
- for (const sameElement of this.eventHandlers.getSelectedSameElements()) {
712
- sameElement.style[property] = value;
713
- }
648
+ applyStyleChange(property, value, sourceLocation) {
649
+ let location = sourceLocation ?? null;
650
+ if (!location?.fileName) {
651
+ const selectedElement = this.eventHandlers.getSelectedElement();
652
+ location = getSourceFromDataAttributes(selectedElement);
653
+ }
654
+ if (!location) return;
655
+ const targets = findElementsBySourceLocation(location);
656
+ for (const el of targets) {
657
+ el.style[property] = value;
714
658
  }
715
659
  }
716
660
  /**
@@ -747,7 +691,17 @@
747
691
  const data = event.data;
748
692
  const typed = data && typeof data === "object" ? data : null;
749
693
  if (typed && typed.type === "style-change") {
750
- interactions.applyStyleChange(String(typed.property ?? ""), String(typed.value ?? ""));
694
+ const sl = typed.sourceLocation;
695
+ const sourceLocation = sl && typeof sl === "object" ? {
696
+ fileName: String(sl.sourceFile ?? ""),
697
+ lineNumber: typeof sl.lineNumber === "number" ? sl.lineNumber : null,
698
+ columnNumber: typeof sl.columnNumber === "number" ? sl.columnNumber : null
699
+ } : void 0;
700
+ interactions.applyStyleChange(
701
+ String(typed.property ?? ""),
702
+ String(typed.value ?? ""),
703
+ sourceLocation
704
+ );
751
705
  }
752
706
  if (typed && typed.type === "enable-interactions") {
753
707
  window.enableInteractions?.();
@@ -33,13 +33,6 @@ export declare class ComponentMatcher {
33
33
  * @returns True if the element should be highlighted
34
34
  */
35
35
  isHighlightableElement(element: HTMLElement | null | undefined): boolean;
36
- /**
37
- * Find all same elements that share the same data-source-file value.
38
- * These are elements rendered by the same component at the same source location.
39
- * @param element - The reference element
40
- * @returns Array of same elements (excludes the element itself)
41
- */
42
- findSameElements(element: HTMLElement): HTMLElement[];
43
36
  /**
44
37
  * Find the nearest highlightable element by walking up the DOM tree
45
38
  * @param target - The target element
@@ -1 +1 @@
1
- {"version":3,"file":"componentMatcher.d.ts","sourceRoot":"","sources":["../../../src/design/interactions/componentMatcher.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AAEH,MAAM,WAAW,uBAAuB;IAEvC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,SAAS,CAAW;gBAEhB,OAAO,GAAE,uBAA4B;IAIjD;;;;OAIG;IACH,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO;IAOnE,OAAO,CAAC,WAAW;IAInB;;;;;OAKG;IACH,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO;IAM7D;;;;OAIG;IACH,sBAAsB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO;IAgBxE;;;;;OAKG;IACH,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW,EAAE;IAmBrD;;;;OAIG;IACH,wBAAwB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,WAAW,GAAG,IAAI;CA+BpF"}
1
+ {"version":3,"file":"componentMatcher.d.ts","sourceRoot":"","sources":["../../../src/design/interactions/componentMatcher.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AAEH,MAAM,WAAW,uBAAuB;IAEvC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,SAAS,CAAW;gBAEhB,OAAO,GAAE,uBAA4B;IAIjD;;;;OAIG;IACH,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO;IAOnE,OAAO,CAAC,WAAW;IAInB;;;;;OAKG;IACH,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO;IAM7D;;;;OAIG;IACH,sBAAsB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO;IAgBxE;;;;OAIG;IACH,wBAAwB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,WAAW,GAAG,IAAI;CA+BpF"}
@@ -48,26 +48,6 @@ export class ComponentMatcher {
48
48
  }
49
49
  return true;
50
50
  }
51
- /**
52
- * Find all same elements that share the same data-source-file value.
53
- * These are elements rendered by the same component at the same source location.
54
- * @param element - The reference element
55
- * @returns Array of same elements (excludes the element itself)
56
- */
57
- findSameElements(element) {
58
- const sourceFile = element.getAttribute("data-source-file");
59
- if (!sourceFile) {
60
- return [];
61
- }
62
- const all = document.querySelectorAll(`[data-source-file="${CSS.escape(sourceFile)}"]`);
63
- const sameElements = [];
64
- for (const el of all) {
65
- if (el !== element) {
66
- sameElements.push(el);
67
- }
68
- }
69
- return sameElements;
70
- }
71
51
  /**
72
52
  * Find the nearest highlightable element by walking up the DOM tree
73
53
  * @param target - The target element
@@ -3,13 +3,8 @@
3
3
  * All rights reserved.
4
4
  * For full license text, see the LICENSE.txt file
5
5
  */
6
- /**
7
- * Event Handlers Module
8
- * Handles mouse and click events for highlighting
9
- */
10
6
  interface ComponentMatcherLike {
11
7
  findHighlightableElement: (target: HTMLElement) => HTMLElement | null;
12
- findSameElements: (element: HTMLElement) => HTMLElement[];
13
8
  }
14
9
  interface StyleManagerLike {
15
10
  highlightElements: (elements: HTMLElement[]) => void;
@@ -30,45 +25,16 @@ export declare class EventHandlers {
30
25
  private styleManager;
31
26
  private editableManager;
32
27
  private communicationManager;
33
- private currentHighlighted;
34
- private currentHighlightedSameElements;
28
+ private currentHighlightedElements;
35
29
  private selectedElement;
36
- private selectedSameElements;
30
+ private selectedElements;
37
31
  constructor(isInteractionsActive: () => boolean, componentMatcher: ComponentMatcherLike, styleManager: StyleManagerLike, editableManager: EditableManagerLike, communicationManager: CommunicationManagerLike);
38
- /**
39
- * Find the nearest highlightable element by walking up the DOM tree
40
- * @param target - The target element
41
- * @returns The highlightable element or null
42
- */
43
32
  private _findHighlightableElement;
44
- /**
45
- * Handle mouseover event
46
- * @param e - The mouseover event
47
- */
48
33
  handleMouseOver(e: MouseEvent): void;
49
- /**
50
- * Handle mouseleave event
51
- */
52
34
  handleMouseLeave(): void;
53
- /**
54
- * Handle click event
55
- * @param e - The click event
56
- */
57
35
  handleClick(e: MouseEvent): void;
58
- /**
59
- * Clear all highlights and selections
60
- */
61
36
  clearAll(): void;
62
- /**
63
- * Get the currently selected element
64
- * @returns The selected element
65
- */
66
37
  getSelectedElement(): HTMLElement | null;
67
- /**
68
- * Get the same elements of the currently selected element
69
- * @returns Array of same elements
70
- */
71
- getSelectedSameElements(): HTMLElement[];
72
38
  }
73
39
  export {};
74
40
  //# sourceMappingURL=eventHandlers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"eventHandlers.d.ts","sourceRoot":"","sources":["../../../src/design/interactions/eventHandlers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AAEH,UAAU,oBAAoB;IAC7B,wBAAwB,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,WAAW,GAAG,IAAI,CAAC;IACtE,gBAAgB,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,WAAW,EAAE,CAAC;CAC1D;AAED,UAAU,gBAAgB;IACzB,iBAAiB,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;IACrD,mBAAmB,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;IACvD,cAAc,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;IAClD,gBAAgB,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;CACpD;AAED,UAAU,mBAAmB;IAC5B,kBAAkB,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACnD,cAAc,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CAC/C;AAED,UAAU,wBAAwB;IACjC,uBAAuB,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CACxD;AAED,qBAAa,aAAa;IACzB,OAAO,CAAC,oBAAoB,CAAgB;IAC5C,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,eAAe,CAAsB;IAC7C,OAAO,CAAC,oBAAoB,CAA2B;IAEvD,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,8BAA8B,CAAgB;IACtD,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,oBAAoB,CAAgB;gBAG3C,oBAAoB,EAAE,MAAM,OAAO,EACnC,gBAAgB,EAAE,oBAAoB,EACtC,YAAY,EAAE,gBAAgB,EAC9B,eAAe,EAAE,mBAAmB,EACpC,oBAAoB,EAAE,wBAAwB;IAmB/C;;;;OAIG;IACH,OAAO,CAAC,yBAAyB;IAIjC;;;OAGG;IACH,eAAe,CAAC,CAAC,EAAE,UAAU,GAAG,IAAI;IAyCpC;;OAEG;IACH,gBAAgB,IAAI,IAAI;IAkBxB;;;OAGG;IACH,WAAW,CAAC,CAAC,EAAE,UAAU,GAAG,IAAI;IAmDhC;;OAEG;IACH,QAAQ,IAAI,IAAI;IAkBhB;;;OAGG;IACH,kBAAkB,IAAI,WAAW,GAAG,IAAI;IAIxC;;;OAGG;IACH,uBAAuB,IAAI,WAAW,EAAE;CAGxC"}
1
+ {"version":3,"file":"eventHandlers.d.ts","sourceRoot":"","sources":["../../../src/design/interactions/eventHandlers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,UAAU,oBAAoB;IAC7B,wBAAwB,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,WAAW,GAAG,IAAI,CAAC;CACtE;AAED,UAAU,gBAAgB;IACzB,iBAAiB,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;IACrD,mBAAmB,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;IACvD,cAAc,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;IAClD,gBAAgB,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;CACpD;AAED,UAAU,mBAAmB;IAC5B,kBAAkB,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACnD,cAAc,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CAC/C;AAED,UAAU,wBAAwB;IACjC,uBAAuB,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CACxD;AAED,qBAAa,aAAa;IACzB,OAAO,CAAC,oBAAoB,CAAgB;IAC5C,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,eAAe,CAAsB;IAC7C,OAAO,CAAC,oBAAoB,CAA2B;IAEvD,OAAO,CAAC,0BAA0B,CAAgB;IAClD,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,gBAAgB,CAAgB;gBAGvC,oBAAoB,EAAE,MAAM,OAAO,EACnC,gBAAgB,EAAE,oBAAoB,EACtC,YAAY,EAAE,gBAAgB,EAC9B,eAAe,EAAE,mBAAmB,EACpC,oBAAoB,EAAE,wBAAwB;IAkB/C,OAAO,CAAC,yBAAyB;IAIjC,eAAe,CAAC,CAAC,EAAE,UAAU,GAAG,IAAI;IAqCpC,gBAAgB,IAAI,IAAI;IAcxB,WAAW,CAAC,CAAC,EAAE,UAAU,GAAG,IAAI;IA+ChC,QAAQ,IAAI,IAAI;IAchB,kBAAkB,IAAI,WAAW,GAAG,IAAI;CAGxC"}
@@ -3,43 +3,37 @@
3
3
  * All rights reserved.
4
4
  * For full license text, see the LICENSE.txt file
5
5
  */
6
+ /**
7
+ * Event Handlers Module
8
+ * Handles mouse and click events for highlighting
9
+ */
10
+ import { findElementsBySourceLocation, getSourceFromDataAttributes } from "./utils/sourceUtils.js";
6
11
  export class EventHandlers {
7
12
  isInteractionsActive;
8
13
  componentMatcher;
9
14
  styleManager;
10
15
  editableManager;
11
16
  communicationManager;
12
- currentHighlighted;
13
- currentHighlightedSameElements;
17
+ currentHighlightedElements;
14
18
  selectedElement;
15
- selectedSameElements;
19
+ selectedElements;
16
20
  constructor(isInteractionsActive, componentMatcher, styleManager, editableManager, communicationManager) {
17
21
  this.isInteractionsActive = isInteractionsActive;
18
22
  this.componentMatcher = componentMatcher;
19
23
  this.styleManager = styleManager;
20
24
  this.editableManager = editableManager;
21
25
  this.communicationManager = communicationManager;
22
- this.currentHighlighted = null;
23
- this.currentHighlightedSameElements = [];
26
+ this.currentHighlightedElements = [];
24
27
  this.selectedElement = null;
25
- this.selectedSameElements = [];
28
+ this.selectedElements = [];
26
29
  // Bind methods to preserve 'this' context
27
30
  this.handleMouseOver = this.handleMouseOver.bind(this);
28
31
  this.handleMouseLeave = this.handleMouseLeave.bind(this);
29
32
  this.handleClick = this.handleClick.bind(this);
30
33
  }
31
- /**
32
- * Find the nearest highlightable element by walking up the DOM tree
33
- * @param target - The target element
34
- * @returns The highlightable element or null
35
- */
36
34
  _findHighlightableElement(target) {
37
35
  return this.componentMatcher.findHighlightableElement(target);
38
36
  }
39
- /**
40
- * Handle mouseover event
41
- * @param e - The mouseover event
42
- */
43
37
  handleMouseOver(e) {
44
38
  if (!this.isInteractionsActive()) {
45
39
  return;
@@ -53,44 +47,29 @@ export class EventHandlers {
53
47
  if (!element) {
54
48
  return;
55
49
  }
56
- if (this.currentHighlighted === element ||
50
+ if (this.currentHighlightedElements.includes(element) ||
57
51
  (this.selectedElement && this.selectedElement === element)) {
58
52
  return;
59
53
  }
60
- if (this.currentHighlighted &&
61
- !this.currentHighlighted.classList.contains("design-mode-selected")) {
62
- this.styleManager.unhighlightElements([
63
- this.currentHighlighted,
64
- ...this.currentHighlightedSameElements,
65
- ]);
66
- this.currentHighlightedSameElements = [];
54
+ if (this.currentHighlightedElements.length > 0 &&
55
+ !this.currentHighlightedElements[0].classList.contains("design-mode-selected")) {
56
+ this.styleManager.unhighlightElements(this.currentHighlightedElements);
57
+ this.currentHighlightedElements = [];
67
58
  }
68
- const sameElements = this.componentMatcher.findSameElements(element);
69
- this.styleManager.highlightElements([element, ...sameElements]);
70
- this.currentHighlighted = element;
71
- this.currentHighlightedSameElements = sameElements;
59
+ const allElements = findElementsBySourceLocation(getSourceFromDataAttributes(element));
60
+ this.styleManager.highlightElements(allElements);
61
+ this.currentHighlightedElements = allElements;
72
62
  }
73
- /**
74
- * Handle mouseleave event
75
- */
76
63
  handleMouseLeave() {
77
64
  if (!this.isInteractionsActive()) {
78
65
  return;
79
66
  }
80
- if (this.currentHighlighted &&
81
- !this.currentHighlighted.classList.contains("design-mode-selected")) {
82
- this.styleManager.unhighlightElements([
83
- this.currentHighlighted,
84
- ...this.currentHighlightedSameElements,
85
- ]);
86
- this.currentHighlighted = null;
87
- this.currentHighlightedSameElements = [];
67
+ if (this.currentHighlightedElements.length > 0 &&
68
+ !this.currentHighlightedElements[0].classList.contains("design-mode-selected")) {
69
+ this.styleManager.unhighlightElements(this.currentHighlightedElements);
70
+ this.currentHighlightedElements = [];
88
71
  }
89
72
  }
90
- /**
91
- * Handle click event
92
- * @param e - The click event
93
- */
94
73
  handleClick(e) {
95
74
  if (!this.isInteractionsActive()) {
96
75
  return;
@@ -110,59 +89,37 @@ export class EventHandlers {
110
89
  }
111
90
  // Deselect previous element and its same elements
112
91
  if (this.selectedElement) {
113
- this.styleManager.deselectElements([this.selectedElement, ...this.selectedSameElements]);
92
+ this.styleManager.deselectElements(this.selectedElements);
114
93
  this.editableManager.removeEditable(this.selectedElement);
115
94
  }
116
- // Remove highlight from current highlighted and its same elements
117
- if (this.currentHighlighted) {
118
- this.styleManager.unhighlightElements([
119
- this.currentHighlighted,
120
- ...this.currentHighlightedSameElements,
121
- ]);
122
- this.currentHighlighted = null;
123
- this.currentHighlightedSameElements = [];
95
+ // Remove highlight from current highlighted elements
96
+ if (this.currentHighlightedElements.length > 0) {
97
+ this.styleManager.unhighlightElements(this.currentHighlightedElements);
98
+ this.currentHighlightedElements = [];
124
99
  }
125
- // Select new element and its same elements
100
+ // Select new element and all elements at the same source location
126
101
  this.selectedElement = element;
127
- const sameElements = this.componentMatcher.findSameElements(element);
128
- this.selectedSameElements = sameElements;
129
- this.styleManager.selectElements([element, ...sameElements]);
102
+ const allElements = findElementsBySourceLocation(getSourceFromDataAttributes(element));
103
+ this.selectedElements = allElements;
104
+ this.styleManager.selectElements(allElements);
130
105
  // Make text elements editable
131
106
  this.editableManager.makeEditableIfText(element);
132
107
  // Notify extension
133
108
  this.communicationManager.notifyComponentSelected(element);
134
109
  }
135
- /**
136
- * Clear all highlights and selections
137
- */
138
110
  clearAll() {
139
- if (this.currentHighlighted) {
140
- this.styleManager.unhighlightElements([
141
- this.currentHighlighted,
142
- ...this.currentHighlightedSameElements,
143
- ]);
144
- this.currentHighlighted = null;
145
- this.currentHighlightedSameElements = [];
111
+ if (this.currentHighlightedElements.length > 0) {
112
+ this.styleManager.unhighlightElements(this.currentHighlightedElements);
113
+ this.currentHighlightedElements = [];
146
114
  }
147
115
  if (this.selectedElement) {
148
- this.styleManager.deselectElements([this.selectedElement, ...this.selectedSameElements]);
116
+ this.styleManager.deselectElements(this.selectedElements);
149
117
  this.editableManager.removeEditable(this.selectedElement);
150
118
  this.selectedElement = null;
151
- this.selectedSameElements = [];
119
+ this.selectedElements = [];
152
120
  }
153
121
  }
154
- /**
155
- * Get the currently selected element
156
- * @returns The selected element
157
- */
158
122
  getSelectedElement() {
159
123
  return this.selectedElement;
160
124
  }
161
- /**
162
- * Get the same elements of the currently selected element
163
- * @returns Array of same elements
164
- */
165
- getSelectedSameElements() {
166
- return this.selectedSameElements;
167
- }
168
125
  }
@@ -33,7 +33,19 @@ if (typeof window !== "undefined") {
33
33
  ? data
34
34
  : null;
35
35
  if (typed && typed.type === "style-change") {
36
- interactions.applyStyleChange(String(typed.property ?? ""), String(typed.value ?? ""));
36
+ const sl = typed.sourceLocation;
37
+ const sourceLocation = sl && typeof sl === "object"
38
+ ? {
39
+ fileName: String(sl.sourceFile ?? ""),
40
+ lineNumber: typeof sl.lineNumber === "number"
41
+ ? sl.lineNumber
42
+ : null,
43
+ columnNumber: typeof sl.columnNumber === "number"
44
+ ? sl.columnNumber
45
+ : null,
46
+ }
47
+ : undefined;
48
+ interactions.applyStyleChange(String(typed.property ?? ""), String(typed.value ?? ""), sourceLocation);
37
49
  }
38
50
  if (typed && typed.type === "enable-interactions") {
39
51
  window.enableInteractions?.();
@@ -3,6 +3,7 @@
3
3
  * All rights reserved.
4
4
  * For full license text, see the LICENSE.txt file
5
5
  */
6
+ import { type SourceLocation } from "./utils/sourceUtils.js";
6
7
  export declare class InteractionsController {
7
8
  private enabled;
8
9
  private isActive;
@@ -25,11 +26,11 @@ export declare class InteractionsController {
25
26
  */
26
27
  disable(): void;
27
28
  /**
28
- * Apply style changes to the selected element
29
- * @param property - CSS property name
30
- * @param value - CSS property value
29
+ * Apply a style change to all elements at the given source location.
30
+ * When sourceLocation is provided (undo/redo), it is used directly.
31
+ * Otherwise the source location is read from the currently selected element.
31
32
  */
32
- applyStyleChange(property: string, value: string): void;
33
+ applyStyleChange(property: string, value: string, sourceLocation?: SourceLocation): void;
33
34
  /**
34
35
  * Cleanup and remove event listeners
35
36
  */
@@ -1 +1 @@
1
- {"version":3,"file":"interactionsController.d.ts","sourceRoot":"","sources":["../../../src/design/interactions/interactionsController.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH,qBAAa,sBAAsB;IAClC,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,QAAQ,CAAU;IAE1B,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,aAAa,CAAgB;gBAEzB,OAAO,UAAO;IAwD1B;;OAEG;IACH,UAAU,IAAI,IAAI;IAiBlB;;OAEG;IACH,MAAM,IAAI,IAAI;IAKd;;OAEG;IACH,OAAO,IAAI,IAAI;IAMf;;;;OAIG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAWvD;;OAEG;IACH,OAAO,IAAI,IAAI;CAOf"}
1
+ {"version":3,"file":"interactionsController.d.ts","sourceRoot":"","sources":["../../../src/design/interactions/interactionsController.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAYH,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,wBAAwB,CAAC;AAEhC,qBAAa,sBAAsB;IAClC,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,QAAQ,CAAU;IAE1B,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,aAAa,CAAgB;gBAEzB,OAAO,UAAO;IAwD1B;;OAEG;IACH,UAAU,IAAI,IAAI;IAiBlB;;OAEG;IACH,MAAM,IAAI,IAAI;IAKd;;OAEG;IACH,OAAO,IAAI,IAAI;IAMf;;;;OAIG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,cAAc,GAAG,IAAI;IAgBxF;;OAEG;IACH,OAAO,IAAI,IAAI;CAOf"}
@@ -12,6 +12,7 @@ import { ComponentMatcher } from "./componentMatcher.js";
12
12
  import { EditableManager } from "./editableManager.js";
13
13
  import { EventHandlers } from "./eventHandlers.js";
14
14
  import { StyleManager } from "./styleManager.js";
15
+ import { findElementsBySourceLocation, getSourceFromDataAttributes, } from "./utils/sourceUtils.js";
15
16
  export class InteractionsController {
16
17
  enabled;
17
18
  isActive;
@@ -99,17 +100,21 @@ export class InteractionsController {
99
100
  console.log("Design Mode Interactions disabled");
100
101
  }
101
102
  /**
102
- * Apply style changes to the selected element
103
- * @param property - CSS property name
104
- * @param value - CSS property value
103
+ * Apply a style change to all elements at the given source location.
104
+ * When sourceLocation is provided (undo/redo), it is used directly.
105
+ * Otherwise the source location is read from the currently selected element.
105
106
  */
106
- applyStyleChange(property, value) {
107
- const selectedElement = this.eventHandlers.getSelectedElement();
108
- if (selectedElement) {
109
- selectedElement.style[property] = value;
110
- for (const sameElement of this.eventHandlers.getSelectedSameElements()) {
111
- sameElement.style[property] = value;
112
- }
107
+ applyStyleChange(property, value, sourceLocation) {
108
+ let location = sourceLocation ?? null;
109
+ if (!location?.fileName) {
110
+ const selectedElement = this.eventHandlers.getSelectedElement();
111
+ location = getSourceFromDataAttributes(selectedElement);
112
+ }
113
+ if (!location)
114
+ return;
115
+ const targets = findElementsBySourceLocation(location);
116
+ for (const el of targets) {
117
+ el.style[property] = value;
113
118
  }
114
119
  }
115
120
  /**
@@ -14,6 +14,12 @@ export interface SourceLocation {
14
14
  * @returns Source location information, or null if missing.
15
15
  */
16
16
  export declare function getSourceFromDataAttributes(element: HTMLElement | null | undefined): SourceLocation | null;
17
+ /**
18
+ * Find all DOM elements whose `data-source-file` attribute matches the given source location.
19
+ * @param location - The source location to match against
20
+ * @returns All matching elements (may be empty)
21
+ */
22
+ export declare function findElementsBySourceLocation(location: SourceLocation): HTMLElement[];
17
23
  /**
18
24
  * Derive a human-readable label from the injected source file name, if present.
19
25
  * @param element - The DOM element
@@ -1 +1 @@
1
- {"version":3,"file":"sourceUtils.d.ts","sourceRoot":"","sources":["../../../../src/design/interactions/utils/sourceUtils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkCH,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAC1C,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GACrC,cAAc,GAAG,IAAI,CAWvB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAiBlF"}
1
+ {"version":3,"file":"sourceUtils.d.ts","sourceRoot":"","sources":["../../../../src/design/interactions/utils/sourceUtils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkCH,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAC1C,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GACrC,cAAc,GAAG,IAAI,CAWvB;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,cAAc,GAAG,WAAW,EAAE,CAepF;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAiBlF"}
@@ -42,6 +42,25 @@ export function getSourceFromDataAttributes(element) {
42
42
  }
43
43
  return parseSourceFileAttribute(source);
44
44
  }
45
+ /**
46
+ * Find all DOM elements whose `data-source-file` attribute matches the given source location.
47
+ * @param location - The source location to match against
48
+ * @returns All matching elements (may be empty)
49
+ */
50
+ export function findElementsBySourceLocation(location) {
51
+ const results = [];
52
+ const elements = document.querySelectorAll("[data-source-file]");
53
+ for (const el of elements) {
54
+ const elSource = getSourceFromDataAttributes(el);
55
+ if (elSource &&
56
+ elSource.fileName === location.fileName &&
57
+ elSource.lineNumber === location.lineNumber &&
58
+ elSource.columnNumber === location.columnNumber) {
59
+ results.push(el);
60
+ }
61
+ }
62
+ return results;
63
+ }
45
64
  /**
46
65
  * Derive a human-readable label from the injected source file name, if present.
47
66
  * @param element - The DOM element
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-experimental",
3
3
  "description": "[experimental] Core package for Salesforce Web Applications",
4
- "version": "1.68.0",
4
+ "version": "1.69.0",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@salesforce/core": "^8.23.4",
48
- "@salesforce/sdk-data": "^1.68.0",
48
+ "@salesforce/sdk-data": "^1.69.0",
49
49
  "axios": "^1.7.7",
50
50
  "micromatch": "^4.0.8",
51
51
  "path-to-regexp": "^8.3.0"