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: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ inside Home Assistant, so say what you actually looked at.
-->

- [ ] `npm run lint`, `npm test` and `npm run build` pass
- [ ] `dist/chartdeck-card.js` is rebuilt and committed (CI fails otherwise)
- [ ] `dist/` is rebuilt and committed (CI fails otherwise)
- [ ] Anything new can be set up from the visual editor, not only in YAML
- [ ] Tried in a real Home Assistant dashboard, in both normal and edit mode, light and dark theme
- [ ] Checked the browser console for errors
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-announcement.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ jobs:
``,
isPrerelease
? `This is a pre-release — it may contain bugs. To test it early, go to **Settings → Devices & services → Marketplace**, open Chartdeck, enable its **Pre-release** entity and turn the switch on (on HACS, before Home Assistant 2026.11: ⋮ → **Redownload**, **Show beta versions**) — or install it manually.`
: `Update from the **Marketplace** (HACS before Home Assistant 2026.11), or download \`chartdeck-card.js\` from the [latest release](${url}) and copy it to \`/config/www/\`.`,
: `Update from the **Marketplace** (HACS before Home Assistant 2026.11), or download every \`.js\` file from the [latest release](${url}) and copy them to \`/config/www/\`.`,
``,
`After updating, **hard-refresh your browser** (Ctrl/Cmd+Shift+R) so the new card module loads instead of a cached one.`,
``,
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ jobs:
id: tagged
run: echo "tag=$(git tag --points-at HEAD | head -n 1)" >> "$GITHUB_OUTPUT"
- name: Attest the bundle's provenance
# Signed statement that dist/chartdeck-card.js was built by this workflow from
# Signed statement that every file in dist/ was built by this workflow from
# this commit, checkable with `gh attestation verify`.
if: steps.tagged.outputs.tag != ''
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: dist/chartdeck-card.js
subject-path: dist/*.js

# The release above is published with GITHUB_TOKEN, which starts no other workflows,
# so the release: published trigger never fires. Call the announcement directly.
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/coverage/
/.tmp/
dist/*
!dist/chartdeck-card.js
!dist/*.js
!dist/*.js.map
*.log
.DS_Store
7 changes: 5 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,11 @@ real Home Assistant before release.

## Translations

Copy `src/locales/en.json` to `src/locales/<code>.json`, translate the values, and register it
in `src/localize.ts`. No git? Open an issue with the language and your translated file pasted
Chartdeck speaks English, German, Spanish, French, Dutch and Portuguese. To add a language, copy
`src/locales/en.json` to `src/locales/<code>.json`, translate the values (leave every
`{placeholder}` as it is), and add a line for it to `LOADERS` in `src/localize.ts`. Each
language builds into a file of its own that loads only for people using it. A string left out
shows in English. To fix a translation, edit the value in that language's file. No git? Open an issue with the language and your translated file pasted
in, and it will be wired in for you.

## Reporting something
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ This page is the summary. What changed in each version is on the [releases page]
fast on wall tablets, and follows your Home Assistant theme.
- **Limits from anywhere.** Minimum, maximum and target can each be a number or another
entity.
- **In your language.** The card and its editor follow the language in your Home Assistant
profile: English, German, Spanish, French, Dutch or Portuguese.

## Styles

Expand Down Expand Up @@ -99,7 +101,8 @@ Chartdeck is in the Home Assistant Marketplace:
On Home Assistant older than 2026.11, the Marketplace is still HACS: open HACS, search for
*Chartdeck* and choose **Download**.

To install by hand, save [chartdeck-card.js][latest-release] to `<config>/www/` and add
To install by hand, save every `.js` file of the [latest release][latest-release] to
`<config>/www/` *(v2.1.0+: each translation is a file of its own beside the main one)* and add
`/local/chartdeck-card.js` as a dashboard resource of type `module`. The
[Installation][wiki-installation] page has the detail.

Expand Down
2 changes: 2 additions & 0 deletions dist/chartdeck-card.de-aHFDhWpe.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.de-aHFDhWpe.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.es-Bjbhmsp-.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.es-Bjbhmsp-.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.fr-Bux47UXp.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.fr-Bux47UXp.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2,057 changes: 1,029 additions & 1,028 deletions dist/chartdeck-card.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.js.map

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.nl-DvVzFIch.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.nl-DvVzFIch.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions dist/chartdeck-card.pt-D8OdPLlD.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/chartdeck-card.pt-D8OdPLlD.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"scripts": {
"build": "npm run lint && npm run rollup",
"lint": "eslint .",
"rollup": "rollup -c",
"rollup": "node scripts/clean-dist.mjs && rollup -c",
"start": "rollup -c --watch",
"test": "vitest run",
"test:watch": "vitest",
Expand Down Expand Up @@ -35,6 +35,7 @@
"lit": "^3.3.3"
},
"devDependencies": {
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.3",
"@rollup/plugin-terser": "^1.0.2",
"@rollup/plugin-typescript": "^12.3.1",
Expand Down
5 changes: 3 additions & 2 deletions release.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,14 @@ const plugins = [
[
'@semantic-release/git',
{
assets: ['README.md', 'package.json', 'package-lock.json', 'dist/chartdeck-card.js'],
assets: ['README.md', 'package.json', 'package-lock.json', 'dist/*.js', 'dist/*.js.map'],
},
],
[
'@semantic-release/github',
{
assets: 'dist/*.js',
// The main file, one file per language beside it, and a source map for each.
assets: ['dist/*.js', 'dist/*.js.map'],
// No "released in" comments on issues and pull requests. Turning them on also needs
// issues: write and pull-requests: write in .github/workflows/release.yml.
successComment: false,
Expand Down
17 changes: 14 additions & 3 deletions rollup.config.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import typescript from '@rollup/plugin-typescript';
import nodeResolve from '@rollup/plugin-node-resolve';
import json from '@rollup/plugin-json';
import terser from '@rollup/plugin-terser';
import serve from 'rollup-plugin-serve';
import { readFileSync } from 'fs';
Expand All @@ -21,14 +22,24 @@ const dev = process.env.ROLLUP_WATCH;
export default {
input: 'src/chartdeck-card.ts',
output: {
file: 'dist/chartdeck-card.js',
dir: 'dist',
format: 'es',
sourcemap: dev ? true : false,
entryFileNames: 'chartdeck-card.js',
// Every language but English is a chunk of its own, loaded by the main file with a relative
// import, so it sits beside it under a name HACS downloads along with the main file: every
// .js in dist/ travels. The hash changes with the content, so a browser that kept an old
// main file never loads a translation it was not built with.
chunkFileNames: 'chartdeck-card.[name]-[hash].js',
// Always, so a stack trace from somebody's console points at a TypeScript line. The map sits
// beside each file in dist/ and on the release; a browser that cannot fetch one simply shows
// the minified line.
sourcemap: true,
},
plugins: [
injectVersion,
nodeResolve(),
typescript({ tsconfig: './tsconfig.json', noEmit: false, declaration: false, sourceMap: !!dev }),
typescript({ tsconfig: './tsconfig.json', noEmit: false, declaration: false, sourceMap: true }),
json({ preferConst: true }),
dev &&
serve({
contentBase: ['./dist'],
Expand Down
16 changes: 16 additions & 0 deletions scripts/clean-dist.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env node
/*
* Empties dist/ of built files before a build. Each language is a chunk whose name carries a
* content hash, so without this a build after a translation change would leave the previous
* chunk behind as a stale file that the CI dist check then complains about.
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

const DIST = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'dist');
if (fs.existsSync(DIST)) {
for (const name of fs.readdirSync(DIST)) {
if (/\.js(\.map)?$/.test(name)) fs.unlinkSync(path.join(DIST, name));
}
}
6 changes: 1 addition & 5 deletions src/chart/annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,14 @@ import { toReading } from '../history/fetch';
import { boundaries, wallTime, zonedTime } from '../history/timezone';
import type { Zone } from '../history/timezone';
import type { Range } from '../history/types';
import { localize, registerStrings } from '../localize';
import { localize } from '../localize';
import type { ChartContext } from '../styles/index';
import type { CardConfig, HomeAssistant, ResolvedBand } from '../types';
import { formatDateTime } from './axes';
import { VIEW_H, VIEW_W } from './frame';
import type { ChartSeries, HistoryFrame } from './frame';
import type { PlotScales } from './grid';

registerStrings('en', {
annotation_at: '{label} at {time}',
});

/** What an annotation marks. */
export const ANNOTATION_TYPES = ['line', 'band', 'time', 'state', 'point'] as const;
export type AnnotationType = (typeof ANNOTATION_TYPES)[number];
Expand Down
15 changes: 0 additions & 15 deletions src/chart/category-plot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { linear } from './scale';
import { choice } from '../editor/chart-editors';
import type { FieldBinding } from '../editor/schemas';
import { formatNumber } from '../format';
import { registerStrings } from '../localize';
import type { ChartContext } from '../styles/index';
import type { CardConfig, HomeAssistant } from '../types';

Expand Down Expand Up @@ -76,20 +75,6 @@ export interface CategoryOptions {

const isNumber = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v);

registerStrings('en', {
editor_orientation: 'Direction',
editor_orientation_horizontal: 'Across',
editor_orientation_vertical: 'Upright',
editor_value_axis: 'Value labels',
editor_value_grid: 'Lines at each value label',
editor_value_min: 'Lowest value shown',
editor_value_max: 'Highest value shown',
dist_median: 'Median',
dist_middle: 'Middle half',
dist_range: 'Lowest – highest',
dist_count: 'Readings',
});

/** The editor fields every distribution chart shares (see `categorySchema`). */
export const CATEGORY_FIELDS: FieldBinding[] = [
{ name: 'orientation', path: ['orientation'], default: 'horizontal' },
Expand Down
9 changes: 1 addition & 8 deletions src/chart/drill.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
import { css, html, nothing } from 'lit';
import type { TemplateResult } from 'lit';
import { localize, registerStrings } from '../localize';
import { localize } from '../localize';
import type { ChartContext } from '../styles/index';
import type { CardConfig } from '../types';
import { findNode } from './hierarchy';
import type { TreeNode } from './hierarchy';

registerStrings('en', {
editor_drill_down: 'Tap a group to show what makes it up',
drill_into: '{label}. Show what makes it up',
drill_back: 'Back to {name}',
drill_top: 'All',
});

/**
* Drilling down: tapping a group in a hierarchy chart (a treemap box, a sunburst ring, an icicle
* cell) redraws the chart with that group as the whole, and a back button returns to the level
Expand Down
15 changes: 1 addition & 14 deletions src/chart/extremes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { FieldBinding } from '../editor/schemas';
import { formatNumber, withUnit } from '../format';
import type { Grouping } from '../history/group';
import type { Point, Range } from '../history/types';
import { languageOf, localize, registerStrings } from '../localize';
import { languageOf, localize } from '../localize';
import type { ChartContext } from '../styles/index';
import type { CardConfig, HomeAssistant } from '../types';
import { formatDateTime, formatTime, timeZoneOf } from './axes';
Expand All @@ -25,19 +25,6 @@ export interface ExtremesOptions {

export const DEFAULT_EXTREMES: ExtremesOptions = { min: false, max: false, average: 'none' };

registerStrings('en', {
extremes_high: 'High',
extremes_low: 'Low',
extremes_average: 'Avg',
editor_extremes_max: 'Mark the highest reading',
editor_extremes_min: 'Mark the lowest reading',
editor_extremes_average: 'Average',
editor_extremes_average_none: "Don't show",
editor_extremes_average_line: 'Line across the chart',
editor_extremes_average_figure: 'Figure only',
editor_extremes: 'Mark highest, lowest and average',
});

type Raw = CardConfig & { extremes?: unknown };

/** Throw a readable error for a bad `extremes` option, on the card or an entity. */
Expand Down
24 changes: 1 addition & 23 deletions src/chart/frame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { pauseStore, storeFor } from '../history/store';
import { boundaries, startOf, wallTime, zoneFor } from '../history/timezone';
import type { Zone } from '../history/timezone';
import type { Point, Range, Source } from '../history/types';
import { localize, registerStrings } from '../localize';
import { localize } from '../localize';
import type { ResolvedEntity } from '../resolve';
import type { ChartContext } from '../styles/index';
import type { CardConfig } from '../types';
Expand Down Expand Up @@ -53,28 +53,6 @@ export interface HistoryFrame {
group?: Grouping;
}

registerStrings('en', {
period_previous: 'Previous period',
period_next: 'Next period',
period_return: 'Back to {label}',
period_today: 'Today',
period_yesterday: 'Yesterday',
period_this_week: 'This week',
period_last_week: 'Last week',
period_this_month: 'This month',
period_last_month: 'Last month',
period_this_year: 'This year',
period_last_year: 'Last year',
period_last_hour: 'Last hour',
period_last_hours: 'Last {n} hours',
period_last_day: 'Last day',
period_last_days: 'Last {n} days',
period_last_week_rolling: 'Last 7 days',
period_last_weeks: 'Last {n} weeks',
period_last_month_rolling: 'Last month',
period_last_months: 'Last {n} months',
});

/** Checks for the options every history chart shares. */
export function validateHistoryOptions(raw: CardConfig): void {
parsePeriod(raw.period as CardConfig['period']);
Expand Down
18 changes: 1 addition & 17 deletions src/chart/parts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { isInteractive } from '../actions';
import { baseColor } from '../bands';
import { SERIES_COLORS } from './frame';
import { formatNumber, withUnit } from '../format';
import { localize, registerStrings } from '../localize';
import { localize } from '../localize';
import type { ResolvedEntity } from '../resolve';
import type { ChartContext } from '../styles/index';
import type { CardConfig, HomeAssistant } from '../types';
Expand All @@ -15,22 +15,6 @@ import type { CardConfig, HomeAssistant } from '../types';
* section's form. They all draw from the entities' current values, so no history is involved.
*/

registerStrings('en', {
editor_color_by: 'Colour',
editor_color_by_series: 'One colour per entity',
editor_color_by_bands: 'By colour band',
editor_color_by_shades: 'Shades of one colour',
editor_shade_color: 'Shade colour (palette name or CSS colour)',
editor_show_percent: 'Show each share as a percentage',
editor_center: 'In the middle',
editor_center_total: 'Total',
editor_center_average: 'Average',
editor_center_none: 'Nothing',
parts_total: 'Total',
parts_average: 'Average',
parts_aria: '{style} of {names}',
});

/** Colours in order, for entities without their own `color` (the same order as the history charts). */
export { SERIES_COLORS };

Expand Down
Loading
Loading