margins + paddings in pop vue
This commit is contained in:
parent
154804ee44
commit
fc6391a53d
1 changed files with 279 additions and 40 deletions
|
|
@ -139,32 +139,54 @@
|
||||||
|
|
||||||
<!-- Outer Margins -->
|
<!-- Outer Margins -->
|
||||||
<div class="settings-subsection">
|
<div class="settings-subsection">
|
||||||
<div class="field" :class="{ 'field--view-only': inheritanceLocked }">
|
<div class="settings-subsection-header">
|
||||||
<label class="label-with-tooltip" data-css="margin">Marges extérieures</label>
|
<span class="label-with-tooltip" data-css="margin">Marges extérieures</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="lock-toggle"
|
||||||
|
:class="{ locked: marginLocked }"
|
||||||
|
:disabled="inheritanceLocked"
|
||||||
|
@click="marginLocked = !marginLocked"
|
||||||
|
:title="marginLocked ? 'Déverrouiller (modifier indépendamment)' : 'Verrouiller (modifier ensemble)'"
|
||||||
|
>
|
||||||
|
<svg v-if="marginLocked" width="11" height="13" viewBox="0 0 11 13" fill="none">
|
||||||
|
<rect x="1" y="5.5" width="9" height="7" rx="1" fill="currentColor"/>
|
||||||
|
<path d="M2.5 5.5V3.5a3 3 0 0 1 6 0v2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
<svg v-else width="11" height="13" viewBox="0 0 11 13" fill="none">
|
||||||
|
<rect x="1" y="5.5" width="9" height="7" rx="1" fill="currentColor"/>
|
||||||
|
<path d="M2.5 5.5V3.5a3 3 0 0 1 6 0" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="side in sides"
|
||||||
|
:key="side.key"
|
||||||
|
class="field field-margin"
|
||||||
|
:class="{ 'field--view-only': inheritanceLocked }"
|
||||||
|
>
|
||||||
|
<label class="label-with-tooltip" :data-css="`margin-${side.key}`">{{ side.label }}</label>
|
||||||
<div class="input-with-unit">
|
<div class="input-with-unit">
|
||||||
<NumberInput
|
<NumberInput
|
||||||
v-model="marginOuter.value"
|
:modelValue="margin[side.key].value"
|
||||||
:min="0"
|
:min="0"
|
||||||
:step="1"
|
:step="1"
|
||||||
:disabled="inheritanceLocked"
|
:disabled="inheritanceLocked"
|
||||||
|
@update:modelValue="(v) => updateMarginValue(side.key, v)"
|
||||||
/>
|
/>
|
||||||
<div class="unit-toggle">
|
<div class="unit-toggle">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
:class="{ active: marginOuter.unit === 'mm' }"
|
:class="{ active: margin[side.key].unit === 'mm' }"
|
||||||
:disabled="inheritanceLocked"
|
:disabled="inheritanceLocked"
|
||||||
@click="updateUnitPropUnit(marginOuter, 'mm')"
|
@click="updateMarginUnit(side.key, 'mm')"
|
||||||
>
|
>mm</button>
|
||||||
mm
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
:class="{ active: marginOuter.unit === 'px' }"
|
:class="{ active: margin[side.key].unit === 'px' }"
|
||||||
:disabled="inheritanceLocked"
|
:disabled="inheritanceLocked"
|
||||||
@click="updateUnitPropUnit(marginOuter, 'px')"
|
@click="updateMarginUnit(side.key, 'px')"
|
||||||
>
|
>px</button>
|
||||||
px
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -172,32 +194,54 @@
|
||||||
|
|
||||||
<!-- Inner Margins (Padding) -->
|
<!-- Inner Margins (Padding) -->
|
||||||
<div class="settings-subsection">
|
<div class="settings-subsection">
|
||||||
<div class="field" :class="{ 'field--view-only': inheritanceLocked }">
|
<div class="settings-subsection-header">
|
||||||
<label class="label-with-tooltip" data-css="padding">Marges intérieures</label>
|
<span class="label-with-tooltip" data-css="padding">Marges intérieures</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="lock-toggle"
|
||||||
|
:class="{ locked: paddingLocked }"
|
||||||
|
:disabled="inheritanceLocked"
|
||||||
|
@click="paddingLocked = !paddingLocked"
|
||||||
|
:title="paddingLocked ? 'Déverrouiller (modifier indépendamment)' : 'Verrouiller (modifier ensemble)'"
|
||||||
|
>
|
||||||
|
<svg v-if="paddingLocked" width="11" height="13" viewBox="0 0 11 13" fill="none">
|
||||||
|
<rect x="1" y="5.5" width="9" height="7" rx="1" fill="currentColor"/>
|
||||||
|
<path d="M2.5 5.5V3.5a3 3 0 0 1 6 0v2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
<svg v-else width="11" height="13" viewBox="0 0 11 13" fill="none">
|
||||||
|
<rect x="1" y="5.5" width="9" height="7" rx="1" fill="currentColor"/>
|
||||||
|
<path d="M2.5 5.5V3.5a3 3 0 0 1 6 0" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="side in sides"
|
||||||
|
:key="side.key"
|
||||||
|
class="field field-margin"
|
||||||
|
:class="{ 'field--view-only': inheritanceLocked }"
|
||||||
|
>
|
||||||
|
<label class="label-with-tooltip" :data-css="`padding-${side.key}`">{{ side.label }}</label>
|
||||||
<div class="input-with-unit">
|
<div class="input-with-unit">
|
||||||
<NumberInput
|
<NumberInput
|
||||||
v-model="paddingInner.value"
|
:modelValue="padding[side.key].value"
|
||||||
:min="0"
|
:min="0"
|
||||||
:step="1"
|
:step="1"
|
||||||
:disabled="inheritanceLocked"
|
:disabled="inheritanceLocked"
|
||||||
|
@update:modelValue="(v) => updatePaddingValue(side.key, v)"
|
||||||
/>
|
/>
|
||||||
<div class="unit-toggle">
|
<div class="unit-toggle">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
:class="{ active: paddingInner.unit === 'mm' }"
|
:class="{ active: padding[side.key].unit === 'mm' }"
|
||||||
:disabled="inheritanceLocked"
|
:disabled="inheritanceLocked"
|
||||||
@click="updateUnitPropUnit(paddingInner, 'mm')"
|
@click="updatePaddingUnit(side.key, 'mm')"
|
||||||
>
|
>mm</button>
|
||||||
mm
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
:class="{ active: paddingInner.unit === 'px' }"
|
:class="{ active: padding[side.key].unit === 'px' }"
|
||||||
:disabled="inheritanceLocked"
|
:disabled="inheritanceLocked"
|
||||||
@click="updateUnitPropUnit(paddingInner, 'px')"
|
@click="updatePaddingUnit(side.key, 'px')"
|
||||||
>
|
>px</button>
|
||||||
px
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -246,8 +290,22 @@ const color = ref('rgb(0, 0, 0)');
|
||||||
const background = ref('transparent');
|
const background = ref('transparent');
|
||||||
const fontSize = reactive({ value: 23, unit: 'px' });
|
const fontSize = reactive({ value: 23, unit: 'px' });
|
||||||
const lineHeight = reactive({ value: 28, 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
|
// Constants
|
||||||
const fonts = ['Alegreya Sans', 'Alegreya', 'Arial', 'Georgia', 'Times New Roman'];
|
const fonts = ['Alegreya Sans', 'Alegreya', 'Arial', 'Georgia', 'Times New Roman'];
|
||||||
|
|
@ -271,10 +329,55 @@ const styleProps = [
|
||||||
const unitProps = [
|
const unitProps = [
|
||||||
{ css: 'font-size', ref: fontSize, debounce: true },
|
{ css: 'font-size', ref: fontSize, debounce: true },
|
||||||
{ css: 'line-height', ref: lineHeight, 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
|
// Generic update: push a single property to the stylesheet store
|
||||||
const updateProp = (cssProp, value, unit) => {
|
const updateProp = (cssProp, value, unit) => {
|
||||||
if (!selector.value) return;
|
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 '';
|
if (properties.length === 0) return '';
|
||||||
|
|
||||||
return `${selector.value} {\n${properties.join('\n')}\n}`;
|
return `${selector.value} {\n${properties.join('\n')}\n}`;
|
||||||
|
|
@ -364,6 +478,10 @@ const applyAllStyles = () => {
|
||||||
for (const prop of unitProps) {
|
for (const prop of unitProps) {
|
||||||
updateProp(prop.css, prop.ref.value, prop.ref.unit);
|
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
|
// 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 handleCssInput = (newCss) => {
|
||||||
const oldBlock = elementCss.value;
|
const oldBlock = elementCss.value;
|
||||||
if (oldBlock) {
|
if (oldBlock) {
|
||||||
|
|
@ -440,6 +602,47 @@ const loadValuesFromStylesheet = () => {
|
||||||
prop.ref.unit = data.unit;
|
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) {
|
} catch (error) {
|
||||||
console.error('Error loading values from stylesheet:', error);
|
console.error('Error loading values from stylesheet:', error);
|
||||||
}
|
}
|
||||||
|
|
@ -517,16 +720,18 @@ const toggleInheritance = () => {
|
||||||
color.value = cs.color;
|
color.value = cs.color;
|
||||||
background.value = cs.backgroundColor;
|
background.value = cs.backgroundColor;
|
||||||
|
|
||||||
const marginMatch = cs.marginTop.match(/([\d.]+)(px|mm|pt)/);
|
for (const side of ['top', 'right', 'bottom', 'left']) {
|
||||||
if (marginMatch) {
|
const cssSide = side.charAt(0).toUpperCase() + side.slice(1);
|
||||||
marginOuter.value = parseFloat(marginMatch[1]);
|
const marginMatch = cs[`margin${cssSide}`].match(/([\d.]+)(px|mm|pt)/);
|
||||||
marginOuter.unit = marginMatch[2];
|
if (marginMatch) {
|
||||||
}
|
margin[side].value = parseFloat(marginMatch[1]);
|
||||||
|
margin[side].unit = marginMatch[2];
|
||||||
const paddingMatch = cs.paddingTop.match(/([\d.]+)(px|mm|pt)/);
|
}
|
||||||
if (paddingMatch) {
|
const paddingMatch = cs[`padding${cssSide}`].match(/([\d.]+)(px|mm|pt)/);
|
||||||
paddingInner.value = parseFloat(paddingMatch[1]);
|
if (paddingMatch) {
|
||||||
paddingInner.unit = paddingMatch[2];
|
padding[side].value = parseFloat(paddingMatch[1]);
|
||||||
|
padding[side].unit = paddingMatch[2];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isUpdatingFromStore = false;
|
isUpdatingFromStore = false;
|
||||||
|
|
@ -558,4 +763,38 @@ defineExpose({ handleIframeClick, close, visible });
|
||||||
color: var(--color-purple);
|
color: var(--color-purple);
|
||||||
font-size: 0.875rem;
|
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;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue