refactor: extract shared patterns from popup/settings components

- Create useColoris composable (shared Coloris init across 4 files)
- Create useLinkedSpacing composable (linked margin/padding logic from TextSettings)
- Create BasePopup component (shared popup shell, CSS editor, inheritance button)
- Add watchProp helper in ElementPopup (12 watchers → 12 compact lines)
- Use extractSpacing for @page margin parsing in PagePopup and PageSettings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
isUnknown 2026-02-26 15:35:45 +01:00
parent 0c682c78c0
commit 69d5ebe7ed
7 changed files with 816 additions and 1187 deletions

View file

@ -0,0 +1,34 @@
import Coloris from '@melloware/coloris';
import '@melloware/coloris/dist/coloris.css';
const defaultConfig = {
el: '[data-coloris]',
theme: 'pill',
themeMode: 'dark',
formatToggle: true,
alpha: true,
closeButton: true,
closeLabel: 'Fermer',
clearButton: true,
clearLabel: 'Effacer',
swatchesOnly: false,
inline: false,
wrap: true,
swatches: [
'#264653',
'#2a9d8f',
'#e9c46a',
'#f4a261',
'#e76f51',
'#d62828',
'#023e8a',
'#0077b6',
'#ffffff',
'#000000',
],
};
export function initColoris(overrides = {}) {
Coloris.init();
Coloris({ ...defaultConfig, ...overrides });
}

View file

@ -0,0 +1,146 @@
import { ref, watch } from 'vue';
import { convertUnit } from '../utils/unit-conversion';
/**
* Composable for managing linked/unlinked spacing (margin/padding) with 4 sides.
* @param {Object} options
* @param {string} options.defaultUnit - Default unit for all sides (default: 'mm')
* @param {Object} options.initialValues - Initial values for sides { top, right, bottom, left } each { value, unit }
* @param {Function} options.onUpdate - Callback called with the detailed sides when values change
* @param {Function} options.isUpdatingFromStore - Function that returns whether we're syncing from store
* @param {Function} options.debouncedUpdate - Debounced update function
*/
export function useLinkedSpacing({ defaultUnit = 'mm', initialValues, onUpdate, isUpdatingFromStore, debouncedUpdate }) {
const sides = ref(initialValues || {
top: { value: 0, unit: defaultUnit },
right: { value: 0, unit: defaultUnit },
bottom: { value: 0, unit: defaultUnit },
left: { value: 0, unit: defaultUnit },
});
const linked = ref(false);
const prevValues = ref({
top: sides.value.top.value,
right: sides.value.right.value,
bottom: sides.value.bottom.value,
left: sides.value.left.value,
});
const updateUnit = (newUnit) => {
const sideNames = ['top', 'right', 'bottom', 'left'];
sideNames.forEach((side) => {
const s = sides.value[side];
s.value = convertUnit(s.value, s.unit, newUnit);
s.unit = newUnit;
});
};
// When link is toggled on, sync all sides to first non-zero value
watch(linked, (isLinked) => {
if (!isLinked) return;
const current = sides.value;
const syncValue = current.top.value || current.bottom.value || current.left.value || current.right.value;
sides.value.top.value = syncValue;
sides.value.bottom.value = syncValue;
sides.value.left.value = syncValue;
sides.value.right.value = syncValue;
prevValues.value.top = syncValue;
prevValues.value.bottom = syncValue;
prevValues.value.left = syncValue;
prevValues.value.right = syncValue;
});
// Watch side values for linked sync + update callback
watch(() => [
sides.value.top.value,
sides.value.bottom.value,
sides.value.left.value,
sides.value.right.value,
], () => {
if (isUpdatingFromStore()) return;
if (linked.value) {
const current = {
top: sides.value.top.value,
bottom: sides.value.bottom.value,
left: sides.value.left.value,
right: sides.value.right.value,
};
let changedValue = null;
if (current.top !== prevValues.value.top) changedValue = current.top;
else if (current.bottom !== prevValues.value.bottom) changedValue = current.bottom;
else if (current.left !== prevValues.value.left) changedValue = current.left;
else if (current.right !== prevValues.value.right) changedValue = current.right;
if (changedValue !== null) {
sides.value.top.value = changedValue;
sides.value.bottom.value = changedValue;
sides.value.left.value = changedValue;
sides.value.right.value = changedValue;
prevValues.value.top = changedValue;
prevValues.value.bottom = changedValue;
prevValues.value.left = changedValue;
prevValues.value.right = changedValue;
}
} else {
prevValues.value.top = sides.value.top.value;
prevValues.value.bottom = sides.value.bottom.value;
prevValues.value.left = sides.value.left.value;
prevValues.value.right = sides.value.right.value;
}
if (onUpdate) {
debouncedUpdate(() => {
onUpdate(sides.value);
});
}
});
/**
* Set all sides from extracted spacing data (from syncFromStore).
* Also updates prevValues and optionally sets linked state.
*/
const setFromSpacing = (spacing) => {
if (spacing.simple) {
sides.value = {
top: { ...spacing.simple },
right: { ...spacing.simple },
bottom: { ...spacing.simple },
left: { ...spacing.simple },
};
linked.value = true;
} else if (spacing.detailed) {
sides.value = spacing.detailed;
const d = spacing.detailed;
const allSame =
d.top.value === d.right.value &&
d.top.value === d.bottom.value &&
d.top.value === d.left.value &&
d.top.unit === d.right.unit &&
d.top.unit === d.bottom.unit &&
d.top.unit === d.left.unit;
linked.value = allSame;
}
prevValues.value = {
top: sides.value.top.value,
right: sides.value.right.value,
bottom: sides.value.bottom.value,
left: sides.value.left.value,
};
};
return {
sides,
linked,
updateUnit,
prevValues,
setFromSpacing,
};
}