Compare commits

..

2 Commits

Author SHA1 Message Date
Aidan Timson
c77b994cb2 Use scrollbar styles on host 2026-02-05 18:47:22 +00:00
Aidan Timson
718ac64ec0 Move scrolling for dashboards inside view container 2026-02-05 18:41:46 +00:00
9 changed files with 159 additions and 128 deletions

View File

@@ -18,7 +18,7 @@ The Home Assistant interface is based on Material Design. It's a design system c
We want to make it as easy for designers to contribute as it is for developers. Theres a lot a designer can contribute to:
- Meet us at <a href="https://www.home-assistant.io/join-chat-design" rel="noopener noreferrer" target="_blank">Discord #designers channel</a>. If you can't see the channel, make sure you set the correct role in Channels & Roles.
- Meet us at <a href="https://www.home-assistant.io/join-chat" rel="noopener noreferrer" target="_blank">devs_ux Discord</a>. Feel free to share your designs, user test or strategic ideas.
- Start designing with our <a href="https://www.figma.com/community/file/967153512097289521/Home-Assistant-DesignKit" rel="noopener noreferrer" target="_blank">Figma DesignKit</a>.
- Find the latest UX <a href="https://github.com/home-assistant/frontend/discussions?discussions_q=label%3Aux" rel="noopener noreferrer" target="_blank">discussions</a> and <a href="https://github.com/home-assistant/frontend/labels/ux" rel="noopener noreferrer" target="_blank">issues</a> on GitHub. Everyone can start a new issue or discussion!

View File

@@ -132,7 +132,7 @@
"stacktrace-js": "2.0.2",
"superstruct": "2.0.2",
"tinykeys": "3.0.0",
"ua-parser-js": "2.0.9",
"ua-parser-js": "2.0.8",
"vue": "2.7.16",
"vue2-daterange-picker": "0.6.8",
"weekstart": "2.0.0",
@@ -155,7 +155,7 @@
"@octokit/plugin-retry": "8.0.3",
"@octokit/rest": "22.0.1",
"@rsdoctor/rspack-plugin": "1.5.1",
"@rspack/core": "1.7.5",
"@rspack/core": "1.7.4",
"@rspack/dev-server": "1.2.1",
"@types/babel__plugin-transform-runtime": "7.9.5",
"@types/chromecast-caf-receiver": "6.0.25",
@@ -191,7 +191,7 @@
"eslint-plugin-wc": "3.0.2",
"fancy-log": "2.0.0",
"fs-extra": "11.3.3",
"glob": "13.0.1",
"glob": "13.0.0",
"gulp": "5.0.1",
"gulp-brotli": "3.0.0",
"gulp-json-transform": "0.5.0",

View File

@@ -71,7 +71,6 @@ class HaConfigSectionAnalytics extends LitElement {
display: block;
max-width: 600px;
margin: 0 auto;
margin-bottom: 24px;
}
`;
}

View File

@@ -5,8 +5,7 @@ import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-alert";
import "../../../../components/ha-button";
import "../../../../components/ha-dialog-footer";
import "../../../../components/ha-wa-dialog";
import { createCloseHeading } from "../../../../components/ha-dialog";
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
import { haStyleDialog } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
@@ -22,21 +21,14 @@ export class DialogJoinBeta
@state() private _dialogParams?: JoinBetaDialogParams;
@state() private _open = false;
public showDialog(dialogParams: JoinBetaDialogParams): void {
this._dialogParams = dialogParams;
this._open = true;
}
public closeDialog() {
this._open = false;
return true;
}
private _dialogClosed() {
this._dialogParams = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
return true;
}
protected render() {
@@ -45,11 +37,13 @@ export class DialogJoinBeta
}
return html`
<ha-wa-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${this.hass.localize("ui.dialogs.join_beta_channel.title")}
@closed=${this._dialogClosed}
<ha-dialog
open
@closed=${this.closeDialog}
.heading=${createCloseHeading(
this.hass,
this.hass.localize("ui.dialogs.join_beta_channel.title")
)}
>
<ha-alert alert-type="warning">
${this.hass.localize("ui.dialogs.join_beta_channel.backup")}
@@ -73,19 +67,17 @@ export class DialogJoinBeta
)}
<ha-svg-icon .path=${mdiOpenInNew}></ha-svg-icon>
</a>
<ha-dialog-footer slot="footer">
<ha-button
slot="secondaryAction"
appearance="plain"
@click=${this._cancel}
>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button slot="primaryAction" @click=${this._join}>
${this.hass.localize("ui.dialogs.join_beta_channel.join")}
</ha-button>
</ha-dialog-footer>
</ha-wa-dialog>
<ha-button
appearance="plain"
slot="primaryAction"
@click=${this._cancel}
>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button slot="primaryAction" @click=${this._join}>
${this.hass.localize("ui.dialogs.join_beta_channel.join")}
</ha-button>
</ha-dialog>
`;
}

View File

@@ -635,8 +635,12 @@ class HUIRoot extends LitElement {
`;
}
private _handleWindowScroll = () => {
this.toggleAttribute("scrolled", window.scrollY !== 0);
private _handleContainerScroll = () => {
const viewRoot = this._viewRoot;
this.toggleAttribute(
"scrolled",
viewRoot ? viewRoot.scrollTop !== 0 : false
);
};
private _locationChanged = () => {
@@ -667,7 +671,7 @@ class HUIRoot extends LitElement {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
window.addEventListener("scroll", this._handleWindowScroll, {
this._viewRoot?.addEventListener("scroll", this._handleContainerScroll, {
passive: true,
});
this._handleUrlChanged();
@@ -678,7 +682,7 @@ class HUIRoot extends LitElement {
public connectedCallback(): void {
super.connectedCallback();
window.addEventListener("scroll", this._handleWindowScroll, {
this._viewRoot?.addEventListener("scroll", this._handleContainerScroll, {
passive: true,
});
window.addEventListener("popstate", this._handlePopState);
@@ -689,10 +693,14 @@ class HUIRoot extends LitElement {
public disconnectedCallback(): void {
super.disconnectedCallback();
window.removeEventListener("scroll", this._handleWindowScroll);
this._viewRoot?.removeEventListener("scroll", this._handleContainerScroll);
window.removeEventListener("popstate", this._handlePopState);
window.removeEventListener("location-changed", this._locationChanged);
this.toggleAttribute("scrolled", window.scrollY !== 0);
const viewRoot = this._viewRoot;
this.toggleAttribute(
"scrolled",
viewRoot ? viewRoot.scrollTop !== 0 : false
);
// Re-enable history scroll restoration when leaving the page
window.history.scrollRestoration = "auto";
}
@@ -825,9 +833,12 @@ class HUIRoot extends LitElement {
(this._restoreScroll && this._viewScrollPositions[newSelectView]) ||
0;
this._restoreScroll = false;
requestAnimationFrame(() =>
scrollTo({ behavior: "auto", top: position })
);
requestAnimationFrame(() => {
const viewRoot = this._viewRoot;
if (viewRoot) {
viewRoot.scrollTo({ behavior: "auto", top: position });
}
});
}
this._selectView(newSelectView, force);
});
@@ -1152,7 +1163,7 @@ class HUIRoot extends LitElement {
const path = this.config.views[viewIndex].path || viewIndex;
this._navigateToView(path);
} else if (!this._editMode) {
scrollTo({ behavior: "smooth", top: 0 });
this._viewRoot?.scrollTo({ behavior: "smooth", top: 0 });
}
}
@@ -1163,7 +1174,8 @@ class HUIRoot extends LitElement {
// Save scroll position of current view
if (this._curView != null) {
this._viewScrollPositions[this._curView] = window.scrollY;
const viewRoot = this._viewRoot;
this._viewScrollPositions[this._curView] = viewRoot?.scrollTop ?? 0;
}
viewIndex = viewIndex === undefined ? 0 : viewIndex;
@@ -1469,9 +1481,14 @@ class HUIRoot extends LitElement {
hui-view-container {
position: relative;
display: flex;
min-height: 100vh;
height: calc(
100vh - var(--header-height) - var(--safe-area-inset-top) - var(
--view-container-padding-top,
0px
)
);
box-sizing: border-box;
padding-top: calc(
margin-top: calc(
var(--header-height) + var(--safe-area-inset-top) +
var(--view-container-padding-top, 0px)
);
@@ -1494,7 +1511,12 @@ class HUIRoot extends LitElement {
* In edit mode we have the tab bar on a new line *
*/
hui-view-container.has-tab-bar {
padding-top: calc(
height: calc(
100vh - var(--header-height, 56px) - calc(
var(--tab-bar-height, 56px) - 2px
) - var(--safe-area-inset-top, 0px)
);
margin-top: calc(
var(--header-height, 56px) +
calc(var(--tab-bar-height, 56px) - 2px) +
var(--safe-area-inset-top, 0px)

View File

@@ -407,12 +407,20 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
}
}
private _getScrollContainer(): Element | null {
// The scroll container is the hui-view-container parent
return this.closest("hui-view-container");
}
private _toggleView() {
const scrollContainer = this._getScrollContainer();
const scrollTop = scrollContainer?.scrollTop ?? 0;
// Save current scroll position
if (this._sidebarTabActive) {
this._sidebarScrollTop = window.scrollY;
this._sidebarScrollTop = scrollTop;
} else {
this._contentScrollTop = window.scrollY;
this._contentScrollTop = scrollTop;
}
this._sidebarTabActive = !this._sidebarTabActive;
@@ -428,7 +436,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
const scrollY = this._sidebarTabActive
? this._sidebarScrollTop
: this._contentScrollTop;
window.scrollTo(0, scrollY);
scrollContainer?.scrollTo(0, scrollY);
});
}

View File

@@ -4,6 +4,7 @@ import { customElement, property, state } from "lit/decorators";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { listenMediaQuery } from "../../../common/dom/media_query";
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
import { haStyleScrollbar } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
type BackgroundConfig = LovelaceViewConfig["background"];
@@ -22,6 +23,7 @@ class HuiViewContainer extends LitElement {
public connectedCallback(): void {
super.connectedCallback();
this.classList.add("ha-scrollbar");
this._setUpMediaQuery();
this._applyTheme();
}
@@ -74,11 +76,16 @@ class HuiViewContainer extends LitElement {
}
}
static styles = css`
:host {
display: relative;
}
`;
static styles = [
haStyleScrollbar,
css`
:host {
display: block;
height: 100%;
-webkit-overflow-scrolling: touch;
}
`,
];
}
declare global {

View File

@@ -224,17 +224,20 @@ export const haStyleDialogFixedTop = css`
`;
export const haStyleScrollbar = css`
.ha-scrollbar::-webkit-scrollbar {
.ha-scrollbar::-webkit-scrollbar,
:host(.ha-scrollbar)::-webkit-scrollbar {
width: 0.4rem;
height: 0.4rem;
}
.ha-scrollbar::-webkit-scrollbar-thumb {
.ha-scrollbar::-webkit-scrollbar-thumb,
:host(.ha-scrollbar)::-webkit-scrollbar-thumb {
border-radius: var(--ha-border-radius-sm);
background: var(--scrollbar-thumb-color);
}
.ha-scrollbar {
.ha-scrollbar,
:host(.ha-scrollbar) {
overflow-y: auto;
scrollbar-color: var(--scrollbar-thumb-color) transparent;
scrollbar-width: thin;

140
yarn.lock
View File

@@ -2007,12 +2007,12 @@ __metadata:
languageName: node
linkType: hard
"@isaacs/brace-expansion@npm:^5.0.1":
version: 5.0.1
resolution: "@isaacs/brace-expansion@npm:5.0.1"
"@isaacs/brace-expansion@npm:^5.0.0":
version: 5.0.0
resolution: "@isaacs/brace-expansion@npm:5.0.0"
dependencies:
"@isaacs/balanced-match": "npm:^4.0.1"
checksum: 10/aec226065bc4285436a27379e08cc35bf94ef59f5098ac1c026495c9ba4ab33d851964082d3648d56d63eb90f2642867bd15a3e1b810b98beb1a8c14efce6a94
checksum: 10/cf3b7f206aff12128214a1df764ac8cdbc517c110db85249b945282407e3dfc5c6e66286383a7c9391a059fc8e6e6a8ca82262fc9d2590bd615376141fbebd2d
languageName: node
linkType: hard
@@ -4013,92 +4013,92 @@ __metadata:
languageName: node
linkType: hard
"@rspack/binding-darwin-arm64@npm:1.7.5":
version: 1.7.5
resolution: "@rspack/binding-darwin-arm64@npm:1.7.5"
"@rspack/binding-darwin-arm64@npm:1.7.4":
version: 1.7.4
resolution: "@rspack/binding-darwin-arm64@npm:1.7.4"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@rspack/binding-darwin-x64@npm:1.7.5":
version: 1.7.5
resolution: "@rspack/binding-darwin-x64@npm:1.7.5"
"@rspack/binding-darwin-x64@npm:1.7.4":
version: 1.7.4
resolution: "@rspack/binding-darwin-x64@npm:1.7.4"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@rspack/binding-linux-arm64-gnu@npm:1.7.5":
version: 1.7.5
resolution: "@rspack/binding-linux-arm64-gnu@npm:1.7.5"
"@rspack/binding-linux-arm64-gnu@npm:1.7.4":
version: 1.7.4
resolution: "@rspack/binding-linux-arm64-gnu@npm:1.7.4"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@rspack/binding-linux-arm64-musl@npm:1.7.5":
version: 1.7.5
resolution: "@rspack/binding-linux-arm64-musl@npm:1.7.5"
"@rspack/binding-linux-arm64-musl@npm:1.7.4":
version: 1.7.4
resolution: "@rspack/binding-linux-arm64-musl@npm:1.7.4"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@rspack/binding-linux-x64-gnu@npm:1.7.5":
version: 1.7.5
resolution: "@rspack/binding-linux-x64-gnu@npm:1.7.5"
"@rspack/binding-linux-x64-gnu@npm:1.7.4":
version: 1.7.4
resolution: "@rspack/binding-linux-x64-gnu@npm:1.7.4"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@rspack/binding-linux-x64-musl@npm:1.7.5":
version: 1.7.5
resolution: "@rspack/binding-linux-x64-musl@npm:1.7.5"
"@rspack/binding-linux-x64-musl@npm:1.7.4":
version: 1.7.4
resolution: "@rspack/binding-linux-x64-musl@npm:1.7.4"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@rspack/binding-wasm32-wasi@npm:1.7.5":
version: 1.7.5
resolution: "@rspack/binding-wasm32-wasi@npm:1.7.5"
"@rspack/binding-wasm32-wasi@npm:1.7.4":
version: 1.7.4
resolution: "@rspack/binding-wasm32-wasi@npm:1.7.4"
dependencies:
"@napi-rs/wasm-runtime": "npm:1.0.7"
conditions: cpu=wasm32
languageName: node
linkType: hard
"@rspack/binding-win32-arm64-msvc@npm:1.7.5":
version: 1.7.5
resolution: "@rspack/binding-win32-arm64-msvc@npm:1.7.5"
"@rspack/binding-win32-arm64-msvc@npm:1.7.4":
version: 1.7.4
resolution: "@rspack/binding-win32-arm64-msvc@npm:1.7.4"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@rspack/binding-win32-ia32-msvc@npm:1.7.5":
version: 1.7.5
resolution: "@rspack/binding-win32-ia32-msvc@npm:1.7.5"
"@rspack/binding-win32-ia32-msvc@npm:1.7.4":
version: 1.7.4
resolution: "@rspack/binding-win32-ia32-msvc@npm:1.7.4"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
"@rspack/binding-win32-x64-msvc@npm:1.7.5":
version: 1.7.5
resolution: "@rspack/binding-win32-x64-msvc@npm:1.7.5"
"@rspack/binding-win32-x64-msvc@npm:1.7.4":
version: 1.7.4
resolution: "@rspack/binding-win32-x64-msvc@npm:1.7.4"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@rspack/binding@npm:1.7.5":
version: 1.7.5
resolution: "@rspack/binding@npm:1.7.5"
"@rspack/binding@npm:1.7.4":
version: 1.7.4
resolution: "@rspack/binding@npm:1.7.4"
dependencies:
"@rspack/binding-darwin-arm64": "npm:1.7.5"
"@rspack/binding-darwin-x64": "npm:1.7.5"
"@rspack/binding-linux-arm64-gnu": "npm:1.7.5"
"@rspack/binding-linux-arm64-musl": "npm:1.7.5"
"@rspack/binding-linux-x64-gnu": "npm:1.7.5"
"@rspack/binding-linux-x64-musl": "npm:1.7.5"
"@rspack/binding-wasm32-wasi": "npm:1.7.5"
"@rspack/binding-win32-arm64-msvc": "npm:1.7.5"
"@rspack/binding-win32-ia32-msvc": "npm:1.7.5"
"@rspack/binding-win32-x64-msvc": "npm:1.7.5"
"@rspack/binding-darwin-arm64": "npm:1.7.4"
"@rspack/binding-darwin-x64": "npm:1.7.4"
"@rspack/binding-linux-arm64-gnu": "npm:1.7.4"
"@rspack/binding-linux-arm64-musl": "npm:1.7.4"
"@rspack/binding-linux-x64-gnu": "npm:1.7.4"
"@rspack/binding-linux-x64-musl": "npm:1.7.4"
"@rspack/binding-wasm32-wasi": "npm:1.7.4"
"@rspack/binding-win32-arm64-msvc": "npm:1.7.4"
"@rspack/binding-win32-ia32-msvc": "npm:1.7.4"
"@rspack/binding-win32-x64-msvc": "npm:1.7.4"
dependenciesMeta:
"@rspack/binding-darwin-arm64":
optional: true
@@ -4120,23 +4120,23 @@ __metadata:
optional: true
"@rspack/binding-win32-x64-msvc":
optional: true
checksum: 10/3e66805d4dae5f2051f10c5a9126e5bf25926d2a6ccb1c794af2aa49c15e4fdcb9e362bd015a9afef1e788a3272dfe7a28a3c866713badda34579896d736ed4f
checksum: 10/0d082e962980ace986c546ac359e7d4b5fada21ce38980db792c70ef97257841257b64a0b65c5d3c2e2660d58ab05b1d7302defc752f325c616507f5837fb98d
languageName: node
linkType: hard
"@rspack/core@npm:1.7.5":
version: 1.7.5
resolution: "@rspack/core@npm:1.7.5"
"@rspack/core@npm:1.7.4":
version: 1.7.4
resolution: "@rspack/core@npm:1.7.4"
dependencies:
"@module-federation/runtime-tools": "npm:0.22.0"
"@rspack/binding": "npm:1.7.5"
"@rspack/binding": "npm:1.7.4"
"@rspack/lite-tapable": "npm:1.1.0"
peerDependencies:
"@swc/helpers": ">=0.5.1"
peerDependenciesMeta:
"@swc/helpers":
optional: true
checksum: 10/c17d93ef1e7e0728b74bf527150642d9bf072cabb63964ebd32c82da94d6d2f9eac7ff2cc13031bbd376c0c03710899f549e61d2bcfc0a6638a9f3bc8620b7ce
checksum: 10/ecce39e64993ba9f5792c737b6d61960946c59399254f5903651a53b6e61761f8bc2a20dfdf81b1b819e1b0f1e26257456d902244485fd035e5a585b5a28a623
languageName: node
linkType: hard
@@ -8754,14 +8754,14 @@ __metadata:
languageName: node
linkType: hard
"glob@npm:13.0.1, glob@npm:^13.0.0":
version: 13.0.1
resolution: "glob@npm:13.0.1"
"glob@npm:13.0.0, glob@npm:^13.0.0":
version: 13.0.0
resolution: "glob@npm:13.0.0"
dependencies:
minimatch: "npm:^10.1.2"
minimatch: "npm:^10.1.1"
minipass: "npm:^7.1.2"
path-scurry: "npm:^2.0.0"
checksum: 10/465e8cc269ab88d7415a3906cdc0f4543a2ae54df99207204af5bc28a944396d8d893822f546a8056a78ec714e608ab4f3502532c4d6b9cc5e113adf0fe5109e
checksum: 10/de390721d29ee1c9ea41e40ec2aa0de2cabafa68022e237dc4297665a5e4d650776f2573191984ea1640aba1bf0ea34eddef2d8cbfbfc2ad24b5fb0af41d8846
languageName: node
linkType: hard
@@ -9120,7 +9120,7 @@ __metadata:
"@octokit/rest": "npm:22.0.1"
"@replit/codemirror-indentation-markers": "npm:6.5.3"
"@rsdoctor/rspack-plugin": "npm:1.5.1"
"@rspack/core": "npm:1.7.5"
"@rspack/core": "npm:1.7.4"
"@rspack/dev-server": "npm:1.2.1"
"@swc/helpers": "npm:0.5.18"
"@thomasloven/round-slider": "npm:0.6.0"
@@ -9178,7 +9178,7 @@ __metadata:
fancy-log: "npm:2.0.0"
fs-extra: "npm:11.3.3"
fuse.js: "npm:7.1.0"
glob: "npm:13.0.1"
glob: "npm:13.0.0"
google-timezones-json: "npm:1.2.0"
gulp: "npm:5.0.1"
gulp-brotli: "npm:3.0.0"
@@ -9228,7 +9228,7 @@ __metadata:
ts-lit-plugin: "npm:2.0.2"
typescript: "npm:5.9.3"
typescript-eslint: "npm:8.54.0"
ua-parser-js: "npm:2.0.9"
ua-parser-js: "npm:2.0.8"
vite-tsconfig-paths: "npm:6.0.5"
vitest: "npm:4.0.18"
vue: "npm:2.7.16"
@@ -10973,12 +10973,12 @@ __metadata:
languageName: node
linkType: hard
"minimatch@npm:^10.1.2":
version: 10.1.2
resolution: "minimatch@npm:10.1.2"
"minimatch@npm:^10.1.1":
version: 10.1.1
resolution: "minimatch@npm:10.1.1"
dependencies:
"@isaacs/brace-expansion": "npm:^5.0.1"
checksum: 10/6f0ef975463739207144e411bdd54f7205ce38770b162fa3bc4c9be4987a16cb20d0962a82f26c2372598cfba90faa97b327239d303b529b774f17681c163b46
"@isaacs/brace-expansion": "npm:^5.0.0"
checksum: 10/110f38921ea527022e90f7a5f43721838ac740d0a0c26881c03b57c261354fb9a0430e40b2c56dfcea2ef3c773768f27210d1106f1f2be19cde3eea93f26f45e
languageName: node
linkType: hard
@@ -14155,16 +14155,16 @@ __metadata:
languageName: node
linkType: hard
"ua-parser-js@npm:2.0.9":
version: 2.0.9
resolution: "ua-parser-js@npm:2.0.9"
"ua-parser-js@npm:2.0.8":
version: 2.0.8
resolution: "ua-parser-js@npm:2.0.8"
dependencies:
detect-europe-js: "npm:^0.1.2"
is-standalone-pwa: "npm:^0.1.1"
ua-is-frozen: "npm:^0.1.2"
bin:
ua-parser-js: script/cli.js
checksum: 10/63e3404e16ade8a7d1b45bf2a871410193ab63620ab02ffa5308a507a984d2698bd74651ce260ac3e0cb9f8df89c71fa55a6beefbe269c12849c6d6cf0974407
checksum: 10/862ad2e3b083d8ffbb325d6e7c78924aa86f751829301173457afdf44ff911c95c5478d8a1bd31cf80555dd6aae946b3194d20615a91550794f20dad7ac998b5
languageName: node
linkType: hard