@camstack/ui-library 1.2.352 → 1.2.353

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.353] - 2026-10-29
11
+
12
+ ### Changed
13
+
14
+ - The pet feeder card shows the food in the bowl with its unit (grams, or ounces with imperial units) instead of a bare number.
15
+
10
16
  ## [1.2.352] - 2026-10-29
11
17
 
12
18
  ### Changed
@@ -1,5 +1,17 @@
1
1
  import { type ReactNode } from "react";
2
2
  import type { ControlPanelProps } from "./types.js";
3
+ export interface BowlReading {
4
+ /** Number text, `—` when the bowl is unknown. */
5
+ readonly text: string;
6
+ /** Unit symbol, `''` when the bowl is unknown. */
7
+ readonly unit: string;
8
+ /** Number and unit as one string, for labels. */
9
+ readonly label: string;
10
+ /** The bowl in grams, for the gauge fill; null when unknown. */
11
+ readonly grams: number | null;
12
+ }
13
+ /** The slice stores the bowl as a mass in kilograms (D944). */
14
+ export declare function resolveBowlReading(kilograms: number | null, system: "metric" | "imperial"): BowlReading;
3
15
  export declare function PetFeederPanel({ trpc, deviceId, layout, optimistic }: ControlPanelProps): ReactNode;
4
16
  interface DeviceControlProps {
5
17
  readonly trpc: ControlPanelProps["trpc"];
package/dist/index.cjs CHANGED
@@ -53126,9 +53126,41 @@ var HOLD_DELAY_MS = 350;
53126
53126
  var HOLD_INTERVAL_MS = 70;
53127
53127
  var HOLD_ACCEL_AFTER = 8;
53128
53128
  var HOLD_FAST_STEP = 5;
53129
- /** The slice stores the bowl as a mass in kilograms (D944); the card draws grams. */
53130
- function bowlGrams(kilograms) {
53131
- return kilograms === null ? null : Math.round(kilograms * 1e3);
53129
+ /** Small bowl masses read in grams (ounces under imperial), never "0.022 kg":
53130
+ * the shared resolver keeps mass in kilograms, so the panel picks the
53131
+ * display unit and the resolver converts and labels it. */
53132
+ var BOWL_UNIT = {
53133
+ metric: "g",
53134
+ imperial: "oz"
53135
+ };
53136
+ var BOWL_PRECISION = {
53137
+ metric: 0,
53138
+ imperial: 1
53139
+ };
53140
+ /** The slice stores the bowl as a mass in kilograms (D944). */
53141
+ function resolveBowlReading(kilograms, system) {
53142
+ if (kilograms === null) return {
53143
+ text: "—",
53144
+ unit: "",
53145
+ label: "—",
53146
+ grams: null
53147
+ };
53148
+ const shown = resolveSensorDisplay({
53149
+ display: { perCap: { "pet-feeder": {
53150
+ unit: BOWL_UNIT[system],
53151
+ precision: BOWL_PRECISION[system]
53152
+ } } },
53153
+ capName: "pet-feeder",
53154
+ quantity: "mass",
53155
+ preference: system
53156
+ }).format(kilograms);
53157
+ const grams = (0, _camstack_types.tryConvertUnit)(kilograms, "kg", "g");
53158
+ return {
53159
+ text: shown.text,
53160
+ unit: shown.unit,
53161
+ label: `${shown.text} ${shown.unit}`,
53162
+ grams: grams === void 0 ? null : Math.round(grams)
53163
+ };
53132
53164
  }
53133
53165
  function clampPortion(grams) {
53134
53166
  return Math.min(_camstack_types.PET_FEEDER_MANUAL_FEED_MAX, Math.max(_camstack_types.PET_FEEDER_MANUAL_FEED_MIN, grams));
@@ -53152,6 +53184,7 @@ function PetFeederPanel({ trpc, deviceId, layout, optimistic }) {
53152
53184
  const [portion, setPortion] = usePortion(display?.lastFeedGrams ?? null);
53153
53185
  const [hopper1, setHopper1] = usePortion(display?.lastFeedHopper1 ?? null);
53154
53186
  const [hopper2, setHopper2] = usePortion(display?.lastFeedHopper2 ?? null);
53187
+ const bowl = resolveBowlReading(display?.bowlFood ?? null, useUnitPreferences().system);
53155
53188
  if (cap === void 0) return null;
53156
53189
  if (display === void 0) return null;
53157
53190
  const busy = !available;
@@ -53181,7 +53214,7 @@ function PetFeederPanel({ trpc, deviceId, layout, optimistic }) {
53181
53214
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UtensilsCrossed, {
53182
53215
  className: "h-3.5 w-3.5",
53183
53216
  "aria-hidden": true
53184
- }), bowlGrams(display.bowlFood) ?? "—"]
53217
+ }), bowl.label]
53185
53218
  }),
53186
53219
  onMains && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
53187
53220
  className: "inline-flex items-center gap-0.5 text-xs text-foreground-subtle",
@@ -53227,21 +53260,29 @@ function PetFeederPanel({ trpc, deviceId, layout, optimistic }) {
53227
53260
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
53228
53261
  className: "flex items-center gap-4 rounded-lg border border-border/50 bg-surface-subtle/40 p-3",
53229
53262
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BowlGauge, {
53230
- grams: bowlGrams(display.bowlFood),
53263
+ grams: bowl.grams,
53264
+ label: bowl.label,
53231
53265
  low: display.lowFood,
53232
53266
  eating: display.eating
53233
53267
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
53234
53268
  className: "flex min-w-0 flex-1 flex-col gap-1.5",
53235
53269
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
53236
53270
  className: "flex items-baseline gap-1",
53237
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
53238
- className: "text-3xl font-bold leading-none tabular-nums",
53239
- style: { color: display.lowFood ? KIBBLE_LOW : KIBBLE },
53240
- children: bowlGrams(display.bowlFood) ?? "—"
53241
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
53242
- className: "text-sm text-foreground-subtle",
53243
- children: t("uiLibrary.petFeeder.inBowl", "in bowl")
53244
- })]
53271
+ children: [
53272
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
53273
+ className: "text-3xl font-bold leading-none tabular-nums",
53274
+ style: { color: display.lowFood ? KIBBLE_LOW : KIBBLE },
53275
+ children: bowl.text
53276
+ }),
53277
+ bowl.unit !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
53278
+ className: "text-sm text-foreground-subtle",
53279
+ children: bowl.unit
53280
+ }),
53281
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
53282
+ className: "text-sm text-foreground-subtle",
53283
+ children: t("uiLibrary.petFeeder.inBowl", "in bowl")
53284
+ })
53285
+ ]
53245
53286
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
53246
53287
  className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-foreground-subtle",
53247
53288
  children: [
@@ -53476,7 +53517,7 @@ function Readout({ icon, label }) {
53476
53517
  * bowl reading (`bowlFood`). Amber when stocked, red when the low-food flag is
53477
53518
  * set; the kibble mound bobs while the pet is eating.
53478
53519
  */
53479
- function BowlGauge({ grams, low, eating }) {
53520
+ function BowlGauge({ grams, label, low, eating }) {
53480
53521
  const t = useUiText();
53481
53522
  const frac = Math.min(1, Math.max(0, (grams ?? 0) / _camstack_types.PET_FEEDER_MANUAL_FEED_MAX));
53482
53523
  const fill = low ? KIBBLE_LOW : KIBBLE;
@@ -53486,7 +53527,7 @@ function BowlGauge({ grams, low, eating }) {
53486
53527
  height: 72,
53487
53528
  viewBox: "0 0 72 72",
53488
53529
  role: "img",
53489
- "aria-label": grams === null ? t("uiLibrary.petFeeder.levelUnknown", "Food level unknown") : t("uiLibrary.petFeeder.gramsInBowl", "{{grams}} in bowl", { grams }),
53530
+ "aria-label": grams === null ? t("uiLibrary.petFeeder.levelUnknown", "Food level unknown") : t("uiLibrary.petFeeder.gramsInBowl", "{{amount}} in bowl", { amount: label }),
53490
53531
  className: "shrink-0",
53491
53532
  children: [
53492
53533
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("clipPath", {
package/dist/index.js CHANGED
@@ -53102,9 +53102,41 @@ var HOLD_DELAY_MS = 350;
53102
53102
  var HOLD_INTERVAL_MS = 70;
53103
53103
  var HOLD_ACCEL_AFTER = 8;
53104
53104
  var HOLD_FAST_STEP = 5;
53105
- /** The slice stores the bowl as a mass in kilograms (D944); the card draws grams. */
53106
- function bowlGrams(kilograms) {
53107
- return kilograms === null ? null : Math.round(kilograms * 1e3);
53105
+ /** Small bowl masses read in grams (ounces under imperial), never "0.022 kg":
53106
+ * the shared resolver keeps mass in kilograms, so the panel picks the
53107
+ * display unit and the resolver converts and labels it. */
53108
+ var BOWL_UNIT = {
53109
+ metric: "g",
53110
+ imperial: "oz"
53111
+ };
53112
+ var BOWL_PRECISION = {
53113
+ metric: 0,
53114
+ imperial: 1
53115
+ };
53116
+ /** The slice stores the bowl as a mass in kilograms (D944). */
53117
+ function resolveBowlReading(kilograms, system) {
53118
+ if (kilograms === null) return {
53119
+ text: "—",
53120
+ unit: "",
53121
+ label: "—",
53122
+ grams: null
53123
+ };
53124
+ const shown = resolveSensorDisplay({
53125
+ display: { perCap: { "pet-feeder": {
53126
+ unit: BOWL_UNIT[system],
53127
+ precision: BOWL_PRECISION[system]
53128
+ } } },
53129
+ capName: "pet-feeder",
53130
+ quantity: "mass",
53131
+ preference: system
53132
+ }).format(kilograms);
53133
+ const grams = tryConvertUnit(kilograms, "kg", "g");
53134
+ return {
53135
+ text: shown.text,
53136
+ unit: shown.unit,
53137
+ label: `${shown.text} ${shown.unit}`,
53138
+ grams: grams === void 0 ? null : Math.round(grams)
53139
+ };
53108
53140
  }
53109
53141
  function clampPortion(grams) {
53110
53142
  return Math.min(PET_FEEDER_MANUAL_FEED_MAX, Math.max(PET_FEEDER_MANUAL_FEED_MIN, grams));
@@ -53128,6 +53160,7 @@ function PetFeederPanel({ trpc, deviceId, layout, optimistic }) {
53128
53160
  const [portion, setPortion] = usePortion(display?.lastFeedGrams ?? null);
53129
53161
  const [hopper1, setHopper1] = usePortion(display?.lastFeedHopper1 ?? null);
53130
53162
  const [hopper2, setHopper2] = usePortion(display?.lastFeedHopper2 ?? null);
53163
+ const bowl = resolveBowlReading(display?.bowlFood ?? null, useUnitPreferences().system);
53131
53164
  if (cap === void 0) return null;
53132
53165
  if (display === void 0) return null;
53133
53166
  const busy = !available;
@@ -53157,7 +53190,7 @@ function PetFeederPanel({ trpc, deviceId, layout, optimistic }) {
53157
53190
  children: [/* @__PURE__ */ jsx(UtensilsCrossed, {
53158
53191
  className: "h-3.5 w-3.5",
53159
53192
  "aria-hidden": true
53160
- }), bowlGrams(display.bowlFood) ?? "—"]
53193
+ }), bowl.label]
53161
53194
  }),
53162
53195
  onMains && /* @__PURE__ */ jsxs("span", {
53163
53196
  className: "inline-flex items-center gap-0.5 text-xs text-foreground-subtle",
@@ -53203,21 +53236,29 @@ function PetFeederPanel({ trpc, deviceId, layout, optimistic }) {
53203
53236
  /* @__PURE__ */ jsxs("div", {
53204
53237
  className: "flex items-center gap-4 rounded-lg border border-border/50 bg-surface-subtle/40 p-3",
53205
53238
  children: [/* @__PURE__ */ jsx(BowlGauge, {
53206
- grams: bowlGrams(display.bowlFood),
53239
+ grams: bowl.grams,
53240
+ label: bowl.label,
53207
53241
  low: display.lowFood,
53208
53242
  eating: display.eating
53209
53243
  }), /* @__PURE__ */ jsxs("div", {
53210
53244
  className: "flex min-w-0 flex-1 flex-col gap-1.5",
53211
53245
  children: [/* @__PURE__ */ jsxs("div", {
53212
53246
  className: "flex items-baseline gap-1",
53213
- children: [/* @__PURE__ */ jsx("span", {
53214
- className: "text-3xl font-bold leading-none tabular-nums",
53215
- style: { color: display.lowFood ? KIBBLE_LOW : KIBBLE },
53216
- children: bowlGrams(display.bowlFood) ?? "—"
53217
- }), /* @__PURE__ */ jsx("span", {
53218
- className: "text-sm text-foreground-subtle",
53219
- children: t("uiLibrary.petFeeder.inBowl", "in bowl")
53220
- })]
53247
+ children: [
53248
+ /* @__PURE__ */ jsx("span", {
53249
+ className: "text-3xl font-bold leading-none tabular-nums",
53250
+ style: { color: display.lowFood ? KIBBLE_LOW : KIBBLE },
53251
+ children: bowl.text
53252
+ }),
53253
+ bowl.unit !== "" && /* @__PURE__ */ jsx("span", {
53254
+ className: "text-sm text-foreground-subtle",
53255
+ children: bowl.unit
53256
+ }),
53257
+ /* @__PURE__ */ jsx("span", {
53258
+ className: "text-sm text-foreground-subtle",
53259
+ children: t("uiLibrary.petFeeder.inBowl", "in bowl")
53260
+ })
53261
+ ]
53221
53262
  }), /* @__PURE__ */ jsxs("div", {
53222
53263
  className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-foreground-subtle",
53223
53264
  children: [
@@ -53452,7 +53493,7 @@ function Readout({ icon, label }) {
53452
53493
  * bowl reading (`bowlFood`). Amber when stocked, red when the low-food flag is
53453
53494
  * set; the kibble mound bobs while the pet is eating.
53454
53495
  */
53455
- function BowlGauge({ grams, low, eating }) {
53496
+ function BowlGauge({ grams, label, low, eating }) {
53456
53497
  const t = useUiText();
53457
53498
  const frac = Math.min(1, Math.max(0, (grams ?? 0) / PET_FEEDER_MANUAL_FEED_MAX));
53458
53499
  const fill = low ? KIBBLE_LOW : KIBBLE;
@@ -53462,7 +53503,7 @@ function BowlGauge({ grams, low, eating }) {
53462
53503
  height: 72,
53463
53504
  viewBox: "0 0 72 72",
53464
53505
  role: "img",
53465
- "aria-label": grams === null ? t("uiLibrary.petFeeder.levelUnknown", "Food level unknown") : t("uiLibrary.petFeeder.gramsInBowl", "{{grams}} in bowl", { grams }),
53506
+ "aria-label": grams === null ? t("uiLibrary.petFeeder.levelUnknown", "Food level unknown") : t("uiLibrary.petFeeder.gramsInBowl", "{{amount}} in bowl", { amount: label }),
53466
53507
  className: "shrink-0",
53467
53508
  children: [
53468
53509
  /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/ui-library",
3
- "version": "1.2.352",
3
+ "version": "1.2.353",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/camstack/server.git",