@glasshome/widget-sdk 1.9.2 → 1.10.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/data.d.ts CHANGED
@@ -6,6 +6,6 @@
6
6
  * store. Phase 4 routes these through capability checks.
7
7
  */
8
8
  export { getForecasts, getStream, getWebRtcClientConfig, hassMediaUrl, sendWebRtcCandidate, startWebRtcSession, state, trackEntityHistory, untrackEntityHistory, } from "@glasshome/sync-layer";
9
- export type { AreaView } from "@glasshome/sync-layer";
10
- export { byDomain, useArea, useCamera, useConnection, useCurrency, useEntities, useEntity, useEntityHistory, useEntityStatistics, useForecast, useHassConfig, useLocale, useStore, useTemperatureUnit, useUnitSystem, } from "@glasshome/sync-layer/solid";
9
+ export type { AreaView, CalendarEvent, CalendarEventsData, CalendarWindowOptions, } from "@glasshome/sync-layer";
10
+ export { byDomain, useArea, useCalendarEvents, useCamera, useConnection, useCurrency, useEntities, useEntity, useEntityHistory, useEntityStatistics, useForecast, useHassConfig, useLocale, useStore, useTemperatureUnit, useUnitSystem, } from "@glasshome/sync-layer/solid";
11
11
  export { useService, useToggle, useTurnOff, useTurnOn } from "./framework/hooks/use-service";
@@ -1,4 +1,5 @@
1
- import { type Component, type JSX } from "solid-js";
1
+ import type { Button as UIButton, ResponsiveDialog as UIResponsiveDialog, ResponsiveDialogContent as UIResponsiveDialogContent, ResponsiveDialogDescription as UIResponsiveDialogDescription, ResponsiveDialogHeader as UIResponsiveDialogHeader, ResponsiveDialogTitle as UIResponsiveDialogTitle, SchemaForm as UISchemaForm } from "@glasshome/ui/solid";
2
+ import { type JSX } from "solid-js";
2
3
  import type { ZodType } from "zod";
3
4
  export interface WidgetDialogTab {
4
5
  id: string;
@@ -28,12 +29,12 @@ export interface WidgetDialogProps {
28
29
  configSchema?: ZodType;
29
30
  config?: Record<string, unknown>;
30
31
  onConfigSave?: (config: Record<string, unknown>) => void;
31
- ResponsiveDialog: Component<any>;
32
- ResponsiveDialogContent: Component<any>;
33
- ResponsiveDialogHeader: Component<any>;
34
- ResponsiveDialogTitle: Component<any>;
35
- ResponsiveDialogDescription: Component<any>;
36
- Button: Component<any>;
37
- SchemaForm?: Component<any>;
32
+ ResponsiveDialog: typeof UIResponsiveDialog;
33
+ ResponsiveDialogContent: typeof UIResponsiveDialogContent;
34
+ ResponsiveDialogHeader: typeof UIResponsiveDialogHeader;
35
+ ResponsiveDialogTitle: typeof UIResponsiveDialogTitle;
36
+ ResponsiveDialogDescription: typeof UIResponsiveDialogDescription;
37
+ Button: typeof UIButton;
38
+ SchemaForm?: typeof UISchemaForm;
38
39
  }
39
40
  export declare function WidgetDialog(props: WidgetDialogProps): JSX.Element;
@@ -12,8 +12,13 @@ export interface WidgetDimensions {
12
12
  height: number;
13
13
  }
14
14
  /** Capability-routed service call: an RPC into the host's HA bridge worker,
15
- validated there against the widget's granted capabilities. */
16
- export type ServiceCallFn = (domain: string, service: string, serviceData?: Record<string, unknown>, target?: Record<string, unknown>) => Promise<void>;
15
+ validated there against the widget's granted capabilities. Pass
16
+ `{ returnResponse: true }` for services that return data (e.g.
17
+ `todo.get_items`); the resolved value is the HA service response, else
18
+ `undefined`. */
19
+ export type ServiceCallFn = (domain: string, service: string, serviceData?: Record<string, unknown>, target?: Record<string, unknown>, options?: {
20
+ returnResponse?: boolean;
21
+ }) => Promise<unknown>;
17
22
  export interface ReactiveWidgetContext {
18
23
  updateConfig: (config: Record<string, unknown>) => void;
19
24
  /**
package/dist/index.js CHANGED
@@ -2,8 +2,8 @@ import { deprecate as e, deprecations as t, formatDeprecation as n } from "./dep
2
2
  import { WidgetManifestSchema as r, formatSchemaError as i } from "./schemas.js";
3
3
  import { z as a, z as o } from "zod";
4
4
  import { getForecasts as s, getStream as c, getWebRtcClientConfig as l, hassMediaUrl as u, sendWebRtcCandidate as d, startWebRtcSession as f, state as p, trackEntityHistory as m, untrackEntityHistory as h } from "@glasshome/sync-layer";
5
- import { byDomain as g, useArea as _, useCamera as v, useConnection as ee, useCurrency as y, useEntities as b, useEntity as te, useEntityHistory as ne, useEntityStatistics as re, useForecast as x, useHassConfig as ie, useLocale as ae, useService as oe, useStore as se, useTemperatureUnit as S, useToggle as ce, useTurnOff as le, useTurnOn as ue, useUnitSystem as de } from "@glasshome/sync-layer/solid";
6
- import { Show as C, createContext as fe, createEffect as w, createMemo as T, createSignal as E, on as pe, onCleanup as D, onMount as me, splitProps as he, untrack as ge, useContext as O } from "solid-js";
5
+ import { byDomain as g, useArea as _, useCalendarEvents as v, useCamera as ee, useConnection as y, useCurrency as b, useEntities as te, useEntity as ne, useEntityHistory as re, useEntityStatistics as x, useForecast as ie, useHassConfig as ae, useLocale as oe, useService as S, useStore as se, useTemperatureUnit as ce, useToggle as le, useTurnOff as ue, useTurnOn as de, useUnitSystem as fe } from "@glasshome/sync-layer/solid";
6
+ import { Show as C, createContext as pe, createEffect as w, createMemo as T, createSignal as E, on as me, onCleanup as D, onMount as he, splitProps as ge, useContext as O } from "solid-js";
7
7
  import { addEventListener as k, className as A, createComponent as j, delegateEvents as _e, effect as M, insert as N, memo as P, setStyleProperty as F, style as ve, template as I, use as ye } from "solid-js/web";
8
8
  import { clsx as be } from "clsx";
9
9
  import { twMerge as xe } from "tailwind-merge";
@@ -32,7 +32,7 @@ function We(e) {
32
32
  }
33
33
  //#endregion
34
34
  //#region src/framework/hooks/use-widget-context.ts
35
- var L = fe();
35
+ var L = pe();
36
36
  function Ge() {
37
37
  let e = O(L);
38
38
  if (!e) throw Error("useWidgetContext must be used within a Widget component");
@@ -46,7 +46,9 @@ function Ke(e) {
46
46
  return n;
47
47
  }
48
48
  function R(e, t) {
49
- return (n, r = {}) => e(Ke(n), t, r, { entity_id: n });
49
+ return async (n, r = {}) => {
50
+ await e(Ke(n), t, r, { entity_id: n });
51
+ };
50
52
  }
51
53
  function qe() {
52
54
  let e = O(L)?.callService;
@@ -55,19 +57,19 @@ function qe() {
55
57
  turnOn: R(e, "turn_on"),
56
58
  turnOff: R(e, "turn_off"),
57
59
  toggle: R(e, "toggle")
58
- } : oe();
60
+ } : S();
59
61
  }
60
62
  function Je() {
61
63
  let e = O(L)?.callService;
62
- return e ? R(e, "turn_on") : ue();
64
+ return e ? R(e, "turn_on") : de();
63
65
  }
64
66
  function Ye() {
65
67
  let e = O(L)?.callService;
66
- return e ? R(e, "turn_off") : le();
68
+ return e ? R(e, "turn_off") : ue();
67
69
  }
68
70
  function Xe() {
69
71
  let e = O(L)?.callService;
70
- return e ? R(e, "toggle") : ce();
72
+ return e ? R(e, "toggle") : le();
71
73
  }
72
74
  //#endregion
73
75
  //#region src/framework/design-system/z-index.ts
@@ -76,7 +78,7 @@ var z = {
76
78
  CONTENT: 10,
77
79
  OVERLAY: 20,
78
80
  ACTIONS: 30
79
- }, Ze = fe();
81
+ }, Ze = pe();
80
82
  function Qe() {
81
83
  let e = O(Ze);
82
84
  if (!e) throw Error("useWidgetDimensions must be called inside <Widget>: move the call into a component rendered within <Widget> (the top-level widget scope only sees the host stub).");
@@ -564,7 +566,7 @@ function Lt(t) {
564
566
  });
565
567
  });
566
568
  t.observe(e), D(() => t.disconnect());
567
- }), me(() => {
569
+ }), he(() => {
568
570
  let e = r()?.getRootNode();
569
571
  rt(typeof ShadowRoot < "u" && e instanceof ShadowRoot || typeof Document < "u" && e instanceof Document ? e : void 0);
570
572
  });
@@ -679,8 +681,8 @@ function Bt(e, t) {
679
681
  }
680
682
  //#endregion
681
683
  //#region src/framework/dialogs/WidgetDialog.tsx
682
- var Vt = /*#__PURE__*/ I("<span class=\"inline-flex size-3.5 shrink-0 items-center\">"), Ht = /*#__PURE__*/ I("<button type=button>"), Ut = /*#__PURE__*/ I("<svg xmlns=http://www.w3.org/2000/svg class=size-3.5 fill=none viewBox=\"0 0 24 24\"stroke=currentColor stroke-width=2><path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"></path><path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\">"), Wt = /*#__PURE__*/ I("<div class=\"rounded-lg bg-muted/30 p-2 text-center md:p-6\"><p class=\"text-muted-foreground text-sm\">No edit options available"), Gt = /*#__PURE__*/ I("<svg xmlns=http://www.w3.org/2000/svg class=size-3.5 fill=none viewBox=\"0 0 24 24\"stroke=currentColor stroke-width=2><line x1=4 y1=21 x2=4 y2=14></line><line x1=4 y1=10 x2=4 y2=3></line><line x1=12 y1=21 x2=12 y2=12></line><line x1=12 y1=8 x2=12 y2=3></line><line x1=20 y1=21 x2=20 y2=16></line><line x1=20 y1=12 x2=20 y2=3></line><line x1=1 y1=14 x2=7 y2=14></line><line x1=9 y1=8 x2=15 y2=8></line><line x1=17 y1=16 x2=23 y2=16>"), Kt = /*#__PURE__*/ I("<div class=\"rounded-lg bg-muted/30 p-2 text-center md:p-6\"><p class=\"text-muted-foreground text-sm\">No additional controls"), qt = /*#__PURE__*/ I("<svg xmlns=http://www.w3.org/2000/svg class=size-3.5 fill=none viewBox=\"0 0 24 24\"stroke=currentColor stroke-width=2><path d=\"m8 2 1.88 1.88\"></path><path d=\"M14.12 3.88 16 2\"></path><path d=\"M9 7.13v-1a3.003 3.003 0 1 1 6 0v1\"></path><path d=\"M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6\"></path><path d=\"M12 20v-9\"></path><path d=\"M6.53 9C4.6 8.8 3 7.1 3 5\"></path><path d=\"M6 13H2\"></path><path d=\"M3 21c0-2.1 1.7-3.9 3.8-4\"></path><path d=\"M20.97 5c0 2.1-1.6 3.8-3.5 4\"></path><path d=\"M22 13h-4\"></path><path d=\"M17.2 17c2.1.1 3.8 1.9 3.8 4\">"), Jt = /*#__PURE__*/ I("<div class=\"rounded-lg bg-muted/30 p-2 text-center md:p-6\"><p class=\"text-muted-foreground text-sm\">No debug information available"), Yt = /*#__PURE__*/ I("<div class=\"min-w-0 shrink-0\">"), Xt = /*#__PURE__*/ I("<div class=\"flex items-center gap-2\"><div class=\"flex h-8 items-center rounded-lg border border-border/50 bg-muted/30 p-0.5\">"), Zt = /*#__PURE__*/ I("<div class=\"gh-scroll min-h-0 flex-1 overflow-y-auto px-6\">"), Qt = /*#__PURE__*/ I("<div class=\"flex shrink-0 items-center justify-end gap-2 border-border/50 border-t px-6 pt-3\">");
683
- function $t(e) {
684
+ var Vt = /*#__PURE__*/ I("<span class=\"inline-flex size-3.5 shrink-0 items-center\">"), Ht = /*#__PURE__*/ I("<button type=button>"), Ut = /*#__PURE__*/ I("<svg xmlns=http://www.w3.org/2000/svg class=size-3.5 fill=none viewBox=\"0 0 24 24\"stroke=currentColor stroke-width=2><path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"></path><path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\">"), Wt = /*#__PURE__*/ I("<div class=\"rounded-lg bg-muted/30 p-2 text-center md:p-6\"><p class=\"text-muted-foreground text-sm\">No edit options available"), Gt = /*#__PURE__*/ I("<svg xmlns=http://www.w3.org/2000/svg class=size-3.5 fill=none viewBox=\"0 0 24 24\"stroke=currentColor stroke-width=2><line x1=4 y1=21 x2=4 y2=14></line><line x1=4 y1=10 x2=4 y2=3></line><line x1=12 y1=21 x2=12 y2=12></line><line x1=12 y1=8 x2=12 y2=3></line><line x1=20 y1=21 x2=20 y2=16></line><line x1=20 y1=12 x2=20 y2=3></line><line x1=1 y1=14 x2=7 y2=14></line><line x1=9 y1=8 x2=15 y2=8></line><line x1=17 y1=16 x2=23 y2=16>"), Kt = /*#__PURE__*/ I("<div class=\"rounded-lg bg-muted/30 p-2 text-center md:p-6\"><p class=\"text-muted-foreground text-sm\">No additional controls"), qt = /*#__PURE__*/ I("<svg xmlns=http://www.w3.org/2000/svg class=size-3.5 fill=none viewBox=\"0 0 24 24\"stroke=currentColor stroke-width=2><path d=\"m8 2 1.88 1.88\"></path><path d=\"M14.12 3.88 16 2\"></path><path d=\"M9 7.13v-1a3.003 3.003 0 1 1 6 0v1\"></path><path d=\"M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6\"></path><path d=\"M12 20v-9\"></path><path d=\"M6.53 9C4.6 8.8 3 7.1 3 5\"></path><path d=\"M6 13H2\"></path><path d=\"M3 21c0-2.1 1.7-3.9 3.8-4\"></path><path d=\"M20.97 5c0 2.1-1.6 3.8-3.5 4\"></path><path d=\"M22 13h-4\"></path><path d=\"M17.2 17c2.1.1 3.8 1.9 3.8 4\">"), Jt = /*#__PURE__*/ I("<div class=\"rounded-lg bg-muted/30 p-2 text-center md:p-6\"><p class=\"text-muted-foreground text-sm\">No debug information available"), Yt = /*#__PURE__*/ I("<div class=\"min-w-0 shrink-0\">"), Xt = /*#__PURE__*/ I("<div class=\"flex items-center gap-2\"><div class=\"flex h-8 items-center rounded-lg border border-border/50 bg-muted/30 p-0.5\">"), Zt = /*#__PURE__*/ I("<div class=\"gh-scroll min-h-0 flex-1 overflow-y-auto px-6\">"), Qt = /*#__PURE__*/ I("<div class=\"flex shrink-0 items-center justify-end gap-2 border-border/50 border-t px-6 pt-3\">"), $t = (e) => He(e);
685
+ function en(e) {
684
686
  return (() => {
685
687
  var t = Ht();
686
688
  return t.$$click = () => e.onClick(), N(t, j(C, {
@@ -694,12 +696,12 @@ function $t(e) {
694
696
  }), null), N(t, () => e.label, null), M(() => A(t, V("flex cursor-pointer items-center justify-center rounded-md px-3 py-1.5 text-xs transition-all duration-300 ease-in-out", e.isActive ? "gap-1.5 bg-foreground/10 font-semibold text-foreground shadow-sm" : "gap-0 text-muted-foreground hover:bg-foreground/5 hover:text-foreground"))), t;
695
697
  })();
696
698
  }
697
- function en(e) {
698
- let [t] = he(e, /* @__PURE__ */ "open.onOpenChange.title.onSave.hasUnsavedChanges.onDelete.editContent.controlsContent.debugContent.debugData.tabs.class.maxWidth.defaultTab.activeTab.onActiveTabChange.headerActions.configSchema.config.onConfigSave.ResponsiveDialog.ResponsiveDialogContent.ResponsiveDialogHeader.ResponsiveDialogTitle.ResponsiveDialogDescription.Button.SchemaForm".split(".")), n = () => !!t.configSchema && !!t.config && !!t.onConfigSave && !!t.SchemaForm, [r, i] = E({}), [a, o] = E(null), [s, c] = E([]);
699
- w(pe(() => t.config, (e) => {
699
+ function tn(e) {
700
+ let [t] = ge(e, /* @__PURE__ */ "open.onOpenChange.title.onSave.hasUnsavedChanges.onDelete.editContent.controlsContent.debugContent.debugData.tabs.class.maxWidth.defaultTab.activeTab.onActiveTabChange.headerActions.configSchema.config.onConfigSave.ResponsiveDialog.ResponsiveDialogContent.ResponsiveDialogHeader.ResponsiveDialogTitle.ResponsiveDialogDescription.Button.SchemaForm".split(".")), n = () => !!t.configSchema && !!t.config && !!t.onConfigSave && !!t.SchemaForm, [r, i] = E({}), [a, o] = E(null), [s, c] = E([]);
701
+ w(me(() => t.config, (e) => {
700
702
  e && i({ ...e });
701
- })), w(pe(() => t.configSchema, (e) => {
702
- e && o(He(e));
703
+ })), w(me(() => t.configSchema, (e) => {
704
+ e && o($t(e));
703
705
  }));
704
706
  let l = () => n() && JSON.stringify(r()) !== JSON.stringify(t.config), u = (e) => {
705
707
  !e && t.config && (i({ ...t.config }), c([])), t.onOpenChange(e);
@@ -731,17 +733,17 @@ function en(e) {
731
733
  label: "Edit",
732
734
  icon: Ut(),
733
735
  content: t.editContent ?? (() => {
734
- let e = n() ? a() : void 0;
735
- return e ? j(ie, {
736
- schema: e,
736
+ let e = t.SchemaForm, o = n() ? a() : void 0;
737
+ return !o || !e ? Wt() : j(e, {
738
+ schema: o,
737
739
  get data() {
738
- return ge(r);
740
+ return r();
739
741
  },
740
742
  onChange: i,
741
743
  get errors() {
742
744
  return s();
743
745
  }
744
- }) : Wt();
746
+ });
745
747
  })()
746
748
  }), e.push({
747
749
  id: "controls",
@@ -760,19 +762,19 @@ function en(e) {
760
762
  await navigator.clipboard.writeText(e);
761
763
  } catch {}
762
764
  }, ee = () => {
763
- let e = ae();
765
+ let e = ie();
764
766
  return e.find((e) => e.id === m())?.content ?? e[0]?.content;
765
- }, y = t.ResponsiveDialog, b = t.ResponsiveDialogContent, te = t.ResponsiveDialogHeader, ne = t.ResponsiveDialogTitle, re = t.ResponsiveDialogDescription, x = t.Button, ie = t.SchemaForm, ae = T(pe([
767
+ }, y = t.ResponsiveDialog, b = t.ResponsiveDialogContent, te = t.ResponsiveDialogHeader, ne = t.ResponsiveDialogTitle, re = t.ResponsiveDialogDescription, x = t.Button, ie = T(me([
766
768
  () => t.open,
767
769
  a,
768
770
  () => t.config,
769
771
  () => t.tabs
770
- ], () => _())), oe = (e) => n() ? u(e) : t.onOpenChange(e), se = () => n() ? l() : t.hasUnsavedChanges, S = () => n() ? d : t.onSave;
772
+ ], () => _())), ae = (e) => n() ? u(e) : t.onOpenChange(e), oe = () => n() ? l() : t.hasUnsavedChanges, S = () => n() ? d : t.onSave;
771
773
  return j(y, {
772
774
  get open() {
773
775
  return t.open;
774
776
  },
775
- onOpenChange: (e) => oe(e),
777
+ onOpenChange: (e) => ae(e),
776
778
  get children() {
777
779
  return j(b, {
778
780
  get class() {
@@ -796,7 +798,7 @@ function en(e) {
796
798
  }), null), e;
797
799
  })(), (() => {
798
800
  var e = Xt(), n = e.firstChild;
799
- return N(n, () => ae().map((e) => j($t, {
801
+ return N(n, () => ie().map((e) => j(en, {
800
802
  get icon() {
801
803
  return e.icon;
802
804
  },
@@ -848,7 +850,7 @@ function en(e) {
848
850
  return j(x, {
849
851
  size: "sm",
850
852
  get disabled() {
851
- return !se();
853
+ return !oe();
852
854
  },
853
855
  onClick: () => S()?.(),
854
856
  children: "Save"
@@ -878,7 +880,7 @@ function en(e) {
878
880
  _e(["click"]);
879
881
  //#endregion
880
882
  //#region src/framework/hooks/use-intersection-pause.ts
881
- function tn(e) {
883
+ function nn(e) {
882
884
  if (typeof IntersectionObserver > "u") return () => !1;
883
885
  let [t, n] = E(!1), r = new IntersectionObserver((e) => {
884
886
  let t = e[0];
@@ -891,14 +893,14 @@ function tn(e) {
891
893
  }
892
894
  //#endregion
893
895
  //#region src/framework/hooks/use-reduced-motion.ts
894
- function nn() {
896
+ function rn() {
895
897
  if (typeof window > "u" || typeof window.matchMedia != "function") return () => !1;
896
898
  let e = window.matchMedia("(prefers-reduced-motion: reduce)"), [t, n] = E(e.matches), r = (e) => n(e.matches);
897
899
  return e.addEventListener("change", r), D(() => e.removeEventListener("change", r)), t;
898
900
  }
899
901
  //#endregion
900
902
  //#region src/framework/hooks/use-widget-dialog.ts
901
- function rn(e = "controls") {
903
+ function an(e = "controls") {
902
904
  let [t, n] = E(!1), [r, i] = E(e), a = (t) => {
903
905
  i(t ?? e), n(!0);
904
906
  }, o = O(L);
@@ -923,7 +925,7 @@ function rn(e = "controls") {
923
925
  }
924
926
  //#endregion
925
927
  //#region src/framework/hooks/use-widget-entity-group.ts
926
- function an(e) {
928
+ function on(e) {
927
929
  let t = e.minEntities ?? 1, n = T(() => e.entities().length), r = T(() => n() >= t), i = T(() => !r() || !e.calculateGroupData ? null : e.calculateGroupData(e.entities())), a = T(() => {
928
930
  if (!r()) return;
929
931
  let t = e.aggregationMode?.();
@@ -944,13 +946,13 @@ function an(e) {
944
946
  //#endregion
945
947
  //#region src/framework/gestures/cursors.ts
946
948
  var U = 32, W = U / 2, G = "white", K = "rgba(0,0,0,0.4)", q = 2, J = 3;
947
- function on(e) {
949
+ function sn(e) {
948
950
  return `url("data:image/svg+xml,${encodeURIComponent(e)}")`;
949
951
  }
950
952
  function Y(e) {
951
953
  return `<svg xmlns="http://www.w3.org/2000/svg" width="${U}" height="${U}" viewBox="0 0 ${U} ${U}">${e}</svg>`;
952
954
  }
953
- var sn = Y(`
955
+ var cn = Y(`
954
956
  <g transform="translate(6, 2)">
955
957
  <!-- shadow -->
956
958
  <g stroke="${K}" stroke-width="${J}" fill="none" stroke-linecap="round" stroke-linejoin="round">
@@ -961,13 +963,13 @@ var sn = Y(`
961
963
  <path d="M10 8V3a2 2 0 0 1 4 0v10l4.5-4.5a2 2 0 0 1 2.83 2.83L14 18.5c-2 2-4 3.5-8 3.5-3 0-5-2-5-5v-6a2 2 0 0 1 4 0v2"/>
962
964
  </g>
963
965
  </g>
964
- `), cn = Y(`
966
+ `), ln = Y(`
965
967
  <g>
966
968
  <circle cx="${W}" cy="${W}" r="8" stroke="${K}" stroke-width="${J}" fill="none"/>
967
969
  <circle cx="${W}" cy="${W}" r="8" stroke="${G}" stroke-width="1.5" fill="none"/>
968
970
  <circle cx="${W}" cy="${W}" r="1.5" fill="${G}"/>
969
971
  </g>
970
- `), ln = Y(`
972
+ `), un = Y(`
971
973
  <g>
972
974
  <!-- shadow -->
973
975
  <g stroke="${K}" stroke-width="${J}" fill="none" stroke-linecap="round" stroke-linejoin="round">
@@ -986,7 +988,7 @@ var sn = Y(`
986
988
  <circle cx="11" cy="${W}" r="1" fill="${G}" opacity="0.6"/>
987
989
  <circle cx="21" cy="${W}" r="1" fill="${G}" opacity="0.6"/>
988
990
  </g>
989
- `), un = Y(`
991
+ `), dn = Y(`
990
992
  <g>
991
993
  <!-- shadow -->
992
994
  <g stroke="${K}" stroke-width="${J}" fill="none" stroke-linecap="round" stroke-linejoin="round">
@@ -1005,7 +1007,7 @@ var sn = Y(`
1005
1007
  <circle cx="${W}" cy="11" r="1" fill="${G}" opacity="0.6"/>
1006
1008
  <circle cx="${W}" cy="21" r="1" fill="${G}" opacity="0.6"/>
1007
1009
  </g>
1008
- `), dn = Y(`
1010
+ `), fn = Y(`
1009
1011
  <g>
1010
1012
  <!-- shadow -->
1011
1013
  <g stroke="${K}" stroke-width="${J}" fill="none" stroke-linecap="round" stroke-linejoin="round">
@@ -1031,54 +1033,54 @@ var sn = Y(`
1031
1033
  `);
1032
1034
  function X(e, t, n) {
1033
1035
  return {
1034
- css: `${on(e)} ${t} ${n}, auto`,
1036
+ css: `${sn(e)} ${t} ${n}, auto`,
1035
1037
  hotspotX: t,
1036
1038
  hotspotY: n
1037
1039
  };
1038
1040
  }
1039
1041
  var Z = {
1040
- tap: X(sn, 12, 4),
1041
- hold: X(cn, 16, 16),
1042
- slideHorizontal: X(ln, W, W),
1043
- slideVertical: X(un, W, W),
1044
- slideAuto: X(dn, W, W)
1042
+ tap: X(cn, 12, 4),
1043
+ hold: X(ln, 16, 16),
1044
+ slideHorizontal: X(un, W, W),
1045
+ slideVertical: X(dn, W, W),
1046
+ slideAuto: X(fn, W, W)
1045
1047
  };
1046
1048
  //#endregion
1047
1049
  //#region src/framework/gestures/haptics.ts
1048
- function fn() {
1050
+ function pn() {
1049
1051
  if (typeof window > "u") return null;
1050
1052
  let e = window;
1051
1053
  return e.Capacitor?.isNativePlatform?.() ? e.Capacitor.Plugins?.Haptics ?? null : null;
1052
1054
  }
1053
- function pn(e) {
1055
+ function mn(e) {
1054
1056
  if (!(typeof navigator > "u" || typeof navigator.vibrate != "function")) try {
1055
1057
  navigator.vibrate(e);
1056
1058
  } catch {}
1057
1059
  }
1058
- var mn = {
1060
+ var hn = {
1059
1061
  tick() {
1060
- let e = fn();
1062
+ let e = pn();
1061
1063
  if (e) {
1062
1064
  e.impact({ style: "Light" }).catch(() => {});
1063
1065
  return;
1064
1066
  }
1065
- pn(8);
1067
+ mn(8);
1066
1068
  },
1067
1069
  bump() {
1068
- let e = fn();
1070
+ let e = pn();
1069
1071
  if (e) {
1070
1072
  e.impact({ style: "Medium" }).catch(() => {});
1071
1073
  return;
1072
1074
  }
1073
- pn(20);
1075
+ mn(20);
1074
1076
  },
1075
1077
  pulse() {
1076
- let e = fn();
1078
+ let e = pn();
1077
1079
  if (e) {
1078
1080
  e.impact({ style: "Heavy" }).catch(() => {});
1079
1081
  return;
1080
1082
  }
1081
- pn([
1083
+ mn([
1082
1084
  0,
1083
1085
  15,
1084
1086
  40,
@@ -1088,7 +1090,7 @@ var mn = {
1088
1090
  };
1089
1091
  //#endregion
1090
1092
  //#region src/framework/gestures/use-widget-gestures.ts
1091
- function hn(e, t) {
1093
+ function gn(e, t) {
1092
1094
  let n = {
1093
1095
  isDown: !1,
1094
1096
  isTouch: !1,
@@ -1131,7 +1133,7 @@ function hn(e, t) {
1131
1133
  if (n.isDown = !0, n.isTouch = t.pointerType === "touch", n.element = t.currentTarget, c(n.element), n.startX = t.clientX, n.startY = t.clientY, n.startTime = Date.now(), n.hasMoved = !1, n.sliding = !1, r.hold) {
1132
1134
  let e = r.hold.delay ?? 300;
1133
1135
  n.holdTimer = setTimeout(() => {
1134
- n.holdTimer = null, !(!n.isDown || n.hasMoved) && (mn.bump(), r.hold?.action(), n.isDown = !1);
1136
+ n.holdTimer = null, !(!n.isDown || n.hasMoved) && (hn.bump(), r.hold?.action(), n.isDown = !1);
1135
1137
  }, e);
1136
1138
  }
1137
1139
  if (!n.isTouch && r.slide) try {
@@ -1199,7 +1201,7 @@ function hn(e, t) {
1199
1201
  }
1200
1202
  //#endregion
1201
1203
  //#region src/framework/theming/svg-colors.ts
1202
- var gn = {
1204
+ var _n = {
1203
1205
  solar: "oklch(0.74 0.18 60)",
1204
1206
  grid: "oklch(0.62 0.12 245)",
1205
1207
  battery: "oklch(0.70 0.18 145)",
@@ -1208,21 +1210,21 @@ var gn = {
1208
1210
  positive: "oklch(0.70 0.14 160)",
1209
1211
  negative: "oklch(0.64 0.22 27)"
1210
1212
  };
1211
- function _n(e) {
1213
+ function vn(e) {
1212
1214
  return {
1213
1215
  fill: `color-mix(in oklch, ${e} 20%, transparent)`,
1214
1216
  stroke: e,
1215
1217
  solid: e
1216
1218
  };
1217
1219
  }
1218
- var vn = Object.fromEntries(Object.entries(gn).map(([e, t]) => [e, _n(t)])), yn = o.enum([
1220
+ var yn = Object.fromEntries(Object.entries(_n).map(([e, t]) => [e, vn(t)])), bn = o.enum([
1219
1221
  "success",
1220
1222
  "warning",
1221
1223
  "danger",
1222
1224
  "info",
1223
1225
  "neutral",
1224
1226
  "accent"
1225
- ]), bn = {
1227
+ ]), xn = {
1226
1228
  title: e(() => o.string().optional().meta({
1227
1229
  title: "Title",
1228
1230
  description: "Optional display name override"
@@ -1242,45 +1244,45 @@ var vn = Object.fromEntries(Object.entries(gn).map(([e, t]) => [e, _n(t)])), yn
1242
1244
  formType: "area-picker",
1243
1245
  title: "Area"
1244
1246
  }), "widgetFields.areaId")
1245
- }, xn = 24;
1247
+ }, Sn = 24;
1246
1248
  function Q(e) {
1247
1249
  let t = {};
1248
1250
  for (let [n, r] of Object.entries(e)) r !== void 0 && (t[n] = r);
1249
1251
  return t;
1250
1252
  }
1251
- function Sn() {
1253
+ function Cn() {
1252
1254
  return o.string().optional().meta({
1253
1255
  title: "Title",
1254
1256
  description: "Optional display name override"
1255
1257
  });
1256
1258
  }
1257
- function Cn(e) {
1259
+ function wn(e) {
1258
1260
  return (e.default === void 0 ? o.string().optional() : o.string().default(e.default)).meta(Q({
1259
1261
  title: e.title,
1260
1262
  description: e.description
1261
1263
  }));
1262
1264
  }
1263
- function wn(e) {
1265
+ function Tn(e) {
1264
1266
  let t = o.number();
1265
1267
  return e.min !== void 0 && (t = t.min(e.min)), e.max !== void 0 && (t = t.max(e.max)), (e.default === void 0 ? t.optional() : t.default(e.default)).meta(Q({
1266
1268
  title: e.title,
1267
1269
  description: e.description
1268
1270
  }));
1269
1271
  }
1270
- function Tn(e) {
1272
+ function En(e) {
1271
1273
  return o.boolean().default(e.default ?? !1).meta(Q({
1272
1274
  title: e.title,
1273
1275
  description: e.description
1274
1276
  }));
1275
1277
  }
1276
- function En(e, t) {
1278
+ function Dn(e, t) {
1277
1279
  let n = o.enum(e);
1278
1280
  return (t.default === void 0 ? n.optional() : n.default(t.default)).meta(Q({
1279
1281
  title: t.title,
1280
1282
  description: t.description
1281
1283
  }));
1282
1284
  }
1283
- function Dn(e, t) {
1285
+ function On(e, t) {
1284
1286
  return o.array(o.string()).default([]).meta(Q({
1285
1287
  domain: e,
1286
1288
  title: t?.title ?? "Entities",
@@ -1288,7 +1290,7 @@ function Dn(e, t) {
1288
1290
  deviceClass: t?.deviceClass
1289
1291
  }));
1290
1292
  }
1291
- function On(e, t) {
1293
+ function kn(e, t) {
1292
1294
  return o.array(o.string()).default([]).meta(Q({
1293
1295
  domain: e,
1294
1296
  title: t?.title ?? "Entity",
@@ -1297,49 +1299,49 @@ function On(e, t) {
1297
1299
  singleSelect: !0
1298
1300
  }));
1299
1301
  }
1300
- function kn(e) {
1302
+ function An(e) {
1301
1303
  return o.string().optional().meta({
1302
1304
  formType: "area-picker",
1303
1305
  title: e?.title ?? "Area"
1304
1306
  });
1305
1307
  }
1306
- function An(e) {
1308
+ function jn(e) {
1307
1309
  let t = o.string().meta({
1308
1310
  formType: "icon-picker",
1309
1311
  title: e?.title ?? "Icon"
1310
1312
  });
1311
1313
  return e?.default === void 0 ? t.optional() : t.default(e.default);
1312
1314
  }
1313
- function jn(e) {
1315
+ function Mn(e) {
1314
1316
  return o.array(o.string()).default([]).meta(Q({
1315
1317
  title: e.title,
1316
1318
  description: e.description
1317
1319
  }));
1318
1320
  }
1319
- function Mn(e, t) {
1321
+ function Nn(e, t) {
1320
1322
  let n = o.object(e);
1321
1323
  return n.default(Ue(n)).meta({ title: t.title });
1322
1324
  }
1323
- function Nn(e) {
1325
+ function Pn(e) {
1324
1326
  return e.def;
1325
1327
  }
1326
1328
  function $(e) {
1327
1329
  if (o.globalRegistry.get(e)?.formType === "list") return !0;
1328
- let t = Nn(e);
1330
+ let t = Pn(e);
1329
1331
  return t.shape ? Object.values(t.shape).some($) : t.options ? t.options.some($) : t.element ? $(t.element) : t.innerType ? $(t.innerType) : !1;
1330
1332
  }
1331
- function Pn(e) {
1332
- let t = e, n = Nn(t).innerType;
1333
- for (; n;) t = n, n = Nn(t).innerType;
1333
+ function Fn(e) {
1334
+ let t = e, n = Pn(t).innerType;
1335
+ for (; n;) t = n, n = Pn(t).innerType;
1334
1336
  return t;
1335
1337
  }
1336
- function Fn(e) {
1337
- let t = Nn(Pn(e));
1338
+ function In(e) {
1339
+ let t = Pn(Fn(e));
1338
1340
  if (t.shape) return new Set(Object.keys(t.shape));
1339
1341
  if (t.options) {
1340
1342
  let e = [];
1341
1343
  for (let n of t.options) {
1342
- let t = Fn(n);
1344
+ let t = In(n);
1343
1345
  if (!t) return null;
1344
1346
  e.push(t);
1345
1347
  }
@@ -1351,7 +1353,7 @@ function Fn(e) {
1351
1353
  }
1352
1354
  return null;
1353
1355
  }
1354
- function In(e, t, n) {
1356
+ function Ln(e, t, n) {
1355
1357
  let r = { [t]: n };
1356
1358
  try {
1357
1359
  return e.parse(r);
@@ -1359,12 +1361,12 @@ function In(e, t, n) {
1359
1361
  return r;
1360
1362
  }
1361
1363
  }
1362
- function Ln(e, t) {
1364
+ function Rn(e, t) {
1363
1365
  if (!Number.isInteger(t.max) || t.max < 1) throw Error(`field.list: max must be a positive integer, got ${t.max}`);
1364
- if (t.max > xn) throw Error(`field.list: max ${t.max} exceeds the ceiling of ${xn}`);
1366
+ if (t.max > Sn) throw Error(`field.list: max ${t.max} exceeds the ceiling of ${Sn}`);
1365
1367
  if (t.min !== void 0 && (!Number.isInteger(t.min) || t.min < 0 || t.min > t.max)) throw Error(`field.list: min must be an integer between 0 and max, got ${t.min}`);
1366
1368
  if ($(e)) throw Error("field.list: a list item cannot contain another field.list (depth 1 only)");
1367
- if (t.labelField !== void 0 && !Fn(e)?.has(t.labelField)) throw Error(`field.list: labelField "${t.labelField}" must name a field present in every item`);
1369
+ if (t.labelField !== void 0 && !In(e)?.has(t.labelField)) throw Error(`field.list: labelField "${t.labelField}" must name a field present in every item`);
1368
1370
  let n = o.array(e);
1369
1371
  return t.min !== void 0 && (n = n.min(t.min)), n = n.max(t.max), n.default([]).meta(Q({
1370
1372
  formType: "list",
@@ -1374,7 +1376,7 @@ function Ln(e, t) {
1374
1376
  labelField: t.labelField
1375
1377
  }));
1376
1378
  }
1377
- function Rn(e, t, n) {
1379
+ function zn(e, t, n) {
1378
1380
  let r = Object.keys(t)[0];
1379
1381
  if (r === void 0) throw Error("field.variants: at least one variant is required");
1380
1382
  if (n.shared && e in n.shared) throw Error(`field.variants: shared fields cannot redefine the discriminator "${e}"`);
@@ -1387,7 +1389,7 @@ function Rn(e, t, n) {
1387
1389
  ...r,
1388
1390
  [e]: o.literal(t)
1389
1391
  })), a = o.discriminatedUnion(e, i), s = i[0];
1390
- return a.default(s ? In(s, e, r) : {}).meta(Q({
1392
+ return a.default(s ? Ln(s, e, r) : {}).meta(Q({
1391
1393
  formType: "variants",
1392
1394
  discriminator: e,
1393
1395
  title: n.title,
@@ -1395,27 +1397,27 @@ function Rn(e, t, n) {
1395
1397
  labels: n.labels
1396
1398
  }));
1397
1399
  }
1398
- var zn = {
1399
- title: Sn,
1400
- text: Cn,
1401
- number: wn,
1402
- toggle: Tn,
1403
- choice: En,
1404
- entities: Dn,
1405
- entity: On,
1406
- area: kn,
1407
- icon: An,
1408
- stringList: jn,
1409
- group: Mn,
1410
- list: Ln,
1411
- variants: Rn
1400
+ var Bn = {
1401
+ title: Cn,
1402
+ text: wn,
1403
+ number: Tn,
1404
+ toggle: En,
1405
+ choice: Dn,
1406
+ entities: On,
1407
+ entity: kn,
1408
+ area: An,
1409
+ icon: jn,
1410
+ stringList: Mn,
1411
+ group: Nn,
1412
+ list: Rn,
1413
+ variants: zn
1412
1414
  };
1413
- function Bn(e) {
1415
+ function Vn(e) {
1414
1416
  return o.object(e);
1415
1417
  }
1416
1418
  //#endregion
1417
1419
  //#region src/shared/spline.ts
1418
- function Vn(e) {
1420
+ function Hn(e) {
1419
1421
  if (e.length < 2) return "";
1420
1422
  let t = e[0], n = e[1];
1421
1423
  if (!t || !n) return "";
@@ -1442,14 +1444,14 @@ function Vn(e) {
1442
1444
  }
1443
1445
  //#endregion
1444
1446
  //#region src/theme.ts
1445
- function Hn() {
1447
+ function Un() {
1446
1448
  return document.documentElement.classList.contains("dark");
1447
1449
  }
1448
1450
  //#endregion
1449
1451
  //#region src/version.ts
1450
- var Un = {
1452
+ var Wn = {
1451
1453
  name: "@glasshome/widget-sdk",
1452
- version: "1.9.2",
1454
+ version: "1.10.0",
1453
1455
  description: "SDK for building GlassHome dashboard widgets with SolidJS",
1454
1456
  keywords: [
1455
1457
  "glasshome",
@@ -1502,11 +1504,13 @@ var Un = {
1502
1504
  scripts: {
1503
1505
  build: "vite build && tsc && bun scripts/check-host-singleton.ts",
1504
1506
  dev: "vite build --watch",
1505
- test: "bun test",
1507
+ test: "bun test src && vitest run",
1508
+ "test:dom": "vitest run",
1509
+ "test:dom:watch": "vitest",
1506
1510
  "typecheck:test": "tsc --noEmit -p tsconfig.test.json"
1507
1511
  },
1508
1512
  peerDependencies: {
1509
- "@glasshome/sync-layer": "0.6.0",
1513
+ "@glasshome/sync-layer": "0.7.0",
1510
1514
  "@glasshome/ui": "1.3.0",
1511
1515
  "solid-js": "^1.9.11"
1512
1516
  },
@@ -1523,11 +1527,13 @@ var Un = {
1523
1527
  },
1524
1528
  devDependencies: {
1525
1529
  "@glasshome/ui": "1.3.0",
1530
+ "@solidjs/testing-library": "^0.8.10",
1526
1531
  "@types/bun": "latest",
1527
1532
  "@types/node": "^25.2.2",
1528
1533
  typescript: "^5",
1529
1534
  vite: "^8.1.0",
1530
- "vite-plugin-solid": "^2.11.12"
1535
+ "vite-plugin-solid": "^2.11.12",
1536
+ vitest: "^4.1.10"
1531
1537
  },
1532
1538
  repository: {
1533
1539
  type: "git",
@@ -1541,4 +1547,4 @@ var Un = {
1541
1547
  engines: { node: ">=18" }
1542
1548
  }.version;
1543
1549
  //#endregion
1544
- export { Se as Badge, Ce as Button, we as ColorSlider, Te as ColorWheel, Ee as Input, De as Label, Oe as ResponsiveDialog, ke as ResponsiveDialogContent, Ae as ResponsiveDialogDescription, je as ResponsiveDialogHeader, Me as ResponsiveDialogTitle, Un as SDK_VERSION, Ne as SchemaForm, Pe as Select, Fe as SelectContent, Ie as SelectItem, Le as SelectTrigger, Re as SelectValue, ze as Slider, Be as Switch, yn as ToneSchema, H as Widget, lt as WidgetContent, L as WidgetCtx, en as WidgetDialog, pt as WidgetIcon, r as WidgetManifestSchema, Ze as WidgetSizeCtx, st as WidgetSliderFill, ht as WidgetStatus, vt as WidgetTitle, At as WidgetValue, g as byDomain, yt as calculateLightGroup, bt as calculateSensorGroup, Tt as countActiveEntities, Bn as defineConfig, We as defineWidget, t as deprecations, zn as field, n as formatDeprecation, i as formatSchemaError, wt as getEntityAttribute, s as getForecasts, c as getStream, l as getWebRtcClientConfig, u as hassMediaUrl, rt as injectTokens, Hn as isDark, Ct as isEntityActive, Vn as monotoneCubicPath, Ve as parseColor, d as sendWebRtcCandidate, f as startWebRtcSession, p as state, vn as svgColors, m as trackEntityHistory, h as untrackEntityHistory, _ as useArea, v as useCamera, ee as useConnection, y as useCurrency, b as useEntities, te as useEntity, ne as useEntityHistory, re as useEntityStatistics, x as useForecast, ie as useHassConfig, tn as useIntersectionPause, ae as useLocale, nn as useReducedMotion, qe as useService, se as useStore, S as useTemperatureUnit, Xe as useToggle, Ye as useTurnOff, Je as useTurnOn, de as useUnitSystem, Ge as useWidgetContext, rn as useWidgetDialog, Qe as useWidgetDimensions, an as useWidgetEntityGroup, hn as useWidgetGestures, bn as widgetFields, a as z };
1550
+ export { Se as Badge, Ce as Button, we as ColorSlider, Te as ColorWheel, Ee as Input, De as Label, Oe as ResponsiveDialog, ke as ResponsiveDialogContent, Ae as ResponsiveDialogDescription, je as ResponsiveDialogHeader, Me as ResponsiveDialogTitle, Wn as SDK_VERSION, Ne as SchemaForm, Pe as Select, Fe as SelectContent, Ie as SelectItem, Le as SelectTrigger, Re as SelectValue, ze as Slider, Be as Switch, bn as ToneSchema, H as Widget, lt as WidgetContent, L as WidgetCtx, tn as WidgetDialog, pt as WidgetIcon, r as WidgetManifestSchema, Ze as WidgetSizeCtx, st as WidgetSliderFill, ht as WidgetStatus, vt as WidgetTitle, At as WidgetValue, g as byDomain, yt as calculateLightGroup, bt as calculateSensorGroup, Tt as countActiveEntities, Vn as defineConfig, We as defineWidget, t as deprecations, Bn as field, n as formatDeprecation, i as formatSchemaError, wt as getEntityAttribute, s as getForecasts, c as getStream, l as getWebRtcClientConfig, u as hassMediaUrl, rt as injectTokens, Un as isDark, Ct as isEntityActive, Hn as monotoneCubicPath, Ve as parseColor, d as sendWebRtcCandidate, f as startWebRtcSession, p as state, yn as svgColors, m as trackEntityHistory, h as untrackEntityHistory, _ as useArea, v as useCalendarEvents, ee as useCamera, y as useConnection, b as useCurrency, te as useEntities, ne as useEntity, re as useEntityHistory, x as useEntityStatistics, ie as useForecast, ae as useHassConfig, nn as useIntersectionPause, oe as useLocale, rn as useReducedMotion, qe as useService, se as useStore, ce as useTemperatureUnit, Xe as useToggle, Ye as useTurnOff, Je as useTurnOn, fe as useUnitSystem, Ge as useWidgetContext, an as useWidgetDialog, Qe as useWidgetDimensions, on as useWidgetEntityGroup, gn as useWidgetGestures, xn as widgetFields, a as z };
@@ -80,7 +80,14 @@ var D = [
80
80
  "schema",
81
81
  "defaultConfig",
82
82
  "examples",
83
- "configVersion"
83
+ "configVersion",
84
+ "defaultSize",
85
+ "capabilities",
86
+ "name",
87
+ "description",
88
+ "icon",
89
+ "minSize",
90
+ "maxSize"
84
91
  ];
85
92
  function O(e, t, n) {
86
93
  let r = l(e, "utf-8"), i = JSON.parse(r), a = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glasshome/widget-sdk",
3
- "version": "1.9.2",
3
+ "version": "1.10.0",
4
4
  "description": "SDK for building GlassHome dashboard widgets with SolidJS",
5
5
  "keywords": [
6
6
  "glasshome",
@@ -53,11 +53,13 @@
53
53
  "scripts": {
54
54
  "build": "vite build && tsc && bun scripts/check-host-singleton.ts",
55
55
  "dev": "vite build --watch",
56
- "test": "bun test",
56
+ "test": "bun test src && vitest run",
57
+ "test:dom": "vitest run",
58
+ "test:dom:watch": "vitest",
57
59
  "typecheck:test": "tsc --noEmit -p tsconfig.test.json"
58
60
  },
59
61
  "peerDependencies": {
60
- "@glasshome/sync-layer": "0.6.0",
62
+ "@glasshome/sync-layer": "0.7.0",
61
63
  "@glasshome/ui": "1.3.0",
62
64
  "solid-js": "^1.9.11"
63
65
  },
@@ -78,11 +80,13 @@
78
80
  },
79
81
  "devDependencies": {
80
82
  "@glasshome/ui": "1.3.0",
83
+ "@solidjs/testing-library": "^0.8.10",
81
84
  "@types/bun": "latest",
82
85
  "@types/node": "^25.2.2",
83
86
  "typescript": "^5",
84
87
  "vite": "^8.1.0",
85
- "vite-plugin-solid": "^2.11.12"
88
+ "vite-plugin-solid": "^2.11.12",
89
+ "vitest": "^4.1.10"
86
90
  },
87
91
  "repository": {
88
92
  "type": "git",