-
+
+
@@ -246,8 +290,22 @@ const color = ref('rgb(0, 0, 0)');
const background = ref('transparent');
const fontSize = reactive({ value: 23, unit: 'px' });
const lineHeight = reactive({ value: 28, unit: 'px' });
-const marginOuter = reactive({ value: 0, unit: 'mm' });
-const paddingInner = reactive({ value: 0, unit: 'mm' });
+
+const marginLocked = ref(true);
+const margin = reactive({
+ top: { value: 0, unit: 'mm' },
+ right: { value: 0, unit: 'mm' },
+ bottom: { value: 0, unit: 'mm' },
+ left: { value: 0, unit: 'mm' },
+});
+
+const paddingLocked = ref(true);
+const padding = reactive({
+ top: { value: 0, unit: 'mm' },
+ right: { value: 0, unit: 'mm' },
+ bottom: { value: 0, unit: 'mm' },
+ left: { value: 0, unit: 'mm' },
+});
// Constants
const fonts = ['Alegreya Sans', 'Alegreya', 'Arial', 'Georgia', 'Times New Roman'];
@@ -271,10 +329,55 @@ const styleProps = [
const unitProps = [
{ css: 'font-size', ref: fontSize, debounce: true },
{ css: 'line-height', ref: lineHeight, debounce: true },
- { css: 'margin', ref: marginOuter, debounce: true },
- { css: 'padding', ref: paddingInner, debounce: true },
];
+const sides = [
+ { key: 'top', label: 'Haut' },
+ { key: 'bottom', label: 'Bas' },
+ { key: 'left', label: 'Gauche' },
+ { key: 'right', label: 'Droite' },
+];
+
+const updateMarginValue = (side, value) => {
+ if (marginLocked.value) {
+ for (const s of ['top', 'right', 'bottom', 'left']) margin[s].value = value;
+ } else {
+ margin[side].value = value;
+ }
+};
+
+const updateMarginUnit = (side, unit) => {
+ if (marginLocked.value) {
+ for (const s of ['top', 'right', 'bottom', 'left']) {
+ margin[s].value = convertUnit(margin[s].value, margin[s].unit, unit);
+ margin[s].unit = unit;
+ }
+ } else {
+ margin[side].value = convertUnit(margin[side].value, margin[side].unit, unit);
+ margin[side].unit = unit;
+ }
+};
+
+const updatePaddingValue = (side, value) => {
+ if (paddingLocked.value) {
+ for (const s of ['top', 'right', 'bottom', 'left']) padding[s].value = value;
+ } else {
+ padding[side].value = value;
+ }
+};
+
+const updatePaddingUnit = (side, unit) => {
+ if (paddingLocked.value) {
+ for (const s of ['top', 'right', 'bottom', 'left']) {
+ padding[s].value = convertUnit(padding[s].value, padding[s].unit, unit);
+ padding[s].unit = unit;
+ }
+ } else {
+ padding[side].value = convertUnit(padding[side].value, padding[side].unit, unit);
+ padding[side].unit = unit;
+ }
+};
+
// Generic update: push a single property to the stylesheet store
const updateProp = (cssProp, value, unit) => {
if (!selector.value) return;
@@ -336,6 +439,17 @@ const generatePreviewCss = () => {
}
}
+ for (const side of ['top', 'right', 'bottom', 'left']) {
+ if (margin[side].value !== undefined && margin[side].value !== null) {
+ properties.push(` margin-${side}: ${margin[side].value}${margin[side].unit};`);
+ }
+ }
+ for (const side of ['top', 'right', 'bottom', 'left']) {
+ if (padding[side].value !== undefined && padding[side].value !== null) {
+ properties.push(` padding-${side}: ${padding[side].value}${padding[side].unit};`);
+ }
+ }
+
if (properties.length === 0) return '';
return `${selector.value} {\n${properties.join('\n')}\n}`;
@@ -364,6 +478,10 @@ const applyAllStyles = () => {
for (const prop of unitProps) {
updateProp(prop.css, prop.ref.value, prop.ref.unit);
}
+ for (const side of ['top', 'right', 'bottom', 'left']) {
+ updateProp(`margin-${side}`, margin[side].value, margin[side].unit);
+ updateProp(`padding-${side}`, padding[side].value, padding[side].unit);
+ }
};
// Watchers — simple props
@@ -388,6 +506,50 @@ for (const prop of unitProps) {
});
}
+// Watchers — margin sides
+watch(
+ () => [margin.top.value, margin.right.value, margin.bottom.value, margin.left.value],
+ () => {
+ if (isUpdatingFromStore) return;
+ debouncedUpdate(() => {
+ for (const side of ['top', 'right', 'bottom', 'left']) {
+ updateProp(`margin-${side}`, margin[side].value, margin[side].unit);
+ }
+ });
+ }
+);
+watch(
+ () => [margin.top.unit, margin.right.unit, margin.bottom.unit, margin.left.unit],
+ () => {
+ if (isUpdatingFromStore) return;
+ for (const side of ['top', 'right', 'bottom', 'left']) {
+ updateProp(`margin-${side}`, margin[side].value, margin[side].unit);
+ }
+ }
+);
+
+// Watchers — padding sides
+watch(
+ () => [padding.top.value, padding.right.value, padding.bottom.value, padding.left.value],
+ () => {
+ if (isUpdatingFromStore) return;
+ debouncedUpdate(() => {
+ for (const side of ['top', 'right', 'bottom', 'left']) {
+ updateProp(`padding-${side}`, padding[side].value, padding[side].unit);
+ }
+ });
+ }
+);
+watch(
+ () => [padding.top.unit, padding.right.unit, padding.bottom.unit, padding.left.unit],
+ () => {
+ if (isUpdatingFromStore) return;
+ for (const side of ['top', 'right', 'bottom', 'left']) {
+ updateProp(`padding-${side}`, padding[side].value, padding[side].unit);
+ }
+ }
+);
+
const handleCssInput = (newCss) => {
const oldBlock = elementCss.value;
if (oldBlock) {
@@ -440,6 +602,47 @@ const loadValuesFromStylesheet = () => {
prop.ref.unit = data.unit;
}
}
+
+ // Margin sides — try individual first, fallback to shorthand
+ const spacingSides = ['top', 'right', 'bottom', 'left'];
+ let anyMarginFound = false;
+ for (const side of spacingSides) {
+ const data = stylesheetStore.extractValue(selector.value, `margin-${side}`);
+ if (data && data.value !== undefined) {
+ margin[side].value = data.value;
+ margin[side].unit = data.unit;
+ anyMarginFound = true;
+ }
+ }
+ if (!anyMarginFound) {
+ const data = stylesheetStore.extractValue(selector.value, 'margin');
+ if (data && data.value !== undefined) {
+ for (const side of spacingSides) {
+ margin[side].value = data.value;
+ margin[side].unit = data.unit;
+ }
+ }
+ }
+
+ // Padding sides — try individual first, fallback to shorthand
+ let anyPaddingFound = false;
+ for (const side of spacingSides) {
+ const data = stylesheetStore.extractValue(selector.value, `padding-${side}`);
+ if (data && data.value !== undefined) {
+ padding[side].value = data.value;
+ padding[side].unit = data.unit;
+ anyPaddingFound = true;
+ }
+ }
+ if (!anyPaddingFound) {
+ const data = stylesheetStore.extractValue(selector.value, 'padding');
+ if (data && data.value !== undefined) {
+ for (const side of spacingSides) {
+ padding[side].value = data.value;
+ padding[side].unit = data.unit;
+ }
+ }
+ }
} catch (error) {
console.error('Error loading values from stylesheet:', error);
}
@@ -517,16 +720,18 @@ const toggleInheritance = () => {
color.value = cs.color;
background.value = cs.backgroundColor;
- const marginMatch = cs.marginTop.match(/([\d.]+)(px|mm|pt)/);
- if (marginMatch) {
- marginOuter.value = parseFloat(marginMatch[1]);
- marginOuter.unit = marginMatch[2];
- }
-
- const paddingMatch = cs.paddingTop.match(/([\d.]+)(px|mm|pt)/);
- if (paddingMatch) {
- paddingInner.value = parseFloat(paddingMatch[1]);
- paddingInner.unit = paddingMatch[2];
+ for (const side of ['top', 'right', 'bottom', 'left']) {
+ const cssSide = side.charAt(0).toUpperCase() + side.slice(1);
+ const marginMatch = cs[`margin${cssSide}`].match(/([\d.]+)(px|mm|pt)/);
+ if (marginMatch) {
+ margin[side].value = parseFloat(marginMatch[1]);
+ margin[side].unit = marginMatch[2];
+ }
+ const paddingMatch = cs[`padding${cssSide}`].match(/([\d.]+)(px|mm|pt)/);
+ if (paddingMatch) {
+ padding[side].value = parseFloat(paddingMatch[1]);
+ padding[side].unit = paddingMatch[2];
+ }
}
isUpdatingFromStore = false;
@@ -558,4 +763,38 @@ defineExpose({ handleIframeClick, close, visible });
color: var(--color-purple);
font-size: 0.875rem;
}
+
+.settings-subsection-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 0.25rem;
+}
+
+.lock-toggle {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: none;
+ border: 1px solid transparent;
+ border-radius: 3px;
+ cursor: pointer;
+ padding: 2px 4px;
+ color: var(--color-text-muted, #999);
+ transition: color 0.15s, border-color 0.15s;
+}
+
+.lock-toggle:hover:not(:disabled) {
+ color: var(--color-text, #333);
+ border-color: var(--color-border, #ccc);
+}
+
+.lock-toggle.locked {
+ color: var(--color-purple, #7c3aed);
+}
+
+.lock-toggle:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+}