preact-homeassistant 0.2.3 → 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 CHANGED
@@ -10,6 +10,12 @@ import { HassServices } from 'home-assistant-js-websocket';
10
10
  import { JSX } from 'preact';
11
11
  import { RefObject } from 'preact';
12
12
 
13
+ declare type Cache_2 = Map<string, CacheEntry<unknown>>;
14
+
15
+ declare interface CacheEntry<T> {
16
+ data: T;
17
+ }
18
+
13
19
  /**
14
20
  * Calendar entity - only exposes the current/next event.
15
21
  * Use useCalendarEvents() to fetch a list of events.
@@ -147,11 +153,13 @@ export declare type ForecastType = 'daily' | 'hourly' | 'twice_daily';
147
153
  */
148
154
  export declare function getAllStyles(): string;
149
155
 
150
- export declare function HAProvider({ hass, subscribeToEntity, children }: HAProviderProps): JSX.Element;
156
+ export declare function HAProvider({ hass, subscribeToEntity, subscribeToHass, cache, children, }: HAProviderProps): JSX.Element;
151
157
 
152
158
  declare interface HAProviderProps {
153
159
  hass: HomeAssistant | undefined;
154
160
  subscribeToEntity: (entityId: string, callback: (entity: any) => void) => () => void;
161
+ subscribeToHass?: SubscribeToHass;
162
+ cache?: Cache_2;
155
163
  children: ComponentChildren;
156
164
  }
157
165
 
@@ -174,8 +182,6 @@ declare type KnownDomain = keyof DomainEntityMap;
174
182
 
175
183
  declare type KnownServiceDomain = keyof DomainServiceMap;
176
184
 
177
- export declare function loadFromCache<T>(key: string): T | undefined;
178
-
179
185
  export declare function registerPreactCard<TConfig>(options: RegisterPreactCardOptions<TConfig>): void;
180
186
 
181
187
  declare interface RegisterPreactCardOptions<TConfig> {
@@ -202,8 +208,6 @@ export declare function registerRawStyles(styles: string): void;
202
208
 
203
209
  export declare type ResizeCallback = (size: ElementSize) => void;
204
210
 
205
- export declare function saveToCache<T>(key: string, data: T): void;
206
-
207
211
  declare type ServiceCaller<T extends string> = <S extends keyof ServicesForId<T> & string>(service: S, ...args: ServicesForId<T>[S] extends undefined ? [] : Record<string, never> extends Exclude<ServicesForId<T>[S], undefined> ? [data?: ServicesForId<T>[S]] : [data: ServicesForId<T>[S]]) => Promise<void>;
208
212
 
209
213
  /**
@@ -214,6 +218,8 @@ declare type ServiceCaller<T extends string> = <S extends keyof ServicesForId<T>
214
218
  */
215
219
  export declare type ServicesForId<T extends string> = T extends `${infer D}.${string}` ? D extends KnownServiceDomain ? DomainServiceMap[D] : Record<string, Record<string, unknown> | undefined> : Record<string, Record<string, unknown> | undefined>;
216
220
 
221
+ declare type SubscribeToHass = (callback: () => void) => () => void;
222
+
217
223
  /**
218
224
  * Sun entity - provides sunrise/sunset and elevation information.
219
225
  */
@@ -247,18 +253,29 @@ declare interface UseCachedFetchResult<T> {
247
253
  }
248
254
 
249
255
  /**
250
- * Fetch calendar events for a date range from a single calendar.
256
+ * Fetch events from one or more calendars for a date range, with in-memory
257
+ * (per-card) caching and stale-while-revalidate behavior. Events are tagged
258
+ * with their source calendar ID. Returns `prefetch` to warm adjacent ranges.
251
259
  */
252
- export declare function useCalendarEvents(entityId: `calendar.${string}`, options: {
260
+ export declare function useCalendarEvents(entityIds: `calendar.${string}`[], options: {
253
261
  start: Date;
254
262
  end: Date;
255
263
  }): UseCalendarEventsResult;
256
264
 
257
265
  declare interface UseCalendarEventsResult {
258
- events: CalendarEvent[] | undefined;
259
- loading: boolean;
266
+ events: CalendarEventWithSource[] | undefined;
267
+ status: FetchStatus;
260
268
  error: Error | undefined;
261
269
  refetch: () => void;
270
+ /**
271
+ * Warm the cache for an arbitrary range (e.g. adjacent months) without
272
+ * touching component state. Best-effort: skips ranges already cached and
273
+ * swallows failures.
274
+ */
275
+ prefetch: (range: {
276
+ start: Date;
277
+ end: Date;
278
+ }) => void;
262
279
  }
263
280
 
264
281
  /**
@@ -270,6 +287,9 @@ declare interface UseCalendarEventsResult {
270
287
  */
271
288
  export declare function useCallbackStable<T extends (...args: never[]) => unknown>(callback: T): T;
272
289
 
290
+ /** Re-renders when the active theme's dark mode flips. */
291
+ export declare function useDarkMode(): boolean;
292
+
273
293
  /**
274
294
  * Subscribe to a specific entity by ID. Re-renders only when that entity changes.
275
295
  *
@@ -289,21 +309,17 @@ export declare function useHass(): {
289
309
  getHass: () => HomeAssistant | undefined;
290
310
  };
291
311
 
312
+ /** Re-renders when `hass.config` changes (units, latitude/longitude, etc.). */
313
+ export declare function useHassConfig(): HomeAssistant['config'] | undefined;
314
+
292
315
  /**
293
- * Fetch events from multiple calendars for a date range, with localStorage
294
- * caching. Events are tagged with their source calendar ID.
316
+ * Subscribe to a derived slice of the `hass` object (e.g. config, themes) and
317
+ * re-render only when that slice changes. Use this for non-entity values —
318
+ * entity state goes through `useEntity`. The selector runs on every hass update
319
+ * but only re-renders the consumer when `isEqual` reports a change, so it's
320
+ * cheap for rarely-changing values like config/themes.
295
321
  */
296
- export declare function useMultiCalendarEvents(entityIds: `calendar.${string}`[], options: {
297
- start: Date;
298
- end: Date;
299
- }): UseMultiCalendarEventsResult;
300
-
301
- declare interface UseMultiCalendarEventsResult {
302
- events: CalendarEventWithSource[] | undefined;
303
- status: FetchStatus;
304
- error: Error | undefined;
305
- refetch: () => void;
306
- }
322
+ export declare function useHassValue<T>(selector: (hass: HomeAssistant | undefined) => T, isEqual?: (a: T, b: T) => boolean): T;
307
323
 
308
324
  /**
309
325
  * Observe an element's size via ResizeObserver. The callback fires:
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
- const CACHE_PREFIX = "preact-ha:";
5
- const CACHE_EXPIRY_MS = 24 * 60 * 60 * 1e3;
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 saveToCache(key, data) {
21
- try {
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({ hass, subscribeToEntity, children }) {
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.hass?.states[entityId];
59
+ const current = store.getHass()?.states[entityId];
66
60
  if (current) return current;
67
- return loadFromCache(cacheKey);
61
+ return readCache(store.cache, cacheKey);
68
62
  });
69
63
  useEffect(() => {
70
64
  const unsubscribe = store.subscribeToEntity(entityId, (newEntity) => {
71
65
  setEntity(newEntity);
72
- saveToCache(cacheKey, newEntity);
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 [data, setData] = useState(() => loadFromCache(cacheKey));
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
- saveToCache(cacheKey, result);
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 useCalendarEvents(entityId, options) {
130
- const { getHass } = useHass();
131
- const [events, setEvents] = useState(void 0);
132
- const [loading, setLoading] = useState(false);
133
- const [error, setError] = useState(void 0);
134
- const fetchIdRef = useRef(0);
135
- const fetchEvents = useCallbackStable(async () => {
136
- const hass = getHass();
137
- if (!hass?.connection) {
138
- setError(new Error("Home Assistant connection not available"));
139
- return;
140
- }
141
- const fetchId = ++fetchIdRef.current;
142
- setLoading(true);
143
- setError(void 0);
144
- try {
145
- const result = await hass.connection.sendMessagePromise({
146
- type: "call_service",
147
- domain: "calendar",
148
- service: "get_events",
149
- service_data: {
150
- start_date_time: options.start.toISOString(),
151
- end_date_time: options.end.toISOString()
152
- },
153
- target: { entity_id: entityId },
154
- return_response: true
155
- });
156
- if (fetchId === fetchIdRef.current) {
157
- const entityEvents = result.response?.[entityId]?.events ?? [];
158
- setEvents(entityEvents);
159
- setLoading(false);
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
- useEffect(() => {
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 useMultiCalendarEvents(entityIds, options) {
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(async () => {
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,19 +314,55 @@ function registerPreactCard(options) {
323
314
  UnconfiguredComponent,
324
315
  getStubConfig
325
316
  } = options;
326
- class HACard extends HTMLElement {
317
+ class BaseHACard extends HTMLElement {
327
318
  _hass;
328
319
  _config;
320
+ _teardownTimer;
321
+ _renderUnconfigured() {
322
+ }
323
+ connectedCallback() {
324
+ if (this._teardownTimer !== void 0) {
325
+ clearTimeout(this._teardownTimer);
326
+ this._teardownTimer = void 0;
327
+ }
328
+ this._maybeRenderOnConnect();
329
+ }
330
+ _maybeRenderOnConnect() {
331
+ if (this._hass && this._config) {
332
+ this._render();
333
+ }
334
+ }
335
+ disconnectedCallback() {
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 {
329
357
  _shadowRoot;
330
358
  _entityChangeListeners = /* @__PURE__ */ new Map();
359
+ _hassChangeListeners = /* @__PURE__ */ new Set();
331
360
  constructor() {
332
361
  super();
333
362
  this._shadowRoot = this.attachShadow({ mode: "open" });
334
363
  }
335
- connectedCallback() {
336
- if (this._hass && this._config) {
337
- this._render();
338
- }
364
+ _getRenderRoot() {
365
+ return this._shadowRoot;
339
366
  }
340
367
  set hass(hass) {
341
368
  const prevStates = this._hass?.states;
@@ -347,16 +374,11 @@ function registerPreactCard(options) {
347
374
  listeners.forEach((listener) => listener(newState));
348
375
  }
349
376
  }
377
+ this._hassChangeListeners.forEach((listener) => listener());
350
378
  if (!prevStates && this._config && this.isConnected) {
351
379
  this._render();
352
380
  }
353
381
  }
354
- setConfig(config) {
355
- this._config = config;
356
- if (this._hass && this.isConnected) {
357
- this._render();
358
- }
359
- }
360
382
  _subscribeToEntity = (entityId, callback) => {
361
383
  if (!this._entityChangeListeners.has(entityId)) {
362
384
  this._entityChangeListeners.set(entityId, /* @__PURE__ */ new Set());
@@ -372,21 +394,34 @@ function registerPreactCard(options) {
372
394
  }
373
395
  };
374
396
  };
375
- _render() {
376
- if (!this._config || !this._hass) {
377
- if (UnconfiguredComponent) {
378
- render(/* @__PURE__ */ jsx(UnconfiguredComponent, {}), this._shadowRoot);
379
- }
380
- return;
381
- }
397
+ _subscribeToHass = (callback) => {
398
+ this._hassChangeListeners.add(callback);
399
+ return () => {
400
+ this._hassChangeListeners.delete(callback);
401
+ };
402
+ };
403
+ _renderTree() {
382
404
  render(
383
- /* @__PURE__ */ jsxs(HAProvider, { hass: this._hass, subscribeToEntity: this._subscribeToEntity, children: [
384
- /* @__PURE__ */ jsx("style", { children: getAllStyles() }),
385
- /* @__PURE__ */ jsx(Component, { config: this._config })
386
- ] }),
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
+ ),
387
417
  this._shadowRoot
388
418
  );
389
419
  }
420
+ _renderUnconfigured() {
421
+ if (UnconfiguredComponent) {
422
+ render(/* @__PURE__ */ jsx(UnconfiguredComponent, {}), this._shadowRoot);
423
+ }
424
+ }
390
425
  static getConfigElement() {
391
426
  if (ConfigComponent) {
392
427
  return document.createElement(`${type}-editor`);
@@ -400,13 +435,19 @@ function registerPreactCard(options) {
400
435
  customElements.define(type, HACard);
401
436
  if (ConfigComponent) {
402
437
  const EditorComponent = ConfigComponent;
403
- class HACardEditor extends HTMLElement {
404
- _hass;
405
- _config;
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.)
406
445
  set hass(hass) {
407
446
  this._hass = hass;
408
447
  this._render();
409
448
  }
449
+ // Render whenever config arrives, regardless of connection — HA may set
450
+ // config/hass before connecting the editor element.
410
451
  setConfig(config) {
411
452
  this._config = config;
412
453
  this._render();
@@ -420,8 +461,8 @@ function registerPreactCard(options) {
420
461
  })
421
462
  );
422
463
  };
423
- _render() {
424
- if (!this._hass || !this._config) return;
464
+ // Render to light DOM so HA's custom elements (ha-form etc.) work.
465
+ _renderTree() {
425
466
  render(
426
467
  /* @__PURE__ */ jsx(
427
468
  EditorComponent,
@@ -488,16 +529,16 @@ export {
488
529
  HAProvider,
489
530
  css,
490
531
  getAllStyles,
491
- loadFromCache,
492
532
  registerPreactCard,
493
533
  registerRawStyles,
494
- saveToCache,
495
534
  useCachedFetch,
496
535
  useCalendarEvents,
497
536
  useCallbackStable,
537
+ useDarkMode,
498
538
  useEntity,
499
539
  useHass,
500
- useMultiCalendarEvents,
540
+ useHassConfig,
541
+ useHassValue,
501
542
  useResizeObserver,
502
543
  useService,
503
544
  useWeatherForecast,