mirror of
https://github.com/home-assistant/frontend.git
synced 2026-08-07 15:06:57 +00:00
Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa0b579c26 | ||
|
|
78a2d4d6df | ||
|
|
0f8d7b4d41 | ||
|
|
cf3009f408 | ||
|
|
bdf629f836 | ||
|
|
464b58af04 | ||
|
|
29a75209ac | ||
|
|
f89b3c23c7 | ||
|
|
29b631b960 | ||
|
|
dd94a7bac1 | ||
|
|
34154195c8 | ||
|
|
0b34fcb559 |
@@ -228,6 +228,7 @@ export default [
|
||||
"entity-state",
|
||||
"ha-markdown",
|
||||
"integration-card",
|
||||
"cloud-account",
|
||||
"box-shadow",
|
||||
"util-long-press",
|
||||
"remove-delete-add-create",
|
||||
|
||||
@@ -106,6 +106,17 @@ export class DemoHaSelectBox extends LitElement {
|
||||
</ha-card>
|
||||
`;
|
||||
})}
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<label>Disabled with a selected option</label>
|
||||
<ha-select-box
|
||||
.value=${"card"}
|
||||
.options=${fullOptions}
|
||||
.disabled=${true}
|
||||
>
|
||||
</ha-select-box>
|
||||
</div>
|
||||
</ha-card>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<p class="title"><b>Column layout</b></p>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Cloud account
|
||||
---
|
||||
|
||||
The [Home Assistant Cloud](https://www.nabucasa.com/) account page, rendered from
|
||||
mocked cloud data. The controls at the top flip the mocked subscription, remote,
|
||||
backup, onboarding, and feature state so every UI state can be previewed here
|
||||
without a real cloud account.
|
||||
@@ -0,0 +1,568 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import type { HASSDomEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/ha-formfield";
|
||||
import "../../../../src/components/ha-select";
|
||||
import type { HaSelectSelectEvent } from "../../../../src/components/ha-select";
|
||||
import "../../../../src/components/ha-switch";
|
||||
import type { HaSwitch } from "../../../../src/components/ha-switch";
|
||||
import type { BackupConfig } from "../../../../src/data/backup";
|
||||
import { BackupScheduleRecurrence } from "../../../../src/data/backup";
|
||||
import type {
|
||||
CloudStatusLoggedIn,
|
||||
RemoteCertificateStatus,
|
||||
SubscriptionInfo,
|
||||
} from "../../../../src/data/cloud";
|
||||
import { ONBOARDING_ITEMS } from "../../../../src/data/cloud";
|
||||
import type { Webhook } from "../../../../src/data/webhook";
|
||||
import type { ShowDialogParams } from "../../../../src/dialogs/make-dialog-manager";
|
||||
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import type { ProvideHassElement } from "../../../../src/mixins/provide-hass-lit-mixin";
|
||||
import "../../../../src/panels/config/cloud/account/cloud-account-onboarding";
|
||||
import "../../../../src/panels/config/cloud/account/cloud-account-overview";
|
||||
import { onboardingComplete } from "../../../../src/panels/config/cloud/account/cloud-account-status";
|
||||
import { showCloudOnboardingDialog } from "../../../../src/panels/config/cloud/account/show-dialog-cloud-onboarding";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
|
||||
// This demo renders the self-contained cloud account cards (overview +
|
||||
// onboarding) directly, driven by mocked data, with controls to flip the
|
||||
// subscription, remote, backup, onboarding, and feature state so every UI state
|
||||
// can be previewed. It intentionally does NOT render the <cloud-account> panel
|
||||
// wrapper, which adds a hass-subpage toolbar/back button and route links that
|
||||
// have no home in the gallery. See src/panels/config/cloud/account.
|
||||
|
||||
// The five PaymentSubscriptionState values.
|
||||
type DemoCloudAccount =
|
||||
"active" | "trialing" | "canceled" | "expired" | "unknown";
|
||||
|
||||
// "local": automatic backups run, but only to the local agent (no cloud copy).
|
||||
// "none": no automatic backups at all.
|
||||
type DemoCloudBackup = "fresh" | "stale" | "failed" | "local" | "none";
|
||||
|
||||
interface CloudDemoScenario {
|
||||
account: DemoCloudAccount;
|
||||
onboarded: boolean;
|
||||
postponed: boolean;
|
||||
remote: boolean;
|
||||
remoteStatus: RemoteCertificateStatus;
|
||||
backup: DemoCloudBackup;
|
||||
alexa: boolean;
|
||||
google: boolean;
|
||||
webrtc: boolean;
|
||||
webhooks: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_SCENARIO: CloudDemoScenario = {
|
||||
account: "active",
|
||||
// Onboarding not completed and streaming (WebRTC) left off, so the onboarding
|
||||
// card shows by default with streaming as the remaining step.
|
||||
onboarded: false,
|
||||
postponed: false,
|
||||
remote: true,
|
||||
remoteStatus: "ready",
|
||||
backup: "fresh",
|
||||
alexa: true,
|
||||
google: true,
|
||||
webrtc: false,
|
||||
webhooks: true,
|
||||
};
|
||||
|
||||
const SUBSCRIPTION_OPTIONS: { value: DemoCloudAccount; label: string }[] = [
|
||||
{ value: "active", label: "Active" },
|
||||
{ value: "trialing", label: "Trialing" },
|
||||
{ value: "canceled", label: "Canceled" },
|
||||
{ value: "expired", label: "Expired" },
|
||||
{ value: "unknown", label: "Unknown" },
|
||||
];
|
||||
|
||||
const REMOTE_STATUS_OPTIONS: {
|
||||
value: RemoteCertificateStatus;
|
||||
label: string;
|
||||
}[] = [
|
||||
{ value: "ready", label: "Ready" },
|
||||
{ value: "generating", label: "Preparing" },
|
||||
{ value: "loading", label: "Loading" },
|
||||
{ value: "loaded", label: "Loaded" },
|
||||
{ value: "error", label: "Error" },
|
||||
];
|
||||
|
||||
const BACKUP_OPTIONS: { value: DemoCloudBackup; label: string }[] = [
|
||||
{ value: "fresh", label: "Recent" },
|
||||
{ value: "stale", label: "Old" },
|
||||
{ value: "failed", label: "Failed" },
|
||||
{ value: "local", label: "Local only" },
|
||||
{ value: "none", label: "None" },
|
||||
];
|
||||
|
||||
const TOGGLES: [keyof CloudDemoScenario, string][] = [
|
||||
["onboarded", "Onboarded"],
|
||||
["postponed", "Onboarding postponed"],
|
||||
["remote", "Remote access"],
|
||||
["alexa", "Alexa linked"],
|
||||
["google", "Google linked"],
|
||||
["webrtc", "Cameras (WebRTC)"],
|
||||
["webhooks", "Has webhooks"],
|
||||
];
|
||||
|
||||
const CLOUD_AGENT = "cloud.cloud";
|
||||
|
||||
const emptyFilter = () => ({
|
||||
include_domains: [],
|
||||
include_entities: [],
|
||||
exclude_domains: [],
|
||||
exclude_entities: [],
|
||||
});
|
||||
|
||||
const demoWebhooks: Webhook[] = [
|
||||
{
|
||||
webhook_id: "demo_front_door",
|
||||
domain: "automation",
|
||||
name: "Front door motion",
|
||||
local_only: false,
|
||||
},
|
||||
{
|
||||
webhook_id: "demo_companion_app",
|
||||
domain: "mobile_app",
|
||||
name: "Companion app",
|
||||
local_only: false,
|
||||
},
|
||||
];
|
||||
|
||||
const buildSubscription = (scenario: CloudDemoScenario): SubscriptionInfo => ({
|
||||
human_description: "Demo subscription, renews automatically",
|
||||
provider: "Nabu Casa, Inc.",
|
||||
plan_renewal_date: 4102444800,
|
||||
subscription: { status: scenario.account },
|
||||
});
|
||||
|
||||
const buildCloudStatus = (scenario: CloudDemoScenario): CloudStatusLoggedIn => {
|
||||
const active =
|
||||
scenario.account !== "canceled" && scenario.account !== "expired";
|
||||
const cloudhooks = scenario.webhooks
|
||||
? Object.fromEntries(
|
||||
demoWebhooks.map((webhook) => [
|
||||
webhook.webhook_id,
|
||||
{
|
||||
webhook_id: webhook.webhook_id,
|
||||
cloudhook_id: `demo-${webhook.webhook_id}`,
|
||||
cloudhook_url: `https://hooks.nabu.casa/demo-${webhook.webhook_id}`,
|
||||
managed: false,
|
||||
},
|
||||
])
|
||||
)
|
||||
: {};
|
||||
return {
|
||||
logged_in: true,
|
||||
cloud: "connected",
|
||||
cloud_last_disconnect_reason: null,
|
||||
email: "[email protected]",
|
||||
google_registered: scenario.google,
|
||||
google_entities: emptyFilter(),
|
||||
google_domains: ["light", "switch", "climate", "cover"],
|
||||
alexa_registered: scenario.alexa,
|
||||
alexa_entities: emptyFilter(),
|
||||
remote_domain: "demo-instance.ui.nabu.casa",
|
||||
remote_connected: scenario.remote,
|
||||
remote_certificate: {
|
||||
common_name: "demo-instance.ui.nabu.casa",
|
||||
expire_date: "2099-01-01T00:00:00+00:00",
|
||||
fingerprint: "demodemodemodemodemodemodemodemodemodemodemodemodemo",
|
||||
alternative_names: ["demo-instance.ui.nabu.casa"],
|
||||
},
|
||||
remote_certificate_status: scenario.remoteStatus,
|
||||
http_use_ssl: false,
|
||||
active_subscription: active,
|
||||
onboarding_postponed: scenario.postponed,
|
||||
onboarding_completed: scenario.onboarded,
|
||||
prefs: {
|
||||
google_enabled: scenario.google,
|
||||
alexa_enabled: scenario.alexa,
|
||||
remote_enabled: scenario.remote,
|
||||
remote_allow_remote_enable: true,
|
||||
strict_connection: "disabled",
|
||||
google_secure_devices_pin: undefined,
|
||||
cloudhooks,
|
||||
alexa_report_state: true,
|
||||
google_report_state: true,
|
||||
tts_default_voice: ["en-US", "JennyNeural"],
|
||||
cloud_ice_servers_enabled: scenario.webrtc,
|
||||
onboarded_items: scenario.onboarded ? [...ONBOARDING_ITEMS] : [],
|
||||
onboarding_postponed_until: scenario.postponed
|
||||
? new Date(Date.now() + 24 * 3600 * 1000).toISOString()
|
||||
: null,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const buildBackupConfig = (scenario: CloudDemoScenario): BackupConfig => {
|
||||
const now = Date.now();
|
||||
const recent = new Date(now - 12 * 3600 * 1000).toISOString();
|
||||
const old = new Date(now - 5 * 86400000).toISOString();
|
||||
const future = new Date(now + 86400000).toISOString();
|
||||
const overdue = new Date(now - 6 * 3600 * 1000).toISOString();
|
||||
|
||||
const cloudEnabled =
|
||||
scenario.backup === "fresh" ||
|
||||
scenario.backup === "stale" ||
|
||||
scenario.backup === "failed";
|
||||
|
||||
let configured = true;
|
||||
let lastCompleted: string | null = null;
|
||||
let lastAttempted: string | null = null;
|
||||
let next: string | null = null;
|
||||
switch (scenario.backup) {
|
||||
case "fresh":
|
||||
case "local":
|
||||
lastCompleted = recent;
|
||||
lastAttempted = recent;
|
||||
next = future;
|
||||
break;
|
||||
case "stale":
|
||||
lastCompleted = old;
|
||||
lastAttempted = old;
|
||||
next = overdue;
|
||||
break;
|
||||
case "failed":
|
||||
lastCompleted = old;
|
||||
lastAttempted = recent;
|
||||
next = future;
|
||||
break;
|
||||
case "none":
|
||||
configured = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
automatic_backups_configured: configured,
|
||||
last_attempted_automatic_backup: lastAttempted,
|
||||
last_completed_automatic_backup: lastCompleted,
|
||||
next_automatic_backup: next,
|
||||
next_automatic_backup_additional: false,
|
||||
create_backup: {
|
||||
agent_ids: cloudEnabled
|
||||
? ["backup.local", CLOUD_AGENT]
|
||||
: ["backup.local"],
|
||||
include_addons: [],
|
||||
include_all_addons: true,
|
||||
include_database: true,
|
||||
include_folders: [],
|
||||
name: null,
|
||||
password: null,
|
||||
},
|
||||
retention: { copies: 3, days: null },
|
||||
schedule: {
|
||||
recurrence: BackupScheduleRecurrence.DAILY,
|
||||
time: null,
|
||||
days: [],
|
||||
},
|
||||
agents: {
|
||||
"backup.local": { protected: true, retention: null },
|
||||
"cloud.cloud": { protected: true, retention: null },
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@customElement("demo-misc-cloud-account")
|
||||
export class DemoMiscCloudAccount
|
||||
extends LitElement
|
||||
implements ProvideHassElement
|
||||
{
|
||||
@state() private hass!: HomeAssistant;
|
||||
|
||||
@state() private _scenario: CloudDemoScenario = { ...DEFAULT_SCENARIO };
|
||||
|
||||
@state() private _cloudStatus!: CloudStatusLoggedIn;
|
||||
|
||||
@state() private _subscription!: SubscriptionInfo;
|
||||
|
||||
@state() private _backupConfig!: BackupConfig;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("config", "en");
|
||||
|
||||
hass.updateHass({
|
||||
config: {
|
||||
...hass.config,
|
||||
components: [
|
||||
...(hass.config?.components ?? []),
|
||||
"cloud",
|
||||
"backup",
|
||||
"webhook",
|
||||
],
|
||||
},
|
||||
});
|
||||
this._registerMocks(hass);
|
||||
this._applyScenario();
|
||||
}
|
||||
|
||||
public provideHass(el) {
|
||||
el.hass = this.hass;
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.addEventListener("show-dialog", this._showDialog);
|
||||
this.addEventListener("cloud-open-onboarding", this._openOnboarding);
|
||||
this.addEventListener("ha-refresh-cloud-status", this._refreshFromMocks);
|
||||
// The overview and onboarding dialog contain real <a href="/config/..">
|
||||
// links to panel routes that do not exist in the gallery. Keep them inert.
|
||||
this.addEventListener("click", this._neutralizeNavigation);
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.removeEventListener("show-dialog", this._showDialog);
|
||||
this.removeEventListener("cloud-open-onboarding", this._openOnboarding);
|
||||
this.removeEventListener("ha-refresh-cloud-status", this._refreshFromMocks);
|
||||
this.removeEventListener("click", this._neutralizeNavigation);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
const showOnboarding =
|
||||
this._cloudStatus.active_subscription &&
|
||||
!this._cloudStatus.onboarding_completed &&
|
||||
!this._cloudStatus.onboarding_postponed &&
|
||||
!onboardingComplete(this._cloudStatus, this._backupConfig);
|
||||
|
||||
return html`
|
||||
<div class="options">
|
||||
<div class="selects">
|
||||
${this._select("Subscription", "account", SUBSCRIPTION_OPTIONS)}
|
||||
${this._select("Remote status", "remoteStatus", REMOTE_STATUS_OPTIONS)}
|
||||
${this._select("Backups", "backup", BACKUP_OPTIONS)}
|
||||
</div>
|
||||
<div class="switches">
|
||||
${TOGGLES.map(([field, label]) => this._toggle(label, field))}
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview">
|
||||
${
|
||||
showOnboarding
|
||||
? html`
|
||||
<cloud-account-onboarding
|
||||
.hass=${this.hass}
|
||||
.cloudStatus=${this._cloudStatus}
|
||||
.backupConfig=${this._backupConfig}
|
||||
></cloud-account-onboarding>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
<cloud-account-overview
|
||||
.hass=${this.hass}
|
||||
.cloudStatus=${this._cloudStatus}
|
||||
.subscription=${this._subscription}
|
||||
.backupConfig=${this._backupConfig}
|
||||
.webhooks=${demoWebhooks}
|
||||
></cloud-account-overview>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _select(
|
||||
label: string,
|
||||
field: keyof CloudDemoScenario,
|
||||
options: { value: string; label: string }[]
|
||||
) {
|
||||
return html`
|
||||
<ha-select
|
||||
.label=${label}
|
||||
.value=${String(this._scenario[field])}
|
||||
.options=${options}
|
||||
data-field=${field}
|
||||
@selected=${this._selectChanged}
|
||||
></ha-select>
|
||||
`;
|
||||
}
|
||||
|
||||
private _toggle(label: string, field: keyof CloudDemoScenario) {
|
||||
return html`
|
||||
<ha-formfield .label=${label}>
|
||||
<ha-switch
|
||||
.checked=${this._scenario[field] as boolean}
|
||||
data-field=${field}
|
||||
@change=${this._switchChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
`;
|
||||
}
|
||||
|
||||
private _selectChanged(ev: HaSelectSelectEvent) {
|
||||
const field = (ev.currentTarget as HTMLElement).dataset
|
||||
.field as keyof CloudDemoScenario;
|
||||
this._setField(field, ev.detail.value as string);
|
||||
}
|
||||
|
||||
private _switchChanged(ev: Event) {
|
||||
const target = ev.target as HaSwitch;
|
||||
this._setField(
|
||||
target.dataset.field as keyof CloudDemoScenario,
|
||||
target.checked
|
||||
);
|
||||
}
|
||||
|
||||
private _setField(field: keyof CloudDemoScenario, value: string | boolean) {
|
||||
if (this._scenario[field] === value) {
|
||||
return;
|
||||
}
|
||||
this._scenario = { ...this._scenario, [field]: value };
|
||||
this._applyScenario();
|
||||
}
|
||||
|
||||
private _applyScenario() {
|
||||
this._cloudStatus = buildCloudStatus(this._scenario);
|
||||
this._subscription = buildSubscription(this._scenario);
|
||||
this._backupConfig = buildBackupConfig(this._scenario);
|
||||
}
|
||||
|
||||
private _openOnboarding = () => {
|
||||
showCloudOnboardingDialog(this, {
|
||||
cloudStatus: this._cloudStatus,
|
||||
backupConfig: this._backupConfig,
|
||||
onChanged: () => this._refreshFromMocks(),
|
||||
});
|
||||
};
|
||||
|
||||
private _showDialog = (ev: HASSDomEvent<ShowDialogParams<unknown>>) => {
|
||||
const { dialogTag, dialogImport, dialogParams, addHistory, parentElement } =
|
||||
ev.detail;
|
||||
showDialog(
|
||||
this,
|
||||
dialogTag,
|
||||
dialogParams,
|
||||
dialogImport,
|
||||
parentElement,
|
||||
addHistory
|
||||
);
|
||||
};
|
||||
|
||||
private _refreshFromMocks = () => {
|
||||
this._cloudStatus = {
|
||||
...this._cloudStatus,
|
||||
prefs: { ...this._cloudStatus.prefs },
|
||||
};
|
||||
this._backupConfig = { ...this._backupConfig };
|
||||
const cloudBackup =
|
||||
this._backupConfig.create_backup.agent_ids.includes(CLOUD_AGENT);
|
||||
this._scenario = {
|
||||
...this._scenario,
|
||||
onboarded: this._cloudStatus.onboarding_completed,
|
||||
postponed: this._cloudStatus.onboarding_postponed,
|
||||
remote: this._cloudStatus.prefs.remote_enabled,
|
||||
webrtc: this._cloudStatus.prefs.cloud_ice_servers_enabled,
|
||||
backup: !this._backupConfig.automatic_backups_configured
|
||||
? "none"
|
||||
: cloudBackup
|
||||
? this._scenario.backup === "fresh" ||
|
||||
this._scenario.backup === "stale" ||
|
||||
this._scenario.backup === "failed"
|
||||
? this._scenario.backup
|
||||
: "fresh"
|
||||
: "local",
|
||||
};
|
||||
};
|
||||
|
||||
private _neutralizeNavigation = (ev: MouseEvent) => {
|
||||
const anchor = ev
|
||||
.composedPath()
|
||||
.find((el): el is HTMLAnchorElement => el instanceof HTMLAnchorElement);
|
||||
if (anchor?.getAttribute("href")?.startsWith("/")) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
private _registerMocks(hass: MockHomeAssistant) {
|
||||
hass.mockWS("cloud/status", () => ({
|
||||
...this._cloudStatus,
|
||||
prefs: { ...this._cloudStatus.prefs },
|
||||
}));
|
||||
|
||||
hass.mockWS("cloud/update_prefs", (msg) => {
|
||||
const { type, ...prefs } = msg;
|
||||
this._cloudStatus.prefs = { ...this._cloudStatus.prefs, ...prefs };
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
hass.mockWS("cloud/onboarding/postpone", () => {
|
||||
this._cloudStatus.onboarding_postponed = true;
|
||||
this._cloudStatus.prefs.onboarding_postponed_until = new Date(
|
||||
Date.now() + 24 * 3600 * 1000
|
||||
).toISOString();
|
||||
return { ...this._cloudStatus, prefs: { ...this._cloudStatus.prefs } };
|
||||
});
|
||||
|
||||
hass.mockWS("cloud/remote/connect", () => {
|
||||
this._cloudStatus.remote_connected = true;
|
||||
this._cloudStatus.prefs.remote_enabled = true;
|
||||
return null;
|
||||
});
|
||||
hass.mockWS("cloud/remote/disconnect", () => {
|
||||
this._cloudStatus.remote_connected = false;
|
||||
this._cloudStatus.prefs.remote_enabled = false;
|
||||
return null;
|
||||
});
|
||||
|
||||
hass.mockWS("backup/config/info", () => ({
|
||||
config: { ...this._backupConfig },
|
||||
}));
|
||||
hass.mockWS("backup/config/update", (msg) => {
|
||||
const { type, ...update } = msg;
|
||||
if (update.create_backup) {
|
||||
this._backupConfig.create_backup = {
|
||||
...this._backupConfig.create_backup,
|
||||
...update.create_backup,
|
||||
};
|
||||
}
|
||||
if (update.automatic_backups_configured !== undefined) {
|
||||
this._backupConfig.automatic_backups_configured =
|
||||
update.automatic_backups_configured;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.options {
|
||||
max-width: 600px;
|
||||
margin: 16px auto 0;
|
||||
padding: 0 16px 16px;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
.selects {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
.selects ha-select {
|
||||
min-width: 160px;
|
||||
flex: 1;
|
||||
}
|
||||
.switches {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 16px;
|
||||
}
|
||||
.preview {
|
||||
padding: 24px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--ha-space-6, 24px);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-misc-cloud-account": DemoMiscCloudAccount;
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -114,7 +114,7 @@
|
||||
"lit": "3.3.3",
|
||||
"lit-html": "3.3.3",
|
||||
"luxon": "3.7.2",
|
||||
"marked": "18.0.7",
|
||||
"marked": "18.0.9",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "4.0.4",
|
||||
"object-hash": "3.0.0",
|
||||
@@ -152,8 +152,8 @@
|
||||
"@octokit/rest": "22.0.1",
|
||||
"@playwright/test": "1.62.1",
|
||||
"@rsdoctor/rspack-plugin": "1.6.1",
|
||||
"@rspack/core": "2.1.7",
|
||||
"@rspack/dev-server": "2.1.0",
|
||||
"@rspack/core": "2.1.8",
|
||||
"@rspack/dev-server": "2.2.0",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
"@types/chromecast-caf-receiver": "6.0.26",
|
||||
"@types/chromecast-caf-sender": "1.0.11",
|
||||
@@ -210,7 +210,7 @@
|
||||
"terser-webpack-plugin": "5.6.1",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "6.0.3",
|
||||
"typescript-eslint": "8.65.0",
|
||||
"typescript-eslint": "8.66.0",
|
||||
"vite-tsconfig-paths": "6.1.1",
|
||||
"vitest": "4.1.10",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
|
||||
@@ -23,24 +23,24 @@ export const isNavigationClick = (e: MouseEvent, preventDefault = true) => {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let href = anchor.href;
|
||||
if (!href || href.indexOf("mailto:") !== -1) {
|
||||
let url: URL;
|
||||
try {
|
||||
// anchor.href is always absolute; an empty or unparseable value throws.
|
||||
url = new URL(anchor.href);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const location = window.location;
|
||||
const origin = location.origin || location.protocol + "//" + location.host;
|
||||
if (!href.startsWith(origin)) {
|
||||
return undefined;
|
||||
}
|
||||
href = href.slice(origin.length);
|
||||
|
||||
if (href === "#") {
|
||||
// Only intercept same-origin links. A different scheme, host, or port is a
|
||||
// different origin (e.g. another port like ":8123") and must trigger a full
|
||||
// browser navigation instead of an in-app route change. Non-http(s) schemes
|
||||
// such as mailto: resolve to a null origin and are excluded here too.
|
||||
if (url.origin !== window.location.origin) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
return href;
|
||||
return url.pathname + url.search + url.hash;
|
||||
};
|
||||
|
||||
@@ -56,6 +56,7 @@ export class HaSelectBox extends LitElement {
|
||||
class="list"
|
||||
style=${styleMap({ "--columns": columns })}
|
||||
.value=${this.value}
|
||||
?disabled=${this.disabled}
|
||||
@change=${this._radioChanged}
|
||||
>
|
||||
${this.options.map((option) => this._renderOption(option))}
|
||||
@@ -100,7 +101,7 @@ export class HaSelectBox extends LitElement {
|
||||
)}
|
||||
aria-labelledby=${`label-${option.value}`}
|
||||
.value=${option.value}
|
||||
.disabled=${disabled}
|
||||
.disabled=${option.disabled || false}
|
||||
></ha-radio-option>
|
||||
<div class="text">
|
||||
<span id=${`label-${option.value}`} class="label"
|
||||
|
||||
@@ -53,7 +53,6 @@ export class HaSelectorAutomationBehavior extends LitElement {
|
||||
value: behavior,
|
||||
label: this._localizeOption(behavior, "label"),
|
||||
description: this._localizeOption(behavior, "description"),
|
||||
disabled: this.disabled,
|
||||
...(isTrigger && {
|
||||
image: {
|
||||
src: `/static/images/form/automation_behavior_trigger_${behavior}.svg`,
|
||||
@@ -66,6 +65,7 @@ export class HaSelectorAutomationBehavior extends LitElement {
|
||||
<ha-select-box
|
||||
.options=${options}
|
||||
.value=${this.value ?? ""}
|
||||
.disabled=${this.disabled}
|
||||
max_columns="1"
|
||||
?stacked_image=${isTrigger}
|
||||
@value-changed=${this._valueChanged}
|
||||
|
||||
@@ -104,6 +104,7 @@ export class HaSelectSelector extends LitElement {
|
||||
<ha-select-box
|
||||
.options=${options}
|
||||
.value=${this.value as string | undefined}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._selectChanged}
|
||||
.maxColumns=${this.selector.select?.box_max_columns}
|
||||
></ha-select-box>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import type {
|
||||
HassEntity,
|
||||
HassEntityAttributeBase,
|
||||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { DOMAINS_WITH_DYNAMIC_PICTURE } from "../common/const";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { stateActive } from "../common/entity/state_active";
|
||||
import { navigate } from "../common/navigate";
|
||||
import type { HomeAssistant, ServiceCallResponse } from "../types";
|
||||
|
||||
@@ -66,6 +70,75 @@ export type SceneMetaData = Record<
|
||||
{ entity_only?: boolean | undefined }
|
||||
>;
|
||||
|
||||
// Hand-edited scenes.yaml is parsed as YAML 1.1 by the backend, so unquoted
|
||||
// on/off arrive here as booleans. The backend applies boolean states as
|
||||
// on/off; mirror that so the badge reflects what activating the scene will
|
||||
// actually do. Everything else the backend rejects (numbers, null, arrays,
|
||||
// objects) yields undefined.
|
||||
const normalizeSceneEntityState = (sceneState: unknown): string | undefined => {
|
||||
if (typeof sceneState === "boolean") {
|
||||
return sceneState ? "on" : "off";
|
||||
}
|
||||
if (typeof sceneState === "string") {
|
||||
return sceneState;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
// Builds a state object from the scene's stored target state, so the scene
|
||||
// editor can render the icon the entity will have once the scene is applied
|
||||
// rather than its current live icon. The parameter is typed unknown because
|
||||
// the scene config API returns raw YAML: booleans, numbers, nulls, and
|
||||
// malformed shapes occur in hand-edited files beyond what the SceneEntities
|
||||
// union declares. Entries that hold no usable target state (an entity left
|
||||
// without a value in the YAML editor parses as null, a dict may lack a state
|
||||
// key) yield undefined so the caller renders no badge instead of guessing.
|
||||
//
|
||||
// The result is a partial HassEntity meant for badge rendering only - it has
|
||||
// no last_changed, last_updated, or context.
|
||||
export const sceneEntityStateObj = (
|
||||
entityId: string,
|
||||
sceneEntity: unknown
|
||||
): HassEntity | undefined => {
|
||||
if (
|
||||
typeof sceneEntity !== "object" ||
|
||||
sceneEntity === null ||
|
||||
Array.isArray(sceneEntity)
|
||||
) {
|
||||
const state = normalizeSceneEntityState(sceneEntity);
|
||||
return state === undefined
|
||||
? undefined
|
||||
: ({ entity_id: entityId, state, attributes: {} } as HassEntity);
|
||||
}
|
||||
const { state: sceneState, ...attributes } = sceneEntity as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
const state = normalizeSceneEntityState(sceneState);
|
||||
if (state === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
// Media-derived entity pictures are snapshotted with an access token that
|
||||
// is stale by the time review mode renders, which would leave the badge
|
||||
// showing a broken image instead of an icon. Stable pictures on other
|
||||
// domains are kept - the same policy createHistoricState applies for the
|
||||
// logbook.
|
||||
if (DOMAINS_WITH_DYNAMIC_PICTURE.has(computeDomain(entityId))) {
|
||||
delete attributes.entity_picture;
|
||||
delete attributes.entity_picture_local;
|
||||
}
|
||||
const stateObj = { entity_id: entityId, state, attributes } as HassEntity;
|
||||
// A live entity never carries color attributes while off, and state-badge
|
||||
// applies rgb_color and brightness without checking activity; drop them for
|
||||
// inactive targets so a scene that turns a light off does not render an
|
||||
// active-looking colored icon.
|
||||
if (!stateActive(stateObj)) {
|
||||
delete attributes.rgb_color;
|
||||
delete attributes.brightness;
|
||||
}
|
||||
return stateObj;
|
||||
};
|
||||
|
||||
export const activateScene = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string
|
||||
|
||||
@@ -55,6 +55,13 @@ export class HassRouterPage extends ReactiveElement {
|
||||
|
||||
private _currentLoadProm?: Promise<void>;
|
||||
|
||||
// True while a route change is loading and the outgoing panel (or a loading
|
||||
// screen) is still shown, waiting to be replaced. While true we don't forward
|
||||
// property updates, because they are meant for the incoming panel. It stays
|
||||
// false when the new panel is shown immediately (no loading screen), so that
|
||||
// panel keeps receiving updates while its module finishes loading.
|
||||
private _replacingPanel = false;
|
||||
|
||||
private _panelReady = new PanelReady();
|
||||
|
||||
private _cache = {};
|
||||
@@ -79,9 +86,9 @@ export class HassRouterPage extends ReactiveElement {
|
||||
}
|
||||
|
||||
if (!changedProps.has("route")) {
|
||||
// Do not update if we have a currentLoadProm, because that means
|
||||
// that there is still an old panel shown and we're moving to a new one.
|
||||
if (this.lastChild && !this._currentLoadProm) {
|
||||
// Skip while the outgoing panel is still shown for a pending route
|
||||
// change; the update is meant for the incoming panel, not this one.
|
||||
if (this.lastChild && !this._replacingPanel) {
|
||||
this.updatePageEl(this.lastChild, changedProps);
|
||||
}
|
||||
return;
|
||||
@@ -188,6 +195,9 @@ export class HassRouterPage extends ReactiveElement {
|
||||
this._currentLoadProm = undefined;
|
||||
};
|
||||
this._currentLoadProm = loadProm.then(loadComplete, loadComplete);
|
||||
// The new panel is shown right away, so keep forwarding updates to it
|
||||
// while its module loads.
|
||||
this._replacingPanel = false;
|
||||
this._createPanel(routerOptions, newPage, routeOptions);
|
||||
return;
|
||||
}
|
||||
@@ -195,6 +205,9 @@ export class HassRouterPage extends ReactiveElement {
|
||||
// We are only going to show the loading screen after some time.
|
||||
// That way we won't have a double fast flash on fast connections.
|
||||
let created = false;
|
||||
// The outgoing panel stays shown until the new one has loaded; don't
|
||||
// forward updates to it in the meantime.
|
||||
this._replacingPanel = true;
|
||||
|
||||
this._showLoadingScreenTimeout = window.setTimeout(() => {
|
||||
if (created || this._currentPage !== newPage) {
|
||||
@@ -223,9 +236,14 @@ export class HassRouterPage extends ReactiveElement {
|
||||
// @ts-ignore TS forgot this is not a string.
|
||||
routeOptions
|
||||
);
|
||||
// The new panel is now shown; resume forwarding updates to it.
|
||||
this._replacingPanel = false;
|
||||
},
|
||||
() => {
|
||||
this._currentLoadProm = undefined;
|
||||
if (this._currentPage === newPage) {
|
||||
this._replacingPanel = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -102,13 +102,12 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
|
||||
) {
|
||||
this.checkDataBaseMigration();
|
||||
}
|
||||
// Wait for `hass.user` to populate so the admin guard can run; it arrives
|
||||
// asynchronously after `hass.config`.
|
||||
if (
|
||||
changedProps.has("hass") &&
|
||||
this.hass?.user &&
|
||||
oldHass?.user !== this.hass.user
|
||||
) {
|
||||
// Wait for `hass.user` to first populate so the admin guard can run; it
|
||||
// arrives asynchronously after `hass.config`. `hass.user` also gets a fresh
|
||||
// reference at runtime (reconnect, profile refresh via subscribeUser), so
|
||||
// only trigger on the initial population (null -> user). Reconnect re-checks
|
||||
// come from connection-mixin, the launch-screen swap re-check from update().
|
||||
if (changedProps.has("hass") && this.hass?.user && !oldHass?.user) {
|
||||
this.checkHttpPendingConfig();
|
||||
}
|
||||
if (
|
||||
@@ -125,12 +124,12 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
|
||||
}
|
||||
|
||||
protected update(changedProps: PropertyValues<this>) {
|
||||
if (
|
||||
this.hass?.states &&
|
||||
this.hass.config &&
|
||||
this.hass.services &&
|
||||
this._databaseMigration === false
|
||||
) {
|
||||
const removingLaunchScreen =
|
||||
!!this.hass?.states &&
|
||||
!!this.hass.config &&
|
||||
!!this.hass.services &&
|
||||
this._databaseMigration === false;
|
||||
if (removingLaunchScreen) {
|
||||
this.render = this.renderHass;
|
||||
this.update = super.update;
|
||||
// partial-panel-resolver removes the launch screen after the first panel
|
||||
@@ -138,6 +137,13 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
|
||||
// screen covers the frontend until frontend/loaded is sent.
|
||||
}
|
||||
super.update(changedProps);
|
||||
if (removingLaunchScreen) {
|
||||
// Surface the HTTP pending config dialog only after super.update() has
|
||||
// committed the render swap above, which clears the launch screen from
|
||||
// the shadow root. Appending the dialog before that render would let it
|
||||
// tear the freshly-added dialog straight back out of the DOM.
|
||||
this.checkHttpPendingConfig();
|
||||
}
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues<this>) {
|
||||
@@ -256,6 +262,13 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
|
||||
if (__DEMO__ || this._httpPendingDialogOpen) {
|
||||
return;
|
||||
}
|
||||
// Only show once the main UI is rendered. During startup the root swaps
|
||||
// the launch screen for the app, which clears its shadow root and would
|
||||
// tear the freshly-appended dialog straight back out of the DOM (closing
|
||||
// it). When called too early we skip; the swap in update() re-runs this.
|
||||
if (this.render !== this.renderHass) {
|
||||
return;
|
||||
}
|
||||
if (!this.hass?.user?.is_admin) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -128,8 +128,12 @@ class DialogAutomationSave
|
||||
`;
|
||||
}
|
||||
|
||||
private get _isDiscardDialog(): boolean {
|
||||
return this._params?.onDiscard !== undefined;
|
||||
}
|
||||
|
||||
protected _renderDiscard() {
|
||||
if (!this._params?.onDiscard) {
|
||||
if (!this._isDiscardDialog) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
@@ -324,10 +328,14 @@ class DialogAutomationSave
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._save}
|
||||
.disabled=${!!this._params.config.alias && !this.isDirtyState}
|
||||
.disabled=${
|
||||
!!this._params.config.alias &&
|
||||
!this._isDiscardDialog &&
|
||||
!this.isDirtyState
|
||||
}
|
||||
>
|
||||
${this.hass.localize(
|
||||
this._params.config.alias && !this._params.onDiscard
|
||||
this._params.config.alias && !this._isDiscardDialog
|
||||
? "ui.panel.config.automation.editor.rename"
|
||||
: "ui.common.save"
|
||||
)}
|
||||
|
||||
@@ -462,7 +462,7 @@ class HaConfigHttpForm extends LitElement {
|
||||
"ui.panel.config.network.http.restart_address.text"
|
||||
)}
|
||||
</p>
|
||||
<a href=${url} rel="noreferrer noopener">${url}</a>
|
||||
<a href=${url} target="_blank" rel="noreferrer noopener">${url}</a>
|
||||
<p class="dialog-note">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.network.http.restart_address.note"
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
mdiPlaylistEdit,
|
||||
mdiTag,
|
||||
} from "@mdi/js";
|
||||
import type { HassEvent } from "home-assistant-js-websocket";
|
||||
import type { HassEntity, HassEvent } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@@ -67,6 +67,7 @@ import {
|
||||
getSceneEditorInitData,
|
||||
saveScene,
|
||||
SCENE_IGNORED_DOMAINS,
|
||||
sceneEntityStateObj,
|
||||
showSceneEditor,
|
||||
} from "../../../data/scene";
|
||||
import {
|
||||
@@ -447,13 +448,15 @@ export class HaSceneEditor extends DirtyStateProviderMixin<number>()(
|
||||
const integrationName = platform
|
||||
? domainToName(this.hass.localize, platform)
|
||||
: undefined;
|
||||
const badgeStateObj = this._badgeStateObj(
|
||||
entityId,
|
||||
entityStateObj
|
||||
);
|
||||
return html`
|
||||
<ha-list-item
|
||||
hasMeta
|
||||
?twoline=${!!secondary}
|
||||
.graphic=${
|
||||
this._mode === "live" ? "icon" : undefined
|
||||
}
|
||||
graphic="icon"
|
||||
.entityId=${entityId}
|
||||
@click=${
|
||||
this._mode === "live"
|
||||
@@ -463,10 +466,10 @@ export class HaSceneEditor extends DirtyStateProviderMixin<number>()(
|
||||
.noninteractive=${this._mode === "review"}
|
||||
>
|
||||
${
|
||||
this._mode === "live"
|
||||
badgeStateObj
|
||||
? html`
|
||||
<state-badge
|
||||
.stateObj=${entityStateObj}
|
||||
.stateObj=${badgeStateObj}
|
||||
slot="graphic"
|
||||
></state-badge>
|
||||
`
|
||||
@@ -552,14 +555,16 @@ export class HaSceneEditor extends DirtyStateProviderMixin<number>()(
|
||||
this.hass.localize,
|
||||
computeDomain(entityId)
|
||||
);
|
||||
const badgeStateObj = this._badgeStateObj(
|
||||
entityId,
|
||||
entityStateObj
|
||||
);
|
||||
return html`
|
||||
<ha-list-item
|
||||
class="entity"
|
||||
hasMeta
|
||||
?twoline=${!!secondary}
|
||||
.graphic=${
|
||||
this._mode === "live" ? "icon" : undefined
|
||||
}
|
||||
graphic="icon"
|
||||
.entityId=${entityId}
|
||||
@click=${
|
||||
this._mode === "live"
|
||||
@@ -569,11 +574,13 @@ export class HaSceneEditor extends DirtyStateProviderMixin<number>()(
|
||||
.noninteractive=${this._mode === "review"}
|
||||
>
|
||||
${
|
||||
this._mode === "live"
|
||||
? html` <state-badge
|
||||
.stateObj=${entityStateObj}
|
||||
slot="graphic"
|
||||
></state-badge>`
|
||||
badgeStateObj
|
||||
? html`
|
||||
<state-badge
|
||||
.stateObj=${badgeStateObj}
|
||||
slot="graphic"
|
||||
></state-badge>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
${primary}
|
||||
@@ -1188,6 +1195,33 @@ export class HaSceneEditor extends DirtyStateProviderMixin<number>()(
|
||||
return { ...stateObj.attributes, state: stateObj.state };
|
||||
}
|
||||
|
||||
// Memoized per config so re-renders reuse the same object references and
|
||||
// the state badges skip work when nothing changed.
|
||||
private _sceneStateObjs = memoizeOne((config?: SceneConfig) => {
|
||||
const objs: Record<string, HassEntity | undefined> = {};
|
||||
for (const entityId of Object.keys(config?.entities ?? {})) {
|
||||
objs[entityId] = sceneEntityStateObj(
|
||||
entityId,
|
||||
config!.entities[entityId]
|
||||
);
|
||||
}
|
||||
return objs;
|
||||
});
|
||||
|
||||
// Picks the state the row's icon should reflect: the live state in live
|
||||
// mode, the scene's stored target in review mode. Undefined in review mode
|
||||
// when the scene holds no usable target for the entity - the row then
|
||||
// renders no badge rather than a live state that could be mistaken for a
|
||||
// target.
|
||||
private _badgeStateObj(
|
||||
entityId: string,
|
||||
entityStateObj: HassEntity
|
||||
): HassEntity | undefined {
|
||||
return this._mode === "live"
|
||||
? entityStateObj
|
||||
: this._sceneStateObjs(this._config)[entityId];
|
||||
}
|
||||
|
||||
private _generateConfigFromLive() {
|
||||
this._config = {
|
||||
...this._config!,
|
||||
|
||||
@@ -3854,7 +3854,7 @@
|
||||
},
|
||||
"disable_view_transition": {
|
||||
"title": "Disable view transitions",
|
||||
"description": "Disable animated view transitions to prevent browser crash when using browser dev tools."
|
||||
"description": "Disable animated view transitions to prevent browser crash when using browser developer tools."
|
||||
},
|
||||
"entity_diagnostic": {
|
||||
"title": "Entity diagnostic",
|
||||
@@ -3899,7 +3899,7 @@
|
||||
},
|
||||
"actions": {
|
||||
"title": "Actions",
|
||||
"description": "The actions dev tool allows you to perform any action available in Home Assistant.",
|
||||
"description": "The actions tool allows you to perform any action available in Home Assistant.",
|
||||
"call_service": "Perform action",
|
||||
"response": "Response",
|
||||
"column_parameter": "Parameter",
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { isNavigationClick } from "../../../src/common/dom/is-navigation-click";
|
||||
|
||||
const createAnchor = (
|
||||
href: string,
|
||||
attributes: Record<string, string> = {}
|
||||
): HTMLAnchorElement => {
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = href;
|
||||
for (const [name, value] of Object.entries(attributes)) {
|
||||
anchor.setAttribute(name, value);
|
||||
}
|
||||
return anchor;
|
||||
};
|
||||
|
||||
const clickEvent = (
|
||||
anchor: HTMLAnchorElement,
|
||||
overrides: Partial<MouseEvent> = {}
|
||||
): MouseEvent =>
|
||||
({
|
||||
defaultPrevented: false,
|
||||
button: 0,
|
||||
metaKey: false,
|
||||
ctrlKey: false,
|
||||
shiftKey: false,
|
||||
composedPath: () => [anchor],
|
||||
preventDefault: vi.fn(),
|
||||
...overrides,
|
||||
}) as unknown as MouseEvent;
|
||||
|
||||
describe("isNavigationClick", () => {
|
||||
it("returns the path for same-origin links", () => {
|
||||
expect(
|
||||
isNavigationClick(clickEvent(createAnchor(`${location.origin}/config`)))
|
||||
).toEqual("/config");
|
||||
expect(
|
||||
isNavigationClick(
|
||||
clickEvent(createAnchor(`${location.origin}/energy?historyBack=1`))
|
||||
)
|
||||
).toEqual("/energy?historyBack=1");
|
||||
expect(
|
||||
isNavigationClick(
|
||||
clickEvent(createAnchor(`${location.origin}/config/areas#section`))
|
||||
)
|
||||
).toEqual("/config/areas#section");
|
||||
});
|
||||
|
||||
it("does not intercept a link on a different port", () => {
|
||||
// The current origin has no port, so a ported URL shares its string prefix
|
||||
// but is a different origin — it must not be treated as internal.
|
||||
expect(
|
||||
isNavigationClick(clickEvent(createAnchor(`${location.origin}:8123/`)))
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not intercept a host that merely starts with the origin", () => {
|
||||
expect(
|
||||
isNavigationClick(
|
||||
clickEvent(createAnchor(`${location.origin}.example.com/`))
|
||||
)
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not intercept other origins", () => {
|
||||
expect(
|
||||
isNavigationClick(clickEvent(createAnchor("https://example.com/")))
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("calls preventDefault for intercepted navigations", () => {
|
||||
const event = clickEvent(createAnchor(`${location.origin}/config`));
|
||||
isNavigationClick(event);
|
||||
expect(event.preventDefault).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not preventDefault when disabled", () => {
|
||||
const event = clickEvent(createAnchor(`${location.origin}/config`));
|
||||
expect(isNavigationClick(event, false)).toEqual("/config");
|
||||
expect(event.preventDefault).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("ignores clicks with a target, download, or rel=external", () => {
|
||||
expect(
|
||||
isNavigationClick(
|
||||
clickEvent(
|
||||
createAnchor(`${location.origin}/config`, { target: "_blank" })
|
||||
)
|
||||
)
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
isNavigationClick(
|
||||
clickEvent(createAnchor(`${location.origin}/config`, { download: "" }))
|
||||
)
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
isNavigationClick(
|
||||
clickEvent(
|
||||
createAnchor(`${location.origin}/config`, { rel: "external" })
|
||||
)
|
||||
)
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("ignores modified clicks and non-left buttons", () => {
|
||||
const anchor = createAnchor(`${location.origin}/config`);
|
||||
expect(
|
||||
isNavigationClick(clickEvent(anchor, { button: 1 }))
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
isNavigationClick(clickEvent(anchor, { metaKey: true }))
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
isNavigationClick(clickEvent(anchor, { ctrlKey: true }))
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
isNavigationClick(clickEvent(anchor, { shiftKey: true }))
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
isNavigationClick(clickEvent(anchor, { defaultPrevented: true }))
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("ignores mailto links and clicks without an anchor", () => {
|
||||
expect(
|
||||
isNavigationClick(clickEvent(createAnchor("mailto:[email protected]")))
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
isNavigationClick(
|
||||
clickEvent(undefined as any, {
|
||||
composedPath: () => [document.createElement("div")],
|
||||
})
|
||||
)
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,116 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { sceneEntityStateObj } from "../../src/data/scene";
|
||||
|
||||
describe("sceneEntityStateObj", () => {
|
||||
it("builds a state object from string shorthand", () => {
|
||||
expect(sceneEntityStateObj("light.kitchen", "on")).toEqual({
|
||||
entity_id: "light.kitchen",
|
||||
state: "on",
|
||||
attributes: {},
|
||||
});
|
||||
});
|
||||
|
||||
it("builds a state object from the dict form", () => {
|
||||
expect(
|
||||
sceneEntityStateObj("light.kitchen", {
|
||||
state: "on",
|
||||
brightness: 180,
|
||||
rgb_color: [255, 64, 112],
|
||||
})
|
||||
).toEqual({
|
||||
entity_id: "light.kitchen",
|
||||
state: "on",
|
||||
attributes: { brightness: 180, rgb_color: [255, 64, 112] },
|
||||
});
|
||||
});
|
||||
|
||||
it("strips media-derived entity pictures but keeps other attributes", () => {
|
||||
const sceneEntity = {
|
||||
state: "playing",
|
||||
entity_picture: "/api/media_player_proxy/x?token=stale",
|
||||
entity_picture_local: "/local/x.jpg",
|
||||
friendly_name: "Player",
|
||||
};
|
||||
expect(sceneEntityStateObj("media_player.x", sceneEntity)).toEqual({
|
||||
entity_id: "media_player.x",
|
||||
state: "playing",
|
||||
attributes: { friendly_name: "Player" },
|
||||
});
|
||||
// The input scene config must not be mutated.
|
||||
expect(sceneEntity.entity_picture).toBe(
|
||||
"/api/media_player_proxy/x?token=stale"
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps a stable entity picture on other domains", () => {
|
||||
expect(
|
||||
sceneEntityStateObj("vacuum.robot", {
|
||||
state: "cleaning",
|
||||
entity_picture: "/local/robot.png",
|
||||
})?.attributes.entity_picture
|
||||
).toBe("/local/robot.png");
|
||||
});
|
||||
|
||||
it("returns undefined for null and undefined values", () => {
|
||||
// An entity left without a value in the YAML editor parses as null.
|
||||
expect(sceneEntityStateObj("light.kitchen", null)).toBeUndefined();
|
||||
expect(sceneEntityStateObj("light.kitchen", undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined for an array value", () => {
|
||||
expect(sceneEntityStateObj("light.kitchen", [255, 100])).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined for a numeric value", () => {
|
||||
// The backend only accepts string and boolean states, so a number never
|
||||
// becomes a target the scene can apply.
|
||||
expect(sceneEntityStateObj("input_number.x", 23)).toBeUndefined();
|
||||
expect(
|
||||
sceneEntityStateObj("input_number.x", { state: 23 })
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined when the dict holds no usable state", () => {
|
||||
expect(
|
||||
sceneEntityStateObj("light.kitchen", { brightness: 100 })
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
sceneEntityStateObj("light.kitchen", { state: null })
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
sceneEntityStateObj("light.kitchen", { state: { brightness: 100 } })
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("normalizes boolean shorthand to on/off like the backend does", () => {
|
||||
// YAML 1.1 parses unquoted on/off in scenes.yaml as booleans.
|
||||
expect(sceneEntityStateObj("light.kitchen", true)?.state).toBe("on");
|
||||
expect(sceneEntityStateObj("light.kitchen", false)?.state).toBe("off");
|
||||
});
|
||||
|
||||
it("normalizes a boolean state in the dict form", () => {
|
||||
expect(sceneEntityStateObj("light.kitchen", { state: true })).toEqual({
|
||||
entity_id: "light.kitchen",
|
||||
state: "on",
|
||||
attributes: {},
|
||||
});
|
||||
});
|
||||
|
||||
it("drops color attributes for an inactive target", () => {
|
||||
// A live entity never carries these while off, and state-badge applies
|
||||
// them without checking activity.
|
||||
expect(
|
||||
sceneEntityStateObj("light.kitchen", {
|
||||
state: "off",
|
||||
brightness: 180,
|
||||
rgb_color: [255, 0, 0],
|
||||
friendly_name: "Kitchen",
|
||||
})
|
||||
).toEqual({
|
||||
entity_id: "light.kitchen",
|
||||
state: "off",
|
||||
attributes: { friendly_name: "Kitchen" },
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { ReactiveElement } from "lit";
|
||||
import { state } from "lit/decorators";
|
||||
import { property, state } from "lit/decorators";
|
||||
import type { RouterOptions } from "../../src/layouts/hass-router-page";
|
||||
import { HassRouterPage } from "../../src/layouts/hass-router-page";
|
||||
import { panelIsReady } from "../../src/layouts/panel-ready";
|
||||
@@ -36,9 +36,71 @@ const loadedPanelImport = new Promise<void>((resolve) => {
|
||||
|
||||
class LoadedPanel extends HTMLElement {}
|
||||
|
||||
let resolvePropPanelImport: () => void;
|
||||
const propPanelImport = new Promise<void>((resolve) => {
|
||||
resolvePropPanelImport = resolve;
|
||||
});
|
||||
|
||||
class PropPanel extends HTMLElement {
|
||||
public value = 0;
|
||||
}
|
||||
|
||||
// A router that shows its panel immediately (no `showLoading`) and forwards a
|
||||
// property to it on every update, mirroring how the config and profile panels
|
||||
// forward `hass` to their child sections.
|
||||
class PropRouter extends HassRouterPage {
|
||||
@property({ attribute: false }) public value = 0;
|
||||
|
||||
protected routerOptions: RouterOptions = {
|
||||
routes: {
|
||||
slow: { tag: "test-prop-panel", load: () => propPanelImport },
|
||||
},
|
||||
};
|
||||
|
||||
protected updatePageEl(el: PropPanel) {
|
||||
el.value = this.value;
|
||||
}
|
||||
}
|
||||
|
||||
let resolveSecondPanelImport: () => void;
|
||||
const secondPanelImport = new Promise<void>((resolve) => {
|
||||
resolveSecondPanelImport = resolve;
|
||||
});
|
||||
|
||||
class SwapFirstPanel extends HTMLElement {
|
||||
public value = 0;
|
||||
}
|
||||
|
||||
class SwapSecondPanel extends HTMLElement {
|
||||
public value = 0;
|
||||
}
|
||||
|
||||
// A router that shows a loading screen between routes, so the outgoing panel
|
||||
// stays visible while the next one loads.
|
||||
class SwapRouter extends HassRouterPage {
|
||||
@property({ attribute: false }) public value = 0;
|
||||
|
||||
protected routerOptions: RouterOptions = {
|
||||
showLoading: true,
|
||||
routes: {
|
||||
first: { tag: "test-swap-first-panel" },
|
||||
second: { tag: "test-swap-second-panel", load: () => secondPanelImport },
|
||||
},
|
||||
};
|
||||
|
||||
protected updatePageEl(el: SwapFirstPanel | SwapSecondPanel) {
|
||||
el.value = this.value;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("test-router", TestRouter);
|
||||
customElements.define("test-immediate-panel", ImmediatePanel);
|
||||
customElements.define("test-deferred-panel", DeferredPanel);
|
||||
customElements.define("test-prop-router", PropRouter);
|
||||
customElements.define("test-prop-panel", PropPanel);
|
||||
customElements.define("test-swap-router", SwapRouter);
|
||||
customElements.define("test-swap-first-panel", SwapFirstPanel);
|
||||
customElements.define("test-swap-second-panel", SwapSecondPanel);
|
||||
|
||||
let router: TestRouter | undefined;
|
||||
|
||||
@@ -101,11 +163,81 @@ describe("HassRouterPage panel readiness", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("HassRouterPage update propagation during load", () => {
|
||||
it("forwards updates to a panel shown while its module is still loading", async () => {
|
||||
const element = document.createElement("test-prop-router") as PropRouter;
|
||||
element.route = { prefix: "", path: "/slow" };
|
||||
document.body.append(element);
|
||||
await element.updateComplete;
|
||||
|
||||
const panel = element.lastElementChild as PropPanel;
|
||||
expect(panel).toBeInstanceOf(PropPanel);
|
||||
expect(panel.value).toBe(0);
|
||||
|
||||
// A new value (e.g. an updated `hass` carrying freshly loaded fragment
|
||||
// translations) arrives while the panel module is still loading. The panel
|
||||
// is already shown, so the update must reach it right away rather than
|
||||
// being skipped as if an outgoing panel were still being replaced.
|
||||
element.value = 1;
|
||||
await element.updateComplete;
|
||||
expect(panel.value).toBe(1);
|
||||
|
||||
// Finishing the load must not lose the value it received in the meantime.
|
||||
resolvePropPanelImport();
|
||||
await propPanelImport;
|
||||
await element.updateComplete;
|
||||
expect(panel.value).toBe(1);
|
||||
|
||||
element.remove();
|
||||
});
|
||||
|
||||
it("does not forward updates to the outgoing panel while a new one loads", async () => {
|
||||
const element = document.createElement("test-swap-router") as SwapRouter;
|
||||
element.route = { prefix: "", path: "/first" };
|
||||
document.body.append(element);
|
||||
// A router with a loading screen creates the panel once its load resolves.
|
||||
await vi.waitFor(() => {
|
||||
expect(element.lastElementChild).toBeInstanceOf(SwapFirstPanel);
|
||||
});
|
||||
const first = element.lastElementChild as SwapFirstPanel;
|
||||
expect(first.value).toBe(0);
|
||||
|
||||
// Navigate to a page whose module is still loading. The first panel stays
|
||||
// shown in the meantime (the loading screen only appears after a delay).
|
||||
element.route = { prefix: "", path: "/second" };
|
||||
await element.updateComplete;
|
||||
expect(element.lastElementChild).toBe(first);
|
||||
|
||||
// An update now is meant for the incoming panel, so it must not leak to the
|
||||
// outgoing one that is about to be replaced.
|
||||
element.value = 1;
|
||||
await element.updateComplete;
|
||||
expect(first.value).toBe(0);
|
||||
|
||||
// Once the new panel is shown, updates resume normally.
|
||||
resolveSecondPanelImport();
|
||||
await vi.waitFor(() => {
|
||||
expect(element.lastElementChild).toBeInstanceOf(SwapSecondPanel);
|
||||
});
|
||||
const second = element.lastElementChild as SwapSecondPanel;
|
||||
element.value = 2;
|
||||
await element.updateComplete;
|
||||
expect(second.value).toBe(2);
|
||||
|
||||
element.remove();
|
||||
});
|
||||
});
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"test-router": TestRouter;
|
||||
"test-immediate-panel": ImmediatePanel;
|
||||
"test-deferred-panel": DeferredPanel;
|
||||
"test-loaded-panel": LoadedPanel;
|
||||
"test-prop-router": PropRouter;
|
||||
"test-prop-panel": PropPanel;
|
||||
"test-swap-router": SwapRouter;
|
||||
"test-swap-first-panel": SwapFirstPanel;
|
||||
"test-swap-second-panel": SwapSecondPanel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2561,13 +2561,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@emnapi/core@npm:1.11.2":
|
||||
version: 1.11.2
|
||||
resolution: "@emnapi/core@npm:1.11.2"
|
||||
"@emnapi/core@npm:1.11.3":
|
||||
version: 1.11.3
|
||||
resolution: "@emnapi/core@npm:1.11.3"
|
||||
dependencies:
|
||||
"@emnapi/wasi-threads": "npm:1.2.2"
|
||||
"@emnapi/wasi-threads": "npm:1.2.3"
|
||||
tslib: "npm:^2.4.0"
|
||||
checksum: 10/b3e9693f79ca1d8bb90cb8a950150c7738f54eca0ae1849d3d5103a87ce4e388c2669fb253cc123d3449ad2ae12583435455dbdc0270b1e9efb0cfd502c952b4
|
||||
checksum: 10/6ed871d5bbd0f6e25a09fe91583e886e4b90ae9344fa9292c78f0891b0b2a7d84899ea65a7d576450b9ac50440c698dad1907907e3d686ec9460ed876a9363a2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2589,12 +2589,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@emnapi/runtime@npm:1.11.2":
|
||||
version: 1.11.2
|
||||
resolution: "@emnapi/runtime@npm:1.11.2"
|
||||
"@emnapi/runtime@npm:1.11.3":
|
||||
version: 1.11.3
|
||||
resolution: "@emnapi/runtime@npm:1.11.3"
|
||||
dependencies:
|
||||
tslib: "npm:^2.4.0"
|
||||
checksum: 10/280a219d3bf302615dabaaa248223b0e5fe9c49bacf0987dd958ca37ab425b62cf05287053cb188e711681418aaa5e447eaed6b62a0848c0a1303b2707864600
|
||||
checksum: 10/ec834cc0fe248e06dd84f6dee10162133f8a692636189e99aa992303c791d4be1679536ee91aeee6661c4478609768cee9c085acd858caa92784c67acaab44a5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2616,6 +2616,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@emnapi/wasi-threads@npm:1.2.3":
|
||||
version: 1.2.3
|
||||
resolution: "@emnapi/wasi-threads@npm:1.2.3"
|
||||
dependencies:
|
||||
tslib: "npm:^2.4.0"
|
||||
checksum: 10/d9fef5c321a6df1988e813e0b621ed1e01d274c23d3028ff9511af97f8935adc921ae60a35167ccf4e0414fecc040b66b9b13458e7c00cfaad622457e1b03529
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint-community/eslint-utils@npm:^4.8.0, @eslint-community/eslint-utils@npm:^4.9.1":
|
||||
version: 4.9.1
|
||||
resolution: "@eslint-community/eslint-utils@npm:4.9.1"
|
||||
@@ -4756,110 +4765,110 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-darwin-arm64@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-darwin-arm64@npm:2.1.7"
|
||||
"@rspack/binding-darwin-arm64@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-darwin-arm64@npm:2.1.8"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-darwin-x64@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-darwin-x64@npm:2.1.7"
|
||||
"@rspack/binding-darwin-x64@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-darwin-x64@npm:2.1.8"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-linux-arm64-gnu@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-linux-arm64-gnu@npm:2.1.7"
|
||||
"@rspack/binding-linux-arm64-gnu@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-linux-arm64-gnu@npm:2.1.8"
|
||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-linux-arm64-musl@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-linux-arm64-musl@npm:2.1.7"
|
||||
"@rspack/binding-linux-arm64-musl@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-linux-arm64-musl@npm:2.1.8"
|
||||
conditions: os=linux & cpu=arm64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-linux-riscv64-gnu@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-linux-riscv64-gnu@npm:2.1.7"
|
||||
"@rspack/binding-linux-riscv64-gnu@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-linux-riscv64-gnu@npm:2.1.8"
|
||||
conditions: os=linux & cpu=riscv64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-linux-riscv64-musl@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-linux-riscv64-musl@npm:2.1.7"
|
||||
"@rspack/binding-linux-riscv64-musl@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-linux-riscv64-musl@npm:2.1.8"
|
||||
conditions: os=linux & cpu=riscv64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-linux-x64-gnu@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-linux-x64-gnu@npm:2.1.7"
|
||||
"@rspack/binding-linux-x64-gnu@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-linux-x64-gnu@npm:2.1.8"
|
||||
conditions: os=linux & cpu=x64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-linux-x64-musl@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-linux-x64-musl@npm:2.1.7"
|
||||
"@rspack/binding-linux-x64-musl@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-linux-x64-musl@npm:2.1.8"
|
||||
conditions: os=linux & cpu=x64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-wasm32-wasi@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-wasm32-wasi@npm:2.1.7"
|
||||
"@rspack/binding-wasm32-wasi@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-wasm32-wasi@npm:2.1.8"
|
||||
dependencies:
|
||||
"@emnapi/core": "npm:1.11.2"
|
||||
"@emnapi/runtime": "npm:1.11.2"
|
||||
"@emnapi/core": "npm:1.11.3"
|
||||
"@emnapi/runtime": "npm:1.11.3"
|
||||
"@napi-rs/wasm-runtime": "npm:1.1.6"
|
||||
conditions: cpu=wasm32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-win32-arm64-msvc@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-win32-arm64-msvc@npm:2.1.7"
|
||||
"@rspack/binding-win32-arm64-msvc@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-win32-arm64-msvc@npm:2.1.8"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-win32-ia32-msvc@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-win32-ia32-msvc@npm:2.1.7"
|
||||
"@rspack/binding-win32-ia32-msvc@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-win32-ia32-msvc@npm:2.1.8"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-win32-x64-msvc@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-win32-x64-msvc@npm:2.1.7"
|
||||
"@rspack/binding-win32-x64-msvc@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-win32-x64-msvc@npm:2.1.8"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding@npm:2.1.7"
|
||||
"@rspack/binding@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding@npm:2.1.8"
|
||||
dependencies:
|
||||
"@rspack/binding-darwin-arm64": "npm:2.1.7"
|
||||
"@rspack/binding-darwin-x64": "npm:2.1.7"
|
||||
"@rspack/binding-linux-arm64-gnu": "npm:2.1.7"
|
||||
"@rspack/binding-linux-arm64-musl": "npm:2.1.7"
|
||||
"@rspack/binding-linux-riscv64-gnu": "npm:2.1.7"
|
||||
"@rspack/binding-linux-riscv64-musl": "npm:2.1.7"
|
||||
"@rspack/binding-linux-x64-gnu": "npm:2.1.7"
|
||||
"@rspack/binding-linux-x64-musl": "npm:2.1.7"
|
||||
"@rspack/binding-wasm32-wasi": "npm:2.1.7"
|
||||
"@rspack/binding-win32-arm64-msvc": "npm:2.1.7"
|
||||
"@rspack/binding-win32-ia32-msvc": "npm:2.1.7"
|
||||
"@rspack/binding-win32-x64-msvc": "npm:2.1.7"
|
||||
"@rspack/binding-darwin-arm64": "npm:2.1.8"
|
||||
"@rspack/binding-darwin-x64": "npm:2.1.8"
|
||||
"@rspack/binding-linux-arm64-gnu": "npm:2.1.8"
|
||||
"@rspack/binding-linux-arm64-musl": "npm:2.1.8"
|
||||
"@rspack/binding-linux-riscv64-gnu": "npm:2.1.8"
|
||||
"@rspack/binding-linux-riscv64-musl": "npm:2.1.8"
|
||||
"@rspack/binding-linux-x64-gnu": "npm:2.1.8"
|
||||
"@rspack/binding-linux-x64-musl": "npm:2.1.8"
|
||||
"@rspack/binding-wasm32-wasi": "npm:2.1.8"
|
||||
"@rspack/binding-win32-arm64-msvc": "npm:2.1.8"
|
||||
"@rspack/binding-win32-ia32-msvc": "npm:2.1.8"
|
||||
"@rspack/binding-win32-x64-msvc": "npm:2.1.8"
|
||||
dependenciesMeta:
|
||||
"@rspack/binding-darwin-arm64":
|
||||
optional: true
|
||||
@@ -4885,15 +4894,15 @@ __metadata:
|
||||
optional: true
|
||||
"@rspack/binding-win32-x64-msvc":
|
||||
optional: true
|
||||
checksum: 10/1400d8553d2122850fc43a45b69828bcdcbccf7715381a627802e160da6f93e90ca41d0280a69d175894bc29a428a374c80157b874883363b998090dc0aff33c
|
||||
checksum: 10/74c73ac543059fa62365557ca949ef7a84e1cebc42fe51a343126c875d90f557b35a52b33bacd9498d9231e8fdb8ab13907988e1fd39131ffbd56becb4260785
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/core@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/core@npm:2.1.7"
|
||||
"@rspack/core@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/core@npm:2.1.8"
|
||||
dependencies:
|
||||
"@rspack/binding": "npm:2.1.7"
|
||||
"@rspack/binding": "npm:2.1.8"
|
||||
peerDependencies:
|
||||
"@module-federation/runtime-tools": ^0.24.1 || ^2.0.0
|
||||
"@swc/helpers": ^0.5.23
|
||||
@@ -4902,7 +4911,7 @@ __metadata:
|
||||
optional: true
|
||||
"@swc/helpers":
|
||||
optional: true
|
||||
checksum: 10/84526df889fa2813cfa7dfbed71b713c47c2010b1f55da26db698a87ea11f3f433a931ec29616ff300cb63d0ea27cbe7d2b40df64abf45f9b1a484b59c94ca1a
|
||||
checksum: 10/c818257225a24feb22af81eb3ed7d02d4b74d6e6cb70689c8cbcd34e3facfcfe833dfe4078e0a35277e926f431cae9fd7f2101c3944b396a72baa7cf3ce99d19
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4918,9 +4927,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/dev-server@npm:2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "@rspack/dev-server@npm:2.1.0"
|
||||
"@rspack/dev-server@npm:2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "@rspack/dev-server@npm:2.2.0"
|
||||
dependencies:
|
||||
"@rspack/dev-middleware": "npm:^2.0.3"
|
||||
peerDependencies:
|
||||
@@ -4929,7 +4938,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
selfsigned:
|
||||
optional: true
|
||||
checksum: 10/402d4f96c60beaba354081a36b053c3cf7c1dda7fddab791031dc4206b9873b98437895d5a6a68247e07cb8a5922a1770143c560772dfdaa00ba90a5396251d8
|
||||
checksum: 10/edfc57223fad64c43c96be95b0b6650194ac2e02d929bef649d524faa9c618233afe8f970ac2e21c77e1b92f6c65dac20f06817e73a9bd661f43c73057ae6b6b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5762,105 +5771,105 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/eslint-plugin@npm:8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.65.0"
|
||||
"@typescript-eslint/eslint-plugin@npm:8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.66.0"
|
||||
dependencies:
|
||||
"@eslint-community/regexpp": "npm:^4.12.2"
|
||||
"@typescript-eslint/scope-manager": "npm:8.65.0"
|
||||
"@typescript-eslint/type-utils": "npm:8.65.0"
|
||||
"@typescript-eslint/utils": "npm:8.65.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.65.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.66.0"
|
||||
"@typescript-eslint/type-utils": "npm:8.66.0"
|
||||
"@typescript-eslint/utils": "npm:8.66.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.66.0"
|
||||
ignore: "npm:^7.0.5"
|
||||
natural-compare: "npm:^1.4.0"
|
||||
ts-api-utils: "npm:^2.5.0"
|
||||
peerDependencies:
|
||||
"@typescript-eslint/parser": ^8.65.0
|
||||
"@typescript-eslint/parser": ^8.66.0
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/20b1e5fd0c01d450c345750582e4911affef8ba934b2b78953ff593102d2a01f21c5f6469e13239fdd6a0e30152d6122906e7623f53aa8c937c6faae9407be47
|
||||
checksum: 10/bb144ff0c27592b6a53d964fe1e2145dd0737f5fe50fe7dcd88217ca5ac4e470d7965d97745a63de16252ac2880d00732a472b9cec14d7d6929ebda97e7bcbb1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/parser@npm:8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "@typescript-eslint/parser@npm:8.65.0"
|
||||
"@typescript-eslint/parser@npm:8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "@typescript-eslint/parser@npm:8.66.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager": "npm:8.65.0"
|
||||
"@typescript-eslint/types": "npm:8.65.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.65.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.65.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.66.0"
|
||||
"@typescript-eslint/types": "npm:8.66.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.66.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.66.0"
|
||||
debug: "npm:^4.4.3"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/55f68666953c02c8adae35a46076848da6456181b1849a28ec836a5866ca37b902f475fb4c32ba7aa3a6a7e5d66828df8859edec297da09181fb937aeea30f8e
|
||||
checksum: 10/a0451abe17d2eeff6ff2e1bf637d5ea19b400378d98f4511672e0f30dc5a7971d03352e32764927176c3632f245d5f85e8b28a0a3e66d020effcf0c61624e9b6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/project-service@npm:8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "@typescript-eslint/project-service@npm:8.65.0"
|
||||
"@typescript-eslint/project-service@npm:8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "@typescript-eslint/project-service@npm:8.66.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.65.0"
|
||||
"@typescript-eslint/types": "npm:^8.65.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.66.0"
|
||||
"@typescript-eslint/types": "npm:^8.66.0"
|
||||
debug: "npm:^4.4.3"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/915662449a66d90f03661a805f7c62a5efaa8f7887671272e08b684b41edab51a9021f47aac4d78001a0f87960e8587adc037424d56f13de883e0ce699e7ca55
|
||||
checksum: 10/e8a71f69ee7f4bd9ca1c6a0a5110361b9927d17df2f41cdf6d042203b49aabfbddece79dec7882fde8dc016b6278671a2ba3f7bf08e5ae241582627b68be3a8b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/scope-manager@npm:8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.65.0"
|
||||
"@typescript-eslint/scope-manager@npm:8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.66.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.65.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.65.0"
|
||||
checksum: 10/038e208c907aa45fe5bb7168e1dccf89c1fc6678d1715a9c04f4b332d4e79ab719b5b43a4e0c2d5a183ea863813ff59fda040b2717fe5517468453af6b99a50a
|
||||
"@typescript-eslint/types": "npm:8.66.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.66.0"
|
||||
checksum: 10/f2024cd2819dc3b967f32089ea1a0b9099acabf12860699432f0ba802d75b01ed35ea488a50576cae544813d5cbe9bd9ee5815bc4f11cefc89fe2fb24559238f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.65.0, @typescript-eslint/tsconfig-utils@npm:^8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.65.0"
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.66.0, @typescript-eslint/tsconfig-utils@npm:^8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.66.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/f88253a4df1d599a1bebeeb403611538485e22c52213f2f2b9c435bde8ebce64645d6bb985c900b01ef221032835fcd4f6fea4b94d178220c18de5d93af905c4
|
||||
checksum: 10/271b28660edc9e6272ae07a91f8da2c69e8a6b009e8588f1009c485c11fe4e66e09356d50dbeabf8e6a44c95c8adeb630f1b6089bdd68aec2512d0036cef7704
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/type-utils@npm:8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.65.0"
|
||||
"@typescript-eslint/type-utils@npm:8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.66.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.65.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.65.0"
|
||||
"@typescript-eslint/utils": "npm:8.65.0"
|
||||
"@typescript-eslint/types": "npm:8.66.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.66.0"
|
||||
"@typescript-eslint/utils": "npm:8.66.0"
|
||||
debug: "npm:^4.4.3"
|
||||
ts-api-utils: "npm:^2.5.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/d52e0c341c9731d8f3bfd2f475bbcd5a5632434e11fc39e8e21acb706145bedb8c6c1998b8ced73e132b43179dd95f2c318effc36c9a1dd61f14546b07b25852
|
||||
checksum: 10/6d3c55a591b96cbac4ac845ad1d281bac54df12a631ca550370d266aed30760fa9d498e44b938e454941930e3decda820c6e544e0748254c5089425d120e1ed1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:8.65.0, @typescript-eslint/types@npm:^8.56.0, @typescript-eslint/types@npm:^8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "@typescript-eslint/types@npm:8.65.0"
|
||||
checksum: 10/a6fc10a733adbb98bbb9c312c8d99791e1a2fd3efef5c2a5b51da1c166aac3db4427c30bf32059808abe305a289f820bf610683914e897baeb18315af6a1d16c
|
||||
"@typescript-eslint/types@npm:8.66.0, @typescript-eslint/types@npm:^8.56.0, @typescript-eslint/types@npm:^8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "@typescript-eslint/types@npm:8.66.0"
|
||||
checksum: 10/de1ea79e7056c11e38e4001a899b88511793a0aa1702508af42ee179bc7303bed2eb02f177a7ae438f20eab00aee9407c7e842bec6fc5b7bb1fdcab4a7cd5c4b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/typescript-estree@npm:8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.65.0"
|
||||
"@typescript-eslint/typescript-estree@npm:8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.66.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/project-service": "npm:8.65.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.65.0"
|
||||
"@typescript-eslint/types": "npm:8.65.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.65.0"
|
||||
"@typescript-eslint/project-service": "npm:8.66.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.66.0"
|
||||
"@typescript-eslint/types": "npm:8.66.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.66.0"
|
||||
debug: "npm:^4.4.3"
|
||||
minimatch: "npm:^10.2.2"
|
||||
semver: "npm:^7.7.3"
|
||||
@@ -5868,32 +5877,32 @@ __metadata:
|
||||
ts-api-utils: "npm:^2.5.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/711fdb5eff67ff34437c56a1a661b16a2e1d6bcd96c9f96196c4242b8d4ddaea8e835ca8badd340c703e043d8dfe8cfca90b32eca60069a4f1d2880afdb670fb
|
||||
checksum: 10/0834efcdd4468c0dd954089daf26b40175e204402bd4075b06cd910786bb6de7a15aec4e79e40f0980f39fce5bfd9c871ec8fcc6871a8895a34a273bb187fd26
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/utils@npm:8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "@typescript-eslint/utils@npm:8.65.0"
|
||||
"@typescript-eslint/utils@npm:8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "@typescript-eslint/utils@npm:8.66.0"
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils": "npm:^4.9.1"
|
||||
"@typescript-eslint/scope-manager": "npm:8.65.0"
|
||||
"@typescript-eslint/types": "npm:8.65.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.65.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.66.0"
|
||||
"@typescript-eslint/types": "npm:8.66.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.66.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/c1dcd555b58aef1e066164978335e521809acac36b56bd6a6dae62cffae80f3ea5f43527506be76dfef0fe3d4c8382a24355a28867c4904b0a7729691ba45656
|
||||
checksum: 10/e7a24ee0f35d58b4392daa03fb1ea37ab7e4519949a034be89151df1b0e9fc416831f50cb69f54150272e252cba350964632ee9bfbdb7e62ac599cc731de56e1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/visitor-keys@npm:8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.65.0"
|
||||
"@typescript-eslint/visitor-keys@npm:8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.66.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.65.0"
|
||||
"@typescript-eslint/types": "npm:8.66.0"
|
||||
eslint-visitor-keys: "npm:^5.0.0"
|
||||
checksum: 10/e7f86d21f0bf03ca7cff9fa9428aab98620564d15fb06c9f56a294c13cee324624d42f9115f3d76fbad92266220479cca334b5c66562f885da5c4d2bda3d87b3
|
||||
checksum: 10/d47e936b10ec94a96f69f77a62463f4634fe72d12d42dd302d7777f7b98689468e0b67c538a52232b723695208f1b3fdeae5745eb3e71a70a90818fdd827a0bc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9927,8 +9936,8 @@ __metadata:
|
||||
"@playwright/test": "npm:1.62.1"
|
||||
"@replit/codemirror-indentation-markers": "npm:6.5.3"
|
||||
"@rsdoctor/rspack-plugin": "npm:1.6.1"
|
||||
"@rspack/core": "npm:2.1.7"
|
||||
"@rspack/dev-server": "npm:2.1.0"
|
||||
"@rspack/core": "npm:2.1.8"
|
||||
"@rspack/dev-server": "npm:2.2.0"
|
||||
"@swc/helpers": "npm:0.5.23"
|
||||
"@thomasloven/round-slider": "npm:0.6.0"
|
||||
"@tsparticles/engine": "npm:4.3.2"
|
||||
@@ -10008,7 +10017,7 @@ __metadata:
|
||||
lodash.template: "npm:4.18.1"
|
||||
luxon: "npm:3.7.2"
|
||||
map-stream: "npm:0.0.7"
|
||||
marked: "npm:18.0.7"
|
||||
marked: "npm:18.0.9"
|
||||
memoize-one: "npm:6.0.0"
|
||||
minify-literals: "npm:2.1.0"
|
||||
node-vibrant: "npm:4.0.4"
|
||||
@@ -10031,7 +10040,7 @@ __metadata:
|
||||
tinykeys: "patch:tinykeys@npm%3A4.0.0#~/.yarn/patches/tinykeys-npm-4.0.0-a6ca3fd771.patch"
|
||||
ts-lit-plugin: "npm:2.0.2"
|
||||
typescript: "npm:6.0.3"
|
||||
typescript-eslint: "npm:8.65.0"
|
||||
typescript-eslint: "npm:8.66.0"
|
||||
vite-tsconfig-paths: "npm:6.1.1"
|
||||
vitest: "npm:4.1.10"
|
||||
webpack-stats-plugin: "npm:1.1.3"
|
||||
@@ -11651,12 +11660,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"marked@npm:18.0.7":
|
||||
version: 18.0.7
|
||||
resolution: "marked@npm:18.0.7"
|
||||
"marked@npm:18.0.9":
|
||||
version: 18.0.9
|
||||
resolution: "marked@npm:18.0.9"
|
||||
bin:
|
||||
marked: bin/marked.js
|
||||
checksum: 10/7ea7b8556a9e8cab2881b194815a7550c61d77639c6b8f8d9022f96790fd2b289b2ea28969cea6e01c24c3b8ec822373288ee02b7c50f178bc3ec79aa42d05f1
|
||||
checksum: 10/99d337c50acd57034734f8460f25b28e9658b15627f950092707cb443b840f0bd29fe343bf211b948e643303c34abdb5b5a12cf5245a846635750697524bb747
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -14883,18 +14892,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript-eslint@npm:8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "typescript-eslint@npm:8.65.0"
|
||||
"typescript-eslint@npm:8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "typescript-eslint@npm:8.66.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/eslint-plugin": "npm:8.65.0"
|
||||
"@typescript-eslint/parser": "npm:8.65.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.65.0"
|
||||
"@typescript-eslint/utils": "npm:8.65.0"
|
||||
"@typescript-eslint/eslint-plugin": "npm:8.66.0"
|
||||
"@typescript-eslint/parser": "npm:8.66.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.66.0"
|
||||
"@typescript-eslint/utils": "npm:8.66.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/5b2242f59005afdd57190849be7df2e86ce33b007fa0bf605f700ad0e38ea14fc14c48deafdf4a039614c1f012b71c61a19edb27f76d52fdc924cde476a100d1
|
||||
checksum: 10/e76da9c684e4fe012b49a5120dd57553df0788cf1cffcbd5dc72c98de33e549f306495e710292f111ee058366b1e5d8d603c2bb14385b12bb35edc7bbf13ad55
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user