mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 17:26:42 +00:00
Add a drag-scroll controller (#25159)
* Add a drag-scroll controller * simplify and fix
This commit is contained in:
parent
af0854e480
commit
3a0c367f76
@ -56,6 +56,7 @@
|
|||||||
"@lit-labs/observers": "2.0.5",
|
"@lit-labs/observers": "2.0.5",
|
||||||
"@lit-labs/virtualizer": "2.1.0",
|
"@lit-labs/virtualizer": "2.1.0",
|
||||||
"@lit/context": "1.1.5",
|
"@lit/context": "1.1.5",
|
||||||
|
"@lit/reactive-element": "2.1.0",
|
||||||
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
||||||
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
||||||
"@material/mwc-base": "0.27.0",
|
"@material/mwc-base": "0.27.0",
|
||||||
|
102
src/common/controllers/drag-scroll-controller.ts
Normal file
102
src/common/controllers/drag-scroll-controller.ts
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
@ -1,30 +1,16 @@
|
|||||||
import TabGroup from "@shoelace-style/shoelace/dist/components/tab-group/tab-group.component";
|
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 TabGroupStyles from "@shoelace-style/shoelace/dist/components/tab-group/tab-group.styles";
|
||||||
import "@shoelace-style/shoelace/dist/components/tab/tab";
|
import "@shoelace-style/shoelace/dist/components/tab/tab";
|
||||||
import type { PropertyValues } from "lit";
|
|
||||||
import { css } 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")
|
@customElement("sl-tab-group")
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
export class HaSlTabGroup extends TabGroup {
|
export class HaSlTabGroup extends TabGroup {
|
||||||
private _mouseIsDown = false;
|
private _dragScrollController = new DragScrollController(this, {
|
||||||
|
selector: ".tab-group__nav",
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
override setAriaLabels() {
|
override setAriaLabels() {
|
||||||
// Override the method to prevent setting aria-labels, as we don't use panels
|
// Override the method to prevent setting aria-labels, as we don't use panels
|
||||||
@ -38,73 +24,15 @@ export class HaSlTabGroup extends TabGroup {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override firstUpdated(_changedProperties: PropertyValues): void {
|
|
||||||
super.firstUpdated(_changedProperties);
|
|
||||||
|
|
||||||
const scrollContainer = this._scrollContainer;
|
|
||||||
|
|
||||||
if (scrollContainer) {
|
|
||||||
scrollContainer.addEventListener("mousedown", this._mouseDown);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
protected override handleClick(event: MouseEvent) {
|
protected override handleClick(event: MouseEvent) {
|
||||||
if (
|
if (this._dragScrollController.scrolled) {
|
||||||
this._mouseReleasedAt &&
|
|
||||||
new Date().getTime() - this._mouseReleasedAt < 100
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
super.handleClick(event);
|
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 = [
|
static override styles = [
|
||||||
TabGroupStyles,
|
TabGroupStyles,
|
||||||
css`
|
css`
|
||||||
|
@ -29,6 +29,7 @@ import {
|
|||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
import "../../../components/ha-relative-time";
|
import "../../../components/ha-relative-time";
|
||||||
import "../../../components/ha-state-icon";
|
import "../../../components/ha-state-icon";
|
||||||
|
import { DragScrollController } from "../../../common/controllers/drag-scroll-controller";
|
||||||
|
|
||||||
@customElement("more-info-weather")
|
@customElement("more-info-weather")
|
||||||
class MoreInfoWeather extends LitElement {
|
class MoreInfoWeather extends LitElement {
|
||||||
@ -42,6 +43,11 @@ class MoreInfoWeather extends LitElement {
|
|||||||
|
|
||||||
@state() private _subscribed?: Promise<() => void>;
|
@state() private _subscribed?: Promise<() => void>;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
private _dragScrollController = new DragScrollController(this, {
|
||||||
|
selector: ".forecast",
|
||||||
|
});
|
||||||
|
|
||||||
private _unsubscribeForecastEvents() {
|
private _unsubscribeForecastEvents() {
|
||||||
if (this._subscribed) {
|
if (this._subscribed) {
|
||||||
this._subscribed.then((unsub) => unsub());
|
this._subscribed.then((unsub) => unsub());
|
||||||
@ -547,6 +553,7 @@ class MoreInfoWeather extends LitElement {
|
|||||||
black 94%,
|
black 94%,
|
||||||
transparent 100%
|
transparent 100%
|
||||||
);
|
);
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.forecast > div {
|
.forecast > div {
|
||||||
|
@ -20,6 +20,7 @@ import { replaceView } from "../editor/config-util";
|
|||||||
import { showEditViewHeaderDialog } from "../editor/view-header/show-edit-view-header-dialog";
|
import { showEditViewHeaderDialog } from "../editor/view-header/show-edit-view-header-dialog";
|
||||||
import type { Lovelace } from "../types";
|
import type { Lovelace } from "../types";
|
||||||
import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog";
|
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_LAYOUT = "center";
|
||||||
export const DEFAULT_VIEW_HEADER_BADGES_POSITION = "bottom";
|
export const DEFAULT_VIEW_HEADER_BADGES_POSITION = "bottom";
|
||||||
@ -51,6 +52,10 @@ export class HuiViewHeader extends LitElement {
|
|||||||
this._checkHidden();
|
this._checkHidden();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private _dragScrollController = new DragScrollController(this, {
|
||||||
|
selector: ".scroll",
|
||||||
|
});
|
||||||
|
|
||||||
connectedCallback(): void {
|
connectedCallback(): void {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this.addEventListener(
|
this.addEventListener(
|
||||||
@ -252,7 +257,12 @@ export class HuiViewHeader extends LitElement {
|
|||||||
: nothing}
|
: nothing}
|
||||||
${this.lovelace && (editMode || this.badges.length > 0)
|
${this.lovelace && (editMode || this.badges.length > 0)
|
||||||
? html`
|
? html`
|
||||||
<div class="badges ${badgesPosition} ${badgesWrap}">
|
<div
|
||||||
|
class="badges ${badgesPosition} ${badgesWrap} ${this
|
||||||
|
._dragScrollController.scrolling
|
||||||
|
? "dragging"
|
||||||
|
: ""}"
|
||||||
|
>
|
||||||
<hui-view-badges
|
<hui-view-badges
|
||||||
.badges=${this.badges}
|
.badges=${this.badges}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@ -471,6 +481,10 @@ export class HuiViewHeader extends LitElement {
|
|||||||
.add:focus {
|
.add:focus {
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dragging {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9364,6 +9364,7 @@ __metadata:
|
|||||||
"@lit-labs/observers": "npm:2.0.5"
|
"@lit-labs/observers": "npm:2.0.5"
|
||||||
"@lit-labs/virtualizer": "npm:2.1.0"
|
"@lit-labs/virtualizer": "npm:2.1.0"
|
||||||
"@lit/context": "npm:1.1.5"
|
"@lit/context": "npm:1.1.5"
|
||||||
|
"@lit/reactive-element": "npm:2.1.0"
|
||||||
"@lokalise/node-api": "npm:14.4.0"
|
"@lokalise/node-api": "npm:14.4.0"
|
||||||
"@material/chips": "npm:=14.0.0-canary.53b3cad2f.0"
|
"@material/chips": "npm:=14.0.0-canary.53b3cad2f.0"
|
||||||
"@material/data-table": "npm:=14.0.0-canary.53b3cad2f.0"
|
"@material/data-table": "npm:=14.0.0-canary.53b3cad2f.0"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user