preact-homeassistant 0.2.2 → 0.2.3
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 +68 -0
- package/dist/index.js +43 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { HassEntityAttributeBase } from 'home-assistant-js-websocket';
|
|
|
8
8
|
import { HassEntityBase } from 'home-assistant-js-websocket';
|
|
9
9
|
import { HassServices } from 'home-assistant-js-websocket';
|
|
10
10
|
import { JSX } from 'preact';
|
|
11
|
+
import { RefObject } from 'preact';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Calendar entity - only exposes the current/next event.
|
|
@@ -74,6 +75,11 @@ export declare interface DomainServiceMap {
|
|
|
74
75
|
fan: FanServices;
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
export declare interface ElementSize {
|
|
79
|
+
width: number;
|
|
80
|
+
height: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
77
83
|
/**
|
|
78
84
|
* Infers the strict entity type from an entity ID literal type.
|
|
79
85
|
*
|
|
@@ -194,6 +200,8 @@ declare interface RegisterPreactCardOptions<TConfig> {
|
|
|
194
200
|
*/
|
|
195
201
|
export declare function registerRawStyles(styles: string): void;
|
|
196
202
|
|
|
203
|
+
export declare type ResizeCallback = (size: ElementSize) => void;
|
|
204
|
+
|
|
197
205
|
export declare function saveToCache<T>(key: string, data: T): void;
|
|
198
206
|
|
|
199
207
|
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>;
|
|
@@ -297,6 +305,39 @@ declare interface UseMultiCalendarEventsResult {
|
|
|
297
305
|
refetch: () => void;
|
|
298
306
|
}
|
|
299
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Observe an element's size via ResizeObserver. The callback fires:
|
|
310
|
+
*
|
|
311
|
+
* 1. Once after mount, with the element's current size.
|
|
312
|
+
* 2. Whenever the element's size changes.
|
|
313
|
+
* 3. Whenever `deps` change, re-firing with the current size — so callers
|
|
314
|
+
* can re-run draws when their inputs change without re-creating the
|
|
315
|
+
* observer.
|
|
316
|
+
*
|
|
317
|
+
* The callback is suppressed only while the element is detached from the
|
|
318
|
+
* document. Zero width/height is delivered to the callback as-is — consumers
|
|
319
|
+
* that need to skip degenerate sizes (e.g. canvas painters where a 0-sized
|
|
320
|
+
* drawImage throws InvalidStateError) should add their own early return.
|
|
321
|
+
*
|
|
322
|
+
* Sizes are read from `offsetWidth` / `offsetHeight` (CSS pixels, includes
|
|
323
|
+
* padding + border). The callback is held in a ref, so passing a fresh
|
|
324
|
+
* function each render is safe — it never re-creates the observer.
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* const containerRef = useRef<HTMLDivElement>(null);
|
|
328
|
+
* const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
329
|
+
*
|
|
330
|
+
* useResizeObserver(
|
|
331
|
+
* containerRef,
|
|
332
|
+
* ({ width, height }) => {
|
|
333
|
+
* if (width === 0 || height === 0) return; // optional, consumer's call
|
|
334
|
+
* drawChart(canvasRef.current, forecast, width, height);
|
|
335
|
+
* },
|
|
336
|
+
* [forecast],
|
|
337
|
+
* );
|
|
338
|
+
*/
|
|
339
|
+
export declare function useResizeObserver<T extends HTMLElement>(ref: RefObject<T>, callback: ResizeCallback, deps?: unknown[]): void;
|
|
340
|
+
|
|
300
341
|
/**
|
|
301
342
|
* Returns a stable function that calls services on a specific HA entity.
|
|
302
343
|
* The service domain is parsed from the entity ID prefix and `entity_id` is
|
|
@@ -323,6 +364,33 @@ declare interface UseWeatherForecastResult {
|
|
|
323
364
|
refetch: () => void;
|
|
324
365
|
}
|
|
325
366
|
|
|
367
|
+
/**
|
|
368
|
+
* Track the current width of a referenced element in CSS pixels.
|
|
369
|
+
*
|
|
370
|
+
* Returns `undefined` until the first non-zero measurement is observed, then
|
|
371
|
+
* a positive number that updates as the element resizes. Once a real width
|
|
372
|
+
* is captured the hook will never report `undefined` or `0` again, even
|
|
373
|
+
* during HA layout transitions (dashboard switch, edit-mode toggle) — the
|
|
374
|
+
* underlying ResizeObserver firings are silently dropped while the element
|
|
375
|
+
* is detached or transiently zero-width, so the component renders with the
|
|
376
|
+
* last good value instead of flashing through a degenerate state.
|
|
377
|
+
*
|
|
378
|
+
* Use this when you need a width value in JSX (responsive layout, prop to a
|
|
379
|
+
* sized child). If you only need the value imperatively inside a draw
|
|
380
|
+
* callback, prefer `useResizeObserver` directly — it doesn't allocate
|
|
381
|
+
* component state or cause re-renders.
|
|
382
|
+
*
|
|
383
|
+
* @example
|
|
384
|
+
* const ref = useRef<HTMLDivElement>(null);
|
|
385
|
+
* const width = useWidth(ref);
|
|
386
|
+
* return (
|
|
387
|
+
* <div ref={ref}>
|
|
388
|
+
* {width !== undefined && <Chart width={width} />}
|
|
389
|
+
* </div>
|
|
390
|
+
* );
|
|
391
|
+
*/
|
|
392
|
+
export declare function useWidth<T extends HTMLElement>(ref: RefObject<T>): number | undefined;
|
|
393
|
+
|
|
326
394
|
/**
|
|
327
395
|
* Weather entity - current conditions only.
|
|
328
396
|
* Use useWeatherForecast() to fetch forecast data.
|
package/dist/index.js
CHANGED
|
@@ -327,33 +327,16 @@ function registerPreactCard(options) {
|
|
|
327
327
|
_hass;
|
|
328
328
|
_config;
|
|
329
329
|
_shadowRoot;
|
|
330
|
-
_hasRendered = false;
|
|
331
330
|
_entityChangeListeners = /* @__PURE__ */ new Map();
|
|
332
|
-
// Pending tree teardown — see disconnectedCallback.
|
|
333
|
-
_unmountTimer;
|
|
334
331
|
constructor() {
|
|
335
332
|
super();
|
|
336
333
|
this._shadowRoot = this.attachShadow({ mode: "open" });
|
|
337
334
|
}
|
|
338
335
|
connectedCallback() {
|
|
339
|
-
if (this.
|
|
340
|
-
clearTimeout(this._unmountTimer);
|
|
341
|
-
this._unmountTimer = void 0;
|
|
342
|
-
}
|
|
343
|
-
if (this._hass && this._config && !this._hasRendered) {
|
|
336
|
+
if (this._hass && this._config) {
|
|
344
337
|
this._render();
|
|
345
338
|
}
|
|
346
339
|
}
|
|
347
|
-
disconnectedCallback() {
|
|
348
|
-
if (this._unmountTimer !== void 0) return;
|
|
349
|
-
this._unmountTimer = setTimeout(() => {
|
|
350
|
-
this._unmountTimer = void 0;
|
|
351
|
-
if (this.isConnected) return;
|
|
352
|
-
render(null, this._shadowRoot);
|
|
353
|
-
this._hasRendered = false;
|
|
354
|
-
this._entityChangeListeners.clear();
|
|
355
|
-
}, 100);
|
|
356
|
-
}
|
|
357
340
|
set hass(hass) {
|
|
358
341
|
const prevStates = this._hass?.states;
|
|
359
342
|
this._hass = hass;
|
|
@@ -403,7 +386,6 @@ function registerPreactCard(options) {
|
|
|
403
386
|
] }),
|
|
404
387
|
this._shadowRoot
|
|
405
388
|
);
|
|
406
|
-
this._hasRendered = true;
|
|
407
389
|
}
|
|
408
390
|
static getConfigElement() {
|
|
409
391
|
if (ConfigComponent) {
|
|
@@ -463,6 +445,45 @@ function registerPreactCard(options) {
|
|
|
463
445
|
""
|
|
464
446
|
);
|
|
465
447
|
}
|
|
448
|
+
function useResizeObserver(ref, callback, deps = []) {
|
|
449
|
+
const callbackRef = useRef(callback);
|
|
450
|
+
callbackRef.current = callback;
|
|
451
|
+
useEffect(() => {
|
|
452
|
+
const element = ref.current;
|
|
453
|
+
if (!element) return;
|
|
454
|
+
const fire = () => {
|
|
455
|
+
if (!element.isConnected) return;
|
|
456
|
+
callbackRef.current({
|
|
457
|
+
width: element.offsetWidth,
|
|
458
|
+
height: element.offsetHeight
|
|
459
|
+
});
|
|
460
|
+
};
|
|
461
|
+
const observer = new ResizeObserver(fire);
|
|
462
|
+
observer.observe(element);
|
|
463
|
+
return () => observer.disconnect();
|
|
464
|
+
}, []);
|
|
465
|
+
const isFirstRun = useRef(true);
|
|
466
|
+
useEffect(() => {
|
|
467
|
+
if (isFirstRun.current) {
|
|
468
|
+
isFirstRun.current = false;
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const element = ref.current;
|
|
472
|
+
if (!element || !element.isConnected) return;
|
|
473
|
+
callbackRef.current({
|
|
474
|
+
width: element.offsetWidth,
|
|
475
|
+
height: element.offsetHeight
|
|
476
|
+
});
|
|
477
|
+
}, deps);
|
|
478
|
+
}
|
|
479
|
+
function useWidth(ref) {
|
|
480
|
+
const [width, setWidth] = useState(void 0);
|
|
481
|
+
useResizeObserver(ref, (size) => {
|
|
482
|
+
if (size.width === 0) return;
|
|
483
|
+
setWidth((prev) => prev === size.width ? prev : size.width);
|
|
484
|
+
});
|
|
485
|
+
return width;
|
|
486
|
+
}
|
|
466
487
|
export {
|
|
467
488
|
HAProvider,
|
|
468
489
|
css,
|
|
@@ -477,7 +498,9 @@ export {
|
|
|
477
498
|
useEntity,
|
|
478
499
|
useHass,
|
|
479
500
|
useMultiCalendarEvents,
|
|
501
|
+
useResizeObserver,
|
|
480
502
|
useService,
|
|
481
|
-
useWeatherForecast
|
|
503
|
+
useWeatherForecast,
|
|
504
|
+
useWidth
|
|
482
505
|
};
|
|
483
506
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/cacheUtils.ts","../src/useCallbackStable.ts","../src/HAContext.tsx","../src/styleRegistry.ts","../src/registerPreactCard.tsx"],"sourcesContent":["const CACHE_PREFIX = 'preact-ha:';\nconst CACHE_EXPIRY_MS = 24 * 60 * 60 * 1000;\n\ninterface CacheEntry<T> {\n data: T;\n timestamp: number;\n}\n\nexport function loadFromCache<T>(key: string): T | undefined {\n try {\n const raw = localStorage.getItem(`${CACHE_PREFIX}${key}`);\n if (!raw) return undefined;\n\n const entry: CacheEntry<T> = JSON.parse(raw);\n if (Date.now() - entry.timestamp > CACHE_EXPIRY_MS) {\n localStorage.removeItem(`${CACHE_PREFIX}${key}`);\n return undefined;\n }\n return entry.data;\n } catch {\n return undefined;\n }\n}\n\nexport function saveToCache<T>(key: string, data: T): void {\n try {\n const entry: CacheEntry<T> = { data, timestamp: Date.now() };\n localStorage.setItem(`${CACHE_PREFIX}${key}`, JSON.stringify(entry));\n } catch (e) {\n console.warn('[preact-homeassistant cache] Failed to save:', e);\n }\n}\n","import { useRef } from 'preact/hooks';\n\n/**\n * Creates a stable callback reference that always calls the latest version of the callback.\n * Unlike useCallback, this never changes identity, so it won't cause re-renders in children.\n *\n * @param callback The callback function to stabilize\n * @returns A stable function reference that always calls the latest callback\n */\nexport function useCallbackStable<T extends (...args: never[]) => unknown>(callback: T): T {\n const callbackRef = useRef<T>(callback);\n\n callbackRef.current = callback;\n\n // Create a stable function reference once\n const stableRef = useRef<T | null>(null);\n if (stableRef.current === null) {\n stableRef.current = ((...args: Parameters<T>) => {\n return callbackRef.current(...args);\n }) as T;\n }\n\n return stableRef.current;\n}\n","import { createContext } from 'preact';\nimport type { ComponentChildren } from 'preact';\nimport { useContext, useEffect, useMemo, useRef, useState } from 'preact/hooks';\n\nimport { loadFromCache, saveToCache } from './cacheUtils';\nimport type {\n CalendarEvent,\n CalendarEventWithSource,\n EntityForId,\n FetchStatus,\n ForecastType,\n HomeAssistant,\n ServicesForId,\n WeatherForecast,\n} from './types';\nimport { useCallbackStable } from './useCallbackStable';\n\ninterface HAStore {\n hass: HomeAssistant | undefined;\n getHass: () => HomeAssistant | undefined;\n subscribeToEntity: (entityId: string, callback: (entity: any) => void) => () => void;\n}\n\nconst HAContext = createContext<HAStore | null>(null);\n\ninterface HAProviderProps {\n hass: HomeAssistant | undefined;\n subscribeToEntity: (entityId: string, callback: (entity: any) => void) => () => void;\n children: ComponentChildren;\n}\n\nexport function HAProvider({ hass, subscribeToEntity, children }: HAProviderProps) {\n const hassRef = useRef(hass);\n hassRef.current = hass;\n\n const getHass = useCallbackStable(() => hassRef.current);\n\n const store = useMemo<HAStore>(\n () => ({\n hass: hassRef.current,\n getHass,\n subscribeToEntity,\n }),\n [getHass, subscribeToEntity],\n );\n\n return <HAContext.Provider value={store}>{children}</HAContext.Provider>;\n}\n\nfunction useHAStore(): HAStore {\n const store = useContext(HAContext);\n if (!store) {\n throw new Error('useEntity/useHass must be used within an HAProvider');\n }\n return store;\n}\n\n/**\n * Subscribe to a specific entity by ID. Re-renders only when that entity changes.\n *\n * Returns a typed entity based on the domain prefix:\n * - 'calendar.xyz' -> CalendarEntity\n * - 'weather.xyz' -> WeatherEntity\n * - 'sun.sun' -> SunEntity\n * - other domains -> HassEntity (fallback)\n */\nexport function useEntity<T extends string>(entityId: T): EntityForId<T> | undefined {\n const store = useHAStore();\n const cacheKey = `entity:${entityId}`;\n\n const [entity, setEntity] = useState<EntityForId<T> | undefined>(() => {\n const current = store.hass?.states[entityId] as EntityForId<T> | undefined;\n if (current) return current;\n return loadFromCache<EntityForId<T>>(cacheKey);\n });\n\n useEffect(() => {\n const unsubscribe = store.subscribeToEntity(entityId, (newEntity) => {\n setEntity(newEntity as EntityForId<T>);\n saveToCache(cacheKey, newEntity);\n });\n return unsubscribe;\n }, [entityId, store.subscribeToEntity, cacheKey]);\n\n return entity;\n}\n\n/**\n * Get access to the full hass object for calling services / accessing config.\n * Does NOT re-render on entity changes. Use useEntity for that.\n */\nexport function useHass(): { getHass: () => HomeAssistant | undefined } {\n const store = useHAStore();\n return { getHass: store.getHass };\n}\n\ntype ServiceCaller<T extends string> = <S extends keyof ServicesForId<T> & string>(\n service: S,\n ...args: ServicesForId<T>[S] extends undefined\n ? []\n : Record<string, never> extends Exclude<ServicesForId<T>[S], undefined>\n ? [data?: ServicesForId<T>[S]]\n : [data: ServicesForId<T>[S]]\n) => Promise<void>;\n\n/**\n * Returns a stable function that calls services on a specific HA entity.\n * The service domain is parsed from the entity ID prefix and `entity_id` is\n * auto-injected into every call. Service names and data shapes are strongly\n * typed via DomainServiceMap when the domain is registered. No-ops if hass\n * is not yet available or the entity ID is empty.\n *\n * const fanService = useService(config.entity); // `fan.${string}`\n * await fanService('turn_off');\n * await fanService('set_percentage', { percentage: 67 });\n */\nexport function useService<T extends string>(entityId: T): ServiceCaller<T> {\n const { getHass } = useHass();\n return useCallbackStable(((service: string, data?: object) => {\n const hass = getHass();\n if (!hass || !entityId.includes('.')) return Promise.resolve();\n const domain = entityId.split('.', 1)[0];\n return hass.callService(domain, service, { entity_id: entityId, ...data });\n }) as ServiceCaller<T>);\n}\n\ninterface UseCachedFetchResult<T> {\n data: T | undefined;\n status: FetchStatus;\n error: Error | undefined;\n refetch: () => void;\n}\n\n/**\n * Generic hook for fetching data with localStorage caching. Returns a cache-aware\n * status string to distinguish cached vs fresh data.\n */\nexport function useCachedFetch<T>(\n cacheKey: string,\n fetcher: () => Promise<T>,\n deps: unknown[],\n): UseCachedFetchResult<T> {\n const [data, setData] = useState<T | undefined>(() => loadFromCache<T>(cacheKey));\n const [isFresh, setIsFresh] = useState(false);\n const [isFetching, setIsFetching] = useState(false);\n const [error, setError] = useState<Error | undefined>(undefined);\n\n const fetchIdRef = useRef(0);\n\n const doFetch = useCallbackStable(async () => {\n const fetchId = ++fetchIdRef.current;\n setIsFetching(true);\n setError(undefined);\n\n try {\n const result = await fetcher();\n if (fetchId === fetchIdRef.current) {\n setData(result);\n setIsFresh(true);\n saveToCache(cacheKey, result);\n }\n } catch (err) {\n if (fetchId === fetchIdRef.current) {\n setError(err instanceof Error ? err : new Error(String(err)));\n }\n } finally {\n if (fetchId === fetchIdRef.current) {\n setIsFetching(false);\n }\n }\n });\n\n useEffect(() => {\n doFetch();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps);\n\n const status: FetchStatus = useMemo(() => {\n if (!data && isFetching) return 'loading';\n if (data && !isFresh && isFetching) return 'cached';\n if (data && isFresh && isFetching) return 'refreshing';\n return 'ready';\n }, [data, isFresh, isFetching]);\n\n return { data, status, error, refetch: doFetch };\n}\n\ninterface UseCalendarEventsResult {\n events: CalendarEvent[] | undefined;\n loading: boolean;\n error: Error | undefined;\n refetch: () => void;\n}\n\n/**\n * Fetch calendar events for a date range from a single calendar.\n */\nexport function useCalendarEvents(\n entityId: `calendar.${string}`,\n options: { start: Date; end: Date },\n): UseCalendarEventsResult {\n const { getHass } = useHass();\n const [events, setEvents] = useState<CalendarEvent[] | undefined>(undefined);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | undefined>(undefined);\n\n const fetchIdRef = useRef(0);\n\n const fetchEvents = useCallbackStable(async () => {\n const hass = getHass();\n if (!hass?.connection) {\n setError(new Error('Home Assistant connection not available'));\n return;\n }\n\n const fetchId = ++fetchIdRef.current;\n setLoading(true);\n setError(undefined);\n\n try {\n const result = await hass.connection.sendMessagePromise<{\n response: { [entityId: string]: { events: CalendarEvent[] } };\n }>({\n type: 'call_service',\n domain: 'calendar',\n service: 'get_events',\n service_data: {\n start_date_time: options.start.toISOString(),\n end_date_time: options.end.toISOString(),\n },\n target: { entity_id: entityId },\n return_response: true,\n });\n\n if (fetchId === fetchIdRef.current) {\n const entityEvents = result.response?.[entityId]?.events ?? [];\n setEvents(entityEvents);\n setLoading(false);\n }\n } catch (err) {\n if (fetchId === fetchIdRef.current) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n }\n }\n });\n\n useEffect(() => {\n fetchEvents();\n }, [entityId, options.start.getTime(), options.end.getTime(), fetchEvents]);\n\n return { events, loading, error, refetch: fetchEvents };\n}\n\ninterface UseMultiCalendarEventsResult {\n events: CalendarEventWithSource[] | undefined;\n status: FetchStatus;\n error: Error | undefined;\n refetch: () => void;\n}\n\n/**\n * Fetch events from multiple calendars for a date range, with localStorage\n * caching. Events are tagged with their source calendar ID.\n */\nexport function useMultiCalendarEvents(\n entityIds: `calendar.${string}`[],\n options: { start: Date; end: Date },\n): UseMultiCalendarEventsResult {\n const store = useHAStore();\n const { getHass } = useHass();\n\n const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const entityIdsKey = entityIds.join(',');\n const dateRangeKey = `${options.start.getTime()}-${options.end.getTime()}`;\n const cacheKey = `events:${entityIdsKey}:${dateRangeKey}`;\n\n const fetcher = useCallbackStable(async () => {\n const hass = getHass();\n if (!hass?.connection) {\n throw new Error('Home Assistant connection not available');\n }\n\n if (entityIds.length === 0) {\n return [];\n }\n\n const results = await Promise.all(\n entityIds.map(async (entityId) => {\n try {\n const result = await hass.connection.sendMessagePromise<{\n response: { [key: string]: { events: CalendarEvent[] } };\n }>({\n type: 'call_service',\n domain: 'calendar',\n service: 'get_events',\n service_data: {\n start_date_time: options.start.toISOString(),\n end_date_time: options.end.toISOString(),\n },\n target: { entity_id: entityId },\n return_response: true,\n });\n\n const calendarEvents = result.response?.[entityId]?.events ?? [];\n return calendarEvents.map(\n (event): CalendarEventWithSource => ({ ...event, calendarId: entityId }),\n );\n } catch (err) {\n console.error(`Failed to fetch events for ${entityId}:`, err);\n return [];\n }\n }),\n );\n\n return results.flat();\n });\n\n const {\n data: events,\n status,\n error,\n refetch,\n } = useCachedFetch(cacheKey, fetcher, [entityIdsKey, dateRangeKey]);\n\n const debouncedRefetch = useCallbackStable(() => {\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current);\n }\n debounceTimerRef.current = setTimeout(() => refetch(), 500);\n });\n\n useEffect(() => {\n const unsubscribes = entityIds.map((entityId) =>\n store.subscribeToEntity(entityId, debouncedRefetch),\n );\n return () => {\n unsubscribes.forEach((unsub) => unsub());\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current);\n }\n };\n }, [entityIdsKey, store.subscribeToEntity, debouncedRefetch]);\n\n return { events, status, error, refetch };\n}\n\ninterface UseWeatherForecastResult {\n forecast: WeatherForecast[] | undefined;\n status: FetchStatus;\n error: Error | undefined;\n refetch: () => void;\n}\n\n/**\n * Fetch weather forecast data with localStorage caching. Auto-refetches at the\n * top of each hour and when the underlying entity changes (debounced).\n */\nexport function useWeatherForecast(\n entityId: `weather.${string}`,\n type: ForecastType,\n): UseWeatherForecastResult {\n const store = useHAStore();\n const { getHass } = useHass();\n const cacheKey = `forecast:${entityId}:${type}`;\n\n const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const hourlyTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const fetcher = useCallbackStable(async () => {\n const hass = getHass();\n if (!hass?.connection) {\n throw new Error('Home Assistant connection not available');\n }\n\n const result = await hass.connection.sendMessagePromise<{\n response: { [entityId: string]: { forecast: WeatherForecast[] } };\n }>({\n type: 'call_service',\n domain: 'weather',\n service: 'get_forecasts',\n service_data: { type },\n target: { entity_id: entityId },\n return_response: true,\n });\n\n return result.response?.[entityId]?.forecast ?? [];\n });\n\n const {\n data: forecast,\n status,\n error,\n refetch,\n } = useCachedFetch(cacheKey, fetcher, [entityId, type]);\n\n const debouncedRefetch = useCallbackStable(() => {\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current);\n }\n debounceTimerRef.current = setTimeout(() => refetch(), 500);\n });\n\n const scheduleHourlyRefetch = useCallbackStable(() => {\n if (hourlyTimerRef.current) {\n clearTimeout(hourlyTimerRef.current);\n }\n const now = new Date();\n const nextHour = new Date(now);\n nextHour.setHours(now.getHours() + 1, 0, 0, 0);\n const msUntilNextHour = nextHour.getTime() - now.getTime();\n\n hourlyTimerRef.current = setTimeout(() => {\n refetch();\n scheduleHourlyRefetch();\n }, msUntilNextHour);\n });\n\n useEffect(() => {\n scheduleHourlyRefetch();\n }, [entityId, type, scheduleHourlyRefetch]);\n\n useEffect(() => {\n const unsubscribe = store.subscribeToEntity(entityId, debouncedRefetch);\n return () => {\n unsubscribe();\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current);\n }\n if (hourlyTimerRef.current) {\n clearTimeout(hourlyTimerRef.current);\n }\n };\n }, [entityId, store.subscribeToEntity, debouncedRefetch]);\n\n return { forecast, status, error, refetch };\n}\n","// Style registry for Shadow DOM injection\n// Each .styles.ts file uses css`` which auto-registers\n\nconst styleRegistry: string[] = [];\n\n/**\n * CSS tagged template literal for syntax highlighting.\n * Automatically registers the styles with the global registry.\n */\nexport const css = (strings: TemplateStringsArray, ...values: unknown[]): string => {\n const result = strings.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '');\n styleRegistry.push(result);\n return result;\n};\n\n/**\n * Register raw CSS string (e.g., from ?inline imports).\n * Only registers if not already present.\n */\nexport function registerRawStyles(styles: string): void {\n if (!styleRegistry.includes(styles)) {\n styleRegistry.push(styles);\n }\n}\n\n/**\n * Get all registered styles for Shadow DOM injection.\n */\nexport function getAllStyles(): string {\n return styleRegistry.join('\\n');\n}\n","import { type ComponentType, render } from 'preact';\nimport { HAProvider } from './HAContext';\nimport { getAllStyles } from './styleRegistry';\nimport type { HomeAssistant } from './types';\n\ninterface RegisterPreactCardOptions<TConfig> {\n type: string;\n name: string;\n description: string;\n Component: ComponentType<{ config: TConfig }>;\n ConfigComponent?: ComponentType<{\n hass: HomeAssistant;\n config: TConfig;\n onConfigChanged: (config: TConfig) => void;\n }>;\n UnconfiguredComponent?: ComponentType<{}>;\n getStubConfig?: () => Partial<TConfig>;\n}\n\ndeclare global {\n interface Window {\n customCards?: Array<{ type: string; name: string; description: string }>;\n }\n}\n\nexport function registerPreactCard<TConfig>(options: RegisterPreactCardOptions<TConfig>) {\n const {\n type,\n name,\n description,\n Component,\n ConfigComponent,\n UnconfiguredComponent,\n getStubConfig,\n } = options;\n\n class HACard extends HTMLElement {\n private _hass?: HomeAssistant;\n private _config?: TConfig;\n private _shadowRoot: ShadowRoot;\n private _hasRendered = false;\n private _entityChangeListeners = new Map<string, Set<(entity: any) => void>>();\n // Pending tree teardown — see disconnectedCallback.\n private _unmountTimer: ReturnType<typeof setTimeout> | undefined;\n\n constructor() {\n super();\n this._shadowRoot = this.attachShadow({ mode: 'open' });\n }\n\n connectedCallback() {\n // If a teardown was scheduled by a recent disconnect, cancel it: HA is\n // doing a transient detach/reattach (edit-mode toggle, drag/drop) and\n // the tree is still valid.\n if (this._unmountTimer !== undefined) {\n clearTimeout(this._unmountTimer);\n this._unmountTimer = undefined;\n }\n if (this._hass && this._config && !this._hasRendered) {\n this._render();\n }\n }\n\n disconnectedCallback() {\n // HA disconnects + reconnects the element during edit-mode toggling and\n // drag-and-drop. A reconnect lands within a tick. If we're still\n // detached after a short delay, tear the Preact tree down properly so\n // useEffect cleanups run (e.g. Leaflet's map.remove(), which removes\n // its non-passive touch/wheel listeners from window/document).\n // Leaving the tree mounted on a detached shadow root leaks listeners on\n // every reconnect cycle and eventually locks up the UI.\n if (this._unmountTimer !== undefined) return;\n this._unmountTimer = setTimeout(() => {\n this._unmountTimer = undefined;\n if (this.isConnected) return;\n render(null, this._shadowRoot);\n this._hasRendered = false;\n this._entityChangeListeners.clear();\n }, 100);\n }\n\n set hass(hass: HomeAssistant) {\n const prevStates = this._hass?.states;\n this._hass = hass;\n\n for (const [entityId, listeners] of this._entityChangeListeners) {\n const newState = hass.states[entityId];\n const oldState = prevStates?.[entityId];\n if (newState !== oldState) {\n listeners.forEach((listener) => listener(newState));\n }\n }\n\n // Render only when attached. HA may set hass/config before insertion;\n // rendering into a detached shadow root then again on connect duplicates\n // the tree. connectedCallback handles the detached-first-render case.\n if (!prevStates && this._config && this.isConnected) {\n this._render();\n }\n }\n\n setConfig(config: TConfig) {\n this._config = config;\n if (this._hass && this.isConnected) {\n this._render();\n }\n }\n\n private _subscribeToEntity = (entityId: string, callback: (entity: any) => void) => {\n if (!this._entityChangeListeners.has(entityId)) {\n this._entityChangeListeners.set(entityId, new Set());\n }\n this._entityChangeListeners.get(entityId)!.add(callback);\n\n return () => {\n const listeners = this._entityChangeListeners.get(entityId);\n if (listeners) {\n listeners.delete(callback);\n if (listeners.size === 0) {\n this._entityChangeListeners.delete(entityId);\n }\n }\n };\n };\n\n private _render() {\n if (!this._config || !this._hass) {\n if (UnconfiguredComponent) {\n render(<UnconfiguredComponent />, this._shadowRoot);\n }\n return;\n }\n\n render(\n <HAProvider hass={this._hass} subscribeToEntity={this._subscribeToEntity}>\n <style>{getAllStyles()}</style>\n <Component config={this._config} />\n </HAProvider>,\n this._shadowRoot,\n );\n this._hasRendered = true;\n }\n\n static getConfigElement() {\n if (ConfigComponent) {\n return document.createElement(`${type}-editor`);\n }\n return undefined;\n }\n\n static getStubConfig() {\n return getStubConfig?.() ?? {};\n }\n }\n\n customElements.define(type, HACard);\n\n if (ConfigComponent) {\n const EditorComponent = ConfigComponent;\n\n class HACardEditor extends HTMLElement {\n private _hass?: HomeAssistant;\n private _config?: TConfig;\n\n set hass(hass: HomeAssistant) {\n this._hass = hass;\n this._render();\n }\n\n setConfig(config: TConfig) {\n this._config = config;\n this._render();\n }\n\n private _fireConfigChanged = (config: TConfig) => {\n this.dispatchEvent(\n new CustomEvent('config-changed', {\n detail: { config },\n bubbles: true,\n composed: true,\n }),\n );\n };\n\n private _render() {\n if (!this._hass || !this._config) return;\n\n // Render to light DOM so HA's custom elements (ha-select etc.) work.\n render(\n <EditorComponent\n hass={this._hass}\n config={this._config}\n onConfigChanged={this._fireConfigChanged}\n />,\n this,\n );\n }\n }\n\n customElements.define(`${type}-editor`, HACardEditor);\n }\n\n window.customCards = window.customCards || [];\n window.customCards.push({ type, name, description });\n\n console.info(\n `%c ${name.toUpperCase()} %c loaded `,\n 'background: #3b82f6; color: white; font-weight: bold',\n '',\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/cacheUtils.ts","../src/useCallbackStable.ts","../src/HAContext.tsx","../src/styleRegistry.ts","../src/registerPreactCard.tsx","../src/useResizeObserver.ts","../src/useWidth.ts"],"sourcesContent":["const CACHE_PREFIX = 'preact-ha:';\nconst CACHE_EXPIRY_MS = 24 * 60 * 60 * 1000;\n\ninterface CacheEntry<T> {\n data: T;\n timestamp: number;\n}\n\nexport function loadFromCache<T>(key: string): T | undefined {\n try {\n const raw = localStorage.getItem(`${CACHE_PREFIX}${key}`);\n if (!raw) return undefined;\n\n const entry: CacheEntry<T> = JSON.parse(raw);\n if (Date.now() - entry.timestamp > CACHE_EXPIRY_MS) {\n localStorage.removeItem(`${CACHE_PREFIX}${key}`);\n return undefined;\n }\n return entry.data;\n } catch {\n return undefined;\n }\n}\n\nexport function saveToCache<T>(key: string, data: T): void {\n try {\n const entry: CacheEntry<T> = { data, timestamp: Date.now() };\n localStorage.setItem(`${CACHE_PREFIX}${key}`, JSON.stringify(entry));\n } catch (e) {\n console.warn('[preact-homeassistant cache] Failed to save:', e);\n }\n}\n","import { useRef } from 'preact/hooks';\n\n/**\n * Creates a stable callback reference that always calls the latest version of the callback.\n * Unlike useCallback, this never changes identity, so it won't cause re-renders in children.\n *\n * @param callback The callback function to stabilize\n * @returns A stable function reference that always calls the latest callback\n */\nexport function useCallbackStable<T extends (...args: never[]) => unknown>(callback: T): T {\n const callbackRef = useRef<T>(callback);\n\n callbackRef.current = callback;\n\n // Create a stable function reference once\n const stableRef = useRef<T | null>(null);\n if (stableRef.current === null) {\n stableRef.current = ((...args: Parameters<T>) => {\n return callbackRef.current(...args);\n }) as T;\n }\n\n return stableRef.current;\n}\n","import { createContext } from 'preact';\nimport type { ComponentChildren } from 'preact';\nimport { useContext, useEffect, useMemo, useRef, useState } from 'preact/hooks';\n\nimport { loadFromCache, saveToCache } from './cacheUtils';\nimport type {\n CalendarEvent,\n CalendarEventWithSource,\n EntityForId,\n FetchStatus,\n ForecastType,\n HomeAssistant,\n ServicesForId,\n WeatherForecast,\n} from './types';\nimport { useCallbackStable } from './useCallbackStable';\n\ninterface HAStore {\n hass: HomeAssistant | undefined;\n getHass: () => HomeAssistant | undefined;\n subscribeToEntity: (entityId: string, callback: (entity: any) => void) => () => void;\n}\n\nconst HAContext = createContext<HAStore | null>(null);\n\ninterface HAProviderProps {\n hass: HomeAssistant | undefined;\n subscribeToEntity: (entityId: string, callback: (entity: any) => void) => () => void;\n children: ComponentChildren;\n}\n\nexport function HAProvider({ hass, subscribeToEntity, children }: HAProviderProps) {\n const hassRef = useRef(hass);\n hassRef.current = hass;\n\n const getHass = useCallbackStable(() => hassRef.current);\n\n const store = useMemo<HAStore>(\n () => ({\n hass: hassRef.current,\n getHass,\n subscribeToEntity,\n }),\n [getHass, subscribeToEntity],\n );\n\n return <HAContext.Provider value={store}>{children}</HAContext.Provider>;\n}\n\nfunction useHAStore(): HAStore {\n const store = useContext(HAContext);\n if (!store) {\n throw new Error('useEntity/useHass must be used within an HAProvider');\n }\n return store;\n}\n\n/**\n * Subscribe to a specific entity by ID. Re-renders only when that entity changes.\n *\n * Returns a typed entity based on the domain prefix:\n * - 'calendar.xyz' -> CalendarEntity\n * - 'weather.xyz' -> WeatherEntity\n * - 'sun.sun' -> SunEntity\n * - other domains -> HassEntity (fallback)\n */\nexport function useEntity<T extends string>(entityId: T): EntityForId<T> | undefined {\n const store = useHAStore();\n const cacheKey = `entity:${entityId}`;\n\n const [entity, setEntity] = useState<EntityForId<T> | undefined>(() => {\n const current = store.hass?.states[entityId] as EntityForId<T> | undefined;\n if (current) return current;\n return loadFromCache<EntityForId<T>>(cacheKey);\n });\n\n useEffect(() => {\n const unsubscribe = store.subscribeToEntity(entityId, (newEntity) => {\n setEntity(newEntity as EntityForId<T>);\n saveToCache(cacheKey, newEntity);\n });\n return unsubscribe;\n }, [entityId, store.subscribeToEntity, cacheKey]);\n\n return entity;\n}\n\n/**\n * Get access to the full hass object for calling services / accessing config.\n * Does NOT re-render on entity changes. Use useEntity for that.\n */\nexport function useHass(): { getHass: () => HomeAssistant | undefined } {\n const store = useHAStore();\n return { getHass: store.getHass };\n}\n\ntype ServiceCaller<T extends string> = <S extends keyof ServicesForId<T> & string>(\n service: S,\n ...args: ServicesForId<T>[S] extends undefined\n ? []\n : Record<string, never> extends Exclude<ServicesForId<T>[S], undefined>\n ? [data?: ServicesForId<T>[S]]\n : [data: ServicesForId<T>[S]]\n) => Promise<void>;\n\n/**\n * Returns a stable function that calls services on a specific HA entity.\n * The service domain is parsed from the entity ID prefix and `entity_id` is\n * auto-injected into every call. Service names and data shapes are strongly\n * typed via DomainServiceMap when the domain is registered. No-ops if hass\n * is not yet available or the entity ID is empty.\n *\n * const fanService = useService(config.entity); // `fan.${string}`\n * await fanService('turn_off');\n * await fanService('set_percentage', { percentage: 67 });\n */\nexport function useService<T extends string>(entityId: T): ServiceCaller<T> {\n const { getHass } = useHass();\n return useCallbackStable(((service: string, data?: object) => {\n const hass = getHass();\n if (!hass || !entityId.includes('.')) return Promise.resolve();\n const domain = entityId.split('.', 1)[0];\n return hass.callService(domain, service, { entity_id: entityId, ...data });\n }) as ServiceCaller<T>);\n}\n\ninterface UseCachedFetchResult<T> {\n data: T | undefined;\n status: FetchStatus;\n error: Error | undefined;\n refetch: () => void;\n}\n\n/**\n * Generic hook for fetching data with localStorage caching. Returns a cache-aware\n * status string to distinguish cached vs fresh data.\n */\nexport function useCachedFetch<T>(\n cacheKey: string,\n fetcher: () => Promise<T>,\n deps: unknown[],\n): UseCachedFetchResult<T> {\n const [data, setData] = useState<T | undefined>(() => loadFromCache<T>(cacheKey));\n const [isFresh, setIsFresh] = useState(false);\n const [isFetching, setIsFetching] = useState(false);\n const [error, setError] = useState<Error | undefined>(undefined);\n\n const fetchIdRef = useRef(0);\n\n const doFetch = useCallbackStable(async () => {\n const fetchId = ++fetchIdRef.current;\n setIsFetching(true);\n setError(undefined);\n\n try {\n const result = await fetcher();\n if (fetchId === fetchIdRef.current) {\n setData(result);\n setIsFresh(true);\n saveToCache(cacheKey, result);\n }\n } catch (err) {\n if (fetchId === fetchIdRef.current) {\n setError(err instanceof Error ? err : new Error(String(err)));\n }\n } finally {\n if (fetchId === fetchIdRef.current) {\n setIsFetching(false);\n }\n }\n });\n\n useEffect(() => {\n doFetch();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps);\n\n const status: FetchStatus = useMemo(() => {\n if (!data && isFetching) return 'loading';\n if (data && !isFresh && isFetching) return 'cached';\n if (data && isFresh && isFetching) return 'refreshing';\n return 'ready';\n }, [data, isFresh, isFetching]);\n\n return { data, status, error, refetch: doFetch };\n}\n\ninterface UseCalendarEventsResult {\n events: CalendarEvent[] | undefined;\n loading: boolean;\n error: Error | undefined;\n refetch: () => void;\n}\n\n/**\n * Fetch calendar events for a date range from a single calendar.\n */\nexport function useCalendarEvents(\n entityId: `calendar.${string}`,\n options: { start: Date; end: Date },\n): UseCalendarEventsResult {\n const { getHass } = useHass();\n const [events, setEvents] = useState<CalendarEvent[] | undefined>(undefined);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | undefined>(undefined);\n\n const fetchIdRef = useRef(0);\n\n const fetchEvents = useCallbackStable(async () => {\n const hass = getHass();\n if (!hass?.connection) {\n setError(new Error('Home Assistant connection not available'));\n return;\n }\n\n const fetchId = ++fetchIdRef.current;\n setLoading(true);\n setError(undefined);\n\n try {\n const result = await hass.connection.sendMessagePromise<{\n response: { [entityId: string]: { events: CalendarEvent[] } };\n }>({\n type: 'call_service',\n domain: 'calendar',\n service: 'get_events',\n service_data: {\n start_date_time: options.start.toISOString(),\n end_date_time: options.end.toISOString(),\n },\n target: { entity_id: entityId },\n return_response: true,\n });\n\n if (fetchId === fetchIdRef.current) {\n const entityEvents = result.response?.[entityId]?.events ?? [];\n setEvents(entityEvents);\n setLoading(false);\n }\n } catch (err) {\n if (fetchId === fetchIdRef.current) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n }\n }\n });\n\n useEffect(() => {\n fetchEvents();\n }, [entityId, options.start.getTime(), options.end.getTime(), fetchEvents]);\n\n return { events, loading, error, refetch: fetchEvents };\n}\n\ninterface UseMultiCalendarEventsResult {\n events: CalendarEventWithSource[] | undefined;\n status: FetchStatus;\n error: Error | undefined;\n refetch: () => void;\n}\n\n/**\n * Fetch events from multiple calendars for a date range, with localStorage\n * caching. Events are tagged with their source calendar ID.\n */\nexport function useMultiCalendarEvents(\n entityIds: `calendar.${string}`[],\n options: { start: Date; end: Date },\n): UseMultiCalendarEventsResult {\n const store = useHAStore();\n const { getHass } = useHass();\n\n const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const entityIdsKey = entityIds.join(',');\n const dateRangeKey = `${options.start.getTime()}-${options.end.getTime()}`;\n const cacheKey = `events:${entityIdsKey}:${dateRangeKey}`;\n\n const fetcher = useCallbackStable(async () => {\n const hass = getHass();\n if (!hass?.connection) {\n throw new Error('Home Assistant connection not available');\n }\n\n if (entityIds.length === 0) {\n return [];\n }\n\n const results = await Promise.all(\n entityIds.map(async (entityId) => {\n try {\n const result = await hass.connection.sendMessagePromise<{\n response: { [key: string]: { events: CalendarEvent[] } };\n }>({\n type: 'call_service',\n domain: 'calendar',\n service: 'get_events',\n service_data: {\n start_date_time: options.start.toISOString(),\n end_date_time: options.end.toISOString(),\n },\n target: { entity_id: entityId },\n return_response: true,\n });\n\n const calendarEvents = result.response?.[entityId]?.events ?? [];\n return calendarEvents.map(\n (event): CalendarEventWithSource => ({ ...event, calendarId: entityId }),\n );\n } catch (err) {\n console.error(`Failed to fetch events for ${entityId}:`, err);\n return [];\n }\n }),\n );\n\n return results.flat();\n });\n\n const {\n data: events,\n status,\n error,\n refetch,\n } = useCachedFetch(cacheKey, fetcher, [entityIdsKey, dateRangeKey]);\n\n const debouncedRefetch = useCallbackStable(() => {\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current);\n }\n debounceTimerRef.current = setTimeout(() => refetch(), 500);\n });\n\n useEffect(() => {\n const unsubscribes = entityIds.map((entityId) =>\n store.subscribeToEntity(entityId, debouncedRefetch),\n );\n return () => {\n unsubscribes.forEach((unsub) => unsub());\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current);\n }\n };\n }, [entityIdsKey, store.subscribeToEntity, debouncedRefetch]);\n\n return { events, status, error, refetch };\n}\n\ninterface UseWeatherForecastResult {\n forecast: WeatherForecast[] | undefined;\n status: FetchStatus;\n error: Error | undefined;\n refetch: () => void;\n}\n\n/**\n * Fetch weather forecast data with localStorage caching. Auto-refetches at the\n * top of each hour and when the underlying entity changes (debounced).\n */\nexport function useWeatherForecast(\n entityId: `weather.${string}`,\n type: ForecastType,\n): UseWeatherForecastResult {\n const store = useHAStore();\n const { getHass } = useHass();\n const cacheKey = `forecast:${entityId}:${type}`;\n\n const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const hourlyTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const fetcher = useCallbackStable(async () => {\n const hass = getHass();\n if (!hass?.connection) {\n throw new Error('Home Assistant connection not available');\n }\n\n const result = await hass.connection.sendMessagePromise<{\n response: { [entityId: string]: { forecast: WeatherForecast[] } };\n }>({\n type: 'call_service',\n domain: 'weather',\n service: 'get_forecasts',\n service_data: { type },\n target: { entity_id: entityId },\n return_response: true,\n });\n\n return result.response?.[entityId]?.forecast ?? [];\n });\n\n const {\n data: forecast,\n status,\n error,\n refetch,\n } = useCachedFetch(cacheKey, fetcher, [entityId, type]);\n\n const debouncedRefetch = useCallbackStable(() => {\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current);\n }\n debounceTimerRef.current = setTimeout(() => refetch(), 500);\n });\n\n const scheduleHourlyRefetch = useCallbackStable(() => {\n if (hourlyTimerRef.current) {\n clearTimeout(hourlyTimerRef.current);\n }\n const now = new Date();\n const nextHour = new Date(now);\n nextHour.setHours(now.getHours() + 1, 0, 0, 0);\n const msUntilNextHour = nextHour.getTime() - now.getTime();\n\n hourlyTimerRef.current = setTimeout(() => {\n refetch();\n scheduleHourlyRefetch();\n }, msUntilNextHour);\n });\n\n useEffect(() => {\n scheduleHourlyRefetch();\n }, [entityId, type, scheduleHourlyRefetch]);\n\n useEffect(() => {\n const unsubscribe = store.subscribeToEntity(entityId, debouncedRefetch);\n return () => {\n unsubscribe();\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current);\n }\n if (hourlyTimerRef.current) {\n clearTimeout(hourlyTimerRef.current);\n }\n };\n }, [entityId, store.subscribeToEntity, debouncedRefetch]);\n\n return { forecast, status, error, refetch };\n}\n","// Style registry for Shadow DOM injection\n// Each .styles.ts file uses css`` which auto-registers\n\nconst styleRegistry: string[] = [];\n\n/**\n * CSS tagged template literal for syntax highlighting.\n * Automatically registers the styles with the global registry.\n */\nexport const css = (strings: TemplateStringsArray, ...values: unknown[]): string => {\n const result = strings.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '');\n styleRegistry.push(result);\n return result;\n};\n\n/**\n * Register raw CSS string (e.g., from ?inline imports).\n * Only registers if not already present.\n */\nexport function registerRawStyles(styles: string): void {\n if (!styleRegistry.includes(styles)) {\n styleRegistry.push(styles);\n }\n}\n\n/**\n * Get all registered styles for Shadow DOM injection.\n */\nexport function getAllStyles(): string {\n return styleRegistry.join('\\n');\n}\n","import { type ComponentType, render } from 'preact';\nimport { HAProvider } from './HAContext';\nimport { getAllStyles } from './styleRegistry';\nimport type { HomeAssistant } from './types';\n\ninterface RegisterPreactCardOptions<TConfig> {\n type: string;\n name: string;\n description: string;\n Component: ComponentType<{ config: TConfig }>;\n ConfigComponent?: ComponentType<{\n hass: HomeAssistant;\n config: TConfig;\n onConfigChanged: (config: TConfig) => void;\n }>;\n UnconfiguredComponent?: ComponentType<{}>;\n getStubConfig?: () => Partial<TConfig>;\n}\n\ndeclare global {\n interface Window {\n customCards?: Array<{ type: string; name: string; description: string }>;\n }\n}\n\nexport function registerPreactCard<TConfig>(options: RegisterPreactCardOptions<TConfig>) {\n const {\n type,\n name,\n description,\n Component,\n ConfigComponent,\n UnconfiguredComponent,\n getStubConfig,\n } = options;\n\n class HACard extends HTMLElement {\n private _hass?: HomeAssistant;\n private _config?: TConfig;\n private _shadowRoot: ShadowRoot;\n private _entityChangeListeners = new Map<string, Set<(entity: any) => void>>();\n\n constructor() {\n super();\n this._shadowRoot = this.attachShadow({ mode: 'open' });\n }\n\n connectedCallback() {\n if (this._hass && this._config) {\n this._render();\n }\n }\n\n set hass(hass: HomeAssistant) {\n const prevStates = this._hass?.states;\n this._hass = hass;\n\n for (const [entityId, listeners] of this._entityChangeListeners) {\n const newState = hass.states[entityId];\n const oldState = prevStates?.[entityId];\n if (newState !== oldState) {\n listeners.forEach((listener) => listener(newState));\n }\n }\n\n if (!prevStates && this._config && this.isConnected) {\n this._render();\n }\n }\n\n setConfig(config: TConfig) {\n this._config = config;\n if (this._hass && this.isConnected) {\n this._render();\n }\n }\n\n private _subscribeToEntity = (entityId: string, callback: (entity: any) => void) => {\n if (!this._entityChangeListeners.has(entityId)) {\n this._entityChangeListeners.set(entityId, new Set());\n }\n this._entityChangeListeners.get(entityId)!.add(callback);\n\n return () => {\n const listeners = this._entityChangeListeners.get(entityId);\n if (listeners) {\n listeners.delete(callback);\n if (listeners.size === 0) {\n this._entityChangeListeners.delete(entityId);\n }\n }\n };\n };\n\n private _render() {\n if (!this._config || !this._hass) {\n if (UnconfiguredComponent) {\n render(<UnconfiguredComponent />, this._shadowRoot);\n }\n return;\n }\n\n render(\n <HAProvider hass={this._hass} subscribeToEntity={this._subscribeToEntity}>\n <style>{getAllStyles()}</style>\n <Component config={this._config} />\n </HAProvider>,\n this._shadowRoot,\n );\n }\n\n static getConfigElement() {\n if (ConfigComponent) {\n return document.createElement(`${type}-editor`);\n }\n return undefined;\n }\n\n static getStubConfig() {\n return getStubConfig?.() ?? {};\n }\n }\n\n customElements.define(type, HACard);\n\n if (ConfigComponent) {\n const EditorComponent = ConfigComponent;\n\n class HACardEditor extends HTMLElement {\n private _hass?: HomeAssistant;\n private _config?: TConfig;\n\n set hass(hass: HomeAssistant) {\n this._hass = hass;\n this._render();\n }\n\n setConfig(config: TConfig) {\n this._config = config;\n this._render();\n }\n\n private _fireConfigChanged = (config: TConfig) => {\n this.dispatchEvent(\n new CustomEvent('config-changed', {\n detail: { config },\n bubbles: true,\n composed: true,\n }),\n );\n };\n\n private _render() {\n if (!this._hass || !this._config) return;\n\n // Render to light DOM so HA's custom elements (ha-select etc.) work.\n render(\n <EditorComponent\n hass={this._hass}\n config={this._config}\n onConfigChanged={this._fireConfigChanged}\n />,\n this,\n );\n }\n }\n\n customElements.define(`${type}-editor`, HACardEditor);\n }\n\n window.customCards = window.customCards || [];\n window.customCards.push({ type, name, description });\n\n console.info(\n `%c ${name.toUpperCase()} %c loaded `,\n 'background: #3b82f6; color: white; font-weight: bold',\n '',\n );\n}\n","import type { RefObject } from 'preact';\nimport { useEffect, useRef } from 'preact/hooks';\n\nexport interface ElementSize {\n width: number;\n height: number;\n}\n\nexport type ResizeCallback = (size: ElementSize) => void;\n\n/**\n * Observe an element's size via ResizeObserver. The callback fires:\n *\n * 1. Once after mount, with the element's current size.\n * 2. Whenever the element's size changes.\n * 3. Whenever `deps` change, re-firing with the current size — so callers\n * can re-run draws when their inputs change without re-creating the\n * observer.\n *\n * The callback is suppressed only while the element is detached from the\n * document. Zero width/height is delivered to the callback as-is — consumers\n * that need to skip degenerate sizes (e.g. canvas painters where a 0-sized\n * drawImage throws InvalidStateError) should add their own early return.\n *\n * Sizes are read from `offsetWidth` / `offsetHeight` (CSS pixels, includes\n * padding + border). The callback is held in a ref, so passing a fresh\n * function each render is safe — it never re-creates the observer.\n *\n * @example\n * const containerRef = useRef<HTMLDivElement>(null);\n * const canvasRef = useRef<HTMLCanvasElement>(null);\n *\n * useResizeObserver(\n * containerRef,\n * ({ width, height }) => {\n * if (width === 0 || height === 0) return; // optional, consumer's call\n * drawChart(canvasRef.current, forecast, width, height);\n * },\n * [forecast],\n * );\n */\nexport function useResizeObserver<T extends HTMLElement>(\n ref: RefObject<T>,\n callback: ResizeCallback,\n deps: unknown[] = [],\n): void {\n const callbackRef = useRef(callback);\n callbackRef.current = callback;\n\n // Set up the observer once per element. ResizeObserver fires once\n // synchronously-ish after `.observe()` with the current size, which\n // covers the initial draw.\n useEffect(() => {\n const element = ref.current;\n if (!element) return;\n\n const fire = () => {\n if (!element.isConnected) return;\n callbackRef.current({\n width: element.offsetWidth,\n height: element.offsetHeight,\n });\n };\n\n const observer = new ResizeObserver(fire);\n observer.observe(element);\n return () => observer.disconnect();\n // ref identity is stable across renders; observer setup runs once.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Re-fire on dependency change. Skip the first render — the observer's\n // initial `.observe()` fire already delivers the mount-time size.\n const isFirstRun = useRef(true);\n useEffect(() => {\n if (isFirstRun.current) {\n isFirstRun.current = false;\n return;\n }\n const element = ref.current;\n if (!element || !element.isConnected) return;\n callbackRef.current({\n width: element.offsetWidth,\n height: element.offsetHeight,\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps);\n}\n","import type { RefObject } from 'preact';\nimport { useState } from 'preact/hooks';\nimport { useResizeObserver } from './useResizeObserver';\n\n/**\n * Track the current width of a referenced element in CSS pixels.\n *\n * Returns `undefined` until the first non-zero measurement is observed, then\n * a positive number that updates as the element resizes. Once a real width\n * is captured the hook will never report `undefined` or `0` again, even\n * during HA layout transitions (dashboard switch, edit-mode toggle) — the\n * underlying ResizeObserver firings are silently dropped while the element\n * is detached or transiently zero-width, so the component renders with the\n * last good value instead of flashing through a degenerate state.\n *\n * Use this when you need a width value in JSX (responsive layout, prop to a\n * sized child). If you only need the value imperatively inside a draw\n * callback, prefer `useResizeObserver` directly — it doesn't allocate\n * component state or cause re-renders.\n *\n * @example\n * const ref = useRef<HTMLDivElement>(null);\n * const width = useWidth(ref);\n * return (\n * <div ref={ref}>\n * {width !== undefined && <Chart width={width} />}\n * </div>\n * );\n */\nexport function useWidth<T extends HTMLElement>(ref: RefObject<T>): number | undefined {\n const [width, setWidth] = useState<number | undefined>(undefined);\n useResizeObserver(ref, (size) => {\n if (size.width === 0) return;\n setWidth((prev) => (prev === size.width ? prev : size.width));\n });\n return width;\n}\n"],"names":[],"mappings":"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"}
|