Add admin scroll controls and site interaction fixes

This commit is contained in:
dcconstructions
2026-06-26 18:43:03 +03:00
parent 25e5afedf2
commit fb1531f7c5
17 changed files with 641 additions and 323 deletions
+66
View File
@@ -923,6 +923,72 @@ body[data-file-picker-active="true"] .editor {
align-content: start;
}
.range-field {
gap: 0.46rem;
}
.range-control {
display: flex;
min-height: 2.72rem;
align-items: center;
gap: 0.85rem;
border-radius: var(--launcher-radius-control);
background: var(--field);
padding: 0 0.86rem;
}
.range-control input[type="range"] {
height: 1.5rem;
min-width: 0;
flex: 1 1 auto;
appearance: none;
border: 0;
border-radius: 0;
background: transparent;
padding: 0;
accent-color: #f7f8f4;
}
.range-control input[type="range"]::-webkit-slider-runnable-track {
height: 0.32rem;
border-radius: 999px;
background: rgba(255, 255, 255, 0.16);
}
.range-control input[type="range"]::-webkit-slider-thumb {
width: 1.08rem;
height: 1.08rem;
margin-top: -0.38rem;
appearance: none;
border-radius: 50%;
background: #f7f8f4;
box-shadow: 0 0 0 0.28rem rgba(255, 255, 255, 0.08);
}
.range-control input[type="range"]::-moz-range-track {
height: 0.32rem;
border-radius: 999px;
background: rgba(255, 255, 255, 0.16);
}
.range-control input[type="range"]::-moz-range-thumb {
width: 1.08rem;
height: 1.08rem;
border: 0;
border-radius: 50%;
background: #f7f8f4;
box-shadow: 0 0 0 0.28rem rgba(255, 255, 255, 0.08);
}
.range-value {
min-width: 3.4rem;
color: var(--text-secondary);
font-size: 0.78rem;
font-weight: 850;
text-align: right;
white-space: nowrap;
}
.boolean-field input,
.group-head-toggle input {
width: 2.42rem;
+82 -3
View File
@@ -202,9 +202,12 @@ const GROUP_COPY = {
"Видео-окно": "Окно демо-видео и подключенный медиа-файл.",
Пункты: "Текстовые пункты внутри секции.",
Слайдер: "Видео-превью и подписи карточек внутри горизонтального слайдера.",
Скролл: "Скорость локальной прокрутки внутри блока: меньше значение — спокойнее шаг, больше — быстрее.",
"Один клик": "Подблок про быстрое добавление и настройку компонентов.",
MCP: "Подблок про MCP-сервер и контекст Webflow.",
FAQ: "Список вопросов и тексты ответов в mail-интерфейсе.",
"Окно FAQ": "Название окна FAQ и служебные подписи mail-интерфейса.",
Отправитель: "Профиль отправителя в FAQ: аватар, имя и подписи письма.",
FAQ: "Вопросы и ответы mail-интерфейса. Каждый пункт можно добавить, удалить и переставить.",
CTA: "Финальный призыв к действию: заголовок, пояснение, кнопка и ссылка.",
Тарифы: "Верхняя часть тарифной таблицы: заголовки, планы, цены, кнопки и примечания.",
"Строки тарифов": "Повторяемые строки тарифной таблицы и значения по каждому плану.",
@@ -324,6 +327,7 @@ function inferFieldGroup(path) {
if (path === "content.legalHtml") return "Юридический блок";
if (path === "content.windowTitle" || path === "content.videoSrc") return "Видео-окно";
if (path.startsWith("content.features.")) return "Пункты";
if (path.startsWith("content.scroll.")) return "Скролл";
if (path.startsWith("content.sliderItems.")) return "Слайдер";
if (path.startsWith("content.oneClick.")) return "Один клик";
if (path.startsWith("content.mcp.")) return "MCP";
@@ -1393,8 +1397,19 @@ function prepareCollectionItemForInsert(items, item) {
return nextItem;
}
function fieldInputValue(input) {
if (input.type === "checkbox") return input.checked;
if (input.dataset.valueType === "number" || input.type === "range" || input.type === "number") {
const value = Number.parseFloat(input.value);
return Number.isFinite(value) ? value : 0;
}
return input.value;
}
function syncFieldValue(block, path, input) {
setByPath(block, path, input.type === "checkbox" ? input.checked : input.value);
setByPath(block, path, fieldInputValue(input));
el.json.value = JSON.stringify(block, null, 2);
markDirty();
}
@@ -1431,6 +1446,60 @@ function renderBooleanField({ block, editableField, grid }) {
grid.append(label);
}
function formatRangeValue(value, editableField) {
const step = Number.parseFloat(editableField.step ?? 1);
const precision = Number.isFinite(step) && step > 0 && step < 1 ? String(step).split(".")[1]?.length || 0 : 0;
return `${Number(value).toFixed(precision)}${editableField.suffix || ""}`;
}
function renderRangeField({ block, editableField, grid }) {
const label = document.createElement("label");
const labelRow = document.createElement("span");
const labelText = document.createElement("span");
const kind = document.createElement("span");
const path = document.createElement("span");
const control = document.createElement("span");
const input = document.createElement("input");
const output = document.createElement("span");
const min = Number.parseFloat(editableField.min ?? 0);
const max = Number.parseFloat(editableField.max ?? 1);
const step = Number.parseFloat(editableField.step ?? 0.01);
const rawValue = Number.parseFloat(getByPath(block, editableField.path));
const value = Number.isFinite(rawValue) ? rawValue : min;
label.className = "field-control range-field wide";
labelRow.className = "field-label-row";
labelText.className = "field-label-text";
labelText.textContent = editableField.label || editableField.path;
kind.className = "field-kind";
kind.textContent = editableField.kind || "range";
path.className = "field-path";
path.textContent = editableField.path;
control.className = "range-control";
output.className = "range-value";
labelRow.append(labelText, kind);
input.dataset.path = editableField.path;
input.dataset.valueType = "number";
input.type = "range";
input.min = Number.isFinite(min) ? String(min) : "0";
input.max = Number.isFinite(max) ? String(max) : "1";
input.step = Number.isFinite(step) ? String(step) : "0.01";
input.value = String(value);
output.textContent = formatRangeValue(value, editableField);
input.addEventListener("input", () => {
const active = activeBlock();
if (!active) return;
output.textContent = formatRangeValue(input.value, editableField);
syncFieldValue(active, editableField.path, input);
});
control.append(input, output);
label.append(labelRow, control, path);
grid.append(label);
}
function renderHeaderBooleanToggle({ block, editableField, head }) {
const label = document.createElement("label");
const input = document.createElement("input");
@@ -1458,6 +1527,11 @@ function renderCollectionScalarField({ block, field, grid }) {
return;
}
if (field.kind === "range") {
renderRangeField({ block, editableField: field, grid });
return;
}
const label = document.createElement("label");
const labelRow = document.createElement("span");
const labelText = document.createElement("span");
@@ -1723,6 +1797,11 @@ function renderContentFields(block) {
continue;
}
if (editableField.kind === "range") {
renderRangeField({ block, editableField, grid });
continue;
}
const label = document.createElement("label");
const labelRow = document.createElement("span");
const labelText = document.createElement("span");
@@ -1770,7 +1849,7 @@ function syncContentFields() {
if (!block) return;
for (const input of el.contentFields.querySelectorAll("[data-path]")) {
setByPath(block, input.dataset.path, input.type === "checkbox" ? input.checked : input.value);
setByPath(block, input.dataset.path, fieldInputValue(input));
}
}