Skip to content

Commit df12c20

Browse files
Merge pull request #311 from shannonhochkins/development
Development
2 parents 0e80a2a + 6b1e895 commit df12c20

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+35482
-20100
lines changed

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
# 6.0.2
2+
3+
### @hakit/components
4+
5+
- BUGIX - spacing issue for ButtonCard when has no content - fixes [issue](https://github.com/shannonhochkins/ha-component-kit/issues/307)
6+
- FEATURE - Introducing "tooltipsWhenClosed" prop, which is now enabled by default to display an indicator of what a button does in collapsed state
7+
8+
9+
### @hakit/core
10+
11+
- BUGFIX - HassConnect suspend/resume now resolves even when browsers skip `visibilitychange` after long tab freezes (added `pageshow` fallback + visibility polling), preventing stuck reconnections with error code 3 - solves [issue](https://github.com/shannonhochkins/ha-component-kit/issues/304)
12+
13+
### General
14+
15+
- BUGFIX - rimraf added to ensure scripts work in different OS's
16+
117
# 6.0.1
218

319
### General

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/components/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@hakit/components",
33
"type": "module",
4-
"version": "6.0.1",
4+
"version": "6.0.2",
55
"private": false,
66
"keywords": [
77
"react",
@@ -70,7 +70,7 @@
7070
"@emotion/react": ">=11.x.x",
7171
"@emotion/styled": ">=11.x",
7272
"@fullcalendar/react": ">=6.x.x",
73-
"@hakit/core": "^6.0.1",
73+
"@hakit/core": "^6.0.2",
7474
"@use-gesture/react": ">=10.x",
7575
"autolinker": ">=4.x",
7676
"fullcalendar": ">=6.x.x",

packages/components/src/Cards/ButtonCard/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,8 +322,8 @@ function InternalButtonCard<E extends EntityName>({
322322
style={
323323
isDefaultLayout || children
324324
? {
325-
marginBottom: "20px",
326-
}
325+
marginBottom: "20px",
326+
}
327327
: undefined
328328
}
329329
>

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@hakit/core",
3-
"version": "6.0.1",
3+
"version": "6.0.2",
44
"private": false,
55
"type": "module",
66
"keywords": [

packages/core/src/HassConnect/handleSuspendResume.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ export function handleSuspendResume(connection: Connection, options: HandleSuspe
5757
* Prevents stacking multiple promises.
5858
*/
5959
let isSuspended = false;
60+
/**
61+
* Fallback interval to detect when the page becomes visible again in cases where
62+
* visibilitychange/focus/resume events are not delivered after a long tab freeze.
63+
*/
64+
let visibilityPollId: number | null = null;
6065

6166
if (connection.connected) {
6267
if (debug) console.log("[SR] Connection is already active → handleSuspendResume will manage suspension");
@@ -88,6 +93,7 @@ export function handleSuspendResume(connection: Connection, options: HandleSuspe
8893
if (debug) console.log("[SR] pendingResolve() called → lifting suspension");
8994
isSuspended = false;
9095
pendingResolve = null;
96+
stopVisibilityPolling();
9197
resolve();
9298
onStatusChange?.("connected");
9399
};
@@ -107,6 +113,7 @@ export function handleSuspendResume(connection: Connection, options: HandleSuspe
107113
if (document.hidden) {
108114
if (debug) console.log("[SR] Hidden timeout elapsed → calling suspend()");
109115
suspend();
116+
startVisibilityPolling();
110117
} else {
111118
// User returned before timeout. Resolve immediately.
112119
if (pendingResolve) {
@@ -119,7 +126,7 @@ export function handleSuspendResume(connection: Connection, options: HandleSuspe
119126
: null;
120127

121128
// If the user focuses before DELAY_MS is up, resume immediately:
122-
if (typeof window !== "undefined") window.addEventListener("focus", onVisibleOrResume, { once: true });
129+
if (typeof window !== "undefined") window.addEventListener("focus", onVisibleOrResume);
123130
}
124131

125132
// helper when page/tab becomes visible or “resume” fires after freeze
@@ -139,6 +146,7 @@ export function handleSuspendResume(connection: Connection, options: HandleSuspe
139146
// resets isSuspended and pendingResolve itself
140147
pendingResolve();
141148
}
149+
stopVisibilityPolling();
142150
}
143151

144152
// helpers for the event listeners to attach
@@ -157,6 +165,29 @@ export function handleSuspendResume(connection: Connection, options: HandleSuspe
157165
onVisibleOrResume();
158166
}
159167

168+
function pageShowHandler() {
169+
if (debug) console.log("[SR] pageshow event fired");
170+
onVisibleOrResume();
171+
}
172+
173+
function startVisibilityPolling() {
174+
if (visibilityPollId !== null) return;
175+
if (typeof window === "undefined") return;
176+
visibilityPollId = window.setInterval(() => {
177+
if (!document.hidden) {
178+
if (debug) console.log("[SR] visibility polling detected VISIBLE");
179+
onVisibleOrResume();
180+
}
181+
}, 2_000);
182+
}
183+
184+
function stopVisibilityPolling() {
185+
if (visibilityPollId !== null && typeof window !== "undefined") {
186+
clearInterval(visibilityPollId);
187+
visibilityPollId = null;
188+
}
189+
}
190+
160191
function suspend() {
161192
if (!connection.connected) {
162193
if (debug) console.log("[SR] Connection already suspended → skipping suspend()");
@@ -172,6 +203,7 @@ export function handleSuspendResume(connection: Connection, options: HandleSuspe
172203
document.addEventListener("visibilitychange", visibilityChangeHandler, false);
173204
document.addEventListener("freeze", suspend);
174205
document.addEventListener("resume", resumeHandler);
206+
document.addEventListener("pageshow", pageShowHandler);
175207

176208
if (debug) console.log("[SR] handleSuspendResume() initialized; debugging is ON");
177209

@@ -182,13 +214,15 @@ export function handleSuspendResume(connection: Connection, options: HandleSuspe
182214
document.removeEventListener("visibilitychange", visibilityChangeHandler, false);
183215
document.removeEventListener("freeze", suspend);
184216
document.removeEventListener("resume", resumeHandler);
217+
document.removeEventListener("pageshow", pageShowHandler);
185218
if (typeof window !== "undefined") window.removeEventListener("focus", onVisibleOrResume);
186219

187220
if (hiddenTimeoutId !== null) {
188221
if (debug) console.log("[SR] cleanup: Clearing hiddenTimeoutId");
189222
clearTimeout(hiddenTimeoutId);
190223
hiddenTimeoutId = null;
191224
}
225+
stopVisibilityPolling();
192226
// If there’s still a pendingResolve (Promise not resolved), resolve it now so reconnection can recover:
193227
if (pendingResolve) {
194228
if (debug) console.log("[SR] cleanup: Resolving pendingResolve() to let reconnection proceed");

packages/core/src/hooks/useHistory/history.ts

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ class HistoryStream {
164164
// only expire the rest of the history as it ages.
165165
const lastExpiredState = expiredStates[expiredStates.length - 1];
166166
lastExpiredState.lu = purgeBeforePythonTime;
167+
delete lastExpiredState.lc;
167168
newHistory[entityId].unshift(lastExpiredState);
168169
}
169170
}
@@ -352,32 +353,27 @@ export const computeHistory = (
352353
currentState || isNumericFromDomain(domain) ? undefined : stateInfo.find((state) => state.a && isNumericFromAttributes(state.a));
353354

354355
const isNumeric = isNumericEntity(domain, currentState, numericStateFromHistory, sensorNumericalDeviceClasses, forceNumeric);
355-
356356
let unit: string | undefined;
357-
358357
if (isNumeric) {
359358
unit = currentState?.attributes.unit_of_measurement || numericStateFromHistory?.a.unit_of_measurement || BLANK_UNIT;
360359
} else {
361-
if (domain === "zone") {
362-
unit = localize("people_in_zone");
363-
} else if (domain === "climate" || domain === "water_heater") {
364-
unit = config?.unit_system.temperature;
365-
} else if (domain === "humidifier") {
366-
unit = "%";
367-
}
360+
unit = {
361+
zone: localize("unit"),
362+
climate: config?.unit_system.temperature,
363+
humidifier: "%",
364+
water_heater: config?.unit_system.temperature,
365+
}[domain as keyof typeof specialDomainClasses];
368366
}
369367

370-
let deviceClassSpecial: string | undefined;
371-
372-
if (domain === "climate") {
373-
deviceClassSpecial = "temperature";
374-
} else if (domain === "humidifier") {
375-
deviceClassSpecial = "humidity";
376-
} else if (domain === "water_heater") {
377-
deviceClassSpecial = "temperature";
378-
}
368+
const specialDomainClasses = {
369+
climate: "temperature",
370+
humidifier: "humidity",
371+
water_heater: "temperature",
372+
};
379373

380-
const deviceClass: string | undefined = deviceClassSpecial || (currentState?.attributes || numericStateFromHistory?.a)?.device_class;
374+
const deviceClass: string | undefined =
375+
specialDomainClasses[domain as keyof typeof specialDomainClasses] ||
376+
(currentState?.attributes || numericStateFromHistory?.a)?.device_class;
381377

382378
const key = computeGroupKey(unit, deviceClass, splitDeviceClasses);
383379

0 commit comments

Comments
 (0)