Skip to content

Commit c1cb32f

Browse files
committed
toolbar is now centered on screen and in editor / preview mode
1 parent ed31a6d commit c1cb32f

File tree

10 files changed

+136
-151
lines changed

10 files changed

+136
-151
lines changed

.github/dependabot.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ updates:
1414
groups:
1515
all-dependencies:
1616
patterns:
17-
- "*" # This pattern matches all dependencies
17+
- '*' # This pattern matches all dependencies
1818
update-types:
19-
- "minor"
20-
- "patch"
19+
- 'minor'
20+
- 'patch'

.github/workflows/pull_request.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ jobs:
6767
github-token: '${{ secrets.GITHUB_TOKEN }}'
6868

6969
- name: Enable auto-merge for Dependabot PRs
70-
if: steps.metadata.outputs.update-type == 'version-update:semver-patch'
70+
if: steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.update-type == 'version-update:semver-minor'
7171
run: gh pr merge --auto --merge "$PR_URL"
7272
env:
7373
PR_URL: ${{github.event.pull_request.html_url}}

.github/workflows/release-cd.yaml

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,12 @@ jobs:
9797
env:
9898
GITHUB_TOKEN: ${{ github.token }}
9999

100-
- name: Get Last PR Title
101-
id: get-pr-title
100+
- name: Get Last PR Title and Description
101+
id: get-pr-info
102102
env:
103103
GH_TOKEN: ${{ github.token }}
104104
run: |
105-
PR_TITLE=$(gh api graphql -f query='
105+
PR_DATA=$(gh api graphql -f query='
106106
query {
107107
repository(owner: "${{ github.repository_owner }}", name: "${{ github.event.repository.name }}") {
108108
commit: object(expression: "${{ github.sha }}") {
@@ -111,23 +111,35 @@ jobs:
111111
edges {
112112
node {
113113
title
114+
body
114115
}
115116
}
116117
}
117118
}
118119
}
119120
}
120121
}
121-
' --jq '.data.repository.commit.associatedPullRequests.edges[0].node.title')
122+
')
123+
PR_TITLE=$(echo "$PR_DATA" | jq -r '.data.repository.commit.associatedPullRequests.edges[0].node.title')
124+
PR_BODY=$(echo "$PR_DATA" | jq -r '.data.repository.commit.associatedPullRequests.edges[0].node.body')
122125
echo "pr_title=$PR_TITLE" >> $GITHUB_OUTPUT
126+
echo "pr_body<<EOF" >> $GITHUB_OUTPUT
127+
echo "$PR_BODY" >> $GITHUB_OUTPUT
128+
echo "EOF" >> $GITHUB_OUTPUT
123129
- uses: ncipollo/release-action@v1
124130
with:
125131
makeLatest: true
126132
artifacts: 'dist/**'
127133
generateReleaseNotes: true
128134
tag: ${{ needs.bump-version.outputs.newTag }}
129-
body: ${{ steps.create-release-notes.outputs.release-notes }}
130-
name: '${{ needs.bump-version.outputs.newTag }} - ${{ steps.get-pr-title.outputs.pr_title }}'
135+
body: |
136+
${{ steps.get-pr-info.outputs.pr_body }}
137+
138+
139+
---
140+
141+
${{ steps.create-release-notes.outputs.release-notes }}
142+
name: '${{ needs.bump-version.outputs.newTag }} - ${{ steps.get-pr-info.outputs.pr_title }}'
131143

132144
update-main:
133145
name: 'Update main branch with release changes'

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,6 @@ The card will automatically:
315315
Common issues and solutions:
316316

317317
1. **Chips not appearing:**
318-
319318
- Verify entities have the "status" label
320319
- Check entity states match their `on_state` attribute or `on`
321320
- Ensure the card is properly loaded in resources

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717
"test": "TS_NODE_PROJECT='./tsconfig.test.json' mocha",
1818
"test:coverage": "nyc npm run test",
1919
"test:watch": "TS_NODE_PROJECT='./tsconfig.test.json' mocha --watch",
20-
"update": "npx npm-check-updates -u && npm i"
20+
"update": "npx npm-check-updates -u && yarn install"
2121
},
2222
"devDependencies": {
2323
"@istanbuljs/nyc-config-typescript": "^1.0.2",
2424
"@open-wc/testing": "^4.0.0",
25-
"@parcel/transformer-inline-string": "^2.15.2",
25+
"@parcel/transformer-inline-string": "^2.15.4",
2626
"@testing-library/dom": "^10.4.0",
2727
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
2828
"@types/chai": "^5.2.2",
@@ -31,12 +31,12 @@
3131
"@types/sinon": "^17.0.4",
3232
"chai": "^5.2.0",
3333
"jsdom": "^26.1.0",
34-
"mocha": "^11.6.0",
34+
"mocha": "^11.7.1",
3535
"nyc": "^17.1.0",
36-
"parcel": "^2.15.2",
37-
"prettier": "3.5.3",
36+
"parcel": "^2.15.4",
37+
"prettier": "3.6.2",
3838
"prettier-plugin-organize-imports": "^4.1.0",
39-
"sinon": "^20.0.0",
39+
"sinon": "^21.0.0",
4040
"ts-node": "^10.9.2",
4141
"tsconfig-paths": "^4.2.0",
4242
"typescript": "^5.8.3"

src/card.ts

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import { Task } from '@lit/task';
55
import type { Config } from '@type/config';
66
import type { HomeAssistant } from '@type/homeassistant';
77
import { CSSResult, html, LitElement, type TemplateResult } from 'lit';
8-
import { state } from 'lit/decorators.js';
8+
import { property, state } from 'lit/decorators.js';
99
import { version } from '../package.json';
10-
import { chipStyles, styles } from './styles';
10+
import { styles } from './styles';
1111
const equal = require('fast-deep-equal');
1212

1313
/**
@@ -41,7 +41,7 @@ export default class ToolbarStatusChips extends LitElement {
4141
* @state Marks this as a reactive property that will trigger updates
4242
*/
4343
@state()
44-
private _slug: string | undefined;
44+
private readonly _slug: string | undefined;
4545

4646
/**
4747
* Reference to the current Home Assistant instance
@@ -52,9 +52,32 @@ export default class ToolbarStatusChips extends LitElement {
5252
/**
5353
* Flag indicating whether the card is in edit mode
5454
* Used by the Home Assistant card editor
55+
* @property Marks this as a reactive property that reflects to attribute
5556
*/
57+
@property({
58+
type: Boolean,
59+
attribute: 'edit-mode',
60+
reflect: true,
61+
})
5662
public editMode: boolean = false;
5763

64+
/**
65+
* Flag indicating whether the card is in preview mode
66+
* Used by the Home Assistant card editor
67+
* @property Marks this as a reactive property that reflects to attribute
68+
*/
69+
@property({
70+
type: Boolean,
71+
attribute: true,
72+
reflect: true,
73+
})
74+
get preview() {
75+
return (
76+
(this as HTMLElement).parentElement?.classList.contains('preview') ??
77+
false
78+
);
79+
}
80+
5881
/**
5982
* Creates an instance of ToolbarStatusChips
6083
* Extracts the current URL slug and logs version information
@@ -78,13 +101,11 @@ export default class ToolbarStatusChips extends LitElement {
78101
* @returns {TemplateResult} The rendered HTML template
79102
*/
80103
override render(): TemplateResult {
81-
const styles = chipStyles(this.isEditing);
82104
return this._entities.length
83105
? this._createChipsTask.render({
84106
initial: () => html``,
85107
pending: () => html``,
86-
complete: (value) =>
87-
html`<div id="chips" style="${styles}">${value}</div>`,
108+
complete: (value) => html`${value}`,
88109
error: (error) => html`${error}`,
89110
})
90111
: html``;
@@ -114,7 +135,7 @@ export default class ToolbarStatusChips extends LitElement {
114135
* @returns {string | undefined} The area ID to use for filtering
115136
*/
116137
get area() {
117-
return this._config.area || this._slug;
138+
return this._config.area ?? this._slug;
118139
}
119140

120141
/**
@@ -144,20 +165,7 @@ export default class ToolbarStatusChips extends LitElement {
144165
* @returns {string} The path identifier for the status/home view
145166
*/
146167
get statusPath() {
147-
return this._config.status_path || 'home';
148-
}
149-
150-
/**
151-
* Determines if the card is currently in editing mode
152-
* True if editMode is explicitly set or if parent has 'preview' class
153-
* @returns {boolean} Whether the card is being edited
154-
*/
155-
get isEditing(): boolean {
156-
return (
157-
this.editMode ||
158-
(this as HTMLElement).parentElement?.classList.contains('preview') ||
159-
false
160-
);
168+
return this._config.status_path ?? 'home';
161169
}
162170

163171
/*
@@ -183,7 +191,7 @@ export default class ToolbarStatusChips extends LitElement {
183191
set hass(hass: HomeAssistant) {
184192
// get entities with the status label
185193
let entities = Object.values(hass.entities).filter((entity) =>
186-
entity.labels.includes(this.soloLabel || 'status'),
194+
entity.labels.includes(this.soloLabel ?? 'status'),
187195
);
188196

189197
// filter entities by additional label if provided or area if not on the status page
@@ -290,7 +298,7 @@ export default class ToolbarStatusChips extends LitElement {
290298
return;
291299
}
292300

293-
var stack = helpers.createCardElement({
301+
const stack = helpers.createCardElement({
294302
type: 'horizontal-stack',
295303
cards,
296304
});

src/entity.ts

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,18 @@ export class ChipEntity {
6262
case StateValidation.Pass:
6363
return 'var(--green-color)';
6464
case StateValidation.Warning:
65-
return this.attributes.on_color || 'var(--amber-color)';
65+
return this.attributes.on_color ?? 'var(--amber-color)';
6666
default:
6767
// Non-numeric fail or below warning threshold
68-
return this.attributes.on_color || 'var(--red-color)';
68+
return this.attributes.on_color ?? 'var(--red-color)';
6969
}
7070
}
7171

7272
/**
7373
* Indicates whether an optional entity should be excluded when inactive from the status path.
7474
*/
7575
get excludeOnStatusPath(): boolean {
76-
return this.attributes.exclude_on_status_path || false;
76+
return this.attributes.exclude_on_status_path ?? false;
7777
}
7878

7979
/**
@@ -109,36 +109,62 @@ export class ChipEntity {
109109
*/
110110
private validateState(): StateValidation {
111111
if (!this.isNumeric(this.state)) {
112-
if (this.attributes.on_state) {
113-
if (this.state === this.attributes.on_state) {
114-
return StateValidation.Error;
115-
}
116-
} else {
117-
if (this.state && ['on', 'true'].includes(this.state.toLowerCase())) {
118-
return StateValidation.Error;
119-
}
120-
}
121-
return StateValidation.Pass;
112+
return this.validateNonNumericState();
113+
}
114+
115+
return this.validateNumericState();
116+
}
117+
118+
/**
119+
* Validates non-numeric state values
120+
*/
121+
private validateNonNumericState(): StateValidation {
122+
if (this.attributes.on_state) {
123+
return this.state === this.attributes.on_state
124+
? StateValidation.Error
125+
: StateValidation.Pass;
126+
}
127+
128+
if (this.state && ['on', 'true'].includes(this.state.toLowerCase())) {
129+
return StateValidation.Error;
122130
}
123131

132+
return StateValidation.Pass;
133+
}
134+
135+
/**
136+
* Validates numeric state values
137+
*/
138+
private validateNumericState(): StateValidation {
124139
const numericState = parseFloat(this.state!);
125140

126-
// Check if threshold attributes exist
127-
if (
128-
this.attributes.numeric_state_pass_threshold === undefined &&
129-
this.attributes.numeric_state_warning_threshold === undefined
130-
) {
131-
// Fallback logic: Pass if 0, Error if > 0
132-
return numericState === 0 ? StateValidation.Pass : StateValidation.Error;
141+
if (this.hasThresholds()) {
142+
return this.validateWithThresholds(numericState);
133143
}
134144

145+
return numericState === 0 ? StateValidation.Pass : StateValidation.Error;
146+
}
147+
148+
/**
149+
* Checks if threshold attributes are defined
150+
*/
151+
private hasThresholds(): boolean {
152+
return (
153+
this.attributes.numeric_state_pass_threshold !== undefined ||
154+
this.attributes.numeric_state_warning_threshold !== undefined
155+
);
156+
}
157+
158+
/**
159+
* Validates numeric state using defined thresholds
160+
*/
161+
private validateWithThresholds(numericState: number): StateValidation {
135162
if (numericState > this.attributes.numeric_state_pass_threshold) {
136163
return StateValidation.Pass;
137164
}
138165
if (numericState > this.attributes.numeric_state_warning_threshold) {
139166
return StateValidation.Warning;
140167
}
141-
142168
return StateValidation.Error;
143169
}
144170
}

src/styles.ts

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,24 @@
11
import { css } from 'lit';
2-
import type { DirectiveResult } from 'lit-html/directive';
3-
import {
4-
type StyleMapDirective,
5-
styleMap,
6-
} from 'lit-html/directives/style-map.js';
72

83
export const styles = css`
9-
#chips {
4+
:host {
105
--stack-card-gap: 0;
6+
width: fit-content;
7+
position: fixed;
8+
top: 10px;
9+
left: 0;
10+
right: 0;
11+
margin-left: auto;
12+
margin-right: auto;
13+
z-index: 5;
1114
}
12-
`;
1315
14-
export const chipStyles = (
15-
editMode: boolean,
16-
): DirectiveResult<typeof StyleMapDirective> => {
17-
return styleMap(
18-
editMode
19-
? {
20-
position: 'inherit',
21-
display: 'flex',
22-
transform: 'none',
23-
justifyContent: 'center',
24-
alignItems: 'center',
25-
flexWrap: 'wrap',
26-
}
27-
: {
28-
position: 'fixed',
29-
top: '10px',
30-
left: '50%',
31-
transform: 'translateX(-50%)',
32-
zIndex: '5',
33-
},
34-
);
35-
};
16+
/* Styles for edit mode or preview mode */
17+
:host([edit-mode]),
18+
:host([preview]) {
19+
position: inherit;
20+
display: flex !important;
21+
justify-content: center;
22+
flex-wrap: wrap;
23+
}
24+
`;

0 commit comments

Comments
 (0)