Compare commits

...

7 commits

Author SHA1 Message Date
Julie Blanc
d88758b226 filed font-size 2025-12-10 13:29:14 +01:00
isUnknown
76274fff04 feat: apply field values when unlocking inheritance
- Unlocking now creates CSS block with current field values
- Locking removes CSS block to restore general styles
- Show commented CSS preview when inheritance is locked
- Preview displays what would be applied if unlocked

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-10 12:11:53 +01:00
isUnknown
d9f3ede661 feat: persist inheritance lock state per element via data attribute
- Store unlock state in data-inheritance-unlocked attribute on DOM element
- Each element/page now remembers its own inheritance state
- Re-locking removes element-specific CSS block to restore inheritance
- Elements revert to general styles from TextSettings/PageSettings

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-10 12:04:58 +01:00
isUnknown
668d950518 fix: auto-create CSS rules when selector or property is missing
updateCssValue now handles three cases:
- Selector doesn't exist: creates the full block with property
- Selector exists but property missing: adds property to block
- Property exists: updates the value

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-10 11:59:09 +01:00
isUnknown
681517db21 refactor: extract debounce logic into shared composable
- Create useDebounce composable to avoid code duplication
- Apply debounce to TextSettings margin/padding inputs
- Harmonize debounce delay to 500ms across all components
- Fix input lag when typing values like "30mm"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-10 11:51:53 +01:00
isUnknown
35c9ab1d3b merge 2025-12-10 11:35:59 +01:00
isUnknown
ceaf318272 improve default styles 2025-12-10 11:35:37 +01:00
17 changed files with 320 additions and 96 deletions

View file

@ -1,10 +1,8 @@
.settings-section {
margin-top: 3em;
margin: var(--space-m) 0;
// .cons
h2 {
@ -12,21 +10,10 @@
font-weight: 600;
font-size: 1.4rem;
// border-radius: var(--border-radius);
// height: var(--input-h);
// padding: 0 1ch;
// display: flex;
// align-items: center;
// color: var(--color-interface-050);
// background-color: var(--color-800);
// display: inline-block;
border-bottom: 1px solid var(--color-200);
color: var(--color-800);
// border-bottom: 2px solid currentColor;
// margin-bottom: 2rem;
}
.infos{

View file

@ -5,7 +5,9 @@ input[type="number"] {
border: 1px solid var(--color-interface-200);
background-color: var(--color-interface-100);
font-family: var(--sans-serif);
color: var(--color-txt);
font-size: 1rem;
padding-left: 0.5ch;
// min-width: var(--input-w);
// width: 100%;
// padding: 0 1ch;
@ -85,10 +87,22 @@ input[type="number"] {
label{
font-weight: 400;
margin-left: 0.75ch;
color: var(--color-txt);
}
}
}
.field-text-size{
input[type="number"]{
width: var(--input-w-small);
padding-left: 0.75ch;
}
input[type="range"]{
flex-grow: 2;
flex-shrink: 2;
}
}
.field-margin, .field-size{
display: inline-grid;
width: calc(50% - 1ch);
@ -196,9 +210,13 @@ input[type="number"] {
top: 0;
button{
height: calc(var(--input-h)*0.6);
height: calc(var(--input-h)*0.5);
cursor: pointer;
padding: 0;
svg{
width: 10px;
height: auto;
}
svg path{
fill: var(--color-interface-600);
}
@ -210,8 +228,9 @@ input[type="number"] {
}
.spinner-down{
svg{
position: relative;
top: -2px;
// position: relative;
// top: -2px;
}
}

View file

@ -25,3 +25,7 @@ button {
background-color: transparent;
border: none;
}
img {
width: 100%;
}

View file

@ -9,7 +9,7 @@
--color-txt: var(--color-interface-800);
--color-txt: var(--color-interface-900);
--color-panel-bg: var(--color-interface-050);
--color-page-highlight: #ff8a50;
@ -20,6 +20,8 @@
--space-xs: 0.5rem;
--space-s: 1rem;
--space: 1.5rem;
--space-m: 2rem;
--space-big: 3em;
--curve: cubic-bezier(0.86, 0, 0.07, 1);
@ -34,4 +36,5 @@
font-size: 14px;
--panel-w: 540px;
--panel-nav-h: 60px;
}

View file

@ -216,10 +216,14 @@ button {
border: none;
}
img {
width: 100%;
}
:root {
--color-browngray-050: #f5f3f0;
--color-browngray-200: #d0c4ba;
--color-txt: var(--color-interface-800);
--color-txt: var(--color-interface-900);
--color-panel-bg: var(--color-interface-050);
--color-page-highlight: #ff8a50;
--color-purple: #7136ff;
@ -227,6 +231,8 @@ button {
--space-xs: 0.5rem;
--space-s: 1rem;
--space: 1.5rem;
--space-m: 2rem;
--space-big: 3em;
--curve: cubic-bezier(0.86, 0, 0.07, 1);
--sans-serif: "DM Sans", sans-serif;
--mono: "Inconsolata", monospace;
@ -236,6 +242,7 @@ button {
--label-w: 18ch;
font-size: 14px;
--panel-w: 540px;
--panel-nav-h: 60px;
}
body {
@ -269,7 +276,9 @@ input[type=number] {
border: 1px solid var(--color-interface-200);
background-color: var(--color-interface-100);
font-family: var(--sans-serif);
color: var(--color-txt);
font-size: 1rem;
padding-left: 0.5ch;
}
.field {
@ -329,6 +338,16 @@ input[type=number] {
.field-font .field-checkbox label {
font-weight: 400;
margin-left: 0.75ch;
color: var(--color-txt);
}
.field-text-size input[type=number] {
width: var(--input-w-small);
padding-left: 0.75ch;
}
.field-text-size input[type=range] {
flex-grow: 2;
flex-shrink: 2;
}
.field-margin, .field-size {
@ -417,23 +436,22 @@ input[type=number] {
top: 0;
}
.number-input .spinner-buttons button {
height: calc(var(--input-h) * 0.6);
height: calc(var(--input-h) * 0.5);
cursor: pointer;
padding: 0;
}
.number-input .spinner-buttons button svg {
width: 10px;
height: auto;
}
.number-input .spinner-buttons button svg path {
fill: var(--color-interface-600);
}
.number-input .spinner-buttons button:hover svg path {
fill: var(--color-interface-900);
}
.number-input .spinner-buttons .spinner-down svg {
position: relative;
top: -2px;
}
.settings-section {
margin-top: 3em;
margin: var(--space-m) 0;
}
.settings-section h2 {
margin-bottom: var(--space);

File diff suppressed because one or more lines are too long

View file

@ -24,5 +24,13 @@ h2 {
p {
font-size: 1rem;
margin: 0mm 0mm 24mm 0mm;
margin: 0mm 0mm 10mm 0mm;
}
img {
width: 100%;
}
li p {
margin-bottom: 0;
}

View file

@ -16,7 +16,7 @@ return array(
'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'),
'League\\ColorExtractor\\' => array($vendorDir . '/league/color-extractor/src'),
'Laminas\\Escaper\\' => array($vendorDir . '/laminas/laminas-escaper/src'),
'Kirby\\' => array($vendorDir . '/getkirby/composer-installer/src', $baseDir . '/kirby/src'),
'Kirby\\' => array($baseDir . '/kirby/src', $vendorDir . '/getkirby/composer-installer/src'),
'Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'),
'Base32\\' => array($vendorDir . '/christian-riesen/base32/src'),
);

View file

@ -96,8 +96,8 @@ class ComposerStaticInit0b7fb803e22a45eb87e24172337208aa
),
'Kirby\\' =>
array (
0 => __DIR__ . '/..' . '/getkirby/composer-installer/src',
1 => __DIR__ . '/../..' . '/kirby/src',
0 => __DIR__ . '/../..' . '/kirby/src',
1 => __DIR__ . '/..' . '/getkirby/composer-installer/src',
),
'Composer\\Semver\\' =>
array (

View file

@ -1,9 +1,9 @@
<?php return array(
'root' => array(
'name' => 'getkirby/plainkit',
'pretty_version' => '5.1.4',
'version' => '5.1.4.0',
'reference' => null,
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => '76274fff04c54514230ad2bb0aca362139618411',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -65,9 +65,9 @@
'dev_requirement' => false,
),
'getkirby/plainkit' => array(
'pretty_version' => '5.1.4',
'version' => '5.1.4.0',
'reference' => null,
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => '76274fff04c54514230ad2bb0aca362139618411',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),

View file

@ -274,6 +274,7 @@
import { ref, computed, watch } from 'vue';
import { useStylesheetStore } from '../stores/stylesheet';
import { usePopupPosition } from '../composables/usePopupPosition';
import { useDebounce } from '../composables/useDebounce';
import NumberInput from './ui/NumberInput.vue';
import Coloris from '@melloware/coloris';
import '@melloware/coloris/dist/coloris.css';
@ -307,7 +308,7 @@ const colorInput = ref(null);
const backgroundInput = ref(null);
let isUpdatingFromStore = false;
let updateTimer = null;
const { debouncedUpdate } = useDebounce(500);
// Style properties
const fontFamily = ref({ value: 'Alegreya Sans' });
@ -320,11 +321,6 @@ const background = ref({ value: 'transparent' });
const marginOuter = ref({ value: 0, unit: 'mm' });
const paddingInner = ref({ value: 0, unit: 'mm' });
const debouncedUpdate = (callback) => {
clearTimeout(updateTimer);
updateTimer = setTimeout(callback, 1000);
};
const immediateUpdate = (callback) => {
callback();
};
@ -373,9 +369,81 @@ const elementCss = computed(() => {
return stylesheetStore.extractBlock(selector.value) || '';
});
// 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};`);
}
if (color.value.value && color.value.value !== 'rgb(0, 0, 0)') {
properties.push(` color: ${color.value.value};`);
}
if (background.value.value && background.value.value !== 'transparent') {
properties.push(` background: ${background.value.value};`);
}
if (marginOuter.value.value) {
properties.push(` margin: ${marginOuter.value.value}${marginOuter.value.unit};`);
}
if (paddingInner.value.value) {
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 unlocked, show the actual CSS block from stylesheet
if (!inheritanceLocked.value) {
return elementCss.value || generatePreviewCss();
}
// If locked, show commented preview of what would be applied
const preview = generatePreviewCss();
if (!preview) return '';
return '/* Héritage verrouillé - déverrouiller pour appliquer */\n/* ' +
preview.split('\n').join('\n ') +
' */';
});
// Remove the element-specific CSS block to restore inheritance
const removeElementBlock = () => {
if (!selector.value) return;
const block = stylesheetStore.extractBlock(selector.value);
if (block) {
// Escape special regex characters in selector
const escaped = selector.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// Remove the block and any surrounding whitespace
stylesheetStore.content = stylesheetStore.content.replace(
new RegExp(`\\n?${escaped}\\s*\\{[^}]*\\}\\n?`),
'\n'
);
}
};
const highlightedCss = computed(() => {
if (!elementCss.value) return '';
return hljs.highlight(elementCss.value, { language: 'css' }).value;
if (!displayedCss.value) return '';
return hljs.highlight(displayedCss.value, { language: 'css' }).value;
});
// Update functions for each property
@ -424,6 +492,21 @@ const updatePaddingInner = () => {
stylesheetStore.updateProperty(selector.value, 'padding', paddingInner.value.value, paddingInner.value.unit);
};
// 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();
};
// Watch for changes
watch(() => fontFamily.value.value, () => {
if (isUpdatingFromStore) return;
@ -605,6 +688,9 @@ const open = (element, event, count = null) => {
// Store instance count if provided, otherwise calculate it
elementInstanceCount.value = count !== null ? count : getInstanceCount(selector.value);
// Read inheritance state from element's data attribute
inheritanceLocked.value = element.dataset.inheritanceUnlocked !== 'true';
// Load values from stylesheet
loadValuesFromStylesheet();
@ -668,8 +754,25 @@ const handleIframeClick = (event, targetElement = null, elementCount = null) =>
};
const toggleInheritance = () => {
const wasLocked = inheritanceLocked.value;
inheritanceLocked.value = !inheritanceLocked.value;
// TODO: Implement CSS priority logic when unlocked
// Store the inheritance state in the element's data attribute
if (selectedElement.value) {
if (inheritanceLocked.value) {
delete selectedElement.value.dataset.inheritanceUnlocked;
} else {
selectedElement.value.dataset.inheritanceUnlocked = 'true';
}
}
if (inheritanceLocked.value && !wasLocked) {
// Re-locking: remove the element-specific CSS block to restore inheritance
removeElementBlock();
} else if (!inheritanceLocked.value && wasLocked) {
// Unlocking: apply all current field values to create the CSS block
applyAllStyles();
}
};
defineExpose({ handleIframeClick, close, visible });

View file

@ -285,6 +285,7 @@
import { ref, computed, watch, onMounted } from 'vue';
import { useStylesheetStore } from '../stores/stylesheet';
import { usePopupPosition } from '../composables/usePopupPosition';
import { useDebounce } from '../composables/useDebounce';
import NumberInput from './ui/NumberInput.vue';
import Coloris from '@melloware/coloris';
import '@melloware/coloris/dist/coloris.css';
@ -312,7 +313,7 @@ const inheritanceLocked = ref(true);
const backgroundColorInput = ref(null);
let isUpdatingFromStore = false;
let updateTimer = null;
const { debouncedUpdate } = useDebounce(500);
const margins = ref({
top: { value: 0, unit: 'mm' },
@ -328,11 +329,6 @@ const background = ref({
const pattern = ref('');
const debouncedUpdate = (callback) => {
clearTimeout(updateTimer);
updateTimer = setTimeout(callback, 1000);
};
const immediateUpdate = (callback) => {
callback();
};
@ -387,9 +383,9 @@ const removeTemplateBlock = () => {
}
};
const updateMargins = () => {
// Only update if inheritance is unlocked
if (inheritanceLocked.value) return;
const updateMargins = (force = false) => {
// Only update if inheritance is unlocked (unless forced)
if (!force && inheritanceLocked.value) return;
const marginValue = `${margins.value.top.value}${margins.value.top.unit} ${margins.value.right.value}${margins.value.right.unit} ${margins.value.bottom.value}${margins.value.bottom.unit} ${margins.value.left.value}${margins.value.left.unit}`;
@ -419,9 +415,9 @@ const updateMargins = () => {
}
};
const updateBackground = () => {
// Only update if inheritance is unlocked
if (inheritanceLocked.value) return;
const updateBackground = (force = false) => {
// Only update if inheritance is unlocked (unless forced)
if (!force && inheritanceLocked.value) return;
if (!background.value.value) return;
const currentBlock = getOrCreateTemplateBlock();
@ -448,6 +444,12 @@ const updateBackground = () => {
}
};
// Apply all current field values to create/update the CSS block
const applyAllStyles = () => {
updateMargins(true);
updateBackground(true);
};
// Watch margin values (number inputs) with debounce
watch(
() => [
@ -536,8 +538,8 @@ const open = (pageElement, event, count = 1) => {
// Extract template name from data-page-type attribute
templateName.value = pageElement.getAttribute('data-page-type') || '';
// Reset inheritance state when opening
inheritanceLocked.value = true;
// Read inheritance state from page element's data attribute
inheritanceLocked.value = pageElement.dataset.inheritanceUnlocked !== 'true';
// Load values from stylesheet (@page block)
loadValuesFromStylesheet();
@ -587,12 +589,22 @@ const toggleInheritance = () => {
const wasLocked = inheritanceLocked.value;
inheritanceLocked.value = !inheritanceLocked.value;
// Store the inheritance state in the page element's data attribute
if (selectedPageElement.value) {
if (inheritanceLocked.value) {
delete selectedPageElement.value.dataset.inheritanceUnlocked;
} else {
selectedPageElement.value.dataset.inheritanceUnlocked = 'true';
}
}
if (inheritanceLocked.value && !wasLocked) {
// Re-locking: remove the template-specific block
// Fields keep their values, but preview returns to @page defaults
removeTemplateBlock();
} else if (!inheritanceLocked.value && wasLocked) {
// Unlocking: apply all current field values to create the CSS block
applyAllStyles();
}
// When unlocking: fields already have values, block will be created on first edit
};
const pageCss = computed(() => {
@ -604,9 +616,47 @@ const pageCss = computed(() => {
return stylesheetStore.extractBlock('@page') || '';
});
// Generate a preview CSS block from current field values
const generatePreviewCss = () => {
if (!templateName.value) return '';
const properties = [];
const marginValue = `${margins.value.top.value}${margins.value.top.unit} ${margins.value.right.value}${margins.value.right.unit} ${margins.value.bottom.value}${margins.value.bottom.unit} ${margins.value.left.value}${margins.value.left.unit}`;
properties.push(` margin: ${marginValue};`);
if (background.value.value) {
properties.push(` background: ${background.value.value};`);
}
if (properties.length === 0) return '';
return `@page ${templateName.value} {\n${properties.join('\n')}\n}`;
};
const displayedCss = computed(() => {
// If unlocked, show the actual CSS block from stylesheet
if (!inheritanceLocked.value) {
return pageCss.value;
}
// If locked, show commented preview of what would be applied
if (!templateName.value) {
// For base @page, just show it normally
return pageCss.value;
}
const preview = generatePreviewCss();
if (!preview) return pageCss.value;
return '/* Héritage verrouillé - déverrouiller pour appliquer */\n/* ' +
preview.split('\n').join('\n ') +
' */';
});
const highlightedCss = computed(() => {
if (!pageCss.value) return '';
return hljs.highlight(pageCss.value, { language: 'css' }).value;
if (!displayedCss.value) return '';
return hljs.highlight(displayedCss.value, { language: 'css' }).value;
});
let cssDebounceTimer = null;
@ -619,6 +669,7 @@ const handleCssInput = (event) => {
}
cssDebounceTimer = setTimeout(() => {
// Get the actual CSS block (not the commented preview)
const oldBlock = pageCss.value;
if (oldBlock) {
stylesheetStore.content = stylesheetStore.content.replace(

View file

@ -119,11 +119,11 @@ nav {
position: relative;
left: calc(var(--panel-w)*-1);
padding: 4rem 0;
background-color: var(--color-panel-bg);
box-shadow: -5px 0px 12px;
transition: left 0.3s var(--curve);
}
@ -132,10 +132,10 @@ nav {
}
.tab-panel {
height: 100%;
height: calc(100% - var(--panel-nav-h)*2);
overflow-y: auto;
overflow-x: hidden;
padding: 0 2em;
// padding-left: 1em;
margin-top: var(--panel-nav-h);
}
</style>

View file

@ -288,16 +288,17 @@
<script setup>
import { ref, computed, watch, onMounted, inject } from 'vue';
import { useStylesheetStore } from '../../stores/stylesheet';
import { useDebounce } from '../../composables/useDebounce';
import Coloris from '@melloware/coloris';
import NumberInput from '../ui/NumberInput.vue';
import '@melloware/coloris/dist/coloris.css';
const stylesheetStore = useStylesheetStore();
const { debouncedUpdate } = useDebounce(500);
const backgroundColorInput = ref(null);
const activeTab = inject('activeTab', ref('document'));
let isUpdatingFromStore = false;
let updateTimer = null;
const pageFormat = ref('A4');
@ -328,11 +329,6 @@ const pattern = ref('');
const pageNumbers = ref(false);
const runningTitle = ref(false);
const debouncedUpdate = (callback) => {
clearTimeout(updateTimer);
updateTimer = setTimeout(callback, 1000);
};
const immediateUpdate = (callback) => {
callback();
};

View file

@ -34,7 +34,7 @@
<!-- Taille du texte -->
<div class="settings-subsection">
<div class="field">
<div class="field field-text-size">
<label for="text-size-range" class="label-with-tooltip" data-css="font-size">Taille du texte</label>
<InputWithUnit
v-model="fontSize"
@ -130,9 +130,11 @@ import InputWithUnit from '../ui/InputWithUnit.vue';
import MarginEditor from '../ui/MarginEditor.vue';
import { useCssUpdater } from '../../composables/useCssUpdater';
import { useCssSync } from '../../composables/useCssSync';
import { useDebounce } from '../../composables/useDebounce';
const { updateStyle, setMargin, setDetailedMargins, setPadding, setDetailedPadding } = useCssUpdater();
const { extractValue, extractNumericValue, extractSpacing } = useCssSync();
const { debouncedUpdate } = useDebounce(500);
// Constants
const fonts = ['Alegreya Sans', 'Arial', 'Georgia', 'Helvetica', 'Times New Roman'];
@ -203,26 +205,32 @@ watch(weight, (val) => {
watch(fontSize, (val) => {
if (isUpdatingFromStore) return;
debouncedUpdate(() => {
updateStyle('p', 'font-size', `${val.value}${val.unit}`);
});
}, { deep: true });
// Margin/Padding handlers
const handleMarginOuterChange = ({ type, simple, detailed }) => {
if (isUpdatingFromStore) return;
debouncedUpdate(() => {
if (type === 'simple') {
setMargin('p', simple.value, simple.unit);
} else {
setDetailedMargins('p', detailed.top, detailed.right, detailed.bottom, detailed.left);
}
});
};
const handleMarginInnerChange = ({ type, simple, detailed }) => {
if (isUpdatingFromStore) return;
debouncedUpdate(() => {
if (type === 'simple') {
setPadding('p', simple.value, simple.unit);
} else {
setDetailedPadding('p', detailed.top, detailed.right, detailed.bottom, detailed.left);
}
});
};
// Sync from store

View file

@ -0,0 +1,15 @@
/**
* Composable for debounced updates
* @param {number} delay - Debounce delay in milliseconds (default: 500ms)
* @returns {Function} debouncedUpdate function
*/
export function useDebounce(delay = 500) {
let timer = null;
const debouncedUpdate = (callback) => {
clearTimeout(timer);
timer = setTimeout(callback, delay);
};
return { debouncedUpdate };
}

View file

@ -16,20 +16,32 @@ const extractCssValue = (css, selector, property) => {
const updateCssValue = ({ css, selector, property, value, unit }) => {
const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const formattedValue = unit === '' || unit === undefined ? value : `${value}${unit}`;
if (unit === '') {
const regex = new RegExp(
`(${escaped}\\s*{[^}]*${property}:\\s*)[^;]+`,
'gi'
);
return css.replace(regex, `$1${value}`);
// Check if selector exists
const selectorRegex = new RegExp(`${escaped}\\s*{([^}]*)}`, 'i');
const selectorMatch = css.match(selectorRegex);
if (!selectorMatch) {
// Selector doesn't exist, create it
return css + `\n${selector} {\n ${property}: ${formattedValue};\n}\n`;
}
const regex = new RegExp(
`(${escaped}\\s*{[^}]*${property}:\\s*)[\\d.]+(px|rem|em|mm|cm|in)`,
'gi'
);
return css.replace(regex, `$1${value}${unit}`);
const blockContent = selectorMatch[1];
// Check if property exists in the block
const propertyRegex = new RegExp(`(${property}\\s*:\\s*)([^;]+)(;?)`, 'i');
const propertyMatch = blockContent.match(propertyRegex);
if (!propertyMatch) {
// Property doesn't exist, add it to the block
const newBlockContent = blockContent.trimEnd() + `\n ${property}: ${formattedValue};`;
return css.replace(selectorRegex, `${selector} {${newBlockContent}}`);
}
// Property exists, replace the entire value
const newBlockContent = blockContent.replace(propertyRegex, `$1${formattedValue}$3`);
return css.replace(selectorRegex, `${selector} {${newBlockContent}}`);
};
const cssParsingUtils = { extractCssBlock, extractCssValue, updateCssValue };