@camstack/ui-library 1.2.325 → 1.2.327
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/dist/composites/device-controls/control-panel.d.ts +0 -2
- package/dist/composites/device-controls/sensor-value-atom.d.ts +6 -14
- package/dist/generated/system-hooks.d.ts +2 -0
- package/dist/index.cjs +196 -116
- package/dist/index.js +197 -118
- package/dist/lib/resolve-sensor-display.d.ts +22 -24
- package/package.json +1 -1
|
@@ -4,7 +4,5 @@ import type { ControlPanelProps } from "./types.js";
|
|
|
4
4
|
export declare function toDateInput(value: string): string;
|
|
5
5
|
/** Trim an HA time value ('HH:MM:SS') to the `<input type=time>` shape ('HH:MM'). */
|
|
6
6
|
export declare function toTimeInput(value: string): string;
|
|
7
|
-
/** Trim an HA datetime value to the `<input type=datetime-local>` shape
|
|
8
|
-
* ('YYYY-MM-DDTHH:MM'). Accepts both 'T'- and space-separated forms. */
|
|
9
7
|
export declare function toDateTimeInput(value: string): string;
|
|
10
8
|
export declare function ControlPanel({ trpc, deviceId, features: _features, layout, optimistic }: ControlPanelProps): ReactNode;
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* at the call site; narrowing occurs after `useDeviceStateSlice`.
|
|
21
21
|
*/
|
|
22
22
|
import type { ReactNode } from "react";
|
|
23
|
-
import type { DeviceProxy, SliceHandle } from "@camstack/types";
|
|
23
|
+
import type { DeviceProxy, ReadingQuantity, SliceHandle } from "@camstack/types";
|
|
24
24
|
import { type UseDeviceProxyTrpc } from "../../hooks/use-device-proxy";
|
|
25
25
|
import { type ControlDateTimeFormat } from "../../lib/format-control-datetime";
|
|
26
26
|
import type { DeviceItemDevice } from "../device-item/helpers";
|
|
@@ -28,12 +28,8 @@ interface NumericDescriptor {
|
|
|
28
28
|
readonly kind: "numeric";
|
|
29
29
|
/** Field in the slice that holds the numeric value. */
|
|
30
30
|
readonly field: string;
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
* directly WITHOUT consulting the slice or sourceInfo.
|
|
34
|
-
* For the generic `numeric-sensor` role this is `undefined` — the unit is
|
|
35
|
-
* read live from the slice (`slice.unit`). */
|
|
36
|
-
readonly canonicalUnit?: string;
|
|
31
|
+
/** Quantity of the stored number. `device` reads `sourceInfo.quantity`. */
|
|
32
|
+
readonly quantity?: ReadingQuantity;
|
|
37
33
|
/** Accessor into DeviceProxy['state'] — returns a typed slice handle. */
|
|
38
34
|
readonly getSliceHandle: (state: DeviceProxy["state"]) => SliceHandle<unknown>;
|
|
39
35
|
}
|
|
@@ -99,13 +95,9 @@ export declare function hasBatteryNumericValue(slice: unknown, field: string): b
|
|
|
99
95
|
* avoids dynamic string indexing — each one is a concrete property
|
|
100
96
|
* access on the typed `DeviceProxy['state']` shape.
|
|
101
97
|
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* so no per-device lookup is needed.
|
|
106
|
-
* - Generic `numeric-sensor` has no `canonicalUnit`. The unit is read live
|
|
107
|
-
* from the slice (`slice.unit`) as populated by the provider from the
|
|
108
|
-
* upstream source on every state push.
|
|
98
|
+
* The unit comes from the descriptor's quantity through `resolveSensorDisplay`.
|
|
99
|
+
* A generic numeric sensor uses quantity `device` and the device's
|
|
100
|
+
* `sourceInfo.quantity`.
|
|
109
101
|
*/
|
|
110
102
|
export declare const ROLE_DESCRIPTOR: Record<string, SensorDescriptor>;
|
|
111
103
|
export interface StringSliceValue {
|
|
@@ -841,6 +841,8 @@ export declare const useFeatureProbeGetStatus: typeof trpc.featureProbe.getStatu
|
|
|
841
841
|
export declare const useFloodGetStatus: typeof trpc.flood.getStatus.useQuery;
|
|
842
842
|
/** Generated alias around `trpc.gas.getStatus.useQuery`. */
|
|
843
843
|
export declare const useGasGetStatus: typeof trpc.gas.getStatus.useQuery;
|
|
844
|
+
/** Generated alias around `trpc.groupStatus.getStatus.useQuery`. */
|
|
845
|
+
export declare const useGroupStatusGetStatus: typeof trpc.groupStatus.getStatus.useQuery;
|
|
844
846
|
/** Generated alias around `trpc.heatingValve.getStatus.useQuery`. */
|
|
845
847
|
export declare const useHeatingValveGetStatus: typeof trpc.heatingValve.getStatus.useQuery;
|
|
846
848
|
/** Generated alias around `trpc.humidifier.setOn.useMutation`. */
|
package/dist/index.cjs
CHANGED
|
@@ -11121,6 +11121,8 @@ var useFeatureProbeGetStatus = trpc.featureProbe.getStatus.useQuery;
|
|
|
11121
11121
|
var useFloodGetStatus = trpc.flood.getStatus.useQuery;
|
|
11122
11122
|
/** Generated alias around `trpc.gas.getStatus.useQuery`. */
|
|
11123
11123
|
var useGasGetStatus = trpc.gas.getStatus.useQuery;
|
|
11124
|
+
/** Generated alias around `trpc.groupStatus.getStatus.useQuery`. */
|
|
11125
|
+
var useGroupStatusGetStatus = trpc.groupStatus.getStatus.useQuery;
|
|
11124
11126
|
/** Generated alias around `trpc.heatingValve.getStatus.useQuery`. */
|
|
11125
11127
|
var useHeatingValveGetStatus = trpc.heatingValve.getStatus.useQuery;
|
|
11126
11128
|
/** Generated alias around `trpc.humidifier.setOn.useMutation`. */
|
|
@@ -20973,21 +20975,16 @@ Skeleton.displayName = "Skeleton";
|
|
|
20973
20975
|
/** `value` null = unknown (D393) — renders an em-dash, never a fake `0`. */
|
|
20974
20976
|
function formatRemaining(t, r) {
|
|
20975
20977
|
if (r.value === null) return "—";
|
|
20976
|
-
|
|
20977
|
-
|
|
20978
|
-
|
|
20979
|
-
|
|
20980
|
-
|
|
20981
|
-
|
|
20982
|
-
|
|
20983
|
-
|
|
20984
|
-
|
|
20985
|
-
|
|
20986
|
-
case "cycles": return t("uiLibrary.consumablesPanel.cycles", {
|
|
20987
|
-
one: "{{count}} cycle",
|
|
20988
|
-
other: "{{count}} cycles"
|
|
20989
|
-
}, { count });
|
|
20990
|
-
}
|
|
20978
|
+
if (r.kind === "count") return t("uiLibrary.consumablesPanel.cycles", {
|
|
20979
|
+
one: "{{count}} cycle",
|
|
20980
|
+
other: "{{count}} cycles"
|
|
20981
|
+
}, { count: r.value });
|
|
20982
|
+
const rendered = (0, _camstack_types.formatReading)({
|
|
20983
|
+
value: r.value,
|
|
20984
|
+
quantity: "duration",
|
|
20985
|
+
precision: 0
|
|
20986
|
+
});
|
|
20987
|
+
return rendered.unit.length === 0 ? rendered.text : `${rendered.text} ${rendered.unit}`;
|
|
20991
20988
|
}
|
|
20992
20989
|
/** Format a ms-epoch as a compact local date for the "replaced" hint. */
|
|
20993
20990
|
function formatResetAt(ms) {
|
|
@@ -46083,6 +46080,66 @@ function formatNumeric(value, precision) {
|
|
|
46083
46080
|
return Number.isInteger(value) ? String(value) : value.toFixed(1);
|
|
46084
46081
|
}
|
|
46085
46082
|
//#endregion
|
|
46083
|
+
//#region src/lib/resolve-sensor-display.ts
|
|
46084
|
+
function canonUnit(raw) {
|
|
46085
|
+
if (raw === void 0) return void 0;
|
|
46086
|
+
return (0, _camstack_types.normalizeUnit)(raw) ?? raw;
|
|
46087
|
+
}
|
|
46088
|
+
function concreteQuantity(input) {
|
|
46089
|
+
if (input.quantity === void 0) return void 0;
|
|
46090
|
+
if (input.quantity === "device") {
|
|
46091
|
+
const chosen = input.deviceQuantity;
|
|
46092
|
+
if (chosen === void 0 || chosen === "device") return void 0;
|
|
46093
|
+
return chosen;
|
|
46094
|
+
}
|
|
46095
|
+
return input.quantity;
|
|
46096
|
+
}
|
|
46097
|
+
function resolveSensorDisplay(input) {
|
|
46098
|
+
const perCap = input.capName !== void 0 ? input.display?.perCap?.[input.capName] : void 0;
|
|
46099
|
+
const precision = perCap?.precision ?? input.display?.precision ?? input.roleDefault?.precision ?? input.declarationPrecision;
|
|
46100
|
+
const quantity = concreteQuantity(input);
|
|
46101
|
+
const preference = input.preference ?? _camstack_types.DEFAULT_DISPLAY_PREFERENCE;
|
|
46102
|
+
const base = quantity === void 0 ? void 0 : (0, _camstack_types.baseUnitOf)(quantity);
|
|
46103
|
+
const preferred = quantity === void 0 ? void 0 : (0, _camstack_types.displayUnitFor)(quantity, preference);
|
|
46104
|
+
const overrideUnit = canonUnit(perCap?.unit ?? input.display?.unit ?? input.roleDefault?.unit);
|
|
46105
|
+
const overrideOk = overrideUnit !== void 0 && base !== void 0 && overrideUnit !== base && (0, _camstack_types.canConvertUnit)(base, overrideUnit);
|
|
46106
|
+
const unit = overrideOk ? overrideUnit : preferred ?? "";
|
|
46107
|
+
const scales = quantity === "duration" || quantity === "information" || quantity === "duration-ms";
|
|
46108
|
+
const toDisplayValue = (raw) => {
|
|
46109
|
+
if (scales || base === void 0 || unit.length === 0 || unit === base) return raw;
|
|
46110
|
+
return (0, _camstack_types.tryConvertUnit)(raw, base, unit) ?? raw;
|
|
46111
|
+
};
|
|
46112
|
+
const fromDisplayValue = (shown) => {
|
|
46113
|
+
if (scales || base === void 0 || unit.length === 0 || unit === base) return shown;
|
|
46114
|
+
return (0, _camstack_types.tryConvertUnit)(shown, unit, base) ?? shown;
|
|
46115
|
+
};
|
|
46116
|
+
const format = (raw) => {
|
|
46117
|
+
if (quantity !== void 0 && !overrideOk) {
|
|
46118
|
+
const rendered = (0, _camstack_types.formatReading)({
|
|
46119
|
+
value: raw,
|
|
46120
|
+
quantity,
|
|
46121
|
+
preference,
|
|
46122
|
+
precision
|
|
46123
|
+
});
|
|
46124
|
+
return {
|
|
46125
|
+
text: rendered.text,
|
|
46126
|
+
unit: rendered.unit
|
|
46127
|
+
};
|
|
46128
|
+
}
|
|
46129
|
+
return {
|
|
46130
|
+
text: formatNumeric(toDisplayValue(raw), precision),
|
|
46131
|
+
unit
|
|
46132
|
+
};
|
|
46133
|
+
};
|
|
46134
|
+
return {
|
|
46135
|
+
unit,
|
|
46136
|
+
precision,
|
|
46137
|
+
toDisplayValue,
|
|
46138
|
+
fromDisplayValue,
|
|
46139
|
+
format
|
|
46140
|
+
};
|
|
46141
|
+
}
|
|
46142
|
+
//#endregion
|
|
46086
46143
|
//#region src/lib/format-control-datetime.ts
|
|
46087
46144
|
var DATE_RE = /^(\d{4})-(\d{2})-(\d{2})/;
|
|
46088
46145
|
var TIME_RE = /^(\d{2}):(\d{2})(?::(\d{2}))?$/;
|
|
@@ -46148,8 +46205,20 @@ function toDateInput(value) {
|
|
|
46148
46205
|
function toTimeInput(value) {
|
|
46149
46206
|
return value.slice(0, 5);
|
|
46150
46207
|
}
|
|
46151
|
-
|
|
46152
|
-
|
|
46208
|
+
function datetimeEditor(format, rawValue) {
|
|
46209
|
+
if (format === "date") return {
|
|
46210
|
+
type: "date",
|
|
46211
|
+
value: toDateInput(rawValue)
|
|
46212
|
+
};
|
|
46213
|
+
if (format === "time") return {
|
|
46214
|
+
type: "time",
|
|
46215
|
+
value: toTimeInput(rawValue)
|
|
46216
|
+
};
|
|
46217
|
+
return {
|
|
46218
|
+
type: "datetime-local",
|
|
46219
|
+
value: toDateTimeInput(rawValue)
|
|
46220
|
+
};
|
|
46221
|
+
}
|
|
46153
46222
|
function toDateTimeInput(value) {
|
|
46154
46223
|
return (value.includes("T") ? value : value.replace(" ", "T")).slice(0, 16);
|
|
46155
46224
|
}
|
|
@@ -46242,24 +46311,35 @@ function TextControl({ value, busy, onCommit }) {
|
|
|
46242
46311
|
className: cn("rounded border border-border bg-surface px-2 py-0.5 text-xs", busy && "opacity-40 cursor-not-allowed")
|
|
46243
46312
|
});
|
|
46244
46313
|
}
|
|
46245
|
-
function ControlWidget({ slice, busy, setValueFn, layout }) {
|
|
46314
|
+
function ControlWidget({ slice, busy, setValueFn, layout, quantity, display, dateTimeShape }) {
|
|
46246
46315
|
const t = useUiText();
|
|
46247
46316
|
switch (slice.kind) {
|
|
46248
|
-
case "numeric":
|
|
46249
|
-
|
|
46250
|
-
|
|
46251
|
-
|
|
46252
|
-
|
|
46253
|
-
|
|
46254
|
-
|
|
46255
|
-
|
|
46256
|
-
|
|
46257
|
-
unit
|
|
46258
|
-
|
|
46259
|
-
|
|
46260
|
-
|
|
46261
|
-
|
|
46262
|
-
|
|
46317
|
+
case "numeric": {
|
|
46318
|
+
const numericValue = typeof slice.value === "number" ? slice.value : Number(slice.value);
|
|
46319
|
+
const resolved = quantity === void 0 ? void 0 : resolveSensorDisplay({
|
|
46320
|
+
quantity,
|
|
46321
|
+
display,
|
|
46322
|
+
capName: "control"
|
|
46323
|
+
});
|
|
46324
|
+
const toShown = resolved?.toDisplayValue ?? ((raw) => raw);
|
|
46325
|
+
const toBase = resolved?.fromDisplayValue ?? ((shown) => shown);
|
|
46326
|
+
const unit = resolved !== void 0 && resolved.unit.length > 0 ? resolved.unit : void 0;
|
|
46327
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumericControl, {
|
|
46328
|
+
value: toShown(numericValue),
|
|
46329
|
+
busy,
|
|
46330
|
+
onCommit: (v) => {
|
|
46331
|
+
setValueFn({ control: {
|
|
46332
|
+
kind: "numeric",
|
|
46333
|
+
value: toBase(v)
|
|
46334
|
+
} });
|
|
46335
|
+
},
|
|
46336
|
+
unit,
|
|
46337
|
+
min: slice.min === void 0 ? void 0 : toShown(slice.min),
|
|
46338
|
+
max: slice.max === void 0 ? void 0 : toShown(slice.max),
|
|
46339
|
+
step: slice.step === void 0 ? void 0 : toShown(slice.step),
|
|
46340
|
+
precision: resolved?.precision
|
|
46341
|
+
});
|
|
46342
|
+
}
|
|
46263
46343
|
case "select": {
|
|
46264
46344
|
const selectValue = String(slice.value);
|
|
46265
46345
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
@@ -46292,7 +46372,7 @@ function ControlWidget({ slice, busy, setValueFn, layout }) {
|
|
|
46292
46372
|
});
|
|
46293
46373
|
case "datetime": {
|
|
46294
46374
|
const rawValue = String(slice.value);
|
|
46295
|
-
const format =
|
|
46375
|
+
const format = dateTimeShape ?? "datetime";
|
|
46296
46376
|
if (layout === "compact") {
|
|
46297
46377
|
if (rawValue === "") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
46298
46378
|
"aria-label": t("uiLibrary.controlPanel.value", "Control value"),
|
|
@@ -46305,8 +46385,9 @@ function ControlWidget({ slice, busy, setValueFn, layout }) {
|
|
|
46305
46385
|
children: formatControlDateTime(rawValue, format)
|
|
46306
46386
|
});
|
|
46307
46387
|
}
|
|
46308
|
-
const
|
|
46309
|
-
const
|
|
46388
|
+
const editor = datetimeEditor(format, rawValue);
|
|
46389
|
+
const inputType = editor.type;
|
|
46390
|
+
const inputValue = editor.value;
|
|
46310
46391
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
46311
46392
|
type: inputType,
|
|
46312
46393
|
"aria-label": t("uiLibrary.controlPanel.value", "Control value"),
|
|
@@ -46326,6 +46407,7 @@ function ControlWidget({ slice, busy, setValueFn, layout }) {
|
|
|
46326
46407
|
}
|
|
46327
46408
|
}
|
|
46328
46409
|
function ControlPanel({ trpc, deviceId, features: _features, layout, optimistic }) {
|
|
46410
|
+
const info = useOptionalSystem()?.getDeviceInfo(deviceId) ?? null;
|
|
46329
46411
|
const { cap, slice, available } = useDeviceCapability({
|
|
46330
46412
|
trpc,
|
|
46331
46413
|
deviceId,
|
|
@@ -46335,6 +46417,8 @@ function ControlPanel({ trpc, deviceId, features: _features, layout, optimistic
|
|
|
46335
46417
|
const { display, applyOptimistic } = useOptimisticSlice(slice, { enabled: optimistic ?? true });
|
|
46336
46418
|
if (display === void 0) return null;
|
|
46337
46419
|
const busy = !available;
|
|
46420
|
+
const chosen = info?.sourceInfo?.quantity;
|
|
46421
|
+
const quantity = (0, _camstack_types.isReadingQuantity)(chosen) && chosen !== "device" && chosen !== "none" ? chosen : void 0;
|
|
46338
46422
|
const setValueFn = (input) => {
|
|
46339
46423
|
if (!busy && cap) {
|
|
46340
46424
|
applyOptimistic({ value: input.control.value });
|
|
@@ -46348,7 +46432,10 @@ function ControlPanel({ trpc, deviceId, features: _features, layout, optimistic
|
|
|
46348
46432
|
slice: display,
|
|
46349
46433
|
busy,
|
|
46350
46434
|
setValueFn,
|
|
46351
|
-
layout
|
|
46435
|
+
layout,
|
|
46436
|
+
quantity,
|
|
46437
|
+
display: info?.display,
|
|
46438
|
+
dateTimeShape: info?.sourceInfo?.dateTimeShape
|
|
46352
46439
|
})
|
|
46353
46440
|
});
|
|
46354
46441
|
}
|
|
@@ -46452,30 +46539,6 @@ function PopoverRowAction({ icon: Icon, title, children }) {
|
|
|
46452
46539
|
}), document.body)] });
|
|
46453
46540
|
}
|
|
46454
46541
|
//#endregion
|
|
46455
|
-
//#region src/lib/resolve-sensor-display.ts
|
|
46456
|
-
/** Table keys are canonical spellings; providers often send `C` / `F` / `celsius`. */
|
|
46457
|
-
function canonUnit(raw) {
|
|
46458
|
-
if (raw === void 0) return void 0;
|
|
46459
|
-
return (0, _camstack_types.normalizeUnit)(raw) ?? raw;
|
|
46460
|
-
}
|
|
46461
|
-
var identity = (raw) => raw;
|
|
46462
|
-
function resolveSensorDisplay(input) {
|
|
46463
|
-
const perCap = input.capName !== void 0 ? input.display?.perCap?.[input.capName] : void 0;
|
|
46464
|
-
const precision = perCap?.precision ?? input.display?.precision ?? input.roleDefault?.precision ?? input.slicePrecision;
|
|
46465
|
-
const sourceUnit = canonUnit(input.sliceUnit ?? input.canonicalUnit);
|
|
46466
|
-
const overrideUnit = canonUnit(perCap?.unit ?? input.display?.unit ?? input.roleDefault?.unit);
|
|
46467
|
-
if (overrideUnit !== void 0 && sourceUnit !== void 0 && overrideUnit !== sourceUnit && (0, _camstack_types.canConvertUnit)(sourceUnit, overrideUnit)) return {
|
|
46468
|
-
unit: overrideUnit,
|
|
46469
|
-
precision,
|
|
46470
|
-
toDisplayValue: (raw) => (0, _camstack_types.tryConvertUnit)(raw, sourceUnit, overrideUnit) ?? raw
|
|
46471
|
-
};
|
|
46472
|
-
return {
|
|
46473
|
-
unit: sourceUnit ?? "",
|
|
46474
|
-
precision,
|
|
46475
|
-
toDisplayValue: identity
|
|
46476
|
-
};
|
|
46477
|
-
}
|
|
46478
|
-
//#endregion
|
|
46479
46542
|
//#region src/composites/device-controls/sensor-value-atom.tsx
|
|
46480
46543
|
/**
|
|
46481
46544
|
* Binary fallback for the `battery-sensor` role.
|
|
@@ -46516,102 +46579,105 @@ function hasBatteryNumericValue(slice, field) {
|
|
|
46516
46579
|
* avoids dynamic string indexing — each one is a concrete property
|
|
46517
46580
|
* access on the typed `DeviceProxy['state']` shape.
|
|
46518
46581
|
*
|
|
46519
|
-
*
|
|
46520
|
-
*
|
|
46521
|
-
*
|
|
46522
|
-
* so no per-device lookup is needed.
|
|
46523
|
-
* - Generic `numeric-sensor` has no `canonicalUnit`. The unit is read live
|
|
46524
|
-
* from the slice (`slice.unit`) as populated by the provider from the
|
|
46525
|
-
* upstream source on every state push.
|
|
46582
|
+
* The unit comes from the descriptor's quantity through `resolveSensorDisplay`.
|
|
46583
|
+
* A generic numeric sensor uses quantity `device` and the device's
|
|
46584
|
+
* `sourceInfo.quantity`.
|
|
46526
46585
|
*/
|
|
46527
46586
|
var ROLE_DESCRIPTOR = {
|
|
46528
46587
|
"temperature-sensor": {
|
|
46529
46588
|
kind: "numeric",
|
|
46530
46589
|
field: "celsius",
|
|
46531
|
-
|
|
46590
|
+
quantity: "temperature",
|
|
46532
46591
|
getSliceHandle: (s) => s.temperatureSensor
|
|
46533
46592
|
},
|
|
46534
46593
|
"humidity-sensor": {
|
|
46535
46594
|
kind: "numeric",
|
|
46536
46595
|
field: "percent",
|
|
46537
|
-
|
|
46596
|
+
quantity: "humidity",
|
|
46538
46597
|
getSliceHandle: (s) => s.humiditySensor
|
|
46539
46598
|
},
|
|
46540
46599
|
"ambient-light-sensor": {
|
|
46541
46600
|
kind: "numeric",
|
|
46542
46601
|
field: "lux",
|
|
46543
|
-
|
|
46602
|
+
quantity: "illuminance",
|
|
46544
46603
|
getSliceHandle: (s) => s.ambientLightSensor
|
|
46545
46604
|
},
|
|
46546
46605
|
"pressure-sensor": {
|
|
46547
46606
|
kind: "numeric",
|
|
46548
46607
|
field: "hpa",
|
|
46549
|
-
|
|
46608
|
+
quantity: "pressure",
|
|
46550
46609
|
getSliceHandle: (s) => s.pressureSensor
|
|
46551
46610
|
},
|
|
46552
46611
|
"numeric-sensor": {
|
|
46553
46612
|
kind: "numeric",
|
|
46554
46613
|
field: "value",
|
|
46614
|
+
quantity: "device",
|
|
46555
46615
|
getSliceHandle: (s) => s.numericSensor
|
|
46556
46616
|
},
|
|
46557
46617
|
"wind-sensor": {
|
|
46558
46618
|
kind: "numeric",
|
|
46559
46619
|
field: "value",
|
|
46620
|
+
quantity: "device",
|
|
46560
46621
|
getSliceHandle: (s) => s.numericSensor
|
|
46561
46622
|
},
|
|
46562
46623
|
"rain-sensor": {
|
|
46563
46624
|
kind: "numeric",
|
|
46564
46625
|
field: "value",
|
|
46626
|
+
quantity: "device",
|
|
46565
46627
|
getSliceHandle: (s) => s.numericSensor
|
|
46566
46628
|
},
|
|
46567
46629
|
"uv-sensor": {
|
|
46568
46630
|
kind: "numeric",
|
|
46569
46631
|
field: "value",
|
|
46632
|
+
quantity: "device",
|
|
46570
46633
|
getSliceHandle: (s) => s.numericSensor
|
|
46571
46634
|
},
|
|
46572
46635
|
"solar-radiation-sensor": {
|
|
46573
46636
|
kind: "numeric",
|
|
46574
46637
|
field: "value",
|
|
46638
|
+
quantity: "device",
|
|
46575
46639
|
getSliceHandle: (s) => s.numericSensor
|
|
46576
46640
|
},
|
|
46577
46641
|
"soil-moisture-sensor": {
|
|
46578
46642
|
kind: "numeric",
|
|
46579
46643
|
field: "value",
|
|
46644
|
+
quantity: "device",
|
|
46580
46645
|
getSliceHandle: (s) => s.numericSensor
|
|
46581
46646
|
},
|
|
46582
46647
|
"power-sensor": {
|
|
46583
46648
|
kind: "numeric",
|
|
46584
46649
|
field: "watts",
|
|
46585
|
-
|
|
46650
|
+
quantity: "power",
|
|
46586
46651
|
getSliceHandle: (s) => s.powerMeter
|
|
46587
46652
|
},
|
|
46588
46653
|
"energy-sensor": {
|
|
46589
46654
|
kind: "numeric",
|
|
46590
46655
|
field: "kwhTotal",
|
|
46591
|
-
|
|
46656
|
+
quantity: "energy",
|
|
46592
46657
|
getSliceHandle: (s) => s.powerMeter
|
|
46593
46658
|
},
|
|
46594
46659
|
"voltage-sensor": {
|
|
46595
46660
|
kind: "numeric",
|
|
46596
46661
|
field: "volts",
|
|
46597
|
-
|
|
46662
|
+
quantity: "voltage",
|
|
46598
46663
|
getSliceHandle: (s) => s.powerMeter
|
|
46599
46664
|
},
|
|
46600
46665
|
"current-sensor": {
|
|
46601
46666
|
kind: "numeric",
|
|
46602
46667
|
field: "amps",
|
|
46603
|
-
|
|
46668
|
+
quantity: "current",
|
|
46604
46669
|
getSliceHandle: (s) => s.powerMeter
|
|
46605
46670
|
},
|
|
46606
46671
|
"battery-sensor": {
|
|
46607
46672
|
kind: "numeric",
|
|
46608
46673
|
field: "value",
|
|
46609
|
-
|
|
46674
|
+
quantity: "percentage",
|
|
46610
46675
|
getSliceHandle: (s) => s.numericSensor
|
|
46611
46676
|
},
|
|
46612
46677
|
"generic-sensor": {
|
|
46613
46678
|
kind: "numeric",
|
|
46614
46679
|
field: "value",
|
|
46680
|
+
quantity: "device",
|
|
46615
46681
|
getSliceHandle: (s) => s.numericSensor
|
|
46616
46682
|
},
|
|
46617
46683
|
"motion-sensor": {
|
|
@@ -46729,19 +46795,16 @@ function SensorValueAtom({ trpc, device }) {
|
|
|
46729
46795
|
children: enumSensorSlice.value
|
|
46730
46796
|
});
|
|
46731
46797
|
if (numericIsColStart) return null;
|
|
46732
|
-
const
|
|
46733
|
-
const
|
|
46734
|
-
const slicePrecision = typeof sliceRecord["precision"] === "number" ? sliceRecord["precision"] : void 0;
|
|
46735
|
-
const resolved = resolveSensorDisplay({
|
|
46798
|
+
const deviceQuantity = device.sourceInfo?.quantity;
|
|
46799
|
+
const rendered = resolveSensorDisplay({
|
|
46736
46800
|
display: device.display,
|
|
46737
46801
|
capName: device.role === "numeric-sensor" ? "numeric-sensor" : void 0,
|
|
46738
46802
|
roleDefault: device.role != null ? roleDefaultsData?.defaults[device.role] : void 0,
|
|
46739
|
-
|
|
46740
|
-
|
|
46741
|
-
|
|
46742
|
-
|
|
46743
|
-
const
|
|
46744
|
-
const formatted = formatNumeric(resolved.toDisplayValue(rawValue), resolved.precision);
|
|
46803
|
+
quantity: descriptor.quantity ?? "device",
|
|
46804
|
+
deviceQuantity: deviceQuantity === "device" ? void 0 : deviceQuantity
|
|
46805
|
+
}).format(rawValue);
|
|
46806
|
+
const unit = rendered.unit;
|
|
46807
|
+
const formatted = rendered.text;
|
|
46745
46808
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
46746
46809
|
className: cn("inline-flex items-baseline gap-0.5 tabular-nums text-xs text-foreground"),
|
|
46747
46810
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatted }), unit && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -46771,7 +46834,7 @@ function SensorValueAtom({ trpc, device }) {
|
|
|
46771
46834
|
if (descriptor.kind === "datetime") {
|
|
46772
46835
|
if (!isStringSliceValue(slice)) return null;
|
|
46773
46836
|
if (slice.value === "") return null;
|
|
46774
|
-
const format = readSliceDateTimeFormat(slice) ?? descriptor.format;
|
|
46837
|
+
const format = device.sourceInfo?.dateTimeShape ?? readSliceDateTimeFormat(slice) ?? descriptor.format;
|
|
46775
46838
|
const formatted = formatControlDateTime(slice.value, format);
|
|
46776
46839
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
46777
46840
|
className: cn("inline-flex items-baseline text-xs text-foreground"),
|
|
@@ -47326,17 +47389,14 @@ function SensorInlineControl({ trpc, deviceId }) {
|
|
|
47326
47389
|
*
|
|
47327
47390
|
* Renders:
|
|
47328
47391
|
* - Typed numeric sensors: value in `text-3xl font-semibold tabular-nums` +
|
|
47329
|
-
*
|
|
47330
|
-
* - Generic numeric-sensor:
|
|
47331
|
-
* (`slice.unit`, populated by the HA provider from state attributes).
|
|
47392
|
+
* the unit `resolveSensorDisplay` derives from the quantity.
|
|
47393
|
+
* - Generic numeric-sensor: the same formatter, with the device's quantity.
|
|
47332
47394
|
* - Binary sensors: coloured state badge ("Detected"/"Clear" etc.) — same
|
|
47333
47395
|
* labels and colours as SensorValueAtom's binary branch.
|
|
47334
47396
|
* - Presence: Home/Away/zone-name badge — same labels as SensorValueAtom.
|
|
47335
47397
|
* - All: sensor name label below the value.
|
|
47336
47398
|
*
|
|
47337
|
-
* Unit resolution uses the same
|
|
47338
|
-
* wins, ROLE_DESCRIPTOR canonicalUnit as fallback) — the two surfaces stay
|
|
47339
|
-
* in sync.
|
|
47399
|
+
* Unit resolution uses the same formatter as SensorValueAtom.
|
|
47340
47400
|
*/
|
|
47341
47401
|
function isNumericSliceValue(v) {
|
|
47342
47402
|
return typeof v === "object" && v !== null;
|
|
@@ -47403,19 +47463,16 @@ function SensorHeroInner({ trpc, device }) {
|
|
|
47403
47463
|
if (!isNumericSliceValue(slice)) return null;
|
|
47404
47464
|
const rawValue = slice[descriptor.field];
|
|
47405
47465
|
if (typeof rawValue !== "number") return null;
|
|
47406
|
-
const
|
|
47407
|
-
const
|
|
47408
|
-
const slicePrecision = typeof sliceRecord["precision"] === "number" ? sliceRecord["precision"] : void 0;
|
|
47409
|
-
const resolved = resolveSensorDisplay({
|
|
47466
|
+
const deviceQuantity = device.sourceInfo?.quantity;
|
|
47467
|
+
const rendered = resolveSensorDisplay({
|
|
47410
47468
|
display: device.display,
|
|
47411
47469
|
capName: device.role === "numeric-sensor" ? "numeric-sensor" : void 0,
|
|
47412
47470
|
roleDefault: device.role != null ? roleDefaultsData?.defaults[device.role] : roleDefaultsData?.defaults["temperature-sensor"],
|
|
47413
|
-
|
|
47414
|
-
|
|
47415
|
-
|
|
47416
|
-
|
|
47417
|
-
const
|
|
47418
|
-
const formatted = formatNumeric(resolved.toDisplayValue(rawValue), resolved.precision);
|
|
47471
|
+
quantity: descriptor.quantity ?? "device",
|
|
47472
|
+
deviceQuantity: deviceQuantity === "device" ? void 0 : deviceQuantity
|
|
47473
|
+
}).format(rawValue);
|
|
47474
|
+
const unit = rendered.unit;
|
|
47475
|
+
const formatted = rendered.text;
|
|
47419
47476
|
const occupancyOn = occupancySlice !== void 0 && isBinarySliceValue(occupancySlice) && typeof occupancySlice["on"] === "boolean" ? Boolean(occupancySlice["on"]) : null;
|
|
47420
47477
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47421
47478
|
className: "flex flex-col items-center justify-center gap-2 rounded-lg border border-border bg-surface p-4",
|
|
@@ -47471,7 +47528,7 @@ function SensorHeroInner({ trpc, device }) {
|
|
|
47471
47528
|
children: t("uiLibrary.sensorHeroCard.awaiting", "Awaiting state…")
|
|
47472
47529
|
})]
|
|
47473
47530
|
});
|
|
47474
|
-
const format = readSliceDateTimeFormat(slice) ?? descriptor.format;
|
|
47531
|
+
const format = device.sourceInfo?.dateTimeShape ?? readSliceDateTimeFormat(slice) ?? descriptor.format;
|
|
47475
47532
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47476
47533
|
className: "flex flex-col items-center justify-center gap-2 rounded-lg border border-border bg-surface p-4",
|
|
47477
47534
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -49927,10 +49984,15 @@ function useWeather(trpc, deviceId) {
|
|
|
49927
49984
|
slice: (d) => d.state.weather
|
|
49928
49985
|
});
|
|
49929
49986
|
}
|
|
49930
|
-
/** Format a
|
|
49931
|
-
function
|
|
49932
|
-
if (
|
|
49933
|
-
|
|
49987
|
+
/** Format a base-unit weather reading (metric). */
|
|
49988
|
+
function formatWeather(value, quantity) {
|
|
49989
|
+
if (value === null) return "—";
|
|
49990
|
+
const rendered = (0, _camstack_types.formatReading)({
|
|
49991
|
+
value,
|
|
49992
|
+
quantity,
|
|
49993
|
+
precision: 0
|
|
49994
|
+
});
|
|
49995
|
+
return rendered.unit.length === 0 ? rendered.text : `${rendered.text}${rendered.unit}`;
|
|
49934
49996
|
}
|
|
49935
49997
|
/**
|
|
49936
49998
|
* WeatherInlineControl — compact condition icon + temperature readout
|
|
@@ -49950,7 +50012,7 @@ function WeatherInlineControl({ trpc, deviceId }) {
|
|
|
49950
50012
|
children: inlineGlyph(meta.glyph)
|
|
49951
50013
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
49952
50014
|
className: "tabular-nums",
|
|
49953
|
-
children:
|
|
50015
|
+
children: formatWeather(slice.temperature, "temperature")
|
|
49954
50016
|
})]
|
|
49955
50017
|
});
|
|
49956
50018
|
}
|
|
@@ -50260,7 +50322,11 @@ function WeatherHeroCard({ trpc, deviceId }) {
|
|
|
50260
50322
|
if (slice === void 0) return null;
|
|
50261
50323
|
const meta = weatherConditionMeta(slice.condition, t);
|
|
50262
50324
|
const tint = weatherTint(slice.condition);
|
|
50263
|
-
const
|
|
50325
|
+
const temperature = slice.temperature === null ? void 0 : (0, _camstack_types.formatReading)({
|
|
50326
|
+
value: slice.temperature,
|
|
50327
|
+
quantity: "temperature",
|
|
50328
|
+
precision: 0
|
|
50329
|
+
});
|
|
50264
50330
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
50265
50331
|
className: "flex flex-col items-stretch gap-4 rounded-lg border border-border bg-surface p-4",
|
|
50266
50332
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -50275,11 +50341,14 @@ function WeatherHeroCard({ trpc, deviceId }) {
|
|
|
50275
50341
|
children: [
|
|
50276
50342
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
50277
50343
|
className: "text-3xl font-semibold tabular-nums text-foreground",
|
|
50278
|
-
children:
|
|
50344
|
+
children: temperature !== void 0 ? temperature.text : "—"
|
|
50279
50345
|
}),
|
|
50280
50346
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
50281
50347
|
className: "text-base text-foreground-subtle",
|
|
50282
|
-
children: unit
|
|
50348
|
+
children: temperature !== void 0 ? temperature.unit : (0, _camstack_types.formatReading)({
|
|
50349
|
+
value: 0,
|
|
50350
|
+
quantity: "temperature"
|
|
50351
|
+
}).unit
|
|
50283
50352
|
}),
|
|
50284
50353
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
50285
50354
|
className: "text-xs text-foreground-subtle ml-2 capitalize",
|
|
@@ -50300,12 +50369,12 @@ function WeatherHeroCard({ trpc, deviceId }) {
|
|
|
50300
50369
|
style: { transform: `rotate(${slice.windBearing}deg)` }
|
|
50301
50370
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Wind, { className: "h-3.5 w-3.5" }),
|
|
50302
50371
|
label: t("uiLibrary.weatherControl.wind", "Wind"),
|
|
50303
|
-
value:
|
|
50372
|
+
value: formatWeather(slice.windSpeed, "speed")
|
|
50304
50373
|
}),
|
|
50305
50374
|
slice.pressure !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Readout$1, {
|
|
50306
50375
|
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Gauge, { className: "h-3.5 w-3.5" }),
|
|
50307
50376
|
label: t("uiLibrary.weatherControl.pressure", "Pressure"),
|
|
50308
|
-
value:
|
|
50377
|
+
value: formatWeather(slice.pressure, "pressure")
|
|
50309
50378
|
})
|
|
50310
50379
|
]
|
|
50311
50380
|
})]
|
|
@@ -50544,6 +50613,16 @@ var VOLUME_MAX = 9;
|
|
|
50544
50613
|
/** Amber kibble accent for the bowl fill; destructive red when low. */
|
|
50545
50614
|
var KIBBLE = "#f59e0b";
|
|
50546
50615
|
var KIBBLE_LOW = "#ef4444";
|
|
50616
|
+
function desiccantLabel(t, seconds) {
|
|
50617
|
+
const rendered = (0, _camstack_types.formatReading)({
|
|
50618
|
+
value: seconds,
|
|
50619
|
+
quantity: "duration"
|
|
50620
|
+
});
|
|
50621
|
+
return t("uiLibrary.petFeeder.desiccant", "{{text}}{{unit}} desiccant", {
|
|
50622
|
+
text: rendered.text,
|
|
50623
|
+
unit: rendered.unit
|
|
50624
|
+
});
|
|
50625
|
+
}
|
|
50547
50626
|
function clampPortion(grams) {
|
|
50548
50627
|
return Math.min(_camstack_types.PET_FEEDER_MANUAL_FEED_MAX, Math.max(_camstack_types.PET_FEEDER_MANUAL_FEED_MIN, grams));
|
|
50549
50628
|
}
|
|
@@ -50666,7 +50745,7 @@ function PetFeederPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
50666
50745
|
}),
|
|
50667
50746
|
display.desiccantLeftDays !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Readout, {
|
|
50668
50747
|
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Wind, { className: "h-3.5 w-3.5" }),
|
|
50669
|
-
label: t
|
|
50748
|
+
label: desiccantLabel(t, display.desiccantLeftDays)
|
|
50670
50749
|
}),
|
|
50671
50750
|
onBatteries && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
50672
50751
|
className: "inline-flex items-center gap-1 font-medium text-amber-500",
|
|
@@ -70033,6 +70112,7 @@ exports.useFanControlSetPreset = useFanControlSetPreset;
|
|
|
70033
70112
|
exports.useFeatureProbeGetStatus = useFeatureProbeGetStatus;
|
|
70034
70113
|
exports.useFloodGetStatus = useFloodGetStatus;
|
|
70035
70114
|
exports.useGasGetStatus = useGasGetStatus;
|
|
70115
|
+
exports.useGroupStatusGetStatus = useGroupStatusGetStatus;
|
|
70036
70116
|
exports.useHeatingValveGetStatus = useHeatingValveGetStatus;
|
|
70037
70117
|
exports.useHistoryPanels = useHistoryPanels;
|
|
70038
70118
|
exports.useHistoryReader = useHistoryReader;
|