@glasshome/widget-sdk 1.17.1 → 1.18.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.
@@ -87,6 +87,13 @@ var e = "https://glasshome.app/docs/widget-sdk/config", t = [
87
87
  replacement: "the Widget's tone or color; the fill follows the card",
88
88
  docsUrl: "https://glasshome.app/docs/widget-sdk/widget",
89
89
  sourcePattern: "<WidgetSliderFill[^>]*\\scolor="
90
+ },
91
+ {
92
+ id: "WidgetVariantConfig.layout",
93
+ since: "1.18.0",
94
+ removeIn: "2.0.0",
95
+ replacement: "WidgetVariantConfig.styles (container, class, cssVars)",
96
+ docsUrl: "https://glasshome.app/docs/widgets/widget-sdk#variants"
90
97
  }
91
98
  ];
92
99
  function n(e) {
@@ -52,7 +52,7 @@ export declare function WidgetAction(props: {
52
52
  /** Visible label; without one the action is a square icon control named by `aria-label`. */
53
53
  children?: JSX.Element;
54
54
  "aria-label"?: string;
55
- run: () => Promise<unknown> | unknown;
55
+ run: () => unknown;
56
56
  confirm?: Confirm;
57
57
  /** Key within a shared `confirm`; defaults to the label or icon. */
58
58
  id?: string;
@@ -20,4 +20,3 @@ export declare const WIDGET_Z: {
20
20
  /** Action layer (buttons, edit controls) */
21
21
  readonly ACTIONS: 30;
22
22
  };
23
- export type WidgetZIndex = (typeof WIDGET_Z)[keyof typeof WIDGET_Z];
@@ -29,7 +29,7 @@ export interface GestureHandlers {
29
29
  /** Keyboard door: Enter or Space taps, the context-menu key or Shift+F10 holds. */
30
30
  onKeyDown?: (e: KeyboardEvent) => void;
31
31
  }
32
- export interface HoldFlood {
32
+ interface HoldFlood {
33
33
  x: number;
34
34
  y: number;
35
35
  /** Radius that reaches the farthest corner, so the fill ends as the hold fires. */
@@ -4,7 +4,7 @@ export interface Confirm {
4
4
  /** Whether anything ran a moment ago. */
5
5
  any: () => boolean;
6
6
  /** Runs the action; resolves whether it succeeded. */
7
- run: (keys: string[], action: () => Promise<unknown> | unknown) => Promise<boolean>;
7
+ run: (keys: string[], action: () => unknown) => Promise<boolean>;
8
8
  }
9
9
  /**
10
10
  * For actions that leave no state to show (a scene, a button press, a stop): the keys that just ran,
@@ -1,7 +1,7 @@
1
1
  /** Core type definitions for the Widget Framework. */
2
2
  import type { JSX } from "solid-js";
3
3
  /** Slide gesture configuration */
4
- export interface SlideGestureConfig {
4
+ interface SlideGestureConfig {
5
5
  /** Current value */
6
6
  value: number;
7
7
  /** Value change handler */
@@ -18,7 +18,7 @@ export interface SlideGestureConfig {
18
18
  /**
19
19
  * Hold gesture configuration
20
20
  */
21
- export interface HoldGestureConfig {
21
+ interface HoldGestureConfig {
22
22
  /** Action to perform on hold */
23
23
  action: () => void;
24
24
  /** Hold delay in ms (default: 300) */
@@ -35,21 +35,15 @@ export interface GestureConfig {
35
35
  /** Slide gesture configuration */
36
36
  slide?: SlideGestureConfig;
37
37
  }
38
- /**
39
- * CSS variable-based styling configuration
40
- */
38
+ /** Look applied to the widget shell: inline style, utility classes and CSS custom properties. */
41
39
  export interface WidgetStyles {
42
- /** Inline CSS properties applied to widget container */
43
40
  container?: JSX.CSSProperties;
44
- /** Tailwind utility classes (for simple styling) */
45
41
  class?: string;
46
- /** CSS custom properties for themeable values */
47
42
  cssVars?: Record<`--widget-${string}` | `--glass-${string}`, string | number>;
48
43
  }
49
- /**
50
- * Flex layout strategy with type-safe configuration
51
- */
52
- export interface FlexLayoutStrategy {
44
+ type WidgetElement = "icon" | "title" | "subtitle" | "status" | "value" | "metrics" | "content" | "background" | "overlay" | "decorations";
45
+ /** @deprecated Never read by the shell. Removed in 2.0.0. */
46
+ type LayoutStrategy = {
53
47
  type: "flex";
54
48
  direction: "row" | "column" | "row-reverse" | "column-reverse";
55
49
  align: "start" | "center" | "end" | "stretch";
@@ -57,106 +51,69 @@ export interface FlexLayoutStrategy {
57
51
  wrap?: boolean;
58
52
  gap?: string;
59
53
  order?: Partial<Record<WidgetElement, number>>;
60
- }
61
- /**
62
- * Grid layout strategy with type-safe configuration
63
- */
64
- export interface GridLayoutStrategy {
54
+ } | {
65
55
  type: "grid";
66
56
  areas: string;
67
57
  columns?: string;
68
58
  rows?: string;
69
59
  gap?: string;
70
60
  elementAreas: Partial<Record<WidgetElement, string>>;
71
- }
72
- /**
73
- * Absolute positioning strategy with type-safe configuration
74
- */
75
- export interface AbsoluteLayoutStrategy {
61
+ } | {
76
62
  type: "absolute";
77
- positions: Partial<Record<WidgetElement, PositionConfig>>;
78
- }
79
- /**
80
- * Custom layout strategy (escape hatch)
81
- */
82
- export interface CustomLayoutStrategy {
63
+ positions: Partial<Record<WidgetElement, {
64
+ top?: string;
65
+ right?: string;
66
+ bottom?: string;
67
+ left?: string;
68
+ transform?: string;
69
+ }>>;
70
+ } | {
83
71
  type: "custom";
84
72
  renderer: string;
85
- }
86
- /**
87
- * Discriminated union of all layout strategies
88
- */
89
- export type LayoutStrategy = FlexLayoutStrategy | GridLayoutStrategy | AbsoluteLayoutStrategy | CustomLayoutStrategy;
90
- /**
91
- * Position configuration for absolute layout
92
- */
93
- export interface PositionConfig {
94
- top?: string;
95
- right?: string;
96
- bottom?: string;
97
- left?: string;
98
- transform?: string;
99
- }
100
- /**
101
- * Widget element identifiers for layout configuration
102
- */
103
- export type WidgetElement = "icon" | "title" | "subtitle" | "status" | "value" | "metrics" | "content" | "background" | "overlay" | "decorations";
104
- /**
105
- * Element-specific configuration
106
- */
107
- export interface ElementConfig {
108
- visible?: Partial<Record<WidgetElement, boolean>>;
109
- styles?: Partial<Record<WidgetElement, JSX.CSSProperties>>;
110
- classNames?: Partial<Record<WidgetElement, string>>;
111
- }
112
- /**
113
- * Plugin system configuration (serializable)
114
- */
115
- export interface VariantPlugins {
116
- background?: string;
117
- overlay?: string;
118
- decorations?: string[];
119
- }
120
- /**
121
- * Interaction configuration
122
- */
123
- export interface InteractionConfig {
124
- hover?: boolean;
125
- active?: boolean;
126
- focus?: boolean;
127
- hoverScale?: number;
128
- activeScale?: number;
129
- }
130
- /**
131
- * Complete variant configuration
132
- */
73
+ };
74
+ /** A custom shell look; only `styles` is applied. */
133
75
  export interface WidgetVariantConfig {
134
76
  id: string;
135
77
  name: string;
136
78
  description?: string;
137
79
  styles?: WidgetStyles;
80
+ /** @deprecated Never read by the shell. Removed in 2.0.0. */
138
81
  layout?: LayoutStrategy;
139
- elements?: ElementConfig;
140
- plugins?: VariantPlugins;
141
- interactions?: InteractionConfig;
82
+ /** @deprecated Never read by the shell. Removed in 2.0.0. */
83
+ elements?: {
84
+ visible?: Partial<Record<WidgetElement, boolean>>;
85
+ styles?: Partial<Record<WidgetElement, JSX.CSSProperties>>;
86
+ classNames?: Partial<Record<WidgetElement, string>>;
87
+ };
88
+ /** @deprecated Never read by the shell. Removed in 2.0.0. */
89
+ plugins?: {
90
+ background?: string;
91
+ overlay?: string;
92
+ decorations?: string[];
93
+ };
94
+ /** @deprecated Never read by the shell. Removed in 2.0.0. */
95
+ interactions?: {
96
+ hover?: boolean;
97
+ active?: boolean;
98
+ focus?: boolean;
99
+ hoverScale?: number;
100
+ activeScale?: number;
101
+ };
102
+ /** @deprecated Never read by the shell. Removed in 2.0.0. */
142
103
  extends?: string;
143
104
  }
144
- /**
145
- * Variant registry type
146
- */
147
- export type VariantRegistry = Record<string, WidgetVariantConfig>;
148
105
  /** Unique entity identifier (e.g., "light.living_room") */
149
- export type EntityId = string;
106
+ type EntityId = string;
150
107
  /** Entity domain (e.g., "light", "sensor", "switch") */
151
- export type EntityDomain = string;
108
+ type EntityDomain = string;
152
109
  /** Area identifier */
153
- export type AreaId = string;
110
+ type AreaId = string;
154
111
  /** Device identifier */
155
- export type DeviceId = string;
112
+ type DeviceId = string;
156
113
  /** Label identifier */
157
- export type LabelId = string;
114
+ type LabelId = string;
158
115
  /** Entity category for filtering */
159
- export type EntityCategory = "config" | "diagnostic" | null;
116
+ type EntityCategory = "config" | "diagnostic" | null;
160
117
  /**
161
118
  * Full entity view interface representing a Home Assistant entity.
162
119
  * This is the canonical type used by SDK framework utils and widgets.
@@ -219,3 +176,4 @@ export interface EntityView {
219
176
  /** Bitmask of supported features */
220
177
  supportedFeatures?: number;
221
178
  }
179
+ export {};
@@ -1,49 +1,6 @@
1
- /**
2
- * Empty State Utilities
3
- *
4
- * Helper functions for creating consistent empty state configurations across widgets.
5
- */
6
1
  import type { JSX } from "solid-js";
7
- /**
8
- * Empty state configuration for a widget
9
- */
10
2
  export interface WidgetEmptyStateConfig {
11
- /** Icon to display */
12
3
  icon: JSX.Element;
13
- /** Main title/heading */
14
4
  title: string;
15
- /** Descriptive message */
16
5
  message: string;
17
6
  }
18
- export interface EmptyStateConfigOptions {
19
- /** Icon to display */
20
- icon: JSX.Element;
21
- /** Main title/heading */
22
- title: string;
23
- /** Descriptive message (optional - uses default template if not provided) */
24
- message?: string;
25
- /** Entity type for default message template (e.g., "a light", "a lock") */
26
- entityType?: string;
27
- }
28
- /**
29
- * Creates a consistent empty state configuration for widgets
30
- *
31
- * @example
32
- * ```typescript
33
- * // Custom message
34
- * const config = createEmptyStateConfig({
35
- * icon: <Thermometer />,
36
- * title: "No climate entity",
37
- * message: "Configure this widget to add a climate device",
38
- * });
39
- *
40
- * // Default message template
41
- * const config = createEmptyStateConfig({
42
- * icon: <Lock />,
43
- * title: "No lock entity",
44
- * entityType: "a lock",
45
- * });
46
- * // Result: message = "Configure this widget to add a lock"
47
- * ```
48
- */
49
- export declare function createEmptyStateConfig(options: EmptyStateConfigOptions): WidgetEmptyStateConfig;
@@ -5,7 +5,6 @@
5
5
  * in this directory are still consumed internally even when not part of the
6
6
  * public re-export surface.
7
7
  */
8
- export { cn } from "./cn";
9
8
  export { copyText, type CopyState, useCopyText } from "./clipboard";
10
9
  export { calculateLightGroup, calculateSensorGroup, type LightGroupResult, type SensorGroupResult, type SensorGroupType, } from "./entity-aggregation";
11
10
  export { countActiveEntities, getEntityAttribute, isEntityActive, } from "./entity-state";
@@ -1,39 +1,2 @@
1
- /**
2
- * Built-in Widget Variants
3
- *
4
- * Simplified set of pre-built variant definitions that ship with the framework.
5
- * These variants can be used as-is or extended/composed.
6
- *
7
- * Includes 3 core variants (classic-glass, minimal, compact-horizontal).
8
- * Additional variants can be added later or created by widget authors
9
- * using the variant composition utilities.
10
- */
11
- import type { VariantRegistry, WidgetVariantConfig } from "../types";
12
- export declare const classicGlass: WidgetVariantConfig;
13
- export declare const minimal: WidgetVariantConfig;
14
- export declare const compactHorizontal: WidgetVariantConfig;
15
- /**
16
- * Built-in variant registry
17
- * Maps variant IDs to their configurations
18
- */
19
- export declare const builtInVariants: VariantRegistry;
20
- /**
21
- * Get a variant by ID from the built-in registry
22
- *
23
- * @param id Variant ID
24
- * @returns Variant configuration or undefined if not found
25
- */
26
- export declare function getBuiltInVariant(id: string): WidgetVariantConfig | undefined;
27
- /**
28
- * Check if a variant ID is a built-in variant
29
- *
30
- * @param id Variant ID to check
31
- * @returns True if the variant is built-in
32
- */
33
- export declare function isBuiltInVariant(id: string): boolean;
34
- /**
35
- * Get all built-in variant IDs
36
- *
37
- * @returns Array of built-in variant IDs
38
- */
39
- export declare function getBuiltInVariantIds(): string[];
1
+ import type { WidgetStyles } from "../types";
2
+ export declare function getBuiltInVariantStyles(id: string): WidgetStyles | undefined;
package/dist/index.js CHANGED
@@ -727,87 +727,30 @@ function ir(e) {
727
727
  let n = t.createElement("style");
728
728
  n.setAttribute("data-glasshome-tokens", ""), n.textContent = $n, t.head.appendChild(n), er = !0;
729
729
  }
730
+ //#endregion
731
+ //#region src/framework/variants/built-in-variants.ts
730
732
  var ar = {
731
- "classic-glass": {
732
- id: "classic-glass",
733
- name: "Classic Glass",
734
- description: "Default glassmorphism design with blur background and gradient",
735
- styles: { cssVars: {
736
- "--widget-padding": "1.5rem",
737
- "--widget-backdrop": "blur(var(--glass-blur, var(--material-blur, 24px))) saturate(1.8)"
738
- } },
739
- layout: {
740
- type: "flex",
741
- direction: "column",
742
- align: "start",
743
- justify: "start",
744
- gap: "0.75rem"
745
- },
746
- interactions: {
747
- hover: !0,
748
- active: !0,
749
- focus: !0,
750
- hoverScale: 1.02,
751
- activeScale: .98
752
- }
753
- },
754
- minimal: {
755
- id: "minimal",
756
- name: "Minimal",
757
- description: "Clean, minimal design with no background effects",
758
- styles: { cssVars: {
759
- "--widget-padding": "1rem",
760
- "--glass-base": "transparent",
761
- "--glass-wash": "0%",
762
- "--glass-light": "0",
763
- "--glass-rim": "0",
764
- "--glass-lift": "0",
765
- "--glass-edge": "transparent"
766
- } },
767
- layout: {
768
- type: "flex",
769
- direction: "column",
770
- align: "start",
771
- justify: "start",
772
- gap: "0.5rem"
773
- },
774
- elements: { visible: {
775
- background: !1,
776
- decorations: !1
777
- } },
778
- interactions: {
779
- hover: !1,
780
- active: !1,
781
- focus: !0
782
- }
783
- },
784
- "compact-horizontal": {
785
- id: "compact-horizontal",
786
- name: "Compact Horizontal",
787
- description: "Horizontal layout for compact widgets",
788
- styles: { cssVars: {
789
- "--widget-padding": "1rem",
790
- "--widget-icon-size": "2.5rem",
791
- "--widget-backdrop": "blur(16px) saturate(1.8)"
792
- } },
793
- layout: {
794
- type: "flex",
795
- direction: "row",
796
- align: "center",
797
- justify: "start",
798
- gap: "1rem"
799
- },
800
- interactions: {
801
- hover: !0,
802
- active: !0,
803
- focus: !0,
804
- hoverScale: 1.02,
805
- activeScale: .98
806
- }
807
- }
733
+ "classic-glass": { cssVars: {
734
+ "--widget-padding": "1.5rem",
735
+ "--widget-backdrop": "blur(var(--glass-blur, var(--material-blur, 24px))) saturate(1.8)"
736
+ } },
737
+ minimal: { cssVars: {
738
+ "--widget-padding": "1rem",
739
+ "--glass-base": "transparent",
740
+ "--glass-wash": "0%",
741
+ "--glass-light": "0",
742
+ "--glass-rim": "0",
743
+ "--glass-lift": "0",
744
+ "--glass-edge": "transparent"
745
+ } },
746
+ "compact-horizontal": { cssVars: {
747
+ "--widget-padding": "1rem",
748
+ "--widget-icon-size": "2.5rem",
749
+ "--widget-backdrop": "blur(16px) saturate(1.8)"
750
+ } }
808
751
  };
809
752
  function or(e) {
810
- return ar[e];
753
+ return Object.hasOwn(ar, e) ? ar[e] : void 0;
811
754
  }
812
755
  //#endregion
813
756
  //#region src/framework/backgrounds/WidgetSliderFill.tsx
@@ -922,7 +865,8 @@ function xr(e = 2e3) {
922
865
  //#endregion
923
866
  //#region src/framework/utils/entity-aggregation.ts
924
867
  function Sr(e, t = !1) {
925
- if (e.length === 0) return {
868
+ let n = e[0];
869
+ if (!n) return {
926
870
  isGroup: !1,
927
871
  state: "unknown",
928
872
  isOn: !1,
@@ -934,84 +878,85 @@ function Sr(e, t = !1) {
934
878
  totalCount: 0,
935
879
  description: "No entities"
936
880
  };
937
- let n = e.length > 1;
938
- if (!n) {
939
- let t = e[0], n = t.state === "on", r = t.attributes?.brightness || 0, i = Math.round(r / 255 * 100);
881
+ let r = e.length > 1;
882
+ if (!r) {
883
+ let e = n, t = e.state === "on", r = e.attributes?.brightness || 0, i = Math.round(r / 255 * 100);
940
884
  return {
941
885
  isGroup: !1,
942
- state: t.state,
943
- isOn: n,
944
- isUnavailable: t.state === "unavailable",
886
+ state: e.state,
887
+ isOn: t,
888
+ isUnavailable: e.state === "unavailable",
945
889
  brightness: r,
946
890
  brightnessPercent: i,
947
- color: Tr(t),
948
- onCount: +!!n,
891
+ color: Tr(e),
892
+ onCount: +!!t,
949
893
  totalCount: 1,
950
- description: n ? "On" : "Off"
894
+ description: t ? "On" : "Off"
951
895
  };
952
896
  }
953
- let r = 0, i = 0, a = 0, o = 0, s = 0, c = 0, l = [];
897
+ let i = 0, a = 0, o = 0, s = 0, c = 0, l = 0, u = [];
954
898
  for (let t of e) switch (t.state) {
955
899
  case "on":
956
- if (r++, t.attributes?.brightness !== void 0 && (s += t.attributes.brightness, c++), t.attributes?.rgb_color) l.push(t.attributes.rgb_color);
900
+ if (i++, t.attributes?.brightness !== void 0 && (c += t.attributes.brightness, l++), t.attributes?.rgb_color) u.push(t.attributes.rgb_color);
957
901
  else if (t.attributes?.hs_color) {
958
902
  let [e, n] = t.attributes.hs_color;
959
- l.push(wr(e, n));
903
+ u.push(wr(e, n));
960
904
  }
961
905
  break;
962
906
  case "off":
963
- i++;
907
+ a++;
964
908
  break;
965
909
  case "unknown":
966
- a++;
910
+ o++;
967
911
  break;
968
- case "unavailable": o++;
912
+ case "unavailable": s++;
969
913
  }
970
- let u = e.length, d = o === u, f = a + o === u, p = r > 0, m = r === u, h = i === u, g, _;
971
- t ? d ? (g = "unavailable", _ = !1) : a > 0 || o > 0 ? (g = "unknown", _ = !1) : i > 0 ? (g = "off", _ = !1) : (g = "on", _ = !0) : d ? (g = "unavailable", _ = !1) : f ? (g = "unknown", _ = !1) : p ? (g = "on", _ = !0) : (g = "off", _ = !1);
972
- let v = c > 0 ? Math.round(s / c) : 0, y = Math.round(v / 255 * 100), b;
973
- if (l.length > 0) {
974
- let e = Math.round(l.reduce((e, t) => e + t[0], 0) / l.length), t = Math.round(l.reduce((e, t) => e + t[1], 0) / l.length), n = Math.round(l.reduce((e, t) => e + t[2], 0) / l.length), r = v / 255;
975
- b = `rgb(${Math.round(e * r)}, ${Math.round(t * r)}, ${Math.round(n * r)})`;
976
- } else b = Tr(e[0]);
977
- let x;
978
- return x = m ? "All on" : h ? "All off" : r > 0 ? `${r} of ${u} on` : "Off", {
979
- isGroup: n,
980
- state: g,
981
- isOn: _,
982
- isUnavailable: g === "unavailable",
983
- brightness: v,
984
- brightnessPercent: y,
985
- color: b,
986
- onCount: r,
987
- totalCount: u,
988
- description: x
914
+ let d = e.length, f = s === d, p = o + s === d, m = i > 0, h = i === d, g = a === d, _, v;
915
+ t ? f ? (_ = "unavailable", v = !1) : o > 0 || s > 0 ? (_ = "unknown", v = !1) : a > 0 ? (_ = "off", v = !1) : (_ = "on", v = !0) : f ? (_ = "unavailable", v = !1) : p ? (_ = "unknown", v = !1) : m ? (_ = "on", v = !0) : (_ = "off", v = !1);
916
+ let y = l > 0 ? Math.round(c / l) : 0, b = Math.round(y / 255 * 100), x;
917
+ if (u.length > 0) {
918
+ let e = Math.round(u.reduce((e, t) => e + t[0], 0) / u.length), t = Math.round(u.reduce((e, t) => e + t[1], 0) / u.length), n = Math.round(u.reduce((e, t) => e + t[2], 0) / u.length), r = y / 255;
919
+ x = `rgb(${Math.round(e * r)}, ${Math.round(t * r)}, ${Math.round(n * r)})`;
920
+ } else x = Tr(n);
921
+ let S;
922
+ return S = h ? "All on" : g ? "All off" : i > 0 ? `${i} of ${d} on` : "Off", {
923
+ isGroup: r,
924
+ state: _,
925
+ isOn: v,
926
+ isUnavailable: _ === "unavailable",
927
+ brightness: y,
928
+ brightnessPercent: b,
929
+ color: x,
930
+ onCount: i,
931
+ totalCount: d,
932
+ description: S
989
933
  };
990
934
  }
991
935
  function Cr(e, t = "mean", n = !0) {
992
- if (e.length === 0) return {
936
+ let r = e[0];
937
+ if (!r) return {
993
938
  isGroup: !1,
994
939
  state: "unknown",
995
940
  numericValue: null,
996
941
  isUnavailable: !0,
997
942
  description: "No entities"
998
943
  };
999
- let r = e.length > 1;
1000
- if (!r) {
1001
- let t = e[0], n = Number.parseFloat(t.state), r = t.state;
1002
- return Number.isNaN(n) || (r = Number.isInteger(n) ? n.toString() : n.toFixed(1)), {
944
+ let i = e.length > 1;
945
+ if (!i) {
946
+ let e = r, t = Number.parseFloat(e.state), n = e.state;
947
+ return Number.isNaN(t) || (n = Number.isInteger(t) ? t.toString() : t.toFixed(1)), {
1003
948
  isGroup: !1,
1004
- state: r,
1005
- numericValue: Number.isNaN(n) ? null : n,
1006
- isUnavailable: t.state === "unavailable",
1007
- unit: t.unitOfMeasurement ?? void 0,
1008
- description: r
949
+ state: n,
950
+ numericValue: Number.isNaN(t) ? null : t,
951
+ isUnavailable: e.state === "unavailable",
952
+ unit: e.unitOfMeasurement ?? void 0,
953
+ description: n
1009
954
  };
1010
955
  }
1011
- let i = [], a = [], o = 0, s;
956
+ let a = [], o = [], s = 0, c;
1012
957
  for (let t of e) {
1013
- if (!s && t.unitOfMeasurement && (s = t.unitOfMeasurement), t.state === "unavailable") {
1014
- o++, a.push({
958
+ if (!c && t.unitOfMeasurement && (c = t.unitOfMeasurement), t.state === "unavailable") {
959
+ s++, o.push({
1015
960
  entityId: t.id,
1016
961
  value: "unavailable",
1017
962
  friendly_name: t.friendlyName
@@ -1019,69 +964,69 @@ function Cr(e, t = "mean", n = !0) {
1019
964
  continue;
1020
965
  }
1021
966
  let e = Number.parseFloat(t.state);
1022
- isNaN(e) ? n || a.push({
967
+ isNaN(e) ? n || o.push({
1023
968
  entityId: t.id,
1024
969
  value: t.state,
1025
970
  friendly_name: t.friendlyName
1026
- }) : (i.push(e), a.push({
971
+ }) : (a.push(e), o.push({
1027
972
  entityId: t.id,
1028
973
  value: e,
1029
974
  friendly_name: t.friendlyName
1030
975
  }));
1031
976
  }
1032
- let c = o === e.length, l = i.length === 0;
1033
- if (c || l) return {
1034
- isGroup: r,
1035
- state: c ? "unavailable" : "unknown",
977
+ let l = s === e.length, u = a.at(-1);
978
+ if (l || u === void 0) return {
979
+ isGroup: i,
980
+ state: l ? "unavailable" : "unknown",
1036
981
  numericValue: null,
1037
- isUnavailable: c,
1038
- unit: s,
1039
- description: c ? "Unavailable" : "No numeric values",
1040
- memberValues: a
982
+ isUnavailable: l,
983
+ unit: c,
984
+ description: l ? "Unavailable" : "No numeric values",
985
+ memberValues: o
1041
986
  };
1042
- let u, d;
987
+ let d, f;
1043
988
  switch (t) {
1044
989
  case "min":
1045
- u = Math.min(...i), d = "Minimum";
990
+ d = Math.min(...a), f = "Minimum";
1046
991
  break;
1047
992
  case "max":
1048
- u = Math.max(...i), d = "Maximum";
993
+ d = Math.max(...a), f = "Maximum";
1049
994
  break;
1050
995
  case "mean":
1051
- u = i.reduce((e, t) => e + t, 0) / i.length, d = "Average";
996
+ d = a.reduce((e, t) => e + t, 0) / a.length, f = "Average";
1052
997
  break;
1053
998
  case "median": {
1054
- let e = [...i].sort((e, t) => e - t), t = Math.floor(e.length / 2);
1055
- u = e.length % 2 == 0 ? (e[t - 1] + e[t]) / 2 : e[t], d = "Median";
999
+ let e = [...a].sort((e, t) => e - t), t = Math.floor(e.length / 2), n = e[t] ?? u;
1000
+ d = e.length % 2 == 0 ? ((e[t - 1] ?? n) + n) / 2 : n, f = "Median";
1056
1001
  break;
1057
1002
  }
1058
1003
  case "sum":
1059
- u = i.reduce((e, t) => e + t, 0), d = "Sum";
1004
+ d = a.reduce((e, t) => e + t, 0), f = "Sum";
1060
1005
  break;
1061
1006
  case "last":
1062
- u = i[i.length - 1], d = "Latest";
1007
+ d = u, f = "Latest";
1063
1008
  break;
1064
1009
  case "range":
1065
- u = Math.max(...i) - Math.min(...i), d = "Range";
1010
+ d = Math.max(...a) - Math.min(...a), f = "Range";
1066
1011
  break;
1067
1012
  case "product":
1068
- u = i.reduce((e, t) => e * t, 1), d = "Product";
1013
+ d = a.reduce((e, t) => e * t, 1), f = "Product";
1069
1014
  break;
1070
1015
  case "std_dev": {
1071
- let e = i.reduce((e, t) => e + t, 0) / i.length, t = i.reduce((t, n) => t + (n - e) ** 2, 0) / i.length;
1072
- u = Math.sqrt(t), d = "Std Dev";
1016
+ let e = a.reduce((e, t) => e + t, 0) / a.length, t = a.reduce((t, n) => t + (n - e) ** 2, 0) / a.length;
1017
+ d = Math.sqrt(t), f = "Std Dev";
1073
1018
  break;
1074
1019
  }
1075
- default: u = i.reduce((e, t) => e + t, 0) / i.length, d = "Average";
1020
+ default: d = a.reduce((e, t) => e + t, 0) / a.length, f = "Average";
1076
1021
  }
1077
1022
  return {
1078
- isGroup: r,
1079
- state: Number.isInteger(u) ? u.toString() : u.toFixed(1),
1080
- numericValue: u,
1023
+ isGroup: i,
1024
+ state: Number.isInteger(d) ? d.toString() : d.toFixed(1),
1025
+ numericValue: d,
1081
1026
  isUnavailable: !1,
1082
- unit: s,
1083
- description: `${d} of ${i.length}`,
1084
- memberValues: a
1027
+ unit: c,
1028
+ description: `${f} of ${a.length}`,
1029
+ memberValues: o
1085
1030
  };
1086
1031
  }
1087
1032
  function wr(e, t) {
@@ -1206,13 +1151,13 @@ function Vr(t) {
1206
1151
  };
1207
1152
  n && Object.defineProperty(d, tn, { value: n });
1208
1153
  let f = D(() => {
1209
- if (t.variant) return typeof t.variant == "string" ? or(t.variant) : t.variant;
1154
+ if (t.variant) return typeof t.variant == "string" ? or(t.variant) : t.variant.styles;
1210
1155
  }), p = e(() => t.gradient, "widget.gradient"), m = D(() => ({
1211
1156
  "container-type": "size",
1212
1157
  "container-name": "widget",
1213
1158
  "touch-action": o() && !t.isEditMode ? o()?.touchAction() : void 0,
1214
- ...f()?.styles?.container,
1215
- ...f()?.styles?.cssVars || {},
1159
+ ...f()?.container,
1160
+ ...f()?.cssVars || {},
1216
1161
  ...t.tone ? { "--widget-color": `var(--tone-${t.tone})` } : {},
1217
1162
  ...t.color ? { "--widget-color": t.color } : {},
1218
1163
  ...t.tone && t.tone !== "neutral" || t.color ? { "--glass-tone": "var(--widget-color)" } : {},
@@ -1295,7 +1240,7 @@ function Vr(t) {
1295
1240
  return P((t) => R(e, "z-index", K.OVERLAY)), e;
1296
1241
  })();
1297
1242
  })(), null), P((r) => {
1298
- var i = X("glasshome-widget glass", "relative h-full w-full select-none overflow-hidden rounded-xl", f()?.styles?.class, t.class), a = m(), o = t.confirmed || void 0, s = h() ? 0 : void 0, c = K.CONTENT;
1243
+ var i = X("glasshome-widget glass", "relative h-full w-full select-none overflow-hidden rounded-xl", f()?.class, t.class), a = m(), o = t.confirmed || void 0, s = h() ? 0 : void 0, c = K.CONTENT;
1299
1244
  return i !== r.e && M(e, r.e = i), r.t = z(e, a, r.t), o !== r.a && L(e, "data-confirmed", r.a = o), s !== r.o && L(e, "tabindex", r.o = s), c !== r.i && R(n, "z-index", r.i = c), r;
1300
1245
  }, {
1301
1246
  e: void 0,
@@ -1797,7 +1742,7 @@ function ki(e) {
1797
1742
  let t = e.replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ");
1798
1743
  return t.charAt(0).toUpperCase() + t.slice(1).toLowerCase();
1799
1744
  }
1800
- var Ai = (e) => typeof e != "object" || !e;
1745
+ var Ai = (e) => e === null || typeof e != "object" && typeof e != "function";
1801
1746
  function ji(e) {
1802
1747
  return e == null ? "None" : String(e);
1803
1748
  }
@@ -1884,7 +1829,7 @@ function Ii(e) {
1884
1829
  })();
1885
1830
  }
1886
1831
  function Li(e) {
1887
- let t = D(() => Object.entries(e.record)), n = D(() => t().filter(([, e]) => Ai(e))), r = D(() => t().filter(([, e]) => !Ai(e)));
1832
+ let t = D(() => Object.entries(e.record)), n = D(() => t().flatMap(([e, t]) => Ai(t) ? [[e, t]] : [])), r = D(() => t().filter(([, e]) => !Ai(e)));
1888
1833
  return N(w, {
1889
1834
  get when() {
1890
1835
  return t().length > 0;
@@ -2829,7 +2774,7 @@ function Qa() {
2829
2774
  //#region src/version.ts
2830
2775
  var $a = {
2831
2776
  name: "@glasshome/widget-sdk",
2832
- version: "1.17.1",
2777
+ version: "1.18.0",
2833
2778
  description: "SDK for building GlassHome dashboard widgets with SolidJS",
2834
2779
  keywords: [
2835
2780
  "glasshome",
@@ -2885,16 +2830,16 @@ var $a = {
2885
2830
  test: "bun test src && vitest run",
2886
2831
  "test:dom": "vitest run",
2887
2832
  "test:dom:watch": "vitest",
2888
- "typecheck:test": "tsc --noEmit -p tsconfig.test.json"
2833
+ "typecheck:test": "tsc --noEmit -p tsconfig.test.json",
2834
+ "check:dead": "fallow dead-code --fail-on-issues"
2889
2835
  },
2890
2836
  peerDependencies: {
2891
- "@glasshome/sync-layer": "0.9.0",
2892
- "@glasshome/ui": "1.20.0",
2837
+ "@glasshome/sync-layer": "0.9.1",
2838
+ "@glasshome/ui": "1.21.1",
2893
2839
  "solid-js": "^1.9.11"
2894
2840
  },
2895
2841
  dependencies: {
2896
2842
  "@glasshome/widget-contract": "0.3.0",
2897
- "@modular-forms/solid": "^0.25.1",
2898
2843
  "@tailwindcss/vite": "^4.1.13",
2899
2844
  clsx: "^2.1.1",
2900
2845
  "happy-dom": "^20.0.0",
@@ -2904,10 +2849,11 @@ var $a = {
2904
2849
  zod: "^4.1.13"
2905
2850
  },
2906
2851
  devDependencies: {
2907
- "@glasshome/ui": "1.20.0",
2852
+ "@glasshome/ui": "1.21.1",
2908
2853
  "@solidjs/testing-library": "^0.8.10",
2909
2854
  "@types/bun": "^1.4.0",
2910
2855
  "@types/node": "^25.2.2",
2856
+ fallow: "3.17.0",
2911
2857
  typescript: "^7.0.2",
2912
2858
  vite: "^8.1.0",
2913
2859
  "vite-plugin-solid": "^2.11.12",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glasshome/widget-sdk",
3
- "version": "1.17.1",
3
+ "version": "1.18.0",
4
4
  "description": "SDK for building GlassHome dashboard widgets with SolidJS",
5
5
  "keywords": [
6
6
  "glasshome",
@@ -56,16 +56,16 @@
56
56
  "test": "bun test src && vitest run",
57
57
  "test:dom": "vitest run",
58
58
  "test:dom:watch": "vitest",
59
- "typecheck:test": "tsc --noEmit -p tsconfig.test.json"
59
+ "typecheck:test": "tsc --noEmit -p tsconfig.test.json",
60
+ "check:dead": "fallow dead-code --fail-on-issues"
60
61
  },
61
62
  "peerDependencies": {
62
- "@glasshome/sync-layer": "0.9.0",
63
- "@glasshome/ui": "1.20.0",
63
+ "@glasshome/sync-layer": "0.9.1",
64
+ "@glasshome/ui": "1.21.1",
64
65
  "solid-js": "^1.9.11"
65
66
  },
66
67
  "dependencies": {
67
68
  "@glasshome/widget-contract": "0.3.0",
68
- "@modular-forms/solid": "^0.25.1",
69
69
  "@tailwindcss/vite": "^4.1.13",
70
70
  "clsx": "^2.1.1",
71
71
  "happy-dom": "^20.0.0",
@@ -75,10 +75,11 @@
75
75
  "zod": "^4.1.13"
76
76
  },
77
77
  "devDependencies": {
78
- "@glasshome/ui": "1.20.0",
78
+ "@glasshome/ui": "1.21.1",
79
79
  "@solidjs/testing-library": "^0.8.10",
80
80
  "@types/bun": "^1.4.0",
81
81
  "@types/node": "^25.2.2",
82
+ "fallow": "3.17.0",
82
83
  "typescript": "^7.0.2",
83
84
  "vite": "^8.1.0",
84
85
  "vite-plugin-solid": "^2.11.12",
package/preview/host.tsx CHANGED
@@ -4,7 +4,7 @@ import { render } from "solid-js/web";
4
4
 
5
5
  function PreviewHost() {
6
6
  const [isDark, setIsDark] = createSignal(false);
7
- const [config, setConfig] = createSignal(widget.manifest.defaultConfig || {});
7
+ const [config] = createSignal(widget.manifest.defaultConfig || {});
8
8
 
9
9
  const toggleDark = () => {
10
10
  const next = !isDark();
@@ -86,4 +86,6 @@ function PreviewHost() {
86
86
  );
87
87
  }
88
88
 
89
- render(() => <PreviewHost />, document.getElementById("root")!);
89
+ const root = document.getElementById("root");
90
+ if (!root) throw new Error("preview: #root is missing");
91
+ render(() => <PreviewHost />, root);
@@ -1,7 +0,0 @@
1
- /**
2
- * Variant System - Barrel Export
3
- *
4
- * Built-in variant presets and composition utilities.
5
- */
6
- export { builtInVariants, classicGlass, compactHorizontal, getBuiltInVariant, getBuiltInVariantIds, isBuiltInVariant, minimal, } from "./built-in-variants";
7
- export { applyCssVars, applyLayout, composeVariants, createFlexLayout, extendVariant, mergeVariants, } from "./variant-utils";
@@ -1,105 +0,0 @@
1
- /**
2
- * Variant Composition Utilities
3
- *
4
- * Utilities for creating, merging, extending, and composing widget variants.
5
- * Enables flexible variant system with type safety and composability.
6
- */
7
- import type { FlexLayoutStrategy, LayoutStrategy, WidgetVariantConfig } from "../types";
8
- /**
9
- * Merge two complete variant configurations
10
- *
11
- * Deep merges all properties, with `override` taking precedence.
12
- * Layout strategies are replaced, not merged (since they have different shapes).
13
- *
14
- * @example
15
- * ```tsx
16
- * const customVariant = mergeVariants(classicGlass, {
17
- * styles: {
18
- * cssVars: {
19
- * "--glass-base": "oklch(0.2 0 0 / 0.5)"
20
- * }
21
- * }
22
- * });
23
- * ```
24
- */
25
- export declare function mergeVariants(base: WidgetVariantConfig, override: Partial<WidgetVariantConfig>): WidgetVariantConfig;
26
- /**
27
- * Extend a base variant with partial overrides
28
- *
29
- * Similar to mergeVariants but returns a new variant that references the base
30
- * via the `extends` property. Useful for variant inheritance chains.
31
- *
32
- * @example
33
- * ```tsx
34
- * const darkGlass = extendVariant("classic-glass", {
35
- * id: "dark-glass",
36
- * name: "Dark Glass",
37
- * styles: {
38
- * cssVars: {
39
- * "--glass-base": "oklch(0.2 0 0 / 0.5)"
40
- * }
41
- * }
42
- * });
43
- * ```
44
- */
45
- export declare function extendVariant(baseId: string, override: Partial<WidgetVariantConfig> & {
46
- id: string;
47
- name: string;
48
- }): WidgetVariantConfig;
49
- /**
50
- * Compose multiple variants together (left to right precedence)
51
- *
52
- * Merges multiple variants in sequence, with later variants taking precedence.
53
- * Useful for combining layout + styling + interaction variants.
54
- *
55
- * @example
56
- * ```tsx
57
- * const custom = composeVariants(
58
- * minimal, // Base: minimal styling
59
- * centeredLayout, // Add: centered layout
60
- * largeIcons // Add: large icon sizing
61
- * );
62
- * ```
63
- */
64
- export declare function composeVariants(...variants: WidgetVariantConfig[]): WidgetVariantConfig;
65
- /**
66
- * Apply a layout strategy override to a variant
67
- *
68
- * Convenience function for changing just the layout of a variant.
69
- *
70
- * @example
71
- * ```tsx
72
- * const centered = applyLayout(minimal, {
73
- * type: "flex",
74
- * direction: "column",
75
- * align: "center",
76
- * justify: "center"
77
- * });
78
- * ```
79
- */
80
- export declare function applyLayout(variant: WidgetVariantConfig, layout: LayoutStrategy): WidgetVariantConfig;
81
- /**
82
- * Apply CSS variable overrides to a variant
83
- *
84
- * Convenience function for changing just the CSS variables of a variant.
85
- *
86
- * @example
87
- * ```tsx
88
- * const blueTinted = applyCssVars(classicGlass, {
89
- * "--glass-tone": "oklch(0.62 0.2 260)",
90
- * "--glass-wash": "30%"
91
- * });
92
- * ```
93
- */
94
- export declare function applyCssVars(variant: WidgetVariantConfig, cssVars: Record<`--widget-${string}` | `--glass-${string}`, string | number>): WidgetVariantConfig;
95
- /**
96
- * Create a flex layout strategy
97
- *
98
- * Convenience function for creating common flex layouts.
99
- *
100
- * @example
101
- * ```tsx
102
- * const layout = createFlexLayout("column", "center", "center");
103
- * ```
104
- */
105
- export declare function createFlexLayout(direction?: FlexLayoutStrategy["direction"], align?: FlexLayoutStrategy["align"], justify?: FlexLayoutStrategy["justify"], options?: Partial<FlexLayoutStrategy>): FlexLayoutStrategy;