Compare commits
2 commits
cc36b73325
...
fa56118e75
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa56118e75 | ||
|
|
203ed18aba |
5 changed files with 407 additions and 664 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -30,3 +30,5 @@ api/.env
|
|||
# Claude settings
|
||||
.claude
|
||||
/.claude/*
|
||||
|
||||
doc-conception.md
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@
|
|||
:editable-css="elementCss"
|
||||
:popup-width="800"
|
||||
:popup-height="600"
|
||||
:show-inheritance="false"
|
||||
@close="close"
|
||||
@css-input="handleCssInput"
|
||||
@toggle-inheritance="toggleInheritance"
|
||||
>
|
||||
<template #header-left>
|
||||
<span class="element-label">{{ selector || '' }}</span>
|
||||
|
|
@ -17,19 +17,20 @@
|
|||
|
||||
<template #controls>
|
||||
<!-- Font Family -->
|
||||
<div class="settings-subsection">
|
||||
<div class="field field-font" :class="{ 'field--view-only': inheritanceLocked }">
|
||||
<div class="settings-subsection" :class="{ 'setting-disabled': !settingEnabled.font }">
|
||||
<input type="checkbox" class="toggle-setting" :checked="settingEnabled.font" @change="onToggleSetting('font', $event.target.checked)" />
|
||||
<div class="field field-font">
|
||||
<label class="label-with-tooltip" data-css="font-family">Police</label>
|
||||
<div class="field-font__options">
|
||||
<select v-model="fontFamily" :disabled="inheritanceLocked">
|
||||
<select v-model="fontFamily">
|
||||
<option v-for="f in fonts" :key="f" :value="f">{{ f }}</option>
|
||||
</select>
|
||||
<div class="field-checkbox">
|
||||
<input type="checkbox" v-model="italic" :disabled="inheritanceLocked" />
|
||||
<input type="checkbox" v-model="italic" />
|
||||
<label class="label-with-tooltip" data-css="font-style">Italique</label>
|
||||
</div>
|
||||
<div class="field-checkbox">
|
||||
<input type="checkbox" v-model="bold" :disabled="inheritanceLocked" />
|
||||
<input type="checkbox" v-model="bold" />
|
||||
<label class="label-with-tooltip" data-css="font-weight">Gras</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -37,62 +38,41 @@
|
|||
</div>
|
||||
|
||||
<!-- Font Size -->
|
||||
<div class="settings-subsection">
|
||||
<div class="field" :class="{ 'field--view-only': inheritanceLocked }">
|
||||
<div class="settings-subsection" :class="{ 'setting-disabled': !settingEnabled.fontSize }">
|
||||
<input type="checkbox" class="toggle-setting" :checked="settingEnabled.fontSize" @change="onToggleSetting('fontSize', $event.target.checked)" />
|
||||
<div class="field field-text-size">
|
||||
<label class="label-with-tooltip" data-css="font-size">Taille du texte</label>
|
||||
<div class="input-with-unit">
|
||||
<NumberInput
|
||||
v-model="fontSize.value"
|
||||
:min="6"
|
||||
:step="1"
|
||||
:disabled="inheritanceLocked"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: fontSize.unit === 'px' }"
|
||||
:disabled="inheritanceLocked"
|
||||
@click="updateUnitPropUnit(fontSize, 'px')"
|
||||
>
|
||||
px
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<InputWithUnit
|
||||
v-model="fontSizeModel"
|
||||
:units="['px']"
|
||||
:min="6"
|
||||
:max="72"
|
||||
showRange
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- LineHeight -->
|
||||
<div class="settings-subsection">
|
||||
<div class="field" :class="{ 'field--view-only': inheritanceLocked }">
|
||||
<div class="settings-subsection" :class="{ 'setting-disabled': !settingEnabled.lineHeight }">
|
||||
<input type="checkbox" class="toggle-setting" :checked="settingEnabled.lineHeight" @change="onToggleSetting('lineHeight', $event.target.checked)" />
|
||||
<div class="field field-text-size">
|
||||
<label class="label-with-tooltip" data-css="line-height">Interlignage</label>
|
||||
<div class="input-with-unit">
|
||||
<NumberInput
|
||||
v-model="lineHeight.value"
|
||||
:min="0"
|
||||
:step="1"
|
||||
:disabled="inheritanceLocked"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: lineHeight.unit === 'px' }"
|
||||
:disabled="inheritanceLocked"
|
||||
@click="updateUnitPropUnit(lineHeight, 'px')"
|
||||
>
|
||||
px
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<InputWithUnit
|
||||
v-model="lineHeightModel"
|
||||
:units="['px']"
|
||||
:min="0"
|
||||
:max="72"
|
||||
showRange
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Text Alignment -->
|
||||
<div class="settings-subsection">
|
||||
<div class="field" :class="{ 'field--view-only': inheritanceLocked }">
|
||||
<div class="settings-subsection" :class="{ 'setting-disabled': !settingEnabled.textAlign }">
|
||||
<input type="checkbox" class="toggle-setting" :checked="settingEnabled.textAlign" @change="onToggleSetting('textAlign', $event.target.checked)" />
|
||||
<div class="field">
|
||||
<label class="label-with-tooltip" data-css="text-align">Alignement</label>
|
||||
<select v-model="textAlign" :disabled="inheritanceLocked">
|
||||
<select v-model="textAlign">
|
||||
<option value="left">Gauche</option>
|
||||
<option value="center">Centre</option>
|
||||
<option value="right">Droite</option>
|
||||
|
|
@ -101,11 +81,44 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Color -->
|
||||
<div class="settings-subsection" :class="{ 'setting-disabled': !settingEnabled.color }">
|
||||
<input type="checkbox" class="toggle-setting" :checked="settingEnabled.color" @change="onToggleSetting('color', $event.target.checked)" />
|
||||
<div class="field field-simple">
|
||||
<label class="label-with-tooltip" data-css="color">Couleur du texte</label>
|
||||
<div class="input-with-color">
|
||||
<input
|
||||
ref="colorInput"
|
||||
type="text"
|
||||
v-model="color"
|
||||
data-coloris
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Background -->
|
||||
<div class="settings-subsection" :class="{ 'setting-disabled': !settingEnabled.background }">
|
||||
<input type="checkbox" class="toggle-setting" :checked="settingEnabled.background" @change="onToggleSetting('background', $event.target.checked)" />
|
||||
<div class="field field-simple">
|
||||
<label class="label-with-tooltip" data-css="background">Arrière-plan</label>
|
||||
<div class="input-with-color">
|
||||
<input
|
||||
ref="backgroundInput"
|
||||
type="text"
|
||||
v-model="background"
|
||||
data-coloris
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bordure -->
|
||||
<div class="settings-subsection">
|
||||
<div class="field field-border" :class="{ 'field--view-only': inheritanceLocked }">
|
||||
<div class="settings-subsection" :class="{ 'setting-disabled': !settingEnabled.border }">
|
||||
<input type="checkbox" class="toggle-setting" :checked="settingEnabled.border" @change="onToggleSetting('border', $event.target.checked)" />
|
||||
<div class="field field-border">
|
||||
<div class="settings-subsection-header">
|
||||
<label class="label-with-tooltip" data-css="border">Bordure</label>
|
||||
<label class="label-with-tooltip" data-css="border">Bordure</label>
|
||||
</div>
|
||||
<div class="field-border__options">
|
||||
<div class="field-border__option">
|
||||
|
|
@ -115,16 +128,15 @@
|
|||
v-model="borderWidth.value"
|
||||
:min="0"
|
||||
:step="1"
|
||||
:disabled="inheritanceLocked"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button type="button" class="active" :disabled="inheritanceLocked">px</button>
|
||||
<button type="button" class="active">px</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-border__option">
|
||||
<label class="label-with-tooltip" data-css="border-style">Style</label>
|
||||
<select v-model="borderStyle" :disabled="inheritanceLocked">
|
||||
<select v-model="borderStyle">
|
||||
<option value="solid">Plein</option>
|
||||
<option value="dotted">Pointillés</option>
|
||||
<option value="dashed">Tirets</option>
|
||||
|
|
@ -138,7 +150,6 @@
|
|||
ref="borderColorInput"
|
||||
type="text"
|
||||
v-model="borderColor"
|
||||
:disabled="inheritanceLocked"
|
||||
data-coloris
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -147,47 +158,15 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Color -->
|
||||
<div class="settings-subsection">
|
||||
<div class="field field-simple" :class="{ 'field--view-only': inheritanceLocked }">
|
||||
<label class="label-with-tooltip" data-css="color">Couleur du texte</label>
|
||||
<div class="input-with-color">
|
||||
<input
|
||||
ref="colorInput"
|
||||
type="text"
|
||||
v-model="color"
|
||||
:disabled="inheritanceLocked"
|
||||
data-coloris
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Background -->
|
||||
<div class="settings-subsection">
|
||||
<div class="field field-simple" :class="{ 'field--view-only': inheritanceLocked }">
|
||||
<label class="label-with-tooltip" data-css="background">Arrière-plan</label>
|
||||
<div class="input-with-color">
|
||||
<input
|
||||
ref="backgroundInput"
|
||||
type="text"
|
||||
v-model="background"
|
||||
:disabled="inheritanceLocked"
|
||||
data-coloris
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Outer Margins -->
|
||||
<div class="settings-subsection">
|
||||
<div class="settings-subsection" :class="{ 'setting-disabled': !settingEnabled.margin }">
|
||||
<input type="checkbox" class="toggle-setting" :checked="settingEnabled.margin" @change="onToggleSetting('margin', $event.target.checked)" />
|
||||
<div class="settings-subsection-header">
|
||||
<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)'"
|
||||
>
|
||||
|
|
@ -205,7 +184,6 @@
|
|||
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">
|
||||
|
|
@ -213,20 +191,17 @@
|
|||
:modelValue="margin[side.key].value"
|
||||
:min="0"
|
||||
:step="1"
|
||||
:disabled="inheritanceLocked"
|
||||
@update:modelValue="(v) => updateMarginValue(side.key, v)"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: margin[side.key].unit === 'mm' }"
|
||||
:disabled="inheritanceLocked"
|
||||
@click="updateMarginUnit(side.key, 'mm')"
|
||||
>mm</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: margin[side.key].unit === 'px' }"
|
||||
:disabled="inheritanceLocked"
|
||||
@click="updateMarginUnit(side.key, 'px')"
|
||||
>px</button>
|
||||
</div>
|
||||
|
|
@ -235,14 +210,14 @@
|
|||
</div>
|
||||
|
||||
<!-- Inner Margins (Padding) -->
|
||||
<div class="settings-subsection">
|
||||
<div class="settings-subsection" :class="{ 'setting-disabled': !settingEnabled.padding }">
|
||||
<input type="checkbox" class="toggle-setting" :checked="settingEnabled.padding" @change="onToggleSetting('padding', $event.target.checked)" />
|
||||
<div class="settings-subsection-header">
|
||||
<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)'"
|
||||
>
|
||||
|
|
@ -260,7 +235,6 @@
|
|||
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">
|
||||
|
|
@ -268,20 +242,17 @@
|
|||
:modelValue="padding[side.key].value"
|
||||
:min="0"
|
||||
:step="1"
|
||||
:disabled="inheritanceLocked"
|
||||
@update:modelValue="(v) => updatePaddingValue(side.key, v)"
|
||||
/>
|
||||
<div class="unit-toggle">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: padding[side.key].unit === 'mm' }"
|
||||
:disabled="inheritanceLocked"
|
||||
@click="updatePaddingUnit(side.key, 'mm')"
|
||||
>mm</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: padding[side.key].unit === 'px' }"
|
||||
:disabled="inheritanceLocked"
|
||||
@click="updatePaddingUnit(side.key, 'px')"
|
||||
>px</button>
|
||||
</div>
|
||||
|
|
@ -296,11 +267,16 @@
|
|||
import { ref, reactive, computed, watch, nextTick } from 'vue';
|
||||
import { useStylesheetStore } from '../stores/stylesheet';
|
||||
import { useDebounce } from '../composables/useDebounce';
|
||||
import { useCssSync } from '../composables/useCssSync';
|
||||
import { useTextDefaults } from '../composables/useTextDefaults';
|
||||
import NumberInput from './ui/NumberInput.vue';
|
||||
import InputWithUnit from './ui/InputWithUnit.vue';
|
||||
import BasePopup from './ui/BasePopup.vue';
|
||||
import { convertUnit } from '../utils/unit-conversion';
|
||||
|
||||
const stylesheetStore = useStylesheetStore();
|
||||
const { extractValue: cssExtractValue, extractNumericValue } = useCssSync();
|
||||
const textDefaults = useTextDefaults();
|
||||
|
||||
const props = defineProps({
|
||||
iframeRef: Object,
|
||||
|
|
@ -311,7 +287,6 @@ const emit = defineEmits(['close']);
|
|||
const basePopup = ref(null);
|
||||
|
||||
const visible = computed(() => basePopup.value?.visible ?? false);
|
||||
const inheritanceLocked = computed(() => basePopup.value?.inheritanceLocked ?? true);
|
||||
|
||||
const selector = ref('');
|
||||
const selectedElement = ref(null);
|
||||
|
|
@ -331,8 +306,22 @@ const textAlign = ref('left');
|
|||
const color = ref('rgb(0, 0, 0)');
|
||||
const background = ref('transparent');
|
||||
const fontSize = reactive({ value: 23, unit: 'px' });
|
||||
const fontSizeModel = computed({
|
||||
get: () => {
|
||||
if (!settingEnabled.fontSize) {
|
||||
// When disabled, display TextSettings' current value reactively
|
||||
return { ...textDefaults.fontSize };
|
||||
}
|
||||
return { value: fontSize.value, unit: fontSize.unit };
|
||||
},
|
||||
set: (v) => { fontSize.value = v.value; fontSize.unit = v.unit; },
|
||||
});
|
||||
const lineHeight = reactive({ value: 28, unit: 'px' });
|
||||
const borderWidth = reactive({ value: 0, unit: 'px' });
|
||||
const lineHeightModel = computed({
|
||||
get: () => ({ value: lineHeight.value, unit: lineHeight.unit }),
|
||||
set: (v) => { lineHeight.value = v.value; lineHeight.unit = v.unit; },
|
||||
});
|
||||
const borderWidth = reactive({ value: 1, unit: 'px' });
|
||||
const borderStyle = ref('solid');
|
||||
const borderColor = ref('#000000');
|
||||
|
||||
|
|
@ -352,25 +341,47 @@ const padding = reactive({
|
|||
left: { value: 0, unit: 'mm' },
|
||||
});
|
||||
|
||||
// Cache for special groups values when unchecked (to restore on re-check)
|
||||
const settingCache = reactive({
|
||||
font: null, // { fontFamily, italic, bold }
|
||||
fontSize: null, // { value, unit }
|
||||
color: null, // string
|
||||
});
|
||||
|
||||
// Per-subsection toggle state
|
||||
// Special groups (font, fontSize, color): checked by default
|
||||
// Other groups: unchecked by default
|
||||
const settingEnabled = reactive({
|
||||
font: true,
|
||||
fontSize: true,
|
||||
lineHeight: false,
|
||||
textAlign: false,
|
||||
color: true,
|
||||
background: false,
|
||||
border: false,
|
||||
margin: false,
|
||||
padding: false,
|
||||
});
|
||||
|
||||
// Constants
|
||||
const fonts = ['Alegreya Sans', 'Alegreya', 'Arial', 'Georgia', 'Times New Roman'];
|
||||
|
||||
// Style property descriptors
|
||||
// Style property descriptors (with group field)
|
||||
const styleProps = [
|
||||
{ css: 'font-family', get: () => fontFamily.value, set: v => fontFamily.value = v.replace(/['"]/g, ''), debounce: false },
|
||||
{ css: 'font-style', get: () => italic.value ? 'italic' : 'normal', set: v => italic.value = v === 'italic', debounce: false },
|
||||
{ css: 'font-weight', get: () => bold.value ? 'bold' : 'normal', set: v => bold.value = v === 'bold' || parseInt(v) >= 700, debounce: false },
|
||||
{ css: 'text-align', get: () => textAlign.value, set: v => textAlign.value = v, debounce: false },
|
||||
{ css: 'color', get: () => color.value, set: v => color.value = v, debounce: true },
|
||||
{ css: 'background', get: () => background.value, set: v => background.value = v, debounce: true },
|
||||
{ css: 'border-style', get: () => borderStyle.value, set: v => borderStyle.value = v || 'solid', debounce: false },
|
||||
{ css: 'border-color', get: () => borderColor.value, set: v => borderColor.value = v, debounce: true },
|
||||
{ css: 'font-family', group: 'font', get: () => fontFamily.value, set: v => fontFamily.value = v.replace(/['"]/g, ''), debounce: false },
|
||||
{ css: 'font-style', group: 'font', get: () => italic.value ? 'italic' : 'normal', set: v => italic.value = v === 'italic', debounce: false, skipWhenDefault: v => v !== 'italic' },
|
||||
{ css: 'font-weight', group: 'font', get: () => bold.value ? 'bold' : 'normal', set: v => bold.value = v === 'bold' || parseInt(v) >= 700, debounce: false, skipWhenDefault: v => v === 'normal' },
|
||||
{ css: 'text-align', group: 'textAlign', get: () => textAlign.value, set: v => textAlign.value = v, debounce: false },
|
||||
{ css: 'color', group: 'color', get: () => color.value, set: v => color.value = v, debounce: true },
|
||||
{ css: 'background', group: 'background', get: () => background.value, set: v => background.value = v, debounce: true },
|
||||
{ css: 'border-style', group: 'border', get: () => borderStyle.value, set: v => borderStyle.value = v || 'solid', debounce: false },
|
||||
{ css: 'border-color', group: 'border', get: () => borderColor.value, set: v => borderColor.value = v, debounce: true },
|
||||
];
|
||||
|
||||
const unitProps = [
|
||||
{ css: 'font-size', ref: fontSize, debounce: true },
|
||||
{ css: 'line-height', ref: lineHeight, debounce: true },
|
||||
{ css: 'border-width', ref: borderWidth, debounce: true },
|
||||
{ css: 'font-size', group: 'fontSize', ref: fontSize, debounce: true },
|
||||
{ css: 'line-height', group: 'lineHeight', ref: lineHeight, debounce: true },
|
||||
{ css: 'border-width', group: 'border', ref: borderWidth, debounce: true },
|
||||
];
|
||||
|
||||
const sides = [
|
||||
|
|
@ -420,17 +431,50 @@ const updatePaddingUnit = (side, unit) => {
|
|||
}
|
||||
};
|
||||
|
||||
// CSS properties covered by each group
|
||||
const settingGroups = {
|
||||
font: ['font-family', 'font-style', 'font-weight'],
|
||||
fontSize: ['font-size'],
|
||||
lineHeight:['line-height'],
|
||||
textAlign: ['text-align'],
|
||||
color: ['color'],
|
||||
background:['background'],
|
||||
border: ['border-width', 'border-style', 'border-color'],
|
||||
margin: ['margin-top', 'margin-right', 'margin-bottom', 'margin-left'],
|
||||
padding: ['padding-top', 'padding-right', 'padding-bottom', 'padding-left'],
|
||||
};
|
||||
|
||||
// Remove CSS properties from the element's custom CSS block
|
||||
const removeProps = (cssProps) => {
|
||||
if (!selector.value) return;
|
||||
|
||||
const block = stylesheetStore.extractBlock(selector.value);
|
||||
if (!block || !stylesheetStore.customCss.includes(block)) return;
|
||||
|
||||
let newBlock = block;
|
||||
for (const prop of cssProps) {
|
||||
const escaped = prop.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
newBlock = newBlock.replace(
|
||||
new RegExp('[ \\t]*' + escaped + '\\s*:[^;]*;[ \\t]*\\n?', 'g'),
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
// If the block is now empty, remove it entirely
|
||||
const inner = newBlock.replace(/^[^{]*\{/, '').replace(/\}[^}]*$/, '');
|
||||
if (!inner.trim()) {
|
||||
stylesheetStore.replaceInCustomCss(block, '');
|
||||
} else {
|
||||
stylesheetStore.replaceBlock(block, newBlock);
|
||||
}
|
||||
};
|
||||
|
||||
// Generic update: push a single property to the stylesheet store
|
||||
const updateProp = (cssProp, value, unit) => {
|
||||
if (!selector.value) return;
|
||||
stylesheetStore.updateProperty(selector.value, cssProp, value, unit);
|
||||
};
|
||||
|
||||
const updateUnitPropUnit = (prop, newUnit) => {
|
||||
prop.value = convertUnit(prop.value, prop.unit, newUnit);
|
||||
prop.unit = newUnit;
|
||||
};
|
||||
|
||||
const getSelectorFromElement = (element) => {
|
||||
if (element.id) {
|
||||
return `#${element.id}`;
|
||||
|
|
@ -469,6 +513,7 @@ const generatePreviewCss = () => {
|
|||
const properties = [];
|
||||
|
||||
for (const prop of styleProps) {
|
||||
if (!settingEnabled[prop.group]) continue;
|
||||
const val = prop.get();
|
||||
if (!val) continue;
|
||||
if (prop.css === 'font-style' && val !== 'italic') continue;
|
||||
|
|
@ -478,19 +523,25 @@ const generatePreviewCss = () => {
|
|||
}
|
||||
|
||||
for (const prop of unitProps) {
|
||||
if (!settingEnabled[prop.group]) continue;
|
||||
if (prop.ref.value !== undefined && prop.ref.value !== null) {
|
||||
properties.push(` ${prop.css}: ${prop.ref.value}${prop.ref.unit};`);
|
||||
}
|
||||
}
|
||||
|
||||
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};`);
|
||||
if (settingEnabled.margin) {
|
||||
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 (settingEnabled.padding) {
|
||||
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};`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -501,51 +552,112 @@ const generatePreviewCss = () => {
|
|||
|
||||
const displayedCss = computed(() => {
|
||||
if (!selector.value) return '';
|
||||
|
||||
if (!inheritanceLocked.value) {
|
||||
return elementCss.value || generatePreviewCss();
|
||||
}
|
||||
|
||||
const preview = generatePreviewCss();
|
||||
if (!preview) return '';
|
||||
|
||||
return '/* Héritage verrouillé - déverrouiller pour appliquer */\n/* ' +
|
||||
preview.split('\n').join('\n ') +
|
||||
' */';
|
||||
return elementCss.value || generatePreviewCss();
|
||||
});
|
||||
|
||||
const applyAllStyles = () => {
|
||||
// Apply all properties for a given group to the stylesheet
|
||||
const applyGroup = (group) => {
|
||||
if (!selector.value) return;
|
||||
|
||||
for (const prop of styleProps) {
|
||||
if (prop.group !== group) continue;
|
||||
if (prop.skipWhenDefault && prop.skipWhenDefault(prop.get())) continue;
|
||||
updateProp(prop.css, prop.get());
|
||||
}
|
||||
for (const prop of unitProps) {
|
||||
updateProp(prop.css, prop.ref.value, prop.ref.unit);
|
||||
if (prop.group === group) 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);
|
||||
if (group === 'margin') {
|
||||
for (const side of ['top', 'right', 'bottom', 'left']) {
|
||||
updateProp(`margin-${side}`, margin[side].value, margin[side].unit);
|
||||
}
|
||||
}
|
||||
if (group === 'padding') {
|
||||
for (const side of ['top', 'right', 'bottom', 'left']) {
|
||||
updateProp(`padding-${side}`, padding[side].value, padding[side].unit);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Watchers — simple props
|
||||
// Apply all currently-enabled groups to keep the CSS block consistent
|
||||
const applyAllEnabledGroups = () => {
|
||||
for (const group of Object.keys(settingEnabled)) {
|
||||
if (settingEnabled[group]) applyGroup(group);
|
||||
}
|
||||
};
|
||||
|
||||
// When unchecking a special group: save element value, load TextSettings fallback for display
|
||||
const saveToCacheAndLoadFallback = (group) => {
|
||||
if (group === 'fontSize') {
|
||||
settingCache.fontSize = { value: fontSize.value, unit: fontSize.unit };
|
||||
// Display is handled reactively by fontSizeModel computed
|
||||
} else if (group === 'font') {
|
||||
settingCache.font = { fontFamily: fontFamily.value, italic: italic.value, bold: bold.value };
|
||||
const ff = cssExtractValue('body', 'font-family');
|
||||
if (ff) fontFamily.value = ff.replace(/['"]/g, '');
|
||||
const fs = cssExtractValue('p', 'font-style');
|
||||
if (fs) italic.value = fs === 'italic';
|
||||
const fw = cssExtractValue('p', 'font-weight');
|
||||
if (fw) bold.value = fw === 'bold' || parseInt(fw) >= 700;
|
||||
} else if (group === 'color') {
|
||||
settingCache.color = color.value;
|
||||
const c = cssExtractValue('body', 'color');
|
||||
if (c) color.value = c;
|
||||
}
|
||||
};
|
||||
|
||||
// When re-checking a special group: restore cached element value
|
||||
const restoreFromCache = (group) => {
|
||||
if (group === 'fontSize' && settingCache.fontSize) {
|
||||
fontSize.value = settingCache.fontSize.value;
|
||||
fontSize.unit = settingCache.fontSize.unit;
|
||||
settingCache.fontSize = null;
|
||||
} else if (group === 'font' && settingCache.font) {
|
||||
fontFamily.value = settingCache.font.fontFamily;
|
||||
italic.value = settingCache.font.italic;
|
||||
bold.value = settingCache.font.bold;
|
||||
settingCache.font = null;
|
||||
} else if (group === 'color' && settingCache.color !== null) {
|
||||
color.value = settingCache.color;
|
||||
settingCache.color = null;
|
||||
}
|
||||
};
|
||||
|
||||
// Toggle a setting group on/off
|
||||
const onToggleSetting = (group, enabled) => {
|
||||
settingEnabled[group] = enabled;
|
||||
if (enabled) {
|
||||
isUpdatingFromStore = true;
|
||||
restoreFromCache(group);
|
||||
isUpdatingFromStore = false;
|
||||
applyAllEnabledGroups();
|
||||
} else {
|
||||
saveToCacheAndLoadFallback(group);
|
||||
removeProps(settingGroups[group]);
|
||||
}
|
||||
};
|
||||
|
||||
// Watchers — simple props (with group guard)
|
||||
for (const prop of styleProps) {
|
||||
watch(prop.get, () => {
|
||||
if (isUpdatingFromStore) return;
|
||||
if (!settingEnabled[prop.group]) return;
|
||||
const fn = () => updateProp(prop.css, prop.get());
|
||||
prop.debounce ? debouncedUpdate(fn) : fn();
|
||||
});
|
||||
}
|
||||
|
||||
// Watchers — unit props (watch both value and unit)
|
||||
// Watchers — unit props (with group guard)
|
||||
for (const prop of unitProps) {
|
||||
watch(() => prop.ref.value, () => {
|
||||
if (isUpdatingFromStore) return;
|
||||
if (!settingEnabled[prop.group]) return;
|
||||
const fn = () => updateProp(prop.css, prop.ref.value, prop.ref.unit);
|
||||
prop.debounce ? debouncedUpdate(fn) : fn();
|
||||
});
|
||||
watch(() => prop.ref.unit, () => {
|
||||
if (isUpdatingFromStore) return;
|
||||
if (!settingEnabled[prop.group]) return;
|
||||
updateProp(prop.css, prop.ref.value, prop.ref.unit);
|
||||
});
|
||||
}
|
||||
|
|
@ -555,6 +667,7 @@ watch(
|
|||
() => [margin.top.value, margin.right.value, margin.bottom.value, margin.left.value],
|
||||
() => {
|
||||
if (isUpdatingFromStore) return;
|
||||
if (!settingEnabled.margin) return;
|
||||
debouncedUpdate(() => {
|
||||
for (const side of ['top', 'right', 'bottom', 'left']) {
|
||||
updateProp(`margin-${side}`, margin[side].value, margin[side].unit);
|
||||
|
|
@ -566,6 +679,7 @@ watch(
|
|||
() => [margin.top.unit, margin.right.unit, margin.bottom.unit, margin.left.unit],
|
||||
() => {
|
||||
if (isUpdatingFromStore) return;
|
||||
if (!settingEnabled.margin) return;
|
||||
for (const side of ['top', 'right', 'bottom', 'left']) {
|
||||
updateProp(`margin-${side}`, margin[side].value, margin[side].unit);
|
||||
}
|
||||
|
|
@ -577,6 +691,7 @@ watch(
|
|||
() => [padding.top.value, padding.right.value, padding.bottom.value, padding.left.value],
|
||||
() => {
|
||||
if (isUpdatingFromStore) return;
|
||||
if (!settingEnabled.padding) return;
|
||||
debouncedUpdate(() => {
|
||||
for (const side of ['top', 'right', 'bottom', 'left']) {
|
||||
updateProp(`padding-${side}`, padding[side].value, padding[side].unit);
|
||||
|
|
@ -588,6 +703,7 @@ watch(
|
|||
() => [padding.top.unit, padding.right.unit, padding.bottom.unit, padding.left.unit],
|
||||
() => {
|
||||
if (isUpdatingFromStore) return;
|
||||
if (!settingEnabled.padding) return;
|
||||
for (const side of ['top', 'right', 'bottom', 'left']) {
|
||||
updateProp(`padding-${side}`, padding[side].value, padding[side].unit);
|
||||
}
|
||||
|
|
@ -625,16 +741,46 @@ watch(
|
|||
}
|
||||
);
|
||||
|
||||
const loadValuesFromStylesheet = () => {
|
||||
const loadValuesFromStylesheet = (isInitialLoad = false) => {
|
||||
if (!selector.value) return;
|
||||
|
||||
if (isInitialLoad) {
|
||||
// Reset settingEnabled to defaults only on initial open
|
||||
settingEnabled.font = true;
|
||||
settingEnabled.fontSize = true;
|
||||
settingEnabled.lineHeight = false;
|
||||
settingEnabled.textAlign = false;
|
||||
settingEnabled.color = true;
|
||||
settingEnabled.background = false;
|
||||
settingEnabled.border = false;
|
||||
settingEnabled.margin = false;
|
||||
settingEnabled.padding = false;
|
||||
}
|
||||
|
||||
const groupsFound = new Set();
|
||||
|
||||
// Only detect settingEnabled from the custom CSS block (not baseCss fallback)
|
||||
const customCssBlock = (() => {
|
||||
const css = stylesheetStore.customCss;
|
||||
if (!css) return '';
|
||||
const escaped = selector.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const match = new RegExp(escaped + '\\s*\\{([^}]*)\\}').exec(css);
|
||||
return match ? match[1] : '';
|
||||
})();
|
||||
const isInCustomCss = (cssProp) =>
|
||||
customCssBlock.includes(cssProp + ':') || customCssBlock.includes(cssProp + ' :');
|
||||
|
||||
try {
|
||||
// Simple props
|
||||
for (const prop of styleProps) {
|
||||
const data = stylesheetStore.extractValue(selector.value, prop.css);
|
||||
if (data) {
|
||||
const value = typeof data === 'string' ? data : data.value;
|
||||
prop.set(value);
|
||||
if (isInCustomCss(prop.css)) groupsFound.add(prop.group);
|
||||
// During live sync, don't overwrite refs for disabled groups (they show cached/fallback values)
|
||||
if (isInitialLoad || settingEnabled[prop.group]) {
|
||||
const value = typeof data === 'string' ? data : data.value;
|
||||
prop.set(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -642,8 +788,11 @@ const loadValuesFromStylesheet = () => {
|
|||
for (const prop of unitProps) {
|
||||
const data = stylesheetStore.extractValue(selector.value, prop.css);
|
||||
if (data && data.value !== undefined) {
|
||||
prop.ref.value = data.value;
|
||||
prop.ref.unit = data.unit;
|
||||
if (isInCustomCss(prop.css)) groupsFound.add(prop.group);
|
||||
if (isInitialLoad || settingEnabled[prop.group]) {
|
||||
prop.ref.value = data.value;
|
||||
prop.ref.unit = data.unit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -655,18 +804,20 @@ const loadValuesFromStylesheet = () => {
|
|||
if (data && data.value !== undefined) {
|
||||
margin[side].value = data.value;
|
||||
margin[side].unit = data.unit;
|
||||
anyMarginFound = true;
|
||||
if (isInCustomCss(`margin-${side}`)) anyMarginFound = true;
|
||||
}
|
||||
}
|
||||
if (!anyMarginFound) {
|
||||
if (!anyMarginFound && isInCustomCss('margin')) {
|
||||
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;
|
||||
}
|
||||
anyMarginFound = true;
|
||||
}
|
||||
}
|
||||
if (anyMarginFound) groupsFound.add('margin');
|
||||
|
||||
// Padding sides — try individual first, fallback to shorthand
|
||||
let anyPaddingFound = false;
|
||||
|
|
@ -675,24 +826,71 @@ const loadValuesFromStylesheet = () => {
|
|||
if (data && data.value !== undefined) {
|
||||
padding[side].value = data.value;
|
||||
padding[side].unit = data.unit;
|
||||
anyPaddingFound = true;
|
||||
if (isInCustomCss(`padding-${side}`)) anyPaddingFound = true;
|
||||
}
|
||||
}
|
||||
if (!anyPaddingFound) {
|
||||
if (!anyPaddingFound && isInCustomCss('padding')) {
|
||||
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;
|
||||
}
|
||||
anyPaddingFound = true;
|
||||
}
|
||||
}
|
||||
if (anyPaddingFound) groupsFound.add('padding');
|
||||
|
||||
// Update settingEnabled based on what was found in the element's CSS
|
||||
if (isInitialLoad) {
|
||||
for (const group of ['lineHeight', 'textAlign', 'background', 'border', 'margin', 'padding']) {
|
||||
settingEnabled[group] = groupsFound.has(group);
|
||||
}
|
||||
} else {
|
||||
// During live sync: only enable groups newly found in CSS, never override user's manual toggles
|
||||
for (const group of Object.keys(settingEnabled)) {
|
||||
if (groupsFound.has(group)) settingEnabled[group] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Special groups: font, fontSize, color — always enabled
|
||||
// If not found in element CSS, load fallback values from TextSettings selectors
|
||||
// Only on initial open — during live sync, ElementPopup is independent from TextSettings
|
||||
if (isInitialLoad) {
|
||||
if (!groupsFound.has('font')) {
|
||||
const ff = stylesheetStore.extractValue('body', 'font-family');
|
||||
if (ff) fontFamily.value = (typeof ff === 'string' ? ff : ff.value).replace(/['"]/g, '');
|
||||
const fs = stylesheetStore.extractValue('p', 'font-style');
|
||||
if (fs) italic.value = (typeof fs === 'string' ? fs : fs.value) === 'italic';
|
||||
const fw = stylesheetStore.extractValue('p', 'font-weight');
|
||||
if (fw) { const v = typeof fw === 'string' ? fw : fw.value; bold.value = v === 'bold' || parseInt(v) >= 700; }
|
||||
}
|
||||
|
||||
if (!groupsFound.has('fontSize')) {
|
||||
const data = stylesheetStore.extractValue('p', 'font-size');
|
||||
if (data && data.value !== undefined) {
|
||||
fontSize.value = data.value;
|
||||
fontSize.unit = data.unit;
|
||||
}
|
||||
}
|
||||
|
||||
if (!groupsFound.has('color')) {
|
||||
const c = stylesheetStore.extractValue('body', 'color');
|
||||
if (c) color.value = typeof c === 'string' ? c : c.value;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading values from stylesheet:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const open = (element, event, count = null) => {
|
||||
// Clear cache from any previous element
|
||||
settingCache.font = null;
|
||||
settingCache.fontSize = null;
|
||||
settingCache.color = null;
|
||||
|
||||
isUpdatingFromStore = true;
|
||||
|
||||
selectedElement.value = element;
|
||||
|
|
@ -700,10 +898,7 @@ const open = (element, event, count = null) => {
|
|||
|
||||
elementInstanceCount.value = count !== null ? count : getInstanceCount(selector.value);
|
||||
|
||||
const blockState = stylesheetStore.getBlockState(selector.value);
|
||||
basePopup.value.inheritanceLocked = blockState !== 'active';
|
||||
|
||||
loadValuesFromStylesheet();
|
||||
loadValuesFromStylesheet(true);
|
||||
basePopup.value.open(event);
|
||||
|
||||
nextTick(() => { isUpdatingFromStore = false; });
|
||||
|
|
@ -732,71 +927,6 @@ const handleIframeClick = (event, targetElement = null, elementCount = null) =>
|
|||
open(element, event, elementCount);
|
||||
};
|
||||
|
||||
const toggleInheritance = () => {
|
||||
const blockState = stylesheetStore.getBlockState(selector.value);
|
||||
|
||||
if (basePopup.value.inheritanceLocked && blockState === 'commented') {
|
||||
stylesheetStore.uncommentCssBlock(selector.value);
|
||||
basePopup.value.inheritanceLocked = false;
|
||||
} else if (basePopup.value.inheritanceLocked && blockState === 'none') {
|
||||
if (selectedElement.value && props.iframeRef && props.iframeRef.contentWindow) {
|
||||
const cs = props.iframeRef.contentWindow.getComputedStyle(selectedElement.value);
|
||||
|
||||
isUpdatingFromStore = true;
|
||||
|
||||
fontFamily.value = cs.fontFamily.replace(/['"]/g, '').split(',')[0].trim();
|
||||
italic.value = cs.fontStyle === 'italic';
|
||||
bold.value = parseInt(cs.fontWeight) >= 700;
|
||||
|
||||
const fontSizeMatch = cs.fontSize.match(/([\d.]+)(px|rem|em|pt)/);
|
||||
if (fontSizeMatch) {
|
||||
fontSize.value = parseFloat(fontSizeMatch[1]);
|
||||
fontSize.unit = fontSizeMatch[2];
|
||||
}
|
||||
|
||||
const lineHeightMatch = cs.lineHeight.match(/([\d.]+)(px|rem|em|pt)/);
|
||||
if (lineHeightMatch) {
|
||||
lineHeight.value = parseFloat(lineHeightMatch[1]);
|
||||
lineHeight.unit = lineHeightMatch[2];
|
||||
}
|
||||
|
||||
textAlign.value = cs.textAlign;
|
||||
color.value = cs.color;
|
||||
background.value = cs.backgroundColor;
|
||||
|
||||
const borderWidthMatch = cs.borderTopWidth.match(/([\d.]+)(px)/);
|
||||
if (borderWidthMatch) {
|
||||
borderWidth.value = parseFloat(borderWidthMatch[1]);
|
||||
borderWidth.unit = 'px';
|
||||
}
|
||||
borderStyle.value = cs.borderTopStyle || 'solid';
|
||||
borderColor.value = cs.borderTopColor || '#000000';
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
applyAllStyles();
|
||||
basePopup.value.inheritanceLocked = false;
|
||||
} else if (!basePopup.value.inheritanceLocked && blockState === 'active') {
|
||||
stylesheetStore.commentCssBlock(selector.value);
|
||||
basePopup.value.inheritanceLocked = true;
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({ handleIframeClick, close, visible });
|
||||
</script>
|
||||
|
||||
|
|
@ -823,6 +953,21 @@ defineExpose({ handleIframeClick, close, visible });
|
|||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
/* Toggle setting checkbox */
|
||||
.toggle-setting {
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
cursor: pointer;
|
||||
accent-color: var(--color-purple, #7c3aed);
|
||||
}
|
||||
|
||||
/* Disabled state: grey out fields but keep checkbox interactive */
|
||||
.setting-disabled .field,
|
||||
.setting-disabled .settings-subsection-header {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.lock-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<section class="settings-section" id="settings-section_elem" data-color-type="elem">
|
||||
<h2>Réglage du texte</h2>
|
||||
<h2>Réglage du texte par défaut</h2>
|
||||
<div class="container">
|
||||
|
||||
<p class="infos">
|
||||
|
|
@ -20,18 +20,14 @@
|
|||
<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 class="field-checkbox">
|
||||
<input id="text-bold" type="checkbox" v-model="bold" />
|
||||
<label for="text-bold" class="label-with-tooltip" data-css="font-weight">Gras</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">
|
||||
|
|
@ -46,18 +42,6 @@
|
|||
</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">
|
||||
|
|
@ -75,296 +59,6 @@
|
|||
</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
|
||||
ref="backgroundInput"
|
||||
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>
|
||||
|
|
@ -373,73 +67,30 @@
|
|||
<script setup>
|
||||
import { ref, watch, onMounted } from 'vue';
|
||||
import { initColoris } from '../../composables/useColoris';
|
||||
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';
|
||||
import { useLinkedSpacing } from '../../composables/useLinkedSpacing';
|
||||
import { useTextDefaults } from '../../composables/useTextDefaults';
|
||||
|
||||
const { updateStyle, setMargin, setDetailedMargins, setPadding, setDetailedPadding } = useCssUpdater();
|
||||
const { extractValue, extractNumericValue, extractSpacing } = useCssSync();
|
||||
const { updateStyle } = useCssUpdater();
|
||||
const { extractValue, extractNumericValue } = useCssSync();
|
||||
const { debouncedUpdate } = useDebounce(500);
|
||||
const textDefaults = useTextDefaults();
|
||||
|
||||
// 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 bold = ref(false);
|
||||
const fontSize = ref({ value: 16, unit: 'px' });
|
||||
const alignment = ref('left');
|
||||
const color = ref('rgb(0, 0, 0)');
|
||||
const background = ref('transparent');
|
||||
const colorInput = ref(null);
|
||||
const backgroundInput = ref(null);
|
||||
|
||||
let isUpdatingFromStore = false;
|
||||
|
||||
const {
|
||||
sides: marginOuterDetailed,
|
||||
linked: marginOuterLinked,
|
||||
updateUnit: updateMarginOuterUnit,
|
||||
setFromSpacing: setMarginOuterFromSpacing,
|
||||
} = useLinkedSpacing({
|
||||
initialValues: {
|
||||
top: { value: 0, unit: 'mm' },
|
||||
right: { value: 0, unit: 'mm' },
|
||||
bottom: { value: 24, unit: 'mm' },
|
||||
left: { value: 0, unit: 'mm' },
|
||||
},
|
||||
isUpdatingFromStore: () => isUpdatingFromStore,
|
||||
debouncedUpdate,
|
||||
onUpdate: (s) => {
|
||||
setDetailedMargins('p', s.top, s.right, s.bottom, s.left);
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
sides: marginInnerDetailed,
|
||||
linked: marginInnerLinked,
|
||||
updateUnit: updateMarginInnerUnit,
|
||||
setFromSpacing: setMarginInnerFromSpacing,
|
||||
} = useLinkedSpacing({
|
||||
isUpdatingFromStore: () => isUpdatingFromStore,
|
||||
debouncedUpdate,
|
||||
onUpdate: (s) => {
|
||||
setDetailedPadding('p', s.top, s.right, s.bottom, s.left);
|
||||
},
|
||||
});
|
||||
|
||||
// Watchers for body styles
|
||||
watch(font, (val) => {
|
||||
if (isUpdatingFromStore) return;
|
||||
|
|
@ -451,33 +102,24 @@ watch(italic, (val) => {
|
|||
updateStyle('p', '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('p', 'background', val);
|
||||
});
|
||||
|
||||
// Watchers for paragraph styles
|
||||
watch(weight, (val) => {
|
||||
watch(bold, (val) => {
|
||||
if (isUpdatingFromStore) return;
|
||||
updateStyle('p', 'font-weight', val);
|
||||
updateStyle('p', 'font-weight', val ? 'bold' : 'normal');
|
||||
});
|
||||
|
||||
watch(fontSize, (val) => {
|
||||
textDefaults.fontSize = { value: val.value, unit: val.unit };
|
||||
if (isUpdatingFromStore) return;
|
||||
debouncedUpdate(() => {
|
||||
updateStyle('p', 'font-size', `${val.value}${val.unit}`);
|
||||
});
|
||||
}, { deep: true });
|
||||
}, { deep: true, immediate: true });
|
||||
|
||||
|
||||
// Sync from store
|
||||
|
|
@ -488,39 +130,23 @@ const syncFromStore = () => {
|
|||
const fontStyle = extractValue('p', '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('p', 'background');
|
||||
if (bgVal) background.value = bgVal;
|
||||
|
||||
// Paragraph styles
|
||||
const fontWeight = extractValue('p', 'font-weight');
|
||||
if (fontWeight) weight.value = fontWeight;
|
||||
if (fontWeight) bold.value = fontWeight === 'bold' || parseInt(fontWeight) >= 700;
|
||||
|
||||
const fontSizeVal = extractNumericValue('p', 'font-size', ['px']); // ['px', 'em', 'rem']
|
||||
if (fontSizeVal) fontSize.value = fontSizeVal;
|
||||
|
||||
// Margins
|
||||
const margins = extractSpacing('p', 'margin');
|
||||
if (margins) setMarginOuterFromSpacing(margins);
|
||||
|
||||
// Padding
|
||||
const padding = extractSpacing('p', 'padding');
|
||||
if (padding) setMarginInnerFromSpacing(padding);
|
||||
|
||||
isUpdatingFromStore = false;
|
||||
};
|
||||
|
||||
const updateColorisButtons = () => {
|
||||
[colorInput.value, backgroundInput.value].forEach((input) => {
|
||||
if (input) {
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
});
|
||||
if (colorInput.value) {
|
||||
colorInput.value.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
|
|
@ -533,48 +159,3 @@ onMounted(() => {
|
|||
});
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
<slot name="controls" />
|
||||
|
||||
<!-- Lock/Unlock Inheritance Button -->
|
||||
<div class="settings-subsection">
|
||||
<div v-if="showInheritance" class="settings-subsection">
|
||||
<button class="inheritance-btn" @click="$emit('toggle-inheritance')">
|
||||
<svg
|
||||
v-if="inheritanceLocked"
|
||||
|
|
@ -54,13 +54,13 @@
|
|||
<span>CSS</span>
|
||||
<label
|
||||
class="toggle"
|
||||
:class="{ 'field--view-only': inheritanceLocked }"
|
||||
:class="{ 'field--view-only': showInheritance && inheritanceLocked }"
|
||||
>
|
||||
<span class="toggle-label">Mode édition</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="isEditable"
|
||||
:disabled="inheritanceLocked"
|
||||
:disabled="showInheritance && inheritanceLocked"
|
||||
/>
|
||||
<span class="toggle-switch"></span>
|
||||
</label>
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
v-else
|
||||
:value="editableCss"
|
||||
@input="handleCssInput"
|
||||
:disabled="inheritanceLocked"
|
||||
:disabled="showInheritance && inheritanceLocked"
|
||||
spellcheck="false"
|
||||
></textarea>
|
||||
</div>
|
||||
|
|
@ -99,6 +99,7 @@ const props = defineProps({
|
|||
editableCss: { type: String, default: '' },
|
||||
popupWidth: { type: Number, default: 800 },
|
||||
popupHeight: { type: Number, default: 600 },
|
||||
showInheritance: { type: Boolean, default: true },
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close', 'css-input', 'toggle-inheritance']);
|
||||
|
|
|
|||
14
src/composables/useTextDefaults.js
Normal file
14
src/composables/useTextDefaults.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { reactive } from 'vue';
|
||||
|
||||
// Singleton reactive — TextSettings writes here, ElementPopup reads when disabled
|
||||
const defaults = reactive({
|
||||
fontSize: { value: 16, unit: 'px' },
|
||||
fontFamily: 'Alegreya Sans',
|
||||
italic: false,
|
||||
bold: false,
|
||||
color: 'rgb(0, 0, 0)',
|
||||
});
|
||||
|
||||
export function useTextDefaults() {
|
||||
return defaults;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue