diff --git a/web/lib/customElements/swipeUpdater.js b/web/lib/customElements/swipeUpdater.js index 27f45fe..02156ec 100644 --- a/web/lib/customElements/swipeUpdater.js +++ b/web/lib/customElements/swipeUpdater.js @@ -19,6 +19,8 @@ class SwipeUpdater extends HTMLElement { // 3. Внутрішній стан this._isReadyToReload = false; // Прапорець, що вказує на готовність до оновлення + this._isStandalone = false; // Кеш для перевірки PWA режиму + this._isTouching = false; // Чи тримає користувач палець на екрані // 4. Створення елементів (Внутрішній HTML) shadow.innerHTML = ` @@ -111,8 +113,10 @@ class SwipeUpdater extends HTMLElement { this._animID = shadow.getElementById('swipe_updater'); this._animIconID = shadow.getElementById('swipe_icon'); - // 6. Прив'язка контексту `this` для обробника подій (важливо для коректної роботи `this.handleScroll`) + // 6. Прив'язка контексту `this` для обробників подій this.handleScroll = this.handleScroll.bind(this); + this.handleTouchStart = this.handleTouchStart.bind(this); + this.handleTouchEnd = this.handleTouchEnd.bind(this); } /** @@ -128,51 +132,81 @@ class SwipeUpdater extends HTMLElement { } } + // Фіксуємо, що користувач торкнувся екрана + handleTouchStart() { + this._isTouching = true; + } + + // Обробка події, коли користувач відпускає екран + handleTouchEnd() { + this._isTouching = false; + const threshold = -165; + + // Якщо палець відпустили, а ефект «гумового скролу» вже повернувся вище порогу + // (тобто користувач передумав і потягнув назад вгору перед тим як відпустити) + if (window.scrollY > threshold && this._isReadyToReload) { + this._resetRefreshState(); + } + } + /** * Обробник події скролу (головна логіка Pull-to-Refresh). * Відстежує прокручування вище верхньої межі сторінки (`window.scrollY < 0`). */ handleScroll() { // Перевірка на режим Standalone (PWA) - функціональність актуальна переважно тут - const isStandalone = window.matchMedia('(display-mode: standalone)').matches; + if (!this._isStandalone) return; - if (isStandalone) { - let scrollY = window.scrollY; - // 1. Анімація іконки під час прокручування за верхню межу (scrollY < 0) - if (scrollY <= -10) { - // Зміщення іконки разом із прокруткою - this._animIconID.style.top = `${scrollY / 1.5}px`; - this._animID.style.zIndex = '115'; // Піднімаємо z-index для видимості над контентом - } else { - this._animID.style.zIndex = '0'; // Повертаємо базовий z-index + const scrollY = window.scrollY; + const threshold = -165; // Поріг прокрутки (наприклад, -125px) для активації оновлення + + // 1. Анімація іконки під час прокручування за верхню межу (scrollY < 0) + if (scrollY <= -10) { + // Зміщення іконки разом із прокруткою + this._animIconID.style.top = `${scrollY / 1.5}px`; + this._animID.style.zIndex = '115'; // Піднімаємо z-index для видимості над контентом + } else { + this._animIconID.style.top = '0px'; + this._animID.style.zIndex = '0'; // Повертаємо базовий z-index + } + + // 2. Логіка активації "готовий до оновлення" + if (scrollY <= threshold) { + if (!this._isReadyToReload) { + this._isReadyToReload = true; + // Поворот іконки на 180 градусів + this._animIconID.style.transform = 'rotate(180deg)'; + this._animIconID.setAttribute('data-state', ''); // Деактивація стану "active" } - - const threshold = -125; // Поріг прокрутки (наприклад, -125px) для активації оновлення - - // 2. Логіка активації "готовий до оновлення" - if (scrollY <= threshold) { - if (!this._isReadyToReload) { - this._isReadyToReload = true; - // Поворот іконки на 180 градусів - this._animIconID.style.transform = 'rotate(180deg)'; - this._animIconID.setAttribute('data-state', ''); // Деактивація стану "active" - } - } - // 3. Логіка виклику оновлення та скидання стану - // Якщо користувач відпускає свайп (scrollY повертається до >= 0) І був готовий до оновлення - else if (scrollY >= 0) { - if (this._isReadyToReload) { - // Виклик користувацької функції (або стандартного window.location.reload()) - this._appReload(); - - // Скидання стану та анімації - this._isReadyToReload = false; - this._animIconID.style.transform = 'rotate(0deg)'; - this._animIconID.setAttribute('data-state', 'active'); - } + } + // Скасування жесту під час руху пальцем вгору (ще до відпускання) + else if (scrollY > threshold && scrollY < 0 && this._isTouching) { + if (this._isReadyToReload) { + this._isReadyToReload = false; + this._animIconID.style.transform = 'rotate(0deg)'; + this._animIconID.setAttribute('data-state', 'active'); } } + // 3. Логіка виклику оновлення, коли сторінка повернулась до нуля + else if (scrollY >= 0) { + if (this._isReadyToReload) { + this._appReload(); + this._resetRefreshState(); + } + } + + } + + /** + * Допоміжний метод для повного скидання графічного стану + */ + _resetRefreshState() { + this._isReadyToReload = false; + this._animIconID.style.transform = 'rotate(0deg)'; + this._animIconID.setAttribute('data-state', 'active'); + this._animIconID.style.top = '0px'; + this._animID.style.zIndex = '0'; } @@ -181,8 +215,15 @@ class SwipeUpdater extends HTMLElement { * Додаємо обробник події прокручування. */ connectedCallback() { - // Прослуховування глобальної події скролу - window.addEventListener('scroll', this.handleScroll); + // Перевіряємо PWA режим ОДИН раз при додаванні компонента на сторінку + this._isStandalone = window.matchMedia('(display-mode: standalone)').matches; + + if (this._isStandalone) { + window.addEventListener('scroll', this.handleScroll); + // passive: true покращує продуктивність скролу на мобільних пристроях + window.addEventListener('touchstart', this.handleTouchStart, { passive: true }); + window.addEventListener('touchend', this.handleTouchEnd, { passive: true }); + } } /** @@ -190,12 +231,17 @@ class SwipeUpdater extends HTMLElement { * Видаляємо обробник події прокручування для запобігання витоку пам'яті. */ disconnectedCallback() { + // Чистимо абсолютно всі додані слухачі window.removeEventListener('scroll', this.handleScroll); + window.removeEventListener('touchstart', this.handleTouchStart); + window.removeEventListener('touchend', this.handleTouchEnd); } } // Реєстрація веб-компонента -customElements.define('swipe-updater', SwipeUpdater); +if (!customElements.get('swipe-updater')) { + customElements.define('swipe-updater', SwipeUpdater); +} /* diff --git a/web/lib/pages/sheeps/index.html b/web/lib/pages/sheeps/index.html index 54abf32..766dd7b 100644 --- a/web/lib/pages/sheeps/index.html +++ b/web/lib/pages/sheeps/index.html @@ -11,7 +11,6 @@ /> - ${butt_add} diff --git a/web/lib/pages/sheeps/script.js b/web/lib/pages/sheeps/script.js index fde0e2b..9a1bbca 100644 --- a/web/lib/pages/sheeps/script.js +++ b/web/lib/pages/sheeps/script.js @@ -162,7 +162,7 @@ const Sheeps = { let html = await fetch('/lib/pages/sheeps/index.html').then((response) => response.text()); app.innerHTML = html; - await Sheeps.sheeps_list.setHTML(); + await Sheeps.sheeps_list.setHTML({}); if (id) Sheeps.editor.setHTML(id); SheepsEvents.init(); @@ -182,7 +182,7 @@ const Sheeps = { return Sheeps.sheeps_list.list }, - setHTML: async (search_value = null) => { + setHTML: async ({search, filter}) => { let block_sheep_list = document.getElementById('block-sheeps-list'); let block_sheep_info = document.getElementById('block-sheep-info'); block_sheep_list.style.display = "flex"; @@ -210,6 +210,9 @@ const Sheeps = {