@camstack/ui-library 1.2.324 → 1.2.326
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/binary-battery.d.ts +8 -0
- 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/composites/index.d.ts +1 -0
- package/dist/index.cjs +211 -121
- package/dist/index.js +209 -123
- package/dist/lib/resolve-sensor-display.d.ts +22 -24
- package/package.json +10 -1
package/dist/index.cjs
CHANGED
|
@@ -20973,21 +20973,16 @@ Skeleton.displayName = "Skeleton";
|
|
|
20973
20973
|
/** `value` null = unknown (D393) — renders an em-dash, never a fake `0`. */
|
|
20974
20974
|
function formatRemaining(t, r) {
|
|
20975
20975
|
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
|
-
}
|
|
20976
|
+
if (r.kind === "count") return t("uiLibrary.consumablesPanel.cycles", {
|
|
20977
|
+
one: "{{count}} cycle",
|
|
20978
|
+
other: "{{count}} cycles"
|
|
20979
|
+
}, { count: r.value });
|
|
20980
|
+
const rendered = (0, _camstack_types.formatReading)({
|
|
20981
|
+
value: r.value,
|
|
20982
|
+
quantity: "duration",
|
|
20983
|
+
precision: 0
|
|
20984
|
+
});
|
|
20985
|
+
return rendered.unit.length === 0 ? rendered.text : `${rendered.text} ${rendered.unit}`;
|
|
20991
20986
|
}
|
|
20992
20987
|
/** Format a ms-epoch as a compact local date for the "replaced" hint. */
|
|
20993
20988
|
function formatResetAt(ms) {
|
|
@@ -33995,10 +33990,19 @@ function AudioWaveform({ samples, sampleRate = 16e3, height = 96, className = ""
|
|
|
33995
33990
|
});
|
|
33996
33991
|
}
|
|
33997
33992
|
//#endregion
|
|
33993
|
+
//#region src/composites/binary-battery.ts
|
|
33994
|
+
/**
|
|
33995
|
+
* Binary battery copy lives with the badge. A binary source has no real
|
|
33996
|
+
* percentage: normal is the full stand-in, low is anything under the threshold.
|
|
33997
|
+
*/
|
|
33998
|
+
var BINARY_BATTERY_LOW_BELOW = 50;
|
|
33999
|
+
var BATTERY_BADGE_LOW_KEY = "uiLibrary.batteryBadge.low";
|
|
34000
|
+
var BATTERY_BADGE_NORMAL_KEY = "uiLibrary.batteryBadge.normal";
|
|
34001
|
+
function binaryBatteryIsLow(percentage) {
|
|
34002
|
+
return percentage < 50;
|
|
34003
|
+
}
|
|
34004
|
+
//#endregion
|
|
33998
34005
|
//#region src/composites/battery-badge.tsx
|
|
33999
|
-
/** A binary battery is "low" below this stand-in percentage (normal maps to
|
|
34000
|
-
* 100, low to a sub-threshold value). Keep in sync with the HA container. */
|
|
34001
|
-
var BINARY_LOW_THRESHOLD = 50;
|
|
34002
34006
|
function pickIcon(percentage) {
|
|
34003
34007
|
if (percentage <= 10) return BatteryWarning;
|
|
34004
34008
|
if (percentage <= 30) return BatteryLow;
|
|
@@ -34025,12 +34029,12 @@ function BatteryBadge({ status, variant = "compact", className }) {
|
|
|
34025
34029
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Battery, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("uiLibrary.batteryBadge.unknown", "Unknown") })]
|
|
34026
34030
|
});
|
|
34027
34031
|
if (binary === true) {
|
|
34028
|
-
const low = percentage
|
|
34032
|
+
const low = binaryBatteryIsLow(percentage);
|
|
34029
34033
|
const BinIcon = low ? BatteryWarning : BatteryFull;
|
|
34030
34034
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
34031
34035
|
className: cn("inline-flex items-center gap-1 text-[10px] font-medium", low ? "text-rose-400" : "text-foreground-subtle", className),
|
|
34032
34036
|
title: low ? t("uiLibrary.batteryBadge.titleLow", "Battery: low") : t("uiLibrary.batteryBadge.titleNormal", "Battery: normal"),
|
|
34033
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BinIcon, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: low ? t(
|
|
34037
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BinIcon, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: low ? t(BATTERY_BADGE_LOW_KEY, "Low") : t(BATTERY_BADGE_NORMAL_KEY, "Normal") })]
|
|
34034
34038
|
});
|
|
34035
34039
|
}
|
|
34036
34040
|
const Icon = pickIcon(percentage);
|
|
@@ -46074,6 +46078,66 @@ function formatNumeric(value, precision) {
|
|
|
46074
46078
|
return Number.isInteger(value) ? String(value) : value.toFixed(1);
|
|
46075
46079
|
}
|
|
46076
46080
|
//#endregion
|
|
46081
|
+
//#region src/lib/resolve-sensor-display.ts
|
|
46082
|
+
function canonUnit(raw) {
|
|
46083
|
+
if (raw === void 0) return void 0;
|
|
46084
|
+
return (0, _camstack_types.normalizeUnit)(raw) ?? raw;
|
|
46085
|
+
}
|
|
46086
|
+
function concreteQuantity(input) {
|
|
46087
|
+
if (input.quantity === void 0) return void 0;
|
|
46088
|
+
if (input.quantity === "device") {
|
|
46089
|
+
const chosen = input.deviceQuantity;
|
|
46090
|
+
if (chosen === void 0 || chosen === "device") return void 0;
|
|
46091
|
+
return chosen;
|
|
46092
|
+
}
|
|
46093
|
+
return input.quantity;
|
|
46094
|
+
}
|
|
46095
|
+
function resolveSensorDisplay(input) {
|
|
46096
|
+
const perCap = input.capName !== void 0 ? input.display?.perCap?.[input.capName] : void 0;
|
|
46097
|
+
const precision = perCap?.precision ?? input.display?.precision ?? input.roleDefault?.precision ?? input.declarationPrecision;
|
|
46098
|
+
const quantity = concreteQuantity(input);
|
|
46099
|
+
const preference = input.preference ?? _camstack_types.DEFAULT_DISPLAY_PREFERENCE;
|
|
46100
|
+
const base = quantity === void 0 ? void 0 : (0, _camstack_types.baseUnitOf)(quantity);
|
|
46101
|
+
const preferred = quantity === void 0 ? void 0 : (0, _camstack_types.displayUnitFor)(quantity, preference);
|
|
46102
|
+
const overrideUnit = canonUnit(perCap?.unit ?? input.display?.unit ?? input.roleDefault?.unit);
|
|
46103
|
+
const overrideOk = overrideUnit !== void 0 && base !== void 0 && overrideUnit !== base && (0, _camstack_types.canConvertUnit)(base, overrideUnit);
|
|
46104
|
+
const unit = overrideOk ? overrideUnit : preferred ?? "";
|
|
46105
|
+
const scales = quantity === "duration" || quantity === "information" || quantity === "duration-ms";
|
|
46106
|
+
const toDisplayValue = (raw) => {
|
|
46107
|
+
if (scales || base === void 0 || unit.length === 0 || unit === base) return raw;
|
|
46108
|
+
return (0, _camstack_types.tryConvertUnit)(raw, base, unit) ?? raw;
|
|
46109
|
+
};
|
|
46110
|
+
const fromDisplayValue = (shown) => {
|
|
46111
|
+
if (scales || base === void 0 || unit.length === 0 || unit === base) return shown;
|
|
46112
|
+
return (0, _camstack_types.tryConvertUnit)(shown, unit, base) ?? shown;
|
|
46113
|
+
};
|
|
46114
|
+
const format = (raw) => {
|
|
46115
|
+
if (quantity !== void 0 && !overrideOk) {
|
|
46116
|
+
const rendered = (0, _camstack_types.formatReading)({
|
|
46117
|
+
value: raw,
|
|
46118
|
+
quantity,
|
|
46119
|
+
preference,
|
|
46120
|
+
precision
|
|
46121
|
+
});
|
|
46122
|
+
return {
|
|
46123
|
+
text: rendered.text,
|
|
46124
|
+
unit: rendered.unit
|
|
46125
|
+
};
|
|
46126
|
+
}
|
|
46127
|
+
return {
|
|
46128
|
+
text: formatNumeric(toDisplayValue(raw), precision),
|
|
46129
|
+
unit
|
|
46130
|
+
};
|
|
46131
|
+
};
|
|
46132
|
+
return {
|
|
46133
|
+
unit,
|
|
46134
|
+
precision,
|
|
46135
|
+
toDisplayValue,
|
|
46136
|
+
fromDisplayValue,
|
|
46137
|
+
format
|
|
46138
|
+
};
|
|
46139
|
+
}
|
|
46140
|
+
//#endregion
|
|
46077
46141
|
//#region src/lib/format-control-datetime.ts
|
|
46078
46142
|
var DATE_RE = /^(\d{4})-(\d{2})-(\d{2})/;
|
|
46079
46143
|
var TIME_RE = /^(\d{2}):(\d{2})(?::(\d{2}))?$/;
|
|
@@ -46139,8 +46203,20 @@ function toDateInput(value) {
|
|
|
46139
46203
|
function toTimeInput(value) {
|
|
46140
46204
|
return value.slice(0, 5);
|
|
46141
46205
|
}
|
|
46142
|
-
|
|
46143
|
-
|
|
46206
|
+
function datetimeEditor(format, rawValue) {
|
|
46207
|
+
if (format === "date") return {
|
|
46208
|
+
type: "date",
|
|
46209
|
+
value: toDateInput(rawValue)
|
|
46210
|
+
};
|
|
46211
|
+
if (format === "time") return {
|
|
46212
|
+
type: "time",
|
|
46213
|
+
value: toTimeInput(rawValue)
|
|
46214
|
+
};
|
|
46215
|
+
return {
|
|
46216
|
+
type: "datetime-local",
|
|
46217
|
+
value: toDateTimeInput(rawValue)
|
|
46218
|
+
};
|
|
46219
|
+
}
|
|
46144
46220
|
function toDateTimeInput(value) {
|
|
46145
46221
|
return (value.includes("T") ? value : value.replace(" ", "T")).slice(0, 16);
|
|
46146
46222
|
}
|
|
@@ -46233,24 +46309,35 @@ function TextControl({ value, busy, onCommit }) {
|
|
|
46233
46309
|
className: cn("rounded border border-border bg-surface px-2 py-0.5 text-xs", busy && "opacity-40 cursor-not-allowed")
|
|
46234
46310
|
});
|
|
46235
46311
|
}
|
|
46236
|
-
function ControlWidget({ slice, busy, setValueFn, layout }) {
|
|
46312
|
+
function ControlWidget({ slice, busy, setValueFn, layout, quantity, display, dateTimeShape }) {
|
|
46237
46313
|
const t = useUiText();
|
|
46238
46314
|
switch (slice.kind) {
|
|
46239
|
-
case "numeric":
|
|
46240
|
-
|
|
46241
|
-
|
|
46242
|
-
|
|
46243
|
-
|
|
46244
|
-
|
|
46245
|
-
|
|
46246
|
-
|
|
46247
|
-
|
|
46248
|
-
unit
|
|
46249
|
-
|
|
46250
|
-
|
|
46251
|
-
|
|
46252
|
-
|
|
46253
|
-
|
|
46315
|
+
case "numeric": {
|
|
46316
|
+
const numericValue = typeof slice.value === "number" ? slice.value : Number(slice.value);
|
|
46317
|
+
const resolved = quantity === void 0 ? void 0 : resolveSensorDisplay({
|
|
46318
|
+
quantity,
|
|
46319
|
+
display,
|
|
46320
|
+
capName: "control"
|
|
46321
|
+
});
|
|
46322
|
+
const toShown = resolved?.toDisplayValue ?? ((raw) => raw);
|
|
46323
|
+
const toBase = resolved?.fromDisplayValue ?? ((shown) => shown);
|
|
46324
|
+
const unit = resolved !== void 0 && resolved.unit.length > 0 ? resolved.unit : void 0;
|
|
46325
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumericControl, {
|
|
46326
|
+
value: toShown(numericValue),
|
|
46327
|
+
busy,
|
|
46328
|
+
onCommit: (v) => {
|
|
46329
|
+
setValueFn({ control: {
|
|
46330
|
+
kind: "numeric",
|
|
46331
|
+
value: toBase(v)
|
|
46332
|
+
} });
|
|
46333
|
+
},
|
|
46334
|
+
unit,
|
|
46335
|
+
min: slice.min === void 0 ? void 0 : toShown(slice.min),
|
|
46336
|
+
max: slice.max === void 0 ? void 0 : toShown(slice.max),
|
|
46337
|
+
step: slice.step === void 0 ? void 0 : toShown(slice.step),
|
|
46338
|
+
precision: resolved?.precision
|
|
46339
|
+
});
|
|
46340
|
+
}
|
|
46254
46341
|
case "select": {
|
|
46255
46342
|
const selectValue = String(slice.value);
|
|
46256
46343
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
@@ -46283,7 +46370,7 @@ function ControlWidget({ slice, busy, setValueFn, layout }) {
|
|
|
46283
46370
|
});
|
|
46284
46371
|
case "datetime": {
|
|
46285
46372
|
const rawValue = String(slice.value);
|
|
46286
|
-
const format =
|
|
46373
|
+
const format = dateTimeShape ?? "datetime";
|
|
46287
46374
|
if (layout === "compact") {
|
|
46288
46375
|
if (rawValue === "") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
46289
46376
|
"aria-label": t("uiLibrary.controlPanel.value", "Control value"),
|
|
@@ -46296,8 +46383,9 @@ function ControlWidget({ slice, busy, setValueFn, layout }) {
|
|
|
46296
46383
|
children: formatControlDateTime(rawValue, format)
|
|
46297
46384
|
});
|
|
46298
46385
|
}
|
|
46299
|
-
const
|
|
46300
|
-
const
|
|
46386
|
+
const editor = datetimeEditor(format, rawValue);
|
|
46387
|
+
const inputType = editor.type;
|
|
46388
|
+
const inputValue = editor.value;
|
|
46301
46389
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
46302
46390
|
type: inputType,
|
|
46303
46391
|
"aria-label": t("uiLibrary.controlPanel.value", "Control value"),
|
|
@@ -46317,6 +46405,7 @@ function ControlWidget({ slice, busy, setValueFn, layout }) {
|
|
|
46317
46405
|
}
|
|
46318
46406
|
}
|
|
46319
46407
|
function ControlPanel({ trpc, deviceId, features: _features, layout, optimistic }) {
|
|
46408
|
+
const info = useOptionalSystem()?.getDeviceInfo(deviceId) ?? null;
|
|
46320
46409
|
const { cap, slice, available } = useDeviceCapability({
|
|
46321
46410
|
trpc,
|
|
46322
46411
|
deviceId,
|
|
@@ -46326,6 +46415,8 @@ function ControlPanel({ trpc, deviceId, features: _features, layout, optimistic
|
|
|
46326
46415
|
const { display, applyOptimistic } = useOptimisticSlice(slice, { enabled: optimistic ?? true });
|
|
46327
46416
|
if (display === void 0) return null;
|
|
46328
46417
|
const busy = !available;
|
|
46418
|
+
const chosen = info?.sourceInfo?.quantity;
|
|
46419
|
+
const quantity = (0, _camstack_types.isReadingQuantity)(chosen) && chosen !== "device" && chosen !== "none" ? chosen : void 0;
|
|
46329
46420
|
const setValueFn = (input) => {
|
|
46330
46421
|
if (!busy && cap) {
|
|
46331
46422
|
applyOptimistic({ value: input.control.value });
|
|
@@ -46339,7 +46430,10 @@ function ControlPanel({ trpc, deviceId, features: _features, layout, optimistic
|
|
|
46339
46430
|
slice: display,
|
|
46340
46431
|
busy,
|
|
46341
46432
|
setValueFn,
|
|
46342
|
-
layout
|
|
46433
|
+
layout,
|
|
46434
|
+
quantity,
|
|
46435
|
+
display: info?.display,
|
|
46436
|
+
dateTimeShape: info?.sourceInfo?.dateTimeShape
|
|
46343
46437
|
})
|
|
46344
46438
|
});
|
|
46345
46439
|
}
|
|
@@ -46443,30 +46537,6 @@ function PopoverRowAction({ icon: Icon, title, children }) {
|
|
|
46443
46537
|
}), document.body)] });
|
|
46444
46538
|
}
|
|
46445
46539
|
//#endregion
|
|
46446
|
-
//#region src/lib/resolve-sensor-display.ts
|
|
46447
|
-
/** Table keys are canonical spellings; providers often send `C` / `F` / `celsius`. */
|
|
46448
|
-
function canonUnit(raw) {
|
|
46449
|
-
if (raw === void 0) return void 0;
|
|
46450
|
-
return (0, _camstack_types.normalizeUnit)(raw) ?? raw;
|
|
46451
|
-
}
|
|
46452
|
-
var identity = (raw) => raw;
|
|
46453
|
-
function resolveSensorDisplay(input) {
|
|
46454
|
-
const perCap = input.capName !== void 0 ? input.display?.perCap?.[input.capName] : void 0;
|
|
46455
|
-
const precision = perCap?.precision ?? input.display?.precision ?? input.roleDefault?.precision ?? input.slicePrecision;
|
|
46456
|
-
const sourceUnit = canonUnit(input.sliceUnit ?? input.canonicalUnit);
|
|
46457
|
-
const overrideUnit = canonUnit(perCap?.unit ?? input.display?.unit ?? input.roleDefault?.unit);
|
|
46458
|
-
if (overrideUnit !== void 0 && sourceUnit !== void 0 && overrideUnit !== sourceUnit && (0, _camstack_types.canConvertUnit)(sourceUnit, overrideUnit)) return {
|
|
46459
|
-
unit: overrideUnit,
|
|
46460
|
-
precision,
|
|
46461
|
-
toDisplayValue: (raw) => (0, _camstack_types.tryConvertUnit)(raw, sourceUnit, overrideUnit) ?? raw
|
|
46462
|
-
};
|
|
46463
|
-
return {
|
|
46464
|
-
unit: sourceUnit ?? "",
|
|
46465
|
-
precision,
|
|
46466
|
-
toDisplayValue: identity
|
|
46467
|
-
};
|
|
46468
|
-
}
|
|
46469
|
-
//#endregion
|
|
46470
46540
|
//#region src/composites/device-controls/sensor-value-atom.tsx
|
|
46471
46541
|
/**
|
|
46472
46542
|
* Binary fallback for the `battery-sensor` role.
|
|
@@ -46507,102 +46577,105 @@ function hasBatteryNumericValue(slice, field) {
|
|
|
46507
46577
|
* avoids dynamic string indexing — each one is a concrete property
|
|
46508
46578
|
* access on the typed `DeviceProxy['state']` shape.
|
|
46509
46579
|
*
|
|
46510
|
-
*
|
|
46511
|
-
*
|
|
46512
|
-
*
|
|
46513
|
-
* so no per-device lookup is needed.
|
|
46514
|
-
* - Generic `numeric-sensor` has no `canonicalUnit`. The unit is read live
|
|
46515
|
-
* from the slice (`slice.unit`) as populated by the provider from the
|
|
46516
|
-
* upstream source on every state push.
|
|
46580
|
+
* The unit comes from the descriptor's quantity through `resolveSensorDisplay`.
|
|
46581
|
+
* A generic numeric sensor uses quantity `device` and the device's
|
|
46582
|
+
* `sourceInfo.quantity`.
|
|
46517
46583
|
*/
|
|
46518
46584
|
var ROLE_DESCRIPTOR = {
|
|
46519
46585
|
"temperature-sensor": {
|
|
46520
46586
|
kind: "numeric",
|
|
46521
46587
|
field: "celsius",
|
|
46522
|
-
|
|
46588
|
+
quantity: "temperature",
|
|
46523
46589
|
getSliceHandle: (s) => s.temperatureSensor
|
|
46524
46590
|
},
|
|
46525
46591
|
"humidity-sensor": {
|
|
46526
46592
|
kind: "numeric",
|
|
46527
46593
|
field: "percent",
|
|
46528
|
-
|
|
46594
|
+
quantity: "humidity",
|
|
46529
46595
|
getSliceHandle: (s) => s.humiditySensor
|
|
46530
46596
|
},
|
|
46531
46597
|
"ambient-light-sensor": {
|
|
46532
46598
|
kind: "numeric",
|
|
46533
46599
|
field: "lux",
|
|
46534
|
-
|
|
46600
|
+
quantity: "illuminance",
|
|
46535
46601
|
getSliceHandle: (s) => s.ambientLightSensor
|
|
46536
46602
|
},
|
|
46537
46603
|
"pressure-sensor": {
|
|
46538
46604
|
kind: "numeric",
|
|
46539
46605
|
field: "hpa",
|
|
46540
|
-
|
|
46606
|
+
quantity: "pressure",
|
|
46541
46607
|
getSliceHandle: (s) => s.pressureSensor
|
|
46542
46608
|
},
|
|
46543
46609
|
"numeric-sensor": {
|
|
46544
46610
|
kind: "numeric",
|
|
46545
46611
|
field: "value",
|
|
46612
|
+
quantity: "device",
|
|
46546
46613
|
getSliceHandle: (s) => s.numericSensor
|
|
46547
46614
|
},
|
|
46548
46615
|
"wind-sensor": {
|
|
46549
46616
|
kind: "numeric",
|
|
46550
46617
|
field: "value",
|
|
46618
|
+
quantity: "device",
|
|
46551
46619
|
getSliceHandle: (s) => s.numericSensor
|
|
46552
46620
|
},
|
|
46553
46621
|
"rain-sensor": {
|
|
46554
46622
|
kind: "numeric",
|
|
46555
46623
|
field: "value",
|
|
46624
|
+
quantity: "device",
|
|
46556
46625
|
getSliceHandle: (s) => s.numericSensor
|
|
46557
46626
|
},
|
|
46558
46627
|
"uv-sensor": {
|
|
46559
46628
|
kind: "numeric",
|
|
46560
46629
|
field: "value",
|
|
46630
|
+
quantity: "device",
|
|
46561
46631
|
getSliceHandle: (s) => s.numericSensor
|
|
46562
46632
|
},
|
|
46563
46633
|
"solar-radiation-sensor": {
|
|
46564
46634
|
kind: "numeric",
|
|
46565
46635
|
field: "value",
|
|
46636
|
+
quantity: "device",
|
|
46566
46637
|
getSliceHandle: (s) => s.numericSensor
|
|
46567
46638
|
},
|
|
46568
46639
|
"soil-moisture-sensor": {
|
|
46569
46640
|
kind: "numeric",
|
|
46570
46641
|
field: "value",
|
|
46642
|
+
quantity: "device",
|
|
46571
46643
|
getSliceHandle: (s) => s.numericSensor
|
|
46572
46644
|
},
|
|
46573
46645
|
"power-sensor": {
|
|
46574
46646
|
kind: "numeric",
|
|
46575
46647
|
field: "watts",
|
|
46576
|
-
|
|
46648
|
+
quantity: "power",
|
|
46577
46649
|
getSliceHandle: (s) => s.powerMeter
|
|
46578
46650
|
},
|
|
46579
46651
|
"energy-sensor": {
|
|
46580
46652
|
kind: "numeric",
|
|
46581
46653
|
field: "kwhTotal",
|
|
46582
|
-
|
|
46654
|
+
quantity: "energy",
|
|
46583
46655
|
getSliceHandle: (s) => s.powerMeter
|
|
46584
46656
|
},
|
|
46585
46657
|
"voltage-sensor": {
|
|
46586
46658
|
kind: "numeric",
|
|
46587
46659
|
field: "volts",
|
|
46588
|
-
|
|
46660
|
+
quantity: "voltage",
|
|
46589
46661
|
getSliceHandle: (s) => s.powerMeter
|
|
46590
46662
|
},
|
|
46591
46663
|
"current-sensor": {
|
|
46592
46664
|
kind: "numeric",
|
|
46593
46665
|
field: "amps",
|
|
46594
|
-
|
|
46666
|
+
quantity: "current",
|
|
46595
46667
|
getSliceHandle: (s) => s.powerMeter
|
|
46596
46668
|
},
|
|
46597
46669
|
"battery-sensor": {
|
|
46598
46670
|
kind: "numeric",
|
|
46599
46671
|
field: "value",
|
|
46600
|
-
|
|
46672
|
+
quantity: "percentage",
|
|
46601
46673
|
getSliceHandle: (s) => s.numericSensor
|
|
46602
46674
|
},
|
|
46603
46675
|
"generic-sensor": {
|
|
46604
46676
|
kind: "numeric",
|
|
46605
46677
|
field: "value",
|
|
46678
|
+
quantity: "device",
|
|
46606
46679
|
getSliceHandle: (s) => s.numericSensor
|
|
46607
46680
|
},
|
|
46608
46681
|
"motion-sensor": {
|
|
@@ -46720,19 +46793,16 @@ function SensorValueAtom({ trpc, device }) {
|
|
|
46720
46793
|
children: enumSensorSlice.value
|
|
46721
46794
|
});
|
|
46722
46795
|
if (numericIsColStart) return null;
|
|
46723
|
-
const
|
|
46724
|
-
const
|
|
46725
|
-
const slicePrecision = typeof sliceRecord["precision"] === "number" ? sliceRecord["precision"] : void 0;
|
|
46726
|
-
const resolved = resolveSensorDisplay({
|
|
46796
|
+
const deviceQuantity = device.sourceInfo?.quantity;
|
|
46797
|
+
const rendered = resolveSensorDisplay({
|
|
46727
46798
|
display: device.display,
|
|
46728
46799
|
capName: device.role === "numeric-sensor" ? "numeric-sensor" : void 0,
|
|
46729
46800
|
roleDefault: device.role != null ? roleDefaultsData?.defaults[device.role] : void 0,
|
|
46730
|
-
|
|
46731
|
-
|
|
46732
|
-
|
|
46733
|
-
|
|
46734
|
-
const
|
|
46735
|
-
const formatted = formatNumeric(resolved.toDisplayValue(rawValue), resolved.precision);
|
|
46801
|
+
quantity: descriptor.quantity ?? "device",
|
|
46802
|
+
deviceQuantity: deviceQuantity === "device" ? void 0 : deviceQuantity
|
|
46803
|
+
}).format(rawValue);
|
|
46804
|
+
const unit = rendered.unit;
|
|
46805
|
+
const formatted = rendered.text;
|
|
46736
46806
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
46737
46807
|
className: cn("inline-flex items-baseline gap-0.5 tabular-nums text-xs text-foreground"),
|
|
46738
46808
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatted }), unit && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -46762,7 +46832,7 @@ function SensorValueAtom({ trpc, device }) {
|
|
|
46762
46832
|
if (descriptor.kind === "datetime") {
|
|
46763
46833
|
if (!isStringSliceValue(slice)) return null;
|
|
46764
46834
|
if (slice.value === "") return null;
|
|
46765
|
-
const format = readSliceDateTimeFormat(slice) ?? descriptor.format;
|
|
46835
|
+
const format = device.sourceInfo?.dateTimeShape ?? readSliceDateTimeFormat(slice) ?? descriptor.format;
|
|
46766
46836
|
const formatted = formatControlDateTime(slice.value, format);
|
|
46767
46837
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
46768
46838
|
className: cn("inline-flex items-baseline text-xs text-foreground"),
|
|
@@ -47317,17 +47387,14 @@ function SensorInlineControl({ trpc, deviceId }) {
|
|
|
47317
47387
|
*
|
|
47318
47388
|
* Renders:
|
|
47319
47389
|
* - Typed numeric sensors: value in `text-3xl font-semibold tabular-nums` +
|
|
47320
|
-
*
|
|
47321
|
-
* - Generic numeric-sensor:
|
|
47322
|
-
* (`slice.unit`, populated by the HA provider from state attributes).
|
|
47390
|
+
* the unit `resolveSensorDisplay` derives from the quantity.
|
|
47391
|
+
* - Generic numeric-sensor: the same formatter, with the device's quantity.
|
|
47323
47392
|
* - Binary sensors: coloured state badge ("Detected"/"Clear" etc.) — same
|
|
47324
47393
|
* labels and colours as SensorValueAtom's binary branch.
|
|
47325
47394
|
* - Presence: Home/Away/zone-name badge — same labels as SensorValueAtom.
|
|
47326
47395
|
* - All: sensor name label below the value.
|
|
47327
47396
|
*
|
|
47328
|
-
* Unit resolution uses the same
|
|
47329
|
-
* wins, ROLE_DESCRIPTOR canonicalUnit as fallback) — the two surfaces stay
|
|
47330
|
-
* in sync.
|
|
47397
|
+
* Unit resolution uses the same formatter as SensorValueAtom.
|
|
47331
47398
|
*/
|
|
47332
47399
|
function isNumericSliceValue(v) {
|
|
47333
47400
|
return typeof v === "object" && v !== null;
|
|
@@ -47394,19 +47461,16 @@ function SensorHeroInner({ trpc, device }) {
|
|
|
47394
47461
|
if (!isNumericSliceValue(slice)) return null;
|
|
47395
47462
|
const rawValue = slice[descriptor.field];
|
|
47396
47463
|
if (typeof rawValue !== "number") return null;
|
|
47397
|
-
const
|
|
47398
|
-
const
|
|
47399
|
-
const slicePrecision = typeof sliceRecord["precision"] === "number" ? sliceRecord["precision"] : void 0;
|
|
47400
|
-
const resolved = resolveSensorDisplay({
|
|
47464
|
+
const deviceQuantity = device.sourceInfo?.quantity;
|
|
47465
|
+
const rendered = resolveSensorDisplay({
|
|
47401
47466
|
display: device.display,
|
|
47402
47467
|
capName: device.role === "numeric-sensor" ? "numeric-sensor" : void 0,
|
|
47403
47468
|
roleDefault: device.role != null ? roleDefaultsData?.defaults[device.role] : roleDefaultsData?.defaults["temperature-sensor"],
|
|
47404
|
-
|
|
47405
|
-
|
|
47406
|
-
|
|
47407
|
-
|
|
47408
|
-
const
|
|
47409
|
-
const formatted = formatNumeric(resolved.toDisplayValue(rawValue), resolved.precision);
|
|
47469
|
+
quantity: descriptor.quantity ?? "device",
|
|
47470
|
+
deviceQuantity: deviceQuantity === "device" ? void 0 : deviceQuantity
|
|
47471
|
+
}).format(rawValue);
|
|
47472
|
+
const unit = rendered.unit;
|
|
47473
|
+
const formatted = rendered.text;
|
|
47410
47474
|
const occupancyOn = occupancySlice !== void 0 && isBinarySliceValue(occupancySlice) && typeof occupancySlice["on"] === "boolean" ? Boolean(occupancySlice["on"]) : null;
|
|
47411
47475
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47412
47476
|
className: "flex flex-col items-center justify-center gap-2 rounded-lg border border-border bg-surface p-4",
|
|
@@ -47462,7 +47526,7 @@ function SensorHeroInner({ trpc, device }) {
|
|
|
47462
47526
|
children: t("uiLibrary.sensorHeroCard.awaiting", "Awaiting state…")
|
|
47463
47527
|
})]
|
|
47464
47528
|
});
|
|
47465
|
-
const format = readSliceDateTimeFormat(slice) ?? descriptor.format;
|
|
47529
|
+
const format = device.sourceInfo?.dateTimeShape ?? readSliceDateTimeFormat(slice) ?? descriptor.format;
|
|
47466
47530
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
47467
47531
|
className: "flex flex-col items-center justify-center gap-2 rounded-lg border border-border bg-surface p-4",
|
|
47468
47532
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -49918,10 +49982,15 @@ function useWeather(trpc, deviceId) {
|
|
|
49918
49982
|
slice: (d) => d.state.weather
|
|
49919
49983
|
});
|
|
49920
49984
|
}
|
|
49921
|
-
/** Format a
|
|
49922
|
-
function
|
|
49923
|
-
if (
|
|
49924
|
-
|
|
49985
|
+
/** Format a base-unit weather reading (metric). */
|
|
49986
|
+
function formatWeather(value, quantity) {
|
|
49987
|
+
if (value === null) return "—";
|
|
49988
|
+
const rendered = (0, _camstack_types.formatReading)({
|
|
49989
|
+
value,
|
|
49990
|
+
quantity,
|
|
49991
|
+
precision: 0
|
|
49992
|
+
});
|
|
49993
|
+
return rendered.unit.length === 0 ? rendered.text : `${rendered.text}${rendered.unit}`;
|
|
49925
49994
|
}
|
|
49926
49995
|
/**
|
|
49927
49996
|
* WeatherInlineControl — compact condition icon + temperature readout
|
|
@@ -49941,7 +50010,7 @@ function WeatherInlineControl({ trpc, deviceId }) {
|
|
|
49941
50010
|
children: inlineGlyph(meta.glyph)
|
|
49942
50011
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
49943
50012
|
className: "tabular-nums",
|
|
49944
|
-
children:
|
|
50013
|
+
children: formatWeather(slice.temperature, "temperature")
|
|
49945
50014
|
})]
|
|
49946
50015
|
});
|
|
49947
50016
|
}
|
|
@@ -50251,7 +50320,11 @@ function WeatherHeroCard({ trpc, deviceId }) {
|
|
|
50251
50320
|
if (slice === void 0) return null;
|
|
50252
50321
|
const meta = weatherConditionMeta(slice.condition, t);
|
|
50253
50322
|
const tint = weatherTint(slice.condition);
|
|
50254
|
-
const
|
|
50323
|
+
const temperature = slice.temperature === null ? void 0 : (0, _camstack_types.formatReading)({
|
|
50324
|
+
value: slice.temperature,
|
|
50325
|
+
quantity: "temperature",
|
|
50326
|
+
precision: 0
|
|
50327
|
+
});
|
|
50255
50328
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
50256
50329
|
className: "flex flex-col items-stretch gap-4 rounded-lg border border-border bg-surface p-4",
|
|
50257
50330
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -50266,11 +50339,14 @@ function WeatherHeroCard({ trpc, deviceId }) {
|
|
|
50266
50339
|
children: [
|
|
50267
50340
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
50268
50341
|
className: "text-3xl font-semibold tabular-nums text-foreground",
|
|
50269
|
-
children:
|
|
50342
|
+
children: temperature !== void 0 ? temperature.text : "—"
|
|
50270
50343
|
}),
|
|
50271
50344
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
50272
50345
|
className: "text-base text-foreground-subtle",
|
|
50273
|
-
children: unit
|
|
50346
|
+
children: temperature !== void 0 ? temperature.unit : (0, _camstack_types.formatReading)({
|
|
50347
|
+
value: 0,
|
|
50348
|
+
quantity: "temperature"
|
|
50349
|
+
}).unit
|
|
50274
50350
|
}),
|
|
50275
50351
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
50276
50352
|
className: "text-xs text-foreground-subtle ml-2 capitalize",
|
|
@@ -50291,12 +50367,12 @@ function WeatherHeroCard({ trpc, deviceId }) {
|
|
|
50291
50367
|
style: { transform: `rotate(${slice.windBearing}deg)` }
|
|
50292
50368
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Wind, { className: "h-3.5 w-3.5" }),
|
|
50293
50369
|
label: t("uiLibrary.weatherControl.wind", "Wind"),
|
|
50294
|
-
value:
|
|
50370
|
+
value: formatWeather(slice.windSpeed, "speed")
|
|
50295
50371
|
}),
|
|
50296
50372
|
slice.pressure !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Readout$1, {
|
|
50297
50373
|
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Gauge, { className: "h-3.5 w-3.5" }),
|
|
50298
50374
|
label: t("uiLibrary.weatherControl.pressure", "Pressure"),
|
|
50299
|
-
value:
|
|
50375
|
+
value: formatWeather(slice.pressure, "pressure")
|
|
50300
50376
|
})
|
|
50301
50377
|
]
|
|
50302
50378
|
})]
|
|
@@ -50535,6 +50611,16 @@ var VOLUME_MAX = 9;
|
|
|
50535
50611
|
/** Amber kibble accent for the bowl fill; destructive red when low. */
|
|
50536
50612
|
var KIBBLE = "#f59e0b";
|
|
50537
50613
|
var KIBBLE_LOW = "#ef4444";
|
|
50614
|
+
function desiccantLabel(t, seconds) {
|
|
50615
|
+
const rendered = (0, _camstack_types.formatReading)({
|
|
50616
|
+
value: seconds,
|
|
50617
|
+
quantity: "duration"
|
|
50618
|
+
});
|
|
50619
|
+
return t("uiLibrary.petFeeder.desiccant", "{{text}}{{unit}} desiccant", {
|
|
50620
|
+
text: rendered.text,
|
|
50621
|
+
unit: rendered.unit
|
|
50622
|
+
});
|
|
50623
|
+
}
|
|
50538
50624
|
function clampPortion(grams) {
|
|
50539
50625
|
return Math.min(_camstack_types.PET_FEEDER_MANUAL_FEED_MAX, Math.max(_camstack_types.PET_FEEDER_MANUAL_FEED_MIN, grams));
|
|
50540
50626
|
}
|
|
@@ -50657,7 +50743,7 @@ function PetFeederPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
50657
50743
|
}),
|
|
50658
50744
|
display.desiccantLeftDays !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Readout, {
|
|
50659
50745
|
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Wind, { className: "h-3.5 w-3.5" }),
|
|
50660
|
-
label: t
|
|
50746
|
+
label: desiccantLabel(t, display.desiccantLeftDays)
|
|
50661
50747
|
}),
|
|
50662
50748
|
onBatteries && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
50663
50749
|
className: "inline-flex items-center gap-1 font-medium text-amber-500",
|
|
@@ -68652,6 +68738,9 @@ exports.AudioWaveform = AudioWaveform;
|
|
|
68652
68738
|
exports.AutomationHeroCard = AutomationHeroCard;
|
|
68653
68739
|
exports.AutomationInlineControl = AutomationInlineControl;
|
|
68654
68740
|
exports.AutotrackSection = AutotrackSection;
|
|
68741
|
+
exports.BATTERY_BADGE_LOW_KEY = BATTERY_BADGE_LOW_KEY;
|
|
68742
|
+
exports.BATTERY_BADGE_NORMAL_KEY = BATTERY_BADGE_NORMAL_KEY;
|
|
68743
|
+
exports.BINARY_BATTERY_LOW_BELOW = BINARY_BATTERY_LOW_BELOW;
|
|
68655
68744
|
exports.BOOLEAN_STATES = BOOLEAN_STATES;
|
|
68656
68745
|
exports.BTN_COMPACT = BTN_COMPACT;
|
|
68657
68746
|
exports.BTN_COMPACT_DANGER = BTN_COMPACT_DANGER;
|
|
@@ -69259,6 +69348,7 @@ exports.assignNodeColorId = _camstack_types.assignNodeColorId;
|
|
|
69259
69348
|
exports.axisTicks = axisTicks;
|
|
69260
69349
|
exports.badgeFor = badgeFor;
|
|
69261
69350
|
exports.badgeFor$1 = badgeFor;
|
|
69351
|
+
exports.binaryBatteryIsLow = binaryBatteryIsLow;
|
|
69262
69352
|
exports.bindsToModel = bindsToModel;
|
|
69263
69353
|
exports.booleanLabels = booleanLabels;
|
|
69264
69354
|
exports.bucketTotals = bucketTotals;
|