@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 +6 -0
- package/dist/composites/device-controls/pet-feeder-panel.d.ts +12 -0
- package/dist/index.cjs +56 -15
- package/dist/index.js +56 -15
- package/package.json +1 -1
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
|
-
/**
|
|
53130
|
-
|
|
53131
|
-
|
|
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
|
-
}),
|
|
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:
|
|
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: [
|
|
53238
|
-
|
|
53239
|
-
|
|
53240
|
-
|
|
53241
|
-
|
|
53242
|
-
|
|
53243
|
-
|
|
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", "{{
|
|
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
|
-
/**
|
|
53106
|
-
|
|
53107
|
-
|
|
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
|
-
}),
|
|
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:
|
|
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: [
|
|
53214
|
-
|
|
53215
|
-
|
|
53216
|
-
|
|
53217
|
-
|
|
53218
|
-
|
|
53219
|
-
|
|
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", "{{
|
|
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", {
|