20240627.0 (#21192)

This commit is contained in:
Bram Kragten 2024-06-27 20:02:18 +02:00 committed by GitHub
commit d72e8c35d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 280 additions and 107 deletions

View File

@ -168,7 +168,7 @@
"@rollup/plugin-node-resolve": "15.2.3",
"@rollup/plugin-replace": "5.0.7",
"@types/babel__plugin-transform-runtime": "7.9.5",
"@types/chromecast-caf-receiver": "6.0.15",
"@types/chromecast-caf-receiver": "6.0.16",
"@types/chromecast-caf-sender": "1.0.10",
"@types/color-name": "1.1.4",
"@types/glob": "8.1.0",

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20240626.2"
version = "20240627.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"

View File

@ -1,72 +1,92 @@
import "@material/mwc-button/mwc-button";
import "@material/mwc-list/mwc-list-item";
import { mdiCamera } from "@mdi/js";
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, html, LitElement, nothing, PropertyValues } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import type QrScanner from "qr-scanner";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { LocalizeFunc } from "../common/translations/localize";
import { addExternalBarCodeListener } from "../external_app/external_app_entrypoint";
import { HomeAssistant } from "../types";
import "./ha-alert";
import "./ha-button-menu";
import "./ha-list-item";
import "./ha-textfield";
import type { HaTextField } from "./ha-textfield";
@customElement("ha-qr-scanner")
class HaQrScanner extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public localize!: LocalizeFunc;
@property() public description?: string;
@property({ attribute: "alternative_option_label" })
public alternativeOptionLabel?: string;
@property() public error?: string;
@state() private _cameras?: QrScanner.Camera[];
@state() private _error?: string;
@state() private _manual = false;
private _qrScanner?: QrScanner;
private _qrNotFoundCount = 0;
@query("video", true) private _video!: HTMLVideoElement;
private _removeListener?: UnsubscribeFunc;
@query("#canvas-container", true) private _canvasContainer!: HTMLDivElement;
@query("video", true) private _video?: HTMLVideoElement;
@query("#canvas-container", true) private _canvasContainer?: HTMLDivElement;
@query("ha-textfield") private _manualInput?: HaTextField;
public disconnectedCallback(): void {
super.disconnectedCallback();
this._qrNotFoundCount = 0;
if (this._nativeBarcodeScanner) {
this._closeExternalScanner();
}
if (this._qrScanner) {
this._qrScanner.stop();
this._qrScanner.destroy();
this._qrScanner = undefined;
}
while (this._canvasContainer.lastChild) {
while (this._canvasContainer?.lastChild) {
this._canvasContainer.removeChild(this._canvasContainer.lastChild);
}
}
public connectedCallback(): void {
super.connectedCallback();
if (this.hasUpdated && navigator.mediaDevices) {
if (this.hasUpdated) {
this._loadQrScanner();
}
}
protected firstUpdated() {
if (navigator.mediaDevices) {
this._loadQrScanner();
}
this._loadQrScanner();
}
protected updated(changedProps: PropertyValues) {
if (changedProps.has("_error") && this._error) {
fireEvent(this, "qr-code-error", { message: this._error });
if (changedProps.has("error") && this.error) {
alert(`error: ${this.error}`);
this._notifyExternalScanner(this.error);
}
}
protected render(): TemplateResult {
return html`${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
protected render() {
if (this._nativeBarcodeScanner && !this._manual) {
return nothing;
}
return html`${this.error
? html`<ha-alert alert-type="error">${this.error}</ha-alert>`
: ""}
${navigator.mediaDevices
${navigator.mediaDevices && !this._manual
? html`<video></video>
<div id="canvas-container">
${this._cameras && this._cameras.length > 1
@ -80,21 +100,26 @@ class HaQrScanner extends LitElement {
></ha-icon-button>
${this._cameras!.map(
(camera) => html`
<mwc-list-item
<ha-list-item
.value=${camera.id}
@click=${this._cameraChanged}
>${camera.label}</mwc-list-item
>
${camera.label}
</ha-list-item>
`
)}
</ha-button-menu>`
: ""}
: nothing}
</div>`
: html`<ha-alert alert-type="warning">
${!window.isSecureContext
? this.localize("ui.components.qr-scanner.only_https_supported")
: this.localize("ui.components.qr-scanner.not_supported")}
</ha-alert>
: html`${this._manual
? nothing
: html`<ha-alert alert-type="warning">
${!window.isSecureContext
? this.localize(
"ui.components.qr-scanner.only_https_supported"
)
: this.localize("ui.components.qr-scanner.not_supported")}
</ha-alert>`}
<p>${this.localize("ui.components.qr-scanner.manual_input")}</p>
<div class="row">
<ha-textfield
@ -102,33 +127,44 @@ class HaQrScanner extends LitElement {
@keyup=${this._manualKeyup}
@paste=${this._manualPaste}
></ha-textfield>
<mwc-button @click=${this._manualSubmit}
>${this.localize("ui.common.submit")}</mwc-button
>
<mwc-button @click=${this._manualSubmit}>
${this.localize("ui.common.submit")}
</mwc-button>
</div>`}`;
}
private get _nativeBarcodeScanner(): boolean {
return Boolean(this.hass.auth.external?.config.hasBarCodeScanner);
}
private async _loadQrScanner() {
if (this._nativeBarcodeScanner) {
this._openExternalScanner();
return;
}
if (!navigator.mediaDevices) {
return;
}
const QrScanner = (await import("qr-scanner")).default;
if (!(await QrScanner.hasCamera())) {
this._error = "No camera found";
this._reportError("No camera found");
return;
}
QrScanner.WORKER_PATH = "/static/js/qr-scanner-worker.min.js";
this._listCameras(QrScanner);
this._qrScanner = new QrScanner(
this._video,
this._video!,
this._qrCodeScanned,
this._qrCodeError
);
// @ts-ignore
const canvas = this._qrScanner.$canvas;
this._canvasContainer.appendChild(canvas);
this._canvasContainer!.appendChild(canvas);
canvas.style.display = "block";
try {
await this._qrScanner.start();
} catch (err: any) {
this._error = err;
this._reportError(err);
}
}
@ -140,16 +176,16 @@ class HaQrScanner extends LitElement {
if (err === "No QR code found") {
this._qrNotFoundCount++;
if (this._qrNotFoundCount === 250) {
this._error = err;
this._reportError(err);
}
return;
}
this._error = err.message || err;
this._reportError(err.message || err);
// eslint-disable-next-line no-console
console.log(err);
};
private _qrCodeScanned = async (qrCodeString: string): Promise<void> => {
private _qrCodeScanned = (qrCodeString: string): void => {
this._qrNotFoundCount = 0;
fireEvent(this, "qr-code-scanned", { value: qrCodeString });
};
@ -175,6 +211,62 @@ class HaQrScanner extends LitElement {
this._qrScanner?.setCamera((ev.target as any).value);
}
private _openExternalScanner() {
this._removeListener = addExternalBarCodeListener((msg) => {
if (msg.command === "bar_code/scan_result") {
if (msg.payload.format !== "qr_code") {
this._notifyExternalScanner(
`Wrong barcode scanned! ${msg.payload.format}: ${msg.payload.rawValue}, we need a QR code.`
);
} else {
this._qrCodeScanned(msg.payload.rawValue);
}
} else if (msg.command === "bar_code/aborted") {
this._closeExternalScanner();
if (msg.payload.reason === "canceled") {
fireEvent(this, "qr-code-closed");
} else {
this._manual = true;
}
}
return true;
});
this.hass.auth.external!.fireMessage({
type: "bar_code/scan",
payload: {
title: this.title || "Scan QR code",
description: this.description || "Scan a barcode.",
alternative_option_label:
this.alternativeOptionLabel || "Click to manually enter the barcode",
},
});
}
private _closeExternalScanner() {
this._removeListener?.();
this._removeListener = undefined;
this.hass.auth.external!.fireMessage({
type: "bar_code/close",
});
}
private _notifyExternalScanner(message: string) {
if (!this.hass.auth.external) {
return;
}
this.hass.auth.external.fireMessage({
type: "bar_code/notify",
payload: {
message,
},
});
this.error = undefined;
}
private _reportError(message: string) {
fireEvent(this, "qr-code-error", { message });
}
static styles = css`
canvas {
width: 100%;
@ -210,6 +302,7 @@ declare global {
interface HASSDomEvents {
"qr-code-scanned": { value: string };
"qr-code-error": { message: string };
"qr-code-closed": undefined;
}
interface HTMLElementTagNameMap {

View File

@ -11,6 +11,7 @@ import {
fetchEntitySourcesWithCache,
} from "../../data/entity_sources";
import type { AreaSelector } from "../../data/selector";
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
import {
filterSelectorDevices,
filterSelectorEntities,
@ -37,6 +38,8 @@ export class HaAreaSelector extends LitElement {
@state() private _entitySources?: EntitySources;
@state() private _configEntries?: ConfigEntry[];
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
private _hasIntegration(selector: AreaSelector) {
@ -72,6 +75,12 @@ export class HaAreaSelector extends LitElement {
this._entitySources = sources;
});
}
if (!this._configEntries && this._hasIntegration(this.selector)) {
this._configEntries = [];
getConfigEntries(this.hass).then((entries) => {
this._configEntries = entries;
});
}
}
protected render() {
@ -136,7 +145,9 @@ export class HaAreaSelector extends LitElement {
const deviceIntegrations = this._entitySources
? this._deviceIntegrationLookup(
this._entitySources,
Object.values(this.hass.entities)
Object.values(this.hass.entities),
Object.values(this.hass.devices),
this._configEntries
)
: undefined;

View File

@ -11,6 +11,7 @@ import {
fetchEntitySourcesWithCache,
} from "../../data/entity_sources";
import type { DeviceSelector } from "../../data/selector";
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
import {
filterSelectorDevices,
filterSelectorEntities,
@ -27,6 +28,8 @@ export class HaDeviceSelector extends LitElement {
@state() private _entitySources?: EntitySources;
@state() private _configEntries?: ConfigEntry[];
@property() public value?: any;
@property() public label?: string;
@ -75,6 +78,12 @@ export class HaDeviceSelector extends LitElement {
this._entitySources = sources;
});
}
if (!this._configEntries && this._hasIntegration(this.selector)) {
this._configEntries = [];
getConfigEntries(this.hass).then((entries) => {
this._configEntries = entries;
});
}
}
protected render() {
@ -123,7 +132,9 @@ export class HaDeviceSelector extends LitElement {
const deviceIntegrations = this._entitySources
? this._deviceIntegrationLookup(
this._entitySources,
Object.values(this.hass.entities)
Object.values(this.hass.entities),
Object.values(this.hass.devices),
this._configEntries
)
: undefined;

View File

@ -11,6 +11,7 @@ import {
fetchEntitySourcesWithCache,
} from "../../data/entity_sources";
import type { FloorSelector } from "../../data/selector";
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
import {
filterSelectorDevices,
filterSelectorEntities,
@ -37,6 +38,8 @@ export class HaFloorSelector extends LitElement {
@state() private _entitySources?: EntitySources;
@state() private _configEntries?: ConfigEntry[];
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
private _hasIntegration(selector: FloorSelector) {
@ -72,6 +75,12 @@ export class HaFloorSelector extends LitElement {
this._entitySources = sources;
});
}
if (!this._configEntries && this._hasIntegration(this.selector)) {
this._configEntries = [];
getConfigEntries(this.hass).then((entries) => {
this._configEntries = entries;
});
}
}
protected render() {
@ -136,7 +145,9 @@ export class HaFloorSelector extends LitElement {
const deviceIntegrations = this._entitySources
? this._deviceIntegrationLookup(
this._entitySources,
Object.values(this.hass.entities)
Object.values(this.hass.entities),
Object.values(this.hass.devices),
this._configEntries
)
: undefined;

View File

@ -5,6 +5,7 @@ import type {
EntityRegistryDisplayEntry,
EntityRegistryEntry,
} from "./entity_registry";
import { ConfigEntry } from "./config_entries";
import type { EntitySources } from "./entity_sources";
export {
@ -142,9 +143,11 @@ export const getDeviceEntityDisplayLookup = (
export const getDeviceIntegrationLookup = (
entitySources: EntitySources,
entities: EntityRegistryDisplayEntry[] | EntityRegistryEntry[]
): Record<string, string[]> => {
const deviceIntegrations: Record<string, string[]> = {};
entities: EntityRegistryDisplayEntry[] | EntityRegistryEntry[],
devices?: DeviceRegistryEntry[],
configEntries?: ConfigEntry[]
): Record<string, Set<string>> => {
const deviceIntegrations: Record<string, Set<string>> = {};
for (const entity of entities) {
const source = entitySources[entity.entity_id];
@ -152,10 +155,22 @@ export const getDeviceIntegrationLookup = (
continue;
}
if (!deviceIntegrations[entity.device_id!]) {
deviceIntegrations[entity.device_id!] = [];
deviceIntegrations[entity.device_id!] =
deviceIntegrations[entity.device_id!] || new Set<string>();
deviceIntegrations[entity.device_id!].add(source.domain);
}
// Lookup devices that have no entities
if (devices && configEntries) {
for (const device of devices) {
for (const config_entry_id of device.config_entries) {
const entry = configEntries.find((e) => e.entry_id === config_entry_id);
if (entry?.domain) {
deviceIntegrations[device.id] =
deviceIntegrations[device.id] || new Set<string>();
deviceIntegrations[device.id].add(entry.domain);
}
}
}
deviceIntegrations[entity.device_id!].push(source.domain);
}
return deviceIntegrations;
};

View File

@ -696,7 +696,7 @@ export const entityMeetsTargetSelector = (
export const filterSelectorDevices = (
filterDevice: DeviceSelectorFilter,
device: DeviceRegistryEntry,
deviceIntegrationLookup?: Record<string, string[]> | undefined
deviceIntegrationLookup?: Record<string, Set<string>> | undefined
): boolean => {
const {
manufacturer: filterManufacturer,
@ -713,7 +713,7 @@ export const filterSelectorDevices = (
}
if (filterIntegration && deviceIntegrationLookup) {
if (!deviceIntegrationLookup?.[device.id]?.includes(filterIntegration)) {
if (!deviceIntegrationLookup?.[device.id]?.has(filterIntegration)) {
return false;
}
}

View File

@ -205,14 +205,13 @@ class DialogZWaveJSAddNode extends LitElement {
Search device
</mwc-button>`
: this._status === "qr_scan"
? html`${this._error
? html`<ha-alert alert-type="error"
>${this._error}</ha-alert
>`
: ""}
<ha-qr-scanner
? html` <ha-qr-scanner
.hass=${this.hass}
.localize=${this.hass.localize}
.error=${this._error}
@qr-code-scanned=${this._qrCodeScanned}
@qr-code-error=${this._qrCodeError}
@qr-code-closed=${this._startOver}
></ha-qr-scanner>
<mwc-button
slot="secondaryAction"
@ -361,7 +360,7 @@ class DialogZWaveJSAddNode extends LitElement {
</p>
</div>
${this._supportsSmartStart
? html` <div class="outline">
? html`<div class="outline">
<h2>
${this.hass.localize(
"ui.panel.config.zwave_js.add_node.qr_code"
@ -498,9 +497,7 @@ class DialogZWaveJSAddNode extends LitElement {
</ha-alert>`
: ""}
<a
href=${`/config/devices/device/${
this._device?.id
}`}
href=${`/config/devices/device/${this._device?.id}`}
>
<mwc-button>
${this.hass.localize(
@ -599,6 +596,10 @@ class DialogZWaveJSAddNode extends LitElement {
this._handleQrCodeScanned(ev.detail.value);
}
private _qrCodeError(ev: CustomEvent): void {
this._error = ev.detail.message;
}
private async _handleQrCodeScanned(qrCodeString: string): Promise<void> {
this._error = undefined;
if (this._status !== "qr_scan" || this._qrProcessing) {

View File

@ -1,4 +1,4 @@
import { PropertyValueMap, PropertyValues, ReactiveElement } from "lit";
import { PropertyValues, ReactiveElement } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import { MediaQueriesListener } from "../../../common/dom/media_query";
@ -23,35 +23,29 @@ declare global {
@customElement("hui-card")
export class HuiCard extends ReactiveElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public preview = false;
@property({ type: Boolean }) public isPanel = false;
set config(config: LovelaceCardConfig | undefined) {
if (!config) return;
if (config.type !== this._config?.type) {
this._buildElement(config);
} else if (config !== this.config) {
this._element?.setConfig(config);
fireEvent(this, "card-updated");
@property({ attribute: false }) public config?: LovelaceCardConfig;
@property({ attribute: false }) public hass?: HomeAssistant;
public load() {
if (!this.config) {
throw new Error("Cannot build card without config");
}
this._config = config;
this._loadElement(this.config);
}
@property({ attribute: false })
public get config() {
return this._config;
}
private _config?: LovelaceCardConfig;
private _element?: LovelaceCard;
private _listeners: MediaQueriesListener[] = [];
protected createRenderRoot() {
const style = document.createElement("style");
style.textContent = `hui-card { display: contents }`;
this.append(style);
return this;
}
@ -90,57 +84,76 @@ export class HuiCard extends ReactiveElement {
return this._element?.getLayoutOptions?.() ?? {};
}
private _createElement(config: LovelaceCardConfig) {
const element = createCardElement(config);
element.hass = this.hass;
element.preview = this.preview;
private _loadElement(config: LovelaceCardConfig) {
this._element = createCardElement(config);
if (this.hass) {
this._element.hass = this.hass;
}
this._element.preview = this.preview;
// For backwards compatibility
(element as any).editMode = this.preview;
(this._element as any).editMode = this.preview;
// Update element when the visibility of the card changes (e.g. conditional card or filter card)
element.addEventListener("card-visibility-changed", (ev: Event) => {
this._element.addEventListener("card-visibility-changed", (ev: Event) => {
ev.stopPropagation();
this._updateVisibility();
});
element.addEventListener(
this._element.addEventListener(
"ll-upgrade",
(ev: Event) => {
ev.stopPropagation();
element.hass = this.hass;
element.preview = this.preview;
if (this.hass) {
this._element!.hass = this.hass;
}
fireEvent(this, "card-updated");
},
{ once: true }
);
element.addEventListener(
this._element.addEventListener(
"ll-rebuild",
(ev: Event) => {
ev.stopPropagation();
this._buildElement(config);
this._loadElement(config);
fireEvent(this, "card-updated");
},
{ once: true }
);
return element;
}
private _buildElement(config: LovelaceCardConfig) {
this._element = this._createElement(config);
while (this.lastChild) {
this.removeChild(this.lastChild);
}
this._updateVisibility();
}
protected willUpdate(changedProps: PropertyValues<typeof this>): void {
super.willUpdate(changedProps);
if (!this._element) {
this.load();
}
}
protected update(changedProps: PropertyValues<typeof this>) {
super.update(changedProps);
if (this._element) {
if (changedProps.has("config") && this.hasUpdated) {
const oldConfig = changedProps.get("config");
if (this.config !== oldConfig && this.config) {
const typeChanged = this.config?.type !== oldConfig?.type;
if (typeChanged) {
this._loadElement(this.config);
} else {
this._element?.setConfig(this.config);
fireEvent(this, "card-updated");
}
}
}
if (changedProps.has("hass")) {
try {
this._element.hass = this.hass;
if (this.hass) {
this._element.hass = this.hass;
}
} catch (e: any) {
this._buildElement(createErrorCardConfig(e.message, null));
this._loadElement(createErrorCardConfig(e.message, null));
}
}
if (changedProps.has("preview")) {
@ -149,18 +162,14 @@ export class HuiCard extends ReactiveElement {
// For backwards compatibility
(this._element as any).editMode = this.preview;
} catch (e: any) {
this._buildElement(createErrorCardConfig(e.message, null));
this._loadElement(createErrorCardConfig(e.message, null));
}
}
if (changedProps.has("isPanel")) {
this._element.isPanel = this.isPanel;
}
}
}
protected willUpdate(
changedProps: PropertyValueMap<any> | Map<PropertyKey, unknown>
): void {
if (changedProps.has("hass") || changedProps.has("preview")) {
this._updateVisibility();
}

View File

@ -41,6 +41,7 @@ class HuiConditionalCard extends HuiConditionalBase implements LovelaceCard {
element.hass = this.hass;
element.preview = this.preview;
element.config = cardConfig;
element.load();
return element;
}

View File

@ -249,6 +249,7 @@ export class HuiEntityFilterCard
element.hass = this.hass;
element.preview = this.preview;
element.config = cardConfig;
element.load();
return element;
}
}

View File

@ -92,6 +92,7 @@ class HuiGridCard extends HuiStackCard<GridCardConfig> {
}
:host([square]) #root > *:not([hidden]) {
display: block;
grid-row: 1 / 1;
grid-column: 1 / 1;
}

View File

@ -30,7 +30,7 @@ export class HuiHorizontalStackCard extends HuiStackCard {
height: 100%;
gap: var(--horizontal-stack-card-gap, var(--stack-card-gap, 8px));
}
#root > * {
#root > hui-card > * {
flex: 1 1 0;
min-width: 0;
}

View File

@ -56,7 +56,7 @@ export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
card.hass = this.hass;
});
}
if (changedProperties.has("editMode")) {
if (changedProperties.has("preview")) {
this._cards.forEach((card) => {
card.preview = this.preview;
});
@ -69,6 +69,7 @@ export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
element.hass = this.hass;
element.preview = this.preview;
element.config = cardConfig;
element.load();
return element;
}

View File

@ -238,6 +238,14 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
></ha-relative-time>
`;
}
if (content === "last-updated") {
return html`
<ha-relative-time
.hass=${this.hass}
.datetime=${stateObj.last_updated}
></ha-relative-time>
`;
}
if (content === "last_triggered") {
return html`
<ha-relative-time

View File

@ -146,6 +146,7 @@ export class HuiCardLayoutEditor extends LitElement {
this._defaultLayoutOptions =
this._cardElement?.getElementLayoutOptions();
});
this._cardElement.load();
this._defaultLayoutOptions = this._cardElement.getElementLayoutOptions();
} catch (err) {
// eslint-disable-next-line no-console

View File

@ -201,6 +201,12 @@ export class HuiTileCardEditor
),
value: "last-changed",
},
{
label: localize(
`ui.panel.lovelace.editor.card.tile.state_content_options.last-updated`
),
value: "last-updated",
},
...Object.keys(stateObj?.attributes ?? {})
.filter((a) => !HIDDEN_ATTRIBUTES.includes(a))
.map((attribute) => ({

View File

@ -64,6 +64,7 @@ export class HuiSection extends ReactiveElement {
ev.stopPropagation();
this._cards = [...this._cards];
});
element.load();
return element;
}

View File

@ -82,6 +82,7 @@ export class HUIView extends ReactiveElement {
ev.stopPropagation();
this._cards = [...this._cards];
});
element.load();
return element;
}

View File

@ -5987,7 +5987,8 @@
"state_content": "State content",
"state_content_options": {
"state": "State",
"last-changed": "Last changed"
"last-changed": "Last changed",
"last-updated": "Last updated"
}
},
"vertical-stack": {

View File

@ -4036,10 +4036,10 @@ __metadata:
languageName: node
linkType: hard
"@types/chromecast-caf-receiver@npm:6.0.15":
version: 6.0.15
resolution: "@types/chromecast-caf-receiver@npm:6.0.15"
checksum: 10/532c926d01b8173013c0aa96fad3b4e3e8b8f02c993b52cbc654b4263c7e396cc7a1497e00561428cccdbe6bb2014824577e7fdf44f1ebd63e68a3815500fd86
"@types/chromecast-caf-receiver@npm:6.0.16":
version: 6.0.16
resolution: "@types/chromecast-caf-receiver@npm:6.0.16"
checksum: 10/8d60a8fb0a7c4c90d8d8bb7fd55007ef0b2367f0a2129b83895bd857dfdc21296934cb57829248806093d92f66a2fc05475c616b7656ebc27c9498011e2f1f01
languageName: node
linkType: hard
@ -8986,7 +8986,7 @@ __metadata:
"@rollup/plugin-replace": "npm:5.0.7"
"@thomasloven/round-slider": "npm:0.6.0"
"@types/babel__plugin-transform-runtime": "npm:7.9.5"
"@types/chromecast-caf-receiver": "npm:6.0.15"
"@types/chromecast-caf-receiver": "npm:6.0.16"
"@types/chromecast-caf-sender": "npm:1.0.10"
"@types/color-name": "npm:1.1.4"
"@types/glob": "npm:8.1.0"