@camstack/ui-library 1.2.342 → 1.2.343

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.343] - 2026-10-28
11
+
12
+ ### Changed
13
+
14
+ - Device value fields are named after their quantity instead of their unit; run the quantity-field migration after updating
15
+
10
16
  ## [1.2.342] - 2026-10-28
11
17
 
12
18
  ### Changed
@@ -15,7 +15,7 @@ import type { JSX } from "react";
15
15
  export interface BatteryStatusLike {
16
16
  /** `null` means no reading has landed yet (cold seed) — NOT a flat 0 %
17
17
  * (D315). Render "Unknown", never a red critical badge. */
18
- readonly percentage: number | null;
18
+ readonly level: number | null;
19
19
  readonly charging: "dc" | "solar" | "none" | "unknown";
20
20
  readonly sleeping: boolean;
21
21
  /** True for a binary LOW_BAT source with no real % — render "Normal"/"Low"
@@ -1,7 +1,7 @@
1
1
  import { type JSX } from "react";
2
2
  export interface NetworkLinkStatusLike {
3
3
  readonly type: "wifi" | "ethernet" | "cellular" | "unknown";
4
- readonly signalPercent: number | null;
4
+ readonly signal: number | null;
5
5
  readonly ssid?: string;
6
6
  }
7
7
  export interface NetworkLinkBadgeProps {
@@ -10,8 +10,8 @@ import { type UseDeviceProxyTrpc } from "./use-device-proxy";
10
10
  export interface NetworkLinkStatus {
11
11
  readonly type: "wifi" | "ethernet" | "cellular" | "unknown";
12
12
  /** `null` = not known or not applicable (a wired link has no signal). */
13
- readonly signalPercent: number | null;
14
- readonly rssiDbm?: number;
13
+ readonly signal: number | null;
14
+ readonly rssi?: number;
15
15
  readonly ssid?: string;
16
16
  readonly lastUpdated: number;
17
17
  }
package/dist/index.cjs CHANGED
@@ -36006,7 +36006,7 @@ function levelColor(percentage, charging) {
36006
36006
  function BatteryBadge({ status, variant = "compact", className }) {
36007
36007
  const t = useUiText();
36008
36008
  if (!status) return null;
36009
- const { percentage, charging, sleeping, binary } = status;
36009
+ const { level: percentage, charging, sleeping, binary } = status;
36010
36010
  if (percentage === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
36011
36011
  className: cn("inline-flex items-center gap-1 text-[10px] font-medium text-foreground-subtle", className),
36012
36012
  title: t("uiLibrary.batteryBadge.titleUnknown", "Battery: unknown"),
@@ -36169,15 +36169,15 @@ function NetworkLinkBadge({ status, className }) {
36169
36169
  title: t("uiLibrary.networkLinkBadge.ethernet", "Network: ethernet"),
36170
36170
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Cable, { className: "h-3.5 w-3.5" })
36171
36171
  });
36172
- const Icon = status.type === "cellular" ? RadioTower : pickWifiIcon(status.signalPercent);
36173
- const signal = status.signalPercent === null ? null : Math.round(status.signalPercent);
36172
+ const Icon = status.type === "cellular" ? RadioTower : pickWifiIcon(status.signal);
36173
+ const signal = status.signal === null ? null : Math.round(status.signal);
36174
36174
  const networkText = status.ssid ? t("uiLibrary.networkLinkBadge.typeSsid", "Network: {{type}} ({{ssid}})", {
36175
36175
  type: status.type,
36176
36176
  ssid: status.ssid
36177
36177
  }) : t("uiLibrary.networkLinkBadge.type", "Network: {{type}}", { type: status.type });
36178
36178
  const title = signal === null ? networkText : `${networkText} · ${t("uiLibrary.networkLinkBadge.signal", "signal {{percent}}%", { percent: signal })}`;
36179
36179
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
36180
- className: cn("inline-flex items-center gap-1 text-[10px] font-medium", signalColor(status.signalPercent), className),
36180
+ className: cn("inline-flex items-center gap-1 text-[10px] font-medium", signalColor(status.signal), className),
36181
36181
  title,
36182
36182
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { className: "h-3.5 w-3.5" }), signal !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [signal, "%"] })]
36183
36183
  });
@@ -47427,16 +47427,16 @@ function BrightnessPanel({ trpc, deviceId, layout, optimistic }) {
47427
47427
  min: 0,
47428
47428
  max: 100,
47429
47429
  step: 1,
47430
- value: display.percentage,
47430
+ value: display.level,
47431
47431
  disabled: busy,
47432
47432
  onChange: (e) => {
47433
47433
  const pct = Number(e.target.value);
47434
- applyOptimistic({ percentage: pct });
47435
- if (!busy) cap?.setBrightness({ percentage: pct });
47434
+ applyOptimistic({ level: pct });
47435
+ if (!busy) cap?.setBrightness({ level: pct });
47436
47436
  }
47437
47437
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
47438
47438
  className: "tabular-nums w-8 text-right",
47439
- children: [display.percentage, "%"]
47439
+ children: [display.level, "%"]
47440
47440
  })]
47441
47441
  });
47442
47442
  }
@@ -47467,17 +47467,17 @@ function FanPanel({ trpc, deviceId, features, layout, optimistic }) {
47467
47467
  min: 0,
47468
47468
  max: 100,
47469
47469
  step,
47470
- value: display.percentage ?? 0,
47470
+ value: display.speed ?? 0,
47471
47471
  disabled: busy,
47472
47472
  onChange: (e) => {
47473
47473
  const raw = Number(e.target.value);
47474
47474
  const pct = Math.min(100, Math.max(0, Math.round(raw / step) * step));
47475
- applyOptimistic({ percentage: pct });
47476
- if (!busy) cap?.setPercentage({ percentage: pct });
47475
+ applyOptimistic({ speed: pct });
47476
+ if (!busy) cap?.setPercentage({ speed: pct });
47477
47477
  }
47478
47478
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
47479
47479
  className: "tabular-nums w-8 text-right",
47480
- children: [display.percentage ?? "—", "%"]
47480
+ children: [display.speed ?? "—", "%"]
47481
47481
  })]
47482
47482
  }), showPreset && display.availablePresets.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
47483
47483
  className: "flex items-center gap-2 text-xs text-foreground-subtle",
@@ -48565,25 +48565,25 @@ function hasBatteryNumericValue(slice, field) {
48565
48565
  var ROLE_DESCRIPTOR = {
48566
48566
  "temperature-sensor": {
48567
48567
  kind: "numeric",
48568
- field: "celsius",
48568
+ field: "temperature",
48569
48569
  quantity: "temperature",
48570
48570
  getSliceHandle: (s) => s.temperatureSensor
48571
48571
  },
48572
48572
  "humidity-sensor": {
48573
48573
  kind: "numeric",
48574
- field: "percent",
48574
+ field: "humidity",
48575
48575
  quantity: "humidity",
48576
48576
  getSliceHandle: (s) => s.humiditySensor
48577
48577
  },
48578
48578
  "ambient-light-sensor": {
48579
48579
  kind: "numeric",
48580
- field: "lux",
48580
+ field: "illuminance",
48581
48581
  quantity: "illuminance",
48582
48582
  getSliceHandle: (s) => s.ambientLightSensor
48583
48583
  },
48584
48584
  "pressure-sensor": {
48585
48585
  kind: "numeric",
48586
- field: "hpa",
48586
+ field: "pressure",
48587
48587
  quantity: "pressure",
48588
48588
  getSliceHandle: (s) => s.pressureSensor
48589
48589
  },
@@ -48625,25 +48625,25 @@ var ROLE_DESCRIPTOR = {
48625
48625
  },
48626
48626
  "power-sensor": {
48627
48627
  kind: "numeric",
48628
- field: "watts",
48628
+ field: "power",
48629
48629
  quantity: "power",
48630
48630
  getSliceHandle: (s) => s.powerMeter
48631
48631
  },
48632
48632
  "energy-sensor": {
48633
48633
  kind: "numeric",
48634
- field: "kwhTotal",
48634
+ field: "energy",
48635
48635
  quantity: "energy",
48636
48636
  getSliceHandle: (s) => s.powerMeter
48637
48637
  },
48638
48638
  "voltage-sensor": {
48639
48639
  kind: "numeric",
48640
- field: "volts",
48640
+ field: "voltage",
48641
48641
  quantity: "voltage",
48642
48642
  getSliceHandle: (s) => s.powerMeter
48643
48643
  },
48644
48644
  "current-sensor": {
48645
48645
  kind: "numeric",
48646
- field: "amps",
48646
+ field: "current",
48647
48647
  quantity: "current",
48648
48648
  getSliceHandle: (s) => s.powerMeter
48649
48649
  },
@@ -49415,16 +49415,16 @@ function capSlice(caps, capName) {
49415
49415
  return caps[capName];
49416
49416
  }
49417
49417
  /**
49418
- * A battery group writes `battery.percentage`. The role's usual cap is
49418
+ * A battery group writes `battery.level`. The role's usual cap is
49419
49419
  * `numeric-sensor.value`. Hand the renderer a slice whose `value` is that
49420
49420
  * percentage so one numeric path draws both.
49421
49421
  */
49422
49422
  function batteryPercentageSlice(caps) {
49423
49423
  const battery = capSlice(caps, "battery");
49424
- if (!isRecord$2(battery) || typeof battery["percentage"] !== "number") return void 0;
49424
+ if (!isRecord$2(battery) || typeof battery["level"] !== "number") return void 0;
49425
49425
  return {
49426
49426
  ...battery,
49427
- value: battery["percentage"]
49427
+ value: battery["level"]
49428
49428
  };
49429
49429
  }
49430
49430
  function snapshotSlice(input) {
@@ -50184,17 +50184,17 @@ function VacuumHeroCard({ trpc, deviceId }) {
50184
50184
  children: meta.label
50185
50185
  }), slice?.batteryLevel != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BatteryBadge$2, { level: slice.batteryLevel })]
50186
50186
  }),
50187
- slice?.progressPercent != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
50187
+ slice?.progress != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
50188
50188
  className: "flex items-center gap-2",
50189
50189
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
50190
50190
  className: "h-1.5 flex-1 rounded bg-surface-hover overflow-hidden",
50191
50191
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
50192
50192
  className: "h-full bg-primary transition-[width] duration-500",
50193
- style: { width: `${Math.max(0, Math.min(100, slice.progressPercent))}%` }
50193
+ style: { width: `${Math.max(0, Math.min(100, slice.progress))}%` }
50194
50194
  })
50195
50195
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
50196
50196
  className: "tabular-nums text-xs text-foreground-subtle",
50197
- children: [Math.round(slice.progressPercent), "%"]
50197
+ children: [Math.round(slice.progress), "%"]
50198
50198
  })]
50199
50199
  }),
50200
50200
  slice?.errorCode != null && slice.errorCode !== 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -50703,7 +50703,7 @@ function LawnMowerHeroCard({ trpc, deviceId }) {
50703
50703
  children: meta.label
50704
50704
  }), slice?.batteryLevel != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BatteryBadge$1, { level: slice.batteryLevel })]
50705
50705
  }),
50706
- slice?.progressPercent != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProgressBar, { percent: slice.progressPercent }),
50706
+ slice?.progress != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProgressBar, { percent: slice.progress }),
50707
50707
  slice?.currentCode != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodeLine, {
50708
50708
  code: slice.currentCode,
50709
50709
  label: slice.currentCodeLabel,
@@ -53354,9 +53354,9 @@ function DishwasherPanel({ trpc, deviceId, layout }) {
53354
53354
  title: display.program,
53355
53355
  children: featureLabel(display.program)
53356
53356
  }),
53357
- display.remainingSeconds != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
53357
+ display.remaining != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
53358
53358
  className: "inline-flex items-center gap-0.5 text-xs text-foreground-subtle tabular-nums shrink-0",
53359
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Timer, { className: "h-3 w-3" }), formatDuration(display.remainingSeconds, t)]
53359
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Timer, { className: "h-3 w-3" }), formatDuration(display.remaining, t)]
53360
53360
  }),
53361
53361
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
53362
53362
  label: t("uiLibrary.dishwasher.start", "Start"),
@@ -53382,7 +53382,7 @@ function DishwasherPanel({ trpc, deviceId, layout }) {
53382
53382
  className: "flex items-start gap-4",
53383
53383
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherGraphic, {
53384
53384
  running,
53385
- progressPercent: display.progressPercent
53385
+ progressPercent: display.progress
53386
53386
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
53387
53387
  className: "flex min-w-0 flex-1 flex-col gap-2",
53388
53388
  children: [
@@ -53401,25 +53401,25 @@ function DishwasherPanel({ trpc, deviceId, layout }) {
53401
53401
  title: display.program,
53402
53402
  children: featureLabel(display.program)
53403
53403
  }),
53404
- display.remainingSeconds != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
53404
+ display.remaining != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
53405
53405
  className: "inline-flex items-center gap-1 text-sm text-foreground-subtle tabular-nums",
53406
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Timer, { className: "h-3.5 w-3.5" }), t("uiLibrary.dishwasher.remaining", "{{time}} left", { time: formatDuration(display.remainingSeconds, t) })]
53406
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Timer, { className: "h-3.5 w-3.5" }), t("uiLibrary.dishwasher.remaining", "{{time}} left", { time: formatDuration(display.remaining, t) })]
53407
53407
  }),
53408
- display.delayedStartSeconds != null && display.delayedStartSeconds > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
53408
+ display.delayedStart != null && display.delayedStart > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
53409
53409
  className: "text-xs text-foreground-subtle tabular-nums",
53410
- children: t("uiLibrary.dishwasher.startsIn", "Starts in {{time}}", { time: formatDuration(display.delayedStartSeconds, t) })
53410
+ children: t("uiLibrary.dishwasher.startsIn", "Starts in {{time}}", { time: formatDuration(display.delayedStart, t) })
53411
53411
  }),
53412
- display.progressPercent != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
53412
+ display.progress != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
53413
53413
  className: "flex items-center gap-2",
53414
53414
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
53415
53415
  className: "h-1.5 flex-1 rounded bg-surface-hover overflow-hidden",
53416
53416
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
53417
53417
  className: "h-full bg-primary transition-[width] duration-500",
53418
- style: { width: `${Math.max(0, Math.min(100, display.progressPercent))}%` }
53418
+ style: { width: `${Math.max(0, Math.min(100, display.progress))}%` }
53419
53419
  })
53420
53420
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
53421
53421
  className: "tabular-nums text-xs text-foreground-subtle",
53422
- children: [Math.round(display.progressPercent), "%"]
53422
+ children: [Math.round(display.progress), "%"]
53423
53423
  })]
53424
53424
  }),
53425
53425
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -54814,12 +54814,12 @@ function LightHeroCard({ trpc, deviceId, features, optimistic }) {
54814
54814
  if (swOpt.display === void 0) return null;
54815
54815
  const on = swOpt.display.on === true;
54816
54816
  const swBusy = !sw.available;
54817
- const percentage = brightOpt.display?.percentage ?? 0;
54817
+ const percentage = brightOpt.display?.level ?? 0;
54818
54818
  const brightBusy = !bright.available;
54819
54819
  const fraction = on ? percentage / 100 : 0;
54820
54820
  const arcColor = on ? ARC_COLOR_ON$1 : ARC_COLOR_OFF$1;
54821
54821
  const supportsCt = (features?.includes(_camstack_types.DeviceFeature.LightColorMired) ?? false) && color.cap !== void 0;
54822
- const ctMireds = colorOpt.display?.mireds;
54822
+ const ctMireds = colorOpt.display?.colorTemperature;
54823
54823
  const ctBusy = !color.available;
54824
54824
  const handleToggle = () => {
54825
54825
  if (swBusy) return;
@@ -54864,8 +54864,8 @@ function LightHeroCard({ trpc, deviceId, features, optimistic }) {
54864
54864
  disabled: brightBusy || !on,
54865
54865
  onChange: (e) => {
54866
54866
  const pct = Number(e.target.value);
54867
- brightOpt.applyOptimistic({ percentage: pct });
54868
- if (!brightBusy) bright.cap?.setBrightness({ percentage: pct });
54867
+ brightOpt.applyOptimistic({ level: pct });
54868
+ if (!brightBusy) bright.cap?.setBrightness({ level: pct });
54869
54869
  }
54870
54870
  }),
54871
54871
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -54912,11 +54912,11 @@ function LightHeroCard({ trpc, deviceId, features, optimistic }) {
54912
54912
  const mireds = Number(e.target.value);
54913
54913
  colorOpt.applyOptimistic({
54914
54914
  mode: "mired",
54915
- mireds
54915
+ colorTemperature: mireds
54916
54916
  });
54917
54917
  if (!ctBusy) color.cap?.setColor({ color: {
54918
54918
  mode: "mired",
54919
- mireds
54919
+ colorTemperature: mireds
54920
54920
  } });
54921
54921
  }
54922
54922
  }),
@@ -54976,7 +54976,7 @@ function FanHeroCard({ trpc, deviceId, features, optimistic }) {
54976
54976
  if (swOpt.display === void 0) return null;
54977
54977
  const on = swOpt.display.on === true;
54978
54978
  const swBusy = !sw.available;
54979
- const percentage = fanOpt.display?.percentage ?? 0;
54979
+ const percentage = fanOpt.display?.speed ?? 0;
54980
54980
  const fanBusy = !fan.available;
54981
54981
  const fraction = on ? percentage / 100 : 0;
54982
54982
  const arcColor = on ? ARC_COLOR_ON : ARC_COLOR_OFF;
@@ -55005,8 +55005,8 @@ function FanHeroCard({ trpc, deviceId, features, optimistic }) {
55005
55005
  if (slice === void 0) return;
55006
55006
  const step = slice.percentageStep !== void 0 && slice.percentageStep > 0 ? slice.percentageStep : 1;
55007
55007
  const pct = Math.min(100, Math.max(0, Math.round(f * 100 / step) * step));
55008
- fanOpt.applyOptimistic({ percentage: pct });
55009
- fan.cap?.setPercentage({ percentage: pct });
55008
+ fanOpt.applyOptimistic({ speed: pct });
55009
+ fan.cap?.setPercentage({ speed: pct });
55010
55010
  };
55011
55011
  const handlePreset = (preset) => {
55012
55012
  if (fanBusy || preset === "") return;
@@ -55228,7 +55228,7 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
55228
55228
  if (display === void 0) return null;
55229
55229
  const busy = !available;
55230
55230
  const action = toggleAction(display.state);
55231
- const supportsSeek = (features?.includes(_camstack_types.DeviceFeature.MediaPlayerSeek) ?? false) && display.durationMs !== null && display.durationMs > 0;
55231
+ const supportsSeek = (features?.includes(_camstack_types.DeviceFeature.MediaPlayerSeek) ?? false) && display.duration !== null && display.duration > 0;
55232
55232
  const supportsNext = features?.includes(_camstack_types.DeviceFeature.MediaPlayerNext) ?? false;
55233
55233
  const supportsPrevious = features?.includes(_camstack_types.DeviceFeature.MediaPlayerPrevious) ?? false;
55234
55234
  const supportsVolume = (features?.includes(_camstack_types.DeviceFeature.MediaPlayerVolume) ?? false) && display.volumeLevel !== null;
@@ -55243,7 +55243,7 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
55243
55243
  }
55244
55244
  };
55245
55245
  const subtitle = display.currentMedia?.artist ?? display.currentMedia?.album ?? null;
55246
- const seekFraction = progressFraction(display.positionMs, display.durationMs);
55246
+ const seekFraction = progressFraction(display.position, display.duration);
55247
55247
  const volumeLevel = display.volumeLevel ?? 0;
55248
55248
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
55249
55249
  className: "flex flex-col items-stretch gap-4 rounded-lg border border-border bg-surface p-6",
@@ -55266,16 +55266,16 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
55266
55266
  "aria-label": t("uiLibrary.mediaPlayerHero.seek", "Seek"),
55267
55267
  type: "range",
55268
55268
  min: 0,
55269
- max: display.durationMs ?? 0,
55269
+ max: display.duration ?? 0,
55270
55270
  step: 1e3,
55271
55271
  className: "absolute inset-0 h-4 w-full cursor-pointer opacity-0",
55272
- value: display.positionMs ?? 0,
55272
+ value: display.position ?? 0,
55273
55273
  disabled: busy,
55274
55274
  onChange: (e) => {
55275
55275
  if (busy) return;
55276
55276
  const positionMs = Number(e.target.value);
55277
- applyOptimistic({ positionMs });
55278
- cap.seek({ positionMs });
55277
+ applyOptimistic({ position: positionMs });
55278
+ cap.seek({ position: positionMs });
55279
55279
  }
55280
55280
  }),
55281
55281
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -55285,7 +55285,7 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
55285
55285
  ]
55286
55286
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
55287
55287
  className: "flex justify-between text-[11px] tabular-nums text-foreground-subtle",
55288
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatClock(display.positionMs) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatClock(display.durationMs) })]
55288
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatClock(display.position) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatClock(display.duration) })]
55289
55289
  })]
55290
55290
  }),
55291
55291
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
package/dist/index.js CHANGED
@@ -35982,7 +35982,7 @@ function levelColor(percentage, charging) {
35982
35982
  function BatteryBadge({ status, variant = "compact", className }) {
35983
35983
  const t = useUiText();
35984
35984
  if (!status) return null;
35985
- const { percentage, charging, sleeping, binary } = status;
35985
+ const { level: percentage, charging, sleeping, binary } = status;
35986
35986
  if (percentage === null) return /* @__PURE__ */ jsxs("span", {
35987
35987
  className: cn("inline-flex items-center gap-1 text-[10px] font-medium text-foreground-subtle", className),
35988
35988
  title: t("uiLibrary.batteryBadge.titleUnknown", "Battery: unknown"),
@@ -36145,15 +36145,15 @@ function NetworkLinkBadge({ status, className }) {
36145
36145
  title: t("uiLibrary.networkLinkBadge.ethernet", "Network: ethernet"),
36146
36146
  children: /* @__PURE__ */ jsx(Cable, { className: "h-3.5 w-3.5" })
36147
36147
  });
36148
- const Icon = status.type === "cellular" ? RadioTower : pickWifiIcon(status.signalPercent);
36149
- const signal = status.signalPercent === null ? null : Math.round(status.signalPercent);
36148
+ const Icon = status.type === "cellular" ? RadioTower : pickWifiIcon(status.signal);
36149
+ const signal = status.signal === null ? null : Math.round(status.signal);
36150
36150
  const networkText = status.ssid ? t("uiLibrary.networkLinkBadge.typeSsid", "Network: {{type}} ({{ssid}})", {
36151
36151
  type: status.type,
36152
36152
  ssid: status.ssid
36153
36153
  }) : t("uiLibrary.networkLinkBadge.type", "Network: {{type}}", { type: status.type });
36154
36154
  const title = signal === null ? networkText : `${networkText} · ${t("uiLibrary.networkLinkBadge.signal", "signal {{percent}}%", { percent: signal })}`;
36155
36155
  return /* @__PURE__ */ jsxs("span", {
36156
- className: cn("inline-flex items-center gap-1 text-[10px] font-medium", signalColor(status.signalPercent), className),
36156
+ className: cn("inline-flex items-center gap-1 text-[10px] font-medium", signalColor(status.signal), className),
36157
36157
  title,
36158
36158
  children: [/* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" }), signal !== null && /* @__PURE__ */ jsxs("span", { children: [signal, "%"] })]
36159
36159
  });
@@ -47403,16 +47403,16 @@ function BrightnessPanel({ trpc, deviceId, layout, optimistic }) {
47403
47403
  min: 0,
47404
47404
  max: 100,
47405
47405
  step: 1,
47406
- value: display.percentage,
47406
+ value: display.level,
47407
47407
  disabled: busy,
47408
47408
  onChange: (e) => {
47409
47409
  const pct = Number(e.target.value);
47410
- applyOptimistic({ percentage: pct });
47411
- if (!busy) cap?.setBrightness({ percentage: pct });
47410
+ applyOptimistic({ level: pct });
47411
+ if (!busy) cap?.setBrightness({ level: pct });
47412
47412
  }
47413
47413
  }), /* @__PURE__ */ jsxs("span", {
47414
47414
  className: "tabular-nums w-8 text-right",
47415
- children: [display.percentage, "%"]
47415
+ children: [display.level, "%"]
47416
47416
  })]
47417
47417
  });
47418
47418
  }
@@ -47443,17 +47443,17 @@ function FanPanel({ trpc, deviceId, features, layout, optimistic }) {
47443
47443
  min: 0,
47444
47444
  max: 100,
47445
47445
  step,
47446
- value: display.percentage ?? 0,
47446
+ value: display.speed ?? 0,
47447
47447
  disabled: busy,
47448
47448
  onChange: (e) => {
47449
47449
  const raw = Number(e.target.value);
47450
47450
  const pct = Math.min(100, Math.max(0, Math.round(raw / step) * step));
47451
- applyOptimistic({ percentage: pct });
47452
- if (!busy) cap?.setPercentage({ percentage: pct });
47451
+ applyOptimistic({ speed: pct });
47452
+ if (!busy) cap?.setPercentage({ speed: pct });
47453
47453
  }
47454
47454
  }), /* @__PURE__ */ jsxs("span", {
47455
47455
  className: "tabular-nums w-8 text-right",
47456
- children: [display.percentage ?? "—", "%"]
47456
+ children: [display.speed ?? "—", "%"]
47457
47457
  })]
47458
47458
  }), showPreset && display.availablePresets.length > 0 && /* @__PURE__ */ jsx("div", {
47459
47459
  className: "flex items-center gap-2 text-xs text-foreground-subtle",
@@ -48541,25 +48541,25 @@ function hasBatteryNumericValue(slice, field) {
48541
48541
  var ROLE_DESCRIPTOR = {
48542
48542
  "temperature-sensor": {
48543
48543
  kind: "numeric",
48544
- field: "celsius",
48544
+ field: "temperature",
48545
48545
  quantity: "temperature",
48546
48546
  getSliceHandle: (s) => s.temperatureSensor
48547
48547
  },
48548
48548
  "humidity-sensor": {
48549
48549
  kind: "numeric",
48550
- field: "percent",
48550
+ field: "humidity",
48551
48551
  quantity: "humidity",
48552
48552
  getSliceHandle: (s) => s.humiditySensor
48553
48553
  },
48554
48554
  "ambient-light-sensor": {
48555
48555
  kind: "numeric",
48556
- field: "lux",
48556
+ field: "illuminance",
48557
48557
  quantity: "illuminance",
48558
48558
  getSliceHandle: (s) => s.ambientLightSensor
48559
48559
  },
48560
48560
  "pressure-sensor": {
48561
48561
  kind: "numeric",
48562
- field: "hpa",
48562
+ field: "pressure",
48563
48563
  quantity: "pressure",
48564
48564
  getSliceHandle: (s) => s.pressureSensor
48565
48565
  },
@@ -48601,25 +48601,25 @@ var ROLE_DESCRIPTOR = {
48601
48601
  },
48602
48602
  "power-sensor": {
48603
48603
  kind: "numeric",
48604
- field: "watts",
48604
+ field: "power",
48605
48605
  quantity: "power",
48606
48606
  getSliceHandle: (s) => s.powerMeter
48607
48607
  },
48608
48608
  "energy-sensor": {
48609
48609
  kind: "numeric",
48610
- field: "kwhTotal",
48610
+ field: "energy",
48611
48611
  quantity: "energy",
48612
48612
  getSliceHandle: (s) => s.powerMeter
48613
48613
  },
48614
48614
  "voltage-sensor": {
48615
48615
  kind: "numeric",
48616
- field: "volts",
48616
+ field: "voltage",
48617
48617
  quantity: "voltage",
48618
48618
  getSliceHandle: (s) => s.powerMeter
48619
48619
  },
48620
48620
  "current-sensor": {
48621
48621
  kind: "numeric",
48622
- field: "amps",
48622
+ field: "current",
48623
48623
  quantity: "current",
48624
48624
  getSliceHandle: (s) => s.powerMeter
48625
48625
  },
@@ -49391,16 +49391,16 @@ function capSlice(caps, capName) {
49391
49391
  return caps[capName];
49392
49392
  }
49393
49393
  /**
49394
- * A battery group writes `battery.percentage`. The role's usual cap is
49394
+ * A battery group writes `battery.level`. The role's usual cap is
49395
49395
  * `numeric-sensor.value`. Hand the renderer a slice whose `value` is that
49396
49396
  * percentage so one numeric path draws both.
49397
49397
  */
49398
49398
  function batteryPercentageSlice(caps) {
49399
49399
  const battery = capSlice(caps, "battery");
49400
- if (!isRecord$2(battery) || typeof battery["percentage"] !== "number") return void 0;
49400
+ if (!isRecord$2(battery) || typeof battery["level"] !== "number") return void 0;
49401
49401
  return {
49402
49402
  ...battery,
49403
- value: battery["percentage"]
49403
+ value: battery["level"]
49404
49404
  };
49405
49405
  }
49406
49406
  function snapshotSlice(input) {
@@ -50160,17 +50160,17 @@ function VacuumHeroCard({ trpc, deviceId }) {
50160
50160
  children: meta.label
50161
50161
  }), slice?.batteryLevel != null && /* @__PURE__ */ jsx(BatteryBadge$2, { level: slice.batteryLevel })]
50162
50162
  }),
50163
- slice?.progressPercent != null && /* @__PURE__ */ jsxs("div", {
50163
+ slice?.progress != null && /* @__PURE__ */ jsxs("div", {
50164
50164
  className: "flex items-center gap-2",
50165
50165
  children: [/* @__PURE__ */ jsx("div", {
50166
50166
  className: "h-1.5 flex-1 rounded bg-surface-hover overflow-hidden",
50167
50167
  children: /* @__PURE__ */ jsx("div", {
50168
50168
  className: "h-full bg-primary transition-[width] duration-500",
50169
- style: { width: `${Math.max(0, Math.min(100, slice.progressPercent))}%` }
50169
+ style: { width: `${Math.max(0, Math.min(100, slice.progress))}%` }
50170
50170
  })
50171
50171
  }), /* @__PURE__ */ jsxs("span", {
50172
50172
  className: "tabular-nums text-xs text-foreground-subtle",
50173
- children: [Math.round(slice.progressPercent), "%"]
50173
+ children: [Math.round(slice.progress), "%"]
50174
50174
  })]
50175
50175
  }),
50176
50176
  slice?.errorCode != null && slice.errorCode !== 0 && /* @__PURE__ */ jsxs("div", {
@@ -50679,7 +50679,7 @@ function LawnMowerHeroCard({ trpc, deviceId }) {
50679
50679
  children: meta.label
50680
50680
  }), slice?.batteryLevel != null && /* @__PURE__ */ jsx(BatteryBadge$1, { level: slice.batteryLevel })]
50681
50681
  }),
50682
- slice?.progressPercent != null && /* @__PURE__ */ jsx(ProgressBar, { percent: slice.progressPercent }),
50682
+ slice?.progress != null && /* @__PURE__ */ jsx(ProgressBar, { percent: slice.progress }),
50683
50683
  slice?.currentCode != null && /* @__PURE__ */ jsx(CodeLine, {
50684
50684
  code: slice.currentCode,
50685
50685
  label: slice.currentCodeLabel,
@@ -53330,9 +53330,9 @@ function DishwasherPanel({ trpc, deviceId, layout }) {
53330
53330
  title: display.program,
53331
53331
  children: featureLabel(display.program)
53332
53332
  }),
53333
- display.remainingSeconds != null && /* @__PURE__ */ jsxs("span", {
53333
+ display.remaining != null && /* @__PURE__ */ jsxs("span", {
53334
53334
  className: "inline-flex items-center gap-0.5 text-xs text-foreground-subtle tabular-nums shrink-0",
53335
- children: [/* @__PURE__ */ jsx(Timer, { className: "h-3 w-3" }), formatDuration(display.remainingSeconds, t)]
53335
+ children: [/* @__PURE__ */ jsx(Timer, { className: "h-3 w-3" }), formatDuration(display.remaining, t)]
53336
53336
  }),
53337
53337
  /* @__PURE__ */ jsx(DishwasherIconButton, {
53338
53338
  label: t("uiLibrary.dishwasher.start", "Start"),
@@ -53358,7 +53358,7 @@ function DishwasherPanel({ trpc, deviceId, layout }) {
53358
53358
  className: "flex items-start gap-4",
53359
53359
  children: [/* @__PURE__ */ jsx(DishwasherGraphic, {
53360
53360
  running,
53361
- progressPercent: display.progressPercent
53361
+ progressPercent: display.progress
53362
53362
  }), /* @__PURE__ */ jsxs("div", {
53363
53363
  className: "flex min-w-0 flex-1 flex-col gap-2",
53364
53364
  children: [
@@ -53377,25 +53377,25 @@ function DishwasherPanel({ trpc, deviceId, layout }) {
53377
53377
  title: display.program,
53378
53378
  children: featureLabel(display.program)
53379
53379
  }),
53380
- display.remainingSeconds != null && /* @__PURE__ */ jsxs("span", {
53380
+ display.remaining != null && /* @__PURE__ */ jsxs("span", {
53381
53381
  className: "inline-flex items-center gap-1 text-sm text-foreground-subtle tabular-nums",
53382
- children: [/* @__PURE__ */ jsx(Timer, { className: "h-3.5 w-3.5" }), t("uiLibrary.dishwasher.remaining", "{{time}} left", { time: formatDuration(display.remainingSeconds, t) })]
53382
+ children: [/* @__PURE__ */ jsx(Timer, { className: "h-3.5 w-3.5" }), t("uiLibrary.dishwasher.remaining", "{{time}} left", { time: formatDuration(display.remaining, t) })]
53383
53383
  }),
53384
- display.delayedStartSeconds != null && display.delayedStartSeconds > 0 && /* @__PURE__ */ jsx("span", {
53384
+ display.delayedStart != null && display.delayedStart > 0 && /* @__PURE__ */ jsx("span", {
53385
53385
  className: "text-xs text-foreground-subtle tabular-nums",
53386
- children: t("uiLibrary.dishwasher.startsIn", "Starts in {{time}}", { time: formatDuration(display.delayedStartSeconds, t) })
53386
+ children: t("uiLibrary.dishwasher.startsIn", "Starts in {{time}}", { time: formatDuration(display.delayedStart, t) })
53387
53387
  }),
53388
- display.progressPercent != null && /* @__PURE__ */ jsxs("div", {
53388
+ display.progress != null && /* @__PURE__ */ jsxs("div", {
53389
53389
  className: "flex items-center gap-2",
53390
53390
  children: [/* @__PURE__ */ jsx("div", {
53391
53391
  className: "h-1.5 flex-1 rounded bg-surface-hover overflow-hidden",
53392
53392
  children: /* @__PURE__ */ jsx("div", {
53393
53393
  className: "h-full bg-primary transition-[width] duration-500",
53394
- style: { width: `${Math.max(0, Math.min(100, display.progressPercent))}%` }
53394
+ style: { width: `${Math.max(0, Math.min(100, display.progress))}%` }
53395
53395
  })
53396
53396
  }), /* @__PURE__ */ jsxs("span", {
53397
53397
  className: "tabular-nums text-xs text-foreground-subtle",
53398
- children: [Math.round(display.progressPercent), "%"]
53398
+ children: [Math.round(display.progress), "%"]
53399
53399
  })]
53400
53400
  }),
53401
53401
  /* @__PURE__ */ jsxs("div", {
@@ -54790,12 +54790,12 @@ function LightHeroCard({ trpc, deviceId, features, optimistic }) {
54790
54790
  if (swOpt.display === void 0) return null;
54791
54791
  const on = swOpt.display.on === true;
54792
54792
  const swBusy = !sw.available;
54793
- const percentage = brightOpt.display?.percentage ?? 0;
54793
+ const percentage = brightOpt.display?.level ?? 0;
54794
54794
  const brightBusy = !bright.available;
54795
54795
  const fraction = on ? percentage / 100 : 0;
54796
54796
  const arcColor = on ? ARC_COLOR_ON$1 : ARC_COLOR_OFF$1;
54797
54797
  const supportsCt = (features?.includes(DeviceFeature.LightColorMired) ?? false) && color.cap !== void 0;
54798
- const ctMireds = colorOpt.display?.mireds;
54798
+ const ctMireds = colorOpt.display?.colorTemperature;
54799
54799
  const ctBusy = !color.available;
54800
54800
  const handleToggle = () => {
54801
54801
  if (swBusy) return;
@@ -54840,8 +54840,8 @@ function LightHeroCard({ trpc, deviceId, features, optimistic }) {
54840
54840
  disabled: brightBusy || !on,
54841
54841
  onChange: (e) => {
54842
54842
  const pct = Number(e.target.value);
54843
- brightOpt.applyOptimistic({ percentage: pct });
54844
- if (!brightBusy) bright.cap?.setBrightness({ percentage: pct });
54843
+ brightOpt.applyOptimistic({ level: pct });
54844
+ if (!brightBusy) bright.cap?.setBrightness({ level: pct });
54845
54845
  }
54846
54846
  }),
54847
54847
  /* @__PURE__ */ jsxs("span", {
@@ -54888,11 +54888,11 @@ function LightHeroCard({ trpc, deviceId, features, optimistic }) {
54888
54888
  const mireds = Number(e.target.value);
54889
54889
  colorOpt.applyOptimistic({
54890
54890
  mode: "mired",
54891
- mireds
54891
+ colorTemperature: mireds
54892
54892
  });
54893
54893
  if (!ctBusy) color.cap?.setColor({ color: {
54894
54894
  mode: "mired",
54895
- mireds
54895
+ colorTemperature: mireds
54896
54896
  } });
54897
54897
  }
54898
54898
  }),
@@ -54952,7 +54952,7 @@ function FanHeroCard({ trpc, deviceId, features, optimistic }) {
54952
54952
  if (swOpt.display === void 0) return null;
54953
54953
  const on = swOpt.display.on === true;
54954
54954
  const swBusy = !sw.available;
54955
- const percentage = fanOpt.display?.percentage ?? 0;
54955
+ const percentage = fanOpt.display?.speed ?? 0;
54956
54956
  const fanBusy = !fan.available;
54957
54957
  const fraction = on ? percentage / 100 : 0;
54958
54958
  const arcColor = on ? ARC_COLOR_ON : ARC_COLOR_OFF;
@@ -54981,8 +54981,8 @@ function FanHeroCard({ trpc, deviceId, features, optimistic }) {
54981
54981
  if (slice === void 0) return;
54982
54982
  const step = slice.percentageStep !== void 0 && slice.percentageStep > 0 ? slice.percentageStep : 1;
54983
54983
  const pct = Math.min(100, Math.max(0, Math.round(f * 100 / step) * step));
54984
- fanOpt.applyOptimistic({ percentage: pct });
54985
- fan.cap?.setPercentage({ percentage: pct });
54984
+ fanOpt.applyOptimistic({ speed: pct });
54985
+ fan.cap?.setPercentage({ speed: pct });
54986
54986
  };
54987
54987
  const handlePreset = (preset) => {
54988
54988
  if (fanBusy || preset === "") return;
@@ -55204,7 +55204,7 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
55204
55204
  if (display === void 0) return null;
55205
55205
  const busy = !available;
55206
55206
  const action = toggleAction(display.state);
55207
- const supportsSeek = (features?.includes(DeviceFeature.MediaPlayerSeek) ?? false) && display.durationMs !== null && display.durationMs > 0;
55207
+ const supportsSeek = (features?.includes(DeviceFeature.MediaPlayerSeek) ?? false) && display.duration !== null && display.duration > 0;
55208
55208
  const supportsNext = features?.includes(DeviceFeature.MediaPlayerNext) ?? false;
55209
55209
  const supportsPrevious = features?.includes(DeviceFeature.MediaPlayerPrevious) ?? false;
55210
55210
  const supportsVolume = (features?.includes(DeviceFeature.MediaPlayerVolume) ?? false) && display.volumeLevel !== null;
@@ -55219,7 +55219,7 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
55219
55219
  }
55220
55220
  };
55221
55221
  const subtitle = display.currentMedia?.artist ?? display.currentMedia?.album ?? null;
55222
- const seekFraction = progressFraction(display.positionMs, display.durationMs);
55222
+ const seekFraction = progressFraction(display.position, display.duration);
55223
55223
  const volumeLevel = display.volumeLevel ?? 0;
55224
55224
  return /* @__PURE__ */ jsxs("div", {
55225
55225
  className: "flex flex-col items-stretch gap-4 rounded-lg border border-border bg-surface p-6",
@@ -55242,16 +55242,16 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
55242
55242
  "aria-label": t("uiLibrary.mediaPlayerHero.seek", "Seek"),
55243
55243
  type: "range",
55244
55244
  min: 0,
55245
- max: display.durationMs ?? 0,
55245
+ max: display.duration ?? 0,
55246
55246
  step: 1e3,
55247
55247
  className: "absolute inset-0 h-4 w-full cursor-pointer opacity-0",
55248
- value: display.positionMs ?? 0,
55248
+ value: display.position ?? 0,
55249
55249
  disabled: busy,
55250
55250
  onChange: (e) => {
55251
55251
  if (busy) return;
55252
55252
  const positionMs = Number(e.target.value);
55253
- applyOptimistic({ positionMs });
55254
- cap.seek({ positionMs });
55253
+ applyOptimistic({ position: positionMs });
55254
+ cap.seek({ position: positionMs });
55255
55255
  }
55256
55256
  }),
55257
55257
  /* @__PURE__ */ jsx("div", {
@@ -55261,7 +55261,7 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
55261
55261
  ]
55262
55262
  }), /* @__PURE__ */ jsxs("div", {
55263
55263
  className: "flex justify-between text-[11px] tabular-nums text-foreground-subtle",
55264
- children: [/* @__PURE__ */ jsx("span", { children: formatClock(display.positionMs) }), /* @__PURE__ */ jsx("span", { children: formatClock(display.durationMs) })]
55264
+ children: [/* @__PURE__ */ jsx("span", { children: formatClock(display.position) }), /* @__PURE__ */ jsx("span", { children: formatClock(display.duration) })]
55265
55265
  })]
55266
55266
  }),
55267
55267
  /* @__PURE__ */ jsxs("div", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/ui-library",
3
- "version": "1.2.342",
3
+ "version": "1.2.343",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/camstack/server.git",