fix(vue): batch color picker drag updates into one undo entry (#187)
PropertyListRoot's update() and patch() pushed a new undo entry on every call, so dragging a color slider or color area in the fill, stroke, or effect pickers filled the undo stack with dozens of intermediate entries per interaction. Cmd+Z had to be pressed many times to revert a single conceptual color change. Wrap update() and patch() in a debounced begin/commit batch keyed by (op, propKey, index, nodeIds). Rapid events collapse into one undo entry, and the batch commits after 300ms of idle. Changing prop key, index, or selection flushes the previous batch immediately, and add/remove/toggleVisibility flush any pending batch before running so button actions never mix with drag history. The existing explicit batch in toggleVisibility for multi-node is preserved.
This commit is contained in:
parent
a4f30325eb
commit
c829f69e1b
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
## Unreleased
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fix color picker dragging flooding the undo stack — fill/stroke/effect color and opacity drags now collapse into a single undo entry per interaction via debounced batching in `PropertyListRoot`
|
||||
|
||||
## 0.11.6 — 2026-04-08
|
||||
|
||||
### Fixes
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, onBeforeUnmount } from 'vue'
|
||||
|
||||
import { useEditor } from '@open-pencil/vue/context/editorContext'
|
||||
import { useNodeProps } from '@open-pencil/vue/controls/useNodeProps'
|
||||
|
|
@ -51,7 +51,35 @@ function targetNodes(): SceneNode[] {
|
|||
return activeNode.value ? [activeNode.value] : []
|
||||
}
|
||||
|
||||
const BATCH_IDLE_MS = 300
|
||||
let batchKey: string | null = null
|
||||
let batchTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
function flushBatch() {
|
||||
if (batchTimer !== null) {
|
||||
clearTimeout(batchTimer)
|
||||
batchTimer = null
|
||||
}
|
||||
if (batchKey !== null) {
|
||||
editor.undo.commitBatch()
|
||||
batchKey = null
|
||||
}
|
||||
}
|
||||
|
||||
function ensureBatch(key: string, label: string) {
|
||||
if (batchKey !== key) {
|
||||
flushBatch()
|
||||
editor.undo.beginBatch(label)
|
||||
batchKey = key
|
||||
}
|
||||
if (batchTimer !== null) clearTimeout(batchTimer)
|
||||
batchTimer = setTimeout(flushBatch, BATCH_IDLE_MS)
|
||||
}
|
||||
|
||||
onBeforeUnmount(flushBatch)
|
||||
|
||||
function add(defaults: ArrayItemType) {
|
||||
flushBatch()
|
||||
emit('add', defaults)
|
||||
for (const n of targetNodes()) {
|
||||
const arr = isMulti.value ? [defaults] : [...n[propKey], defaults]
|
||||
|
|
@ -64,6 +92,7 @@ function add(defaults: ArrayItemType) {
|
|||
}
|
||||
|
||||
function remove(index: number) {
|
||||
flushBatch()
|
||||
emit('remove', index)
|
||||
for (const n of targetNodes()) {
|
||||
editor.updateNodeWithUndo(
|
||||
|
|
@ -78,7 +107,11 @@ function remove(index: number) {
|
|||
|
||||
function update(index: number, item: ArrayItemType) {
|
||||
emit('update', index, item)
|
||||
for (const n of targetNodes()) {
|
||||
const nodes = targetNodes()
|
||||
if (nodes.length === 0) return
|
||||
const key = `update:${propKey}:${index}:${nodes.map((n) => n.id).join(',')}`
|
||||
ensureBatch(key, `Change ${propKey}`)
|
||||
for (const n of nodes) {
|
||||
const arr = [...n[propKey]] as ArrayItemType[]
|
||||
arr[index] = item
|
||||
editor.updateNodeWithUndo(n.id, { [propKey]: arr } as Partial<SceneNode>, `Change ${propKey}`)
|
||||
|
|
@ -87,7 +120,11 @@ function update(index: number, item: ArrayItemType) {
|
|||
|
||||
function patch(index: number, changes: Record<string, unknown>) {
|
||||
emit('patch', index, changes)
|
||||
for (const n of targetNodes()) {
|
||||
const nodes = targetNodes()
|
||||
if (nodes.length === 0) return
|
||||
const key = `patch:${propKey}:${index}:${nodes.map((n) => n.id).join(',')}`
|
||||
ensureBatch(key, `Change ${propKey}`)
|
||||
for (const n of nodes) {
|
||||
const arr = [...n[propKey]] as ArrayItemType[]
|
||||
arr[index] = { ...arr[index], ...changes } as ArrayItemType
|
||||
editor.updateNodeWithUndo(n.id, { [propKey]: arr } as Partial<SceneNode>, `Change ${propKey}`)
|
||||
|
|
@ -95,6 +132,7 @@ function patch(index: number, changes: Record<string, unknown>) {
|
|||
}
|
||||
|
||||
function toggleVisibility(index: number) {
|
||||
flushBatch()
|
||||
emit('toggleVisibility', index)
|
||||
const nodes = targetNodes()
|
||||
if (nodes.length === 0) return
|
||||
|
|
|
|||
Loading…
Reference in a new issue