@glasshome/widget-sdk 0.1.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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +79 -0
  3. package/dist/create-entity.d.ts +22 -0
  4. package/dist/define-widget.d.ts +10 -0
  5. package/dist/framework/backgrounds/Glow.d.ts +24 -0
  6. package/dist/framework/backgrounds/WidgetSliderFill.d.ts +33 -0
  7. package/dist/framework/components/WidgetContent.d.ts +29 -0
  8. package/dist/framework/components/WidgetEmptyState.d.ts +34 -0
  9. package/dist/framework/components/WidgetIcon.d.ts +48 -0
  10. package/dist/framework/components/WidgetMetrics.d.ts +47 -0
  11. package/dist/framework/components/WidgetStatus.d.ts +26 -0
  12. package/dist/framework/components/WidgetSubtitle.d.ts +28 -0
  13. package/dist/framework/components/WidgetTitle.d.ts +29 -0
  14. package/dist/framework/components/WidgetValue.d.ts +31 -0
  15. package/dist/framework/core/Widget.d.ts +88 -0
  16. package/dist/framework/design-system/index.d.ts +8 -0
  17. package/dist/framework/design-system/spacing.d.ts +40 -0
  18. package/dist/framework/design-system/typography.d.ts +40 -0
  19. package/dist/framework/design-system/z-index.d.ts +23 -0
  20. package/dist/framework/dialogs/WidgetDialog.d.ts +47 -0
  21. package/dist/framework/dialogs/index.d.ts +4 -0
  22. package/dist/framework/gestures/cursors.d.ts +17 -0
  23. package/dist/framework/gestures/use-widget-gestures.d.ts +53 -0
  24. package/dist/framework/hooks/index.d.ts +15 -0
  25. package/dist/framework/hooks/use-debug-data.d.ts +46 -0
  26. package/dist/framework/hooks/use-widget-config.d.ts +48 -0
  27. package/dist/framework/hooks/use-widget-context.d.ts +46 -0
  28. package/dist/framework/hooks/use-widget-dialog.d.ts +45 -0
  29. package/dist/framework/hooks/use-widget-entity-group.d.ts +67 -0
  30. package/dist/framework/hooks/use-widget-entity.d.ts +53 -0
  31. package/dist/framework/hooks/use-widget-form.d.ts +79 -0
  32. package/dist/framework/hooks/use-widget-responsive.d.ts +41 -0
  33. package/dist/framework/index.d.ts +55 -0
  34. package/dist/framework/layout/WidgetStack.d.ts +28 -0
  35. package/dist/framework/theming/adaptive-color.d.ts +28 -0
  36. package/dist/framework/theming/colors.d.ts +59 -0
  37. package/dist/framework/theming/index.d.ts +8 -0
  38. package/dist/framework/types.d.ts +335 -0
  39. package/dist/framework/utils/cn.d.ts +28 -0
  40. package/dist/framework/utils/empty-state.d.ts +49 -0
  41. package/dist/framework/utils/entity-aggregation.d.ts +73 -0
  42. package/dist/framework/utils/entity-state.d.ts +103 -0
  43. package/dist/framework/utils/format-value.d.ts +22 -0
  44. package/dist/framework/utils/index.d.ts +12 -0
  45. package/dist/framework/utils/interpret-value.d.ts +17 -0
  46. package/dist/framework/variants/built-in-variants.d.ts +39 -0
  47. package/dist/framework/variants/index.d.ts +7 -0
  48. package/dist/framework/variants/variant-utils.d.ts +105 -0
  49. package/dist/index.d.ts +7 -0
  50. package/dist/index.js +1955 -0
  51. package/dist/theme.d.ts +21 -0
  52. package/dist/types.d.ts +51 -0
  53. package/dist/version.d.ts +5 -0
  54. package/dist/vite/index.d.ts +56 -0
  55. package/dist/vite/index.js +184 -0
  56. package/package.json +76 -0
  57. package/preview/host.tsx +89 -0
  58. package/preview/preview.html +49 -0
  59. package/tailwind-sources.css +1 -0
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Theme utilities — plain DOM reads.
3
+ * Framework-agnostic: no SolidJS imports.
4
+ *
5
+ * These functions read CSS custom properties and class state from the
6
+ * document root. They are synchronous and work in any browser environment.
7
+ */
8
+ /**
9
+ * Read a CSS custom property (theme token) from the document root.
10
+ * Automatically prepends "--" if not already present.
11
+ *
12
+ * @example
13
+ * getThemeToken("color-primary") // reads --color-primary
14
+ * getThemeToken("--color-primary") // also works
15
+ */
16
+ export declare function getThemeToken(name: string): string;
17
+ /**
18
+ * Check whether the current theme is dark mode.
19
+ * Reads the presence of the "dark" class on <html>.
20
+ */
21
+ export declare function isDark(): boolean;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Grid size constraint — number of columns (w) and rows (h).
3
+ */
4
+ export interface GridSize {
5
+ w: number;
6
+ h: number;
7
+ }
8
+ /**
9
+ * Widget manifest — declarative metadata about a widget.
10
+ * Framework-agnostic: no SolidJS imports.
11
+ */
12
+ export interface WidgetManifest {
13
+ tag: string;
14
+ name: string;
15
+ description?: string;
16
+ minSize: GridSize;
17
+ maxSize: GridSize;
18
+ sdkVersion: string;
19
+ icon?: string;
20
+ schema?: object;
21
+ defaultConfig?: Record<string, unknown>;
22
+ }
23
+ /**
24
+ * Runtime context provided to widgets by the host.
25
+ * Framework-agnostic: plain objects and functions.
26
+ */
27
+ export interface WidgetContext {
28
+ config: Record<string, unknown>;
29
+ theme: {
30
+ getToken: (name: string) => string;
31
+ isDark: () => boolean;
32
+ };
33
+ dimensions: {
34
+ width: number;
35
+ height: number;
36
+ };
37
+ }
38
+ /**
39
+ * A complete widget definition combining manifest metadata with a component.
40
+ * The component return type is `any` so the type contract does not force SolidJS
41
+ * (internally the runtime knows it is SolidJS, but widget authors code against
42
+ * a stable contract that does not leak framework internals).
43
+ *
44
+ * @template C - Widget configuration type
45
+ */
46
+ export interface WidgetDefinition<C = Record<string, unknown>> {
47
+ manifest: WidgetManifest;
48
+ component: (props: {
49
+ config: C;
50
+ }) => any;
51
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * SDK version constant for runtime version compatibility checking.
3
+ * The host can compare this against widget manifest sdkVersion fields.
4
+ */
5
+ export declare const SDK_VERSION = "0.2.0";
@@ -0,0 +1,56 @@
1
+ import type { InlineConfig, Plugin } from "vite";
2
+ export interface GlasshomeWidgetOptions {
3
+ /** Entry file for the widget (default: "src/index.tsx") */
4
+ entry?: string;
5
+ }
6
+ /** Packages provided by the host import map — widgets must not bundle these. */
7
+ export declare function isWidgetExternal(id: string): boolean;
8
+ interface DiscoveredWidget {
9
+ name: string;
10
+ entry: string;
11
+ }
12
+ /** Scan srcDir for subdirectories containing index.tsx + manifest.json. */
13
+ export declare function discoverWidgets(srcDir: string): DiscoveredWidget[];
14
+ /** Generate registry.json from widget manifests. */
15
+ export declare function generateRegistry(srcDir: string, outDir: string): void;
16
+ export interface BuildWidgetsOptions {
17
+ /** Directory containing widget subdirectories (default: "src") */
18
+ srcDir?: string;
19
+ /** Output directory for built bundles and registry (default: "dist") */
20
+ outDir?: string;
21
+ /** Additional Vite plugins to apply to each widget build (e.g. solid()) */
22
+ plugins?: Plugin[];
23
+ /** Extra Vite config merged into each widget build */
24
+ viteConfig?: InlineConfig;
25
+ /** Build only these widget names (subdirectory names). Skips full clean. */
26
+ only?: string[];
27
+ }
28
+ /**
29
+ * Build each widget as a separate Vite invocation so shared code is inlined
30
+ * into each bundle (no chunk splitting).
31
+ */
32
+ export declare function buildWidgets(options?: BuildWidgetsOptions): Promise<void>;
33
+ /**
34
+ * Vite plugin for GlassHome widget development and building.
35
+ *
36
+ * Returns an array of plugins:
37
+ * - Build mode: configures library build with proper externals
38
+ * - Dev mode: serves a preview host with dark mode toggle and manifest display
39
+ */
40
+ export declare function glasshomeWidget(options?: GlasshomeWidgetOptions): Plugin[];
41
+ export interface GlasshomeWidgetsOptions {
42
+ /** Directory containing widget subdirectories (default: "src") */
43
+ srcDir?: string;
44
+ /** Output directory for built bundles and registry (default: "dist") */
45
+ outDir?: string;
46
+ }
47
+ /**
48
+ * Vite plugin for multi-widget projects.
49
+ *
50
+ * In build mode, delegates to `buildWidgets()` which runs a separate Vite
51
+ * build per widget so shared code is inlined (no chunk splitting).
52
+ * The plugin's `config()` returns a minimal build config to suppress Vite's
53
+ * default build, and `closeBundle()` runs the actual per-widget builds.
54
+ */
55
+ export declare function glasshomeWidgets(options?: GlasshomeWidgetsOptions): Plugin[];
56
+ export {};
@@ -0,0 +1,184 @@
1
+ import { existsSync as a, rmSync as x, mkdirSync as w, readdirSync as v, statSync as b, readFileSync as D, writeFileSync as S } from "node:fs";
2
+ import { resolve as l, join as g, sep as h, dirname as W } from "node:path";
3
+ import { fileURLToPath as I } from "node:url";
4
+ const j = "virtual:glasshome-widget", p = "\0virtual:glasshome-widget", E = "/@glasshome/preview";
5
+ function y(e) {
6
+ return h === "\\" ? e.split(h).join("/") : e;
7
+ }
8
+ function f() {
9
+ const e = I(import.meta.url);
10
+ return l(W(e), "../../preview");
11
+ }
12
+ function P(e) {
13
+ return e === "solid-js" || e.startsWith("solid-js/") || e === "@glasshome/widget-sdk" || e.startsWith("@glasshome/widget-sdk/") || e === "@glasshome/ui" || e.startsWith("@glasshome/ui/") || e === "@glasshome/sync-layer" || e.startsWith("@glasshome/sync-layer/");
14
+ }
15
+ function _(e) {
16
+ const i = [];
17
+ if (!a(e)) return i;
18
+ for (const r of v(e)) {
19
+ const n = l(e, r);
20
+ if (!b(n).isDirectory()) continue;
21
+ const t = l(n, "index.tsx"), s = l(n, "manifest.json");
22
+ a(t) && a(s) && i.push({ name: r, entry: t });
23
+ }
24
+ return i;
25
+ }
26
+ function O(e, i) {
27
+ const r = [];
28
+ if (a(e))
29
+ for (const t of v(e)) {
30
+ if (!b(g(e, t)).isDirectory()) continue;
31
+ const s = g(e, t, "manifest.json");
32
+ try {
33
+ const o = JSON.parse(D(s, "utf-8"));
34
+ r.push({ ...o, bundleUrl: `./${t}.js` });
35
+ } catch {
36
+ }
37
+ }
38
+ const n = {
39
+ version: 1,
40
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
41
+ baseUrl: "./",
42
+ widgets: r
43
+ };
44
+ a(i) || w(i, { recursive: !0 }), S(g(i, "registry.json"), JSON.stringify(n, null, 2)), console.log(`[registry] Generated registry.json with ${r.length} widget(s)`);
45
+ }
46
+ async function R(e) {
47
+ const { build: i } = await import("vite"), r = process.cwd(), n = l(r, e?.srcDir ?? "src"), t = l(r, e?.outDir ?? "dist");
48
+ let s = _(n);
49
+ if (s.length === 0) {
50
+ console.warn("[glasshome-widgets] No widgets found in", n);
51
+ return;
52
+ }
53
+ if (e?.only) {
54
+ const o = new Set(e.only);
55
+ s = s.filter((u) => o.has(u.name));
56
+ }
57
+ e?.only || a(t) && x(t, { recursive: !0 }), a(t) || w(t, { recursive: !0 });
58
+ for (const o of s)
59
+ await i({
60
+ configFile: !1,
61
+ root: r,
62
+ plugins: e?.plugins ?? [],
63
+ ...e?.viteConfig,
64
+ build: {
65
+ lib: {
66
+ entry: o.entry,
67
+ formats: ["es"],
68
+ fileName: o.name
69
+ },
70
+ rollupOptions: {
71
+ external: P
72
+ },
73
+ outDir: t,
74
+ emptyOutDir: !1,
75
+ copyPublicDir: !1,
76
+ ...e?.viteConfig?.build
77
+ },
78
+ logLevel: "warn"
79
+ });
80
+ O(n, t);
81
+ }
82
+ function L(e) {
83
+ const i = e?.entry ?? "src/index.tsx";
84
+ return [{
85
+ name: "glasshome-widget:build",
86
+ apply: "build",
87
+ config() {
88
+ return {
89
+ build: {
90
+ lib: {
91
+ entry: i,
92
+ formats: ["es"],
93
+ fileName: "index"
94
+ },
95
+ rollupOptions: {
96
+ external: P
97
+ }
98
+ }
99
+ };
100
+ }
101
+ }, {
102
+ name: "glasshome-widget:dev",
103
+ apply: "serve",
104
+ enforce: "pre",
105
+ config() {
106
+ const t = f(), s = process.cwd(), o = l(s, "..", "..");
107
+ return {
108
+ server: {
109
+ fs: {
110
+ allow: [t, s, o]
111
+ }
112
+ }
113
+ };
114
+ },
115
+ configureServer(t) {
116
+ const s = f(), o = g(s, "preview.html");
117
+ t.middlewares.use(async (u, d, m) => {
118
+ if (u.url === "/" || u.url === "/index.html")
119
+ try {
120
+ let c = D(o, "utf-8");
121
+ c = await t.transformIndexHtml(u.url, c), d.setHeader("Content-Type", "text/html"), d.statusCode = 200, d.end(c);
122
+ } catch (c) {
123
+ m(c);
124
+ }
125
+ else
126
+ m();
127
+ });
128
+ },
129
+ resolveId(t) {
130
+ if (t === E) {
131
+ const s = f();
132
+ return y(g(s, "host.tsx"));
133
+ }
134
+ if (t === j)
135
+ return p;
136
+ },
137
+ load(t) {
138
+ if (t === p)
139
+ return `export { default } from "${y(l(process.cwd(), i))}";`;
140
+ }
141
+ }];
142
+ }
143
+ function k(e) {
144
+ const i = e?.srcDir ?? "src", r = e?.outDir ?? "dist";
145
+ let n = [];
146
+ return [{
147
+ name: "glasshome-widgets:build",
148
+ apply: "build",
149
+ config(s) {
150
+ return n = (s.plugins ?? []).flat().filter(Boolean).filter(
151
+ (o) => !o.name?.startsWith("glasshome-widgets:")
152
+ ), {
153
+ build: {
154
+ rollupOptions: {
155
+ input: { __glasshome_noop: "\0glasshome-noop" }
156
+ },
157
+ outDir: r,
158
+ copyPublicDir: !1
159
+ }
160
+ };
161
+ },
162
+ resolveId(s) {
163
+ if (s === "\0glasshome-noop") return s;
164
+ },
165
+ load(s) {
166
+ if (s === "\0glasshome-noop") return "export {}";
167
+ },
168
+ async closeBundle() {
169
+ await R({
170
+ srcDir: i,
171
+ outDir: r,
172
+ plugins: n
173
+ });
174
+ }
175
+ }];
176
+ }
177
+ export {
178
+ R as buildWidgets,
179
+ _ as discoverWidgets,
180
+ O as generateRegistry,
181
+ L as glasshomeWidget,
182
+ k as glasshomeWidgets,
183
+ P as isWidgetExternal
184
+ };
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@glasshome/widget-sdk",
3
+ "version": "0.1.0",
4
+ "description": "SDK for building GlassHome dashboard widgets with SolidJS",
5
+ "keywords": [
6
+ "glasshome",
7
+ "widget",
8
+ "sdk",
9
+ "solidjs",
10
+ "vite",
11
+ "home-assistant",
12
+ "dashboard",
13
+ "smart-home"
14
+ ],
15
+ "type": "module",
16
+ "sideEffects": false,
17
+ "main": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "@glasshome/source": "./src/index.ts",
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ },
25
+ "./vite": {
26
+ "@glasshome/source": "./src/vite/index.ts",
27
+ "types": "./dist/vite/index.d.ts",
28
+ "import": "./dist/vite/index.js"
29
+ },
30
+ "./tailwind-sources": "./tailwind-sources.css",
31
+ "./package.json": "./package.json"
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "preview",
36
+ "tailwind-sources.css"
37
+ ],
38
+ "scripts": {
39
+ "build": "vite build && tsc",
40
+ "dev": "vite build --watch"
41
+ },
42
+ "peerDependencies": {
43
+ "solid-js": "^1.9.11"
44
+ },
45
+ "dependencies": {
46
+ "@modular-forms/solid": "^0.25.1",
47
+ "@solid-primitives/resize-observer": "^2.1.5",
48
+ "clsx": "^2.1.1",
49
+ "tailwind-merge": "^3.5.0",
50
+ "zod": "^4.1.13"
51
+ },
52
+ "devDependencies": {
53
+ "@types/bun": "latest",
54
+ "@types/node": "^25.2.2",
55
+ "typescript": "^5",
56
+ "vite": "^7.3.1",
57
+ "vite-plugin-externalize-deps": "^0.8.0",
58
+ "vite-plugin-solid": "^2.11.10"
59
+ },
60
+ "repository": {
61
+ "type": "git",
62
+ "url": "https://github.com/glasshome/widget-sdk.git"
63
+ },
64
+ "homepage": "https://github.com/glasshome/widget-sdk#readme",
65
+ "bugs": {
66
+ "url": "https://github.com/glasshome/widget-sdk/issues"
67
+ },
68
+ "author": "GlassHome Labs",
69
+ "license": "MIT",
70
+ "publishConfig": {
71
+ "access": "public"
72
+ },
73
+ "engines": {
74
+ "node": ">=18"
75
+ }
76
+ }
@@ -0,0 +1,89 @@
1
+ import widget from "virtual:glasshome-widget";
2
+ import { createSignal } from "solid-js";
3
+ import { render } from "solid-js/web";
4
+
5
+ function PreviewHost() {
6
+ const [isDark, setIsDark] = createSignal(false);
7
+ const [config, setConfig] = createSignal(widget.manifest.defaultConfig || {});
8
+
9
+ const toggleDark = () => {
10
+ const next = !isDark();
11
+ setIsDark(next);
12
+ document.documentElement.classList.toggle("dark", next);
13
+ };
14
+
15
+ const WidgetComponent = widget.component;
16
+
17
+ return (
18
+ <div
19
+ style={{
20
+ "max-width": "600px",
21
+ margin: "0 auto",
22
+ padding: "24px",
23
+ }}
24
+ >
25
+ <header
26
+ style={{
27
+ display: "flex",
28
+ "align-items": "center",
29
+ "justify-content": "space-between",
30
+ "margin-bottom": "24px",
31
+ "padding-bottom": "16px",
32
+ "border-bottom": "1px solid var(--color-border)",
33
+ }}
34
+ >
35
+ <div>
36
+ <h1 style={{ "font-size": "1.5rem", "font-weight": "bold" }}>{widget.manifest.name}</h1>
37
+ <p
38
+ style={{
39
+ "font-size": "0.875rem",
40
+ opacity: 0.7,
41
+ "margin-top": "4px",
42
+ }}
43
+ >
44
+ &lt;{widget.manifest.tag}&gt; | {widget.manifest.type} | {widget.manifest.size}
45
+ </p>
46
+ </div>
47
+ <button
48
+ type="button"
49
+ onClick={toggleDark}
50
+ style={{
51
+ padding: "8px 16px",
52
+ "border-radius": "6px",
53
+ border: "1px solid var(--color-border)",
54
+ background: "transparent",
55
+ color: "var(--color-foreground)",
56
+ cursor: "pointer",
57
+ "font-size": "0.875rem",
58
+ }}
59
+ >
60
+ {isDark() ? "Light Mode" : "Dark Mode"}
61
+ </button>
62
+ </header>
63
+
64
+ <section
65
+ style={{
66
+ padding: "24px",
67
+ border: "1px solid var(--color-border)",
68
+ "border-radius": "8px",
69
+ background: "var(--color-background)",
70
+ }}
71
+ >
72
+ <WidgetComponent config={config()} />
73
+ </section>
74
+
75
+ <footer
76
+ style={{
77
+ "margin-top": "16px",
78
+ "font-size": "0.75rem",
79
+ opacity: 0.5,
80
+ "text-align": "center",
81
+ }}
82
+ >
83
+ GlassHome Widget Preview | SDK {widget.manifest.sdkVersion}
84
+ </footer>
85
+ </div>
86
+ );
87
+ }
88
+
89
+ render(() => <PreviewHost />, document.getElementById("root")!);
@@ -0,0 +1,49 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Widget Preview</title>
7
+ <style>
8
+ *,
9
+ *::before,
10
+ *::after {
11
+ box-sizing: border-box;
12
+ margin: 0;
13
+ padding: 0;
14
+ }
15
+
16
+ :root {
17
+ --color-primary: #3b82f6;
18
+ --color-background: #ffffff;
19
+ --color-foreground: #0f172a;
20
+ --color-border: #e2e8f0;
21
+ --color-success: #22c55e;
22
+ --color-error: #ef4444;
23
+ color-scheme: light;
24
+ }
25
+
26
+ .dark {
27
+ --color-primary: #60a5fa;
28
+ --color-background: #0f172a;
29
+ --color-foreground: #f1f5f9;
30
+ --color-border: #334155;
31
+ --color-success: #4ade80;
32
+ --color-error: #f87171;
33
+ color-scheme: dark;
34
+ }
35
+
36
+ body {
37
+ font-family: system-ui, -apple-system, sans-serif;
38
+ background: var(--color-background);
39
+ color: var(--color-foreground);
40
+ min-height: 100vh;
41
+ transition: background 0.2s, color 0.2s;
42
+ }
43
+ </style>
44
+ </head>
45
+ <body>
46
+ <div id="root"></div>
47
+ <script type="module" src="/@glasshome/preview"></script>
48
+ </body>
49
+ </html>
@@ -0,0 +1 @@
1
+ @source "./src";