Add a drag-scroll controller (#25159)

* Add a drag-scroll controller

* simplify and fix
This commit is contained in:
Bram Kragten 2025-04-25 07:51:35 +02:00 committed by GitHub
parent af0854e480
commit 3a0c367f76
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 132 additions and 79 deletions

View File

@ -56,6 +56,7 @@
"@lit-labs/observers": "2.0.5",
"@lit-labs/virtualizer": "2.1.0",
"@lit/context": "1.1.5",
"@lit/reactive-element": "2.1.0",
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
"@material/mwc-base": "0.27.0",

View File

@ -0,0 +1,102 @@
import type {
ReactiveController,
ReactiveControllerHost,
} from "@lit/reactive-element/reactive-controller";
import type { LitElement } from "lit";
/**
* The config options for a DragScrollController.
*/
export interface DragScrollControllerConfig {
selector: string;
}
export class DragScrollController implements ReactiveController {
public mouseIsDown = false;
public scrolled = false;
public scrolling = false;
public scrollStartX = 0;
public scrollLeft = 0;
private _host: ReactiveControllerHost & LitElement;
private _selector: string;
private _scrollContainer?: HTMLElement | null;
constructor(
host: ReactiveControllerHost & LitElement,
{ selector }: DragScrollControllerConfig
) {
this._selector = selector;
this._host = host;
host.addController(this);
}
hostUpdated() {
if (this._scrollContainer) {
return;
}
this._scrollContainer = this._host.renderRoot?.querySelector(
this._selector
);
if (this._scrollContainer) {
this._scrollContainer.addEventListener("mousedown", this._mouseDown);
}
}
hostDisconnected() {
window.removeEventListener("mousemove", this._mouseMove);
window.removeEventListener("mouseup", this._mouseUp);
}
private _mouseDown = (event: MouseEvent) => {
const scrollContainer = this._scrollContainer;
if (!scrollContainer) {
return;
}
this.scrollStartX = event.pageX - scrollContainer.offsetLeft;
this.scrollLeft = scrollContainer.scrollLeft;
this.mouseIsDown = true;
this.scrolled = false;
window.addEventListener("mousemove", this._mouseMove);
window.addEventListener("mouseup", this._mouseUp, { once: true });
};
private _mouseUp = () => {
this.mouseIsDown = false;
this.scrolling = false;
this._host.requestUpdate();
window.removeEventListener("mousemove", this._mouseMove);
};
private _mouseMove = (event: MouseEvent) => {
if (!this.mouseIsDown) {
return;
}
const scrollContainer = this._scrollContainer;
if (!scrollContainer) {
return;
}
const x = event.pageX - scrollContainer.offsetLeft;
const scroll = x - this.scrollStartX;
if (!this.scrolled) {
this.scrolled = Math.abs(scroll) > 1;
this.scrolling = this.scrolled;
this._host.requestUpdate();
}
scrollContainer.scrollLeft = this.scrollLeft - scroll;
};
}

View File

@ -1,30 +1,16 @@
import TabGroup from "@shoelace-style/shoelace/dist/components/tab-group/tab-group.component";
import TabGroupStyles from "@shoelace-style/shoelace/dist/components/tab-group/tab-group.styles";
import "@shoelace-style/shoelace/dist/components/tab/tab";
import type { PropertyValues } from "lit";
import { css } from "lit";
import { customElement, query } from "lit/decorators";
import { customElement } from "lit/decorators";
import { DragScrollController } from "../common/controllers/drag-scroll-controller";
@customElement("sl-tab-group")
// @ts-ignore
export class HaSlTabGroup extends TabGroup {
private _mouseIsDown = false;
private _scrolled = false;
private _mouseReleasedAt?: number;
private _scrollStartX = 0;
private _scrollLeft = 0;
@query(".tab-group__nav", true) private _scrollContainer?: HTMLElement;
public disconnectedCallback(): void {
super.disconnectedCallback();
window.removeEventListener("mousemove", this._mouseMove);
window.removeEventListener("mouseup", this._mouseUp);
}
private _dragScrollController = new DragScrollController(this, {
selector: ".tab-group__nav",
});
override setAriaLabels() {
// Override the method to prevent setting aria-labels, as we don't use panels
@ -38,73 +24,15 @@ export class HaSlTabGroup extends TabGroup {
return [];
}
protected override firstUpdated(_changedProperties: PropertyValues): void {
super.firstUpdated(_changedProperties);
const scrollContainer = this._scrollContainer;
if (scrollContainer) {
scrollContainer.addEventListener("mousedown", this._mouseDown);
}
}
// @ts-ignore
protected override handleClick(event: MouseEvent) {
if (
this._mouseReleasedAt &&
new Date().getTime() - this._mouseReleasedAt < 100
) {
if (this._dragScrollController.scrolled) {
return;
}
// @ts-ignore
super.handleClick(event);
}
private _mouseDown = (event: MouseEvent) => {
const scrollContainer = this._scrollContainer;
if (!scrollContainer) {
return;
}
this._scrollStartX = event.pageX - scrollContainer.offsetLeft;
this._scrollLeft = scrollContainer.scrollLeft;
this._mouseIsDown = true;
this._scrolled = false;
window.addEventListener("mousemove", this._mouseMove);
window.addEventListener("mouseup", this._mouseUp, { once: true });
};
private _mouseUp = () => {
this._mouseIsDown = false;
if (this._scrolled) {
this._mouseReleasedAt = new Date().getTime();
}
window.removeEventListener("mousemove", this._mouseMove);
};
private _mouseMove = (event: MouseEvent) => {
if (!this._mouseIsDown) {
return;
}
const scrollContainer = this._scrollContainer;
if (!scrollContainer) {
return;
}
const x = event.pageX - scrollContainer.offsetLeft;
const scroll = x - this._scrollStartX;
if (!this._scrolled) {
this._scrolled = Math.abs(scroll) > 1;
}
scrollContainer.scrollLeft = this._scrollLeft - scroll;
};
static override styles = [
TabGroupStyles,
css`

View File

@ -29,6 +29,7 @@ import {
import type { HomeAssistant } from "../../../types";
import "../../../components/ha-relative-time";
import "../../../components/ha-state-icon";
import { DragScrollController } from "../../../common/controllers/drag-scroll-controller";
@customElement("more-info-weather")
class MoreInfoWeather extends LitElement {
@ -42,6 +43,11 @@ class MoreInfoWeather extends LitElement {
@state() private _subscribed?: Promise<() => void>;
// @ts-ignore
private _dragScrollController = new DragScrollController(this, {
selector: ".forecast",
});
private _unsubscribeForecastEvents() {
if (this._subscribed) {
this._subscribed.then((unsub) => unsub());
@ -547,6 +553,7 @@ class MoreInfoWeather extends LitElement {
black 94%,
transparent 100%
);
user-select: none;
}
.forecast > div {

View File

@ -20,6 +20,7 @@ import { replaceView } from "../editor/config-util";
import { showEditViewHeaderDialog } from "../editor/view-header/show-edit-view-header-dialog";
import type { Lovelace } from "../types";
import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog";
import { DragScrollController } from "../../../common/controllers/drag-scroll-controller";
export const DEFAULT_VIEW_HEADER_LAYOUT = "center";
export const DEFAULT_VIEW_HEADER_BADGES_POSITION = "bottom";
@ -51,6 +52,10 @@ export class HuiViewHeader extends LitElement {
this._checkHidden();
};
private _dragScrollController = new DragScrollController(this, {
selector: ".scroll",
});
connectedCallback(): void {
super.connectedCallback();
this.addEventListener(
@ -252,7 +257,12 @@ export class HuiViewHeader extends LitElement {
: nothing}
${this.lovelace && (editMode || this.badges.length > 0)
? html`
<div class="badges ${badgesPosition} ${badgesWrap}">
<div
class="badges ${badgesPosition} ${badgesWrap} ${this
._dragScrollController.scrolling
? "dragging"
: ""}"
>
<hui-view-badges
.badges=${this.badges}
.hass=${this.hass}
@ -471,6 +481,10 @@ export class HuiViewHeader extends LitElement {
.add:focus {
border-style: solid;
}
.dragging {
pointer-events: none;
}
`;
}

View File

@ -9364,6 +9364,7 @@ __metadata:
"@lit-labs/observers": "npm:2.0.5"
"@lit-labs/virtualizer": "npm:2.1.0"
"@lit/context": "npm:1.1.5"
"@lit/reactive-element": "npm:2.1.0"
"@lokalise/node-api": "npm:14.4.0"
"@material/chips": "npm:=14.0.0-canary.53b3cad2f.0"
"@material/data-table": "npm:=14.0.0-canary.53b3cad2f.0"