@mp-consulting/homebridge-ui-kit 1.2.2 → 1.3.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.
@@ -0,0 +1,2 @@
1
+ /* @mp-consulting/homebridge-ui-kit v1.3.0 */
2
+ (()=>{(function(){try{var t=document.documentElement,e=null;try{e=localStorage.getItem("mp-kit-theme")}catch{}var a=e==="dark"||e!=="light"&&!!window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches;t.setAttribute("data-bs-theme",a?"dark":"light"),a?t.classList.add("mp-theme-dark"):t.classList.remove("mp-theme-dark")}catch{}})();})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mp-consulting/homebridge-ui-kit",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "description": "Brand design system for @mp-consulting Homebridge plugins",
5
5
  "keywords": [
6
6
  "homebridge",
@@ -15,14 +15,35 @@
15
15
  "author": "MP Consulting",
16
16
  "license": "MIT",
17
17
  "type": "module",
18
+ "types": "./dist/kit.d.mts",
19
+ "module": "./dist/kit.mjs",
18
20
  "repository": {
19
21
  "type": "git",
20
22
  "url": "git+https://github.com/mp-consulting/homebridge-ui-kit.git"
21
23
  },
22
24
  "exports": {
25
+ ".": {
26
+ "types": "./dist/kit.d.mts",
27
+ "import": "./dist/kit.mjs",
28
+ "default": "./dist/kit.mjs"
29
+ },
23
30
  "./kit.css": "./dist/kit.css",
24
- "./kit.js": "./dist/kit.js",
31
+ "./kit.min.css": "./dist/kit.min.css",
32
+ "./kit.js": {
33
+ "types": "./dist/kit.d.ts",
34
+ "default": "./dist/kit.js"
35
+ },
36
+ "./kit.min.js": {
37
+ "types": "./dist/kit.d.ts",
38
+ "default": "./dist/kit.min.js"
39
+ },
40
+ "./kit.mjs": {
41
+ "types": "./dist/kit.d.mts",
42
+ "default": "./dist/kit.mjs"
43
+ },
25
44
  "./ai.css": "./dist/ai.css",
45
+ "./ai.min.css": "./dist/ai.min.css",
46
+ "./theme-boot.js": "./dist/theme-boot.js",
26
47
  "./dist/*": "./dist/*",
27
48
  "./package.json": "./package.json"
28
49
  },
@@ -39,6 +60,21 @@
39
60
  "scripts": {
40
61
  "build": "node scripts/build.js",
41
62
  "build:check": "node scripts/build.js --check",
42
- "test": "node --test test/*.test.js"
63
+ "test": "node --test test/*.test.js",
64
+ "test:types": "tsc -p test/types",
65
+ "examples": "node scripts/copy-assets.js --vendor --dest examples/lib",
66
+ "serve": "node e2e/serve.js",
67
+ "test:visual": "playwright test",
68
+ "test:visual:update": "playwright test --update-snapshots",
69
+ "size": "node scripts/check-size.js"
70
+ },
71
+ "devDependencies": {
72
+ "@axe-core/playwright": "^4.13.0",
73
+ "@playwright/test": "^1.63.0",
74
+ "bootstrap": "^5.3.8",
75
+ "bootstrap-icons": "^1.13.1",
76
+ "esbuild": "^0.28.2",
77
+ "jsdom": "^27.4.0",
78
+ "typescript": "^7.0.2"
43
79
  }
44
80
  }
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
- // mp-ui-kit-copy — copies the UI kit's dist/ files (kit.css, kit.js, ai.css)
3
- // into a plugin's custom UI folder. Zero dependencies.
2
+ // mp-ui-kit-copy — copies the UI kit's browser files from dist/ (kit.css,
3
+ // kit.js, ai.css, their .min variants, theme-boot.js, …) into a plugin's
4
+ // custom UI folder. Zero dependencies.
4
5
  //
5
6
  // mp-ui-kit-copy → <cwd>/homebridge-ui/public/lib/
6
7
  // mp-ui-kit-copy --dest <dir> → <dir> (relative to the current directory)
8
+ // mp-ui-kit-copy --only a,b → only these dist files (e.g. kit.min.css,kit.min.js)
7
9
  // mp-ui-kit-copy --vendor → also Bootstrap / Bootstrap Icons from the
8
10
  // plugin's node_modules (lib/ + lib/fonts/)
9
11
 
@@ -40,6 +42,29 @@ const VENDOR = [
40
42
  // The plugin UI does not serve source maps, so drop the references to them.
41
43
  const SOURCE_MAP = /^\s*\/[*/]# sourceMappingURL=.*$/gm;
42
44
 
45
+ // Source maps and TypeScript declarations are not served to the browser.
46
+ const NOT_FOR_BROWSER = /\.(map|d\.ts|d\.mts)$/;
47
+
48
+ /** The dist/ files the CLI can copy. */
49
+ export function browserFiles() {
50
+ return readdirSync(distDir)
51
+ .filter(name => statSync(join(distDir, name)).isFile() && !NOT_FOR_BROWSER.test(name))
52
+ .sort();
53
+ }
54
+
55
+ const KIT_CSS = /(?:^|\/)kit(?:\.min)?\.css$/;
56
+ const AI_CSS = /(?:^|\/)ai(?:\.min)?\.css$/;
57
+ const STYLESHEET_HREF = /<link\b[^>]*\bhref\s*=\s*["']([^"']+)["']/gi;
58
+
59
+ /**
60
+ * True when an HTML document links both kit.css and ai.css (or their .min
61
+ * variants). kit.css already contains ai.css, so loading both duplicates it.
62
+ */
63
+ export function loadsKitAndAi(html) {
64
+ const hrefs = Array.from(String(html).matchAll(STYLESHEET_HREF), m => m[1].split(/[?#]/)[0]);
65
+ return hrefs.some(h => KIT_CSS.test(h)) && hrefs.some(h => AI_CSS.test(h));
66
+ }
67
+
43
68
  function findPackage(cwd, pkg) {
44
69
  try {
45
70
  const require = createRequire(join(cwd, 'package.json'));
@@ -54,16 +79,43 @@ function findPackage(cwd, pkg) {
54
79
  * Copies the kit into `dest` (default `<cwd>/homebridge-ui/public/lib`).
55
80
  * Returns the list of written file paths.
56
81
  */
57
- export function copyAssets({ cwd = process.cwd(), dest, vendor = false, log = () => {} } = {}) {
82
+ export function copyAssets({
83
+ cwd = process.cwd(), dest, vendor = false, only, log = () => {},
84
+ } = {}) {
58
85
  const target = resolve(cwd, dest || DEFAULT_DEST);
86
+ const available = browserFiles();
87
+ let names = available;
88
+ if (only && only.length) {
89
+ const unknown = only.filter(n => !available.includes(n));
90
+ if (unknown.length) {
91
+ throw new Error(`--only: unknown file ${unknown.join(', ')} (available: ${available.join(', ')})`);
92
+ }
93
+ names = available.filter(n => only.includes(n));
94
+ }
59
95
  mkdirSync(target, { recursive: true });
60
96
  const written = [];
61
97
 
62
- for (const name of readdirSync(distDir)) {
98
+ for (const name of names) {
63
99
  const from = join(distDir, name);
64
- if (!statSync(from).isFile()) { continue; }
65
- copyFileSync(from, join(target, name));
66
- written.push(join(target, name));
100
+ const to = join(target, name);
101
+ if (/\.(css|js|mjs)$/.test(name)) {
102
+ // The .map files are not copied, so drop the references to them.
103
+ writeFileSync(to, readFileSync(from, 'utf8').replace(SOURCE_MAP, ''));
104
+ } else {
105
+ copyFileSync(from, to);
106
+ }
107
+ written.push(to);
108
+ }
109
+
110
+ // kit.css already contains ai.css: warn when a page next to lib/ loads both.
111
+ const pageDir = dirname(target);
112
+ if (existsSync(pageDir)) {
113
+ for (const page of readdirSync(pageDir).filter(n => n.endsWith('.html'))) {
114
+ if (loadsKitAndAi(readFileSync(join(pageDir, page), 'utf8'))) {
115
+ log(`! ${relative(cwd, join(pageDir, page))} loads both kit.css and ai.css; `
116
+ + 'kit.css already includes ai.css, so load only one of them');
117
+ }
118
+ }
67
119
  }
68
120
 
69
121
  if (vendor) {
@@ -96,7 +148,11 @@ export function copyAssets({ cwd = process.cwd(), dest, vendor = false, log = ()
96
148
  }
97
149
 
98
150
  function parseArgs(argv) {
99
- const opts = { vendor: false, help: false };
151
+ const opts = { vendor: false, help: false, only: [] };
152
+ const addOnly = value => {
153
+ if (!value) { throw new Error('--only needs a comma-separated list of files'); }
154
+ opts.only.push(...value.split(',').map(v => v.trim()).filter(Boolean));
155
+ };
100
156
  for (let i = 0; i < argv.length; i += 1) {
101
157
  const arg = argv[i];
102
158
  if (arg === '--dest') {
@@ -105,6 +161,11 @@ function parseArgs(argv) {
105
161
  if (!opts.dest) { throw new Error('--dest needs a directory'); }
106
162
  } else if (arg.startsWith('--dest=')) {
107
163
  opts.dest = arg.slice('--dest='.length);
164
+ } else if (arg === '--only') {
165
+ addOnly(argv[i + 1]);
166
+ i += 1;
167
+ } else if (arg.startsWith('--only=')) {
168
+ addOnly(arg.slice('--only='.length));
108
169
  } else if (arg === '--vendor') {
109
170
  opts.vendor = true;
110
171
  } else if (arg === '--help' || arg === '-h') {
@@ -116,15 +177,20 @@ function parseArgs(argv) {
116
177
  return opts;
117
178
  }
118
179
 
119
- const USAGE = `Usage: mp-ui-kit-copy [--dest <dir>] [--vendor]
180
+ const USAGE = `Usage: mp-ui-kit-copy [--dest <dir>] [--only <files>] [--vendor]
181
+
182
+ Copies @mp-consulting/homebridge-ui-kit's browser files (kit.css, kit.js,
183
+ ai.css, their .min variants, theme-boot.js, …) into ${DEFAULT_DEST}/ of the
184
+ current directory.
120
185
 
121
- Copies @mp-consulting/homebridge-ui-kit's dist/ (kit.css, kit.js, ai.css)
122
- into ${DEFAULT_DEST}/ of the current directory.
186
+ --dest <dir> copy into <dir> instead (relative to the current directory)
187
+ --only <files> copy only these dist files, comma-separated
188
+ (e.g. --only kit.min.css,kit.min.js,theme-boot.js)
189
+ --vendor also copy bootstrap.min.css, bootstrap.bundle.min.js,
190
+ bootstrap-icons.min.css and fonts/ from your node_modules
191
+ -h, --help show this help
123
192
 
124
- --dest <dir> copy into <dir> instead (relative to the current directory)
125
- --vendor also copy bootstrap.min.css, bootstrap.bundle.min.js,
126
- bootstrap-icons.min.css and fonts/ from your node_modules
127
- -h, --help show this help`;
193
+ Note: kit.css already contains ai.css. Load one of them, never both.`;
128
194
 
129
195
  function isMain() {
130
196
  if (!process.argv[1]) { return false; }