preact-homeassistant 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +104 -20
- package/dist/index.js +222 -158
- package/dist/index.js.map +1 -1
- package/package.json +32 -15
- package/src/HAContext.tsx +162 -120
- package/src/__tests__/cacheUtils.test.ts +23 -48
- package/src/__tests__/registerPreactCard.test.tsx +75 -0
- package/src/__tests__/testHelpers.tsx +11 -1
- package/src/__tests__/useCachedFetch.test.tsx +57 -33
- package/src/__tests__/{useMultiCalendarEvents.test.tsx → useCalendarEvents.test.tsx} +3 -4
- package/src/__tests__/useEntity.test.tsx +11 -17
- package/src/__tests__/useHassValue.test.tsx +99 -0
- package/src/cacheUtils.ts +12 -24
- package/src/index.ts +3 -2
- package/src/registerPreactCard.tsx +101 -32
package/dist/index.js
CHANGED
|
@@ -1,29 +1,11 @@
|
|
|
1
1
|
import { jsx, jsxs } from "preact/jsx-runtime";
|
|
2
2
|
import { createContext, render } from "preact";
|
|
3
3
|
import { useRef, useMemo, useState, useEffect, useContext } from "preact/hooks";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
function loadFromCache(key) {
|
|
7
|
-
try {
|
|
8
|
-
const raw = localStorage.getItem(`${CACHE_PREFIX}${key}`);
|
|
9
|
-
if (!raw) return void 0;
|
|
10
|
-
const entry = JSON.parse(raw);
|
|
11
|
-
if (Date.now() - entry.timestamp > CACHE_EXPIRY_MS) {
|
|
12
|
-
localStorage.removeItem(`${CACHE_PREFIX}${key}`);
|
|
13
|
-
return void 0;
|
|
14
|
-
}
|
|
15
|
-
return entry.data;
|
|
16
|
-
} catch {
|
|
17
|
-
return void 0;
|
|
18
|
-
}
|
|
4
|
+
function readCache(cache, key) {
|
|
5
|
+
return cache.get(key)?.data;
|
|
19
6
|
}
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
const entry = { data, timestamp: Date.now() };
|
|
23
|
-
localStorage.setItem(`${CACHE_PREFIX}${key}`, JSON.stringify(entry));
|
|
24
|
-
} catch (e) {
|
|
25
|
-
console.warn("[preact-homeassistant cache] Failed to save:", e);
|
|
26
|
-
}
|
|
7
|
+
function writeCache(cache, key, data) {
|
|
8
|
+
cache.set(key, { data });
|
|
27
9
|
}
|
|
28
10
|
function useCallbackStable(callback) {
|
|
29
11
|
const callbackRef = useRef(callback);
|
|
@@ -36,18 +18,30 @@ function useCallbackStable(callback) {
|
|
|
36
18
|
}
|
|
37
19
|
return stableRef.current;
|
|
38
20
|
}
|
|
21
|
+
const noopSubscribeToHass = () => () => {
|
|
22
|
+
};
|
|
39
23
|
const HAContext = createContext(null);
|
|
40
|
-
function HAProvider({
|
|
24
|
+
function HAProvider({
|
|
25
|
+
hass,
|
|
26
|
+
subscribeToEntity,
|
|
27
|
+
subscribeToHass,
|
|
28
|
+
cache,
|
|
29
|
+
children
|
|
30
|
+
}) {
|
|
41
31
|
const hassRef = useRef(hass);
|
|
42
32
|
hassRef.current = hass;
|
|
43
33
|
const getHass = useCallbackStable(() => hassRef.current);
|
|
34
|
+
const cacheRef = useRef();
|
|
35
|
+
if (!cacheRef.current) cacheRef.current = cache ?? /* @__PURE__ */ new Map();
|
|
36
|
+
const resolvedSubscribeToHass = subscribeToHass ?? noopSubscribeToHass;
|
|
44
37
|
const store = useMemo(
|
|
45
38
|
() => ({
|
|
46
|
-
hass: hassRef.current,
|
|
47
39
|
getHass,
|
|
48
|
-
subscribeToEntity
|
|
40
|
+
subscribeToEntity,
|
|
41
|
+
subscribeToHass: resolvedSubscribeToHass,
|
|
42
|
+
cache: cacheRef.current
|
|
49
43
|
}),
|
|
50
|
-
[getHass, subscribeToEntity]
|
|
44
|
+
[getHass, subscribeToEntity, resolvedSubscribeToHass]
|
|
51
45
|
);
|
|
52
46
|
return /* @__PURE__ */ jsx(HAContext.Provider, { value: store, children });
|
|
53
47
|
}
|
|
@@ -62,23 +56,45 @@ function useEntity(entityId) {
|
|
|
62
56
|
const store = useHAStore();
|
|
63
57
|
const cacheKey = `entity:${entityId}`;
|
|
64
58
|
const [entity, setEntity] = useState(() => {
|
|
65
|
-
const current = store.
|
|
59
|
+
const current = store.getHass()?.states[entityId];
|
|
66
60
|
if (current) return current;
|
|
67
|
-
return
|
|
61
|
+
return readCache(store.cache, cacheKey);
|
|
68
62
|
});
|
|
69
63
|
useEffect(() => {
|
|
70
64
|
const unsubscribe = store.subscribeToEntity(entityId, (newEntity) => {
|
|
71
65
|
setEntity(newEntity);
|
|
72
|
-
|
|
66
|
+
writeCache(store.cache, cacheKey, newEntity);
|
|
73
67
|
});
|
|
74
68
|
return unsubscribe;
|
|
75
|
-
}, [entityId, store.subscribeToEntity, cacheKey]);
|
|
69
|
+
}, [entityId, store.subscribeToEntity, store.cache, cacheKey]);
|
|
76
70
|
return entity;
|
|
77
71
|
}
|
|
78
72
|
function useHass() {
|
|
79
73
|
const store = useHAStore();
|
|
80
74
|
return { getHass: store.getHass };
|
|
81
75
|
}
|
|
76
|
+
function useHassValue(selector, isEqual = Object.is) {
|
|
77
|
+
const store = useHAStore();
|
|
78
|
+
const selectorRef = useRef(selector);
|
|
79
|
+
selectorRef.current = selector;
|
|
80
|
+
const isEqualRef = useRef(isEqual);
|
|
81
|
+
isEqualRef.current = isEqual;
|
|
82
|
+
const [value, setValue] = useState(() => selectorRef.current(store.getHass()));
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
const unsubscribe = store.subscribeToHass(() => {
|
|
85
|
+
const next = selectorRef.current(store.getHass());
|
|
86
|
+
setValue((prev) => isEqualRef.current(prev, next) ? prev : next);
|
|
87
|
+
});
|
|
88
|
+
return unsubscribe;
|
|
89
|
+
}, [store.subscribeToHass, store.getHass]);
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
function useHassConfig() {
|
|
93
|
+
return useHassValue((hass) => hass?.config);
|
|
94
|
+
}
|
|
95
|
+
function useDarkMode() {
|
|
96
|
+
return useHassValue((hass) => hass?.themes?.darkMode ?? false);
|
|
97
|
+
}
|
|
82
98
|
function useService(entityId) {
|
|
83
99
|
const { getHass } = useHass();
|
|
84
100
|
return useCallbackStable(((service, data) => {
|
|
@@ -89,10 +105,19 @@ function useService(entityId) {
|
|
|
89
105
|
}));
|
|
90
106
|
}
|
|
91
107
|
function useCachedFetch(cacheKey, fetcher, deps) {
|
|
92
|
-
const
|
|
108
|
+
const store = useHAStore();
|
|
109
|
+
const [data, setData] = useState(() => readCache(store.cache, cacheKey));
|
|
93
110
|
const [isFresh, setIsFresh] = useState(false);
|
|
94
111
|
const [isFetching, setIsFetching] = useState(false);
|
|
95
112
|
const [error, setError] = useState(void 0);
|
|
113
|
+
const dataKeyRef = useRef(cacheKey);
|
|
114
|
+
if (cacheKey !== dataKeyRef.current) {
|
|
115
|
+
dataKeyRef.current = cacheKey;
|
|
116
|
+
setIsFresh(false);
|
|
117
|
+
setError(void 0);
|
|
118
|
+
const cached = readCache(store.cache, cacheKey);
|
|
119
|
+
if (cached !== void 0) setData(cached);
|
|
120
|
+
}
|
|
96
121
|
const fetchIdRef = useRef(0);
|
|
97
122
|
const doFetch = useCallbackStable(async () => {
|
|
98
123
|
const fetchId = ++fetchIdRef.current;
|
|
@@ -103,7 +128,7 @@ function useCachedFetch(cacheKey, fetcher, deps) {
|
|
|
103
128
|
if (fetchId === fetchIdRef.current) {
|
|
104
129
|
setData(result);
|
|
105
130
|
setIsFresh(true);
|
|
106
|
-
|
|
131
|
+
writeCache(store.cache, cacheKey, result);
|
|
107
132
|
}
|
|
108
133
|
} catch (err) {
|
|
109
134
|
if (fetchId === fetchIdRef.current) {
|
|
@@ -126,97 +151,62 @@ function useCachedFetch(cacheKey, fetcher, deps) {
|
|
|
126
151
|
}, [data, isFresh, isFetching]);
|
|
127
152
|
return { data, status, error, refetch: doFetch };
|
|
128
153
|
}
|
|
129
|
-
function
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
} catch (err) {
|
|
162
|
-
if (fetchId === fetchIdRef.current) {
|
|
163
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
164
|
-
setLoading(false);
|
|
154
|
+
function calendarEventsCacheKey(entityIds, range) {
|
|
155
|
+
return `events:${entityIds.join(",")}:${range.start.getTime()}-${range.end.getTime()}`;
|
|
156
|
+
}
|
|
157
|
+
async function fetchCalendarRange(hass, entityIds, range) {
|
|
158
|
+
if (!hass?.connection) {
|
|
159
|
+
throw new Error("Home Assistant connection not available");
|
|
160
|
+
}
|
|
161
|
+
if (entityIds.length === 0) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
const results = await Promise.all(
|
|
165
|
+
entityIds.map(async (entityId) => {
|
|
166
|
+
try {
|
|
167
|
+
const result = await hass.connection.sendMessagePromise({
|
|
168
|
+
type: "call_service",
|
|
169
|
+
domain: "calendar",
|
|
170
|
+
service: "get_events",
|
|
171
|
+
service_data: {
|
|
172
|
+
start_date_time: range.start.toISOString(),
|
|
173
|
+
end_date_time: range.end.toISOString()
|
|
174
|
+
},
|
|
175
|
+
target: { entity_id: entityId },
|
|
176
|
+
return_response: true
|
|
177
|
+
});
|
|
178
|
+
const calendarEvents = result.response?.[entityId]?.events ?? [];
|
|
179
|
+
return calendarEvents.map(
|
|
180
|
+
(event) => ({ ...event, calendarId: entityId })
|
|
181
|
+
);
|
|
182
|
+
} catch (err) {
|
|
183
|
+
console.error(`Failed to fetch events for ${entityId}:`, err);
|
|
184
|
+
return [];
|
|
165
185
|
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
fetchEvents();
|
|
170
|
-
}, [entityId, options.start.getTime(), options.end.getTime(), fetchEvents]);
|
|
171
|
-
return { events, loading, error, refetch: fetchEvents };
|
|
186
|
+
})
|
|
187
|
+
);
|
|
188
|
+
return results.flat();
|
|
172
189
|
}
|
|
173
|
-
function
|
|
190
|
+
function useCalendarEvents(entityIds, options) {
|
|
174
191
|
const store = useHAStore();
|
|
175
192
|
const { getHass } = useHass();
|
|
176
193
|
const debounceTimerRef = useRef(null);
|
|
177
194
|
const entityIdsKey = entityIds.join(",");
|
|
178
195
|
const dateRangeKey = `${options.start.getTime()}-${options.end.getTime()}`;
|
|
179
196
|
const cacheKey = `events:${entityIdsKey}:${dateRangeKey}`;
|
|
180
|
-
const fetcher = useCallbackStable(
|
|
181
|
-
const hass = getHass();
|
|
182
|
-
if (!hass?.connection) {
|
|
183
|
-
throw new Error("Home Assistant connection not available");
|
|
184
|
-
}
|
|
185
|
-
if (entityIds.length === 0) {
|
|
186
|
-
return [];
|
|
187
|
-
}
|
|
188
|
-
const results = await Promise.all(
|
|
189
|
-
entityIds.map(async (entityId) => {
|
|
190
|
-
try {
|
|
191
|
-
const result = await hass.connection.sendMessagePromise({
|
|
192
|
-
type: "call_service",
|
|
193
|
-
domain: "calendar",
|
|
194
|
-
service: "get_events",
|
|
195
|
-
service_data: {
|
|
196
|
-
start_date_time: options.start.toISOString(),
|
|
197
|
-
end_date_time: options.end.toISOString()
|
|
198
|
-
},
|
|
199
|
-
target: { entity_id: entityId },
|
|
200
|
-
return_response: true
|
|
201
|
-
});
|
|
202
|
-
const calendarEvents = result.response?.[entityId]?.events ?? [];
|
|
203
|
-
return calendarEvents.map(
|
|
204
|
-
(event) => ({ ...event, calendarId: entityId })
|
|
205
|
-
);
|
|
206
|
-
} catch (err) {
|
|
207
|
-
console.error(`Failed to fetch events for ${entityId}:`, err);
|
|
208
|
-
return [];
|
|
209
|
-
}
|
|
210
|
-
})
|
|
211
|
-
);
|
|
212
|
-
return results.flat();
|
|
213
|
-
});
|
|
197
|
+
const fetcher = useCallbackStable(() => fetchCalendarRange(getHass(), entityIds, options));
|
|
214
198
|
const {
|
|
215
199
|
data: events,
|
|
216
200
|
status,
|
|
217
201
|
error,
|
|
218
202
|
refetch
|
|
219
203
|
} = useCachedFetch(cacheKey, fetcher, [entityIdsKey, dateRangeKey]);
|
|
204
|
+
const prefetch = useCallbackStable((range) => {
|
|
205
|
+
const key = calendarEventsCacheKey(entityIds, range);
|
|
206
|
+
if (store.cache.has(key)) return;
|
|
207
|
+
fetchCalendarRange(getHass(), entityIds, range).then((result) => writeCache(store.cache, key, result)).catch(() => {
|
|
208
|
+
});
|
|
209
|
+
});
|
|
220
210
|
const debouncedRefetch = useCallbackStable(() => {
|
|
221
211
|
if (debounceTimerRef.current) {
|
|
222
212
|
clearTimeout(debounceTimerRef.current);
|
|
@@ -234,7 +224,7 @@ function useMultiCalendarEvents(entityIds, options) {
|
|
|
234
224
|
}
|
|
235
225
|
};
|
|
236
226
|
}, [entityIdsKey, store.subscribeToEntity, debouncedRefetch]);
|
|
237
|
-
return { events, status, error, refetch };
|
|
227
|
+
return { events, status, error, refetch, prefetch };
|
|
238
228
|
}
|
|
239
229
|
function useWeatherForecast(entityId, type) {
|
|
240
230
|
const store = useHAStore();
|
|
@@ -313,6 +303,7 @@ function registerRawStyles(styles) {
|
|
|
313
303
|
function getAllStyles() {
|
|
314
304
|
return styleRegistry.join("\n");
|
|
315
305
|
}
|
|
306
|
+
const TEARDOWN_GRACE_MS = 5e3;
|
|
316
307
|
function registerPreactCard(options) {
|
|
317
308
|
const {
|
|
318
309
|
type,
|
|
@@ -323,36 +314,55 @@ function registerPreactCard(options) {
|
|
|
323
314
|
UnconfiguredComponent,
|
|
324
315
|
getStubConfig
|
|
325
316
|
} = options;
|
|
326
|
-
class
|
|
317
|
+
class BaseHACard extends HTMLElement {
|
|
327
318
|
_hass;
|
|
328
319
|
_config;
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
_entityChangeListeners = /* @__PURE__ */ new Map();
|
|
332
|
-
// Pending tree teardown — see disconnectedCallback.
|
|
333
|
-
_unmountTimer;
|
|
334
|
-
constructor() {
|
|
335
|
-
super();
|
|
336
|
-
this._shadowRoot = this.attachShadow({ mode: "open" });
|
|
320
|
+
_teardownTimer;
|
|
321
|
+
_renderUnconfigured() {
|
|
337
322
|
}
|
|
338
323
|
connectedCallback() {
|
|
339
|
-
if (this.
|
|
340
|
-
clearTimeout(this.
|
|
341
|
-
this.
|
|
324
|
+
if (this._teardownTimer !== void 0) {
|
|
325
|
+
clearTimeout(this._teardownTimer);
|
|
326
|
+
this._teardownTimer = void 0;
|
|
342
327
|
}
|
|
343
|
-
|
|
328
|
+
this._maybeRenderOnConnect();
|
|
329
|
+
}
|
|
330
|
+
_maybeRenderOnConnect() {
|
|
331
|
+
if (this._hass && this._config) {
|
|
344
332
|
this._render();
|
|
345
333
|
}
|
|
346
334
|
}
|
|
347
335
|
disconnectedCallback() {
|
|
348
|
-
if (this.
|
|
349
|
-
this.
|
|
350
|
-
this.
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
336
|
+
if (this._teardownTimer !== void 0) clearTimeout(this._teardownTimer);
|
|
337
|
+
this._teardownTimer = setTimeout(() => {
|
|
338
|
+
render(null, this._getRenderRoot());
|
|
339
|
+
this._teardownTimer = void 0;
|
|
340
|
+
}, TEARDOWN_GRACE_MS);
|
|
341
|
+
}
|
|
342
|
+
setConfig(config) {
|
|
343
|
+
this._config = config;
|
|
344
|
+
if (this._hass && this.isConnected) {
|
|
345
|
+
this._render();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
_render() {
|
|
349
|
+
if (!this._config || !this._hass) {
|
|
350
|
+
this._renderUnconfigured();
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
this._renderTree();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
class HACard extends BaseHACard {
|
|
357
|
+
_shadowRoot;
|
|
358
|
+
_entityChangeListeners = /* @__PURE__ */ new Map();
|
|
359
|
+
_hassChangeListeners = /* @__PURE__ */ new Set();
|
|
360
|
+
constructor() {
|
|
361
|
+
super();
|
|
362
|
+
this._shadowRoot = this.attachShadow({ mode: "open" });
|
|
363
|
+
}
|
|
364
|
+
_getRenderRoot() {
|
|
365
|
+
return this._shadowRoot;
|
|
356
366
|
}
|
|
357
367
|
set hass(hass) {
|
|
358
368
|
const prevStates = this._hass?.states;
|
|
@@ -364,16 +374,11 @@ function registerPreactCard(options) {
|
|
|
364
374
|
listeners.forEach((listener) => listener(newState));
|
|
365
375
|
}
|
|
366
376
|
}
|
|
377
|
+
this._hassChangeListeners.forEach((listener) => listener());
|
|
367
378
|
if (!prevStates && this._config && this.isConnected) {
|
|
368
379
|
this._render();
|
|
369
380
|
}
|
|
370
381
|
}
|
|
371
|
-
setConfig(config) {
|
|
372
|
-
this._config = config;
|
|
373
|
-
if (this._hass && this.isConnected) {
|
|
374
|
-
this._render();
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
382
|
_subscribeToEntity = (entityId, callback) => {
|
|
378
383
|
if (!this._entityChangeListeners.has(entityId)) {
|
|
379
384
|
this._entityChangeListeners.set(entityId, /* @__PURE__ */ new Set());
|
|
@@ -389,21 +394,33 @@ function registerPreactCard(options) {
|
|
|
389
394
|
}
|
|
390
395
|
};
|
|
391
396
|
};
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
397
|
+
_subscribeToHass = (callback) => {
|
|
398
|
+
this._hassChangeListeners.add(callback);
|
|
399
|
+
return () => {
|
|
400
|
+
this._hassChangeListeners.delete(callback);
|
|
401
|
+
};
|
|
402
|
+
};
|
|
403
|
+
_renderTree() {
|
|
399
404
|
render(
|
|
400
|
-
/* @__PURE__ */ jsxs(
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
405
|
+
/* @__PURE__ */ jsxs(
|
|
406
|
+
HAProvider,
|
|
407
|
+
{
|
|
408
|
+
hass: this._hass,
|
|
409
|
+
subscribeToEntity: this._subscribeToEntity,
|
|
410
|
+
subscribeToHass: this._subscribeToHass,
|
|
411
|
+
children: [
|
|
412
|
+
/* @__PURE__ */ jsx("style", { children: getAllStyles() }),
|
|
413
|
+
/* @__PURE__ */ jsx(Component, { config: this._config })
|
|
414
|
+
]
|
|
415
|
+
}
|
|
416
|
+
),
|
|
404
417
|
this._shadowRoot
|
|
405
418
|
);
|
|
406
|
-
|
|
419
|
+
}
|
|
420
|
+
_renderUnconfigured() {
|
|
421
|
+
if (UnconfiguredComponent) {
|
|
422
|
+
render(/* @__PURE__ */ jsx(UnconfiguredComponent, {}), this._shadowRoot);
|
|
423
|
+
}
|
|
407
424
|
}
|
|
408
425
|
static getConfigElement() {
|
|
409
426
|
if (ConfigComponent) {
|
|
@@ -418,13 +435,19 @@ function registerPreactCard(options) {
|
|
|
418
435
|
customElements.define(type, HACard);
|
|
419
436
|
if (ConfigComponent) {
|
|
420
437
|
const EditorComponent = ConfigComponent;
|
|
421
|
-
class HACardEditor extends
|
|
422
|
-
|
|
423
|
-
|
|
438
|
+
class HACardEditor extends BaseHACard {
|
|
439
|
+
_getRenderRoot() {
|
|
440
|
+
return this;
|
|
441
|
+
}
|
|
442
|
+
// The editor re-renders on every hass update: it passes `hass` straight to
|
|
443
|
+
// HA's <ha-form>/<ha-selector>, whose entity pickers need a fresh hass to
|
|
444
|
+
// stay current. (Unlike the card, which renders once then subscribes.)
|
|
424
445
|
set hass(hass) {
|
|
425
446
|
this._hass = hass;
|
|
426
447
|
this._render();
|
|
427
448
|
}
|
|
449
|
+
// Render whenever config arrives, regardless of connection — HA may set
|
|
450
|
+
// config/hass before connecting the editor element.
|
|
428
451
|
setConfig(config) {
|
|
429
452
|
this._config = config;
|
|
430
453
|
this._render();
|
|
@@ -438,8 +461,8 @@ function registerPreactCard(options) {
|
|
|
438
461
|
})
|
|
439
462
|
);
|
|
440
463
|
};
|
|
441
|
-
|
|
442
|
-
|
|
464
|
+
// Render to light DOM so HA's custom elements (ha-form etc.) work.
|
|
465
|
+
_renderTree() {
|
|
443
466
|
render(
|
|
444
467
|
/* @__PURE__ */ jsx(
|
|
445
468
|
EditorComponent,
|
|
@@ -463,21 +486,62 @@ function registerPreactCard(options) {
|
|
|
463
486
|
""
|
|
464
487
|
);
|
|
465
488
|
}
|
|
489
|
+
function useResizeObserver(ref, callback, deps = []) {
|
|
490
|
+
const callbackRef = useRef(callback);
|
|
491
|
+
callbackRef.current = callback;
|
|
492
|
+
useEffect(() => {
|
|
493
|
+
const element = ref.current;
|
|
494
|
+
if (!element) return;
|
|
495
|
+
const fire = () => {
|
|
496
|
+
if (!element.isConnected) return;
|
|
497
|
+
callbackRef.current({
|
|
498
|
+
width: element.offsetWidth,
|
|
499
|
+
height: element.offsetHeight
|
|
500
|
+
});
|
|
501
|
+
};
|
|
502
|
+
const observer = new ResizeObserver(fire);
|
|
503
|
+
observer.observe(element);
|
|
504
|
+
return () => observer.disconnect();
|
|
505
|
+
}, []);
|
|
506
|
+
const isFirstRun = useRef(true);
|
|
507
|
+
useEffect(() => {
|
|
508
|
+
if (isFirstRun.current) {
|
|
509
|
+
isFirstRun.current = false;
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const element = ref.current;
|
|
513
|
+
if (!element || !element.isConnected) return;
|
|
514
|
+
callbackRef.current({
|
|
515
|
+
width: element.offsetWidth,
|
|
516
|
+
height: element.offsetHeight
|
|
517
|
+
});
|
|
518
|
+
}, deps);
|
|
519
|
+
}
|
|
520
|
+
function useWidth(ref) {
|
|
521
|
+
const [width, setWidth] = useState(void 0);
|
|
522
|
+
useResizeObserver(ref, (size) => {
|
|
523
|
+
if (size.width === 0) return;
|
|
524
|
+
setWidth((prev) => prev === size.width ? prev : size.width);
|
|
525
|
+
});
|
|
526
|
+
return width;
|
|
527
|
+
}
|
|
466
528
|
export {
|
|
467
529
|
HAProvider,
|
|
468
530
|
css,
|
|
469
531
|
getAllStyles,
|
|
470
|
-
loadFromCache,
|
|
471
532
|
registerPreactCard,
|
|
472
533
|
registerRawStyles,
|
|
473
|
-
saveToCache,
|
|
474
534
|
useCachedFetch,
|
|
475
535
|
useCalendarEvents,
|
|
476
536
|
useCallbackStable,
|
|
537
|
+
useDarkMode,
|
|
477
538
|
useEntity,
|
|
478
539
|
useHass,
|
|
479
|
-
|
|
540
|
+
useHassConfig,
|
|
541
|
+
useHassValue,
|
|
542
|
+
useResizeObserver,
|
|
480
543
|
useService,
|
|
481
|
-
useWeatherForecast
|
|
544
|
+
useWeatherForecast,
|
|
545
|
+
useWidth
|
|
482
546
|
};
|
|
483
547
|
//# sourceMappingURL=index.js.map
|