refactor: extract debounce logic into shared composable

- Create useDebounce composable to avoid code duplication
- Apply debounce to TextSettings margin/padding inputs
- Harmonize debounce delay to 500ms across all components
- Fix input lag when typing values like "30mm"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
isUnknown 2025-12-10 11:51:53 +01:00
parent 35c9ab1d3b
commit 681517db21
5 changed files with 40 additions and 29 deletions

View file

@ -274,6 +274,7 @@
import { ref, computed, watch } from 'vue'; import { ref, computed, watch } from 'vue';
import { useStylesheetStore } from '../stores/stylesheet'; import { useStylesheetStore } from '../stores/stylesheet';
import { usePopupPosition } from '../composables/usePopupPosition'; import { usePopupPosition } from '../composables/usePopupPosition';
import { useDebounce } from '../composables/useDebounce';
import NumberInput from './ui/NumberInput.vue'; import NumberInput from './ui/NumberInput.vue';
import Coloris from '@melloware/coloris'; import Coloris from '@melloware/coloris';
import '@melloware/coloris/dist/coloris.css'; import '@melloware/coloris/dist/coloris.css';
@ -307,7 +308,7 @@ const colorInput = ref(null);
const backgroundInput = ref(null); const backgroundInput = ref(null);
let isUpdatingFromStore = false; let isUpdatingFromStore = false;
let updateTimer = null; const { debouncedUpdate } = useDebounce(500);
// Style properties // Style properties
const fontFamily = ref({ value: 'Alegreya Sans' }); const fontFamily = ref({ value: 'Alegreya Sans' });
@ -320,11 +321,6 @@ const background = ref({ value: 'transparent' });
const marginOuter = ref({ value: 0, unit: 'mm' }); const marginOuter = ref({ value: 0, unit: 'mm' });
const paddingInner = ref({ value: 0, unit: 'mm' }); const paddingInner = ref({ value: 0, unit: 'mm' });
const debouncedUpdate = (callback) => {
clearTimeout(updateTimer);
updateTimer = setTimeout(callback, 1000);
};
const immediateUpdate = (callback) => { const immediateUpdate = (callback) => {
callback(); callback();
}; };

View file

@ -285,6 +285,7 @@
import { ref, computed, watch, onMounted } from 'vue'; import { ref, computed, watch, onMounted } from 'vue';
import { useStylesheetStore } from '../stores/stylesheet'; import { useStylesheetStore } from '../stores/stylesheet';
import { usePopupPosition } from '../composables/usePopupPosition'; import { usePopupPosition } from '../composables/usePopupPosition';
import { useDebounce } from '../composables/useDebounce';
import NumberInput from './ui/NumberInput.vue'; import NumberInput from './ui/NumberInput.vue';
import Coloris from '@melloware/coloris'; import Coloris from '@melloware/coloris';
import '@melloware/coloris/dist/coloris.css'; import '@melloware/coloris/dist/coloris.css';
@ -312,7 +313,7 @@ const inheritanceLocked = ref(true);
const backgroundColorInput = ref(null); const backgroundColorInput = ref(null);
let isUpdatingFromStore = false; let isUpdatingFromStore = false;
let updateTimer = null; const { debouncedUpdate } = useDebounce(500);
const margins = ref({ const margins = ref({
top: { value: 0, unit: 'mm' }, top: { value: 0, unit: 'mm' },
@ -328,11 +329,6 @@ const background = ref({
const pattern = ref(''); const pattern = ref('');
const debouncedUpdate = (callback) => {
clearTimeout(updateTimer);
updateTimer = setTimeout(callback, 1000);
};
const immediateUpdate = (callback) => { const immediateUpdate = (callback) => {
callback(); callback();
}; };

View file

@ -288,16 +288,17 @@
<script setup> <script setup>
import { ref, computed, watch, onMounted, inject } from 'vue'; import { ref, computed, watch, onMounted, inject } from 'vue';
import { useStylesheetStore } from '../../stores/stylesheet'; import { useStylesheetStore } from '../../stores/stylesheet';
import { useDebounce } from '../../composables/useDebounce';
import Coloris from '@melloware/coloris'; import Coloris from '@melloware/coloris';
import NumberInput from '../ui/NumberInput.vue'; import NumberInput from '../ui/NumberInput.vue';
import '@melloware/coloris/dist/coloris.css'; import '@melloware/coloris/dist/coloris.css';
const stylesheetStore = useStylesheetStore(); const stylesheetStore = useStylesheetStore();
const { debouncedUpdate } = useDebounce(500);
const backgroundColorInput = ref(null); const backgroundColorInput = ref(null);
const activeTab = inject('activeTab', ref('document')); const activeTab = inject('activeTab', ref('document'));
let isUpdatingFromStore = false; let isUpdatingFromStore = false;
let updateTimer = null;
const pageFormat = ref('A4'); const pageFormat = ref('A4');
@ -328,11 +329,6 @@ const pattern = ref('');
const pageNumbers = ref(false); const pageNumbers = ref(false);
const runningTitle = ref(false); const runningTitle = ref(false);
const debouncedUpdate = (callback) => {
clearTimeout(updateTimer);
updateTimer = setTimeout(callback, 1000);
};
const immediateUpdate = (callback) => { const immediateUpdate = (callback) => {
callback(); callback();
}; };

View file

@ -130,9 +130,11 @@ import InputWithUnit from '../ui/InputWithUnit.vue';
import MarginEditor from '../ui/MarginEditor.vue'; import MarginEditor from '../ui/MarginEditor.vue';
import { useCssUpdater } from '../../composables/useCssUpdater'; import { useCssUpdater } from '../../composables/useCssUpdater';
import { useCssSync } from '../../composables/useCssSync'; import { useCssSync } from '../../composables/useCssSync';
import { useDebounce } from '../../composables/useDebounce';
const { updateStyle, setMargin, setDetailedMargins, setPadding, setDetailedPadding } = useCssUpdater(); const { updateStyle, setMargin, setDetailedMargins, setPadding, setDetailedPadding } = useCssUpdater();
const { extractValue, extractNumericValue, extractSpacing } = useCssSync(); const { extractValue, extractNumericValue, extractSpacing } = useCssSync();
const { debouncedUpdate } = useDebounce(500);
// Constants // Constants
const fonts = ['Alegreya Sans', 'Arial', 'Georgia', 'Helvetica', 'Times New Roman']; const fonts = ['Alegreya Sans', 'Arial', 'Georgia', 'Helvetica', 'Times New Roman'];
@ -203,26 +205,32 @@ watch(weight, (val) => {
watch(fontSize, (val) => { watch(fontSize, (val) => {
if (isUpdatingFromStore) return; if (isUpdatingFromStore) return;
updateStyle('p', 'font-size', `${val.value}${val.unit}`); debouncedUpdate(() => {
updateStyle('p', 'font-size', `${val.value}${val.unit}`);
});
}, { deep: true }); }, { deep: true });
// Margin/Padding handlers // Margin/Padding handlers
const handleMarginOuterChange = ({ type, simple, detailed }) => { const handleMarginOuterChange = ({ type, simple, detailed }) => {
if (isUpdatingFromStore) return; if (isUpdatingFromStore) return;
if (type === 'simple') { debouncedUpdate(() => {
setMargin('p', simple.value, simple.unit); if (type === 'simple') {
} else { setMargin('p', simple.value, simple.unit);
setDetailedMargins('p', detailed.top, detailed.right, detailed.bottom, detailed.left); } else {
} setDetailedMargins('p', detailed.top, detailed.right, detailed.bottom, detailed.left);
}
});
}; };
const handleMarginInnerChange = ({ type, simple, detailed }) => { const handleMarginInnerChange = ({ type, simple, detailed }) => {
if (isUpdatingFromStore) return; if (isUpdatingFromStore) return;
if (type === 'simple') { debouncedUpdate(() => {
setPadding('p', simple.value, simple.unit); if (type === 'simple') {
} else { setPadding('p', simple.value, simple.unit);
setDetailedPadding('p', detailed.top, detailed.right, detailed.bottom, detailed.left); } else {
} setDetailedPadding('p', detailed.top, detailed.right, detailed.bottom, detailed.left);
}
});
}; };
// Sync from store // Sync from store

View file

@ -0,0 +1,15 @@
/**
* Composable for debounced updates
* @param {number} delay - Debounce delay in milliseconds (default: 500ms)
* @returns {Function} debouncedUpdate function
*/
export function useDebounce(delay = 500) {
let timer = null;
const debouncedUpdate = (callback) => {
clearTimeout(timer);
timer = setTimeout(callback, delay);
};
return { debouncedUpdate };
}