2025-11-24 16:51:55 +01:00
|
|
|
<template>
|
2026-02-26 15:35:45 +01:00
|
|
|
<BasePopup
|
|
|
|
|
ref="basePopup"
|
2025-11-24 16:51:55 +01:00
|
|
|
id="element-popup"
|
2026-02-26 15:35:45 +01:00
|
|
|
:display-css="displayedCss"
|
|
|
|
|
:editable-css="elementCss"
|
|
|
|
|
:popup-width="800"
|
|
|
|
|
:popup-height="600"
|
|
|
|
|
@close="close"
|
|
|
|
|
@css-input="handleCssInput"
|
|
|
|
|
@toggle-inheritance="toggleInheritance"
|
2025-11-24 16:51:55 +01:00
|
|
|
>
|
2026-02-26 15:35:45 +01:00
|
|
|
<template #header-left>
|
|
|
|
|
<span class="element-label">{{ selectorTag }}</span>
|
|
|
|
|
<span class="instance-count">{{ instanceCount }} instances</span>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<template #controls>
|
|
|
|
|
<!-- Font Family -->
|
|
|
|
|
<div class="settings-subsection">
|
|
|
|
|
<div class="field field-font" :class="{ 'field--view-only': inheritanceLocked }">
|
|
|
|
|
<label class="label-with-tooltip" data-css="font-family">Police</label>
|
|
|
|
|
<div class="field-with-option">
|
|
|
|
|
<select v-model="fontFamily.value" :disabled="inheritanceLocked">
|
|
|
|
|
<option v-for="f in fonts" :key="f" :value="f">{{ f }}</option>
|
|
|
|
|
</select>
|
|
|
|
|
<div class="field-checkbox">
|
|
|
|
|
<input type="checkbox" v-model="fontStyle.italic" :disabled="inheritanceLocked" />
|
|
|
|
|
<label class="label-with-tooltip" data-css="font-style">Italique</label>
|
2025-12-11 13:39:23 +01:00
|
|
|
</div>
|
2025-12-05 18:21:54 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-02-26 15:35:45 +01:00
|
|
|
</div>
|
2025-12-05 18:21:54 +01:00
|
|
|
|
2026-02-26 15:35:45 +01:00
|
|
|
<!-- Font Weight -->
|
|
|
|
|
<div class="settings-subsection">
|
|
|
|
|
<div class="field" :class="{ 'field--view-only': inheritanceLocked }">
|
|
|
|
|
<label class="label-with-tooltip" data-css="font-weight">Graisse</label>
|
|
|
|
|
<UnitToggle v-model="fontWeightString" :units="weights" :disabled="inheritanceLocked" />
|
2025-12-05 18:21:54 +01:00
|
|
|
</div>
|
2026-02-26 15:35:45 +01:00
|
|
|
</div>
|
2025-12-05 18:21:54 +01:00
|
|
|
|
2026-02-26 15:35:45 +01:00
|
|
|
<!-- Font Size -->
|
|
|
|
|
<div class="settings-subsection">
|
|
|
|
|
<div class="field" :class="{ 'field--view-only': inheritanceLocked }">
|
|
|
|
|
<label class="label-with-tooltip" data-css="font-size">Taille du texte</label>
|
|
|
|
|
<div class="input-with-unit">
|
|
|
|
|
<NumberInput
|
|
|
|
|
v-model="fontSize.value"
|
|
|
|
|
:min="0"
|
|
|
|
|
:step="1"
|
|
|
|
|
:disabled="inheritanceLocked"
|
|
|
|
|
/>
|
|
|
|
|
<div class="unit-toggle">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
:class="{ active: fontSize.unit === 'px' }"
|
2025-12-05 18:21:54 +01:00
|
|
|
:disabled="inheritanceLocked"
|
2026-02-26 15:35:45 +01:00
|
|
|
@click="updateFontSizeUnit('px')"
|
|
|
|
|
>
|
|
|
|
|
px
|
|
|
|
|
</button>
|
2025-12-05 18:21:54 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-02-26 15:35:45 +01:00
|
|
|
</div>
|
2025-12-05 18:21:54 +01:00
|
|
|
|
2026-02-26 15:35:45 +01:00
|
|
|
<!-- Text Alignment -->
|
|
|
|
|
<div class="settings-subsection">
|
|
|
|
|
<div class="field" :class="{ 'field--view-only': inheritanceLocked }">
|
|
|
|
|
<label class="label-with-tooltip" data-css="text-align">Alignement</label>
|
|
|
|
|
<select v-model="textAlign.value" :disabled="inheritanceLocked">
|
|
|
|
|
<option value="left">Gauche</option>
|
|
|
|
|
<option value="center">Centre</option>
|
|
|
|
|
<option value="right">Droite</option>
|
|
|
|
|
<option value="justify">Justifié</option>
|
|
|
|
|
</select>
|
2025-12-05 18:21:54 +01:00
|
|
|
</div>
|
2026-02-26 15:35:45 +01:00
|
|
|
</div>
|
2025-12-05 18:21:54 +01:00
|
|
|
|
2026-02-26 15:35:45 +01:00
|
|
|
<!-- Color -->
|
|
|
|
|
<div class="settings-subsection">
|
|
|
|
|
<div class="field field-simple" :class="{ 'field--view-only': inheritanceLocked }">
|
|
|
|
|
<label class="label-with-tooltip" data-css="color">Couleur</label>
|
|
|
|
|
<div class="input-with-color">
|
|
|
|
|
<input
|
|
|
|
|
ref="colorInput"
|
|
|
|
|
type="text"
|
|
|
|
|
v-model="color.value"
|
|
|
|
|
:disabled="inheritanceLocked"
|
|
|
|
|
data-coloris
|
|
|
|
|
/>
|
2025-12-05 18:21:54 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-02-26 15:35:45 +01:00
|
|
|
</div>
|
2025-12-05 18:21:54 +01:00
|
|
|
|
2026-02-26 15:35:45 +01:00
|
|
|
<!-- 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.value"
|
|
|
|
|
:disabled="inheritanceLocked"
|
|
|
|
|
data-coloris
|
|
|
|
|
/>
|
2025-12-05 18:21:54 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-02-26 15:35:45 +01:00
|
|
|
</div>
|
2025-12-05 18:21:54 +01:00
|
|
|
|
2026-02-26 15:35:45 +01:00
|
|
|
<!-- Outer Margins -->
|
|
|
|
|
<div class="settings-subsection">
|
|
|
|
|
<div class="field" :class="{ 'field--view-only': inheritanceLocked }">
|
|
|
|
|
<label class="label-with-tooltip" data-css="margin">Marges extérieures</label>
|
|
|
|
|
<div class="input-with-unit">
|
|
|
|
|
<NumberInput
|
|
|
|
|
v-model="marginOuter.value"
|
|
|
|
|
:min="0"
|
|
|
|
|
:step="1"
|
|
|
|
|
:disabled="inheritanceLocked"
|
|
|
|
|
/>
|
|
|
|
|
<div class="unit-toggle">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
:class="{ active: marginOuter.unit === 'mm' }"
|
2025-12-05 18:21:54 +01:00
|
|
|
:disabled="inheritanceLocked"
|
2026-02-26 15:35:45 +01:00
|
|
|
@click="updateMarginOuterUnit('mm')"
|
|
|
|
|
>
|
|
|
|
|
mm
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
:class="{ active: marginOuter.unit === 'px' }"
|
2025-12-05 18:21:54 +01:00
|
|
|
:disabled="inheritanceLocked"
|
2026-02-26 15:35:45 +01:00
|
|
|
@click="updateMarginOuterUnit('px')"
|
|
|
|
|
>
|
|
|
|
|
px
|
|
|
|
|
</button>
|
2025-12-05 18:21:54 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-11-24 16:51:55 +01:00
|
|
|
</div>
|
2025-12-05 18:21:54 +01:00
|
|
|
|
2026-02-26 15:35:45 +01:00
|
|
|
<!-- Inner Margins (Padding) -->
|
|
|
|
|
<div class="settings-subsection">
|
|
|
|
|
<div class="field" :class="{ 'field--view-only': inheritanceLocked }">
|
|
|
|
|
<label class="label-with-tooltip" data-css="padding">Marges intérieures</label>
|
|
|
|
|
<div class="input-with-unit">
|
|
|
|
|
<NumberInput
|
|
|
|
|
v-model="paddingInner.value"
|
|
|
|
|
:min="0"
|
|
|
|
|
:step="1"
|
2025-12-05 18:21:54 +01:00
|
|
|
:disabled="inheritanceLocked"
|
|
|
|
|
/>
|
2026-02-26 15:35:45 +01:00
|
|
|
<div class="unit-toggle">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
:class="{ active: paddingInner.unit === 'mm' }"
|
|
|
|
|
:disabled="inheritanceLocked"
|
|
|
|
|
@click="updatePaddingInnerUnit('mm')"
|
|
|
|
|
>
|
|
|
|
|
mm
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
:class="{ active: paddingInner.unit === 'px' }"
|
|
|
|
|
:disabled="inheritanceLocked"
|
|
|
|
|
@click="updatePaddingInnerUnit('px')"
|
|
|
|
|
>
|
|
|
|
|
px
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-12-05 18:21:54 +01:00
|
|
|
</div>
|
2025-11-24 16:51:55 +01:00
|
|
|
</div>
|
2026-02-26 15:35:45 +01:00
|
|
|
</template>
|
|
|
|
|
</BasePopup>
|
2025-11-24 16:51:55 +01:00
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2026-01-09 14:31:42 +01:00
|
|
|
import { ref, computed, watch, nextTick } from 'vue';
|
2025-11-24 17:55:42 +01:00
|
|
|
import { useStylesheetStore } from '../stores/stylesheet';
|
2025-12-10 11:51:53 +01:00
|
|
|
import { useDebounce } from '../composables/useDebounce';
|
2025-12-09 17:08:40 +01:00
|
|
|
import NumberInput from './ui/NumberInput.vue';
|
2025-12-11 13:39:23 +01:00
|
|
|
import UnitToggle from './ui/UnitToggle.vue';
|
2026-02-26 15:35:45 +01:00
|
|
|
import BasePopup from './ui/BasePopup.vue';
|
2026-02-24 14:08:30 +01:00
|
|
|
import { convertUnit } from '../utils/unit-conversion';
|
2025-11-24 16:51:55 +01:00
|
|
|
|
2025-11-24 17:55:42 +01:00
|
|
|
const stylesheetStore = useStylesheetStore();
|
|
|
|
|
|
2025-11-24 16:51:55 +01:00
|
|
|
const props = defineProps({
|
2025-12-04 15:55:52 +01:00
|
|
|
iframeRef: Object,
|
2025-11-24 17:55:42 +01:00
|
|
|
});
|
|
|
|
|
|
2025-12-08 14:15:06 +01:00
|
|
|
const emit = defineEmits(['close']);
|
|
|
|
|
|
2026-02-26 15:35:45 +01:00
|
|
|
const basePopup = ref(null);
|
2025-12-05 18:21:54 +01:00
|
|
|
|
2026-02-26 15:35:45 +01:00
|
|
|
const visible = computed(() => basePopup.value?.visible ?? false);
|
|
|
|
|
const inheritanceLocked = computed(() => basePopup.value?.inheritanceLocked ?? true);
|
2025-12-05 18:21:54 +01:00
|
|
|
|
2025-11-24 18:18:27 +01:00
|
|
|
const selector = ref('');
|
2025-12-05 18:21:54 +01:00
|
|
|
const selectedElement = ref(null);
|
2026-02-26 15:35:45 +01:00
|
|
|
const elementInstanceCount = ref(0);
|
2025-12-05 18:21:54 +01:00
|
|
|
const colorInput = ref(null);
|
|
|
|
|
const backgroundInput = ref(null);
|
|
|
|
|
|
|
|
|
|
let isUpdatingFromStore = false;
|
2025-12-10 11:51:53 +01:00
|
|
|
const { debouncedUpdate } = useDebounce(500);
|
2025-12-05 18:21:54 +01:00
|
|
|
|
|
|
|
|
// Style properties
|
|
|
|
|
const fontFamily = ref({ value: 'Alegreya Sans' });
|
|
|
|
|
const fontStyle = ref({ italic: false });
|
|
|
|
|
const fontWeight = ref({ value: 400 });
|
|
|
|
|
const fontSize = ref({ value: 23, unit: 'px' });
|
|
|
|
|
const textAlign = ref({ value: 'left' });
|
|
|
|
|
const color = ref({ value: 'rgb(0, 0, 0)' });
|
|
|
|
|
const background = ref({ value: 'transparent' });
|
|
|
|
|
const marginOuter = ref({ value: 0, unit: 'mm' });
|
|
|
|
|
const paddingInner = ref({ value: 0, unit: 'mm' });
|
|
|
|
|
|
2025-12-11 13:39:23 +01:00
|
|
|
// Constants
|
|
|
|
|
const fonts = ['Alegreya Sans', 'Alegreya', 'Arial', 'Georgia', 'Times New Roman'];
|
|
|
|
|
const weights = ['200', '300', '400', '600', '800'];
|
|
|
|
|
|
|
|
|
|
// Computed to adapt fontWeight for UnitToggle
|
|
|
|
|
const fontWeightString = computed({
|
|
|
|
|
get: () => String(fontWeight.value.value),
|
|
|
|
|
set: (val) => {
|
|
|
|
|
fontWeight.value.value = parseInt(val);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-12-05 18:21:54 +01:00
|
|
|
const immediateUpdate = (callback) => {
|
|
|
|
|
callback();
|
2025-11-24 18:18:27 +01:00
|
|
|
};
|
|
|
|
|
|
2026-02-24 14:08:30 +01:00
|
|
|
const updateFontSizeUnit = (newUnit) => {
|
|
|
|
|
fontSize.value.value = convertUnit(fontSize.value.value, fontSize.value.unit, newUnit);
|
|
|
|
|
fontSize.value.unit = newUnit;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateMarginOuterUnit = (newUnit) => {
|
|
|
|
|
marginOuter.value.value = convertUnit(marginOuter.value.value, marginOuter.value.unit, newUnit);
|
|
|
|
|
marginOuter.value.unit = newUnit;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updatePaddingInnerUnit = (newUnit) => {
|
|
|
|
|
paddingInner.value.value = convertUnit(paddingInner.value.value, paddingInner.value.unit, newUnit);
|
|
|
|
|
paddingInner.value.unit = newUnit;
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-05 18:21:54 +01:00
|
|
|
const getSelectorFromElement = (element) => {
|
|
|
|
|
if (element.id) {
|
|
|
|
|
return `#${element.id}`;
|
|
|
|
|
}
|
2025-11-24 18:18:27 +01:00
|
|
|
|
2025-12-05 18:21:54 +01:00
|
|
|
const tagName = element.tagName.toLowerCase();
|
|
|
|
|
|
2025-12-08 16:35:28 +01:00
|
|
|
const classes = Array.from(element.classList).filter(
|
|
|
|
|
(cls) => !['element-hovered', 'element-selected', 'page-hovered', 'page-selected'].includes(cls)
|
|
|
|
|
);
|
2025-12-05 18:21:54 +01:00
|
|
|
if (classes.length > 0) {
|
|
|
|
|
return `${tagName}.${classes[0]}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return tagName;
|
2025-11-24 18:18:27 +01:00
|
|
|
};
|
|
|
|
|
|
2025-12-05 18:21:54 +01:00
|
|
|
const getInstanceCount = (selector) => {
|
|
|
|
|
if (!props.iframeRef || !props.iframeRef.contentDocument) return 0;
|
2025-11-24 18:18:27 +01:00
|
|
|
|
2025-12-05 18:21:54 +01:00
|
|
|
try {
|
|
|
|
|
const elements = props.iframeRef.contentDocument.querySelectorAll(selector);
|
|
|
|
|
return elements.length;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return 0;
|
2025-11-24 18:18:27 +01:00
|
|
|
}
|
|
|
|
|
};
|
2025-11-24 17:55:42 +01:00
|
|
|
|
2025-12-05 18:21:54 +01:00
|
|
|
const selectorTag = computed(() => {
|
|
|
|
|
return selector.value || '';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const instanceCount = computed(() => {
|
2025-12-08 14:15:06 +01:00
|
|
|
return elementInstanceCount.value;
|
2025-12-05 18:21:54 +01:00
|
|
|
});
|
|
|
|
|
|
2025-11-24 17:55:42 +01:00
|
|
|
const elementCss = computed(() => {
|
2025-11-24 18:18:27 +01:00
|
|
|
if (!selector.value) return '';
|
2025-12-05 18:21:54 +01:00
|
|
|
return stylesheetStore.extractBlock(selector.value) || '';
|
2025-11-24 16:51:55 +01:00
|
|
|
});
|
|
|
|
|
|
2025-12-10 12:11:53 +01:00
|
|
|
// Generate a preview CSS block from current field values
|
|
|
|
|
const generatePreviewCss = () => {
|
|
|
|
|
if (!selector.value) return '';
|
|
|
|
|
|
|
|
|
|
const properties = [];
|
|
|
|
|
|
|
|
|
|
if (fontFamily.value.value) {
|
|
|
|
|
properties.push(` font-family: ${fontFamily.value.value};`);
|
|
|
|
|
}
|
|
|
|
|
if (fontStyle.value.italic) {
|
|
|
|
|
properties.push(` font-style: italic;`);
|
|
|
|
|
}
|
|
|
|
|
if (fontWeight.value.value) {
|
|
|
|
|
properties.push(` font-weight: ${fontWeight.value.value};`);
|
|
|
|
|
}
|
|
|
|
|
if (fontSize.value.value) {
|
|
|
|
|
properties.push(` font-size: ${fontSize.value.value}${fontSize.value.unit};`);
|
|
|
|
|
}
|
|
|
|
|
if (textAlign.value.value) {
|
|
|
|
|
properties.push(` text-align: ${textAlign.value.value};`);
|
|
|
|
|
}
|
2025-12-10 13:14:48 +01:00
|
|
|
if (color.value.value) {
|
2025-12-10 12:11:53 +01:00
|
|
|
properties.push(` color: ${color.value.value};`);
|
|
|
|
|
}
|
2025-12-10 13:14:48 +01:00
|
|
|
if (background.value.value) {
|
2025-12-10 12:11:53 +01:00
|
|
|
properties.push(` background: ${background.value.value};`);
|
|
|
|
|
}
|
2025-12-10 13:14:48 +01:00
|
|
|
if (marginOuter.value.value !== undefined && marginOuter.value.value !== null) {
|
2025-12-10 12:11:53 +01:00
|
|
|
properties.push(` margin: ${marginOuter.value.value}${marginOuter.value.unit};`);
|
|
|
|
|
}
|
2025-12-10 13:14:48 +01:00
|
|
|
if (paddingInner.value.value !== undefined && paddingInner.value.value !== null) {
|
2025-12-10 12:11:53 +01:00
|
|
|
properties.push(` padding: ${paddingInner.value.value}${paddingInner.value.unit};`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (properties.length === 0) return '';
|
|
|
|
|
|
|
|
|
|
return `${selector.value} {\n${properties.join('\n')}\n}`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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 ') +
|
|
|
|
|
' */';
|
|
|
|
|
});
|
|
|
|
|
|
2025-12-05 18:21:54 +01:00
|
|
|
// Update functions for each property
|
|
|
|
|
const updateFontFamily = () => {
|
|
|
|
|
if (!selector.value || !fontFamily.value.value) return;
|
|
|
|
|
stylesheetStore.updateProperty(selector.value, 'font-family', fontFamily.value.value);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateFontStyle = () => {
|
|
|
|
|
if (!selector.value) return;
|
|
|
|
|
stylesheetStore.updateProperty(selector.value, 'font-style', fontStyle.value.italic ? 'italic' : 'normal');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateFontWeight = () => {
|
|
|
|
|
if (!selector.value) return;
|
|
|
|
|
stylesheetStore.updateProperty(selector.value, 'font-weight', fontWeight.value.value);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateFontSize = () => {
|
|
|
|
|
if (!selector.value) return;
|
|
|
|
|
stylesheetStore.updateProperty(selector.value, 'font-size', fontSize.value.value, fontSize.value.unit);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateTextAlign = () => {
|
|
|
|
|
if (!selector.value) return;
|
|
|
|
|
stylesheetStore.updateProperty(selector.value, 'text-align', textAlign.value.value);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateColor = () => {
|
|
|
|
|
if (!selector.value || !color.value.value) return;
|
|
|
|
|
stylesheetStore.updateProperty(selector.value, 'color', color.value.value);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateBackground = () => {
|
|
|
|
|
if (!selector.value || !background.value.value) return;
|
|
|
|
|
stylesheetStore.updateProperty(selector.value, 'background', background.value.value);
|
2025-11-24 17:55:42 +01:00
|
|
|
};
|
2025-11-24 18:18:27 +01:00
|
|
|
|
2025-12-05 18:21:54 +01:00
|
|
|
const updateMarginOuter = () => {
|
|
|
|
|
if (!selector.value) return;
|
|
|
|
|
stylesheetStore.updateProperty(selector.value, 'margin', marginOuter.value.value, marginOuter.value.unit);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updatePaddingInner = () => {
|
|
|
|
|
if (!selector.value) return;
|
|
|
|
|
stylesheetStore.updateProperty(selector.value, 'padding', paddingInner.value.value, paddingInner.value.unit);
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-10 12:11:53 +01:00
|
|
|
// Apply all current field values to create/update the CSS block
|
|
|
|
|
const applyAllStyles = () => {
|
|
|
|
|
if (!selector.value) return;
|
|
|
|
|
|
|
|
|
|
updateFontFamily();
|
|
|
|
|
updateFontStyle();
|
|
|
|
|
updateFontWeight();
|
|
|
|
|
updateFontSize();
|
|
|
|
|
updateTextAlign();
|
|
|
|
|
updateColor();
|
|
|
|
|
updateBackground();
|
|
|
|
|
updateMarginOuter();
|
|
|
|
|
updatePaddingInner();
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-26 15:35:45 +01:00
|
|
|
// Helper to reduce watcher boilerplate
|
|
|
|
|
const watchProp = (getter, updateFn, debounce = false) => {
|
|
|
|
|
watch(getter, () => {
|
|
|
|
|
if (isUpdatingFromStore) return;
|
|
|
|
|
debounce ? debouncedUpdate(updateFn) : immediateUpdate(updateFn);
|
|
|
|
|
});
|
2025-11-24 18:39:29 +01:00
|
|
|
};
|
|
|
|
|
|
2026-02-26 15:35:45 +01:00
|
|
|
watchProp(() => fontFamily.value.value, updateFontFamily);
|
|
|
|
|
watchProp(() => fontStyle.value.italic, updateFontStyle);
|
|
|
|
|
watchProp(() => fontWeight.value.value, updateFontWeight);
|
|
|
|
|
watchProp(() => fontSize.value.value, updateFontSize, true);
|
|
|
|
|
watchProp(() => fontSize.value.unit, updateFontSize);
|
|
|
|
|
watchProp(() => textAlign.value.value, updateTextAlign);
|
|
|
|
|
watchProp(() => color.value.value, updateColor, true);
|
|
|
|
|
watchProp(() => background.value.value, updateBackground, true);
|
|
|
|
|
watchProp(() => marginOuter.value.value, updateMarginOuter, true);
|
|
|
|
|
watchProp(() => marginOuter.value.unit, updateMarginOuter);
|
|
|
|
|
watchProp(() => paddingInner.value.value, updatePaddingInner, true);
|
|
|
|
|
watchProp(() => paddingInner.value.unit, updatePaddingInner);
|
|
|
|
|
|
|
|
|
|
const handleCssInput = (newCss) => {
|
|
|
|
|
const oldBlock = elementCss.value;
|
|
|
|
|
if (oldBlock) {
|
|
|
|
|
stylesheetStore.replaceInCustomCss(oldBlock, newCss);
|
2025-12-05 18:21:54 +01:00
|
|
|
}
|
2026-02-26 15:35:45 +01:00
|
|
|
};
|
2025-12-05 18:21:54 +01:00
|
|
|
|
|
|
|
|
// Watch stylesheet changes to sync values
|
|
|
|
|
watch(
|
2026-01-09 14:35:23 +01:00
|
|
|
() => stylesheetStore.customCss,
|
2025-12-05 18:21:54 +01:00
|
|
|
() => {
|
2026-02-26 15:35:45 +01:00
|
|
|
if (basePopup.value?.visible && !isUpdatingFromStore) {
|
2026-01-09 14:35:23 +01:00
|
|
|
isUpdatingFromStore = true;
|
2025-12-05 18:21:54 +01:00
|
|
|
loadValuesFromStylesheet();
|
2026-01-09 14:35:23 +01:00
|
|
|
nextTick(() => {
|
|
|
|
|
isUpdatingFromStore = false;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Also watch when exiting edit mode
|
|
|
|
|
watch(
|
|
|
|
|
() => stylesheetStore.isEditing,
|
|
|
|
|
(isEditing, wasEditing) => {
|
2026-02-26 15:35:45 +01:00
|
|
|
if (basePopup.value?.visible && wasEditing && !isEditing && !isUpdatingFromStore) {
|
2026-01-09 14:35:23 +01:00
|
|
|
isUpdatingFromStore = true;
|
|
|
|
|
loadValuesFromStylesheet();
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
isUpdatingFromStore = false;
|
|
|
|
|
});
|
2025-12-05 18:21:54 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const loadValuesFromStylesheet = () => {
|
|
|
|
|
if (!selector.value) return;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const fontFamilyData = stylesheetStore.extractValue(selector.value, 'font-family');
|
|
|
|
|
if (fontFamilyData) {
|
|
|
|
|
const value = typeof fontFamilyData === 'string' ? fontFamilyData : fontFamilyData.value;
|
|
|
|
|
fontFamily.value.value = value.replace(/['"]/g, '');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fontStyleData = stylesheetStore.extractValue(selector.value, 'font-style');
|
|
|
|
|
if (fontStyleData) {
|
|
|
|
|
const value = typeof fontStyleData === 'string' ? fontStyleData : fontStyleData.value;
|
|
|
|
|
fontStyle.value.italic = value === 'italic';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fontWeightData = stylesheetStore.extractValue(selector.value, 'font-weight');
|
|
|
|
|
if (fontWeightData) {
|
|
|
|
|
const value = typeof fontWeightData === 'string' ? fontWeightData : fontWeightData.value;
|
|
|
|
|
fontWeight.value.value = parseInt(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fontSizeData = stylesheetStore.extractValue(selector.value, 'font-size');
|
|
|
|
|
if (fontSizeData && fontSizeData.value !== undefined) {
|
|
|
|
|
fontSize.value.value = fontSizeData.value;
|
|
|
|
|
fontSize.value.unit = fontSizeData.unit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const textAlignData = stylesheetStore.extractValue(selector.value, 'text-align');
|
|
|
|
|
if (textAlignData) {
|
|
|
|
|
const value = typeof textAlignData === 'string' ? textAlignData : textAlignData.value;
|
|
|
|
|
textAlign.value.value = value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const colorData = stylesheetStore.extractValue(selector.value, 'color');
|
|
|
|
|
if (colorData) {
|
|
|
|
|
const value = typeof colorData === 'string' ? colorData : colorData.value;
|
|
|
|
|
color.value.value = value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const backgroundData = stylesheetStore.extractValue(selector.value, 'background');
|
|
|
|
|
if (backgroundData) {
|
|
|
|
|
const value = typeof backgroundData === 'string' ? backgroundData : backgroundData.value;
|
|
|
|
|
background.value.value = value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const marginData = stylesheetStore.extractValue(selector.value, 'margin');
|
|
|
|
|
if (marginData && marginData.value !== undefined) {
|
|
|
|
|
marginOuter.value.value = marginData.value;
|
|
|
|
|
marginOuter.value.unit = marginData.unit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const paddingData = stylesheetStore.extractValue(selector.value, 'padding');
|
|
|
|
|
if (paddingData && paddingData.value !== undefined) {
|
|
|
|
|
paddingInner.value.value = paddingData.value;
|
|
|
|
|
paddingInner.value.unit = paddingData.unit;
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error loading values from stylesheet:', error);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-08 14:15:06 +01:00
|
|
|
const open = (element, event, count = null) => {
|
2026-01-09 14:31:42 +01:00
|
|
|
isUpdatingFromStore = true;
|
|
|
|
|
|
2025-12-05 18:21:54 +01:00
|
|
|
selectedElement.value = element;
|
|
|
|
|
selector.value = getSelectorFromElement(element);
|
|
|
|
|
|
2025-12-08 14:15:06 +01:00
|
|
|
// Store instance count if provided, otherwise calculate it
|
|
|
|
|
elementInstanceCount.value = count !== null ? count : getInstanceCount(selector.value);
|
|
|
|
|
|
2026-01-09 14:31:42 +01:00
|
|
|
// Detect inheritance state from CSS block state
|
|
|
|
|
const blockState = stylesheetStore.getBlockState(selector.value);
|
|
|
|
|
|
|
|
|
|
if (blockState === 'active') {
|
2026-02-26 15:35:45 +01:00
|
|
|
basePopup.value.inheritanceLocked = false;
|
2026-01-09 14:31:42 +01:00
|
|
|
} else {
|
2026-02-26 15:35:45 +01:00
|
|
|
basePopup.value.inheritanceLocked = true;
|
2026-01-09 14:31:42 +01:00
|
|
|
}
|
2025-12-10 12:04:58 +01:00
|
|
|
|
2026-01-09 14:31:42 +01:00
|
|
|
// Load values from stylesheet (includes commented blocks)
|
2025-12-05 18:21:54 +01:00
|
|
|
loadValuesFromStylesheet();
|
|
|
|
|
|
2026-02-26 15:35:45 +01:00
|
|
|
// Open popup (sets visible, position, inits Coloris)
|
|
|
|
|
basePopup.value.open(event);
|
2025-12-05 18:21:54 +01:00
|
|
|
|
2026-01-09 14:31:42 +01:00
|
|
|
nextTick(() => {
|
|
|
|
|
isUpdatingFromStore = false;
|
|
|
|
|
});
|
2025-12-05 18:21:54 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const close = () => {
|
2026-02-26 15:35:45 +01:00
|
|
|
basePopup.value?.close();
|
2025-12-05 18:21:54 +01:00
|
|
|
selector.value = '';
|
|
|
|
|
selectedElement.value = null;
|
2025-12-08 14:15:06 +01:00
|
|
|
emit('close');
|
2025-12-05 18:21:54 +01:00
|
|
|
};
|
|
|
|
|
|
2025-12-08 14:15:06 +01:00
|
|
|
const handleIframeClick = (event, targetElement = null, elementCount = null) => {
|
2025-12-08 12:41:14 +01:00
|
|
|
const element = targetElement || event.target;
|
2025-12-05 18:21:54 +01:00
|
|
|
|
|
|
|
|
if (element.tagName === 'BODY' || element.tagName === 'HTML') {
|
|
|
|
|
close();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-26 15:35:45 +01:00
|
|
|
if (basePopup.value?.visible) {
|
2025-12-05 18:21:54 +01:00
|
|
|
close();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-08 14:15:06 +01:00
|
|
|
open(element, event, elementCount);
|
2025-12-05 18:21:54 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const toggleInheritance = () => {
|
2026-01-09 14:31:42 +01:00
|
|
|
const blockState = stylesheetStore.getBlockState(selector.value);
|
|
|
|
|
|
2026-02-26 15:35:45 +01:00
|
|
|
if (basePopup.value.inheritanceLocked && blockState === 'commented') {
|
2026-01-09 14:31:42 +01:00
|
|
|
stylesheetStore.uncommentCssBlock(selector.value);
|
2026-02-26 15:35:45 +01:00
|
|
|
basePopup.value.inheritanceLocked = false;
|
|
|
|
|
} else if (basePopup.value.inheritanceLocked && blockState === 'none') {
|
2026-01-09 14:31:42 +01:00
|
|
|
if (selectedElement.value && props.iframeRef && props.iframeRef.contentWindow) {
|
|
|
|
|
const computed = props.iframeRef.contentWindow.getComputedStyle(selectedElement.value);
|
|
|
|
|
|
|
|
|
|
isUpdatingFromStore = true;
|
|
|
|
|
|
|
|
|
|
fontFamily.value.value = computed.fontFamily.replace(/['"]/g, '').split(',')[0].trim();
|
|
|
|
|
fontStyle.value.italic = computed.fontStyle === 'italic';
|
|
|
|
|
fontWeight.value.value = parseInt(computed.fontWeight);
|
|
|
|
|
|
|
|
|
|
const fontSizeMatch = computed.fontSize.match(/([\d.]+)(px|rem|em|pt)/);
|
|
|
|
|
if (fontSizeMatch) {
|
|
|
|
|
fontSize.value.value = parseFloat(fontSizeMatch[1]);
|
|
|
|
|
fontSize.value.unit = fontSizeMatch[2];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
textAlign.value.value = computed.textAlign;
|
|
|
|
|
color.value.value = computed.color;
|
|
|
|
|
background.value.value = computed.backgroundColor;
|
|
|
|
|
|
|
|
|
|
const marginMatch = computed.marginTop.match(/([\d.]+)(px|mm|pt)/);
|
|
|
|
|
if (marginMatch) {
|
|
|
|
|
marginOuter.value.value = parseFloat(marginMatch[1]);
|
|
|
|
|
marginOuter.value.unit = marginMatch[2];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const paddingMatch = computed.paddingTop.match(/([\d.]+)(px|mm|pt)/);
|
|
|
|
|
if (paddingMatch) {
|
|
|
|
|
paddingInner.value.value = parseFloat(paddingMatch[1]);
|
|
|
|
|
paddingInner.value.unit = paddingMatch[2];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isUpdatingFromStore = false;
|
2025-12-10 12:04:58 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-10 12:11:53 +01:00
|
|
|
applyAllStyles();
|
2026-02-26 15:35:45 +01:00
|
|
|
basePopup.value.inheritanceLocked = false;
|
|
|
|
|
} else if (!basePopup.value.inheritanceLocked && blockState === 'active') {
|
2026-01-09 14:31:42 +01:00
|
|
|
stylesheetStore.commentCssBlock(selector.value);
|
2026-02-26 15:35:45 +01:00
|
|
|
basePopup.value.inheritanceLocked = true;
|
2025-12-10 12:04:58 +01:00
|
|
|
}
|
2025-12-05 18:21:54 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
defineExpose({ handleIframeClick, close, visible });
|
2025-11-24 16:51:55 +01:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
2025-12-08 16:17:34 +01:00
|
|
|
/* ElementPopup-specific styles (purple theme) */
|
2025-12-05 18:21:54 +01:00
|
|
|
.element-label {
|
2025-12-08 14:15:06 +01:00
|
|
|
background: var(--color-purple);
|
2025-12-05 18:21:54 +01:00
|
|
|
color: white;
|
|
|
|
|
padding: 0.25rem 0.5rem;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
font-size: 0.875rem;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.instance-count {
|
2025-12-08 14:15:06 +01:00
|
|
|
color: var(--color-purple);
|
2025-11-24 16:51:55 +01:00
|
|
|
font-size: 0.875rem;
|
|
|
|
|
}
|
|
|
|
|
</style>
|