3 values systeme for heritage
This commit is contained in:
parent
449f0eda31
commit
bc2317ab69
4 changed files with 280 additions and 215 deletions
|
|
@ -481,6 +481,7 @@ input[type=number] {
|
|||
|
||||
.settings-subsection {
|
||||
padding: var(--space-xs) 0;
|
||||
padding-left: 30px;
|
||||
}
|
||||
.settings-subsection h3 {
|
||||
margin-top: calc(var(--space-xs) * 1.5);
|
||||
|
|
|
|||
|
|
@ -346,6 +346,31 @@ const settingCache = reactive({
|
|||
color: null, // string
|
||||
});
|
||||
|
||||
// Persistent state per element selector (survives open/close)
|
||||
const elementStates = new Map();
|
||||
|
||||
const saveElementState = () => {
|
||||
if (!selector.value) return;
|
||||
elementStates.set(selector.value, {
|
||||
toggles: { ...settingEnabled },
|
||||
cache: {
|
||||
font: settingCache.font ? { ...settingCache.font } : null,
|
||||
fontSize: settingCache.fontSize ? { ...settingCache.fontSize } : null,
|
||||
lineHeight: settingCache.lineHeight ? { ...settingCache.lineHeight } : null,
|
||||
color: settingCache.color ?? null,
|
||||
},
|
||||
// Store current values for enabled groups (survives external CSS changes on shared selectors)
|
||||
values: {
|
||||
fontFamily: fontFamily.value,
|
||||
italic: italic.value,
|
||||
bold: bold.value,
|
||||
fontSize: { value: fontSize.value, unit: fontSize.unit },
|
||||
lineHeight: { value: lineHeight.value, unit: lineHeight.unit },
|
||||
color: color.value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Per-subsection toggle state — all unchecked by default
|
||||
// Special groups (font, fontSize, lineHeight, color) appear in popup-css with "valeur par défaut" comment
|
||||
const settingEnabled = reactive({
|
||||
|
|
@ -362,7 +387,7 @@ const settingEnabled = reactive({
|
|||
|
||||
// Style property descriptors (with group field)
|
||||
const styleProps = [
|
||||
{ css: 'font-family', group: 'font', get: () => fontFamily.value === 'sans-serif' ? 'sans-serif' : `"${fontFamily.value}"`, set: v => fontFamily.value = v.replace(/['"]/g, ''), debounce: false },
|
||||
{ css: 'font-family', group: 'font', get: () => fontFamily.value === 'sans-serif' ? 'sans-serif' : `"${fontFamily.value}"`, set: v => fontFamily.value = v.replace(/['"]/g, ''), debounce: false, skipWatch: true },
|
||||
{ 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 },
|
||||
|
|
@ -501,90 +526,74 @@ const elementCss = computed(() => {
|
|||
return stylesheetStore.extractBlock(selector.value) || '';
|
||||
});
|
||||
|
||||
const generatePreviewCss = () => {
|
||||
if (!selector.value) return '';
|
||||
|
||||
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;
|
||||
if (prop.css === 'font-weight' && val === 'normal') continue;
|
||||
if ((prop.css === 'border-style' || prop.css === 'border-color') && borderWidth.value === 0) continue;
|
||||
properties.push(` ${prop.css}: ${val};`);
|
||||
}
|
||||
|
||||
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};`);
|
||||
}
|
||||
}
|
||||
|
||||
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};`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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};`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (properties.length === 0) return '';
|
||||
|
||||
return `${selector.value} {\n${properties.join('\n')}\n}`;
|
||||
};
|
||||
// Canonical property order matching the template settings-subsections
|
||||
const displayedCssOrder = [
|
||||
// font group
|
||||
{ css: 'font-family', group: 'font', special: true,
|
||||
getValue: () => { const val = settingEnabled.font ? fontFamily.value : textDefaults.fontFamily; return val === 'sans-serif' ? 'sans-serif' : `"${val}"`; } },
|
||||
{ css: 'font-style', group: 'font',
|
||||
getValue: () => italic.value ? 'italic' : null,
|
||||
skip: () => !settingEnabled.font || !italic.value },
|
||||
{ css: 'font-weight', group: 'font',
|
||||
getValue: () => bold.value ? 'bold' : null,
|
||||
skip: () => !settingEnabled.font || !bold.value },
|
||||
// fontSize group
|
||||
{ css: 'font-size', group: 'fontSize', special: true,
|
||||
getValue: () => `${(settingEnabled.fontSize ? fontSize : textDefaults.fontSize).value}${(settingEnabled.fontSize ? fontSize : textDefaults.fontSize).unit}` },
|
||||
// lineHeight group
|
||||
{ css: 'line-height', group: 'lineHeight', special: true,
|
||||
getValue: () => `${(settingEnabled.lineHeight ? lineHeight : textDefaults.lineHeight).value}${(settingEnabled.lineHeight ? lineHeight : textDefaults.lineHeight).unit}` },
|
||||
// textAlign group
|
||||
{ css: 'text-align', group: 'textAlign',
|
||||
getValue: () => textAlign.value,
|
||||
skip: () => !settingEnabled.textAlign },
|
||||
// color group
|
||||
{ css: 'color', group: 'color', special: true,
|
||||
getValue: () => settingEnabled.color ? color.value : textDefaults.color },
|
||||
// background group
|
||||
{ css: 'background', group: 'background',
|
||||
getValue: () => background.value,
|
||||
skip: () => !settingEnabled.background },
|
||||
// border group
|
||||
{ css: 'border-width', group: 'border',
|
||||
getValue: () => `${borderWidth.value}${borderWidth.unit}`,
|
||||
skip: () => !settingEnabled.border },
|
||||
{ css: 'border-style', group: 'border',
|
||||
getValue: () => borderStyle.value,
|
||||
skip: () => !settingEnabled.border || borderWidth.value === 0 },
|
||||
{ css: 'border-color', group: 'border',
|
||||
getValue: () => borderColor.value,
|
||||
skip: () => !settingEnabled.border || borderWidth.value === 0 },
|
||||
];
|
||||
|
||||
const displayedCss = computed(() => {
|
||||
if (!selector.value) return '';
|
||||
|
||||
let base = elementCss.value || generatePreviewCss();
|
||||
if (!base) base = `${selector.value} {\n}`;
|
||||
const lines = [];
|
||||
|
||||
// Special groups config for displayedCss annotation
|
||||
const specialDisplayProps = [
|
||||
{ group: 'fontSize', css: 'font-size', getValue: () => `${(settingEnabled.fontSize ? fontSize : textDefaults.fontSize).value}${(settingEnabled.fontSize ? fontSize : textDefaults.fontSize).unit}` },
|
||||
{ group: 'lineHeight', css: 'line-height', getValue: () => `${(settingEnabled.lineHeight ? lineHeight : textDefaults.lineHeight).value}${(settingEnabled.lineHeight ? lineHeight : textDefaults.lineHeight).unit}` },
|
||||
{ group: 'color', css: 'color', getValue: () => settingEnabled.color ? color.value : textDefaults.color },
|
||||
{ group: 'font', css: 'font-family', getValue: () => { const val = settingEnabled.font ? fontFamily.value : textDefaults.fontFamily; return val === 'sans-serif' ? 'sans-serif' : `"${val}"`; } },
|
||||
];
|
||||
for (const entry of displayedCssOrder) {
|
||||
if (entry.skip && entry.skip()) continue;
|
||||
const val = entry.getValue();
|
||||
if (val === null || val === undefined) continue;
|
||||
const comment = (entry.special && !settingEnabled[entry.group]) ? ' /* valeur par défaut */' : '';
|
||||
lines.push(` ${entry.css}: ${val};${comment}`);
|
||||
}
|
||||
|
||||
// For disabled special groups: annotate existing lines with comment
|
||||
// For enabled special groups: remove stale comments
|
||||
for (const sp of specialDisplayProps) {
|
||||
if (!settingEnabled[sp.group] && base.includes(sp.css + ':')) {
|
||||
const escaped = sp.css.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
base = base.replace(new RegExp(`(${escaped}:\\s*[^;]+;)(?!\\s*\\/\\*)`), '$1 /* valeur par défaut */');
|
||||
}
|
||||
if (settingEnabled[sp.group]) {
|
||||
const escaped = sp.css.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
base = base.replace(new RegExp(`(${escaped}:\\s*[^;]+;)\\s*\\/\\* valeur par défaut \\*\\/`), '$1');
|
||||
// margin
|
||||
if (settingEnabled.margin) {
|
||||
for (const side of ['top', 'right', 'bottom', 'left']) {
|
||||
lines.push(` margin-${side}: ${margin[side].value}${margin[side].unit};`);
|
||||
}
|
||||
}
|
||||
|
||||
// Inject missing special properties (if not yet in block at all)
|
||||
const toInject = [];
|
||||
for (const sp of specialDisplayProps) {
|
||||
if (!base.includes(sp.css + ':')) {
|
||||
const comment = !settingEnabled[sp.group] ? ' /* valeur par défaut */' : '';
|
||||
toInject.push(` ${sp.css}: ${sp.getValue()};${comment}`);
|
||||
// padding
|
||||
if (settingEnabled.padding) {
|
||||
for (const side of ['top', 'right', 'bottom', 'left']) {
|
||||
lines.push(` padding-${side}: ${padding[side].value}${padding[side].unit};`);
|
||||
}
|
||||
}
|
||||
|
||||
if (toInject.length > 0) {
|
||||
base = base.replace(/\}\s*$/, toInject.join('\n') + '\n}');
|
||||
}
|
||||
|
||||
return base;
|
||||
return `${selector.value} {\n${lines.join('\n')}\n}`;
|
||||
});
|
||||
|
||||
// Apply all properties for a given group to the stylesheet
|
||||
|
|
@ -652,19 +661,24 @@ const restoreFromCache = (group) => {
|
|||
}
|
||||
};
|
||||
|
||||
// Replace a special group's CSS values with TextSettings defaults
|
||||
const applyDefaultsForGroup = (group) => {
|
||||
// Remove a special group's CSS properties so the element inherits from TextSettings defaults
|
||||
// Then re-write TextSettings values on their target selectors (p/body) to prevent
|
||||
// shared selector conflicts (e.g. popup on `p` removing TextSettings' font-size)
|
||||
const removeSpecialGroupProps = (group) => {
|
||||
if (!selector.value) return;
|
||||
if (group === 'fontSize') {
|
||||
updateProp('font-size', textDefaults.fontSize.value, textDefaults.fontSize.unit);
|
||||
removeProps(['font-size']);
|
||||
stylesheetStore.updateProperty('p', 'font-size', textDefaults.fontSize.value, textDefaults.fontSize.unit);
|
||||
} else if (group === 'lineHeight') {
|
||||
updateProp('line-height', textDefaults.lineHeight.value, textDefaults.lineHeight.unit);
|
||||
removeProps(['line-height']);
|
||||
stylesheetStore.updateProperty('p', 'line-height', textDefaults.lineHeight.value, textDefaults.lineHeight.unit);
|
||||
} else if (group === 'color') {
|
||||
updateProp('color', textDefaults.color);
|
||||
removeProps(['color']);
|
||||
stylesheetStore.updateProperty('body', 'color', textDefaults.color);
|
||||
} else if (group === 'font') {
|
||||
removeProps(['font-family', 'font-style', 'font-weight']);
|
||||
const fontVal = textDefaults.fontFamily === 'sans-serif' ? 'sans-serif' : `"${textDefaults.fontFamily}"`;
|
||||
updateProp('font-family', fontVal);
|
||||
removeProps(['font-style', 'font-weight']);
|
||||
stylesheetStore.updateProperty('body', 'font-family', fontVal);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -679,41 +693,47 @@ const onToggleSetting = (group, enabled) => {
|
|||
saveToCache(group);
|
||||
const specialGroups = ['font', 'fontSize', 'lineHeight', 'color'];
|
||||
if (specialGroups.includes(group)) {
|
||||
applyDefaultsForGroup(group);
|
||||
removeSpecialGroupProps(group);
|
||||
} else {
|
||||
removeProps(settingGroups[group]);
|
||||
}
|
||||
}
|
||||
saveElementState();
|
||||
nextTick(() => { isUpdatingFromStore = false; });
|
||||
};
|
||||
|
||||
// Load font when fontFamily changes
|
||||
// Load font when fontFamily changes, then update CSS (after font-face is ready)
|
||||
watch(fontFamily, async (val) => {
|
||||
if (val && val !== 'sans-serif') await loadFont(val);
|
||||
if (isUpdatingFromStore) return;
|
||||
if (!settingEnabled.font) return;
|
||||
const cssValue = val === 'sans-serif' ? 'sans-serif' : `"${val}"`;
|
||||
updateProp('font-family', cssValue);
|
||||
});
|
||||
|
||||
// Sync disabled special groups with TextSettings defaults
|
||||
// Sync special groups fields with TextSettings — only when toggle is OFF and no cached element value
|
||||
watch(() => textDefaults.fontFamily, (val) => {
|
||||
if (!settingEnabled.font) fontFamily.value = val;
|
||||
if (!settingEnabled.font && settingCache.font === null) fontFamily.value = val;
|
||||
});
|
||||
watch(() => textDefaults.fontSize, (val) => {
|
||||
if (!settingEnabled.fontSize) {
|
||||
if (!settingEnabled.fontSize && settingCache.fontSize === null) {
|
||||
fontSize.value = val.value;
|
||||
fontSize.unit = val.unit;
|
||||
}
|
||||
}, { deep: true });
|
||||
watch(() => textDefaults.lineHeight, (val) => {
|
||||
if (!settingEnabled.lineHeight) {
|
||||
if (!settingEnabled.lineHeight && settingCache.lineHeight === null) {
|
||||
lineHeight.value = val.value;
|
||||
lineHeight.unit = val.unit;
|
||||
}
|
||||
}, { deep: true });
|
||||
watch(() => textDefaults.color, (val) => {
|
||||
if (!settingEnabled.color) color.value = val;
|
||||
if (!settingEnabled.color && settingCache.color === null) color.value = val;
|
||||
});
|
||||
|
||||
// Watchers — simple props (with group guard)
|
||||
for (const prop of styleProps) {
|
||||
if (prop.skipWatch) continue;
|
||||
watch(prop.get, () => {
|
||||
if (isUpdatingFromStore) return;
|
||||
if (!settingEnabled[prop.group]) return;
|
||||
|
|
@ -792,16 +812,48 @@ const handleCssInput = (newCss) => {
|
|||
}
|
||||
};
|
||||
|
||||
// Watch stylesheet changes to sync values
|
||||
// Re-apply stored enabled properties for all elements with active toggles
|
||||
// Protects popup values when TextSettings writes to shared selectors (e.g. `p`)
|
||||
const reapplyStoredEnabledGroups = () => {
|
||||
const currentSel = basePopup.value?.visible ? selector.value : null;
|
||||
|
||||
for (const [sel, state] of elementStates) {
|
||||
if (!state.toggles || !state.values) continue;
|
||||
if (sel === currentSel) continue; // handled by applyAllEnabledGroups below
|
||||
|
||||
if (state.toggles.font) {
|
||||
const val = state.values.fontFamily === 'sans-serif' ? 'sans-serif' : `"${state.values.fontFamily}"`;
|
||||
stylesheetStore.updateProperty(sel, 'font-family', val);
|
||||
}
|
||||
if (state.toggles.fontSize) {
|
||||
stylesheetStore.updateProperty(sel, 'font-size', state.values.fontSize.value, state.values.fontSize.unit);
|
||||
}
|
||||
if (state.toggles.lineHeight) {
|
||||
stylesheetStore.updateProperty(sel, 'line-height', state.values.lineHeight.value, state.values.lineHeight.unit);
|
||||
}
|
||||
if (state.toggles.color) {
|
||||
stylesheetStore.updateProperty(sel, 'color', state.values.color);
|
||||
}
|
||||
}
|
||||
|
||||
// For visible popup, use live refs
|
||||
if (currentSel) {
|
||||
applyAllEnabledGroups();
|
||||
}
|
||||
};
|
||||
|
||||
// Watch stylesheet changes: re-apply all stored enabled properties
|
||||
// flush: 'sync' ensures this runs BEFORE the preview renderer watcher,
|
||||
// so the corrected CSS is what gets rendered (avoids race condition)
|
||||
watch(
|
||||
() => stylesheetStore.customCss,
|
||||
() => {
|
||||
if (basePopup.value?.visible && !isUpdatingFromStore) {
|
||||
isUpdatingFromStore = true;
|
||||
loadValuesFromStylesheet();
|
||||
nextTick(() => { isUpdatingFromStore = false; });
|
||||
}
|
||||
}
|
||||
if (isUpdatingFromStore) return;
|
||||
isUpdatingFromStore = true;
|
||||
reapplyStoredEnabledGroups();
|
||||
nextTick(() => { isUpdatingFromStore = false; });
|
||||
},
|
||||
{ flush: 'sync' }
|
||||
);
|
||||
|
||||
// Also watch when exiting edit mode
|
||||
|
|
@ -819,30 +871,16 @@ watch(
|
|||
const loadValuesFromStylesheet = (isInitialLoad = false) => {
|
||||
if (!selector.value) return;
|
||||
|
||||
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 + ' :');
|
||||
// During live sync, don't update any refs — the popup's state is the source of truth
|
||||
if (!isInitialLoad) return;
|
||||
|
||||
try {
|
||||
// Simple props
|
||||
// Simple props — read from element's CSS block
|
||||
for (const prop of styleProps) {
|
||||
const data = stylesheetStore.extractValue(selector.value, prop.css);
|
||||
if (data) {
|
||||
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);
|
||||
}
|
||||
const value = typeof data === 'string' ? data : data.value;
|
||||
prop.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -850,94 +888,59 @@ const loadValuesFromStylesheet = (isInitialLoad = false) => {
|
|||
for (const prop of unitProps) {
|
||||
const data = stylesheetStore.extractValue(selector.value, prop.css);
|
||||
if (data && data.value !== undefined) {
|
||||
if (isInCustomCss(prop.css)) groupsFound.add(prop.group);
|
||||
if (isInitialLoad || settingEnabled[prop.group]) {
|
||||
prop.ref.value = data.value;
|
||||
prop.ref.unit = data.unit;
|
||||
}
|
||||
prop.ref.value = data.value;
|
||||
prop.ref.unit = data.unit;
|
||||
}
|
||||
}
|
||||
|
||||
// Margin sides — try individual first, fallback to shorthand
|
||||
const spacingSides = ['top', 'right', 'bottom', 'left'];
|
||||
let anyMarginFound = false;
|
||||
for (const side of spacingSides) {
|
||||
const data = stylesheetStore.extractValue(selector.value, `margin-${side}`);
|
||||
if (data && data.value !== undefined) {
|
||||
margin[side].value = data.value;
|
||||
margin[side].unit = data.unit;
|
||||
if (isInCustomCss(`margin-${side}`)) anyMarginFound = true;
|
||||
}
|
||||
}
|
||||
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;
|
||||
for (const side of spacingSides) {
|
||||
const data = stylesheetStore.extractValue(selector.value, `padding-${side}`);
|
||||
if (data && data.value !== undefined) {
|
||||
padding[side].value = data.value;
|
||||
padding[side].unit = data.unit;
|
||||
if (isInCustomCss(`padding-${side}`)) anyPaddingFound = true;
|
||||
}
|
||||
}
|
||||
if (!anyPaddingFound && isInCustomCss('padding')) {
|
||||
const data = stylesheetStore.extractValue(selector.value, 'padding');
|
||||
|
||||
// Special groups: for disabled groups without cache, load fallback from TextSettings selectors
|
||||
if (!settingEnabled.font && !settingCache.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 (!settingEnabled.fontSize && !settingCache.fontSize) {
|
||||
const data = stylesheetStore.extractValue('p', 'font-size');
|
||||
if (data && data.value !== undefined) {
|
||||
for (const side of spacingSides) {
|
||||
padding[side].value = data.value;
|
||||
padding[side].unit = data.unit;
|
||||
}
|
||||
anyPaddingFound = true;
|
||||
fontSize.value = data.value;
|
||||
fontSize.unit = data.unit;
|
||||
}
|
||||
}
|
||||
if (anyPaddingFound) groupsFound.add('padding');
|
||||
|
||||
// settingEnabled is NEVER modified automatically — only by user toggle clicks
|
||||
|
||||
// Special groups: font, fontSize, color
|
||||
// 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 (!settingEnabled.lineHeight && !settingCache.lineHeight) {
|
||||
const data = stylesheetStore.extractValue('p', 'line-height');
|
||||
if (data && data.value !== undefined) {
|
||||
lineHeight.value = data.value;
|
||||
lineHeight.unit = data.unit;
|
||||
}
|
||||
}
|
||||
|
||||
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('lineHeight')) {
|
||||
const data = stylesheetStore.extractValue('p', 'line-height');
|
||||
if (data && data.value !== undefined) {
|
||||
lineHeight.value = data.value;
|
||||
lineHeight.unit = data.unit;
|
||||
}
|
||||
}
|
||||
|
||||
if (!groupsFound.has('color')) {
|
||||
const c = stylesheetStore.extractValue('body', 'color');
|
||||
if (c) color.value = typeof c === 'string' ? c : c.value;
|
||||
}
|
||||
if (!settingEnabled.color && settingCache.color === null) {
|
||||
const c = stylesheetStore.extractValue('body', 'color');
|
||||
if (c) color.value = typeof c === 'string' ? c : c.value;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
|
|
@ -946,31 +949,72 @@ const loadValuesFromStylesheet = (isInitialLoad = false) => {
|
|||
};
|
||||
|
||||
const open = (element, event, count = null) => {
|
||||
// Reset all toggles to unchecked for each new element
|
||||
for (const group of Object.keys(settingEnabled)) {
|
||||
settingEnabled[group] = false;
|
||||
}
|
||||
|
||||
// Clear cache from any previous element
|
||||
settingCache.font = null;
|
||||
settingCache.fontSize = null;
|
||||
settingCache.lineHeight = null;
|
||||
settingCache.color = null;
|
||||
|
||||
isUpdatingFromStore = true;
|
||||
|
||||
selectedElement.value = element;
|
||||
selector.value = getSelectorFromElement(element);
|
||||
|
||||
elementInstanceCount.value = count !== null ? count : getInstanceCount(selector.value);
|
||||
|
||||
loadValuesFromStylesheet(true);
|
||||
basePopup.value.open(event);
|
||||
// Restore persistent state for this element, or initialize all OFF
|
||||
const stored = elementStates.get(selector.value);
|
||||
if (stored) {
|
||||
Object.assign(settingEnabled, stored.toggles);
|
||||
settingCache.font = stored.cache.font ? { ...stored.cache.font } : null;
|
||||
settingCache.fontSize = stored.cache.fontSize ? { ...stored.cache.fontSize } : null;
|
||||
settingCache.lineHeight = stored.cache.lineHeight ? { ...stored.cache.lineHeight } : null;
|
||||
settingCache.color = stored.cache.color ?? null;
|
||||
} else {
|
||||
for (const group of Object.keys(settingEnabled)) {
|
||||
settingEnabled[group] = false;
|
||||
}
|
||||
settingCache.font = null;
|
||||
settingCache.fontSize = null;
|
||||
settingCache.lineHeight = null;
|
||||
settingCache.color = null;
|
||||
}
|
||||
|
||||
// Load CSS values for initial population
|
||||
loadValuesFromStylesheet(true);
|
||||
|
||||
// Restore stored values: for enabled groups (CSS may have been overwritten by TextSettings)
|
||||
// and for disabled groups with cache (field shows cached user value, grayed out)
|
||||
if (stored && stored.values) {
|
||||
if (settingEnabled.font) {
|
||||
fontFamily.value = stored.values.fontFamily;
|
||||
italic.value = stored.values.italic;
|
||||
bold.value = stored.values.bold;
|
||||
} else if (settingCache.font) {
|
||||
fontFamily.value = settingCache.font.fontFamily;
|
||||
italic.value = settingCache.font.italic;
|
||||
bold.value = settingCache.font.bold;
|
||||
}
|
||||
if (settingEnabled.fontSize) {
|
||||
fontSize.value = stored.values.fontSize.value;
|
||||
fontSize.unit = stored.values.fontSize.unit;
|
||||
} else if (settingCache.fontSize) {
|
||||
fontSize.value = settingCache.fontSize.value;
|
||||
fontSize.unit = settingCache.fontSize.unit;
|
||||
}
|
||||
if (settingEnabled.lineHeight) {
|
||||
lineHeight.value = stored.values.lineHeight.value;
|
||||
lineHeight.unit = stored.values.lineHeight.unit;
|
||||
} else if (settingCache.lineHeight) {
|
||||
lineHeight.value = settingCache.lineHeight.value;
|
||||
lineHeight.unit = settingCache.lineHeight.unit;
|
||||
}
|
||||
if (settingEnabled.color) {
|
||||
color.value = stored.values.color;
|
||||
} else if (settingCache.color !== null) {
|
||||
color.value = settingCache.color;
|
||||
}
|
||||
}
|
||||
|
||||
basePopup.value.open(event);
|
||||
nextTick(() => { isUpdatingFromStore = false; });
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
saveElementState();
|
||||
basePopup.value?.close();
|
||||
selector.value = '';
|
||||
selectedElement.value = null;
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted } from 'vue';
|
||||
import { ref, watch, onMounted, nextTick } from 'vue';
|
||||
import { initColoris } from '../../composables/useColoris';
|
||||
import InputWithUnit from '../ui/InputWithUnit.vue';
|
||||
import { useCssUpdater } from '../../composables/useCssUpdater';
|
||||
|
|
@ -87,67 +87,85 @@ const { debouncedUpdate } = useDebounce(500);
|
|||
const textDefaults = useTextDefaults();
|
||||
const { fonts: projectFonts, loadFont, loadAllFontPreviews } = useProjectFonts();
|
||||
|
||||
// State
|
||||
const font = ref('sans-serif');
|
||||
const fontSize = ref({ value: 16, unit: 'px' });
|
||||
const lineHeight = ref({ value: 20, unit: 'px' });
|
||||
// State — initial values match stylesheet.print.css (overwritten by syncFromStore)
|
||||
const font = ref('DM Sans');
|
||||
const fontSize = ref({ value: 14, unit: 'px' });
|
||||
const lineHeight = ref({ value: 18, unit: 'px' });
|
||||
const color = ref('rgb(0, 0, 0)');
|
||||
const colorInput = ref(null);
|
||||
|
||||
let isUpdatingFromStore = false;
|
||||
// Start true to block immediate watchers from overwriting textDefaults during setup
|
||||
let isUpdatingFromStore = true;
|
||||
|
||||
// Watchers for body styles
|
||||
watch(font, async (val) => {
|
||||
if (isUpdatingFromStore) return;
|
||||
textDefaults.fontFamily = val;
|
||||
if (val !== 'sans-serif') await loadFont(val);
|
||||
if (isUpdatingFromStore) return;
|
||||
const cssValue = val === 'sans-serif' ? 'sans-serif' : `"${val}"`;
|
||||
updateStyle('body', 'font-family', cssValue);
|
||||
}, { immediate: true });
|
||||
|
||||
watch(color, (val) => {
|
||||
textDefaults.color = val;
|
||||
if (isUpdatingFromStore) return;
|
||||
textDefaults.color = val;
|
||||
updateStyle('body', 'color', val);
|
||||
}, { immediate: true });
|
||||
|
||||
watch(fontSize, (val) => {
|
||||
textDefaults.fontSize = { value: val.value, unit: val.unit };
|
||||
if (isUpdatingFromStore) return;
|
||||
textDefaults.fontSize = { value: val.value, unit: val.unit };
|
||||
debouncedUpdate(() => {
|
||||
updateStyle('p', 'font-size', `${val.value}${val.unit}`);
|
||||
});
|
||||
}, { deep: true, immediate: true });
|
||||
|
||||
watch(lineHeight, (val) => {
|
||||
textDefaults.lineHeight = { value: val.value, unit: val.unit };
|
||||
if (isUpdatingFromStore) return;
|
||||
textDefaults.lineHeight = { value: val.value, unit: val.unit };
|
||||
debouncedUpdate(() => {
|
||||
updateStyle('p', 'line-height', `${val.value}${val.unit}`);
|
||||
});
|
||||
}, { deep: true, immediate: true });
|
||||
|
||||
|
||||
// Sync from store
|
||||
// Sync from store (first mount) or from textDefaults (subsequent mounts)
|
||||
const syncFromStore = () => {
|
||||
isUpdatingFromStore = true;
|
||||
|
||||
const fontVal = extractValue('body', 'font-family');
|
||||
if (fontVal) {
|
||||
const cleaned = fontVal.replace(/['"]/g, '').trim();
|
||||
font.value = cleaned || 'sans-serif';
|
||||
if (textDefaults._initialized) {
|
||||
// Already initialized — restore from textDefaults (not from CSS which may contain popup values)
|
||||
font.value = textDefaults.fontFamily;
|
||||
fontSize.value = { value: textDefaults.fontSize.value, unit: textDefaults.fontSize.unit };
|
||||
lineHeight.value = { value: textDefaults.lineHeight.value, unit: textDefaults.lineHeight.unit };
|
||||
color.value = textDefaults.color;
|
||||
} else {
|
||||
// First mount — read from CSS store
|
||||
const fontVal = extractValue('body', 'font-family');
|
||||
if (fontVal) {
|
||||
const cleaned = fontVal.replace(/['"]/g, '').trim();
|
||||
font.value = cleaned || 'sans-serif';
|
||||
}
|
||||
|
||||
const colorVal = extractValue('body', 'color');
|
||||
if (colorVal) color.value = colorVal;
|
||||
|
||||
const fontSizeVal = extractNumericValue('p', 'font-size', ['px']);
|
||||
if (fontSizeVal) fontSize.value = fontSizeVal;
|
||||
|
||||
const lineHeightVal = extractNumericValue('p', 'line-height', ['px']);
|
||||
if (lineHeightVal) lineHeight.value = lineHeightVal;
|
||||
|
||||
// Persist to textDefaults from CSS-synced values
|
||||
textDefaults.fontFamily = font.value;
|
||||
textDefaults.fontSize = { value: fontSize.value.value, unit: fontSize.value.unit };
|
||||
textDefaults.lineHeight = { value: lineHeight.value.value, unit: lineHeight.value.unit };
|
||||
textDefaults.color = color.value;
|
||||
textDefaults._initialized = true;
|
||||
}
|
||||
|
||||
const colorVal = extractValue('body', 'color');
|
||||
if (colorVal) color.value = colorVal;
|
||||
|
||||
const fontSizeVal = extractNumericValue('p', 'font-size', ['px']); // ['px', 'em', 'rem']
|
||||
if (fontSizeVal) fontSize.value = fontSizeVal;
|
||||
|
||||
const lineHeightVal = extractNumericValue('p', 'line-height', ['px']);
|
||||
if (lineHeightVal) lineHeight.value = lineHeightVal;
|
||||
|
||||
isUpdatingFromStore = false;
|
||||
// Release flag after watchers triggered by ref changes have been skipped
|
||||
nextTick(() => { isUpdatingFromStore = false; });
|
||||
};
|
||||
|
||||
const updateColorisButtons = () => {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import { reactive } from 'vue';
|
||||
|
||||
// Singleton reactive — TextSettings writes here, ElementPopup reads when disabled
|
||||
// Initial values match stylesheet.print.css (overwritten by syncFromStore on first mount)
|
||||
const defaults = reactive({
|
||||
fontSize: { value: 16, unit: 'px' },
|
||||
lineHeight: { value: 20, unit: 'px' },
|
||||
fontSize: { value: 14, unit: 'px' },
|
||||
lineHeight: { value: 18, unit: 'px' },
|
||||
fontFamily: 'sans-serif',
|
||||
color: 'rgb(0, 0, 0)',
|
||||
_initialized: false,
|
||||
});
|
||||
|
||||
export function useTextDefaults() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue