From 6a815dde28407d21ebe51251561eb21c823e180a Mon Sep 17 00:00:00 2001 From: MarsLuay <70299537+MarsLuay@users.noreply.github.com> Date: Sun, 6 Sep 2026 04:51:57 +0000 Subject: [PATCH] perf(powerpoint): optimize selection class rendering updates Replaces global `querySelectorAll('g[data-ooxml-shape-idx]')` calls inside `previewSelectionClasses` and `applySelectionClasses` with targeted DOM updates. The previous O(N) DOM scan across all shapes has been replaced by: 1. Fetching currently selected shapes using the fast `getElementsByClassName` live collection. 2. Building removal queues and sets of already-rendered shapes. 3. Adding CSS classes dynamically only via targeted `querySelector` calls on specific updated shape indices. This dramatically reduces JS overhead during drag operations (marquees) and rapid selection clicks. --- src/powerpoint/ui/NativePowerPointView.ts | 58 +++++++++++++++++++---- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/src/powerpoint/ui/NativePowerPointView.ts b/src/powerpoint/ui/NativePowerPointView.ts index 00fded3..4dc2f84 100644 --- a/src/powerpoint/ui/NativePowerPointView.ts +++ b/src/powerpoint/ui/NativePowerPointView.ts @@ -4824,14 +4824,33 @@ export class NativePowerPointView extends FileView { } private applySelectionClasses(): void { - this.svgEl?.querySelectorAll('g[data-ooxml-shape-idx]').forEach((shape) => { + if (!this.svgEl) return; + + const currentlySelected = this.svgEl.getElementsByClassName('native-powerpoint-shape-selected'); + const alreadySelected = new Set(); + const toRemove: Element[] = []; + + for (let i = 0; i < currentlySelected.length; i++) { + const shape = currentlySelected[i]; + if (!shape) continue; const index = getShapeIndex(shape); - if (index !== null && this.selectedShapeIndices.has(index)) { - shape.addClass('native-powerpoint-shape-selected'); + if (index === null || !this.selectedShapeIndices.has(index)) { + toRemove.push(shape); } else { - shape.removeClass('native-powerpoint-shape-selected'); + alreadySelected.add(index); } - }); + } + + for (let i = 0; i < toRemove.length; i++) { + toRemove[i]?.removeClass('native-powerpoint-shape-selected'); + } + + for (const index of this.selectedShapeIndices) { + if (!alreadySelected.has(index)) { + const shape = this.svgEl.querySelector(`g[data-ooxml-shape-idx="${index}"]`); + if (shape) shape.addClass('native-powerpoint-shape-selected'); + } + } } private clearSelection(options: { skipTextCommit?: boolean } = {}): void { @@ -12288,14 +12307,33 @@ export class NativePowerPointView extends FileView { } private previewSelectionClasses(indices: Set): void { - this.svgEl?.querySelectorAll('g[data-ooxml-shape-idx]').forEach((shape) => { + if (!this.svgEl) return; + + const currentlySelected = this.svgEl.getElementsByClassName('native-powerpoint-shape-selected'); + const alreadySelected = new Set(); + const toRemove: Element[] = []; + + for (let i = 0; i < currentlySelected.length; i++) { + const shape = currentlySelected[i]; + if (!shape) continue; const index = getShapeIndex(shape); - if (index !== null && indices.has(index)) { - shape.addClass('native-powerpoint-shape-selected'); + if (index === null || !indices.has(index)) { + toRemove.push(shape); } else { - shape.removeClass('native-powerpoint-shape-selected'); + alreadySelected.add(index); } - }); + } + + for (let i = 0; i < toRemove.length; i++) { + toRemove[i]?.removeClass('native-powerpoint-shape-selected'); + } + + for (const index of indices) { + if (!alreadySelected.has(index)) { + const shape = this.svgEl.querySelector(`g[data-ooxml-shape-idx="${index}"]`); + if (shape) shape.addClass('native-powerpoint-shape-selected'); + } + } } private beginMarquee(event: PointerEvent, additive: boolean): void {