All checks were successful
Deploy / Build and Deploy to Production (push) Successful in 16s
Fixed TextSettings fields not updating the stylesheet and preview after the store refactoring that made content a computed property. - Add missing font watcher in TextSettings.vue - Update useCssUpdater.js to use store.replaceBlock() instead of writing to readonly store.content - Update createRule() to append to store.customCss instead of store.content All TextSettings fields (font, size, margins, padding, alignment) now correctly update the stylesheet and preview. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
799 lines
29 KiB
Vue
799 lines
29 KiB
Vue
<template>
|
|
<section class="settings-section" id="settings-section_elem" data-color-type="elem">
|
|
<h2>Réglage du texte</h2>
|
|
<div class="container">
|
|
|
|
<p class="infos">
|
|
Ces réglages s'appliquent à l'ensemble des éléments du document. Vous
|
|
pouvez modifier ensuite les éléments indépendamment.
|
|
</p>
|
|
|
|
<!-- Police -->
|
|
<div class="settings-subsection">
|
|
<div class="field field-font">
|
|
<label for="text-font" class="label-with-tooltip" data-css="font-family">Police</label>
|
|
<div class="field-with-option">
|
|
<select id="text-font" v-model="font">
|
|
<option v-for="f in fonts" :key="f" :value="f">{{ f }}</option>
|
|
</select>
|
|
<div class="field-checkbox">
|
|
<input id="text-italic" type="checkbox" v-model="italic" />
|
|
<label for="text-italic" class="label-with-tooltip" data-css="font-style">Italique</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Graisse -->
|
|
<div class="settings-subsection">
|
|
<div class="field">
|
|
<label class="label-with-tooltip" data-css="font-weight">Graisse</label>
|
|
<UnitToggle v-model="weight" :units="weights" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Taille du texte -->
|
|
<div class="settings-subsection">
|
|
<div class="field field-text-size">
|
|
<label for="text-size-range" class="label-with-tooltip" data-css="font-size">Taille du texte</label>
|
|
<InputWithUnit
|
|
v-model="fontSize"
|
|
:units="['px', 'em', 'rem']"
|
|
:min="8"
|
|
:max="72"
|
|
showRange
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Alignement -->
|
|
<div class="settings-subsection">
|
|
<div class="field field-simple">
|
|
<label for="text-alignment" class="label-with-tooltip" data-css="text-align">Alignement</label>
|
|
<select id="text-alignment" v-model="alignment">
|
|
<option v-for="a in alignments" :key="a.value" :value="a.value">
|
|
{{ a.label }}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Couleurs -->
|
|
<div class="settings-subsection">
|
|
<div class="field field-simple">
|
|
<label for="text-color" class="label-with-tooltip" data-css="color">Couleur</label>
|
|
<div class="input-with-color">
|
|
<input
|
|
id="text-color"
|
|
type="text"
|
|
v-model="color"
|
|
class="color-input"
|
|
data-coloris
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Couleurs // arrière plan -->
|
|
<div class="settings-subsection">
|
|
<div class="field field-simple">
|
|
<label for="text-background" class="label-with-tooltip" data-css="background">Arrière-plan</label>
|
|
<div class="input-with-color">
|
|
<input
|
|
id="text-background"
|
|
type="text"
|
|
v-model="background"
|
|
class="color-input"
|
|
data-coloris
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Marges extérieures -->
|
|
<div class="settings-subsection margins">
|
|
<div class="subsection-header">
|
|
<h3>Marges extérieures</h3>
|
|
<button
|
|
type="button"
|
|
class="link-button"
|
|
:class="{ active: marginOuterLinked }"
|
|
@click="marginOuterLinked = !marginOuterLinked"
|
|
:title="marginOuterLinked ? 'Dissocier les marges' : 'Lier les marges'"
|
|
>
|
|
<svg v-if="marginOuterLinked" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M18.3638 15.5355L16.9496 14.1213L18.3638 12.7071C20.3164 10.7545 20.3164 7.58866 18.3638 5.63604C16.4112 3.68341 13.2453 3.68341 11.2927 5.63604L9.87849 7.05025L8.46428 5.63604L9.87849 4.22182C12.6122 1.48815 17.0443 1.48815 19.778 4.22182C22.5117 6.95549 22.5117 11.3876 19.778 14.1213L18.3638 15.5355ZM15.5353 18.364L14.1211 19.7782C11.3875 22.5118 6.95531 22.5118 4.22164 19.7782C1.48797 17.0445 1.48797 12.6123 4.22164 9.87868L5.63585 8.46446L7.05007 9.87868L5.63585 11.2929C3.68323 13.2455 3.68323 16.4113 5.63585 18.364C7.58847 20.3166 10.7543 20.3166 12.7069 18.364L14.1211 16.9497L15.5353 18.364ZM14.8282 7.75736L16.2425 9.17157L9.17139 16.2426L7.75717 14.8284L14.8282 7.75736Z"></path></svg>
|
|
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M17 17H22V19H19V22H17V17ZM7 7H2V5H5V2H7V7ZM18.364 15.5355L16.9497 14.1213L18.364 12.7071C20.3166 10.7545 20.3166 7.58866 18.364 5.63604C16.4113 3.68342 13.2455 3.68342 11.2929 5.63604L9.87868 7.05025L8.46447 5.63604L9.87868 4.22183C12.6123 1.48816 17.0445 1.48816 19.7782 4.22183C22.5118 6.9555 22.5118 11.3877 19.7782 14.1213L18.364 15.5355ZM15.5355 18.364L14.1213 19.7782C11.3877 22.5118 6.9555 22.5118 4.22183 19.7782C1.48816 17.0445 1.48816 12.6123 4.22183 9.87868L5.63604 8.46447L7.05025 9.87868L5.63604 11.2929C3.68342 13.2455 3.68342 16.4113 5.63604 18.364C7.58866 20.3166 10.7545 20.3166 12.7071 18.364L14.1213 16.9497L15.5355 18.364ZM14.8284 7.75736L16.2426 9.17157L9.17157 16.2426L7.75736 14.8284L14.8284 7.75736Z"></path></svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="field field-margin">
|
|
<label for="margin-outer-top" class="label-with-tooltip" data-css="margin-top">Haut</label>
|
|
<div class="input-with-unit">
|
|
<NumberInput
|
|
id="margin-outer-top"
|
|
:modelValue="marginOuterDetailed.top.value"
|
|
@update:modelValue="(value) => marginOuterDetailed.top.value = value"
|
|
:min="0"
|
|
:step="1"
|
|
/>
|
|
<div class="unit-toggle">
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginOuterDetailed.top.unit === 'mm' }"
|
|
@click="updateMarginOuterUnit('mm')"
|
|
>mm</button>
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginOuterDetailed.top.unit === 'px' }"
|
|
@click="updateMarginOuterUnit('px')"
|
|
>px</button>
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginOuterDetailed.top.unit === 'rem' }"
|
|
@click="updateMarginOuterUnit('rem')"
|
|
>rem</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field field-margin">
|
|
<label for="margin-outer-bottom" class="label-with-tooltip" data-css="margin-bottom">Bas</label>
|
|
<div class="input-with-unit">
|
|
<NumberInput
|
|
id="margin-outer-bottom"
|
|
:modelValue="marginOuterDetailed.bottom.value"
|
|
@update:modelValue="(value) => marginOuterDetailed.bottom.value = value"
|
|
:min="0"
|
|
:step="1"
|
|
/>
|
|
<div class="unit-toggle">
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginOuterDetailed.bottom.unit === 'mm' }"
|
|
@click="updateMarginOuterUnit('mm')"
|
|
>mm</button>
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginOuterDetailed.bottom.unit === 'px' }"
|
|
@click="updateMarginOuterUnit('px')"
|
|
>px</button>
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginOuterDetailed.bottom.unit === 'rem' }"
|
|
@click="updateMarginOuterUnit('rem')"
|
|
>rem</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field field-margin">
|
|
<label for="margin-outer-left" class="label-with-tooltip" data-css="margin-left">Gauche</label>
|
|
<div class="input-with-unit">
|
|
<NumberInput
|
|
id="margin-outer-left"
|
|
:modelValue="marginOuterDetailed.left.value"
|
|
@update:modelValue="(value) => marginOuterDetailed.left.value = value"
|
|
:min="0"
|
|
:step="1"
|
|
/>
|
|
<div class="unit-toggle">
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginOuterDetailed.left.unit === 'mm' }"
|
|
@click="updateMarginOuterUnit('mm')"
|
|
>mm</button>
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginOuterDetailed.left.unit === 'px' }"
|
|
@click="updateMarginOuterUnit('px')"
|
|
>px</button>
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginOuterDetailed.left.unit === 'rem' }"
|
|
@click="updateMarginOuterUnit('rem')"
|
|
>rem</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field field-margin">
|
|
<label for="margin-outer-right" class="label-with-tooltip" data-css="margin-right">Droite</label>
|
|
<div class="input-with-unit">
|
|
<NumberInput
|
|
id="margin-outer-right"
|
|
:modelValue="marginOuterDetailed.right.value"
|
|
@update:modelValue="(value) => marginOuterDetailed.right.value = value"
|
|
:min="0"
|
|
:step="1"
|
|
/>
|
|
<div class="unit-toggle">
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginOuterDetailed.right.unit === 'mm' }"
|
|
@click="updateMarginOuterUnit('mm')"
|
|
>mm</button>
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginOuterDetailed.right.unit === 'px' }"
|
|
@click="updateMarginOuterUnit('px')"
|
|
>px</button>
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginOuterDetailed.right.unit === 'rem' }"
|
|
@click="updateMarginOuterUnit('rem')"
|
|
>rem</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Marges intérieures -->
|
|
<div class="settings-subsection margins">
|
|
<div class="subsection-header">
|
|
<h3>Marges intérieures</h3>
|
|
<button
|
|
type="button"
|
|
class="link-button"
|
|
:class="{ active: marginInnerLinked }"
|
|
@click="marginInnerLinked = !marginInnerLinked"
|
|
:title="marginInnerLinked ? 'Dissocier les marges' : 'Lier les marges'"
|
|
>
|
|
<svg v-if="marginInnerLinked" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M18.3638 15.5355L16.9496 14.1213L18.3638 12.7071C20.3164 10.7545 20.3164 7.58866 18.3638 5.63604C16.4112 3.68341 13.2453 3.68341 11.2927 5.63604L9.87849 7.05025L8.46428 5.63604L9.87849 4.22182C12.6122 1.48815 17.0443 1.48815 19.778 4.22182C22.5117 6.95549 22.5117 11.3876 19.778 14.1213L18.3638 15.5355ZM15.5353 18.364L14.1211 19.7782C11.3875 22.5118 6.95531 22.5118 4.22164 19.7782C1.48797 17.0445 1.48797 12.6123 4.22164 9.87868L5.63585 8.46446L7.05007 9.87868L5.63585 11.2929C3.68323 13.2455 3.68323 16.4113 5.63585 18.364C7.58847 20.3166 10.7543 20.3166 12.7069 18.364L14.1211 16.9497L15.5353 18.364ZM14.8282 7.75736L16.2425 9.17157L9.17139 16.2426L7.75717 14.8284L14.8282 7.75736Z"></path></svg>
|
|
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M17 17H22V19H19V22H17V17ZM7 7H2V5H5V2H7V7ZM18.364 15.5355L16.9497 14.1213L18.364 12.7071C20.3166 10.7545 20.3166 7.58866 18.364 5.63604C16.4113 3.68342 13.2455 3.68342 11.2929 5.63604L9.87868 7.05025L8.46447 5.63604L9.87868 4.22183C12.6123 1.48816 17.0445 1.48816 19.7782 4.22183C22.5118 6.9555 22.5118 11.3877 19.7782 14.1213L18.364 15.5355ZM15.5355 18.364L14.1213 19.7782C11.3877 22.5118 6.9555 22.5118 4.22183 19.7782C1.48816 17.0445 1.48816 12.6123 4.22183 9.87868L5.63604 8.46447L7.05025 9.87868L5.63604 11.2929C3.68342 13.2455 3.68342 16.4113 5.63604 18.364C7.58866 20.3166 10.7545 20.3166 12.7071 18.364L14.1213 16.9497L15.5355 18.364ZM14.8284 7.75736L16.2426 9.17157L9.17157 16.2426L7.75736 14.8284L14.8284 7.75736Z"></path></svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="field field-margin">
|
|
<label for="margin-inner-top" class="label-with-tooltip" data-css="padding-top">Haut</label>
|
|
<div class="input-with-unit">
|
|
<NumberInput
|
|
id="margin-inner-top"
|
|
:modelValue="marginInnerDetailed.top.value"
|
|
@update:modelValue="(value) => marginInnerDetailed.top.value = value"
|
|
:min="0"
|
|
:step="1"
|
|
/>
|
|
<div class="unit-toggle">
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginInnerDetailed.top.unit === 'mm' }"
|
|
@click="updateMarginInnerUnit('mm')"
|
|
>mm</button>
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginInnerDetailed.top.unit === 'px' }"
|
|
@click="updateMarginInnerUnit('px')"
|
|
>px</button>
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginInnerDetailed.top.unit === 'rem' }"
|
|
@click="updateMarginInnerUnit('rem')"
|
|
>rem</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field field-margin">
|
|
<label for="margin-inner-bottom" class="label-with-tooltip" data-css="padding-bottom">Bas</label>
|
|
<div class="input-with-unit">
|
|
<NumberInput
|
|
id="margin-inner-bottom"
|
|
:modelValue="marginInnerDetailed.bottom.value"
|
|
@update:modelValue="(value) => marginInnerDetailed.bottom.value = value"
|
|
:min="0"
|
|
:step="1"
|
|
/>
|
|
<div class="unit-toggle">
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginInnerDetailed.bottom.unit === 'mm' }"
|
|
@click="updateMarginInnerUnit('mm')"
|
|
>mm</button>
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginInnerDetailed.bottom.unit === 'px' }"
|
|
@click="updateMarginInnerUnit('px')"
|
|
>px</button>
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginInnerDetailed.bottom.unit === 'rem' }"
|
|
@click="updateMarginInnerUnit('rem')"
|
|
>rem</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field field-margin">
|
|
<label for="margin-inner-left" class="label-with-tooltip" data-css="padding-left">Gauche</label>
|
|
<div class="input-with-unit">
|
|
<NumberInput
|
|
id="margin-inner-left"
|
|
:modelValue="marginInnerDetailed.left.value"
|
|
@update:modelValue="(value) => marginInnerDetailed.left.value = value"
|
|
:min="0"
|
|
:step="1"
|
|
/>
|
|
<div class="unit-toggle">
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginInnerDetailed.left.unit === 'mm' }"
|
|
@click="updateMarginInnerUnit('mm')"
|
|
>mm</button>
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginInnerDetailed.left.unit === 'px' }"
|
|
@click="updateMarginInnerUnit('px')"
|
|
>px</button>
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginInnerDetailed.left.unit === 'rem' }"
|
|
@click="updateMarginInnerUnit('rem')"
|
|
>rem</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field field-margin">
|
|
<label for="margin-inner-right" class="label-with-tooltip" data-css="padding-right">Droite</label>
|
|
<div class="input-with-unit">
|
|
<NumberInput
|
|
id="margin-inner-right"
|
|
:modelValue="marginInnerDetailed.right.value"
|
|
@update:modelValue="(value) => marginInnerDetailed.right.value = value"
|
|
:min="0"
|
|
:step="1"
|
|
/>
|
|
<div class="unit-toggle">
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginInnerDetailed.right.unit === 'mm' }"
|
|
@click="updateMarginInnerUnit('mm')"
|
|
>mm</button>
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginInnerDetailed.right.unit === 'px' }"
|
|
@click="updateMarginInnerUnit('px')"
|
|
>px</button>
|
|
<button
|
|
type="button"
|
|
:class="{ active: marginInnerDetailed.right.unit === 'rem' }"
|
|
@click="updateMarginInnerUnit('rem')"
|
|
>rem</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</section>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, watch, onMounted } from 'vue';
|
|
import Coloris from '@melloware/coloris';
|
|
import UnitToggle from '../ui/UnitToggle.vue';
|
|
import InputWithUnit from '../ui/InputWithUnit.vue';
|
|
import NumberInput from '../ui/NumberInput.vue';
|
|
import { useCssUpdater } from '../../composables/useCssUpdater';
|
|
import { useCssSync } from '../../composables/useCssSync';
|
|
import { useDebounce } from '../../composables/useDebounce';
|
|
|
|
const { updateStyle, setMargin, setDetailedMargins, setPadding, setDetailedPadding } = useCssUpdater();
|
|
const { extractValue, extractNumericValue, extractSpacing } = useCssSync();
|
|
const { debouncedUpdate } = useDebounce(500);
|
|
|
|
// Constants
|
|
const fonts = ['Alegreya Sans', 'Arial', 'Georgia', 'Helvetica', 'Times New Roman'];
|
|
const weights = ['200', '300', '400', '600', '800', 'normal', 'bold'];
|
|
const alignments = [
|
|
{ value: 'left', label: 'Gauche' },
|
|
{ value: 'center', label: 'Centre' },
|
|
{ value: 'right', label: 'Droite' },
|
|
{ value: 'justify', label: 'Justifié' }
|
|
];
|
|
|
|
// State
|
|
const font = ref('Alegreya Sans');
|
|
const italic = ref(false);
|
|
const weight = ref('400');
|
|
const fontSize = ref({ value: 16, unit: 'px' });
|
|
const alignment = ref('left');
|
|
const color = ref('rgb(0, 0, 0)');
|
|
const background = ref('transparent');
|
|
|
|
const marginOuterDetailed = ref({
|
|
top: { value: 0, unit: 'mm' },
|
|
right: { value: 0, unit: 'mm' },
|
|
bottom: { value: 24, unit: 'mm' },
|
|
left: { value: 0, unit: 'mm' }
|
|
});
|
|
|
|
const marginInnerDetailed = ref({
|
|
top: { value: 0, unit: 'mm' },
|
|
right: { value: 0, unit: 'mm' },
|
|
bottom: { value: 0, unit: 'mm' },
|
|
left: { value: 0, unit: 'mm' }
|
|
});
|
|
|
|
const marginOuterLinked = ref(false);
|
|
const marginInnerLinked = ref(false);
|
|
|
|
// Track previous values to detect which one changed
|
|
const prevMarginOuter = ref({
|
|
top: 0,
|
|
right: 0,
|
|
bottom: 24,
|
|
left: 0
|
|
});
|
|
|
|
const prevMarginInner = ref({
|
|
top: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
left: 0
|
|
});
|
|
|
|
let isUpdatingFromStore = false;
|
|
|
|
// Update margin outer unit for all sides
|
|
const updateMarginOuterUnit = (unit) => {
|
|
marginOuterDetailed.value.top.unit = unit;
|
|
marginOuterDetailed.value.right.unit = unit;
|
|
marginOuterDetailed.value.bottom.unit = unit;
|
|
marginOuterDetailed.value.left.unit = unit;
|
|
};
|
|
|
|
// Update margin inner unit for all sides
|
|
const updateMarginInnerUnit = (unit) => {
|
|
marginInnerDetailed.value.top.unit = unit;
|
|
marginInnerDetailed.value.right.unit = unit;
|
|
marginInnerDetailed.value.bottom.unit = unit;
|
|
marginInnerDetailed.value.left.unit = unit;
|
|
};
|
|
|
|
// Watchers for body styles
|
|
watch(font, (val) => {
|
|
if (isUpdatingFromStore) return;
|
|
updateStyle('body', 'font-family', `"${val}"`);
|
|
});
|
|
|
|
watch(italic, (val) => {
|
|
if (isUpdatingFromStore) return;
|
|
updateStyle('body', 'font-style', val ? 'italic' : 'normal');
|
|
});
|
|
|
|
watch(alignment, (val) => {
|
|
if (isUpdatingFromStore) return;
|
|
updateStyle('body', 'text-align', val);
|
|
});
|
|
|
|
watch(color, (val) => {
|
|
if (isUpdatingFromStore) return;
|
|
updateStyle('body', 'color', val);
|
|
});
|
|
|
|
watch(background, (val) => {
|
|
if (isUpdatingFromStore) return;
|
|
updateStyle('body', 'background', val);
|
|
});
|
|
|
|
// Watchers for paragraph styles
|
|
watch(weight, (val) => {
|
|
if (isUpdatingFromStore) return;
|
|
updateStyle('p', 'font-weight', val);
|
|
});
|
|
|
|
watch(fontSize, (val) => {
|
|
if (isUpdatingFromStore) return;
|
|
debouncedUpdate(() => {
|
|
updateStyle('p', 'font-size', `${val.value}${val.unit}`);
|
|
});
|
|
}, { deep: true });
|
|
|
|
// Watch when link is toggled
|
|
watch(marginOuterLinked, (isLinked) => {
|
|
if (isLinked) {
|
|
// When linking, sync all to the first non-zero value or top value
|
|
const current = marginOuterDetailed.value;
|
|
const syncValue = current.top.value || current.bottom.value || current.left.value || current.right.value;
|
|
|
|
isUpdatingFromStore = true;
|
|
marginOuterDetailed.value.top.value = syncValue;
|
|
marginOuterDetailed.value.bottom.value = syncValue;
|
|
marginOuterDetailed.value.left.value = syncValue;
|
|
marginOuterDetailed.value.right.value = syncValue;
|
|
|
|
prevMarginOuter.value.top = syncValue;
|
|
prevMarginOuter.value.bottom = syncValue;
|
|
prevMarginOuter.value.left = syncValue;
|
|
prevMarginOuter.value.right = syncValue;
|
|
isUpdatingFromStore = false;
|
|
}
|
|
});
|
|
|
|
watch(marginInnerLinked, (isLinked) => {
|
|
if (isLinked) {
|
|
// When linking, sync all to the first non-zero value or top value
|
|
const current = marginInnerDetailed.value;
|
|
const syncValue = current.top.value || current.bottom.value || current.left.value || current.right.value;
|
|
|
|
isUpdatingFromStore = true;
|
|
marginInnerDetailed.value.top.value = syncValue;
|
|
marginInnerDetailed.value.bottom.value = syncValue;
|
|
marginInnerDetailed.value.left.value = syncValue;
|
|
marginInnerDetailed.value.right.value = syncValue;
|
|
|
|
prevMarginInner.value.top = syncValue;
|
|
prevMarginInner.value.bottom = syncValue;
|
|
prevMarginInner.value.left = syncValue;
|
|
prevMarginInner.value.right = syncValue;
|
|
isUpdatingFromStore = false;
|
|
}
|
|
});
|
|
|
|
// Watch margin outer values
|
|
watch(() => [
|
|
marginOuterDetailed.value.top.value,
|
|
marginOuterDetailed.value.bottom.value,
|
|
marginOuterDetailed.value.left.value,
|
|
marginOuterDetailed.value.right.value,
|
|
], () => {
|
|
if (isUpdatingFromStore) return;
|
|
|
|
// If linked, sync all values to the one that changed
|
|
if (marginOuterLinked.value) {
|
|
const current = {
|
|
top: marginOuterDetailed.value.top.value,
|
|
bottom: marginOuterDetailed.value.bottom.value,
|
|
left: marginOuterDetailed.value.left.value,
|
|
right: marginOuterDetailed.value.right.value,
|
|
};
|
|
|
|
// Find which value actually changed by comparing with previous
|
|
let changedValue = null;
|
|
if (current.top !== prevMarginOuter.value.top) changedValue = current.top;
|
|
else if (current.bottom !== prevMarginOuter.value.bottom) changedValue = current.bottom;
|
|
else if (current.left !== prevMarginOuter.value.left) changedValue = current.left;
|
|
else if (current.right !== prevMarginOuter.value.right) changedValue = current.right;
|
|
|
|
if (changedValue !== null) {
|
|
isUpdatingFromStore = true;
|
|
marginOuterDetailed.value.top.value = changedValue;
|
|
marginOuterDetailed.value.bottom.value = changedValue;
|
|
marginOuterDetailed.value.left.value = changedValue;
|
|
marginOuterDetailed.value.right.value = changedValue;
|
|
|
|
// Update previous values
|
|
prevMarginOuter.value.top = changedValue;
|
|
prevMarginOuter.value.bottom = changedValue;
|
|
prevMarginOuter.value.left = changedValue;
|
|
prevMarginOuter.value.right = changedValue;
|
|
isUpdatingFromStore = false;
|
|
}
|
|
} else {
|
|
// Update previous values even when not linked
|
|
prevMarginOuter.value.top = marginOuterDetailed.value.top.value;
|
|
prevMarginOuter.value.bottom = marginOuterDetailed.value.bottom.value;
|
|
prevMarginOuter.value.left = marginOuterDetailed.value.left.value;
|
|
prevMarginOuter.value.right = marginOuterDetailed.value.right.value;
|
|
}
|
|
|
|
debouncedUpdate(() => {
|
|
setDetailedMargins('p',
|
|
marginOuterDetailed.value.top,
|
|
marginOuterDetailed.value.right,
|
|
marginOuterDetailed.value.bottom,
|
|
marginOuterDetailed.value.left
|
|
);
|
|
});
|
|
});
|
|
|
|
// Watch margin inner values
|
|
watch(() => [
|
|
marginInnerDetailed.value.top.value,
|
|
marginInnerDetailed.value.bottom.value,
|
|
marginInnerDetailed.value.left.value,
|
|
marginInnerDetailed.value.right.value,
|
|
], () => {
|
|
if (isUpdatingFromStore) return;
|
|
|
|
// If linked, sync all values to the one that changed
|
|
if (marginInnerLinked.value) {
|
|
const current = {
|
|
top: marginInnerDetailed.value.top.value,
|
|
bottom: marginInnerDetailed.value.bottom.value,
|
|
left: marginInnerDetailed.value.left.value,
|
|
right: marginInnerDetailed.value.right.value,
|
|
};
|
|
|
|
// Find which value actually changed by comparing with previous
|
|
let changedValue = null;
|
|
if (current.top !== prevMarginInner.value.top) changedValue = current.top;
|
|
else if (current.bottom !== prevMarginInner.value.bottom) changedValue = current.bottom;
|
|
else if (current.left !== prevMarginInner.value.left) changedValue = current.left;
|
|
else if (current.right !== prevMarginInner.value.right) changedValue = current.right;
|
|
|
|
if (changedValue !== null) {
|
|
isUpdatingFromStore = true;
|
|
marginInnerDetailed.value.top.value = changedValue;
|
|
marginInnerDetailed.value.bottom.value = changedValue;
|
|
marginInnerDetailed.value.left.value = changedValue;
|
|
marginInnerDetailed.value.right.value = changedValue;
|
|
|
|
// Update previous values
|
|
prevMarginInner.value.top = changedValue;
|
|
prevMarginInner.value.bottom = changedValue;
|
|
prevMarginInner.value.left = changedValue;
|
|
prevMarginInner.value.right = changedValue;
|
|
isUpdatingFromStore = false;
|
|
}
|
|
} else {
|
|
// Update previous values even when not linked
|
|
prevMarginInner.value.top = marginInnerDetailed.value.top.value;
|
|
prevMarginInner.value.bottom = marginInnerDetailed.value.bottom.value;
|
|
prevMarginInner.value.left = marginInnerDetailed.value.left.value;
|
|
prevMarginInner.value.right = marginInnerDetailed.value.right.value;
|
|
}
|
|
|
|
debouncedUpdate(() => {
|
|
setDetailedPadding('p',
|
|
marginInnerDetailed.value.top,
|
|
marginInnerDetailed.value.right,
|
|
marginInnerDetailed.value.bottom,
|
|
marginInnerDetailed.value.left
|
|
);
|
|
});
|
|
});
|
|
|
|
// Sync from store
|
|
const syncFromStore = () => {
|
|
isUpdatingFromStore = true;
|
|
|
|
// Body styles
|
|
const fontStyle = extractValue('body', 'font-style');
|
|
if (fontStyle) italic.value = fontStyle === 'italic';
|
|
|
|
const textAlign = extractValue('body', 'text-align');
|
|
if (textAlign) alignment.value = textAlign;
|
|
|
|
const colorVal = extractValue('body', 'color');
|
|
if (colorVal) color.value = colorVal;
|
|
|
|
const bgVal = extractValue('body', 'background');
|
|
if (bgVal) background.value = bgVal;
|
|
|
|
// Paragraph styles
|
|
const fontWeight = extractValue('p', 'font-weight');
|
|
if (fontWeight) weight.value = fontWeight;
|
|
|
|
const fontSizeVal = extractNumericValue('p', 'font-size', ['px', 'em', 'rem']);
|
|
if (fontSizeVal) fontSize.value = fontSizeVal;
|
|
|
|
// Margins
|
|
const margins = extractSpacing('p', 'margin');
|
|
if (margins) {
|
|
if (margins.simple) {
|
|
// All margins are the same
|
|
marginOuterDetailed.value = {
|
|
top: { ...margins.simple },
|
|
right: { ...margins.simple },
|
|
bottom: { ...margins.simple },
|
|
left: { ...margins.simple }
|
|
};
|
|
marginOuterLinked.value = true;
|
|
} else if (margins.detailed) {
|
|
marginOuterDetailed.value = margins.detailed;
|
|
// Check if all values are the same
|
|
const allSame =
|
|
margins.detailed.top.value === margins.detailed.right.value &&
|
|
margins.detailed.top.value === margins.detailed.bottom.value &&
|
|
margins.detailed.top.value === margins.detailed.left.value &&
|
|
margins.detailed.top.unit === margins.detailed.right.unit &&
|
|
margins.detailed.top.unit === margins.detailed.bottom.unit &&
|
|
margins.detailed.top.unit === margins.detailed.left.unit;
|
|
marginOuterLinked.value = allSame;
|
|
}
|
|
}
|
|
|
|
// Padding
|
|
const padding = extractSpacing('p', 'padding');
|
|
if (padding) {
|
|
if (padding.simple) {
|
|
// All paddings are the same
|
|
marginInnerDetailed.value = {
|
|
top: { ...padding.simple },
|
|
right: { ...padding.simple },
|
|
bottom: { ...padding.simple },
|
|
left: { ...padding.simple }
|
|
};
|
|
marginInnerLinked.value = true;
|
|
} else if (padding.detailed) {
|
|
marginInnerDetailed.value = padding.detailed;
|
|
// Check if all values are the same
|
|
const allSame =
|
|
padding.detailed.top.value === padding.detailed.right.value &&
|
|
padding.detailed.top.value === padding.detailed.bottom.value &&
|
|
padding.detailed.top.value === padding.detailed.left.value &&
|
|
padding.detailed.top.unit === padding.detailed.right.unit &&
|
|
padding.detailed.top.unit === padding.detailed.bottom.unit &&
|
|
padding.detailed.top.unit === padding.detailed.left.unit;
|
|
marginInnerLinked.value = allSame;
|
|
}
|
|
}
|
|
|
|
// Update previous values to match current state
|
|
prevMarginOuter.value = {
|
|
top: marginOuterDetailed.value.top.value,
|
|
right: marginOuterDetailed.value.right.value,
|
|
bottom: marginOuterDetailed.value.bottom.value,
|
|
left: marginOuterDetailed.value.left.value
|
|
};
|
|
|
|
prevMarginInner.value = {
|
|
top: marginInnerDetailed.value.top.value,
|
|
right: marginInnerDetailed.value.right.value,
|
|
bottom: marginInnerDetailed.value.bottom.value,
|
|
left: marginInnerDetailed.value.left.value
|
|
};
|
|
|
|
isUpdatingFromStore = false;
|
|
};
|
|
|
|
onMounted(() => {
|
|
Coloris.init();
|
|
Coloris({
|
|
themeMode: 'dark',
|
|
alpha: true,
|
|
format: 'auto',
|
|
formatToggle: true,
|
|
swatches: ['#000000', '#FFFFFF', '#FF0000', '#00FF00', '#0000FF', 'transparent']
|
|
});
|
|
syncFromStore();
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.subsection-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.subsection-header h3 {
|
|
margin: 0;
|
|
}
|
|
|
|
.link-button {
|
|
background: none;
|
|
border: 1px solid var(--color-border, #ddd);
|
|
border-radius: 4px;
|
|
padding: 0.25rem;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: all 0.2s;
|
|
width: 24px;
|
|
height: 24px;
|
|
}
|
|
|
|
.link-button svg {
|
|
width: 16px;
|
|
height: 16px;
|
|
color: var(--color-text-secondary, #666);
|
|
}
|
|
|
|
.link-button:hover {
|
|
background: var(--color-hover, #f0f0f0);
|
|
}
|
|
|
|
.link-button.active {
|
|
background: var(--color-primary, #007bff);
|
|
border-color: var(--color-primary, #007bff);
|
|
}
|
|
|
|
.link-button.active svg {
|
|
color: white;
|
|
}
|
|
</style>
|