Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ The `current` object controls the display of current weather information and att
| :--------------------- | :------ | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `extra_attribute` | string | optional | The extra attribute to show below the weather forecast. Currently supports, `precipitation_probability`, `wind_direction` and `wind_bearing` |
| `hourly_group_size` | number | `1` | Number of hours to group together in hourly forecast. Group data will be aggregated per forecast attribute. |
| `hourly_slots` | number | optional | Limit the number of hourly forecast entries to show. Defaults to unlimited. |
| `daily_slots` | number | optional | Limit the number of daily forecast entries to show. Defaults to unlimited. |
| `mode` | string | `simple` | Forecast display mode. `simple`: Horizontal scrollable list of forecast entries. `chart`: Visualize temperature and precipitation trends on a line/bar chart. |
| `scroll_to_selected` | boolean | `false` | Automatically scrolls to the first hourly forecast of the selected date when switching to hourly view, and returns to the first daily entry when switching back. |
| `show_sun_times` | boolean | `true` | Displays sunrise and sunset times in the hourly forecast, and uses specific icons to visualize clear night conditions. |
Expand Down
18 changes: 18 additions & 0 deletions src/editor/weather-forecast-card-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,16 @@ export class WeatherForecastCardEditor
selector: { number: { min: 1, max: 4 } },
default: 1,
},
{
name: "forecast.hourly_slots",
optional: true,
selector: { number: { min: 0 } },
},
{
name: "forecast.daily_slots",
optional: true,
selector: { number: { min: 0 } },
},
] as const;

private _interactionsSchema = (): HaFormSchema[] =>
Expand Down Expand Up @@ -430,6 +440,10 @@ export class WeatherForecastCardEditor
return "Use color thresholds";
case "forecast.hourly_group_size":
return "Hourly forecast group size";
case "forecast.hourly_slots":
return "Hourly forecast slots";
case "forecast.daily_slots":
return "Daily forecast slots";
case "forecast_interactions":
return `${this.hass!.localize("ui.card.weather.forecast")} ${(
this.hass!.localize(
Expand Down Expand Up @@ -473,6 +487,10 @@ export class WeatherForecastCardEditor
return "Replaces solid temperature lines with a gradient based on actual values when using forecast chart mode.";
case "forecast.hourly_group_size":
return "Aggregate hourly forecast data into groups to reduce the number of forecast entries shown.";
case "forecast.hourly_slots":
return "Limit the number of hourly forecast entries to show.";
case "forecast.daily_slots":
return "Limit the number of daily forecast entries to show.";
case "name":
return "Overrides the friendly name of the entity.";
case "show_condition_effects":
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export interface WeatherForecastCardForecastConfig {
mode?: ForecastMode;
show_sun_times?: boolean;
hourly_group_size?: number;
hourly_slots?: number;
daily_slots?: number;
scroll_to_selected?: boolean;
use_color_thresholds?: boolean;
}
Expand Down
18 changes: 18 additions & 0 deletions src/weather-forecast-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,14 @@ export class WeatherForecastCard extends LitElement {
}

this._dailyForecastData = dailyForecastData?.forecast;

if (this._dailyForecastData && this.config?.forecast?.daily_slots != null) {
this._dailyForecastData = this._dailyForecastData.slice(
0,
this.config.forecast.daily_slots
);
}

const hourlyGroupSize = this.config?.forecast?.hourly_group_size || 0;

if (hourlyGroupSize > 1 && hourlyForecastData?.forecast) {
Expand All @@ -343,6 +351,16 @@ export class WeatherForecastCard extends LitElement {
this._hourlyForecastData = hourlyForecastData?.forecast;
}

if (
this._hourlyForecastData &&
this.config?.forecast?.hourly_slots != null
) {
this._hourlyForecastData = this._hourlyForecastData.slice(
0,
this.config.forecast.hourly_slots
);
}

// Recalculate layout if the number of items changed
const newLength = this.getCurrentForecast().length;

Expand Down
96 changes: 96 additions & 0 deletions test/weather-forecast-card.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,100 @@ describe("weather-forecast-card", () => {
"0°C / 0°C"
);
});

describe("should respect forecast limits", () => {
it("should respect daily_slots limit", async () => {
const config: WeatherForecastCardConfig = {
type: "custom:weather-forecast-card",
entity: "weather.demo",
forecast: {
daily_slots: 3,
},
};

card.setConfig(config);
await card.updateComplete;
await new Promise((resolve) => setTimeout(resolve, 200));

// @ts-expect-error: accessing private property
expect(card._dailyForecastData?.length).toBe(3);
});

it("should respect hourly_slots limit", async () => {
const config: WeatherForecastCardConfig = {
type: "custom:weather-forecast-card",
entity: "weather.demo",
default_forecast: "hourly",
forecast: {
hourly_slots: 10,
},
};

card.setConfig(config);
await card.updateComplete;
await new Promise((resolve) => setTimeout(resolve, 200));

// @ts-expect-error: accessing private property
expect(card._hourlyForecastData?.length).toBe(10);
});

it("should respect hourly_slots limit with hourly_group_size", async () => {
const config: WeatherForecastCardConfig = {
type: "custom:weather-forecast-card",
entity: "weather.demo",
default_forecast: "hourly",
forecast: {
hourly_group_size: 3,
hourly_slots: 5,
},
};

card.setConfig(config);
await card.updateComplete;
await new Promise((resolve) => setTimeout(resolve, 200));

// Total hourly items = 72. Grouped by 3 = 24 items.
// Limited by 5 slots = 5 items.
// @ts-expect-error: accessing private property
expect(card._hourlyForecastData?.length).toBe(5);
});

it("should show all items when slots are not defined", async () => {
const config: WeatherForecastCardConfig = {
type: "custom:weather-forecast-card",
entity: "weather.demo",
};

card.setConfig(config);
await card.updateComplete;
await new Promise((resolve) => setTimeout(resolve, 200));

// @ts-expect-error: accessing private property
expect(card._dailyForecastData?.length).toBe(TEST_FORECAST_DAILY.length);
// @ts-expect-error: accessing private property
expect(card._hourlyForecastData?.length).toBe(
TEST_FORECAST_HOURLY.length
);
});

it("should support 0 slots", async () => {
const config: WeatherForecastCardConfig = {
type: "custom:weather-forecast-card",
entity: "weather.demo",
forecast: {
daily_slots: 0,
hourly_slots: 0,
},
};

card.setConfig(config);
await card.updateComplete;
await new Promise((resolve) => setTimeout(resolve, 200));

// @ts-expect-error: accessing private property
expect(card._dailyForecastData?.length).toBe(0);
// @ts-expect-error: accessing private property
expect(card._hourlyForecastData?.length).toBe(0);
});
});
});