From 1d8ad6d885f7126c81efb9e418622d73735a6f79 Mon Sep 17 00:00:00 2001 From: jominki354 Date: Sat, 9 May 2026 09:55:19 +0900 Subject: [PATCH 1/3] fx --- selfdrive/carrot/web/css/pages/settings.css | 41 ++++++++++++++++ selfdrive/carrot/web/index.html | 12 +++++ selfdrive/carrot/web/js/pages/setting.js | 18 +++++++ .../carrot/web/js/pages/setting_device.js | 49 +++++++++++++++++++ 4 files changed, 120 insertions(+) diff --git a/selfdrive/carrot/web/css/pages/settings.css b/selfdrive/carrot/web/css/pages/settings.css index d76f565798..8902f34372 100644 --- a/selfdrive/carrot/web/css/pages/settings.css +++ b/selfdrive/carrot/web/css/pages/settings.css @@ -536,10 +536,14 @@ overflow: hidden; opacity: 1; transform: translateY(0); +<<<<<<< HEAD transition: grid-template-rows 0.24s cubic-bezier(.2, 0, 0, 1), opacity 0.16s ease, transform 0.2s cubic-bezier(.2, 0, 0, 1); +======= + will-change: grid-template-rows, opacity, transform; +>>>>>>> f3ee1c57 (fix) } .setting-profile-section.is-collapsed .setting-profile-section__body { @@ -554,6 +558,18 @@ overflow: hidden; } +<<<<<<< HEAD +======= +.setting-profile-section.is-collapsing .setting-profile-section__body { + pointer-events: none; + animation: setting-profile-section-fold 0.24s cubic-bezier(.2, 0, 0, 1) both; +} + +.setting-profile-section.is-expanding .setting-profile-section__body { + animation: setting-profile-section-fold 0.24s cubic-bezier(.2, 0, 0, 1) reverse both; +} + +>>>>>>> f3ee1c57 (fix) .setting-subnav__tab--profile { border-color: color-mix(in srgb, var(--md-primary) 34%, var(--md-outline-var)); } @@ -760,16 +776,41 @@ } } +<<<<<<< HEAD +======= +@keyframes setting-profile-section-fold { + from { + grid-template-rows: 1fr; + opacity: 1; + transform: translateY(0); + } + to { + grid-template-rows: 0fr; + opacity: 0; + transform: translateY(-4px); + } +} + +>>>>>>> f3ee1c57 (fix) @media (prefers-reduced-motion: reduce) { .setting-profile-panel, .page--setting #items > .setting.ui-stagger-item, .page--setting #items > .setting-profile-section.ui-stagger-item, +<<<<<<< HEAD .page--setting #deviceItems > .setting.ui-stagger-item { +======= + .page--setting #deviceItems > .setting.ui-stagger-item, + .setting-profile-section.is-collapsing .setting-profile-section__body, + .setting-profile-section.is-expanding .setting-profile-section__body { +>>>>>>> f3ee1c57 (fix) animation: none; } .setting-profile-section__header, +<<<<<<< HEAD .setting-profile-section__body, +======= +>>>>>>> f3ee1c57 (fix) .setting-profile-section__chevron, .setting-toolbar-action { transition: none; diff --git a/selfdrive/carrot/web/index.html b/selfdrive/carrot/web/index.html index ea72565d79..4d68448b10 100644 --- a/selfdrive/carrot/web/index.html +++ b/selfdrive/carrot/web/index.html @@ -77,7 +77,11 @@ +<<<<<<< HEAD +======= + +>>>>>>> f3ee1c57 (fix) @@ -624,12 +628,20 @@

Home

+<<<<<<< HEAD +======= + +>>>>>>> f3ee1c57 (fix) +<<<<<<< HEAD +======= + +>>>>>>> f3ee1c57 (fix) diff --git a/selfdrive/carrot/web/js/pages/setting.js b/selfdrive/carrot/web/js/pages/setting.js index cbde3d3611..bd7d5744f4 100644 --- a/selfdrive/carrot/web/js/pages/setting.js +++ b/selfdrive/carrot/web/js/pages/setting.js @@ -2153,9 +2153,27 @@ async function renderItems(group, options = {}) { const bodyInner = document.createElement("div"); bodyInner.className = "setting-profile-section__bodyInner"; header.onclick = () => { +<<<<<<< HEAD const nextExpanded = section.classList.toggle("is-collapsed") ? false : true; settingProfileSectionExpandedState.set(stateKey, nextExpanded); header.setAttribute("aria-expanded", nextExpanded ? "true" : "false"); +======= + const wasCollapsed = section.classList.contains("is-collapsed"); + const nextExpanded = wasCollapsed; + section.classList.remove("is-expanding", "is-collapsing"); + if (section.__settingProfileMotionTimer) { + window.clearTimeout(section.__settingProfileMotionTimer); + } + void section.offsetWidth; + section.classList.toggle("is-collapsed", !nextExpanded); + section.classList.add(nextExpanded ? "is-expanding" : "is-collapsing"); + settingProfileSectionExpandedState.set(stateKey, nextExpanded); + header.setAttribute("aria-expanded", nextExpanded ? "true" : "false"); + section.__settingProfileMotionTimer = window.setTimeout(() => { + section.classList.remove("is-expanding", "is-collapsing"); + section.__settingProfileMotionTimer = null; + }, 280); +>>>>>>> f3ee1c57 (fix) }; body.appendChild(bodyInner); section.appendChild(header); diff --git a/selfdrive/carrot/web/js/pages/setting_device.js b/selfdrive/carrot/web/js/pages/setting_device.js index f7a7a42dfb..657e183a32 100644 --- a/selfdrive/carrot/web/js/pages/setting_device.js +++ b/selfdrive/carrot/web/js/pages/setting_device.js @@ -140,17 +140,66 @@ function renderDeviceGroups(options = {}) { const subnavContainer = document.getElementById("deviceSubnav"); if (!groupContainer) return; const animateGroups = options.animateGroups !== false; +<<<<<<< HEAD groupContainer.innerHTML = ""; if (subnavContainer) subnavContainer.innerHTML = ""; +======= +>>>>>>> f3ee1c57 (fix) const visibleGroups = getVisibleDeviceGroups(); if (!visibleGroups.some((group) => group.id === CURRENT_DEVICE_GROUP)) { CURRENT_DEVICE_GROUP = visibleGroups[0]?.id || "Device"; } + const groupEntries = visibleGroups.map((group) => ({ + group, + label: getDeviceGroupLabel(group.id), + })); + const signature = groupEntries.map((entry) => `${entry.group.id}:${entry.label}`).join("|"); +<<<<<<< HEAD visibleGroups.forEach((group, index) => { const label = getDeviceGroupLabel(group.id); +======= + if ( + !animateGroups && + groupContainer.dataset.deviceGroupsSignature === signature && + groupContainer.children.length === groupEntries.length && + (!subnavContainer || subnavContainer.children.length === groupEntries.length) + ) { + Array.from(groupContainer.children).forEach((button, index) => { + const entry = groupEntries[index]; + button.className = "btn groupBtn"; + if (entry.group.id === CURRENT_DEVICE_GROUP) button.classList.add("active"); + button.dataset.deviceGroup = entry.group.id; + button.innerHTML = `${escapeHtml(entry.label)}`; + button.onclick = () => selectDeviceGroup(entry.group.id); + }); + + if (subnavContainer && subnavContainer.children.length === groupEntries.length) { + Array.from(subnavContainer.children).forEach((tab, index) => { + const entry = groupEntries[index]; + tab.className = "setting-subnav__tab"; + if (entry.group.id === CURRENT_DEVICE_GROUP) tab.classList.add("is-active"); + tab.dataset.deviceGroup = entry.group.id; + tab.textContent = entry.label; + tab.onclick = () => selectDeviceGroup(entry.group.id); + }); + } + return; + } + + groupContainer.innerHTML = ""; + groupContainer.dataset.deviceGroupsSignature = signature; + if (subnavContainer) { + subnavContainer.innerHTML = ""; + subnavContainer.dataset.deviceGroupsSignature = signature; + } + + groupEntries.forEach((entry, index) => { + const group = entry.group; + const label = entry.label; +>>>>>>> f3ee1c57 (fix) const button = document.createElement("button"); button.type = "button"; button.className = animateGroups ? "btn groupBtn ui-stagger-item" : "btn groupBtn"; From fceb47139696fd08a1ac1babff68d684f04d6256 Mon Sep 17 00:00:00 2001 From: jominki354 Date: Sat, 9 May 2026 10:05:30 +0900 Subject: [PATCH 2/3] fix --- selfdrive/carrot/web/css/pages/settings.css | 21 ------------------- selfdrive/carrot/web/index.html | 12 ----------- selfdrive/carrot/web/js/pages/setting.js | 6 ------ .../carrot/web/js/pages/setting_device.js | 11 ---------- 4 files changed, 50 deletions(-) diff --git a/selfdrive/carrot/web/css/pages/settings.css b/selfdrive/carrot/web/css/pages/settings.css index 8902f34372..c51942f161 100644 --- a/selfdrive/carrot/web/css/pages/settings.css +++ b/selfdrive/carrot/web/css/pages/settings.css @@ -536,14 +536,7 @@ overflow: hidden; opacity: 1; transform: translateY(0); -<<<<<<< HEAD - transition: - grid-template-rows 0.24s cubic-bezier(.2, 0, 0, 1), - opacity 0.16s ease, - transform 0.2s cubic-bezier(.2, 0, 0, 1); -======= will-change: grid-template-rows, opacity, transform; ->>>>>>> f3ee1c57 (fix) } .setting-profile-section.is-collapsed .setting-profile-section__body { @@ -558,8 +551,6 @@ overflow: hidden; } -<<<<<<< HEAD -======= .setting-profile-section.is-collapsing .setting-profile-section__body { pointer-events: none; animation: setting-profile-section-fold 0.24s cubic-bezier(.2, 0, 0, 1) both; @@ -569,7 +560,6 @@ animation: setting-profile-section-fold 0.24s cubic-bezier(.2, 0, 0, 1) reverse both; } ->>>>>>> f3ee1c57 (fix) .setting-subnav__tab--profile { border-color: color-mix(in srgb, var(--md-primary) 34%, var(--md-outline-var)); } @@ -776,8 +766,6 @@ } } -<<<<<<< HEAD -======= @keyframes setting-profile-section-fold { from { grid-template-rows: 1fr; @@ -791,26 +779,17 @@ } } ->>>>>>> f3ee1c57 (fix) @media (prefers-reduced-motion: reduce) { .setting-profile-panel, .page--setting #items > .setting.ui-stagger-item, .page--setting #items > .setting-profile-section.ui-stagger-item, -<<<<<<< HEAD - .page--setting #deviceItems > .setting.ui-stagger-item { -======= .page--setting #deviceItems > .setting.ui-stagger-item, .setting-profile-section.is-collapsing .setting-profile-section__body, .setting-profile-section.is-expanding .setting-profile-section__body { ->>>>>>> f3ee1c57 (fix) animation: none; } .setting-profile-section__header, -<<<<<<< HEAD - .setting-profile-section__body, -======= ->>>>>>> f3ee1c57 (fix) .setting-profile-section__chevron, .setting-toolbar-action { transition: none; diff --git a/selfdrive/carrot/web/index.html b/selfdrive/carrot/web/index.html index 4d68448b10..8e253e9834 100644 --- a/selfdrive/carrot/web/index.html +++ b/selfdrive/carrot/web/index.html @@ -77,11 +77,7 @@ -<<<<<<< HEAD - -======= ->>>>>>> f3ee1c57 (fix) @@ -628,20 +624,12 @@

Home

-<<<<<<< HEAD - -======= ->>>>>>> f3ee1c57 (fix) -<<<<<<< HEAD - -======= ->>>>>>> f3ee1c57 (fix) diff --git a/selfdrive/carrot/web/js/pages/setting.js b/selfdrive/carrot/web/js/pages/setting.js index bd7d5744f4..f0c32237a5 100644 --- a/selfdrive/carrot/web/js/pages/setting.js +++ b/selfdrive/carrot/web/js/pages/setting.js @@ -2153,11 +2153,6 @@ async function renderItems(group, options = {}) { const bodyInner = document.createElement("div"); bodyInner.className = "setting-profile-section__bodyInner"; header.onclick = () => { -<<<<<<< HEAD - const nextExpanded = section.classList.toggle("is-collapsed") ? false : true; - settingProfileSectionExpandedState.set(stateKey, nextExpanded); - header.setAttribute("aria-expanded", nextExpanded ? "true" : "false"); -======= const wasCollapsed = section.classList.contains("is-collapsed"); const nextExpanded = wasCollapsed; section.classList.remove("is-expanding", "is-collapsing"); @@ -2173,7 +2168,6 @@ async function renderItems(group, options = {}) { section.classList.remove("is-expanding", "is-collapsing"); section.__settingProfileMotionTimer = null; }, 280); ->>>>>>> f3ee1c57 (fix) }; body.appendChild(bodyInner); section.appendChild(header); diff --git a/selfdrive/carrot/web/js/pages/setting_device.js b/selfdrive/carrot/web/js/pages/setting_device.js index 657e183a32..f6b0182c90 100644 --- a/selfdrive/carrot/web/js/pages/setting_device.js +++ b/selfdrive/carrot/web/js/pages/setting_device.js @@ -140,12 +140,6 @@ function renderDeviceGroups(options = {}) { const subnavContainer = document.getElementById("deviceSubnav"); if (!groupContainer) return; const animateGroups = options.animateGroups !== false; -<<<<<<< HEAD - - groupContainer.innerHTML = ""; - if (subnavContainer) subnavContainer.innerHTML = ""; -======= ->>>>>>> f3ee1c57 (fix) const visibleGroups = getVisibleDeviceGroups(); if (!visibleGroups.some((group) => group.id === CURRENT_DEVICE_GROUP)) { @@ -157,10 +151,6 @@ function renderDeviceGroups(options = {}) { })); const signature = groupEntries.map((entry) => `${entry.group.id}:${entry.label}`).join("|"); -<<<<<<< HEAD - visibleGroups.forEach((group, index) => { - const label = getDeviceGroupLabel(group.id); -======= if ( !animateGroups && groupContainer.dataset.deviceGroupsSignature === signature && @@ -199,7 +189,6 @@ function renderDeviceGroups(options = {}) { groupEntries.forEach((entry, index) => { const group = entry.group; const label = entry.label; ->>>>>>> f3ee1c57 (fix) const button = document.createElement("button"); button.type = "button"; button.className = animateGroups ? "btn groupBtn ui-stagger-item" : "btn groupBtn"; From ec795f36e9ad54784fb10532d8705d0cca17cfda Mon Sep 17 00:00:00 2001 From: jominki354 Date: Sat, 9 May 2026 10:14:47 +0900 Subject: [PATCH 3/3] fix --- selfdrive/carrot/web/css/pages/settings.css | 8 +- selfdrive/carrot/web/index.html | 6 +- selfdrive/carrot/web/js/pages/setting.js | 116 ++++++++++++++++-- .../carrot/web/js/pages/setting_device.js | 2 + 4 files changed, 116 insertions(+), 16 deletions(-) diff --git a/selfdrive/carrot/web/css/pages/settings.css b/selfdrive/carrot/web/css/pages/settings.css index c51942f161..ec475dc6d2 100644 --- a/selfdrive/carrot/web/css/pages/settings.css +++ b/selfdrive/carrot/web/css/pages/settings.css @@ -1242,7 +1242,8 @@ display: none; } - .page--setting.setting-layout-split #groupList .groupBtn .setting-group-label { + .page--setting.setting-layout-split #groupList .groupBtn .setting-group-label, + .page--setting.setting-layout-split #deviceGroupList .groupBtn .setting-group-label { display: inline-block; min-width: max-content; transform: translateX(0); @@ -1251,7 +1252,10 @@ .page--setting.setting-layout-split #groupList .groupBtn.is-overflowing:hover .setting-group-label, .page--setting.setting-layout-split #groupList .groupBtn.is-overflowing:focus .setting-group-label, - .page--setting.setting-layout-split #groupList .groupBtn.is-overflowing:active .setting-group-label { + .page--setting.setting-layout-split #groupList .groupBtn.is-overflowing:active .setting-group-label, + .page--setting.setting-layout-split #deviceGroupList .groupBtn.is-overflowing:hover .setting-group-label, + .page--setting.setting-layout-split #deviceGroupList .groupBtn.is-overflowing:focus .setting-group-label, + .page--setting.setting-layout-split #deviceGroupList .groupBtn.is-overflowing:active .setting-group-label { animation: setting-left-text-pan 4.2s ease-in-out 1; } diff --git a/selfdrive/carrot/web/index.html b/selfdrive/carrot/web/index.html index 8e253e9834..844e9c20cf 100644 --- a/selfdrive/carrot/web/index.html +++ b/selfdrive/carrot/web/index.html @@ -77,7 +77,7 @@ - + @@ -624,12 +624,12 @@

Home

- + - + diff --git a/selfdrive/carrot/web/js/pages/setting.js b/selfdrive/carrot/web/js/pages/setting.js index f0c32237a5..d4e994fb88 100644 --- a/selfdrive/carrot/web/js/pages/setting.js +++ b/selfdrive/carrot/web/js/pages/setting.js @@ -550,6 +550,78 @@ async function loadSettings(options = {}) { return settingsLoadPromise; } +let settingOverflowSyncRaf = 0; +let settingOverflowSyncTimer = 0; +let settingOverflowResizeObserver = null; + +function measureSettingGroupButtonOverflow(button) { + if (!button) return; + const labelEl = button.querySelector(".setting-group-label"); + if (!labelEl) return; + const buttonWidth = button.clientWidth || 0; + if (buttonWidth <= 0) return; + const shift = Math.min(0, buttonWidth - labelEl.scrollWidth - 8); + button.style.setProperty("--setting-label-shift", `${shift}px`); + button.classList.toggle("is-overflowing", shift < 0); +} + +function syncSettingGroupLabelOverflow(root = document) { + const scope = root && typeof root.querySelectorAll === "function" ? root : document; + if (scope.matches?.("#groupList .groupBtn, #deviceGroupList .groupBtn")) { + measureSettingGroupButtonOverflow(scope); + } + const selector = (scope.id === "groupList" || scope.id === "deviceGroupList") + ? ".groupBtn" + : "#groupList .groupBtn, #deviceGroupList .groupBtn"; + scope.querySelectorAll(selector).forEach(measureSettingGroupButtonOverflow); +} + +function syncSettingOverflow(root = document) { + syncSettingMarqueeOverflow(root); + syncSettingGroupLabelOverflow(root); +} + +function scheduleSettingOverflowSync(root = document, delayMs = 0) { + if (settingOverflowSyncRaf) cancelAnimationFrame(settingOverflowSyncRaf); + if (settingOverflowSyncTimer) { + window.clearTimeout(settingOverflowSyncTimer); + settingOverflowSyncTimer = 0; + } + + const run = () => { + settingOverflowSyncRaf = requestAnimationFrame(() => { + settingOverflowSyncRaf = 0; + syncSettingOverflow(root); + }); + }; + + if (delayMs > 0) { + settingOverflowSyncTimer = window.setTimeout(() => { + settingOverflowSyncTimer = 0; + run(); + }, delayMs); + } else { + run(); + } +} + +function initSettingOverflowObservers() { + if (settingOverflowResizeObserver || typeof ResizeObserver !== "function") return; + settingOverflowResizeObserver = new ResizeObserver(() => scheduleSettingOverflowSync(document)); + [ + "settingScreenHost", + "settingScreenGroups", + "settingScreenItems", + "groupList", + "deviceGroupList", + "items", + "deviceItems", + ].forEach((id) => { + const el = document.getElementById(id); + if (el) settingOverflowResizeObserver.observe(el); + }); +} + function renderGroups(options = {}) { const box = document.getElementById("groupList"); const animateGroups = options.animateGroups !== false; @@ -560,13 +632,7 @@ function renderGroups(options = {}) { const text = Number.isFinite(Number(count)) ? `${label} (${count})` : label; button.title = text; button.innerHTML = `${escapeHtml(text)}`; - requestAnimationFrame(() => { - const labelEl = button.querySelector(".setting-group-label"); - if (!labelEl) return; - const shift = Math.min(0, button.clientWidth - labelEl.scrollWidth - 8); - button.style.setProperty("--setting-label-shift", `${shift}px`); - button.classList.toggle("is-overflowing", shift < 0); - }); + requestAnimationFrame(() => measureSettingGroupButtonOverflow(button)); } if (!animateGroups && box.dataset.groupsSignature === signature && box.children.length === groups.length) { @@ -588,6 +654,7 @@ function renderGroups(options = {}) { setGroupButtonLabel(button, label, g.count); button.onclick = () => selectGroup(g.group); }); + scheduleSettingOverflowSync(box); return; } @@ -617,6 +684,7 @@ function renderGroups(options = {}) { b.onclick = () => selectGroup(g.group); box.appendChild(b); }); + scheduleSettingOverflowSync(box); } function getSettingGroupMeta(group) { @@ -1300,10 +1368,28 @@ function syncSettingMarqueeOverflow(root = document) { root.querySelectorAll(".setting-marquee").forEach((el) => { const content = el.querySelector(".setting-marquee__content"); if (!content) return; + const elWidth = el.clientWidth || 0; + if (elWidth <= 0) return; const overflow = content.scrollWidth > el.clientWidth + 2; const distance = Math.max(0, content.scrollWidth - el.clientWidth + 18); + const nextDistance = `${distance}px`; + const prevDistance = el.style.getPropertyValue("--setting-marquee-distance"); + const wasOverflowing = el.classList.contains("is-overflowing"); + el.style.setProperty("--setting-marquee-distance", nextDistance); + el.scrollLeft = 0; + if (!overflow) { + el.classList.remove("is-overflowing"); + content.style.animation = ""; + return; + } + + if (!wasOverflowing || prevDistance !== nextDistance) { + el.classList.remove("is-overflowing"); + content.style.animation = "none"; + void content.offsetWidth; + content.style.animation = ""; + } el.classList.toggle("is-overflowing", overflow); - el.style.setProperty("--setting-marquee-distance", `${distance}px`); }); } @@ -2359,7 +2445,7 @@ async function renderItems(group, options = {}) { }); itemsBox.dataset.renderedGroup = group; - requestAnimationFrame(() => syncSettingMarqueeOverflow(itemsBox)); + scheduleSettingOverflowSync(itemsBox); if (pendingSettingFocus?.group === group) { requestAnimationFrame(() => focusSettingItem(pendingSettingFocus.name)); @@ -2540,11 +2626,19 @@ window.addEventListener("carrot:paramsrestored", (event) => { window.addEventListener("resize", () => { scheduleSettingViewportLayoutSync(false); - requestAnimationFrame(() => syncSettingMarqueeOverflow(document.getElementById("items") || document)); + scheduleSettingOverflowSync(document, 80); }, { passive: true }); window.addEventListener("orientationchange", () => { scheduleSettingViewportLayoutSync(true); - window.setTimeout(() => syncSettingMarqueeOverflow(document.getElementById("items") || document), 160); + scheduleSettingOverflowSync(document, 180); }, { passive: true }); +if (window.visualViewport) { + window.visualViewport.addEventListener("resize", () => { + scheduleSettingOverflowSync(document, 80); + }, { passive: true }); +} + +initSettingOverflowObservers(); + diff --git a/selfdrive/carrot/web/js/pages/setting_device.js b/selfdrive/carrot/web/js/pages/setting_device.js index f6b0182c90..4085a68317 100644 --- a/selfdrive/carrot/web/js/pages/setting_device.js +++ b/selfdrive/carrot/web/js/pages/setting_device.js @@ -176,6 +176,7 @@ function renderDeviceGroups(options = {}) { tab.onclick = () => selectDeviceGroup(entry.group.id); }); } + if (typeof scheduleSettingOverflowSync === "function") scheduleSettingOverflowSync(groupContainer); return; } @@ -211,6 +212,7 @@ function renderDeviceGroups(options = {}) { subnavContainer.appendChild(tab); } }); + if (typeof scheduleSettingOverflowSync === "function") scheduleSettingOverflowSync(groupContainer); } function applyDeviceItemsStagger(container) {