{"_id":"@anedyasystems/anedya-widgets-react","_rev":"8-00ff519abdec577de0cbf2b85776b600","name":"@anedyasystems/anedya-widgets-react","dist-tags":{"latest":"0.0.3","beta":"0.0.3-beta.1"},"versions":{"0.0.1":{"name":"@anedyasystems/anedya-widgets-react","version":"0.0.1","license":"ISC","_id":"@anedyasystems/anedya-widgets-react@0.0.1","maintainers":[{"name":"suraj-maurya-npm","email":"suraj.maurya@anedya.io"},{"name":"anedya","email":"dev@anedya.io"}],"homepage":"https://github.com/anedyaio/anedya-widgets-react#readme","bugs":{"url":"https://github.com/anedyaio/anedya-widgets-react/issues"},"dist":{"shasum":"b4f0a5de0d42526ca81b1b6e3e75fb5c33739467","tarball":"https://registry.npmjs.org/@anedyasystems/anedya-widgets-react/-/anedya-widgets-react-0.0.1.tgz","fileCount":14,"integrity":"sha512-swnFy190zWyNEP3gIfmLd3uwI63AtS8x+NUyLYY2zhvzr9PXCXcRdudGK75zO8f/AMEncyvwa+F8ymxYg0aWYw==","signatures":[{"sig":"MEUCIQDiUB4kDjd/l2HAjJ94HX6A/VdwPXzriaHonIlB6ESbcwIgLnJXTuriKpkrKeGAyd+giv269vcf5ZeOYdmz7sRY+zo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":196949},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","module":"dist/index.mjs","exports":{".":{"import":"./dist/index.mjs","require":"./dist/index.cjs"},"./formatters":{"types":"./dist/formatters.d.mts","import":"./dist/formatters.mjs","require":"./dist/formatters.cjs"},"./styles.css":"./dist/styles.css"},"gitHead":"5e1d9de4ab0eebdab6646063e57d7cc937ed3bd2","scripts":{"dev":"npm run clean && tsdown --watch","build":"npm run clean && npm run build:js && npm run build:css","clean":"rm -rf dist","build:js":"tsdown","build:css":"tailwindcss -i ./src/styles/base.css -o ./dist/styles.css --minify","changeset":"changeset","typecheck":"tsc --noEmit","changeset:status":"changeset status","changeset:version":"changeset version"},"_npmUser":{"name":"suraj-maurya-npm","email":"suraj.maurya@anedya.io"},"repository":{"url":"git+https://github.com/anedyaio/anedya-widgets-react.git","type":"git"},"_npmVersion":"11.7.0","description":"Anedya widget SDK","directories":{},"_nodeVersion":"24.12.0","dependencies":{"d3":"^7.9.0","clsx":"^2.1.1","@types/d3":"^7.4.3","tailwind-merge":"^3.6.0","@anedyasystems/anedya-frontend-sdk":"^0.1.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.0.0","tsdown":"^0.22.13","react-dom":"^19.0.0","typescript":"^5.7.3","tailwindcss":"^4.3.2","@types/react":"^19.2.17","@base-ui/react":"^1.6.0","@changesets/cli":"^2.31.1","@tailwindcss/cli":"^4.3.3","@types/react-dom":"^19.2.3"},"peerDependencies":{"react":"^17.0.0 || ^18.0.0 || ^19.0.0","react-dom":"^17.0.0 || ^18.0.0 || ^19.0.0","@base-ui/react":"^1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/anedya-widgets-react_0.0.1_1785242517455_0.050807442352862386","host":"s3://npm-registry-packages-npm-production"}},"0.0.2-beta.0":{"name":"@anedyasystems/anedya-widgets-react","version":"0.0.2-beta.0","license":"ISC","_id":"@anedyasystems/anedya-widgets-react@0.0.2-beta.0","maintainers":[{"name":"suraj-maurya-npm","email":"suraj.maurya@anedya.io"},{"name":"anedya","email":"dev@anedya.io"}],"homepage":"https://github.com/anedyaio/anedya-widgets-react#readme","bugs":{"url":"https://github.com/anedyaio/anedya-widgets-react/issues"},"dist":{"shasum":"5285eb7029ca01c13e1e9bee4900403cc7f06475","tarball":"https://registry.npmjs.org/@anedyasystems/anedya-widgets-react/-/anedya-widgets-react-0.0.2-beta.0.tgz","fileCount":14,"integrity":"sha512-luPpv1UiaVG/qBzISrHn5XzE+u7vBM+lOK6R8uL8HIBRuzVge+tO5tOOpmcIfu4asbMFfd1peRIYc1wWXCPXnw==","signatures":[{"sig":"MEUCIQCBas/FK8kdKMtSICkS729OWKscKr+EzzRdusnj8JBUpAIgBe63dVjTnjXHJoKw+77Ourze5UqYbU4veT10cX+Pkgo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":196842},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","module":"dist/index.mjs","exports":{".":{"import":"./dist/index.mjs","require":"./dist/index.cjs"},"./formatters":{"types":"./dist/formatters.d.mts","import":"./dist/formatters.mjs","require":"./dist/formatters.cjs"},"./styles.css":"./dist/styles.css"},"gitHead":"ab99c3d4c7a17002b0c2a5f9195d3dfffc721da9","scripts":{"dev":"npm run clean && tsdown --watch","build":"npm run clean && npm run build:js && npm run build:css","clean":"rm -rf dist","build:js":"tsdown","build:css":"tailwindcss -i ./src/styles/base.css -o ./dist/styles.css --minify","changeset":"changeset","typecheck":"tsc --noEmit","changeset:status":"changeset status","changeset:version":"changeset version"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2a7da6f9-39a9-442a-ba32-6d056ace1043"}},"repository":{"url":"git+https://github.com/anedyaio/anedya-widgets-react.git","type":"git"},"_npmVersion":"11.5.1","description":"Anedya widget SDK","directories":{},"_nodeVersion":"22.23.1","dependencies":{"d3":"^7.9.0","clsx":"^2.1.1","@types/d3":"^7.4.3","tailwind-merge":"^3.6.0","@anedyasystems/anedya-frontend-sdk":"^0.1.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"react":"^19.0.0","tsdown":"^0.22.13","react-dom":"^19.0.0","typescript":"^5.7.3","tailwindcss":"^4.3.2","@types/react":"^19.2.17","@base-ui/react":"^1.6.0","@changesets/cli":"^2.31.1","@tailwindcss/cli":"^4.3.3","@types/react-dom":"^19.2.3"},"peerDependencies":{"react":"^17.0.0 || ^18.0.0 || ^19.0.0","react-dom":"^17.0.0 || ^18.0.0 || ^19.0.0","@base-ui/react":"^1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/anedya-widgets-react_0.0.2-beta.0_1785242992850_0.9177953502293454","host":"s3://npm-registry-packages-npm-production"}},"0.0.2-beta.1":{"name":"@anedyasystems/anedya-widgets-react","version":"0.0.2-beta.1","keywords":["IoT","Anedya","SDK","widget","JavaScript","React","commands","data-access","cloud","device-management","API","browser"],"license":"ISC","_id":"@anedyasystems/anedya-widgets-react@0.0.2-beta.1","maintainers":[{"name":"suraj-maurya-npm","email":"suraj.maurya@anedya.io"},{"name":"anedya","email":"dev@anedya.io"}],"homepage":"https://anedya.io","bugs":{"url":"https://github.com/anedyaio/anedya-widgets-react/issues"},"dist":{"shasum":"4e7c3e16e376d04aee7aba56183bad5e1bfde2ca","tarball":"https://registry.npmjs.org/@anedyasystems/anedya-widgets-react/-/anedya-widgets-react-0.0.2-beta.1.tgz","fileCount":14,"integrity":"sha512-H9lM3XwWG5tlcdj+StKUv6UGSGdZ79f/Rqm+Mp/pTkbuS7U0EphmMYDk83OR9hdbXPP9yLokR+bcEk5quVBysg==","signatures":[{"sig":"MEUCIQC5p39q5LO0CAqPfJ+caZy+8eJs3QaVXsXqEpbIc+9aQwIgafEMnpq6oL2DAqlxgWjIqdlR4LFL/ngmky6BiZhBPYU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":197315},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","module":"dist/index.mjs","exports":{".":{"import":"./dist/index.mjs","require":"./dist/index.cjs"},"./formatters":{"types":"./dist/formatters.d.mts","import":"./dist/formatters.mjs","require":"./dist/formatters.cjs"},"./styles.css":"./dist/styles.css"},"gitHead":"3d327a3bdac3d937c488b26931b39f99caa425b4","scripts":{"dev":"npm run clean && tsdown --watch","build":"npm run clean && npm run build:js && npm run build:css","clean":"rm -rf dist","build:js":"tsdown","build:css":"tailwindcss -i ./src/styles/base.css -o ./dist/styles.css --minify","changeset":"changeset","typecheck":"tsc --noEmit","changeset:status":"changeset status","changeset:version":"changeset version"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2a7da6f9-39a9-442a-ba32-6d056ace1043"}},"overrides":{"human-id":"4.1.3"},"repository":{"url":"git+https://github.com/anedyaio/anedya-widgets-react.git","type":"git"},"_npmVersion":"11.5.1","description":"Anedya widget SDK","directories":{},"_nodeVersion":"22.23.1","dependencies":{"d3":"^7.9.0","clsx":"^2.1.1","@types/d3":"^7.4.3","tailwind-merge":"^3.6.0","@anedyasystems/anedya-frontend-sdk":"^0.1.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"react":"^19.0.0","tsdown":"^0.22.13","react-dom":"^19.0.0","typescript":"^5.7.3","tailwindcss":"^4.3.2","@types/react":"^19.2.17","@base-ui/react":"^1.6.0","@changesets/cli":"^2.31.1","@tailwindcss/cli":"^4.3.3","@types/react-dom":"^19.2.3"},"peerDependencies":{"react":"^17.0.0 || ^18.0.0 || ^19.0.0","react-dom":"^17.0.0 || ^18.0.0 || ^19.0.0","@base-ui/react":"^1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/anedya-widgets-react_0.0.2-beta.1_1785245509241_0.04651266877055926","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"@anedyasystems/anedya-widgets-react","version":"0.0.2","keywords":["IoT","Anedya","SDK","widget","JavaScript","React","commands","data-access","cloud","device-management","API","browser"],"license":"ISC","_id":"@anedyasystems/anedya-widgets-react@0.0.2","maintainers":[{"name":"suraj-maurya-npm","email":"suraj.maurya@anedya.io"},{"name":"anedya","email":"dev@anedya.io"}],"homepage":"https://anedya.io","bugs":{"url":"https://github.com/anedyaio/anedya-widgets-react/issues"},"dist":{"shasum":"68a50e00f483c28183b00fd998dec3239e7f3aed","tarball":"https://registry.npmjs.org/@anedyasystems/anedya-widgets-react/-/anedya-widgets-react-0.0.2.tgz","fileCount":14,"integrity":"sha512-CFSAiOgBID9+4mzOc8VYhwYVm/hiTgRbK6y0dJS/YN6hxdpK2pT3JRe03LSfP3gO42MEQiWy8bLSvhqv8kFReQ==","signatures":[{"sig":"MEQCIElVfLhwOxL1bugEEywbuUZtIhTGO7VEBc6tFiOyMfnSAiB9aF7DoXlN4edIwMHV0AnIU4benNdPDlzLkIxQjbApPA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@anedyasystems%2fanedya-widgets-react@0.0.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":197308},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","module":"dist/index.mjs","exports":{".":{"import":"./dist/index.mjs","require":"./dist/index.cjs"},"./formatters":{"types":"./dist/formatters.d.mts","import":"./dist/formatters.mjs","require":"./dist/formatters.cjs"},"./styles.css":"./dist/styles.css"},"gitHead":"853769814761ea3add3e3d7e30127b1e6c1a4326","scripts":{"dev":"npm run clean && tsdown --watch","build":"npm run clean && npm run build:js && npm run build:css","clean":"rm -rf dist","build:js":"tsdown","build:css":"tailwindcss -i ./src/styles/base.css -o ./dist/styles.css --minify","changeset":"changeset","typecheck":"tsc --noEmit","changeset:status":"changeset status","changeset:version":"changeset version"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2a7da6f9-39a9-442a-ba32-6d056ace1043"}},"overrides":{"human-id":"4.1.3"},"repository":{"url":"git+https://github.com/anedyaio/anedya-widgets-react.git","type":"git"},"_npmVersion":"11.5.1","description":"Anedya widget SDK","directories":{},"_nodeVersion":"22.23.1","dependencies":{"d3":"^7.9.0","clsx":"^2.1.1","@types/d3":"^7.4.3","tailwind-merge":"^3.6.0","@anedyasystems/anedya-frontend-sdk":"^0.1.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.0.0","tsdown":"^0.22.13","react-dom":"^19.0.0","typescript":"^5.7.3","tailwindcss":"^4.3.2","@types/react":"^19.2.17","@base-ui/react":"^1.6.0","@changesets/cli":"^2.31.1","@tailwindcss/cli":"^4.3.3","@types/react-dom":"^19.2.3"},"peerDependencies":{"react":"^17.0.0 || ^18.0.0 || ^19.0.0","react-dom":"^17.0.0 || ^18.0.0 || ^19.0.0","@base-ui/react":"^1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/anedya-widgets-react_0.0.2_1785248472168_0.7830834103620705","host":"s3://npm-registry-packages-npm-production"}},"0.0.2-beta.2":{"name":"@anedyasystems/anedya-widgets-react","version":"0.0.2-beta.2","keywords":["IoT","Anedya","SDK","widget","JavaScript","React","commands","data-access","cloud","device-management","API","browser"],"license":"ISC","_id":"@anedyasystems/anedya-widgets-react@0.0.2-beta.2","maintainers":[{"name":"suraj-maurya-npm","email":"suraj.maurya@anedya.io"},{"name":"anedya","email":"dev@anedya.io"}],"homepage":"https://anedya.io","bugs":{"url":"https://github.com/anedyaio/anedya-widgets-react/issues"},"dist":{"shasum":"0c697e6274abe9ac3343da88d3e7862f33d6dcda","tarball":"https://registry.npmjs.org/@anedyasystems/anedya-widgets-react/-/anedya-widgets-react-0.0.2-beta.2.tgz","fileCount":14,"integrity":"sha512-OUBjs9mTKRNJO4nyL0UBjO343Fa8w3J451Kuqs/RMAJ/NV3Pa9BpFdkqGGl3YfrtmqH6mYIJ2XpyDedEwHM69Q==","signatures":[{"sig":"MEUCICJtaYDkNqY4FudcS556TKYSw5B0JPeWJu96sgOeHjGRAiEA+Mr5+AE4bZkDHtX69rHU4tfiYanwV5tiHBKLpWJhpcc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@anedyasystems%2fanedya-widgets-react@0.0.2-beta.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":207379},"main":"dist/index.js","type":"module","types":"./dist/index.d.mts","module":"dist/index.mjs","exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./formatters":{"import":{"types":"./dist/formatters.d.mts","default":"./dist/formatters.mjs"},"require":{"types":"./dist/formatters.d.cts","default":"./dist/formatters.cjs"}},"./styles.css":"./dist/styles.css"},"gitHead":"54192821a12d68623fc86c60f6f009ea263644f3","scripts":{"dev":"npm run clean && tsdown --watch","build":"npm run clean && npm run build:js && npm run build:css","clean":"rm -rf dist","build:js":"tsdown","build:css":"tailwindcss -i ./src/styles/base.css -o ./dist/styles.css --minify","changeset":"changeset","typecheck":"tsc --noEmit","changeset:status":"changeset status","changeset:version":"changeset version"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2a7da6f9-39a9-442a-ba32-6d056ace1043"}},"overrides":{"human-id":"4.1.3"},"repository":{"url":"git+https://github.com/anedyaio/anedya-widgets-react.git","type":"git"},"_npmVersion":"11.5.1","description":"Anedya widget SDK","directories":{},"_nodeVersion":"22.23.2","dependencies":{"d3":"^7.9.0","clsx":"^2.1.1","@types/d3":"^7.4.3","tailwind-merge":"^3.6.0","@anedyasystems/anedya-frontend-sdk":"^0.1.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"react":"^19.0.0","tsdown":"^0.22.13","react-dom":"^19.0.0","typescript":"^5.7.3","tailwindcss":"^4.3.2","@types/react":"^19.2.17","@base-ui/react":"^1.6.0","@changesets/cli":"^2.31.1","@tailwindcss/cli":"^4.3.3","@types/react-dom":"^19.2.3"},"peerDependencies":{"react":"^17.0.0 || ^18.0.0 || ^19.0.0","react-dom":"^17.0.0 || ^18.0.0 || ^19.0.0","@base-ui/react":"^1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/anedya-widgets-react_0.0.2-beta.2_1788164929287_0.5603375911665378","host":"s3://npm-registry-packages-npm-production"}},"0.0.3-beta.0":{"name":"@anedyasystems/anedya-widgets-react","version":"0.0.3-beta.0","keywords":["IoT","Anedya","SDK","widget","JavaScript","React","commands","data-access","cloud","device-management","API","browser"],"license":"ISC","_id":"@anedyasystems/anedya-widgets-react@0.0.3-beta.0","maintainers":[{"name":"suraj-maurya-npm","email":"suraj.maurya@anedya.io"},{"name":"anedya","email":"dev@anedya.io"}],"homepage":"https://anedya.io","bugs":{"url":"https://github.com/anedyaio/anedya-widgets-react/issues"},"dist":{"shasum":"dc4a786329d48ccf09da860165a2ab4b9fc5d286","tarball":"https://registry.npmjs.org/@anedyasystems/anedya-widgets-react/-/anedya-widgets-react-0.0.3-beta.0.tgz","fileCount":14,"integrity":"sha512-4a+uxOjCOyds11KiomT3TVBOhYocsxhaS6ygvzA2z9lztKW859iN3SpH5IQHtLns0zcLKaEkcpQ+MVxMRp7Wdw==","signatures":[{"sig":"MEYCIQDpctRQI9K29faVz6CVMtcd5qoy/xVfz1nCJnOjjz60QwIhAPtCc/spXv9PgqoSYy+SAOOuTYUvWtpOJqfwn9m0rU2r","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@anedyasystems%2fanedya-widgets-react@0.0.3-beta.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":207386},"main":"dist/index.js","type":"module","types":"./dist/index.d.mts","module":"dist/index.mjs","exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./formatters":{"import":{"types":"./dist/formatters.d.mts","default":"./dist/formatters.mjs"},"require":{"types":"./dist/formatters.d.cts","default":"./dist/formatters.cjs"}},"./styles.css":"./dist/styles.css"},"gitHead":"e0b75944e799d44c861784b1b51213be959bd3d0","scripts":{"dev":"npm run clean && tsdown --watch","build":"npm run clean && npm run build:js && npm run build:css","clean":"rm -rf dist","build:js":"tsdown","build:css":"tailwindcss -i ./src/styles/base.css -o ./dist/styles.css --minify","changeset":"changeset","typecheck":"tsc --noEmit","changeset:status":"changeset status","changeset:version":"changeset version"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2a7da6f9-39a9-442a-ba32-6d056ace1043"}},"overrides":{"human-id":"4.1.3"},"repository":{"url":"git+https://github.com/anedyaio/anedya-widgets-react.git","type":"git"},"_npmVersion":"11.5.1","description":"Anedya widget SDK","directories":{},"_nodeVersion":"22.23.2","dependencies":{"d3":"^7.9.0","clsx":"^2.1.1","@types/d3":"^7.4.3","tailwind-merge":"^3.6.0","@anedyasystems/anedya-frontend-sdk":"^0.1.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"react":"^19.0.0","tsdown":"^0.22.13","react-dom":"^19.0.0","typescript":"^5.7.3","tailwindcss":"^4.3.2","@types/react":"^19.2.17","@base-ui/react":"^1.6.0","@changesets/cli":"^2.31.1","@tailwindcss/cli":"^4.3.3","@types/react-dom":"^19.2.3"},"peerDependencies":{"react":"^17.0.0 || ^18.0.0 || ^19.0.0","react-dom":"^17.0.0 || ^18.0.0 || ^19.0.0","@base-ui/react":"^1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/anedya-widgets-react_0.0.3-beta.0_1788166456625_0.3143276697708297","host":"s3://npm-registry-packages-npm-production"}},"0.0.3-beta.1":{"name":"@anedyasystems/anedya-widgets-react","version":"0.0.3-beta.1","keywords":["IoT","Anedya","SDK","widget","JavaScript","React","commands","data-access","cloud","device-management","API","browser"],"license":"ISC","_id":"@anedyasystems/anedya-widgets-react@0.0.3-beta.1","maintainers":[{"name":"suraj-maurya-npm","email":"suraj.maurya@anedya.io"},{"name":"anedya","email":"dev@anedya.io"}],"homepage":"https://anedya.io","bugs":{"url":"https://github.com/anedyaio/anedya-widgets-react/issues"},"dist":{"shasum":"a838b02d1402f0615c096da677d1050fd80f6158","tarball":"https://registry.npmjs.org/@anedyasystems/anedya-widgets-react/-/anedya-widgets-react-0.0.3-beta.1.tgz","fileCount":14,"integrity":"sha512-YoAbYyL7VlNfvfnjkxnk2glD7ZnfhsHtlpEfNQK2wrj/ZRAoLmt9L6zxN+ILLcltoZlfJs4ycZiUQ+4HIkUEzg==","signatures":[{"sig":"MEUCIGz2TMlIrEfPYQm5OUN3dPbCmnzf6yQw8JgxQCpH/VB4AiEAptqKbjFb8L33H1cUcc9PlZU/8nvNTlWRejtVdQADc4o=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@anedyasystems%2fanedya-widgets-react@0.0.3-beta.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":283596},"main":"dist/index.js","type":"module","types":"./dist/index.d.mts","module":"dist/index.mjs","exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./formatters":{"import":{"types":"./dist/formatters.d.mts","default":"./dist/formatters.mjs"},"require":{"types":"./dist/formatters.d.cts","default":"./dist/formatters.cjs"}},"./styles.css":"./dist/styles.css"},"gitHead":"abcbe206a30d7226d040ed89a026eba47bd30b72","scripts":{"dev":"npm run clean && tsdown --watch","build":"npm run clean && npm run build:js && npm run build:css","clean":"rm -rf dist","build:js":"tsdown","build:css":"tailwindcss -i ./src/styles/base.css -o ./dist/styles.css --minify","changeset":"changeset","typecheck":"tsc --noEmit","changeset:status":"changeset status","changeset:version":"changeset version"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2a7da6f9-39a9-442a-ba32-6d056ace1043"}},"overrides":{"human-id":"4.1.3"},"repository":{"url":"git+https://github.com/anedyaio/anedya-widgets-react.git","type":"git"},"_npmVersion":"11.5.1","description":"Anedya widget SDK","directories":{},"_nodeVersion":"22.23.2","dependencies":{"d3":"^7.9.0","clsx":"^2.1.1","@types/d3":"^7.4.3","tailwind-merge":"^3.6.0","@anedyasystems/anedya-frontend-sdk":"^0.1.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"react":"^19.0.0","tsdown":"^0.22.13","react-dom":"^19.0.0","typescript":"^5.7.3","tailwindcss":"^4.3.2","@types/react":"^19.2.17","@base-ui/react":"^1.6.0","@changesets/cli":"^2.31.1","@tailwindcss/cli":"^4.3.3","@types/react-dom":"^19.2.3"},"peerDependencies":{"react":"^17.0.0 || ^18.0.0 || ^19.0.0","react-dom":"^17.0.0 || ^18.0.0 || ^19.0.0","@base-ui/react":"^1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/anedya-widgets-react_0.0.3-beta.1_1789372826725_0.02787389880447","host":"s3://npm-registry-packages-npm-production"}},"0.0.3":{"name":"@anedyasystems/anedya-widgets-react","version":"0.0.3","description":"Anedya widget SDK","homepage":"https://anedya.io","main":"dist/index.js","module":"dist/index.mjs","type":"module","types":"./dist/index.d.mts","publishConfig":{"access":"public"},"exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./styles.css":"./dist/styles.css","./formatters":{"import":{"types":"./dist/formatters.d.mts","default":"./dist/formatters.mjs"},"require":{"types":"./dist/formatters.d.cts","default":"./dist/formatters.cjs"}}},"scripts":{"clean":"rm -rf dist","build:css":"tailwindcss -i ./src/styles/base.css -o ./dist/styles.css --minify","build:js":"tsdown","build":"npm run clean && npm run build:js && npm run build:css","dev":"npm run clean && tsdown --watch","typecheck":"tsc --noEmit","changeset":"changeset","changeset:status":"changeset status","changeset:version":"changeset version"},"repository":{"type":"git","url":"git+https://github.com/anedyaio/anedya-widgets-react.git"},"license":"ISC","dependencies":{"@anedyasystems/anedya-frontend-sdk":"^0.1.1","@types/d3":"^7.4.3","clsx":"^2.1.1","d3":"^7.9.0","tailwind-merge":"^3.6.0"},"peerDependencies":{"@base-ui/react":"^1.0.0","react":"^17.0.0 || ^18.0.0 || ^19.0.0","react-dom":"^17.0.0 || ^18.0.0 || ^19.0.0"},"devDependencies":{"@base-ui/react":"^1.6.0","@changesets/cli":"^2.31.1","@tailwindcss/cli":"^4.3.3","@types/react":"^19.2.17","@types/react-dom":"^19.2.3","react":"^19.0.0","react-dom":"^19.0.0","tailwindcss":"^4.3.2","tsdown":"^0.22.13","typescript":"^5.7.3"},"keywords":["IoT","Anedya","SDK","widget","JavaScript","React","commands","data-access","cloud","device-management","API","browser"],"overrides":{"human-id":"4.1.3"},"_id":"@anedyasystems/anedya-widgets-react@0.0.3","gitHead":"c32da3ab4f9b71fd807412c4532e2bbde86f7444","bugs":{"url":"https://github.com/anedyaio/anedya-widgets-react/issues"},"_nodeVersion":"22.23.2","_npmVersion":"11.5.1","dist":{"integrity":"sha512-R2Lc3i3vzkaCoOYBhZYP8EHBfPjDIM0cRS5JAXXWovtcSybfvfQp6PkJuOG1XQYHfswsStpAXJ4CeiL35vWtVQ==","shasum":"1340328fcedbd1fe4e19d318a3567c1f4d7ef154","tarball":"https://registry.npmjs.org/@anedyasystems/anedya-widgets-react/-/anedya-widgets-react-0.0.3.tgz","fileCount":14,"unpackedSize":283589,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@anedyasystems%2fanedya-widgets-react@0.0.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCkuiOPQ3Dx35mku764yomYeb9jR8OF3FA+m0dH6WeykQIgVykY6q9Ww1BCCfeE4GCvqPIIVKHMEuDc+0E7lbgHFEg="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2a7da6f9-39a9-442a-ba32-6d056ace1043"}},"directories":{},"maintainers":[{"name":"suraj-maurya-npm","email":"suraj.maurya@anedya.io"},{"name":"anedya","email":"dev@anedya.io"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/anedya-widgets-react_0.0.3_1789372990522_0.11425299068274608"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-28T12:41:57.091Z","modified":"2026-09-14T08:03:10.981Z","0.0.1":"2026-07-28T12:41:57.608Z","0.0.2-beta.0":"2026-07-28T12:49:53.012Z","0.0.2-beta.1":"2026-07-28T13:31:49.366Z","0.0.2":"2026-07-28T14:21:12.336Z","0.0.2-beta.2":"2026-08-31T08:28:49.428Z","0.0.3-beta.0":"2026-08-31T08:54:16.766Z","0.0.3-beta.1":"2026-09-14T08:00:26.864Z","0.0.3":"2026-09-14T08:03:10.657Z"},"bugs":{"url":"https://github.com/anedyaio/anedya-widgets-react/issues"},"license":"ISC","homepage":"https://anedya.io","keywords":["IoT","Anedya","SDK","widget","JavaScript","React","commands","data-access","cloud","device-management","API","browser"],"repository":{"type":"git","url":"git+https://github.com/anedyaio/anedya-widgets-react.git"},"description":"Anedya widget SDK","maintainers":[{"name":"suraj-maurya-npm","email":"suraj.maurya@anedya.io"},{"name":"anedya","email":"dev@anedya.io"}],"readme":"[<img alt=\"PyPI\" src=\"https://img.shields.io/npm/v/%40anedyasystems%2Fanedya-widgets-react?style=for-the-badge\">](https://www.npmjs.com/package/@anedyasystems/anedya-widgets-react)&nbsp;&nbsp;[<img alt=\"Anedya Documentation\" src=\"https://img.shields.io/badge/Anedya-Documentation-blue?style=for-the-badge\">](https://docs.anedya.io?utm_source=github&utm_medium=link&utm_campaign=github-sdk&utm_content=js)\n\n<!---<div style=\"width:20%; margin:0 auto;margin-bottom:50px;margin-top:50px;\">-->\n<p align=\"center\">\n    <img src=\"https://cdn.anedya.io/anedya_black_banner.png\" alt=\"Logo\">\n</p>\n<!--</div>-->\n\n# Anedya Widgets SDK\n\nA collection of pre-built, themeable React widgets for displaying Anedya IoT data in dashboards and front-end applications.\n\nDesigned for developers using the Anedya Frontend SDK, the library provides drop-in UI components that fetch and display device data with minimal setup. Each widget is fully customizable through themes, Tailwind classes, and component props, making it easy to match your application's design while avoiding repetitive UI implementation.\n\n## Table of Contents\n\n- [Installation & Setup](#installation--setup)\n- [Widgets](#widgets)\n  - [AnedyaCard](#AnedyaCard)\n    - [Required props](#required-props)\n    - [Manual vs live values](#manual-vs-live-values)\n    - [The styling model](#the-styling-model)\n    - [`styles` vs `className`](#styles-vs-classname--the-distinction-that-matters-most)\n    - [Theming](#theming)\n    - [Tailwind responsive utilities](#tailwind-responsive-utilities)\n    - [`onDataChange` — data-driven rendering](#ondatachange--data-driven-rendering)\n    - [How props are resolved](#how-props-are-resolved)\n  - [AnedyaGauge](#AnedyaGauge)\n    - [Required props](#required-props-1)\n    - [Manual vs live values](#manual-vs-live-values)\n    - [Slots & styling](#slots--styling)\n    - [Theming](#theming-1)\n    - [Arc configuration](#arc-configuration)\n    - [Track configuration](#track-configuration)\n    - [Bar color & gradients](#bar-color--gradients)\n    - [Needle configuration](#needle-configuration)\n    - [Needle-less (donut) mode](#needle-less-donut-mode)\n    - [Tick marks](#tick-marks)\n    - [Tooltip](#tooltip)\n    - [Animation configuration](#animation-configuration)\n    - [`onDataChange` — data-driven rendering](#ondatachange--data-driven-rendering-1)\n    - [How props are resolved](#how-props-are-resolved-1)\n  - [AnedyaLineChart](#anedyalinechart)\n    - [Required props](#required-props-2)\n    - [Time range & data fetching](#time-range--data-fetching)\n    - [The D3 passthrough model](#the-d3-passthrough-model)\n    - [Tooltip](#tooltip-1)\n    - [Area, points & grid](#area-points--grid)\n    - [`onDataChange` — data-driven rendering](#ondatachange--data-driven-rendering-2)\n- [Common](#common)\n  - [Automatic theme detection](#automatic-theme-detection)\n  - [Formatting vs rendering](#formatting-vs-rendering)\n  - [The last-updated label](#the-last-updated-label)\n  - [Error & empty states](#error--empty-states)\n  - [Formatters export](#formatters-export)\n  - [Sizing](#sizing)\n  - [Responsive sizing](#responsive-sizing)\n  - [Loading & error states](#loading--error-states)\n  - [Other props](#other-props)\n- [Stylesheet import](#stylesheet-import)\n\n---\n\n## Installation & Setup\n\nWidgets in this SDK don't create their own Anedya client or node — they only render data you fetch through the [Anedya Frontend SDK](https://www.npmjs.com/package/@anedyasystems/anedya-frontend-sdk), which is a separate package you'll need alongside this one.\n\n**1. Install both packages:**\n\n```bash\nnpm install @anedyasystems/anedya-frontend-sdk @anedyasystems/anedya-widgets-react\n```\n\n**2. Import the stylesheet once**, anywhere in your app's entry point:\n\n```jsx\nimport \"@anedyasystems/anedya-widgets-react/styles.css\";\n```\n\n**3. Create a client and node using the Frontend SDK, then pass the node into any widget:**\n\n```jsx\nimport { Anedya } from \"@anedyasystems/anedya-frontend-sdk\";\nimport { AnedyaCard } from \"@anedyasystems/anedya-widgets-react\";\nimport \"anedya-widgets-react/styles.css\";\n\nconst anedya = new Anedya();\nconst config = anedya.newConfig(tokenId, token);\nconst client = anedya.newClient(config);\nconst node = anedya.newNode(client, nodeId);\n\n<AnedyaCard node={node} variable=\"humidity\" />;\n```\n\n> `tokenId`, `token`, and `nodeId` come from your Anedya account/device setup — see the [Frontend SDK docs](https://www.npmjs.com/package/@anedyasystems/anedya-frontend-sdk) for details on obtaining these and on other `node`/`client` methods (fetching historical data, live streaming, key-value store, etc.) beyond what these widgets use directly.\n\n---\n\n## Widgets\n\n### AnedyaCard\n\nA single-value display widget — shows the latest reading, a title, and a last-updated label.\n\n```jsx\n<AnedyaCard\n  node={node}\n  variable=\"humidity\"\n  title=\"Humidity\"\n  unit=\"%\"\n  decimalPlaces={1}\n/>\n```\n\n#### Required props\n\n| Prop       | Type     | Description                                                              |\n| ---------- | -------- | ---------------------------------------------------------------------------- |\n| `node`     | `any`    | `anedya.newNode(client, nodeId)` — omit only if using `value` for a fully controlled widget (see below) |\n| `variable` | `string` | Variable name to display — required whenever `node` is provided               |\n\n---\n\n#### Manual vs live values\n\nLike `AnedyaGauge`, `AnedyaCard` can also render **without** a live `node` fetch — pass `value` directly for a fully controlled card:\n\n```jsx\n<AnedyaCard value={72} unit=\"%\" title=\"Manual reading\" />\n```\n\nIf both a `node`/`variable` fetch *and* a `value` prop are present, the fetched value takes over once it arrives — `value` acts as the initial/fallback reading until then.\n\n`onDataChange` reflects live fetched data only — it does not fire based on the manual `value` prop.\n\n---\n\n#### The styling model\n\n`AnedyaCard` is **entirely class-based** — every visual choice is a CSS class string, not a style property. This is deliberate: card widgets are commonly styled with Tailwind, and classes compose and override far more predictably than inline styles do once you're combining a theme, a per-instance override, and a conditional rule all on the same element.\n\nEvery part of the card is a named **slot**:\n\n```ts\ntype CardSlot = \"container\" | \"title\" | \"value\" | \"unit\" | \"label\";\n```\n\n---\n\n#### `styles` vs `className` — the distinction that matters most\n\nThese look similar but do genuinely different things:\n\n|             | Shape                 | Targets                                                              | Purpose                                                |\n| ----------- | ---------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------- |\n| `styles`    | object, keyed by slot  | any inner slot (`title`, `value`, `unit`, `label`) _and_ `container` | reach inside the widget                                  |\n| `className` | plain string           | only the outermost container element                                  | the ordinary React convention every component supports  |\n\n```jsx\n<AnedyaCard\n  className=\"shadow-lg\" // outer box only\n  styles={{\n    value: \"text-red-500 text-5xl\", // the \"value\" slot specifically\n    title: \"uppercase tracking-wider\", // the \"title\" slot specifically\n  }}\n/>\n```\n\nBoth can be used together freely — `className` is merged (via `twMerge`) with whatever `styles.container` already resolved to; it doesn't replace it.\n\n> **Note:** `styles` classes are consumer-authored strings forwarded straight into `className` at render — the widget doesn't generate any CSS for them at build time. Any class you pass here must already exist in your own app's compiled CSS (e.g. your own Tailwind build, or a plain hand-written class in a stylesheet you import).\n\n---\n\n#### Theming\n\n`theme` accepts a built-in preset name, or a full custom `WidgetTheme<CardSlot>` object — just a plain object shaped like `styles`, reusable across every widget instance that uses it:\n\n```jsx\nconst emeraldTheme = {\n  styles: {\n    container: \"bg-emerald-50 border-emerald-200\",\n    title: \"text-emerald-700\",\n    value: \"text-emerald-900\",\n    label: \"text-emerald-500\",\n  },\n};\n\n<AnedyaCard theme={emeraldTheme} />\n// or\n<AnedyaCard theme=\"dark\" />   // built-in preset, or \"light\" (default)\n```\n\n---\n\n#### Tailwind responsive utilities\n\nYou can freely use Tailwind's responsive prefixes inside `styles`:\n\n```tsx\n<AnedyaCard\n  styles={{\n    value: \"text-2xl md:text-4xl xl:text-6xl\",\n  }}\n/>\n```\n\nThese breakpoints come entirely from your application's Tailwind configuration and respond to the **browser viewport**. They are independent of the widget's built-in container-query sizing (see [Responsive sizing](#responsive-sizing) below).\n\nIf you supply your own `text-*` classes (responsive or otherwise), they replace the widget's default responsive typography for that slot.\n\n---\n\n#### `onDataChange` — data-driven rendering\n\n`onDataChange` is called whenever the latest fetched data changes.\n\nIt receives the raw fetched data:\n\n```ts\n{\n  value: number;\n  timestamp: number;\n}\n```\n\n(or `null` while no data is available).\n\nInstead of returning CSS classes, it returns a **partial set of widget props**. Those returned props temporarily override the widget's original props until the callback runs again.\n\n```tsx\n<AnedyaCard\n  onDataChange={(data) => {\n    if (!data) return;\n\n    if (data.value > 80) {\n      return {\n        title: \"High Humidity\",\n        theme: \"dark\",\n        styles: {\n          value: \"text-red-500\",\n        },\n      };\n    }\n  }}\n/>\n```\n\nReturning `undefined` (or nothing) clears any previous overrides and restores the widget's original props.\n\n`onDataChange` also receives a second argument describing which state just occurred — see [Error & empty states](#error--empty-states) below for the full `meta` shape and examples reacting to errors/empty data.\n\n---\n\n#### How props are resolved\n\nRendering happens in layers.\n\n1. The props you pass to `<AnedyaCard />`\n2. Any temporary overrides returned from `onDataChange`\n3. Theme resolution (`\"light\"`, `\"dark\"`, or a custom `WidgetTheme`)\n4. Per-slot class resolution\n\nFor each slot (`container`, `title`, `value`, `unit`, `label`), classes are merged in this order:\n\n1. `CARD_DEFAULT_CLASSES`\n2. Active theme classes\n3. `styles`\n\nLater layers win whenever two Tailwind utilities conflict. Conflicts are resolved with `twMerge`.\n\n`className` is separate from this chain — it is merged onto the outer container after the container slot has been resolved.\n\n> **Rule of thumb:** defaults → theme → `styles` → `onDataChange`. User overrides always win.\n\n---\n\n### AnedyaGauge\n\nA radial arc gauge — shows a single value as a filled arc with an optional needle, min/max tick marks, and a last-updated label.\n\n```jsx\n<AnedyaGauge\n  node={node}\n  variable=\"temperature\"\n  title=\"Temperature\"\n  unit=\"°C\"\n  min={0}\n  max={100}\n/>\n```\n\n> **Default behaviour:** simply pass `node` and `variable` — the gauge renders with the light theme, title `\"Latest Value\"`, no tick marks, and the last‑updated label showing the current time.\n\n#### Required props\n\n\n| Prop       | Type     | Description                                                              |\n| ---------- | -------- | ---------------------------------------------------------------------------- |\n| `node`     | `any`    | `anedya.newNode(client, nodeId)` — omit only if using `value` for a fully controlled widget (see below) |\n| `variable` | `string` | Variable name to display — required whenever `node` is provided               |\n\n---\n\n#### Manual vs live values\n\nUnlike `AnedyaCard`, `AnedyaGauge` can also render **without** a live `node` fetch — pass `value` directly for a fully controlled gauge (e.g. driven by your own state, a slider, or a value computed elsewhere in your app):\n\n```jsx\n<AnedyaGauge value={72} min={0} max={100} unit=\"%\" />\n```\n\nIf both a `node`/`variable` fetch *and* a `value` prop are present, the fetched value takes over once it arrives — `value` acts as the initial/fallback reading until then.\n\n`min` and `max` (default `0` / `100`) define the gauge's range regardless of which mode you're in. The displayed value is always clamped into `[min, max]` before it's drawn or formatted.\n\n---\n\n#### Slots & styling\n\nLike `AnedyaCard`, every part of the gauge is a named **slot**, and the same `styles` (per-slot) / `className` (outer container only) system applies:\n\n```ts\ntype GaugeSlot =\n  | \"container\" | \"title\" | \"unit\" | \"track\" | \"bar\"\n  | \"needle\" | \"needleCap\" | \"value\" | \"label\"\n  | \"tick\" | \"tickLabel\" | \"tooltip\" | \"error\" | \"empty\";\n```\n\n```jsx\n<AnedyaGauge\n  className=\"shadow-lg\"          // outer box only\n  styles={{\n    value: \"text-red-500 text-4xl\",\n    bar: \"text-emerald-500\",     // arc fill reads this via currentColor\n    needle: \"text-slate-800\",\n  }}\n/>\n```\n\n> **Note:** `track`, `bar`, `needle`, and `needleCap` are SVG shapes, not text — their \"color\" comes from `currentColor`, which is why these slots are styled with Tailwind text-color utilities (`text-emerald-500`) rather than `fill-*`/`stroke-*` classes.\n\n---\n\n#### Theming\n\n`theme` works exactly as it does for `AnedyaCard` — a preset name, or a custom `WidgetTheme<GaugeSlot>` object:\n\n```jsx\nconst emeraldGaugeTheme = {\n  styles: {\n    bar: \"text-emerald-500\",\n    track: \"text-emerald-100\",\n    value: \"text-emerald-900\",\n    needle: \"text-emerald-700\",\n    needleCap: \"text-emerald-700\",\n    tooltip: \"bg-emerald-200 text-emerald-700\",\n  },\n};\n\n<AnedyaGauge theme={emeraldGaugeTheme} />\n// or\n<AnedyaGauge theme=\"dark\" />   // built-in preset, or \"light\" (default)\n```\n\n---\n\n#### Arc configuration\n\n`arc` controls the shape of the gauge itself:\n\n```jsx\n<AnedyaGauge\n  arc={{\n    startAngle: -120,   // degrees, 0 = 12 o'clock, clockwise-positive\n    endAngle: 120,\n    radius: 90,          // px; auto-fit to the container if omitted\n    thickness: 14,        // px; defaults to ~18% of radius if omitted\n    cornerRadius: 6,      // rounds the ends of the arc/bar\n  }}\n/>\n```\n\n| Prop           | Type     | Default                    | Description                                                          |\n| -------------- | -------- | ---------------------------- | ------------------------------------------------------------------------ |\n| `startAngle`   | `number` | `-90`                        | Degrees, `0` = 12 o'clock, clockwise-positive                            |\n| `endAngle`     | `number` | `90`                         | Degrees, same convention as `startAngle`                                 |\n| `radius`       | `number` | auto-fit to container        | Capped to whatever actually fits the current width/height/tick space     |\n| `thickness`    | `number` | `~18%` of resolved radius     | Arc/bar stroke width in px                                                |\n| `cornerRadius` | `number` | `0`                           | Rounds the ends of both the track and the value bar                       |\n\nA default `startAngle`/`endAngle` of `-90`/`90` draws the classic bottom half-circle speedometer; narrowing the range (e.g. `-120`/`120`) draws a fuller arc.\n\n`radius` is a *maximum* — the gauge always shrinks it further if needed so the arc (plus tick marks, if shown) fits inside the widget's actual rendered box.\n\n---\n\n#### Track configuration\n\n`track` is the background arc the value bar sits on top of:\n\n```jsx\n<AnedyaGauge track={{ show: true, color: \"#e2e8f0\" }} />\n```\n\n| Prop    | Type      | Default              | Description                                          |\n| ------- | --------- | ---------------------- | ------------------------------------------------------- |\n| `show`  | `boolean` | `true`                 | Whether the background track arc is drawn                |\n| `color` | `string`  | theme's `track` slot   | Overrides the track color directly (any CSS color)        |\n\n---\n\n#### Bar color & gradients\n\n`color` sets the fill of the value arc — a single color, or 2+ colors for a gradient:\n\n```jsx\n<AnedyaGauge color=\"#f97316\" />\n\n<AnedyaGauge color={[\"#22c55e\", \"#eab308\", \"#ef4444\"]} />\n// renders a left-to-right gradient across the filled portion of the arc\n```\n\nIf omitted, the bar falls back to the theme's `bar` slot color via `currentColor`.\n\n---\n\n#### Needle configuration\n\n```jsx\n<AnedyaGauge\n  needle={{\n    show: true,\n    type: \"drop\",          // \"line\" | \"rounded\" | \"drop\" | \"triangle\"\n    length: \"medium\",      // \"short\" | \"medium\" | \"full\" | number (px)\n    width: 6,\n    color: \"#334155\",      // shorthand: sets both needleColor and capColor\n    capRadius: 8,\n    animation: true,\n  }}\n/>\n```\n\n| Prop          | Type                                            | Default          | Description                                                                     |\n| ------------- | ------------------------------------------------ | ----------------- | ----------------------------------------------------------------------------------- |\n| `show`        | `boolean`                                        | `true`             | Whether the needle is drawn at all                                                   |\n| `type`        | `\"line\" \\| \"rounded\" \\| \"drop\" \\| \"triangle\"`     | `\"rounded\"`        | Needle shape                                                                          |\n| `length`      | `\"short\" \\| \"medium\" \\| \"full\" \\| number`         | `\"medium\"`         | Preset (relative to the resolved radius) or an explicit px value                     |\n| `width`       | `number`                                         | `4`                | Needle thickness in px                                                               |\n| `color`       | `string`                                         | `theme`                  | Shorthand — sets both `needleColor` and `capColor` at once                           |\n| `needleColor` | `string`                                         | `color` or theme   | Color of the needle shape specifically, overrides `color` for the shape only         |\n| `capColor`    | `string`                                         | `color` or theme   | Color of the center cap circle specifically                                          |\n| `capRadius`   | `number`                                         | `6`                | Radius of the center cap circle in px                                                |\n| `animation`   | `boolean`                                        | `true`             | Whether the needle rotates smoothly to its new position                              |\n\n`needle.animation` and the top-level `animation.show` (below) are independent switches — **both** must be `true` for the needle to animate. This lets you, for example, animate the bar fill while snapping the needle instantly, or vice versa.\n\n---\n\n#### Needle-less (donut) mode\n\nSetting `needle={{ show: false }}` turns the gauge into a donut-style indicator — the value/unit/label block is centered directly over the arc instead of rendered below it:\n\n```jsx\n<AnedyaGauge needle={{ show: false }} arc={{ startAngle: -180, endAngle: 180 }} />\n```\n\n---\n\n#### Tick marks\n\n`tick` draws radial marks (and optional labels) around the arc between `min` and `max`. Off by default:\n\n```jsx\n<AnedyaGauge\n  tick={{\n    show: true,\n    count: 10,          // number of INTERVALS, so 10 -> 11 marks (0,10,...,100)\n    size: 6,\n    radiusOffset: 4,     // gap between the arc's outer edge and the tick line\n    labelGap: 4,          // gap between the tick line and its label\n    labelSize: 10,         // px — always wins over any styles.tickLabel font-size class\n    color: \"#cbd5e1\",\n    labelColor: \"#94a3b8\",\n    labelFormat: (v) => `${v}°`,\n  }}\n/>\n```\n\n| Prop           | Type                        | Default | Description                                                                                     |\n| -------------- | ---------------------------- | ------- | --------------------------------------------------------------------------------------------------- |\n| `show`         | `boolean`                    | `false` | Whether tick marks are drawn                                                                         |\n| `count`        | `number`                     | `10`    | Number of **intervals** between `min` and `max` — `count: 10` draws 11 marks                          |\n| `size`         | `number`                     | `6`     | Tick line length in px                                                                                |\n| `color`        | `string`                     | `theme`       | Tick line color; falls back to the theme's `tick` slot via `currentColor`                            |\n| `radiusOffset` | `number`                     | `4`     | Gap in px between the arc's outer edge and the start of the tick line                                 |\n| `labelGap`     | `number`                     | `4`     | Gap in px between the end of the tick line and its label                                              |\n| `labelSize`    | `number`                     | `10`    | Tick label font size in px — set as an inline style, so it always wins over `styles.tickLabel`         |\n| `labelColor`   | `string`                     | `theme`       | Tick label color; falls back to the theme's `tickLabel` slot                                          |\n| `labelFormat`  | `(value: number) => string`  | —       | Custom formatter for tick numbers. If omitted, reuses the widget's `format` preset if one is set, otherwise a plain rounded number |\n\nEnabling ticks reserves extra space inside the widget for the line + gaps + label, shrinking the arc's radius slightly so the ring is never clipped — you don't need to size the container any differently to accommodate it.\n\n> Because `labelSize` is applied as an inline style, a `styles.tickLabel` class controlling font size will have no effect — use the `labelSize` prop for sizing, and `styles.tickLabel` / `tickLabel` theme classes for anything else (weight, tracking, etc.).\n\n---\n\n\n#### Tooltip\n\n`tooltip` shows a hover tooltip over the arc (both the track and the filled bar respond) — enabled by default. The built-in tooltip text is `\"variable: value unit\"`, using the **formatted** value (`displayValue` — after `format`/`formatValue`/`decimalPlaces` is applied), not the raw number:\n\n```jsx\n<GaugeWidget node={node} variable=\"temperature\" unit=\"°C\" decimalPlaces={1} />\n// hovering the arc shows: \"temperature: 42.5 °C\"\n```\n\n| Prop      | Type                                                                                              | Default | Description                                                                 |\n| --------- | ---------------------------------------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------ |\n| `show`    | `boolean`                                                                                              | `true`  | Whether hovering the arc shows a tooltip                                       |\n| `content` | `(data: { variable: string; value: number; displayValue: string; unit?: string }) => ReactNode`      | —       | Full custom tooltip content. Receives both the **raw** clamped `value` and the already-**formatted** `displayValue`/`unit`, alongside the widget's `variable` name |\n\nDisable it with `tooltip={{ show: false }}`, or fully replace the content with your own JSX — `content` gets the raw value in case you want to do your own formatting/thresholds rather than reuse `displayValue`:\n\n```jsx\n<GaugeWidget\n  tooltip={{\n    content: ({ variable, value, displayValue, unit }) => (\n      <div className=\"flex flex-col items-center\">\n        <span className=\"font-semibold\">{variable}</span>\n        <span>{displayValue}{unit ? ` ${unit}` : \"\"}</span>\n        <span className=\"text-xs opacity-70\">raw: {value}</span>\n      </div>\n    ),\n  }}\n/>\n```\n\nThe tooltip follows the pointer and flips to the opposite side automatically once the pointer crosses the horizontal midpoint of the gauge, so it never overflows off the edge of a narrow container. It's hidden automatically while the widget is in a loading, error, or empty state.\n\nLike every other part of the gauge, the tooltip is styleable via the `tooltip` slot in `styles` or a custom theme:\n\n```jsx\n<GaugeWidget\n  styles={{\n    tooltip: \"bg-slate-900 text-white text-xs px-2 py-1 rounded shadow-lg\",\n  }}\n/>\n```\n---\n\n#### Animation configuration\n\n`animation` controls the transition when the value (and, if enabled, the needle) moves to a new reading:\n\n```jsx\n<AnedyaGauge animation={{ show: true, duration: 800, easing: \"elasticOut\" }} />\n```\n\n| Prop       | Type          | Default      | Description                                                |\n| ---------- | ------------- | ------------ | -------------------------------------------------------------- |\n| `show`     | `boolean`     | `true`       | Whether the bar (and needle, if also enabled) animates          |\n| `duration` | `number`      | `1000`       | Transition duration in ms                                       |\n| `easing`   | `GaugeEasing` | `\"cubicOut\"` | Any of the standard d3-ease families — `easeLinear`, `easeQuadIn`, `easeQuadOut`, `easeQuadInOut`, `easeCubicIn`, `easeCubicOut`, `easeCubicInOut`, `easeSinIn`, `easeSinOut`, `easeSinInOut`, `easeExpIn`, `easeExpOut`, `easeExpInOut`, `easeCircleIn`, `easeCircleOut`, `easeCircleInOut`, `easeBackIn`, `easeBackOut`, `easeBackInOut`, `easeElasticIn`, `easeElasticOut`, `easeElasticInOut`, `easeBounceIn`, `easeBounceOut`, `easeBounceInOut` |\n\nWith `animation.show: false`, both the bar and needle snap directly to the new value with no transition, regardless of `needle.animation`.\n\n---\n\n#### `onDataChange` — data-driven rendering\n\n`onDataChange` works the same way it does on `AnedyaCard` (see [`onDataChange` under AnedyaCard](#ondatachange--data-driven-rendering)) — it's called with the latest fetched reading and a `meta` object, and can return a partial set of props (including `styles`) that temporarily override the gauge's own props:\n\n**Example 1 – change many props based on value ranges**\n\n```tsx\n<AnedyaGauge\n  node={node}\n  variable=\"pressure\"\n  min={0}\n  max={100}\n  onDataChange={(data, meta) => {\n    if (!data || meta.kind !== \"success\") return;\n\n    if (data.value > 80) {\n      return {\n        title: \"🔴 High\",\n        color: \"#ef4444\",\n        theme: \"dark\",\n        arc: { startAngle: -135, endAngle: 135 },\n        needle: { type: \"triangle\", color: \"#dc2626\" },\n        animation: { duration: 500, easing: \"elasticOut\" },\n        tick: { show: true, color: \"#fca5a5\", labelColor: \"#f87171\" },\n        tooltip: {\n          content: ({ variable, displayValue, unit }) => (\n            <span className=\"text-red-400 font-semibold\">\n              ⚠ {variable}: {displayValue}{unit ? ` ${unit}` : \"\"} — critical\n            </span>\n          ),\n        },\n        styles: {\n          value: \"text-red-500 text-4xl font-black\",\n          title: \"uppercase tracking-wide\",\n        },\n      };\n    }\n    if (data.value < 20) {\n      return {\n        title: \"🟢 Low\",\n        color: [\"#10b981\", \"#34d399\"],\n        arc: { thickness: 18 },\n        needle: { show: false },\n        tooltip: { show: false }, // low readings aren't worth hovering for\n      };\n    }\n    // Clear overrides for mid-range values — tooltip reverts to the default\n  }}\n/>\n```\n\n**Example 2 – custom error and empty rendering**\n\n```tsx\n<AnedyaGauge\n  node={node}\n  variable=\"sensor\"\n  onDataChange={(data, meta) => {\n    if (meta.kind === \"error\") {\n      return {\n        renderError: (msg) => (\n          <div className=\"flex flex-col items-center gap-2\">\n            <span className=\"text-2xl\">⚠️</span>\n            <span className=\"text-sm text-orange-600 italic\">{msg}</span>\n          </div>\n        ),\n      };\n    }\n    if (meta.kind === \"empty\") {\n      return {\n        renderEmpty: () => (\n          <span className=\"text-slate-400 animate-pulse\">— waiting for first reading —</span>\n        ),\n      };\n    }\n    // No override on success\n  }}\n/>\n```\n\nThe only difference from Card is the data shape passed in — `GaugeData` (`{ value, timestamp }`) and `GaugeDataMeta` — but the resolution behavior (temporary override until the callback runs again; `undefined`/nothing clears it) is identical.\n\n---\n\n#### How props are resolved\n\nRendering follows the same layered order as `AnedyaCard`:\n\n1. The props you pass to `<AnedyaGauge />`\n2. Any temporary overrides returned from `onDataChange`\n3. Theme resolution (`\"light\"`, `\"dark\"`, or a custom `WidgetTheme<GaugeSlot>`)\n4. Per-slot class resolution\n\nFor each slot (`container`, `title`, `unit`, `track`, `bar`, `needle`, `needleCap`, `value`, `label`, `tick`, `tickLabel`, `error`, `empty`), classes are merged in this order:\n\n1. `GAUGE_DEFAULT_CLASSES`\n2. Active theme classes\n3. `styles`\n\nLater layers win whenever two Tailwind utilities conflict, resolved with `twMerge`. `className` is separate from this chain — merged onto the outer container after the container slot has been resolved.\n\n> **Rule of thumb:** defaults → theme → `styles` → `onDataChange`. User overrides always win.\n\n---\n\n### AnedyaLineChart\n\nA time-series line chart built on D3 — shows a variable's history as a line, with an optional gradient area fill, point markers, gridlines, a floating \"latest value\" badge, and a hover tooltip with a vertical crosshair.\n\n```jsx\n<AnedyaLineChart node={node} variable=\"humidity\" title=\"Humidity\" unit=\"%\" />\n```\n\n#### Required props\n\n| Prop | Type | Description |\n|---|---|---|\n| `node` | `any` | `anedya.newNode(client, nodeId)` |\n| `variable` | `string` | Variable name to fetch |\n\n---\n\n#### Data fetching\n\nCalls `node.getData({ variable, from, to, limit, order })` **and** `node.getLatestData(variable)` together, on mount and whenever `refresh` is clicked.\n\n| Prop | Type | Default | Description |\n|---|---|---|---|\n| `from` | `number` | one year before `to` | Start of the range, in **milliseconds** |\n| `to` | `number` | now | End of the range, in **milliseconds** |\n| `limit` | `number` | `1000` | Max data points fetched |\n| `order` | `\"asc\" \\| \"desc\"` | `\"asc\"` | Fetch order — deliberately different from the SDK's own `\"desc\"` default, since a chart reads left-to-right chronologically |\n| `refresh` | `boolean` | `true` | Show the refresh button, top-right of the toolbar |\n| `onRefresh` | `() => void` | — | Called after a manual refresh completes |\n\n**The \"latest value\" fetch always runs, even if the badge is hidden** — it also serves as a single-point fallback: if `getData` returns no points in the requested range but a live reading exists, the chart draws that one point instead of showing an empty state.\n\n```jsx\n<AnedyaLineChart\n  node={node}\n  variable=\"humidity\"\n  from={Date.now() - 24 * 60 * 60 * 1000}\n  to={Date.now()}\n/>\n```\n\n---\n\n#### The D3 passthrough model\n\nChart-drawing customizations are **real D3 objects, passed through unmodified** — if you already know D3, you already know how to customize this chart.\n\n```jsx\nimport * as d3 from \"d3\";\n\n<AnedyaLineChart\n  node={node}\n  variable=\"temperature\"\n  line={(line) => line.curve(d3.curveMonotoneX)}\n  xAxis={(axis) => axis.ticks(6).tickFormat(d3.timeFormat(\"%b %d\") as any)}\n/>\n```\n\n| Prop | Type | Description |\n|---|---|---|\n| `line` | `(line: d3.Line<LineChartDataPoint>) => d3.Line<LineChartDataPoint>` | Receives a `d3.line()` already bound to the data's x/y accessors — chain any D3 curve/method on it |\n| `xScale` | `(scale: d3.ScaleTime<number, number>) => ...` | Customize the time scale directly |\n| `yScale` | `(scale: d3.ScaleLinear<number, number>) => ...` | Customize the value scale directly |\n| `xAxis` | `(axis: d3.Axis<...>) => ...` | Customize the x-axis generator directly |\n| `yAxis` | `(axis: d3.Axis<...>) => ...` | Customize the y-axis generator directly |\n\n**Note on data with long flat stretches** (e.g. a sensor reporting the same value repeatedly): the line can visually look \"stepped\" even with the default `curveLinear` — that's the data, not the curve. Test curve changes against data with genuine variation to see the effect clearly.\n\n**X-axis labels auto-rotate** when they'd otherwise overlap at the current tick spacing — short default labels stay horizontal; longer custom formats rotate automatically. No configuration needed.\n\n---\n\n#### Tooltip\n\nOn by default, with built-in content, pointer-following positioning, and a vertical dashed crosshair line at the hovered point.\n\n```jsx\n<AnedyaLineChart\n  node={node}\n  variable=\"humidity\"\n  tooltip={{ content: (d) => <span className=\"text-red-400\">{d.value}% RH</span> }}\n/>\n```\n\n| Prop | Type | Description |\n|---|---|---|\n| `show` | `boolean` | Default `true` |\n| `content` | `(d: LineChartDataPoint) => ReactNode` | Custom content, keeps the widget's built-in positioning + crosshair |\n| `onMouseOver` / `onMouseMove` / `onMouseOut` | Raw D3 event handlers `(event: MouseEvent, d: LineChartDataPoint) => void` | Full manual control — same signatures you'd pass to `.on(\"mouseover\", ...)` on a real D3 selection. Providing any of these opts you out of the built-in tooltip **and** crosshair entirely. |\n\n---\n\n#### Area, points & grid\n\n```jsx\n<AnedyaLineChart\n  node={node}\n  variable=\"humidity\"\n  area={{ show: true, opacity: 0.35 }}\n  point={{ show: true, radius: 4 }}\n  grid={{ ticksY: 4, ticksX: 6 }}\n/>\n```\n\n| Prop | Type | Description |\n|---|---|---|\n| `area.show` | `boolean` | Gradient fill under the line, fading to transparent. Default `false` |\n| `area.opacity` | `number` | Top-of-gradient opacity. Default `0.35` |\n| `point.show` | `boolean` | Dot at each data point. Default `false` |\n| `point.radius` | `number` | Dot radius in px. Default `3` |\n| `grid.show` | `boolean` | Background gridlines (both axes). Default `true` |\n| `grid.ticksX` / `grid.ticksY` | `number` | Approximate gridline count per axis. Default `5` each |\n\nThe area's gradient color follows `currentColor`, same as the line — set both together via `styles`:\n```jsx\n<AnedyaLineChart styles={{ line: \"text-[#42a5f5]\", area: \"text-[#42a5f5]\" }} area={{ show: true }} />\n```\n\n---\n\n#### Latest value badge\n\nA floating pill pinned to the top-right corner, showing the most recent reading — separate from the hover tooltip, always visible (when enabled) regardless of mouse position.\n\n```jsx\n<AnedyaLineChart node={node} variable=\"humidity\" showLatestValue={false} />\n```\n\n| Prop | Type | Default | Description |\n|---|---|---|---|\n| `showLatestValue` | `boolean` | `true` | Whether the badge is shown. The `getLatestData` call still always runs regardless — this only controls visibility |\n\n---\n\n#### Min / Avg / Max summary\n\n```jsx\n<AnedyaLineChart node={node} variable=\"humidity\" summary />\n```\n\nShows a row below the chart with the minimum, average, and maximum values from the fetched range, including timestamps for min/max. Computed from the real range data only — not shown when the chart is displaying the single-point fallback.\n\n> If you pass a fixed `height` alongside `summary`, add roughly 44px extra to leave room for the summary row — otherwise it may be clipped.\n\n---\n\n#### Sizing\n\nWithout an explicit `width`/`height`, the chart fills its container's width and derives a proportional height, capped at a sensible maximum (`320px` by default) so it doesn't grow unreasonably large inside a very wide container.\n\n```jsx\n<AnedyaLineChart node={node} variable=\"humidity\" width={600} height={280} />\n```\n\n| Prop | Type | Description |\n|---|---|---|\n| `width` / `height` | `number` | Explicit size in px |\n| `minWidth` / `maxWidth` | `number` | Width bounds |\n| `minHeight` / `maxHeight` | `number` | Height bounds — `maxHeight` also caps the default auto-computed height |\n\n---\n\n#### `onDataChange` — data-driven rendering\n\n```jsx\n<AnedyaLineChart\n  node={node}\n  variable=\"humidity\"\n  onDataChange={(data, meta) => {\n    if (meta.kind === \"error\") {\n      return { renderError: () => <span className=\"italic\">Offline</span> };\n    }\n    if (!data) return;\n    const max = Math.max(...data.map((d) => d.value));\n    if (max > 90) return { title: \"⚠ Peak humidity detected\" };\n  }}\n/>\n```\n\nCalled whenever the fetched dataset changes; receives the full array of points (not a single value) plus a `meta` object (`{ kind: \"success\" | \"error\" | \"empty\", error? }`). Returns a partial set of props that temporarily override the chart's own props — same resolution model as `AnedyaCard`/`AnedyaGauge`.\n\n---\n\n## Common\n\nThe sections below apply to every widget in this SDK. Where behavior is identical, only the shared explanation is given once; each subsection includes an example for both `AnedyaCard` and `AnedyaGauge`.\n\n### Automatic theme detection\n\nBy default (no `theme` or `styles` prop set), every widget tries to match the colors — and, for `AnedyaCard`, the border radius — of the app it's rendered in, before falling back to its own built-in palette.\n\nThis works by reading standard CSS custom properties, in this priority order:\n\n1. **shadcn/ui convention** — `--card`, `--card-foreground`, `--border`, `--muted-foreground`, `--primary`, `--destructive`, `--radius`. If your app uses shadcn/ui (or any design system defining these same variable names), the widget matches it automatically with zero configuration.\n2. **MUI CSS-variables mode** — `--mui-palette-background-paper`, `--mui-palette-text-primary`, `--mui-palette-text-secondary`, `--mui-palette-divider`, `--mui-palette-primary-main`, `--mui-palette-error-main`. Only present if your MUI theme was created with `cssVariables: true` (MUI v5.15+/v6) — classic MUI setups (CSS-in-JS only) don't expose anything to detect here.\n3. **The widget's own built-in colors** — used whenever neither of the above are present.\n\n```jsx\n// No theme, no styles — automatically matches shadcn's card colors and\n// radius if the app defines --card/--card-foreground/--border/\n// --muted-foreground/--radius.\n<AnedyaCard node={node} variable=\"humidity\" />\n```\n\n#### What this does and doesn't cover\n\n- **Colors and border radius only.** Spacing (padding, gap) and typography scale are governed entirely by each widget's own responsive (`clamp()`-based) sizing system — see [Responsive sizing](#responsive-sizing) — and are not part of auto-detection. A widget matched to your app's colors may still have different padding/height than a native component in that design system, since the two use independent spacing logic.\n- **Tooltips are intentionally excluded** (`AnedyaGauge`'s `tooltip` slot). Tooltip colors are always a fixed high-contrast inversion (dark background + light text in light mode, and vice versa in dark mode), regardless of the app's theme — this guarantees the tooltip stays readable even if the app's `--popover`/`--card` colors happen to be close to the page background.\n- **Only covers design systems that expose their palette as CSS custom properties** under the specific variable names listed above. Plain Tailwind apps with no design-system layer, and MUI setups not using CSS-variables mode, have no equivalent \"app theme\" for the widget to detect — for these, use `theme`/`styles` to style the widget manually, exactly as you always could.\n- **Explicit props always win.** Passing `theme=\"light\"` / `theme=\"dark\"` / a custom `WidgetTheme` object bypasses auto-detection entirely for that widget instance. Passing `styles` overrides individual slots regardless of what auto-detection resolved to. Auto-detection is only ever the *lowest*-priority layer — see [How props are resolved](#how-props-are-resolved).\n\nIf your app uses a different design system with its own CSS-variable convention (not shadcn or MUI), you can still get automatic matching by defining your app's variables under the names above — e.g. setting `--card`/`--card-foreground` at your own `:root` to alias your system's real tokens — since the widget doesn't know or care which library originally defined them.\n\n### Formatting vs rendering\n\n`format`, `formatOptions`, `decimalPlaces`, and `labelFormat` control **what text is displayed**.\n\n`styles` controls **how the widget looks**.\n\n`onDataChange` can override any of these (along with most other widget props), allowing the widget to react to incoming data.\n\n#### `format` — named presets for common unit types\n\nFor values that need unit-aware scaling (bytes, durations, lengths, etc.), pass a `format` preset instead of manually computing a unit string:\n\n```tsx\n// AnedyaCard\n<AnedyaCard node={node} variable=\"freeMemory\" format=\"bytes\" />\n// renders \"512\" + \"MB\" — auto-scaled, no manual unit needed\n\n<AnedyaCard node={node} variable=\"uptime\" format=\"duration\" />\n// renders \"2d 4h 12m\"\n\n<AnedyaCard node={node} variable=\"distance\" format=\"length\" formatOptions={{ formatOptions: 2 }} />\n// renders \"1.25\" + \"km\"\n```\n\n```tsx\n// AnedyaGauge\n<AnedyaGauge node={node} variable=\"freeMemory\" format=\"bytes\" min={0} max={8_000_000_000} />\n// renders \"512\" + \"MB\" on the value, and (if tick.show is on) scaled units on tick labels too\n\n<AnedyaGauge node={node} variable=\"uptime\" format=\"duration\" min={0} max={86400} />\n// renders \"2d 4h 12m\"\n```\n\nAvailable presets:\n\n| Preset     | Input           | Output example                                                                                  |\n| ---------- | --------------- | ----------------------------------------------------------------------------------------------- |\n| `number`   | any             | `\"123,456\"` — locale-aware thousands separators                                                 |\n| `bytes`    | bytes           | `\"500\"` + `\"KB\"` — auto-scales B/KB/MB/GB/TB (or KiB/MiB/GiB with `formatOptions.binary: true`) |\n| `duration` | seconds         | `\"2h 15m 30s\"`                                                                                  |\n| `length`   | meters          | `\"1.25\"` + `\"km\"` — auto-scales mm/cm/m/km                                                      |\n| `volume`   | milliliters     | auto-scales mL/L                                                                                |\n| `dataRate` | bits per second | auto-scales bps/Kbps/Mbps                                                                       |\n| `percent`  | 0–100           | `\"45%\"`                                                                                          |\n\n`formatOptions` accepts:\n\n```ts\n{\n  locale?: string;      // BCP 47 tag, e.g. \"en-IN\" — defaults to browser locale\n  binary?: boolean;     // bytes only: 1024-based (KiB/MiB) vs 1000-based (KB/MB)\n  formatOptions?: number;   // decimal places for the scaled number\n}\n```\n\n**When to use each option:**\n\n- **`locale`** — force a specific number format instead of relying on the visitor's browser/OS locale. Useful for a dashboard serving a specific region, or an app with its own explicit language setting:\n```tsx\n  <AnedyaCard format=\"number\" formatOptions={{ locale: \"en-IN\" }} />\n  // \"1,23,456\" (Indian digit grouping) instead of \"123,456\"\n\n  <AnedyaGauge format=\"number\" formatOptions={{ locale: \"de-DE\" }} min={0} max={200000} />\n  // \"123.456\" (German uses \".\" as the thousands separator, \",\" as the decimal point)\n```\n\n- **`formatOptions`** — control decimal places in the *scaled* output (not the raw value). Useful when the default rounding is too coarse for scientific/financial data, or you want whole numbers for a cleaner dashboard look:\n```tsx\n  <AnedyaCard format=\"bytes\" formatOptions={{ formatOptions: 2 }} />\n  // \"512.34\" + \"MB\" — more decimal places than the default\n\n  <AnedyaGauge format=\"bytes\" formatOptions={{ formatOptions: 0 }} min={0} max={1_000_000_000} />\n  // \"512\" + \"MB\" — whole number, no decimals\n```\n\n- **`binary`** — *(`bytes` preset only)* choose between 1000-based and 1024-based scaling. These genuinely produce different numbers for the same raw byte count, so picking the wrong one can make a value look incorrect even though the math is technically right for the mode you're in:\n```tsx\n  <AnedyaCard format=\"bytes\" formatOptions={{ binary: true }} />\n  // raw 1048576 -> \"1\" + \"MiB\" — matches how OS file managers/RAM usage are usually reported\n\n  <AnedyaCard format=\"bytes\" />\n  // raw 1048576 -> \"1.05\" + \"MB\" — matches how storage manufacturers/network specs are usually reported\n```\n  As a rule of thumb: use `binary: true` for memory/RAM/file-size-on-disk readings, and the default (decimal) for network throughput or storage capacity marketing figures.\n\n**`format` takes over the `unit` slot entirely.** When set, the preset determines its own unit per value (e.g. switching from `\"KB\"` to `\"MB\"` as a byte count grows) — the `unit` prop is ignored while `format` is active. On `AnedyaGauge`, if `tick.labelFormat` isn't set, enabled tick labels also reuse the active `format` preset, so the value and the tick ring stay unit-consistent.\n\n> **Presets don't validate that they match your data's actual meaning** — they only know how to scale a raw number. Applying `format=\"length\"` to a Celsius reading, for example, will scale it as if it were meters and display a plausible-looking but semantically wrong unit. Only use a preset that matches what the underlying value actually represents.\n\n#### Precedence\n\n`formatValue` always wins if provided — it's a full custom formatting function and bypasses `format`/`decimalPlaces` entirely:\n\n```tsx\n// AnedyaCard\n<AnedyaCard\n  formatValue={(v) => `${v.toFixed(2)} % RH`}\n  labelText={(ts) => `As of ${new Date(ts).toLocaleDateString()}`}\n/>\n```\n\n```tsx\n// AnedyaGauge\n<AnedyaGauge\n  formatValue={(v) => `${v.toFixed(1)}%`}\n  labelText={(ts) => `As of ${new Date(ts).toLocaleDateString()}`}\n/>\n```\n\n> Long `formatValue` output (e.g. `\"56.00 % RH\"`) may wrap onto a second line at small widths — the value slot wraps rather than overflowing. On `AnedyaCard` this interacts with a fixed `height` — see [Sizing](#sizing) below. On `AnedyaGauge`, the value block sits below (or centered over, in needle-less mode) the arc and wraps the same way within the available space.\n\n---\n\n### The last-updated label\n\nBy default, the label under the value shows a fixed clock time (`\"Updated 14:32:10\"`). This can be changed with `labelFormat`, or fully replaced with `labelText`.\n\n#### `timezone` — override auto-detection\n\n```tsx\n<AnedyaCard {...commonProps} labelFormat=\"datetime\" timezone=\"America/New_York\" />\n// commonProps = { node, variable: \"humidity\" }\n// shows the timestamp converted to Eastern time, regardless of the visitor's own browser timezone\n\n<AnedyaGauge {...commonProps} labelFormat=\"datetime\" timezone=\"Asia/Kolkata\" />\n// commonProps = { node, variable: \"humidity\" }\n// shows the timestamp converted to Indian Standard Time\n```\n\nIf omitted, the widget uses `Intl.DateTimeFormat().resolvedOptions().timeZone` — the browser's own detected timezone. Pass any IANA timezone name (e.g. `\"Asia/Kolkata\"`, `\"Europe/London\"`) to force a specific one instead — useful for dashboards where all viewers should see times in the device's local timezone rather than their own.\n\n`timezone` affects the `time`, `date`, and `datetime` label presets. It has no effect on `relative` (a time difference is timezone-independent) or `iso` (ISO 8601 output is always UTC by definition).\n\n+> **In manual `value` mode** (either widget, no `node` provided): there's no fetched timestamp to show. The label falls back to the moment the widget first mounted, so it still renders something meaningful instead of disappearing.\n\n#### `labelFormat` — named presets\n\n```tsx\n<AnedyaCard {...commonProps} labelFormat=\"relative\" />\n// commonProps = { node, variable: \"humidity\" }\n// \"5 minutes ago\" instead of \"Updated 14:32:10\"\n\n<AnedyaCard {...commonProps} labelFormat=\"date\" />\n// commonProps = { node, variable: \"humidity\" }\n// \"Updated 7/24/2026\"\n\n<AnedyaGauge {...commonProps} labelFormat=\"relative\" />\n// commonProps = { node, variable: \"humidity\" }\n// \"5 minutes ago\" instead of \"Updated 14:32:10\"\n```\n\nAvailable presets:\n\n| Preset           | Output example                    |\n| ---------------- | ---------------------------------- |\n| `time` (default) | `\"Updated 14:32:10\"`               |\n| `date`           | `\"Updated 7/24/2026\"`              |\n| `datetime`       | `\"Updated 7/24/2026, 2:32:10 PM\"`  |\n| `relative`       | `\"5 minutes ago\"`                  |\n| `iso`            | `\"2026-07-24T14:32:10.000Z\"`       |\n\n#### `labelText` — full custom control\n\n`labelText` always takes precedence over `labelFormat` and receives the raw timestamp, so you can use it for anything the presets don't cover — 12-hour/AM-PM formatting, hours-only, non-default locales, or combining relative time with custom surrounding text:\n\n```tsx\n// AM/PM, 12-hour\n<AnedyaCard\n  labelText={(ts) => {\n    const d = new Date(ts < 1e12 ? ts * 1000 : ts);\n    return `Updated ${d.toLocaleTimeString(\"en-US\", { hour: \"numeric\", minute: \"2-digit\", hour12: true })}`;\n  }}\n/>;\n// \"Updated 2:32 PM\"\n\n// Combining relative time with custom text\nimport { relativeTime } from \"@anedyasystems/anedya-widgets-react/formatters\";\n\n<AnedyaCard labelText={(ts) => `Last synced ${relativeTime(ts)}`} />;\n// \"Last synced 5 minutes ago\"\n\n<AnedyaGauge labelText={(ts) => `Last synced ${relativeTime(ts)}`} />;\n// \"Last synced 5 minutes ago\"\n```\n\n`labelFormat` covers the common cases with zero code; `labelText` is the general escape hatch for everything else — the same relationship `formatValue` has with `format`.\n\n---\n\n### Error & empty states\n\nEvery widget distinguishes between three outcomes of a fetch, each rendered differently:\n\n| State | When it happens | Default appearance |\n|---|---|---|\n| Success | Data was fetched normally | The formatted value, unit, and label |\n| Error | The fetch failed (network error, bad request, etc.) | The error message, styled via the `error` slot |\n| Empty | The fetch succeeded, but no data exists yet for this variable | `\"N/A\"`, styled via the `empty` slot |\n\nBoth `error` and `empty` are real slots, so they pick up theme colors automatically and can be restyled the same way as any other slot:\n\n```tsx\n<AnedyaCard\n  {...commonProps}\n  styles={{\n    error: \"text-orange-500 italic\",\n    empty: \"text-slate-300\",\n  }}\n/>\n\n<AnedyaGauge\n  {...commonProps}\n  styles={{\n    error: \"text-orange-500 italic\",\n    empty: \"text-slate-300\",\n  }}\n/>\n\n//commonProps = { node, variable: \"humidity\" }\n```\n\n#### `renderError` / `renderEmpty` — full custom rendering\n\nFor full control beyond just restyling text, `renderError` and `renderEmpty` let you replace the entire error/empty state with your own JSX:\n\n```tsx\n<AnedyaCard\n  {...commonProps}\n  renderError={(error) => (\n    <div className=\"flex flex-col items-center gap-1\">\n      <span className=\"text-orange-500\">⚠ Sensor unreachable</span>\n      <span className=\"text-xs text-slate-400\">{error}</span>\n    </div>\n  )}\n  renderEmpty={() => <span className=\"text-slate-300\">— no reading yet —</span>}\n/>\n\n<AnedyaGauge\n  {...commonProps}\n  renderError={(error) => (\n    <div className=\"flex flex-col items-center gap-1\">\n      <span className=\"text-orange-500\">⚠ Sensor unreachable</span>\n      <span className=\"text-xs text-slate-400\">{error}</span>\n    </div>\n  )}\n  renderEmpty={() => <span className=\"text-slate-300\">— no reading yet —</span>}\n/>\n\n// commonProps = { node, variable: \"humidity\" }\n```\n\n`renderError` receives the raw error message string; `renderEmpty` takes no arguments. When either is provided, it fully replaces the default `error`/`empty` slot rendering — the corresponding `styles.error`/`styles.empty` classes are ignored in that case, since there's no default element left for them to apply to.\n\n#### Reacting to errors/empty state via `onDataChange`\n\n`onDataChange` receives a second argument describing which state just occurred, so you can react to errors or empty data the same way you already react to values:\n\n```tsx\n<AnedyaCard\n  {...commonProps}\n  onDataChange={(data, meta) => {\n    if (meta.kind === \"error\") {\n      return { renderError: () => <span className=\"italic\">Offline</span> };\n    }\n    if (meta.kind === \"empty\") {\n      return { renderEmpty: () => <span className=\"text-slate-300\">Waiting for first reading…</span> };\n    }\n    if (!data) return;\n    if (data.value > 80) {\n      return { title: \"High Humidity\", theme: \"dark\" };\n    }\n  }}\n/>\n\n<AnedyaGauge\n  {...commonProps}\n  onDataChange={(data, meta) => {\n    if (meta.kind === \"error\") {\n      return { renderError: () => <span className=\"italic\">Offline</span> };\n    }\n    if (meta.kind === \"empty\") {\n      return { renderEmpty: () => <span className=\"text-slate-300\">Waiting for first reading…</span> };\n    }\n    if (!data) return;\n    if (data.value > 80) {\n      return { color: \"#ef4444\", theme: \"dark\" };\n    }\n  }}\n/>\n\n// commonProps = { node, variable: \"humidity\" }\n```\n\n`meta.kind` is one of `\"success\"`, `\"error\"`, or `\"empty\"`; `meta.error` is present only when `meta.kind === \"error\"`, carrying the underlying error message.\n\nThis is backwards compatible — existing `onDataChange={(data) => {...}}` callbacks that ignore the second argument continue to work unchanged.\n\n---\n\n### Formatters export\n\nThe formatting helpers used internally are also available as a standalone import, for composing into your own `labelText`/`formatValue` functions — usable with either widget:\n\n```tsx\nimport { relativeTime } from \"@anedyasystems/anedya-widgets-react/formatters\";\n```\n\n| Export         | Signature                                        | Description                                                                                             |\n| -------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- |\n| `relativeTime` | `(timestamp: number, locale?: string) => string` | Converts a timestamp into a human-relative string (`\"5 minutes ago\"`). Accepts seconds or milliseconds. |\n\n---\n\n### Sizing\n\n#### AnedyaCard\n\n`width` / `height` / `minWidth` / `maxWidth` are plain props, applied as inline styles on the container — independent of the whole `styles`/theme system:\n\n```jsx\n<AnedyaCard width={320} height={200} minWidth={280} maxWidth={400} />\n```\n\n##### `height` has two distinct modes\n\nWhether you pass `height` changes how the card behaves — this is intentional, not a quirk:\n\n|                      | Behavior                                                                                                                                                                                                                                                                                                |\n| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **`height` omitted** | The card **auto-grows** to fit its content. If `formatValue`/`labelText` produce long text that wraps, the card simply becomes taller. Sizing scales with **width only**.                                                                                                                               |\n| **`height` passed**  | The card becomes a **fixed-size box**. Sizing scales with **both width and height** (see [Responsive sizing](#responsive-sizing)). If content is too tall to fit (e.g. a long wrapped value at a small height), it is **clipped**, not overflowed — the card will not grow past the height you gave it. |\n\n```jsx\n// Auto-height: card grows to fit content, whatever that content turns out to be\n<AnedyaCard node={node} variable=\"humidity\" />\n\n// Fixed-height: card is exactly 300px tall; content that doesn't fit is clipped\n<AnedyaCard node={node} variable=\"humidity\" height={300} width={700} />\n```\n\nIf you're using a `formatValue` that can produce long strings, either don't pass a fixed `height` (let the card grow to fit), or pass a `height` generous enough for the wrapped text at your chosen `width`.\n\n#### AnedyaGauge\n\n```jsx\n<AnedyaGauge width={280} height={200} />\n<AnedyaGauge size={220} />   // shorthand: sets width AND height to the same value (a square gauge)\n```\n\n`size` takes priority over `width`/`height` when both are set. Without `size` or `height`, the gauge fills its container's width and derives a proportional height automatically (unless `aspectRatio` is set).\n\n| Prop          | Type     | Description                                                             |\n| ------------- | -------- | -------------------------------------------------------------------------- |\n| `size`        | `number` | Shorthand for equal `width`/`height` — a fixed square gauge                |\n| `width`       | `number` | Container width in px                                                      |\n| `height`      | `number` | Container height in px                                                     |\n| `minWidth`    | `number` | Minimum container width in px                                              |\n| `maxWidth`    | `number` | Maximum container width in px                                              |\n| `minHeight`   | `number` | Minimum container height in px                                             |\n| `maxHeight`   | `number` | Maximum container height in px                                             |\n| `aspectRatio` | `number` | Width-to-height ratio used when neither `size` nor `height` is set         |\n\nUnlike Card, the gauge doesn't clip content past a fixed `height` — instead, the arc's radius and internal spacing shrink to fit whatever box is available (see [Responsive sizing](#responsive-sizing) below).\n\n---\n\n### Responsive sizing\n\nThe widget automatically scales its internal spacing and typography using **CSS Container Queries** — based on the widget's own rendered size, not the browser viewport.\n\nBy default the following properties scale automatically:\n\n- container padding\n- title font size\n- value font size\n- unit font size\n- label font size\n- spacing (`gap`)\n- arc thickness (gauge bar thickness)\n\n**When `height` is omitted**, scaling responds to the container's **width only** (`cqw`).\n\n**When `height` is passed**, scaling responds to **both width and height** (`cqw` + `cqh`) — a taller fixed-size container renders bigger text/gaps than a shorter one at the same width, in addition to width's usual effect. For the `AnedyaGauge`, the arc radius and thickness are also computed from the available container size, so they resize automatically.\n\nA widget inside a narrow dashboard column will use smaller typography than the same widget rendered full-width, even on the same screen — this behavior is built in and needs no configuration.\n\n#### Overriding responsive sizing\n\nIf you provide your own font-size utility for a slot, you're opting out of the default responsive sizing for that slot.\n\n```tsx\n// AnedyaCard\n<AnedyaCard\n  styles={{\n    value: \"text-6xl\",\n  }}\n/>\n\n// AnedyaGauge\n<AnedyaGauge\n  styles={{\n    value: \"text-6xl\",\n  }}\n/>\n```\n\nThe value will always render at `text-6xl`. Likewise, responsive Tailwind classes (`text-2xl md:text-5xl`) take precedence over the widget's default responsive sizing — your classes always win over the widget defaults.\n\n> **AnedyaGauge note:** the arc's radius itself is handled separately from these CSS variables (it's a geometry calculation, not a font size) — it auto-fits to whatever space is left after padding and, if enabled, tick-mark space is subtracted, capped by `arc.radius` when you set one explicitly.\n\n---\n\n### Loading & error states\n\n#### AnedyaCard\n\nWhile the initial fetch is in flight, the card renders **skeleton loaders** in place of the value and label — pulsing placeholder blocks sized to match each slot's current font-size variable (`var(--anedya-card-value-size)` / `var(--anedya-card-label-size)`), so the skeleton scales along with the card exactly like the real content would.\n\nIf the fetch fails or no data is available, the card renders the error message text in place of the value (title still renders normally).\n\nNeither state currently accepts style overrides via `styles` — flag it if you need to customize their appearance.\n\n#### AnedyaGauge\n\nWhile the initial fetch is in flight, the gauge renders the same kind of pulsing skeleton blocks in place of the value and label (sized against `var(--anedya-gauge-value-size)` / `var(--anedya-gauge-label-size)`), and the arc/needle themselves render at reduced opacity rather than disappearing, so the gauge's shape stays visible while data loads.\n\nIf the fetch fails or no data is available, the error message (or `renderError`/`renderEmpty` output) replaces the value/label block; the arc still renders behind it at the gauge's minimum value.\n\n---\n\n### Other props\n\n#### AnedyaCard\n\n| Prop            | Type     | Description                                                                        |\n| --------------- | -------- | ------------------------------------------------------------------------------------ |\n| `title`         | `string` | Card title. Default: `\"Latest Value\"`                                                |\n| `value`         | `number` | Manual/controlled value. Used as the initial value, or the only value if no `node` is given |\n| `unit`          | `string` | Unit suffix shown next to the value                                                  |\n| `decimalPlaces` | `number` | Decimal places for the displayed value (used only if `formatValue` isn't provided)   |\n\n#### AnedyaGauge\n\n| Prop            | Type      | Description                                                                                    |\n| --------------- | --------- | -------------------------------------------------------------------------------------------------- |\n| `title`         | `string`  | Gauge title. Default: `\"Latest Value\"`                                                              |\n| `value`         | `number`  | Manual/controlled value. Used as the initial value, or the only value if no `node` is given         |\n| `min`           | `number`  | Minimum of the gauge's range. Default: `0`                                                          |\n| `max`           | `number`  | Maximum of the gauge's range. Default: `100`                                                        |\n| `unit`          | `string`  | Unit suffix shown next to the value                                                                 |\n| `decimalPlaces` | `number`  | Decimal places for the displayed value (used only if `formatValue` isn't provided)                  |\n\n---\n\n## Stylesheet import\n\nThis SDK ships pre-compiled CSS alongside its JavaScript — you don't need Tailwind configured in your own project for these widgets to render correctly, even if you don't use Tailwind at all.\n\n**Import the stylesheet once**, anywhere in your app's entry point (e.g. `main.tsx`, `App.tsx`, or your global styles file):\n\n```jsx\nimport \"@anedyasystems/anedya-widgets-react/styles.css\";\n```\n\nThat's the entire integration on your end. No `content` glob changes to your `tailwind.config`, no build coordination, nothing else required — every widget in this SDK will render fully styled as soon as this import is present.\n\n> **If styles aren't appearing:** double-check the import path matches your installed package name exactly, and that it's imported somewhere that actually runs before your widgets render (e.g. not inside a conditionally-loaded file). If you're using a bundler with strict CSS module resolution, confirm it supports subpath imports from `node_modules` (most modern bundlers — Vite, Webpack 5+, Next.js — do, out of the box).","readmeFilename":"README.md"}