diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index b81b8db9..4d6b66ad 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -26,6 +26,8 @@ jobs: build: name: Build Docs runs-on: ubuntu-latest + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true steps: - uses: actions/checkout@v4 with: @@ -33,7 +35,7 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '24' cache: 'npm' - name: Install dependencies @@ -56,6 +58,8 @@ jobs: needs: build if: github.event_name != 'pull_request' runs-on: ubuntu-latest + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc2c63c1..411b62cf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,6 +38,8 @@ jobs: release: name: Build and Release runs-on: ubuntu-latest + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true steps: # ── Checkout ───────────────────────────────────────────────────────────── @@ -50,7 +52,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "20.x" + node-version: "24" cache: "npm" - name: Install dependencies diff --git a/.github/workflows/validate-integration.yml b/.github/workflows/validate-integration.yml index ee0768c5..04422a80 100644 --- a/.github/workflows/validate-integration.yml +++ b/.github/workflows/validate-integration.yml @@ -13,6 +13,8 @@ jobs: validate-hassfest: name: Hassfest Validation runs-on: ubuntu-latest + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true steps: - uses: actions/checkout@v4 - uses: home-assistant/actions/hassfest@master @@ -20,6 +22,8 @@ jobs: validate-hacs: name: HACS Validation runs-on: ubuntu-latest + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true steps: - name: Checkout code uses: actions/checkout@v4 @@ -32,6 +36,8 @@ jobs: validate-build: name: Validate Build runs-on: ubuntu-latest + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true steps: - name: Checkout code uses: actions/checkout@v4 @@ -39,7 +45,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '20.x' + node-version: '24' cache: 'npm' - name: Install dependencies @@ -70,6 +76,8 @@ jobs: validate-version: name: Validate Version Format runs-on: ubuntu-latest + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true steps: - name: Checkout code uses: actions/checkout@v4 @@ -92,6 +100,8 @@ jobs: validate-structure: name: Validate Project Structure runs-on: ubuntu-latest + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/package-lock.json b/package-lock.json index 708e330e..eace26b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "lcards", - "version": "2026.03.28.2", + "version": "2026.03.29.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "lcards", - "version": "2026.03.28.2", + "version": "2026.03.29.1", "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.20.0", @@ -21,8 +21,8 @@ "@fsegurai/codemirror-theme-vscode-dark": "^6.2.3", "@replit/codemirror-indentation-markers": "^6.5.3", "animejs": "^4.3.6", - "apexcharts": "^3.54.1", - "custom-card-helpers": "^1.9.0", + "apexcharts": "^5.10.4", + "custom-card-helpers": "^2.0.0", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0", "lit": "^3.3.2" @@ -30,7 +30,7 @@ "devDependencies": { "inquirer": "^13.1.0", "js-yaml": "^4.1.0", - "jsdom": "^27.1.0", + "jsdom": "^29.0.1", "mermaid": "^11.13.0", "typescript": "^5.9.3", "vite": "^5.4.21", @@ -39,13 +39,6 @@ "vitepress-plugin-tabs": "^0.8.0" } }, - "node_modules/@acemir/cssom": { - "version": "0.9.23", - "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.23.tgz", - "integrity": "sha512-2kJ1HxBKzPLbmhZpxBiTZggjtgCwKg1ma5RHShxvd6zgqhDEdEkzpiwe7jLkI2p2BrZvFCXIihdoMkl1H39VnA==", - "dev": true, - "license": "MIT" - }, "node_modules/@algolia/abtesting": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.15.2.tgz", @@ -319,31 +312,37 @@ } }, "node_modules/@asamuzakjp/css-color": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz", - "integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.1.tgz", + "integrity": "sha512-iGWN8E45Ws0XWx3D44Q1t6vX2LqhCKcwfmwBYCDsFrYFS6m4q/Ks61L2veETaLv+ckDC6+dTETJoaAAb7VjLiw==", "dev": true, "license": "MIT", "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "lru-cache": "^11.2.1" + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.7" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/@asamuzakjp/dom-selector": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.4.tgz", - "integrity": "sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.4.tgz", + "integrity": "sha512-jXR6x4AcT3eIrS2fSNAwJpwirOkGcd+E7F7CP3zjdTqz9B/2huHOL8YJZBgekKwLML+u7qB/6P1LXQuMScsx0w==", "dev": true, "license": "MIT", "dependencies": { "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", - "css-tree": "^3.1.0", + "css-tree": "^3.2.1", "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.2" + "lru-cache": "^11.2.7" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/@asamuzakjp/nwsapi": { @@ -410,6 +409,19 @@ "dev": true, "license": "MIT" }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, "node_modules/@chevrotain/cst-dts-gen": { "version": "11.1.2", "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.2.tgz", @@ -455,9 +467,9 @@ "license": "Apache-2.0" }, "node_modules/@codemirror/autocomplete": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz", - "integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==", + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.1.tgz", + "integrity": "sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==", "license": "MIT", "dependencies": { "@codemirror/language": "^6.0.0", @@ -467,21 +479,21 @@ } }, "node_modules/@codemirror/commands": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.1.tgz", - "integrity": "sha512-uWDWFypNdQmz2y1LaNJzK7fL7TYKLeUAU0npEC685OKTF3KcQ2Vu3klIM78D7I6wGhktme0lh3CuQLv0ZCrD9Q==", + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.3.tgz", + "integrity": "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==", "license": "MIT", "dependencies": { "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.4.0", + "@codemirror/state": "^6.6.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "node_modules/@codemirror/lang-yaml": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz", - "integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.3.tgz", + "integrity": "sha512-AZ8DJBuXGVHybpBQhmZtgew5//4hv3tdkXnr3vDmOUMJRuB6vn/uuwtmTOTlqEaQFg3hQSVeA90NmvIQyUV6FQ==", "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.0.0", @@ -494,23 +506,23 @@ } }, "node_modules/@codemirror/language": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz", - "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==", + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.3.tgz", + "integrity": "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", - "@lezer/common": "^1.1.0", + "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "node_modules/@codemirror/lint": { - "version": "6.9.2", - "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.2.tgz", - "integrity": "sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ==", + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.5.tgz", + "integrity": "sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", @@ -530,9 +542,9 @@ } }, "node_modules/@codemirror/state": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", - "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz", + "integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==", "license": "MIT", "dependencies": { "@marijn/find-cluster-break": "^1.0.0" @@ -551,21 +563,21 @@ } }, "node_modules/@codemirror/view": { - "version": "6.39.4", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.4.tgz", - "integrity": "sha512-xMF6OfEAUVY5Waega4juo1QGACfNkNF+aJLqpd8oUJz96ms2zbfQ9Gh35/tI3y8akEV31FruKfj7hBnIU/nkqA==", + "version": "6.40.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.40.0.tgz", + "integrity": "sha512-WA0zdU7xfF10+5I3HhUUq3kqOx3KjqmtQ9lqZjfK7jtYk4G72YW9rezcSywpaUMCWOMlq+6E0pO1IWg1TNIhtg==", "license": "MIT", "dependencies": { - "@codemirror/state": "^6.5.0", + "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "node_modules/@csstools/color-helpers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", "dev": true, "funding": [ { @@ -579,13 +591,13 @@ ], "license": "MIT-0", "engines": { - "node": ">=18" + "node": ">=20.19.0" } }, "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", "dev": true, "funding": [ { @@ -599,17 +611,17 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-color-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", "dev": true, "funding": [ { @@ -623,21 +635,21 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^5.1.0", - "@csstools/css-calc": "^2.1.4" + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", "dev": true, "funding": [ { @@ -651,16 +663,16 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.15.tgz", - "integrity": "sha512-q0p6zkVq2lJnmzZVPR33doA51G7YOja+FBvRdp5ISIthL0MtFCgYHHhR563z9WFGxcOn0WfjSkPDJ5Qig3H3Sw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.2.tgz", + "integrity": "sha512-5GkLzz4prTIpoyeUiIu3iV6CSG3Plo7xRVOFPKI7FVEJ3mZ0A8SwK0XU3Gl7xAkiQ+mDyam+NNp875/C5y+jSA==", "dev": true, "funding": [ { @@ -673,14 +685,19 @@ } ], "license": "MIT-0", - "engines": { - "node": ">=18" + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } } }, "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", "dev": true, "funding": [ { @@ -694,7 +711,7 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" } }, "node_modules/@docsearch/css": { @@ -1139,53 +1156,73 @@ "node": ">=12" } }, - "node_modules/@formatjs/ecma402-abstract": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz", - "integrity": "sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==", + "node_modules/@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "dev": true, "license": "MIT", - "dependencies": { - "@formatjs/intl-localematcher": "0.2.25", - "tslib": "^2.1.0" + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } } }, - "node_modules/@formatjs/fast-memoize": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.1.tgz", - "integrity": "sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==", + "node_modules/@formatjs/bigdecimal": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/bigdecimal/-/bigdecimal-0.2.0.tgz", + "integrity": "sha512-GeaxHZbUoYvHL9tC5eltHLs+1zU70aPw0s7LwqgktIzF5oMhNY4o4deEtusJMsq7WFJF3Ye2zQEzdG8beVk73w==", + "license": "MIT" + }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-3.2.0.tgz", + "integrity": "sha512-dHnqHgBo6GXYGRsepaE1wmsC2etaivOWd5VaJstZd+HI2zR3DCUjbDVZRtoPGkkXZmyHvBwrdEUuqfvzhF/DtQ==", "license": "MIT", "dependencies": { - "tslib": "^2.1.0" + "@formatjs/bigdecimal": "0.2.0", + "@formatjs/fast-memoize": "3.1.1", + "@formatjs/intl-localematcher": "0.8.2" } }, + "node_modules/@formatjs/fast-memoize": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.1.tgz", + "integrity": "sha512-CbNbf+tlJn1baRnPkNePnBqTLxGliG6DDgNa/UtV66abwIjwsliPMOt0172tzxABYzSuxZBZfcp//qI8AvBWPg==", + "license": "MIT" + }, "node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.0.tgz", - "integrity": "sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.3.tgz", + "integrity": "sha512-HJWZ9S6JWey6iY5+YXE3Kd0ofWU1sC2KTTp56e1168g/xxWvVvr8k9G4fexIgwYV9wbtjY7kGYK5FjoWB3B2OQ==", "license": "MIT", "dependencies": { - "@formatjs/ecma402-abstract": "1.11.4", - "@formatjs/icu-skeleton-parser": "1.3.6", - "tslib": "^2.1.0" + "@formatjs/ecma402-abstract": "3.2.0", + "@formatjs/icu-skeleton-parser": "2.1.3" } }, "node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.6.tgz", - "integrity": "sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.3.tgz", + "integrity": "sha512-9mFp8TJ166ZM2pcjKwsBWXrDnOJGT7vMEScVgLygUODPOsE8S6f/FHoacvrlHK1B4dYZk8vSCNruyPU64AfgJQ==", "license": "MIT", "dependencies": { - "@formatjs/ecma402-abstract": "1.11.4", - "tslib": "^2.1.0" + "@formatjs/ecma402-abstract": "3.2.0" } }, "node_modules/@formatjs/intl-localematcher": { - "version": "0.2.25", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz", - "integrity": "sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.2.tgz", + "integrity": "sha512-q05KMYGJLyqFNFtIb8NhWLF5X3aK/k0wYt7dnRFuy6aLQL+vUwQ1cg5cO4qawEiINybeCPXAWlprY2mSBjSXAQ==", "license": "MIT", "dependencies": { - "tslib": "^2.1.0" + "@formatjs/fast-memoize": "3.1.1" } }, "node_modules/@formatjs/intl-utils": { @@ -1199,9 +1236,9 @@ } }, "node_modules/@fsegurai/codemirror-theme-vscode-dark": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/@fsegurai/codemirror-theme-vscode-dark/-/codemirror-theme-vscode-dark-6.2.3.tgz", - "integrity": "sha512-702b/C3fdTQEfAXiXNewHuvyht83volDFQPR7/uF3S+F6xJ94nUHh4xaffLEFZskvuNVQErX477mq/hnYRdRbA==", + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@fsegurai/codemirror-theme-vscode-dark/-/codemirror-theme-vscode-dark-6.2.4.tgz", + "integrity": "sha512-Vjr1658LIYgvC3ALVeqMhSMLA1ljoLmQpdAq8JuFdH8HkENfCXPmLX498Lnab3UYyn/b3qZAbHtAF+wQ64iMkg==", "license": "MIT", "peerDependencies": { "@codemirror/language": "^6.0.0", @@ -1240,9 +1277,9 @@ } }, "node_modules/@inquirer/ansi": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.2.tgz", - "integrity": "sha512-SYLX05PwJVnW+WVegZt1T4Ip1qba1ik+pNJPDiqvk6zS5Y/i8PhRzLpGEtVd7sW0G8cMtkD8t4AZYhQwm8vnww==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.4.tgz", + "integrity": "sha512-DpcZrQObd7S0R/U3bFdkcT5ebRwbTTC4D3tCc1vsJizmgPLxNJBo+AAFmrZwe8zk30P2QzgzGWZ3Q9uJwWuhIg==", "dev": true, "license": "MIT", "engines": { @@ -1250,16 +1287,16 @@ } }, "node_modules/@inquirer/checkbox": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.0.3.tgz", - "integrity": "sha512-xtQP2eXMFlOcAhZ4ReKP2KZvDIBb1AnCfZ81wWXG3DXLVH0f0g4obE0XDPH+ukAEMRcZT0kdX2AS1jrWGXbpxw==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.1.2.tgz", + "integrity": "sha512-PubpMPO2nJgMufkoB3P2wwxNXEMUXnBIKi/ACzDUYfaoPuM7gSTmuxJeMscoLVEsR4qqrCMf5p0SiYGWnVJ8kw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.2", - "@inquirer/core": "^11.1.0", - "@inquirer/figures": "^2.0.2", - "@inquirer/type": "^4.0.2" + "@inquirer/ansi": "^2.0.4", + "@inquirer/core": "^11.1.7", + "@inquirer/figures": "^2.0.4", + "@inquirer/type": "^4.0.4" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -1274,14 +1311,14 @@ } }, "node_modules/@inquirer/confirm": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.0.3.tgz", - "integrity": "sha512-lyEvibDFL+NA5R4xl8FUmNhmu81B+LDL9L/MpKkZlQDJZXzG8InxiqYxiAlQYa9cqLLhYqKLQwZqXmSTqCLjyw==", + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.0.10.tgz", + "integrity": "sha512-tiNyA73pgpQ0FQ7axqtoLUe4GDYjNCDcVsbgcA5anvwg2z6i+suEngLKKJrWKJolT//GFPZHwN30binDIHgSgQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.0", - "@inquirer/type": "^4.0.2" + "@inquirer/core": "^11.1.7", + "@inquirer/type": "^4.0.4" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -1296,19 +1333,19 @@ } }, "node_modules/@inquirer/core": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.1.0.tgz", - "integrity": "sha512-+jD/34T1pK8M5QmZD/ENhOfXdl9Zr+BrQAUc5h2anWgi7gggRq15ZbiBeLoObj0TLbdgW7TAIQRU2boMc9uOKQ==", + "version": "11.1.7", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.1.7.tgz", + "integrity": "sha512-1BiBNDk9btIwYIzNZpkikIHXWeNzNncJePPqwDyVMhXhD1ebqbpn1mKGctpoqAbzywZfdG0O4tvmsGIcOevAPQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.2", - "@inquirer/figures": "^2.0.2", - "@inquirer/type": "^4.0.2", + "@inquirer/ansi": "^2.0.4", + "@inquirer/figures": "^2.0.4", + "@inquirer/type": "^4.0.4", "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", - "signal-exit": "^4.1.0", - "wrap-ansi": "^9.0.2" + "signal-exit": "^4.1.0" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -1322,59 +1359,16 @@ } } }, - "node_modules/@inquirer/core/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@inquirer/core/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@inquirer/core/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/@inquirer/editor": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.0.3.tgz", - "integrity": "sha512-wYyQo96TsAqIciP/r5D3cFeV8h4WqKQ/YOvTg5yOfP2sqEbVVpbxPpfV3LM5D0EP4zUI3EZVHyIUIllnoIa8OQ==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.0.10.tgz", + "integrity": "sha512-VJx4XyaKea7t8hEApTw5dxeIyMtWXre2OiyJcICCRZI4hkoHsMoCnl/KbUnJJExLbH9csLLHMVR144ZhFE1CwA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.0", - "@inquirer/external-editor": "^2.0.2", - "@inquirer/type": "^4.0.2" + "@inquirer/core": "^11.1.7", + "@inquirer/external-editor": "^2.0.4", + "@inquirer/type": "^4.0.4" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -1389,14 +1383,14 @@ } }, "node_modules/@inquirer/expand": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.0.3.tgz", - "integrity": "sha512-2oINvuL27ujjxd95f6K2K909uZOU2x1WiAl7Wb1X/xOtL8CgQ1kSxzykIr7u4xTkXkXOAkCuF45T588/YKee7w==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.0.10.tgz", + "integrity": "sha512-fC0UHJPXsTRvY2fObiwuQYaAnHrp3aDqfwKUJSdfpgv18QUG054ezGbaRNStk/BKD5IPijeMKWej8VV8O5Q/eQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.0", - "@inquirer/type": "^4.0.2" + "@inquirer/core": "^11.1.7", + "@inquirer/type": "^4.0.4" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -1411,14 +1405,14 @@ } }, "node_modules/@inquirer/external-editor": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-2.0.2.tgz", - "integrity": "sha512-X/fMXK7vXomRWEex1j8mnj7s1mpnTeP4CO/h2gysJhHLT2WjBnLv4ZQEGpm/kcYI8QfLZ2fgW+9kTKD+jeopLg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-2.0.4.tgz", + "integrity": "sha512-Prenuv9C1PHj2Itx0BcAOVBTonz02Hc2Nd2DbU67PdGUaqn0nPCnV34oDyyoaZHnmfRxkpuhh/u51ThkrO+RdA==", "dev": true, "license": "MIT", "dependencies": { "chardet": "^2.1.1", - "iconv-lite": "^0.7.0" + "iconv-lite": "^0.7.2" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -1432,27 +1426,10 @@ } } }, - "node_modules/@inquirer/external-editor/node_modules/iconv-lite": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", - "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/@inquirer/figures": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.2.tgz", - "integrity": "sha512-qXm6EVvQx/FmnSrCWCIGtMHwqeLgxABP8XgcaAoywsL0NFga9gD5kfG0gXiv80GjK9Hsoz4pgGwF/+CjygyV9A==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.4.tgz", + "integrity": "sha512-eLBsjlS7rPS3WEhmOmh1znQ5IsQrxWzxWDxO51e4urv+iVrSnIHbq4zqJIOiyNdYLa+BVjwOtdetcQx1lWPpiQ==", "dev": true, "license": "MIT", "engines": { @@ -1460,14 +1437,14 @@ } }, "node_modules/@inquirer/input": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.0.3.tgz", - "integrity": "sha512-4R0TdWl53dtp79Vs6Df2OHAtA2FVNqya1hND1f5wjHWxZJxwDMSNB1X5ADZJSsQKYAJ5JHCTO+GpJZ42mK0Otw==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.0.10.tgz", + "integrity": "sha512-nvZ6qEVeX/zVtZ1dY2hTGDQpVGD3R7MYPLODPgKO8Y+RAqxkrP3i/3NwF3fZpLdaMiNuK0z2NaYIx9tPwiSegQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.0", - "@inquirer/type": "^4.0.2" + "@inquirer/core": "^11.1.7", + "@inquirer/type": "^4.0.4" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -1482,14 +1459,14 @@ } }, "node_modules/@inquirer/number": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.0.3.tgz", - "integrity": "sha512-TjQLe93GGo5snRlu83JxE38ZPqj5ZVggL+QqqAF2oBA5JOJoxx25GG3EGH/XN/Os5WOmKfO8iLVdCXQxXRZIMQ==", + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.0.10.tgz", + "integrity": "sha512-Ht8OQstxiS3APMGjHV0aYAjRAysidWdwurWEo2i8yI5xbhOBWqizT0+MU1S2GCcuhIBg+3SgWVjEoXgfhY+XaA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.0", - "@inquirer/type": "^4.0.2" + "@inquirer/core": "^11.1.7", + "@inquirer/type": "^4.0.4" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -1504,15 +1481,15 @@ } }, "node_modules/@inquirer/password": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.0.3.tgz", - "integrity": "sha512-rCozGbUMAHedTeYWEN8sgZH4lRCdgG/WinFkit6ZPsp8JaNg2T0g3QslPBS5XbpORyKP/I+xyBO81kFEvhBmjA==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.0.10.tgz", + "integrity": "sha512-QbNyvIE8q2GTqKLYSsA8ATG+eETo+m31DSR0+AU7x3d2FhaTWzqQek80dj3JGTo743kQc6mhBR0erMjYw5jQ0A==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.2", - "@inquirer/core": "^11.1.0", - "@inquirer/type": "^4.0.2" + "@inquirer/ansi": "^2.0.4", + "@inquirer/core": "^11.1.7", + "@inquirer/type": "^4.0.4" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -1527,22 +1504,22 @@ } }, "node_modules/@inquirer/prompts": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.1.0.tgz", - "integrity": "sha512-LsZMdKcmRNF5LyTRuZE5nWeOjganzmN3zwbtNfcs6GPh3I2TsTtF1UYZlbxVfhxd+EuUqLGs/Lm3Xt4v6Az1wA==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.3.2.tgz", + "integrity": "sha512-yFroiSj2iiBFlm59amdTvAcQFvWS6ph5oKESls/uqPBect7rTU2GbjyZO2DqxMGuIwVA8z0P4K6ViPcd/cp+0w==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/checkbox": "^5.0.3", - "@inquirer/confirm": "^6.0.3", - "@inquirer/editor": "^5.0.3", - "@inquirer/expand": "^5.0.3", - "@inquirer/input": "^5.0.3", - "@inquirer/number": "^4.0.3", - "@inquirer/password": "^5.0.3", - "@inquirer/rawlist": "^5.1.0", - "@inquirer/search": "^4.0.3", - "@inquirer/select": "^5.0.3" + "@inquirer/checkbox": "^5.1.2", + "@inquirer/confirm": "^6.0.10", + "@inquirer/editor": "^5.0.10", + "@inquirer/expand": "^5.0.10", + "@inquirer/input": "^5.0.10", + "@inquirer/number": "^4.0.10", + "@inquirer/password": "^5.0.10", + "@inquirer/rawlist": "^5.2.6", + "@inquirer/search": "^4.1.6", + "@inquirer/select": "^5.1.2" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -1557,14 +1534,14 @@ } }, "node_modules/@inquirer/rawlist": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.1.0.tgz", - "integrity": "sha512-yUCuVh0jW026Gr2tZlG3kHignxcrLKDR3KBp+eUgNz+BAdSeZk0e18yt2gyBr+giYhj/WSIHCmPDOgp1mT2niQ==", + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.2.6.tgz", + "integrity": "sha512-jfw0MLJ5TilNsa9zlJ6nmRM0ZFVZhhTICt4/6CU2Dv1ndY7l3sqqo1gIYZyMMDw0LvE1u1nzJNisfHEhJIxq5w==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.0", - "@inquirer/type": "^4.0.2" + "@inquirer/core": "^11.1.7", + "@inquirer/type": "^4.0.4" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -1579,15 +1556,15 @@ } }, "node_modules/@inquirer/search": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.0.3.tgz", - "integrity": "sha512-lzqVw0YwuKYetk5VwJ81Ba+dyVlhseHPx9YnRKQgwXdFS0kEavCz2gngnNhnMIxg8+j1N/rUl1t5s1npwa7bqg==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.1.6.tgz", + "integrity": "sha512-3/6kTRae98hhDevENScy7cdFEuURnSpM3JbBNg8yfXLw88HgTOl+neUuy/l9W0No5NzGsLVydhBzTIxZP7yChQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.0", - "@inquirer/figures": "^2.0.2", - "@inquirer/type": "^4.0.2" + "@inquirer/core": "^11.1.7", + "@inquirer/figures": "^2.0.4", + "@inquirer/type": "^4.0.4" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -1602,16 +1579,16 @@ } }, "node_modules/@inquirer/select": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.0.3.tgz", - "integrity": "sha512-M+ynbwS0ecQFDYMFrQrybA0qL8DV0snpc4kKevCCNaTpfghsRowRY7SlQBeIYNzHqXtiiz4RG9vTOeb/udew7w==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.1.2.tgz", + "integrity": "sha512-kTK8YIkHV+f02y7bWCh7E0u2/11lul5WepVTclr3UMBtBr05PgcZNWfMa7FY57ihpQFQH/spLMHTcr0rXy50tA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.2", - "@inquirer/core": "^11.1.0", - "@inquirer/figures": "^2.0.2", - "@inquirer/type": "^4.0.2" + "@inquirer/ansi": "^2.0.4", + "@inquirer/core": "^11.1.7", + "@inquirer/figures": "^2.0.4", + "@inquirer/type": "^4.0.4" }, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" @@ -1626,9 +1603,9 @@ } }, "node_modules/@inquirer/type": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.2.tgz", - "integrity": "sha512-cae7mzluplsjSdgFA6ACLygb5jC8alO0UUnFPyu0E7tNRPrL+q/f8VcSXp+cjZQ7l5CMpDpi2G1+IQvkOiL1Lw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.4.tgz", + "integrity": "sha512-PamArxO3cFJZoOzspzo6cxVlLeIftyBsZw/S9bKY5DzxqJVZgjoj1oP8d0rskKtp7sZxBycsoer1g6UeJV1BBA==", "dev": true, "license": "MIT", "engines": { @@ -1651,9 +1628,9 @@ "license": "MIT" }, "node_modules/@lezer/common": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.4.0.tgz", - "integrity": "sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.1.tgz", + "integrity": "sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==", "license": "MIT" }, "node_modules/@lezer/highlight": { @@ -2848,12 +2825,6 @@ "url": "https://github.com/sponsors/antfu" } }, - "node_modules/@yr/monotone-cubic-spline": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", - "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==", - "license": "MIT" - }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -2867,16 +2838,6 @@ "node": ">=0.4.0" } }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/algoliasearch": { "version": "5.49.2", "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.49.2.tgz", @@ -2913,46 +2874,11 @@ "url": "https://github.com/sponsors/juliangarnier" } }, - "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/apexcharts": { - "version": "3.54.1", - "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.54.1.tgz", - "integrity": "sha512-E4et0h/J1U3r3EwS/WlqJCQIbepKbp6wGUmaAwJOMjHUP4Ci0gxanLa7FR3okx6p9coi4st6J853/Cb1NP0vpA==", - "license": "MIT", - "dependencies": { - "@yr/monotone-cubic-spline": "^1.0.3", - "svg.draggable.js": "^2.2.2", - "svg.easing.js": "^2.0.0", - "svg.filter.js": "^2.0.2", - "svg.pathmorphing.js": "^0.1.3", - "svg.resize.js": "^1.4.3", - "svg.select.js": "^3.0.1" - } + "version": "5.10.4", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-5.10.4.tgz", + "integrity": "sha512-gt0VUqZ2+mr25ScbUcKZgJr96jKYm4vjOcxEWCEh/E5F4dWqhyo3dBhPRvNNnkKiWxkMd2cBwj3ZYH3rK39fkA==", + "license": "SEE LICENSE IN LICENSE" }, "node_modules/argparse": { "version": "2.0.1", @@ -3110,34 +3036,19 @@ "license": "MIT" }, "node_modules/css-tree": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", - "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "dev": true, "license": "MIT", "dependencies": { - "mdn-data": "2.12.2", - "source-map-js": "^1.0.1" + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, - "node_modules/cssstyle": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.3.tgz", - "integrity": "sha512-OytmFH+13/QXONJcC75QNdMtKpceNk3u8ThBjyyYjkEcy/ekBwR1mMAuNvi3gdBPW3N5TlCzQ0WZw8H0lN/bDw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@asamuzakjp/css-color": "^4.0.3", - "@csstools/css-syntax-patches-for-csstree": "^1.0.14", - "css-tree": "^3.1.0" - }, - "engines": { - "node": ">=20" - } - }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -3146,71 +3057,22 @@ "license": "MIT" }, "node_modules/custom-card-helpers": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/custom-card-helpers/-/custom-card-helpers-1.9.0.tgz", - "integrity": "sha512-5IW4OXq3MiiCqDvqeu+MYsM1NmntKW1WfJhyJFsdP2tbzqEI4BOnqRz2qzdp08lE4QLVhYfRLwe0WAqgQVNeFg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/custom-card-helpers/-/custom-card-helpers-2.0.0.tgz", + "integrity": "sha512-TrWc9lybHFrbdZFv1kzKaiFY/vYon08N1k4iUS8EDLIJObWP0K+B1lU0XCAG6Q/3euAyM5nn1QjUSvcnCYpNtg==", "license": "MIT", "dependencies": { "@formatjs/intl-utils": "^3.8.4", - "home-assistant-js-websocket": "^6.0.1", - "intl-messageformat": "^9.11.1", - "lit": "^2.1.1", - "rollup": "^2.63.0", - "superstruct": "^0.15.3", - "typescript": "^4.5.4" - } - }, - "node_modules/custom-card-helpers/node_modules/@lit/reactive-element": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz", - "integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==", - "license": "BSD-3-Clause", - "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.0.0" - } - }, - "node_modules/custom-card-helpers/node_modules/lit": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz", - "integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==", - "license": "BSD-3-Clause", - "dependencies": { - "@lit/reactive-element": "^1.6.0", - "lit-element": "^3.3.0", - "lit-html": "^2.8.0" - } - }, - "node_modules/custom-card-helpers/node_modules/lit-element": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz", - "integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==", - "license": "BSD-3-Clause", - "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.1.0", - "@lit/reactive-element": "^1.3.0", - "lit-html": "^2.8.0" - } - }, - "node_modules/custom-card-helpers/node_modules/lit-html": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz", - "integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==", - "license": "BSD-3-Clause", - "dependencies": { - "@types/trusted-types": "^2.0.2" - } - }, - "node_modules/custom-card-helpers/node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "home-assistant-js-websocket": "^9.6.0", + "intl-messageformat": "^11.1.2", + "superstruct": "^2.0.2" }, "engines": { - "node": ">=4.2.0" + "node": ">=24", + "npm": ">=11" + }, + "peerDependencies": { + "lit": ">=3.0.0" } }, "node_modules/cytoscape": { @@ -3769,17 +3631,17 @@ } }, "node_modules/data-urls": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", - "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", "dev": true, "license": "MIT", "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^15.0.0" + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" }, "engines": { - "node": ">=20" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/dayjs": { @@ -3915,6 +3777,33 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.0.tgz", + "integrity": "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, "node_modules/focus-trap": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz", @@ -3929,6 +3818,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -3939,19 +3829,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/hachure-fill": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", @@ -3998,9 +3875,9 @@ } }, "node_modules/home-assistant-js-websocket": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/home-assistant-js-websocket/-/home-assistant-js-websocket-6.1.1.tgz", - "integrity": "sha512-TnZFzF4mn5F/v0XKUTK2GMQXrn/+eQpgaSDSELl6U0HSwSbFwRhGWLz330YT+hiKMspDflamsye//RPL+zwhDw==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/home-assistant-js-websocket/-/home-assistant-js-websocket-9.6.0.tgz", + "integrity": "sha512-TK8HWW6UjCsUdjIBQRB2sBbEya0VniOBFqFjgqSznJiB7YriNgN8jghyThxOkgxwrnt2eN6oWoZMCTSp1o7H+w==", "license": "Apache-2.0" }, "node_modules/hookable": { @@ -4011,16 +3888,16 @@ "license": "MIT" }, "node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", "dev": true, "license": "MIT", "dependencies": { - "whatwg-encoding": "^3.1.1" + "@exodus/bytes": "^1.6.0" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/html-void-elements": { @@ -4034,95 +3911,34 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">=6.0" + "node": ">=0.10.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/inquirer": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-13.1.0.tgz", - "integrity": "sha512-4vv4GS/9HLnn0radvmHlXUXiNkd2gYCBQ4U1rxZWBJDisu2Z06bzUM9CFU8pcu1vwuAQjo6O+CFiqCYNsEi6qQ==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-13.3.2.tgz", + "integrity": "sha512-bh/OjBGxNR9qvfQj1n5bxtIF58mbOTp2InN5dKuwUK03dXcDGFsjlDinQRuXMZ4EGiJaFieUWHCAaxH2p7iUBw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.2", - "@inquirer/core": "^11.1.0", - "@inquirer/prompts": "^8.1.0", - "@inquirer/type": "^4.0.2", + "@inquirer/ansi": "^2.0.4", + "@inquirer/core": "^11.1.7", + "@inquirer/prompts": "^8.3.2", + "@inquirer/type": "^4.0.4", "mute-stream": "^3.0.0", "run-async": "^4.0.6", "rxjs": "^7.8.2" @@ -4150,15 +3966,14 @@ } }, "node_modules/intl-messageformat": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-9.13.0.tgz", - "integrity": "sha512-7sGC7QnSQGa5LZP7bXLDhVDtQOeKGeBFGHF2Y8LVBwYZoQZCgWeKoPGTa5GMG8g/TzDgeXuYJQis7Ggiw2xTOw==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.0.tgz", + "integrity": "sha512-IhghAA8n4KSlXuWKzYsWyWb82JoYTzShfyvdSF85oJPnNOjvv4kAo7S7Jtkm3/vJ53C7dQNRO+Gpnj3iWgTjBQ==", "license": "BSD-3-Clause", "dependencies": { - "@formatjs/ecma402-abstract": "1.11.4", - "@formatjs/fast-memoize": "1.2.1", - "@formatjs/icu-messageformat-parser": "2.1.0", - "tslib": "^2.1.0" + "@formatjs/ecma402-abstract": "3.2.0", + "@formatjs/fast-memoize": "3.1.1", + "@formatjs/icu-messageformat-parser": "3.5.3" } }, "node_modules/is-potential-custom-element-name": { @@ -4195,35 +4010,36 @@ } }, "node_modules/jsdom": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.1.0.tgz", - "integrity": "sha512-Pcfm3eZ+eO4JdZCXthW9tCDT3nF4K+9dmeZ+5X39n+Kqz0DDIABRP5CAEOHRFZk8RGuC2efksTJxrjp8EXCunQ==", + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.1.tgz", + "integrity": "sha512-z6JOK5gRO7aMybVq/y/MlIpKh8JIi68FBKMUtKkK2KH/wMSRlCxQ682d08LB9fYXplyY/UXG8P4XXTScmdjApg==", "dev": true, "license": "MIT", "dependencies": { - "@acemir/cssom": "^0.9.19", - "@asamuzakjp/dom-selector": "^6.7.3", - "cssstyle": "^5.3.2", - "data-urls": "^6.0.0", + "@asamuzakjp/css-color": "^5.0.1", + "@asamuzakjp/dom-selector": "^7.0.3", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.1", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", "decimal.js": "^10.6.0", - "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.6", + "html-encoding-sniffer": "^6.0.0", "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.7", "parse5": "^8.0.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^6.0.0", + "tough-cookie": "^6.0.1", + "undici": "^7.24.5", "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^8.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^15.1.0", - "ws": "^8.18.3", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", "xml-name-validator": "^5.0.0" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" }, "peerDependencies": { "canvas": "^3.0.0" @@ -4234,28 +4050,6 @@ } } }, - "node_modules/jsdom/node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/katex": { "version": "0.16.38", "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.38.tgz", @@ -4353,11 +4147,11 @@ "license": "MIT" }, "node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } @@ -4415,9 +4209,9 @@ } }, "node_modules/mdn-data": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", - "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", "dev": true, "license": "CC0-1.0" }, @@ -4825,21 +4619,6 @@ "dev": true, "license": "Unlicense" }, - "node_modules/rollup": { - "version": "2.80.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz", - "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", - "license": "MIT", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/roughjs": { "version": "4.6.6", "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", @@ -4984,22 +4763,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/style-mod": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", @@ -5027,100 +4790,12 @@ } }, "node_modules/superstruct": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.15.5.tgz", - "integrity": "sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ==", - "license": "MIT" - }, - "node_modules/svg.draggable.js": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", - "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", - "license": "MIT", - "dependencies": { - "svg.js": "^2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/svg.easing.js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz", - "integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==", - "license": "MIT", - "dependencies": { - "svg.js": ">=2.3.x" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/svg.filter.js": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz", - "integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==", - "license": "MIT", - "dependencies": { - "svg.js": "^2.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/svg.js": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", - "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==", - "license": "MIT" - }, - "node_modules/svg.pathmorphing.js": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz", - "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==", - "license": "MIT", - "dependencies": { - "svg.js": "^2.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/svg.resize.js": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", - "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", - "license": "MIT", - "dependencies": { - "svg.js": "^2.6.5", - "svg.select.js": "^2.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/svg.resize.js/node_modules/svg.select.js": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", - "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", - "license": "MIT", - "dependencies": { - "svg.js": "^2.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/svg.select.js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", - "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz", + "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==", "license": "MIT", - "dependencies": { - "svg.js": "^2.6.5" - }, "engines": { - "node": ">= 0.8.0" + "node": ">=14.0.0" } }, "node_modules/symbol-tree": { @@ -5148,29 +4823,29 @@ } }, "node_modules/tldts": { - "version": "7.0.17", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.17.tgz", - "integrity": "sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==", + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.27.tgz", + "integrity": "sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.0.17" + "tldts-core": "^7.0.27" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.0.17", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.17.tgz", - "integrity": "sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==", + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.27.tgz", + "integrity": "sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg==", "dev": true, "license": "MIT" }, "node_modules/tough-cookie": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", - "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -5218,6 +4893,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, "license": "0BSD" }, "node_modules/typescript": { @@ -5241,6 +4917,16 @@ "dev": true, "license": "MIT" }, + "node_modules/undici": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.6.tgz", + "integrity": "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/unist-util-is": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", @@ -5617,63 +5303,38 @@ } }, "node_modules/webidl-conversions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", - "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=20" } }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/whatwg-url": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", - "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", "dev": true, "license": "MIT", "dependencies": { + "@exodus/bytes": "^1.11.0", "tr46": "^6.0.0", - "webidl-conversions": "^8.0.0" + "webidl-conversions": "^8.0.1" }, "engines": { - "node": ">=20" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/xml-name-validator": { diff --git a/package.json b/package.json index 1c0383c1..26778f06 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lcards", - "version": "2026.03.28.2", + "version": "2026.03.29.1", "type": "module", "description": "LCARdS - LCARS card system for Home Assistant", "main": "index.js", @@ -33,7 +33,7 @@ "devDependencies": { "inquirer": "^13.1.0", "js-yaml": "^4.1.0", - "jsdom": "^27.1.0", + "jsdom": "^29.0.1", "mermaid": "^11.13.0", "typescript": "^5.9.3", "vite": "^5.4.21", @@ -54,8 +54,8 @@ "@fsegurai/codemirror-theme-vscode-dark": "^6.2.3", "@replit/codemirror-indentation-markers": "^6.5.3", "animejs": "^4.3.6", - "apexcharts": "^3.54.1", - "custom-card-helpers": "^1.9.0", + "apexcharts": "^5.10.4", + "custom-card-helpers": "^2.0.0", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0", "lit": "^3.3.2" diff --git a/src/base/LCARdSNativeCard.js b/src/base/LCARdSNativeCard.js index 37941afa..b8ec79f4 100644 --- a/src/base/LCARdSNativeCard.js +++ b/src/base/LCARdSNativeCard.js @@ -315,10 +315,11 @@ export class LCARdSNativeCard extends LitElement { /** * Determine if the card should re-render based on HASS changes * Checks if the card's entity or any tracked entities changed + * Subclasses may override to add additional entity dependency checks. * @param {Object} newHass - New HASS object * @param {Object} oldHass - Previous HASS object * @returns {boolean} True if re-render needed - * @private + * @protected */ _shouldUpdateOnHassChange(newHass, oldHass) { // First HASS update - always render diff --git a/src/cards/lcards-chart.js b/src/cards/lcards-chart.js index 0c77775e..0971c2b8 100644 --- a/src/cards/lcards-chart.js +++ b/src/cards/lcards-chart.js @@ -1,7 +1,7 @@ /** * LCARdS Chart Card * - * Data visualization using ApexCharts library (already bundled). + * Data visualization using ApexCharts library (bundled, v5). * Supports real-time and historical data via the unified DataSource + ProcessorManager pipeline. * * Features: @@ -58,6 +58,7 @@ import { lcardsLog } from '../utils/lcards-logging.js'; import ApexCharts from 'apexcharts'; import { ApexChartsAdapter } from '../charts/ApexChartsAdapter.js'; import { resolveThemeTokensRecursive } from '../utils/lcards-theme.js'; +import { deepMerge } from '../utils/deepMerge.js'; import { getChartSchema } from './schemas/chart-schema.js'; // Import chart editor for GUI editing import '../editor/cards/lcards-chart-editor.js'; @@ -199,6 +200,9 @@ export class LCARdSChart extends LCARdSCard { // Subscribe to data sources (may auto-create them) await this._subscribeToDataSources(); + // Subscribe to alert mode changes so chart colors re-resolve when the palette shifts + this._subscribeToAlertMode(); + // Note: Chart initialization now happens in updated() after container is rendered } @@ -270,6 +274,40 @@ export class LCARdSChart extends LCARdSCard { } } + /** + * Subscribe to ThemeManager alert mode changes. + * Called once after first update (skipped in preview mode). + * @private + */ + _subscribeToAlertMode() { + this._alertModeUnsubscribe?.(); + this._alertModeUnsubscribe = null; + + const themeManager = window.lcards?.core?.themeManager; + if (themeManager?.subscribeToAlertMode) { + this._alertModeUnsubscribe = themeManager.subscribeToAlertMode( + this._handleAlertModeChange.bind(this) + ); + lcardsLog.debug('[LCARdSChart] Subscribed to ThemeManager alert mode changes'); + } else { + lcardsLog.warn('[LCARdSChart] ThemeManager.subscribeToAlertMode not available — alert mode subscription skipped'); + } + } + + /** + * Called by ThemeManager AFTER CSS variables have been written and the + * token resolver cache has been cleared. Re-resolves all chart colours + * so the canvas reflects the new palette immediately. + * @private + * @param {string} _mode - Incoming alert mode (unused; colours are re-read from CSS) + */ + _handleAlertModeChange(_mode) { + if (this._chart && this._chartReady) { + lcardsLog.debug(`[LCARdSChart] Alert mode changed to '${_mode}' — updating chart colours`); + this._updateChartOptions(); + } + } + /** * Extract data from the selected buffer. * Supports main RollingBuffer or any processor output buffer by key. @@ -577,10 +615,51 @@ export class LCARdSChart extends LCARdSCard { // Get style with rule patches applied const style = this._getMergedStyleWithRules(this.config.style || {}); + // Build style baseline from theme's components.chart tokens so minimal + // configs get LCARS colors / typography instead of ApexCharts palette1 defaults. + // User style always wins because deepMerge puts it on top. + let tokenStyleDefaults = {}; + const tm = this._singletons?.themeManager; + if (tm) { + const seriesColors = tm.getToken('components.chart.colors'); + const gridColor = tm.getToken('components.chart.gridColor'); + const axisColor = tm.getToken('components.chart.axisColor'); + const strokeWidth = tm.getToken('components.chart.strokeWidth'); + const fontFamily = tm.getToken('components.chart.fontFamily'); + const fontSize = tm.getToken('components.chart.fontSize'); + + if (seriesColors != null) { + tokenStyleDefaults.colors = { series: seriesColors }; + } + if (gridColor != null) { + tokenStyleDefaults.colors = { ...tokenStyleDefaults.colors, grid: gridColor }; + } + if (axisColor != null) { + tokenStyleDefaults.colors = { + ...tokenStyleDefaults.colors, + axis: { x: axisColor, y: axisColor } + }; + } + if (strokeWidth != null) { + tokenStyleDefaults.stroke = { width: strokeWidth }; + } + if (fontFamily != null) { + tokenStyleDefaults.typography = { font_family: fontFamily }; + } + if (fontSize != null) { + tokenStyleDefaults.typography = { ...tokenStyleDefaults.typography, font_size: fontSize }; + } + + lcardsLog.trace('[LCARdSChart] Chart token defaults:', tokenStyleDefaults); + } + + // token defaults < user style: user config always overrides + const mergedStyle = deepMerge(tokenStyleDefaults, style); + // Merge chart_type from config root into style for adapter let enhancedStyle = { chart_type: this.config.chart_type || 'line', - ...style + ...mergedStyle }; // Map xaxis_type into chart_options so it passes through the adapter's @@ -914,6 +993,10 @@ export class LCARdSChart extends LCARdSCard { this._chartReady = false; this._chartInitialized = false; + // Unsubscribe from alert mode changes + this._alertModeUnsubscribe?.(); + this._alertModeUnsubscribe = null; + super._onDisconnected(); } diff --git a/src/cards/lcards-slider.js b/src/cards/lcards-slider.js index c94f2b90..7bbd1d5f 100644 --- a/src/cards/lcards-slider.js +++ b/src/cards/lcards-slider.js @@ -450,7 +450,7 @@ export class LCARdSSlider extends LCARdSButton { // Call parent to handle state-based color resolution super._handleHassUpdate(newHass, oldHass); - // Update entity value + // Update entity value (only when a primary entity is configured) if (this.config.entity && this._entity) { // Get new value and set it // Lit's hasChanged() automatically determines if re-render is needed @@ -466,10 +466,13 @@ export class LCARdSSlider extends LCARdSButton { // Update control config if entity attributes changed this._updateControlConfig(); - - // Re-resolve marker values since entity attributes may have changed - this._resolveMarkerValues(); } + + // Always re-resolve range templates — range bounds and colors may reference + // entities other than the primary entity (handled by _shouldUpdateOnHassChange). + // Called unconditionally so cards with no primary entity but with dynamic + // range templates also respond correctly. + this._resolveMarkerValues(); } /** @@ -641,7 +644,7 @@ export class LCARdSSlider extends LCARdSButton { _getEntityValue(entity) { if (!entity) return 0; - const attribute = this._controlConfig.attribute; + const attribute = this._resolveControlAttribute(this._controlConfig.attribute); let rawValue = 0; if (attribute && entity.attributes?.[attribute] !== undefined) { @@ -796,6 +799,50 @@ export class LCARdSSlider extends LCARdSButton { return null; } + /** + * Resolve a control attribute template to a string attribute name. + * Supports token templates ({entity.attributes.xxx}, {entity.state}) and + * JS templates ([[[return ...]]]). Plain strings are returned as-is. + * @param {string|null} rawAttr - Raw attribute config value + * @returns {string|null} Resolved attribute name string + * @private + */ + _resolveControlAttribute(rawAttr) { + if (!rawAttr || typeof rawAttr !== 'string') return rawAttr; + const str = rawAttr.trim(); + + // Token: {entity.attributes.xxx} → resolve to the attribute value as string + const tokenMatch = str.match(/^\{([^}]+)\}$/); + if (tokenMatch) { + const token = tokenMatch[1]; + if (token === 'entity.state') { + return String(this._entity?.state ?? rawAttr); + } + const attrMatch = token.match(/^entity\.attributes\.(.+)$/); + if (attrMatch) { + return String(this._entity?.attributes?.[attrMatch[1]] ?? rawAttr); + } + } + + // JS template: [[[return expression]]] + if (str.startsWith('[[[') && str.endsWith(']]]')) { + if (!this.hass) return rawAttr; + const jsBody = str.slice(3, -3).trim(); + try { + // eslint-disable-next-line no-new-func + const fn = new Function('hass', 'entity', 'states', jsBody); + const result = fn(this.hass, this._entity, this.hass.states); + return result != null ? String(result) : rawAttr; + } catch (e) { + lcardsLog.warn(`[LCARdSSlider] Control attribute JS template error:`, e); + return rawAttr; + } + } + + // Plain string — return as-is + return rawAttr; + } + /** * Resolve all value-based (marker) range entries to numeric positions. * Stores a parallel array to style.ranges in this._resolvedMarkerValues. @@ -811,6 +858,121 @@ export class LCARdSSlider extends LCARdSButton { lcardsLog.trace(`[LCARdSSlider] Marker range[${idx}] resolved: ${JSON.stringify(range.value)} → ${resolved}`); return resolved; }); + // Also resolve dynamic band range bounds (min/max templates) + this._resolveRangeBounds(); + } + + /** + * Resolve band range `min`/`max` templates to numeric values. + * Supports the same template syntaxes as _resolveMarkerValue(): + * static numbers, {entity.state}, {entity.attributes.xxx}, + * {states.entity_id.state}, {states.entity_id.attributes.xxx}, [[[JS]]]. + * Stores a parallel array in this._resolvedRangeBounds indexed to this._sliderStyle.ranges. + * Null entries correspond to marker ranges (those with a `value` key). + * Called automatically from _resolveMarkerValues() on every HASS update and style change. + * @private + */ + _resolveRangeBounds() { + const ranges = this._sliderStyle?.ranges || []; + this._resolvedRangeBounds = ranges.map((range, idx) => { + if ('value' in range) return null; // Marker range, not a band + const resolvedMin = this._resolveMarkerValue(range.min); + const resolvedMax = this._resolveMarkerValue(range.max); + lcardsLog.trace(`[LCARdSSlider] Band range[${idx}] bounds resolved: min=${JSON.stringify(range.min)}→${resolvedMin}, max=${JSON.stringify(range.max)}→${resolvedMax}`); + return { min: resolvedMin, max: resolvedMax }; + }); + // Update entity dependency tracking so external entities trigger re-renders + this._extractRangeEntityDependencies(); + } + + /** + * Scan all range bound templates (min, max, value) for entity ID references + * and store them in this._rangeTemplateEntities. + * Handles both token syntax ({states.entity_id.state}) and JS template syntax + * (states['entity.id'], states["entity.id"], hass.states['entity.id']). + * Called from _resolveRangeBounds() so it refreshes on every style/hass update. + * @private + */ + _extractRangeEntityDependencies() { + const entities = new Set(); + const ranges = this._sliderStyle?.ranges || []; + + const extractFromTemplate = (tmpl) => { + if (!tmpl || typeof tmpl !== 'string') return; + + // Token syntax: {states.entity_id.state} or {states.entity_id.attributes.xxx} + const tokenMatch = tmpl.match(/^\{states\.([^.}]+\.[^.}]+)\./) + if (tokenMatch) { + entities.add(tokenMatch[1]); + return; + } + + // JS template syntax: states['entity.id'] or states["entity.id"] + // Also matches hass.states['entity.id'] + const jsMatches = tmpl.matchAll(/states\[['"](\S+?)['"]/g); + for (const m of jsMatches) { + entities.add(m[1]); + } + }; + + ranges.forEach(range => { + extractFromTemplate(range.min); + extractFromTemplate(range.max); + extractFromTemplate(range.value); // Marker ranges too + }); + + this._rangeTemplateEntities = entities; + lcardsLog.trace(`[LCARdSSlider] Range template dependencies:`, [...entities]); + } + + /** + * Override shouldUpdate to also watch entities referenced in range templates. + * Without this override, if a range bound template references an entity other + * than the card's primary entity, changes to that entity would not trigger + * a re-render and the range bands would be stale. + * @override + */ + _shouldUpdateOnHassChange(newHass, oldHass) { + // Always defer to parent logic first (handles primary entity + trackedEntities) + if (super._shouldUpdateOnHassChange(newHass, oldHass)) return true; + + // Additionally check entities extracted from range bound templates + if (this._rangeTemplateEntities?.size > 0) { + for (const entityId of this._rangeTemplateEntities) { + if (oldHass?.states?.[entityId] !== newHass?.states?.[entityId]) { + lcardsLog.debug(`[LCARdSSlider] Range template entity changed: ${entityId} — triggering re-render`); + return true; + } + } + } + + return false; + } + + /** + * Resolve a range color value with optional state-awareness. + * If colorConfig is an object, routes through resolveStateColor() so the + * full state-aware syntax ({ active, inactive, above:70, … }) works on ranges. + * If colorConfig is a string (or undefined), falls back to _resolveColorValue() + * for static colors, CSS variables, and computed theme tokens — preserving the + * existing behavior for users who have not adopted the state-aware syntax. + * @param {string|Object} colorConfig - Color string or state-aware config object + * @param {string} [fallback='#888888'] - Fallback color + * @returns {string} Resolved color + * @private + */ + _resolveRangeColor(colorConfig, fallback = '#888888') { + if (colorConfig && typeof colorConfig === 'object') { + const resolved = resolveStateColor({ + actualState: this._entity?.state, + classifiedState: this._getButtonState(), + colorConfig, + fallback + }); + // resolveStateColor can return string | number | null; coerce to string for SVG attributes + return resolved != null ? String(resolved) : fallback; + } + return this._resolveColorValue(colorConfig, fallback); } @@ -1550,8 +1712,12 @@ export class LCARdSSlider extends LCARdSButton { `${orientation}|${width}|${height}|` + `${JSON.stringify(this._sliderStyle?.ranges || [])}|` + `${JSON.stringify(this._resolvedMarkerValues)}|` + + `${JSON.stringify(this._resolvedRangeBounds)}|` + `${this._lightColorValue || ''}|` + - `${window.lcards?.core?.themeManager?.getAlertMode?.() || 'green_alert'}`; // Bust cache on alert mode change + `${window.lcards?.core?.themeManager?.getAlertMode?.() || 'green_alert'}|` + + // Entity state + classified state bust the cache when state-aware range + // colors resolve differently (e.g. { active: red, inactive: gray }). + `${this._entity?.state ?? ''}|${this._getButtonState?.() ?? ''}`; // Bust cache on alert mode change // Check memoization cache if (this._memoizedTrack && this._memoizedTrackConfig === configHash) { @@ -1614,17 +1780,30 @@ export class LCARdSSlider extends LCARdSButton { * @private */ const getPillColor = (pillIndex, pillCount) => { - // Calculate the value this pill represents (in display space) + // Calculate the value this pill represents (in display space). + // When invert_fill is active, the pill at index 0 physically represents + // the display-max end of the value scale (matching the swapped gradient + // and the reversed fill direction from _updatePillOpacities), so the + // range lookup must also be mirrored to colour the correct pills. const valuePercent = pillIndex / (pillCount - 1 || 1); - const pillValue = displayMin + (valuePercent * displayRange); + const pillValue = this._invertFill + ? displayMax - (valuePercent * displayRange) + : displayMin + (valuePercent * displayRange); // Check if this value falls within any defined range if (ranges.length > 0) { - // FIX: Use inclusive upper boundary (<=) to match standard range notation - // This ensures pills at exact boundaries (e.g., 66.6, 77.7, 88.8 in 66-100 range) are matched - const matchingRange = ranges.find(r => pillValue >= r.min && pillValue <= r.max); - if (matchingRange) { - return this._resolveColorValue(matchingRange.color); + // Use resolved bounds (supports templates for min/max) with fallback to raw config values. + // FIX: Use inclusive upper boundary (<=) to match standard range notation. + for (let ri = 0; ri < ranges.length; ri++) { + const r = ranges[ri]; + if ('value' in r) continue; // Skip marker ranges + const bounds = this._resolvedRangeBounds?.[ri]; + const rMin = bounds?.min ?? r.min; + const rMax = bounds?.max ?? r.max; + if (rMin === null || rMax === null || rMin === undefined || rMax === undefined) continue; + if (pillValue >= rMin && pillValue <= rMax) { + return this._resolveRangeColor(r.color); + } } } @@ -1635,6 +1814,30 @@ export class LCARdSSlider extends LCARdSButton { return ColorUtils.mix(gradientEnd, gradientStart, t); }; + /** + * Return the unfilled opacity for a specific pill, honoring per-band opacity + * overrides from style.ranges[].opacity. Falls back to globalDefault when no + * band covers the pill's value position. + */ + const getRangeOpacity = (pillIndex, pillCount, globalDefault) => { + if (!ranges || ranges.length === 0) return globalDefault; + const valuePercent = pillIndex / (pillCount - 1 || 1); + const pillValue = this._invertFill + ? displayMax - (valuePercent * displayRange) + : displayMin + (valuePercent * displayRange); + for (let ri = 0; ri < ranges.length; ri++) { + const r = ranges[ri]; + if ('value' in r) continue; // Skip marker ranges + if (r.opacity === undefined || r.opacity === null) continue; + const bounds = this._resolvedRangeBounds?.[ri]; + const rMin = bounds?.min ?? r.min; + const rMax = bounds?.max ?? r.max; + if (rMin === null || rMax === null || rMin === undefined || rMax === undefined) continue; + if (pillValue >= rMin && pillValue <= rMax) return r.opacity; + } + return globalDefault; + }; + // Calculate count and dimensions based on orientation let count, pillWidth, pillHeight; @@ -1731,7 +1934,7 @@ export class LCARdSSlider extends LCARdSButton { const pillIdx = Math.max(0, Math.min(count - 1, Math.round(valuePercent * (count - 1)))); const pillStyle = range.pill_style || {}; markerPillMap.set(pillIdx, { - color: this._resolveColorValue(range.color || 'var(--lcars-white, #ffffff)'), + color: this._resolveRangeColor(range.color || 'var(--lcars-white, #ffffff)'), strokeEnabled: pillStyle.stroke !== false, // default true strokeWidth: pillStyle.stroke_width ?? 2 }); @@ -1770,7 +1973,8 @@ export class LCARdSSlider extends LCARdSButton { rx="${radius}" ry="${radius}" fill="${color}" - opacity="${unfilledOpacity}" + opacity="${getRangeOpacity(i, count, unfilledOpacity)}" + data-unfilled-opacity="${getRangeOpacity(i, count, unfilledOpacity)}" data-pill-index="${i}"${markerAttrsV} /> `; } @@ -1807,7 +2011,8 @@ export class LCARdSSlider extends LCARdSButton { rx="${radius}" ry="${radius}" fill="${color}" - opacity="${unfilledOpacity}" + opacity="${getRangeOpacity(i, count, unfilledOpacity)}" + data-unfilled-opacity="${getRangeOpacity(i, count, unfilledOpacity)}" data-pill-index="${i}"${markerAttrsH} /> `; } @@ -1938,7 +2143,14 @@ export class LCARdSSlider extends LCARdSButton { skipRanges, entityState: this._entity?.state, // Include state for reactive tick colors lightColor: this._lightColorValue || null, // Bust cache when light colour changes - alertMode: window.lcards?.core?.themeManager?.getAlertMode?.() || 'green_alert' // Bust cache on alert mode change + alertMode: window.lcards?.core?.themeManager?.getAlertMode?.() || 'green_alert', // Bust cache on alert mode change + controlMin: this._controlConfig.min, // Bust cache when control range changes (affects bg extent) + controlMax: this._controlConfig.max, + displayMin: this._displayConfig.min, + displayMax: this._displayConfig.max, + classifiedState: this._getButtonState?.() ?? '', + resolvedMarkerValues: this._resolvedMarkerValues, + resolvedRangeBounds: this._resolvedRangeBounds }); if (this._memoizedGauge && this._memoizedGaugeConfig === configHash) { @@ -1963,9 +2175,11 @@ export class LCARdSSlider extends LCARdSButton { ranges.forEach((rangeConfig, idx) => { if ('value' in rangeConfig) return; // Marker ranges are handled separately - const rangeMin = rangeConfig.min; - const rangeMax = rangeConfig.max; - const rangeColor = this._resolveColorValue(rangeConfig.color); + const bounds = this._resolvedRangeBounds?.[idx]; + const rangeMin = bounds?.min ?? rangeConfig.min; + const rangeMax = bounds?.max ?? rangeConfig.max; + if (rangeMin === null || rangeMin === undefined || rangeMax === null || rangeMax === undefined) return; + const rangeColor = this._resolveRangeColor(rangeConfig.color); const rangeOpacity = rangeConfig.opacity ?? 0.3; // Calculate range position as percentage of display range @@ -2057,7 +2271,48 @@ export class LCARdSSlider extends LCARdSButton { fallback: 'var(--lcars-blue-light)' }); const progressHeight = progressConfig?.height || 12; - const progressRadius = progressConfig?.radius !== undefined ? progressConfig?.radius : 2; + // Fill bar radius: uniform number (default 2) or per-end { start, end } object + const _prRaw = progressConfig?.radius; + const pr = (_prRaw !== null && typeof _prRaw === 'object') + ? { start: _prRaw.start ?? 2, end: _prRaw.end ?? 2 } + : { start: (_prRaw ?? 2), end: (_prRaw ?? 2) }; + // Background radius: independently configurable, defaults to fill radius + const _bgRRaw = progressConfig?.background?.radius ?? progressConfig?.radius; + const bgPr = (_bgRRaw !== null && typeof _bgRRaw === 'object') + ? { start: _bgRRaw.start ?? 2, end: _bgRRaw.end ?? 2 } + : { start: (_bgRRaw ?? 2), end: (_bgRRaw ?? 2) }; + // Helper: rect (uniform) or path (per-end). isH=true: left=start,right=end; false: bottom=start,top=end + const pbRect = (rad, px, py, pw, ph, isH, fill, close = '') => { + if (rad.start === rad.end) { + return ``; + } + const corners = isH + ? { tl: rad.start, tr: rad.end, br: rad.end, bl: rad.start } + : { tl: rad.end, tr: rad.end, br: rad.start, bl: rad.start }; + return ``; + }; + const pbPath = (px, py, pw, ph, isH, fill) => pbRect(pr, px, py, pw, ph, isH, fill); + const bgPbPath = (px, py, pw, ph, isH, fill) => pbRect(bgPr, px, py, pw, ph, isH, fill); + const progressBgColor = resolveStateColor({ + actualState: this._entity?.state, + classifiedState: this._getButtonState(), + colorConfig: progressConfig?.background?.color, + fallback: '' + }); + const progressBgThickness = progressConfig?.background?.height ?? progressHeight; + // Background track extent — explicit background.min/max override control-range clamping + const _dispMin = this._displayConfig.min; + const _dispMax = this._displayConfig.max; + const _dispRange = _dispMax - _dispMin; + const _ctrlMin = progressConfig?.background?.min ?? this._controlConfig.min; + const _ctrlMax = progressConfig?.background?.max ?? this._controlConfig.max; + const bgStartFrac = _dispRange > 0 ? Math.max(0, Math.min(1, (_ctrlMin - _dispMin) / _dispRange)) : 0; + const bgEndFrac = _dispRange > 0 ? Math.max(0, Math.min(1, (_ctrlMax - _dispMin) / _dispRange)) : 1; + lcardsLog.debug('[LCARdSSlider] _generateGaugeSVG() background extent', { + bgMin: _ctrlMin, bgMax: _ctrlMax, bgStartFrac, bgEndFrac, + controlMin: this._controlConfig.min, controlMax: this._controlConfig.max, + displayMin: _dispMin, displayMax: _dispMax + }); // Calculate progress bar position (at bottom of minor ticks) const progressY = minorHeight; @@ -2174,12 +2429,14 @@ export class LCARdSSlider extends LCARdSButton { // Draw progress bar (at bottom of minor ticks, extends based on value) if (!skipProgressBar) { - svg += ` - - `; + // Background track — extent clamped to control range (or background.min/max), optional custom thickness + if (progressBgColor) { + const bgX = bgStartFrac * trackWidth; + const bgW = (bgEndFrac - bgStartFrac) * trackWidth; + const bgYH = progressY + (progressHeight - progressBgThickness) / 2; + svg += bgPbPath(bgX, bgYH, bgW, progressBgThickness, true, progressBgColor); + } + svg += pbPath(progressX, progressY, progressWidth, progressHeight, true, progressColor); } } else { @@ -2290,12 +2547,14 @@ export class LCARdSSlider extends LCARdSButton { // Draw progress bar (fills from bottom up) - skip if component has separate progress zone if (!skipProgressBar) { - svg += ` - - `; + // Background track — vertical axis: y=0=top=max, y=height=bottom=min + if (progressBgColor) { + const bgYV = (1 - bgEndFrac) * trackHeight; + const bgHV = (bgEndFrac - bgStartFrac) * trackHeight; + const bgXV = progressX + (progressBarWidth - progressBgThickness) / 2; + svg += bgPbPath(bgXV, bgYV, progressBgThickness, bgHV, false, progressBgColor); + } + svg += pbPath(progressX, progressY, progressBarWidth, progressBarHeight, false, progressColor); } } @@ -2353,6 +2612,11 @@ export class LCARdSSlider extends LCARdSButton { pills.forEach((pill, index) => { let opacity; + // Per-pill unfilled opacity: band ranges may specify their own opacity value. + // The SVG rect has data-unfilled-opacity set at generation time; fall back to + // the global trackConfig unfilled opacity when the attribute is absent. + const pillUnfilledOpacity = parseFloat(pill.getAttribute('data-unfilled-opacity') || '') || unfilledOpacity; + // Determine if this pill should be fully filled (not including transition pill) let isFilled; @@ -2375,21 +2639,21 @@ export class LCARdSSlider extends LCARdSButton { // For inverted: transition pill is at the left boundary of filled region const transitionPillIndex = pills.length - Math.ceil(fillCount); if (index === transitionPillIndex) { - opacity = unfilledOpacity + ((fillCount % 1) * (filledOpacity - unfilledOpacity)); + opacity = pillUnfilledOpacity + ((fillCount % 1) * (filledOpacity - pillUnfilledOpacity)); } else { - opacity = unfilledOpacity; + opacity = pillUnfilledOpacity; } } else { // For normal: transition pill is at the right boundary of filled region if (index === Math.floor(fillCount)) { - opacity = unfilledOpacity + ((fillCount % 1) * (filledOpacity - unfilledOpacity)); + opacity = pillUnfilledOpacity + ((fillCount % 1) * (filledOpacity - pillUnfilledOpacity)); } else { - opacity = unfilledOpacity; + opacity = pillUnfilledOpacity; } } } else { // Unfilled - opacity = unfilledOpacity; + opacity = pillUnfilledOpacity; } pill.setAttribute('opacity', opacity); }); @@ -2642,23 +2906,17 @@ export class LCARdSSlider extends LCARdSButton { // Calculate relative position (0 = top, 1 = bottom) const relativeY = (event.clientY - rect.top) / rect.height; - // Convert to value using DISPLAY range (for visual alignment with gauge) - // Then clamp to CONTROL range (what user can actually set) - const displayMin = this._displayConfig.min; - const displayMax = this._displayConfig.max; - let value = displayMax - (relativeY * (displayMax - displayMin)); - - // Apply invert fill if configured - if (this._invertFill) { - value = displayMax - value + displayMin; - } - - // Clamp to control range and apply step + // The overlay div is constrained to the control-range band of the track, + // so relativeY 0→1 maps directly to ctrlMax→ctrlMin (no display range math needed). const controlMin = this._controlConfig.min; const controlMax = this._controlConfig.max; - value = Math.max(controlMin, Math.min(controlMax, value)); + let value = this._invertFill + ? controlMin + (relativeY * (controlMax - controlMin)) + : controlMax - (relativeY * (controlMax - controlMin)); + + // Step snap and safety clamp const step = this._controlConfig.step || 1; - value = Math.round(value / step) * step; + value = Math.max(controlMin, Math.min(controlMax, Math.round(value / step) * step)); lcardsLog.debug(`[LCARdSSlider] Vertical slider input`, { mouseY: event.clientY, @@ -2666,8 +2924,6 @@ export class LCARdSSlider extends LCARdSButton { rectHeight: rect.height, relativeY, value, - displayMin, - displayMax, controlMin, controlMax }); @@ -2690,23 +2946,17 @@ export class LCARdSSlider extends LCARdSButton { // Calculate relative position (0 = top, 1 = bottom) const relativeY = (touch.clientY - rect.top) / rect.height; - // Convert to value using DISPLAY range (for visual alignment with gauge) - // Then clamp to CONTROL range (what user can actually set) - const displayMin = this._displayConfig.min; - const displayMax = this._displayConfig.max; - let value = displayMax - (relativeY * (displayMax - displayMin)); - - // Apply invert fill if configured - if (this._invertFill) { - value = displayMax - value + displayMin; - } - - // Clamp to control range and apply step + // The overlay div is constrained to the control-range band of the track, + // so relativeY 0→1 maps directly to ctrlMax→ctrlMin (no display range math needed). const controlMin = this._controlConfig.min; const controlMax = this._controlConfig.max; - value = Math.max(controlMin, Math.min(controlMax, value)); + let value = this._invertFill + ? controlMin + (relativeY * (controlMax - controlMin)) + : controlMax - (relativeY * (controlMax - controlMin)); + + // Step snap and safety clamp const step = this._controlConfig.step || 1; - value = Math.round(value / step) * step; + value = Math.max(controlMin, Math.min(controlMax, Math.round(value / step) * step)); this._sliderValue = value; this._updateDynamicElements(); @@ -3022,9 +3272,24 @@ export class LCARdSSlider extends LCARdSButton { const controlZone = (effectiveMode === 'shaped' && zones._shaped) ? zones._shaped : zones.control; const isVertical = orientation === 'vertical'; + // Map control range into display-range pixel space so the overlay/input + // spans only the fraction of the track that the control range covers. + // This keeps the thumb and the progress bar fill in exact alignment even + // when the display range extends beyond the control range. + const { min: ctrlMin, max: ctrlMax } = this._controlConfig; + const dispMin = this._displayConfig.min; + const dispMax = this._displayConfig.max; + const dispRange = dispMax - dispMin; + const ctrlStartFrac = dispRange > 0 ? Math.max(0, Math.min(1, (ctrlMin - dispMin) / dispRange)) : 0; + const ctrlEndFrac = dispRange > 0 ? Math.max(0, Math.min(1, (ctrlMax - dispMin) / dispRange)) : 1; + // For vertical sliders, use a div overlay with mouse events instead of // because writing-mode breaks mouse coordinate mapping in browsers if (isVertical) { + // Vertical: y=0 is top = visual max; y=zoneHeight is bottom = visual min. + // Overlay top aligns with ctrlMax, overlay bottom aligns with ctrlMin. + const overlayTop = controlZone.y + (1 - ctrlEndFrac) * controlZone.height; + const overlayHeight = (ctrlEndFrac - ctrlStartFrac) * controlZone.height; return html`
${unsafeHTML(finalSvg)} @@ -3035,9 +3300,9 @@ export class LCARdSSlider extends LCARdSButton { @touchstart="${this._handleVerticalSliderTouchStart}" style=" left: ${controlZone.x}px; - top: ${controlZone.y}px; + top: ${overlayTop}px; width: ${controlZone.width}px; - height: ${controlZone.height}px; + height: ${overlayHeight}px; cursor: pointer; " >
@@ -3047,13 +3312,16 @@ export class LCARdSSlider extends LCARdSButton { } // Horizontal sliders work fine with + // Constrain the input to the control-range pixel footprint within the display range + // so the native thumb position stays in sync with the fill bar. // When invert_fill is true, mirror the input value so the thumb visually aligns with // the fill end. Using a CSS scaleX(-1) transform is unreliable because browsers // calculate the reported value from the pre-transform coordinate space, causing the // thumb and the fill to move in opposite directions during drag. Feeding the mirrored // value (max - value + min) is the reliable equivalent: the event handlers already // invert the raw value back to the actual entity value. - const { min: ctrlMin, max: ctrlMax } = this._controlConfig; + const inputLeft = controlZone.x + ctrlStartFrac * controlZone.width; + const inputWidth = (ctrlEndFrac - ctrlStartFrac) * controlZone.width; const inputDisplayValue = this._invertFill ? String(ctrlMax - this._sliderValue + ctrlMin) : String(this._sliderValue); @@ -3073,9 +3341,9 @@ export class LCARdSSlider extends LCARdSButton { @input="${this._handleSliderInput}" @change="${this._handleSliderChange}" style=" - left: ${controlZone.x}px; + left: ${inputLeft}px; top: ${controlZone.y}px; - width: ${controlZone.width}px; + width: ${inputWidth}px; height: ${controlZone.height}px; " /> @@ -3142,10 +3410,11 @@ export class LCARdSSlider extends LCARdSButton { let svg = ''; ranges.forEach((rangeConfig, idx) => { if ('value' in rangeConfig) return; // Skip markers - const rangeMin = rangeConfig.min; - const rangeMax = rangeConfig.max; - if (rangeMin === undefined || rangeMax === undefined) return; - const rangeColor = this._resolveColorValue(rangeConfig.color); + const bounds = this._resolvedRangeBounds?.[idx]; + const rangeMin = bounds?.min ?? rangeConfig.min; + const rangeMax = bounds?.max ?? rangeConfig.max; + if (rangeMin === null || rangeMin === undefined || rangeMax === null || rangeMax === undefined) return; + const rangeColor = this._resolveRangeColor(rangeConfig.color); const rangeOpacity = rangeConfig.opacity ?? 0.3; const startPercent = Math.max(0, (rangeMin - min) / displayRange); const endPercent = Math.min(1, (rangeMax - min) / displayRange); @@ -3254,11 +3523,12 @@ export class LCARdSSlider extends LCARdSButton { let svg = ''; - ranges.forEach(range => { - const rangeMin = range.min ?? displayMin; - const rangeMax = range.max ?? displayMax; - // Resolve through full pipeline so computed tokens work in range colors too - const color = this._resolveColorValue(range.color, '#888888'); + ranges.forEach((range, idx) => { + const bounds = this._resolvedRangeBounds?.[idx]; + const rangeMin = bounds?.min ?? (range.min ?? displayMin); + const rangeMax = bounds?.max ?? (range.max ?? displayMax); + // Resolve through full pipeline; state-aware object syntax ({ active, inactive, … }) also supported + const color = this._resolveRangeColor(range.color, '#888888'); const opacity = range.opacity ?? 1; const startPct = Math.max(0, (rangeMin - displayMin) / displayRange); @@ -3388,9 +3658,46 @@ export class LCARdSSlider extends LCARdSButton { colorConfig: progressBarConfig.color, fallback: 'var(--lcars-blue-light)' }); + // Radius: uniform number or per-end { start, end } object + const _prRawZ = progressBarConfig.radius; + const prZ = (_prRawZ !== null && typeof _prRawZ === 'object') + ? { start: _prRawZ.start ?? 2, end: _prRawZ.end ?? 2 } + : { start: (_prRawZ ?? 2), end: (_prRawZ ?? 2) }; + // Background radius — independently configurable, defaults to fill radius + const _bgRRawZ = progressBarConfig.background?.radius ?? progressBarConfig.radius; + const bgPrZ = (_bgRRawZ !== null && typeof _bgRRawZ === 'object') + ? { start: _bgRRawZ.start ?? 2, end: _bgRRawZ.end ?? 2 } + : { start: (_bgRRawZ ?? 2), end: (_bgRRawZ ?? 2) }; + const pbPathZ = (rad, px, py, pw, ph, isH, fill) => { + if (rad.start === rad.end) { + return ``; + } + const corners = isH + ? { tl: rad.start, tr: rad.end, br: rad.end, bl: rad.start } + : { tl: rad.end, tr: rad.end, br: rad.start, bl: rad.start }; + return ``; + }; + // Optional background track — extent clamped to background.min/max (defaults to control range) + const bgColor = resolveStateColor({ + actualState: this._entity?.state, + classifiedState: this._getButtonState(), + colorConfig: progressBarConfig.background?.color, + fallback: '' + }); + const bgThicknessZ = progressBarConfig.background?.height ?? null; + // Background extent: background.min/max override, defaults to _controlConfig.min/max clamped to display range + const bgMinVal = progressBarConfig.background?.min ?? this._controlConfig.min; + const bgMaxVal = progressBarConfig.background?.max ?? this._controlConfig.max; + const bgStartFracZ = range > 0 ? Math.max(0, Math.min(1, (bgMinVal - min) / range)) : 0; + const bgEndFracZ = range > 0 ? Math.max(0, Math.min(1, (bgMaxVal - min) / range)) : 1; + lcardsLog.debug('[LCARdSSlider] _generateProgressBar() background extent', { + bgMinVal, bgMaxVal, bgStartFracZ, bgEndFracZ, + controlMin: this._controlConfig.min, controlMax: this._controlConfig.max, + displayMin: min, displayMax: max + }); let svg = ''; - // Generate progress bar rect - fill zone based on value percentage + // Generate progress bar — fill zone based on value percentage if (isVertical) { const barHeight = height * progress; let barY = y + height - barHeight; // Start from bottom (default) @@ -3400,7 +3707,15 @@ export class LCARdSSlider extends LCARdSButton { barY = y; // Fill from top instead } - svg += ``; + if (bgColor) { + const bgW = bgThicknessZ ?? width; + const bgX2 = x + (width - bgW) / 2; + // Clamp background to bgStartFracZ..bgEndFracZ (y=0 is top = display max) + const bgH_z = (bgEndFracZ - bgStartFracZ) * height; + const bgY_z = y + (1 - bgEndFracZ) * height; + svg += pbPathZ(bgPrZ, bgX2, bgY_z, bgW, bgH_z, false, bgColor); + } + svg += pbPathZ(prZ, x, barY, width, barHeight, false, fillColor); } else { const barWidth = width * progress; let barX = x; // Start from left (default) @@ -3410,7 +3725,15 @@ export class LCARdSSlider extends LCARdSButton { barX = x + width - barWidth; // Fill from right instead } - svg += ``; + if (bgColor) { + const bgH = bgThicknessZ ?? height; + const bgY2 = y + (height - bgH) / 2; + // Clamp background to bgStartFracZ..bgEndFracZ (x=0 is left = display min) + const bgX_z = x + bgStartFracZ * width; + const bgW_z = (bgEndFracZ - bgStartFracZ) * width; + svg += pbPathZ(bgPrZ, bgX_z, bgY2, bgW_z, bgH, true, bgColor); + } + svg += pbPathZ(prZ, barX, y, barWidth, height, true, fillColor); } // Render indicator if in gauge mode and enabled diff --git a/src/cards/schemas/slider-schema.js b/src/cards/schemas/slider-schema.js index bc78cb7c..7cb4abb4 100644 --- a/src/cards/schemas/slider-schema.js +++ b/src/cards/schemas/slider-schema.js @@ -490,6 +490,84 @@ export function getSliderSchema(options = {}) { bottom: { type: 'number', minimum: 0 }, left: { type: 'number', minimum: 0 } } + }, + background: { + type: 'object', + description: 'Background track drawn behind the progress bar fill, clamped to control range. Leave unset for no background.', + properties: { + color: { + ...stateColorSchema, + description: 'Background track colour. Supports state-based maps.', + 'x-ui-hints': { + label: 'Background Colour', + helper: 'Colour for the background track. Supports state-reactive values.' + } + }, + height: { + oneOf: [ + { type: 'number', minimum: 1, maximum: 100 }, + { type: 'string' } + ], + 'x-ui-hints': { + selector: { number: { min: 1, max: 100, mode: 'box', unit_of_measurement: 'px' } }, + label: 'Background Thickness', + helper: 'Cross-section of the background track. Defaults to the same as the progress bar height.' + } + }, + radius: { + oneOf: [ + { type: 'number', minimum: 0 }, + { + type: 'object', + properties: { + start: { type: 'number', minimum: 0 }, + end: { type: 'number', minimum: 0 } + } + } + ], + 'x-ui-hints': { + selector: { number: { min: 0, max: 50, mode: 'box', unit_of_measurement: 'px' } }, + label: 'Background Radius', + helper: 'Corner radius for the background track. Defaults to progress_bar.radius if unset.' + } + }, + min: { + type: 'number', + 'x-ui-hints': { + label: 'Background Start', + helper: 'Value at which the background track starts. Defaults to control.min (clamped to display range).' + } + }, + max: { + type: 'number', + 'x-ui-hints': { + label: 'Background End', + helper: 'Value at which the background track ends. Defaults to control.max (clamped to display range).' + } + } + }, + 'x-ui-hints': { + label: 'Background Track', + helper: 'Optional track behind the progress bar fill, clamped to control range.' + } + }, + radius: { + oneOf: [ + { type: 'number', minimum: 0 }, + { + type: 'object', + properties: { + start: { type: 'number', minimum: 0, description: 'Radius on start end (left for horizontal, bottom for vertical)' }, + end: { type: 'number', minimum: 0, description: 'Radius on end (right for horizontal, top for vertical)' } + }, + 'x-ui-hints': { label: 'Per-End Radius' } + } + ], + 'x-ui-hints': { + selector: { number: { min: 0, max: 50, mode: 'box', unit_of_measurement: 'px' } }, + label: 'Corner Radius', + helper: 'Uniform radius (number) or per-end via YAML: { start: 6, end: 0 }. start = left/bottom, end = right/top.' + } } } }, @@ -845,20 +923,16 @@ export function getSliderSchema(options = {}) { type: 'object', properties: { min: { - type: 'number', - description: 'Range start value (in display space, matching style.track.display.min/max)', - examples: [0, 18, 80] + type: ['number', 'string'], + description: 'Range start value (in display space). Supports static numbers or templates: {entity.state}, {entity.attributes.xxx}, {states.entity_id.state}, [[[JS return expr]]].', + examples: [0, 18, 80, '{entity.attributes.min_temp}', '{states.input_number.low.state}', '[[[return Number(entity.state) - 5]]]'] }, max: { - type: 'number', - description: 'Range end value (in display space, matching style.track.display.min/max)', - examples: [20, 24, 100] - }, - color: { - type: 'string', - description: 'Range background colour (hex, rgba, named colour, theme token, or CSS variable)', - examples: ['var(--error-color)', '#ff0000', 'rgba(255,0,0,0.3)', 'theme:palette.danger', 'orange', 'transparent'] + type: ['number', 'string'], + description: 'Range end value (in display space). Supports static numbers or templates: {entity.state}, {entity.attributes.xxx}, {states.entity_id.state}, [[[JS return expr]]].', + examples: [20, 24, 100, '{entity.attributes.max_temp}', '{states.input_number.high.state}', '[[[return Number(entity.state) + 5]]]'] }, + color: stateColorSchema, opacity: { type: 'number', minimum: 0, diff --git a/src/charts/ApexChartsAdapter.js b/src/charts/ApexChartsAdapter.js index 0dca48dc..746f7e69 100644 --- a/src/charts/ApexChartsAdapter.js +++ b/src/charts/ApexChartsAdapter.js @@ -614,7 +614,11 @@ export class ApexChartsAdapter { shadeIntensity: monochromeIntensity } }) - } + }, + + // Annotations (threshold lines, zero line) + // Built from style.zero_line and style.thresholds[] + annotations: this._buildAnnotations(style) }; // ============================================================================ @@ -1243,6 +1247,8 @@ export class ApexChartsAdapter { const original = obj; const resolver = window.lcards?.core?.themeManager?.resolver; const afterResolver = resolver ? resolver.resolve(obj, obj) : obj; + // ColorUtils.resolveCssVariable now does in-place per-var substitution so + // multi-value strings (font-family stacks, etc.) are fully preserved. const resolved = afterResolver.includes('var(') ? ColorUtils.resolveCssVariable(afterResolver) : afterResolver; if (original !== resolved) { lcardsLog.debug(`[ApexChartsAdapter] 🎨 Resolved CSS variable: ${original} → ${resolved}`); diff --git a/src/core/themes/ColorUtils.js b/src/core/themes/ColorUtils.js index 21808647..dd18e73f 100644 --- a/src/core/themes/ColorUtils.js +++ b/src/core/themes/ColorUtils.js @@ -194,81 +194,125 @@ export class ColorUtils { } /** - * Resolve CSS variable to actual computed value + * Resolve all CSS variables in a string to their computed values. * - * Handles var() syntax with fallbacks, recursive resolution, and malformed cases. - * Used when CSS variables need to be resolved to actual values (e.g., for canvas rendering). + * Each `var()` in the input is replaced in-place so that multi-value CSS + * properties — font-family stacks, background shorthands, etc. — are fully + * preserved. The previous implementation matched only the **first** `var()` + * and returned its resolved value, silently discarding the rest of the string. * - * @param {string|Array} value - CSS value that may contain var(), or array of values - * @param {string} [defaultValue='#000000'] - Default value if resolution fails - * @returns {string} Resolved color value (or array when array input is given) + * Single-var expressions (e.g. a plain color token) behave identically to + * before: the resolved value is returned directly. + * + * Overloads allow TypeScript to infer the return type from the input type so + * callers that pass a `string` don't need a cast to re-narrow the result. + * + * @overload + * @param {string} value + * @param {string} [defaultValue] + * @returns {string} + */ + /** + * @overload + * @param {string[]} value + * @param {string} [defaultValue] + * @returns {string[]} + */ + /** + * @param {string|string[]} value - CSS value that may contain one or more var(), or an array of values + * @param {string} [defaultValue='#000000'] - Fallback for each var() that cannot be resolved + * @returns {string|string[]} Input with every var() replaced by its computed value * * @example - * ColorUtils.resolveCssVariable('var(--primary-color, #ff0000)') // => computed color or '#ff0000' - * ColorUtils.resolveCssVariable('var(--color)') // => computed color or '#000000' - * ColorUtils.resolveCssVariable('#ff0000') // => '#ff0000' (passthrough) + * // Single var — unchanged behaviour + * ColorUtils.resolveCssVariable('var(--primary-color, #ff0000)') // => computed or '#ff0000' + * ColorUtils.resolveCssVariable('var(--color)') // => computed or '#000000' + * ColorUtils.resolveCssVariable('#ff0000') // => '#ff0000' (passthrough) + * + * // Multi-value strings — fixed: full string preserved + * ColorUtils.resolveCssVariable("var(--lcars-font), 'Antonio', sans-serif") + * // => "Tungsten, 'Antonio', sans-serif" (not just "Tungsten") + * + * // Arrays * ColorUtils.resolveCssVariable(['var(--color1)', 'var(--color2)']) // => [resolved1, resolved2] */ static resolveCssVariable(value, defaultValue = '#000000') { // Handle arrays recursively if (Array.isArray(value)) { - // @ts-ignore - returns string[] when input is array, string otherwise return value.map(v => this.resolveCssVariable(v, defaultValue)); } - // Non-string or falsy values pass through + // Non-string or falsy values pass through unchanged if (!value || typeof value !== 'string') { return value; } - // Check if it's a CSS variable - if (value.includes('var(')) { - // Handle potentially malformed or nested var() - extract the first complete var() - const match = value.match(/var\(([^,)]+)(?:,\s*(.+))?\)/); - if (match) { - const varName = match[1].trim(); - const fallback = match[2]?.trim(); + if (!value.includes('var(')) { + return value; + } + + // Replace every var() occurrence in-place. + // The regex handles one level of nesting — var(--a, var(--b, #x)) — which + // covers all real-world cases in the LCARdS token set. + const resolved = value.replace( + /var\((?:[^)(]|\([^)]*\))*\)/g, + (varExpr) => this._resolveSingleVar(varExpr, defaultValue) + ); + + lcardsLog.trace(`[ColorUtils] resolveCssVariable: ${value} → ${resolved}`); + return resolved; + } - // Try to get computed value + /** + * Resolve a single, complete `var(--name, fallback)` expression. + * + * @private + * @param {string} varExpr - A complete `var(...)` token + * @param {string} defaultValue - Last-resort value if the var is undefined and has no fallback + * @returns {string} Resolved value + */ + static _resolveSingleVar(varExpr, defaultValue) { + // Extract --custom-property-name and optional fallback. + // The fallback capture group is greedy so it picks up everything after the + // first comma, including nested var() expressions. + const match = varExpr.match(/^var\(\s*(--[^,)]+?)\s*(?:,\s*([\s\S]+))?\s*\)$/); + if (!match) { + // Malformed var() — attempt a best-effort extraction + const nameMatch = varExpr.match(/var\(\s*(--[^,)]+)/); + if (nameMatch) { try { - const computedValue = getComputedStyle(document.documentElement) - .getPropertyValue(varName).trim(); - - if (computedValue) { - lcardsLog.trace(`[ColorUtils] ✅ Resolved CSS variable: ${value} → ${computedValue}`); - return computedValue; - } - } catch (e) { - // getComputedStyle might fail during dashboard edits - lcardsLog.trace(`[ColorUtils] ⚠️ Error getting computed style for ${varName}:`, e.message); - } - - // Recursively resolve fallback if it's also a var() - if (fallback) { - return this.resolveCssVariable(fallback, defaultValue); - } - - // No computed value and no fallback - return default - lcardsLog.trace(`[ColorUtils] ⚠️ Using default for ${value}: ${defaultValue}`); - return defaultValue; - } else { - // Malformed var() - try to extract variable name and get its value - const varMatch = value.match(/var\(([^,)]+)/); - if (varMatch) { - try { - const computedValue = getComputedStyle(document.documentElement) - .getPropertyValue(varMatch[1].trim()).trim(); - if (computedValue) return computedValue; - } catch (e) { - // Fall through to default - } - } - // Can't parse - return default - return defaultValue; + const computed = getComputedStyle(document.documentElement) + .getPropertyValue(nameMatch[1].trim()).trim(); + if (computed) return computed; + } catch (_) { /* ignore */ } } + return defaultValue; + } + + const varName = match[1].trim(); + const fallback = match[2]?.trim(); + + try { + const computed = getComputedStyle(document.documentElement) + .getPropertyValue(varName).trim(); + if (computed) { + lcardsLog.trace(`[ColorUtils] ✅ Resolved ${varName} → ${computed}`); + return computed; + } + } catch (e) { + lcardsLog.trace(`[ColorUtils] ⚠️ getComputedStyle failed for ${varName}:`, e.message); + } + + // Var is undefined — recurse into the CSS-level fallback if one exists. + // String() cast: resolveCssVariable returns string|string[] depending on + // input type, but fallback is always a string here so the result is always + // a string. The cast keeps _resolveSingleVar's return type consistent. + if (fallback) { + return String(this.resolveCssVariable(fallback, defaultValue)); } - return value; + lcardsLog.trace(`[ColorUtils] ⚠️ Using default for ${varExpr}: ${defaultValue}`); + return defaultValue; } // ─── Public primitives ──────────────────────────────────────────────────── diff --git a/src/editor/cards/lcards-slider-editor.js b/src/editor/cards/lcards-slider-editor.js index 9a3d1aed..a56e91d7 100644 --- a/src/editor/cards/lcards-slider-editor.js +++ b/src/editor/cards/lcards-slider-editor.js @@ -393,7 +393,6 @@ export class LCARdSSliderEditor extends LCARdSBaseEditor { this._handleAttributeChange(/** @type {any} */ ({ detail: { value: '' } }))}> ` : ''} @@ -1155,78 +1154,151 @@ export class LCARdSSliderEditor extends LCARdSBaseEditor { */ _renderGaugeConfiguration() { return html` - + - - ${FormField.renderField(this, 'style.gauge.progress_bar.height', { - label: 'Height', - helper: 'Cross-sectional thickness (width in vertical, height in horizontal)' - })} + + - ${FormField.renderField(this, 'style.gauge.progress_bar.align', { - label: 'Alignment', - helper: 'Cross-sectional alignment (left/middle/right in vertical)', - // @ts-ignore - TS2353: auto-suppressed - type: 'select', - mode: 'dropdown', - options: [ - { value: 'start', label: 'Start' }, - { value: 'middle', label: 'Middle' }, - { value: 'end', label: 'End' } - ] - })} + + + ${FormField.renderField(this, 'style.gauge.progress_bar.height', { + label: 'Height', + helper: 'Cross-sectional thickness (width in vertical, height in horizontal)' + })} + ${FormField.renderField(this, 'style.gauge.progress_bar.align', { + label: 'Alignment', + helper: 'Cross-sectional alignment', + // @ts-ignore - TS2353: auto-suppressed + type: 'select', + mode: 'dropdown', + options: [ + { value: 'start', label: 'Start' }, + { value: 'middle', label: 'Middle' }, + { value: 'end', label: 'End' } + ] + })} + ${FormField.renderField(this, 'style.gauge.progress_bar.layer', { + label: 'Layer', + helper: 'Render behind or in front of tick marks', + // @ts-ignore - TS2353: auto-suppressed + type: 'select', + mode: 'dropdown', + options: [ + { value: 'background', label: 'Behind ticks' }, + { value: 'foreground', label: 'In front of ticks' } + ] + })} + - ${FormField.renderField(this, 'style.gauge.progress_bar.layer', { - label: 'Layer', - helper: 'Render behind or in front of gauge tick marks', - // @ts-ignore - TS2353: auto-suppressed - type: 'select', - mode: 'dropdown', - options: [ - { value: 'background', label: 'Background (behind ticks)' }, - { value: 'foreground', label: 'Foreground (in front of ticks)' } - ] - })} - + + + ${FormField.renderField(this, 'style.gauge.progress_bar.radius.start', { + label: 'Radius Start', + helper: 'Fill bar start-end radius — left (horizontal) or bottom (vertical)' + })} + ${FormField.renderField(this, 'style.gauge.progress_bar.radius.end', { + label: 'Radius End', + helper: 'Fill bar end radius — right (horizontal) or top (vertical)' + })} + - - - ${FormField.renderField(this, 'style.gauge.progress_bar.padding.top', { - label: 'Padding Top', - helper: 'Top padding in pixels' - })} - ${FormField.renderField(this, 'style.gauge.progress_bar.padding.right', { - label: 'Padding Right', - helper: 'Right padding in pixels' - })} - ${FormField.renderField(this, 'style.gauge.progress_bar.padding.bottom', { - label: 'Padding Bottom', - helper: 'Bottom padding in pixels' - })} - ${FormField.renderField(this, 'style.gauge.progress_bar.padding.left', { - label: 'Padding Left', - helper: 'Left padding in pixels' - })} - + + + ${FormField.renderField(this, 'style.gauge.progress_bar.padding.top', { + label: 'Padding Top', + helper: 'Top padding in pixels' + })} + ${FormField.renderField(this, 'style.gauge.progress_bar.padding.right', { + label: 'Padding Right', + helper: 'Right padding in pixels' + })} + ${FormField.renderField(this, 'style.gauge.progress_bar.padding.bottom', { + label: 'Padding Bottom', + helper: 'Bottom padding in pixels' + })} + ${FormField.renderField(this, 'style.gauge.progress_bar.padding.left', { + label: 'Padding Left', + helper: 'Left padding in pixels' + })} + - - - + + + + + + + + + + + + ${FormField.renderField(this, 'style.gauge.progress_bar.background.height', { + label: 'Thickness', + helper: 'Background track cross-section. Defaults to the fill bar height.' + })} + ${FormField.renderField(this, 'style.gauge.progress_bar.background.min', { + label: 'Start Value', + helper: 'Value where the background track starts. Defaults to control.min.' + })} + ${FormField.renderField(this, 'style.gauge.progress_bar.background.max', { + label: 'End Value', + helper: 'Value where the background track ends. Defaults to control.max.' + })} + + + + + ${FormField.renderField(this, 'style.gauge.progress_bar.background.radius.start', { + label: 'Radius Start', + helper: 'Background start-end radius — left (horizontal) or bottom (vertical). Defaults to fill radius.' + })} + ${FormField.renderField(this, 'style.gauge.progress_bar.background.radius.end', { + label: 'Radius End', + helper: 'Background end radius — right (horizontal) or top (vertical). Defaults to fill radius.' + })} + + + + + + +