UNPKG

@hakit/core

Version:

A collection of React hooks and helpers for Home Assistant to easily communicate with the Home Assistant WebSocket API.

1 lines 13.9 kB
{"version":3,"file":"index-CUxh5LWn.cjs","sources":["../src/hooks/useHass/index.ts","../src/hooks/useLocale/index.ts","../src/hooks/useConfig/index.ts","../src/HassConnect/FetchLocale/index.tsx","../src/HassConnect/index.tsx"],"sourcesContent":["import { useContext } from \"react\";\nimport { isEmpty } from \"lodash\";\nimport { HassContext } from \"@core\";\nimport type { HassContextProps } from \"@core\";\n\nexport function useHass(): HassContextProps {\n const context = useContext(HassContext);\n if (context === undefined || isEmpty(context)) {\n throw new Error(\"useHass must be used within a HassProvider, have you wrapped your application in <HassConnect hassUrl={HASS_URL} />?\");\n }\n return context;\n}\n","import { useState, useEffect } from \"react\";\nimport { LocaleKeys } from \"./locales/types\";\nimport locales from \"./locales\";\nimport { useHass } from \"../useHass\";\n\nconst LOCALES: Record<string, string> = {};\n\nexport function updateLocales(translations: Record<string, string>): void {\n Object.assign(LOCALES, translations);\n}\n\ninterface Options {\n /** if the string isn't found as some languages might not have specific values translated, it will use this value. */\n fallback?: string;\n /** value to search & replace */\n search?: string;\n /** value to search & replace */\n replace?: string;\n}\n\nexport function localize(key: LocaleKeys, options?: Options): string {\n const { search, replace, fallback } = options ?? {};\n if (!LOCALES[key]) {\n if (fallback) {\n return fallback;\n }\n // as a generic fallback, we just return the keyname\n return key;\n }\n if (typeof search === \"string\" && typeof replace === \"string\") {\n return LOCALES[key].replace(`${search}`, replace).trim();\n }\n return LOCALES[key];\n}\n\nexport function useLocales(): Record<LocaleKeys, string> {\n return LOCALES;\n}\n\nexport const useLocale = (key: LocaleKeys, options?: Options) => {\n const { fallback = localize(\"unknown\") } = options ?? {};\n const [value, setValue] = useState<string>(fallback);\n const { getConfig } = useHass();\n\n useEffect(() => {\n const fetchAndSetLocale = async () => {\n const locale = (await getConfig())?.language;\n const localeData = locales.find((l) => l.code === locale);\n if (localeData) {\n const data = await localeData.fetch();\n setValue(data[key] ?? fallback);\n }\n };\n\n fetchAndSetLocale();\n }, [key, fallback, getConfig]);\n\n return value;\n};\n","import { useEffect, useState, useMemo } from \"react\";\nimport { subscribeConfig, type HassConfig } from \"home-assistant-js-websocket\";\nimport { useHass } from \"@core\";\n\nexport function useConfig() {\n const { useStore } = useHass();\n const connection = useStore((state) => state.connection);\n const [config, setConfig] = useState<HassConfig | null>(null);\n\n useEffect(() => {\n if (!connection) return;\n // Subscribe to config updates\n const unsubscribe = subscribeConfig(connection, (newConfig) => {\n setConfig(newConfig);\n });\n // Cleanup function to unsubscribe on unmount\n return () => {\n unsubscribe();\n };\n }, [connection]); // Only re-run if connection changes\n\n // Memoize the config to prevent unnecessary re-renders\n return useMemo(() => config, [config]);\n}\n","import { useConfig } from \"@hooks\";\nimport { Locales } from \"@typings\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { updateLocales } from \"../../hooks/useLocale\";\nimport locales from \"../../hooks/useLocale/locales\";\nimport { useStore } from \"../HassContext\";\n\ninterface FetchLocaleProps {\n locale?: Locales;\n children?: React.ReactNode;\n}\nexport function FetchLocale({ locale, children }: FetchLocaleProps) {\n const config = useConfig();\n const [fetched, setFetched] = useState(false);\n const fetchPending = useRef(false);\n const previousLocale = useRef<Locales | null>(null);\n const setError = useStore((store) => store.setError);\n const setLocales = useStore((store) => store.setLocales);\n\n useEffect(() => {\n const _locale = locale ?? config?.language;\n if (!_locale) {\n // may just be waiting for the users config to resolve\n return;\n }\n const match = locales.find(({ code }) => code === (locale ?? config?.language));\n if (previousLocale.current !== match?.code) {\n setFetched(false);\n fetchPending.current = false;\n setError(null);\n }\n\n if (!match) {\n fetchPending.current = false;\n setError(\n `Locale \"${locale ?? config?.language}\" not found, available options are \"${locales.map(({ code }) => `${code}`).join(\", \")}\"`,\n );\n } else {\n if (fetchPending.current) return;\n fetchPending.current = true;\n previousLocale.current = match.code;\n match\n .fetch()\n .then((response) => {\n fetchPending.current = false;\n setFetched(true);\n updateLocales(response);\n setLocales(response);\n })\n .catch((e) => {\n fetchPending.current = false;\n setFetched(true);\n setError(`Error retrieving translations from Home Assistant: ${e?.message ?? e}`);\n });\n }\n }, [config, fetched, setLocales, setError, locale]);\n\n return fetched ? children : null;\n}\n","import { memo, useMemo, type ReactNode } from \"react\";\nimport { useRef } from \"react\";\nimport { HassProvider } from \"./Provider\";\nimport type { HassProviderProps } from \"./Provider\";\nimport styled from \"@emotion/styled\";\nimport { keyframes } from \"@emotion/react\";\nimport { FetchLocale } from \"./FetchLocale\";\n\nexport type HassConnectProps = {\n /** Any react node to render when authenticated */\n children: ReactNode;\n /** The url to your home assistant instance, can be local, nabucasa or any hosted url with home-assistant. */\n hassUrl: string;\n /** if you provide a hassToken you will bypass the login screen altogether - @see https://developers.home-assistant.io/docs/auth_api/#long-lived-access-token */\n hassToken?: string;\n /** Any react node to render when not authenticated or loading */\n loading?: ReactNode;\n /** called once the entity subscription is successful, and only once */\n onReady?: () => void;\n /** options for the provider */\n options?: Omit<HassProviderProps, \"children\" | \"hassUrl\">;\n};\n\nconst blip = keyframes`\n 0% {stroke-width:0; opacity:0;}\n 50% {stroke-width:5; opacity:1;}\n 100% {stroke-width:0; opacity:0;}\n`;\n\nfunction LoaderBase({ className }: { className?: string }) {\n return (\n <div className={className}>\n <svg>\n <path d=\"m 12.5,20 15,0 0,0 -15,0 z\" />\n <path d=\"m 32.5,20 15,0 0,0 -15,0 z\" />\n <path d=\"m 52.5,20 15,0 0,0 -15,0 z\" />\n <path d=\"m 72.5,20 15,0 0,0 -15,0 z\" />\n </svg>\n </div>\n );\n}\n\nconst Loader = styled(LoaderBase)`\n position: fixed;\n inset: 0;\n background-color: #1a1a1a;\n svg {\n position: absolute;\n top: 50%;\n left: 50%;\n width: 6.25em;\n height: 3.125em;\n margin: -1.562em 0 0 -3.125em;\n path {\n fill: none;\n stroke: #f0c039;\n opacity: 0;\n }\n path:nth-of-type(1) {\n animation: ${blip} 1s ease-in-out 0s infinite alternate;\n }\n path:nth-of-type(2) {\n animation: ${blip} 1s ease-in-out 0.1s infinite alternate;\n }\n path:nth-of-type(3) {\n animation: ${blip} 1s ease-in-out 0.2s infinite alternate;\n }\n path:nth-of-type(4) {\n animation: ${blip} 1s ease-in-out 0.3s infinite alternate;\n }\n }\n`;\n\nconst Wrapper = styled.div`\n width: 100%;\n height: 100%;\n`;\n\n/** This component will show the Home Assistant login form you're used to seeing normally when logging into HA, once logged in you shouldn't see this again unless you clear device storage, once authenticated it will render the child components of HassConnect and provide access to the api. */\nexport const HassConnect = memo(function HassConnect({\n children,\n hassUrl,\n hassToken,\n loading = <Loader />,\n onReady,\n options = {},\n}: HassConnectProps): ReactNode {\n const onReadyCalled = useRef(false);\n\n const sanitizedUrl = useMemo(() => {\n try {\n // htftp://lofcalhost:1234/ => origin of \"null\" so we need to account for malformed urls\n // @see https://github.com/shannonhochkins/ha-component-kit/issues/146#issuecomment-2138352567\n return new URL(hassUrl).origin;\n } catch (e) {\n console.log(\"Error:\", e);\n return null;\n }\n }, [hassUrl]);\n\n if (!sanitizedUrl || sanitizedUrl === \"null\" || sanitizedUrl === null) {\n return <>{`Provide the hassUrl prop with a valid url to your home assistant instance.`}</>;\n }\n\n return (\n <HassProvider hassUrl={sanitizedUrl} hassToken={hassToken} {...options}>\n {(ready) => (\n <>\n {ready ? (\n <Wrapper>\n <FetchLocale locale={options.locale}>\n {onReady &&\n !onReadyCalled.current &&\n ((() => {\n onReady();\n onReadyCalled.current = true;\n })(),\n null)}\n {children}\n </FetchLocale>\n </Wrapper>\n ) : (\n <Wrapper>{loading}</Wrapper>\n )}\n </>\n )}\n </HassProvider>\n );\n});\n"],"names":["useHass","context","useContext","HassContext","isEmpty","LOCALES","updateLocales","translations","localize","key","options","search","replace","fallback","useLocales","useLocale","value","setValue","useState","getConfig","useEffect","locale","localeData","locales","l","data","useConfig","useStore","connection","state","config","setConfig","unsubscribe","subscribeConfig","newConfig","useMemo","FetchLocale","children","fetched","setFetched","fetchPending","useRef","previousLocale","setError","store","setLocales","match","code","response","e","blip","keyframes","LoaderBase","className","jsx","jsxs","Loader","styled","Wrapper","HassConnect","memo","hassUrl","hassToken","loading","onReady","onReadyCalled","sanitizedUrl","HassProvider","ready","Fragment"],"mappings":"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"}