@camstack/ui-library 1.2.299 → 1.2.300
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/{MotionZonesSettings-Bv8XzCa3.js → MotionZonesSettings-C3Tj12yc.js} +1 -1
- package/dist/{PrivacyMaskSettings-BlkbESYj.js → PrivacyMaskSettings-C9wfHkiB.js} +1 -1
- package/dist/{SceneMonitorEditor-i3-nXAEU.js → SceneMonitorEditor-30H29vN4.js} +1 -1
- package/dist/composites/config-form-field.d.ts +3 -1
- package/dist/composites/device-controls/ac-panel.d.ts +14 -1
- package/dist/composites/device-controls/climate-panel.d.ts +4 -1
- package/dist/composites/device-controls/dishwasher-control.d.ts +17 -0
- package/dist/composites/device-controls/heating-valve-readout.d.ts +4 -0
- package/dist/composites/device-controls/types.d.ts +1 -1
- package/dist/generated/system-hooks.d.ts +20 -0
- package/dist/{icons-CLC60C-_.cjs → icons-B1AFXUaZ.cjs} +2 -78
- package/dist/{icons-BXXScOuy.js → icons-BT8c2hxp.js} +1 -77
- package/dist/index.cjs +860 -167
- package/dist/index.js +854 -173
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2185,6 +2185,54 @@ var Hash = require_createLucideIcon.createLucideIcon("hash", [
|
|
|
2185
2185
|
* This source code is licensed under the ISC license.
|
|
2186
2186
|
* See the LICENSE file in the root directory of this source tree.
|
|
2187
2187
|
*/
|
|
2188
|
+
var Heater = require_createLucideIcon.createLucideIcon("heater", [
|
|
2189
|
+
["path", {
|
|
2190
|
+
d: "M11 8c2-3-2-3 0-6",
|
|
2191
|
+
key: "1ldv5m"
|
|
2192
|
+
}],
|
|
2193
|
+
["path", {
|
|
2194
|
+
d: "M15.5 8c2-3-2-3 0-6",
|
|
2195
|
+
key: "1otqoz"
|
|
2196
|
+
}],
|
|
2197
|
+
["path", {
|
|
2198
|
+
d: "M6 10h.01",
|
|
2199
|
+
key: "1lbq93"
|
|
2200
|
+
}],
|
|
2201
|
+
["path", {
|
|
2202
|
+
d: "M6 14h.01",
|
|
2203
|
+
key: "zudwn7"
|
|
2204
|
+
}],
|
|
2205
|
+
["path", {
|
|
2206
|
+
d: "M10 16v-4",
|
|
2207
|
+
key: "1c25yv"
|
|
2208
|
+
}],
|
|
2209
|
+
["path", {
|
|
2210
|
+
d: "M14 16v-4",
|
|
2211
|
+
key: "1dkbt8"
|
|
2212
|
+
}],
|
|
2213
|
+
["path", {
|
|
2214
|
+
d: "M18 16v-4",
|
|
2215
|
+
key: "1yg9me"
|
|
2216
|
+
}],
|
|
2217
|
+
["path", {
|
|
2218
|
+
d: "M20 6a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h3",
|
|
2219
|
+
key: "1ubg90"
|
|
2220
|
+
}],
|
|
2221
|
+
["path", {
|
|
2222
|
+
d: "M5 20v2",
|
|
2223
|
+
key: "1abpe8"
|
|
2224
|
+
}],
|
|
2225
|
+
["path", {
|
|
2226
|
+
d: "M19 20v2",
|
|
2227
|
+
key: "kqn6ft"
|
|
2228
|
+
}]
|
|
2229
|
+
]);
|
|
2230
|
+
/**
|
|
2231
|
+
* @license lucide-react v0.576.0 - ISC
|
|
2232
|
+
*
|
|
2233
|
+
* This source code is licensed under the ISC license.
|
|
2234
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
2235
|
+
*/
|
|
2188
2236
|
var House = require_createLucideIcon.createLucideIcon("house", [["path", {
|
|
2189
2237
|
d: "M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8",
|
|
2190
2238
|
key: "5wwlr5"
|
|
@@ -4173,6 +4221,34 @@ var Thermometer = require_createLucideIcon.createLucideIcon("thermometer", [["pa
|
|
|
4173
4221
|
* This source code is licensed under the ISC license.
|
|
4174
4222
|
* See the LICENSE file in the root directory of this source tree.
|
|
4175
4223
|
*/
|
|
4224
|
+
var Timer = require_createLucideIcon.createLucideIcon("timer", [
|
|
4225
|
+
["line", {
|
|
4226
|
+
x1: "10",
|
|
4227
|
+
x2: "14",
|
|
4228
|
+
y1: "2",
|
|
4229
|
+
y2: "2",
|
|
4230
|
+
key: "14vaq8"
|
|
4231
|
+
}],
|
|
4232
|
+
["line", {
|
|
4233
|
+
x1: "12",
|
|
4234
|
+
x2: "15",
|
|
4235
|
+
y1: "14",
|
|
4236
|
+
y2: "11",
|
|
4237
|
+
key: "17fdiu"
|
|
4238
|
+
}],
|
|
4239
|
+
["circle", {
|
|
4240
|
+
cx: "12",
|
|
4241
|
+
cy: "14",
|
|
4242
|
+
r: "8",
|
|
4243
|
+
key: "1e1u0o"
|
|
4244
|
+
}]
|
|
4245
|
+
]);
|
|
4246
|
+
/**
|
|
4247
|
+
* @license lucide-react v0.576.0 - ISC
|
|
4248
|
+
*
|
|
4249
|
+
* This source code is licensed under the ISC license.
|
|
4250
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
4251
|
+
*/
|
|
4176
4252
|
var ToggleLeft = require_createLucideIcon.createLucideIcon("toggle-left", [["circle", {
|
|
4177
4253
|
cx: "9",
|
|
4178
4254
|
cy: "12",
|
|
@@ -5275,6 +5351,11 @@ var DEVICE_TYPE_META = {
|
|
|
5275
5351
|
icon: Bone,
|
|
5276
5352
|
labelKey: "uiLibrary.deviceMeta.type.petFeeder",
|
|
5277
5353
|
labelFallback: "Pet feeder"
|
|
5354
|
+
},
|
|
5355
|
+
[_camstack_types.DeviceType.Dishwasher]: {
|
|
5356
|
+
icon: Waves,
|
|
5357
|
+
labelKey: "uiLibrary.deviceMeta.type.dishwasher",
|
|
5358
|
+
labelFallback: "Dishwasher"
|
|
5278
5359
|
}
|
|
5279
5360
|
};
|
|
5280
5361
|
var DEVICE_ROLE_META = {
|
|
@@ -5756,7 +5837,7 @@ function lucideExportName(key) {
|
|
|
5756
5837
|
function loadIconGlyphs() {
|
|
5757
5838
|
if (fullTable !== void 0) return Promise.resolve();
|
|
5758
5839
|
if (failure !== void 0) return Promise.reject(failure);
|
|
5759
|
-
pending ??= Promise.resolve().then(() => require("./icons-
|
|
5840
|
+
pending ??= Promise.resolve().then(() => require("./icons-B1AFXUaZ.cjs")).then((module) => {
|
|
5760
5841
|
fullTable = module;
|
|
5761
5842
|
publish();
|
|
5762
5843
|
}, (error) => {
|
|
@@ -10931,6 +11012,24 @@ var useDeviceStateListClaims = trpc.deviceState.listClaims.useQuery;
|
|
|
10931
11012
|
var useDeviceStateGetNativeCapSlice = trpc.deviceState.getNativeCapSlice.useQuery;
|
|
10932
11013
|
/** Generated alias around `trpc.deviceStatus.getStatus.useQuery`. */
|
|
10933
11014
|
var useDeviceStatusGetStatus = trpc.deviceStatus.getStatus.useQuery;
|
|
11015
|
+
/** Generated alias around `trpc.dishwasher.start.useMutation`. */
|
|
11016
|
+
var useDishwasherStart = trpc.dishwasher.start.useMutation;
|
|
11017
|
+
/** Generated alias around `trpc.dishwasher.pause.useMutation`. */
|
|
11018
|
+
var useDishwasherPause = trpc.dishwasher.pause.useMutation;
|
|
11019
|
+
/** Generated alias around `trpc.dishwasher.stop.useMutation`. */
|
|
11020
|
+
var useDishwasherStop = trpc.dishwasher.stop.useMutation;
|
|
11021
|
+
/** Generated alias around `trpc.dishwasher.setProgram.useMutation`. */
|
|
11022
|
+
var useDishwasherSetProgram = trpc.dishwasher.setProgram.useMutation;
|
|
11023
|
+
/** Generated alias around `trpc.dishwasher.setPower.useMutation`. */
|
|
11024
|
+
var useDishwasherSetPower = trpc.dishwasher.setPower.useMutation;
|
|
11025
|
+
/** Generated alias around `trpc.dishwasher.setHalfLoad.useMutation`. */
|
|
11026
|
+
var useDishwasherSetHalfLoad = trpc.dishwasher.setHalfLoad.useMutation;
|
|
11027
|
+
/** Generated alias around `trpc.dishwasher.setHygienePlus.useMutation`. */
|
|
11028
|
+
var useDishwasherSetHygienePlus = trpc.dishwasher.setHygienePlus.useMutation;
|
|
11029
|
+
/** Generated alias around `trpc.dishwasher.setOption.useMutation`. */
|
|
11030
|
+
var useDishwasherSetOption = trpc.dishwasher.setOption.useMutation;
|
|
11031
|
+
/** Generated alias around `trpc.dishwasher.getStatus.useQuery`. */
|
|
11032
|
+
var useDishwasherGetStatus = trpc.dishwasher.getStatus.useQuery;
|
|
10934
11033
|
/** Generated alias around `trpc.doorbell.getStatus.useQuery`. */
|
|
10935
11034
|
var useDoorbellGetStatus = trpc.doorbell.getStatus.useQuery;
|
|
10936
11035
|
/** Generated alias around `trpc.enumSensor.getStatus.useQuery`. */
|
|
@@ -10985,6 +11084,8 @@ var useFeatureProbeGetStatus = trpc.featureProbe.getStatus.useQuery;
|
|
|
10985
11084
|
var useFloodGetStatus = trpc.flood.getStatus.useQuery;
|
|
10986
11085
|
/** Generated alias around `trpc.gas.getStatus.useQuery`. */
|
|
10987
11086
|
var useGasGetStatus = trpc.gas.getStatus.useQuery;
|
|
11087
|
+
/** Generated alias around `trpc.heatingValve.getStatus.useQuery`. */
|
|
11088
|
+
var useHeatingValveGetStatus = trpc.heatingValve.getStatus.useQuery;
|
|
10988
11089
|
/** Generated alias around `trpc.humidifier.setOn.useMutation`. */
|
|
10989
11090
|
var useHumidifierSetOn = trpc.humidifier.setOn.useMutation;
|
|
10990
11091
|
/** Generated alias around `trpc.humidifier.setTargetHumidity.useMutation`. */
|
|
@@ -28815,7 +28916,7 @@ function AddonActionSelectField({ field, value, values, onChange, disabled, onAc
|
|
|
28815
28916
|
})()
|
|
28816
28917
|
});
|
|
28817
28918
|
}
|
|
28818
|
-
function AddonActionButtonField({ field, values, disabled, onAction }) {
|
|
28919
|
+
function AddonActionButtonField({ field, values, disabled, onAction, onChange, onChangeMany }) {
|
|
28819
28920
|
const t = useUiText();
|
|
28820
28921
|
const [loading, setLoading] = (0, react$1.useState)(false);
|
|
28821
28922
|
const [status, setStatus] = (0, react$1.useState)(null);
|
|
@@ -28847,6 +28948,17 @@ function AddonActionButtonField({ field, values, disabled, onAction }) {
|
|
|
28847
28948
|
try {
|
|
28848
28949
|
const result = await onAction(field.action, field.key, params);
|
|
28849
28950
|
const resultRecord = typeof result === "object" && result !== null ? result : null;
|
|
28951
|
+
if (resultRecord !== null && field.assignResult && (onChange || onChangeMany)) {
|
|
28952
|
+
const patch = {};
|
|
28953
|
+
for (const [formKey, resultKey] of Object.entries(field.assignResult)) {
|
|
28954
|
+
const assigned = resultRecord[resultKey];
|
|
28955
|
+
if (typeof assigned === "string") patch[formKey] = assigned;
|
|
28956
|
+
}
|
|
28957
|
+
if (Object.keys(patch).length > 0) {
|
|
28958
|
+
if (onChangeMany) onChangeMany(patch);
|
|
28959
|
+
else if (onChange) for (const [formKey, assigned] of Object.entries(patch)) onChange(formKey, assigned);
|
|
28960
|
+
}
|
|
28961
|
+
}
|
|
28850
28962
|
setStatus({
|
|
28851
28963
|
kind: "ok",
|
|
28852
28964
|
text: field.successMessage ? field.successMessage.replace(/\{(\w+)\}/g, (_, k) => {
|
|
@@ -29195,7 +29307,7 @@ function isFieldVisible(field, values, allFields) {
|
|
|
29195
29307
|
if (condition.notIncludes !== void 0) return !Array.isArray(condVal) || !condVal.includes(condition.notIncludes);
|
|
29196
29308
|
return true;
|
|
29197
29309
|
}
|
|
29198
|
-
function FormField({ field, values, onChange, disabled, translationFn, onTestField, externalProbe, allFields, onAction }) {
|
|
29310
|
+
function FormField({ field, values, onChange, onChangeMany, disabled, translationFn, onTestField, externalProbe, allFields, onAction }) {
|
|
29199
29311
|
if (hasMultiple(field)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MultipleFieldWrapper, {
|
|
29200
29312
|
field,
|
|
29201
29313
|
values,
|
|
@@ -29431,7 +29543,9 @@ function FormField({ field, values, onChange, disabled, translationFn, onTestFie
|
|
|
29431
29543
|
field,
|
|
29432
29544
|
values,
|
|
29433
29545
|
disabled,
|
|
29434
|
-
onAction
|
|
29546
|
+
onAction,
|
|
29547
|
+
onChange,
|
|
29548
|
+
onChangeMany
|
|
29435
29549
|
});
|
|
29436
29550
|
case "device-action-button": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceActionButtonField, {
|
|
29437
29551
|
field,
|
|
@@ -42914,6 +43028,19 @@ function FanPanel({ trpc, deviceId, features, layout, optimistic }) {
|
|
|
42914
43028
|
}
|
|
42915
43029
|
//#endregion
|
|
42916
43030
|
//#region src/composites/device-controls/climate-panel.tsx
|
|
43031
|
+
/** Step size for target ± buttons; defaults to 1 °C when unset or invalid. */
|
|
43032
|
+
function resolveClimateTempStep(tempStep) {
|
|
43033
|
+
if (typeof tempStep === "number" && Number.isFinite(tempStep) && tempStep > 0) return tempStep;
|
|
43034
|
+
return 1;
|
|
43035
|
+
}
|
|
43036
|
+
function nextClimateTarget(current, deltaSteps, display) {
|
|
43037
|
+
let next = current + deltaSteps * resolveClimateTempStep(display.tempStep);
|
|
43038
|
+
const min = display.minTemp;
|
|
43039
|
+
const max = display.maxTemp;
|
|
43040
|
+
if (typeof min === "number" && Number.isFinite(min)) next = Math.max(next, min);
|
|
43041
|
+
if (typeof max === "number" && Number.isFinite(max)) next = Math.min(next, max);
|
|
43042
|
+
return next;
|
|
43043
|
+
}
|
|
42917
43044
|
/**
|
|
42918
43045
|
* Exhaustive colour map — one entry per HvacMode. Build will fail if a
|
|
42919
43046
|
* mode is added to the enum and not reflected here (Record<HvacMode, ...>
|
|
@@ -42994,7 +43121,7 @@ function ClimatePanel({ trpc, deviceId, features: _features, layout, optimistic
|
|
|
42994
43121
|
onClick: (e) => {
|
|
42995
43122
|
e.stopPropagation();
|
|
42996
43123
|
if (!busy && display.target !== null) {
|
|
42997
|
-
const next = display.target -
|
|
43124
|
+
const next = nextClimateTarget(display.target, -1, display);
|
|
42998
43125
|
applyOptimistic({ target: next });
|
|
42999
43126
|
cap.setTarget({ target: next });
|
|
43000
43127
|
}
|
|
@@ -43008,7 +43135,7 @@ function ClimatePanel({ trpc, deviceId, features: _features, layout, optimistic
|
|
|
43008
43135
|
onClick: (e) => {
|
|
43009
43136
|
e.stopPropagation();
|
|
43010
43137
|
if (!busy && display.target !== null) {
|
|
43011
|
-
const next = display.target
|
|
43138
|
+
const next = nextClimateTarget(display.target, 1, display);
|
|
43012
43139
|
applyOptimistic({ target: next });
|
|
43013
43140
|
cap.setTarget({ target: next });
|
|
43014
43141
|
}
|
|
@@ -43063,7 +43190,7 @@ function ClimatePanel({ trpc, deviceId, features: _features, layout, optimistic
|
|
|
43063
43190
|
onClick: (e) => {
|
|
43064
43191
|
e.stopPropagation();
|
|
43065
43192
|
if (!busy && display.target !== null) {
|
|
43066
|
-
const next = display.target -
|
|
43193
|
+
const next = nextClimateTarget(display.target, -1, display);
|
|
43067
43194
|
applyOptimistic({ target: next });
|
|
43068
43195
|
cap.setTarget({ target: next });
|
|
43069
43196
|
}
|
|
@@ -43078,7 +43205,7 @@ function ClimatePanel({ trpc, deviceId, features: _features, layout, optimistic
|
|
|
43078
43205
|
onClick: (e) => {
|
|
43079
43206
|
e.stopPropagation();
|
|
43080
43207
|
if (!busy && display.target !== null) {
|
|
43081
|
-
const next = display.target
|
|
43208
|
+
const next = nextClimateTarget(display.target, 1, display);
|
|
43082
43209
|
applyOptimistic({ target: next });
|
|
43083
43210
|
cap.setTarget({ target: next });
|
|
43084
43211
|
}
|
|
@@ -43736,7 +43863,8 @@ var CONTROL_CAP_NAMES = [
|
|
|
43736
43863
|
"climate-control",
|
|
43737
43864
|
"media-player",
|
|
43738
43865
|
"control",
|
|
43739
|
-
"pet-feeder"
|
|
43866
|
+
"pet-feeder",
|
|
43867
|
+
"dishwasher"
|
|
43740
43868
|
];
|
|
43741
43869
|
//#endregion
|
|
43742
43870
|
//#region src/composites/device-controls/popover-row-action.tsx
|
|
@@ -48547,7 +48675,405 @@ function PetFeederHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
48547
48675
|
});
|
|
48548
48676
|
}
|
|
48549
48677
|
//#endregion
|
|
48678
|
+
//#region src/composites/device-controls/dishwasher-control.tsx
|
|
48679
|
+
/**
|
|
48680
|
+
* Dishwasher controls for a `DeviceType.Dishwasher` device.
|
|
48681
|
+
*
|
|
48682
|
+
* Reads the `dishwasher` cap status slice and renders inline + hero surfaces
|
|
48683
|
+
* mirroring vacuum-control / pet-feeder patterns.
|
|
48684
|
+
*/
|
|
48685
|
+
function dishwasherStateMeta(state, t = defaultUiText) {
|
|
48686
|
+
switch (state) {
|
|
48687
|
+
case "running": return {
|
|
48688
|
+
label: t("uiLibrary.dishwasher.running", "Running"),
|
|
48689
|
+
tone: "text-primary"
|
|
48690
|
+
};
|
|
48691
|
+
case "paused": return {
|
|
48692
|
+
label: t("uiLibrary.dishwasher.paused", "Paused"),
|
|
48693
|
+
tone: "text-warning"
|
|
48694
|
+
};
|
|
48695
|
+
case "finished": return {
|
|
48696
|
+
label: t("uiLibrary.dishwasher.finished", "Finished"),
|
|
48697
|
+
tone: "text-success"
|
|
48698
|
+
};
|
|
48699
|
+
case "error": return {
|
|
48700
|
+
label: t("uiLibrary.dishwasher.error", "Error"),
|
|
48701
|
+
tone: "text-destructive"
|
|
48702
|
+
};
|
|
48703
|
+
case "unavailable": return {
|
|
48704
|
+
label: t("uiLibrary.dishwasher.unavailable", "Unavailable"),
|
|
48705
|
+
tone: "text-foreground-subtle"
|
|
48706
|
+
};
|
|
48707
|
+
case "idle": return {
|
|
48708
|
+
label: t("uiLibrary.dishwasher.idle", "Idle"),
|
|
48709
|
+
tone: "text-foreground-subtle"
|
|
48710
|
+
};
|
|
48711
|
+
case void 0: return {
|
|
48712
|
+
label: t("uiLibrary.dishwasher.unknown", "Unknown"),
|
|
48713
|
+
tone: "text-foreground-subtle"
|
|
48714
|
+
};
|
|
48715
|
+
}
|
|
48716
|
+
}
|
|
48717
|
+
function consumableTone(level) {
|
|
48718
|
+
switch (level) {
|
|
48719
|
+
case "ok": return "text-success";
|
|
48720
|
+
case "low": return "text-warning";
|
|
48721
|
+
case "empty": return "text-destructive";
|
|
48722
|
+
case null: return "text-foreground-subtle";
|
|
48723
|
+
}
|
|
48724
|
+
}
|
|
48725
|
+
function useDishwasher(trpc, deviceId) {
|
|
48726
|
+
return useDeviceCapability({
|
|
48727
|
+
trpc,
|
|
48728
|
+
deviceId,
|
|
48729
|
+
cap: (d) => d.dishwasher,
|
|
48730
|
+
slice: (d) => d.state.dishwasher
|
|
48731
|
+
});
|
|
48732
|
+
}
|
|
48733
|
+
function DishwasherIconButton({ label, busy, active, onClick, children }) {
|
|
48734
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
48735
|
+
type: "button",
|
|
48736
|
+
"aria-label": label,
|
|
48737
|
+
title: label,
|
|
48738
|
+
disabled: busy,
|
|
48739
|
+
onClick: (e) => {
|
|
48740
|
+
e.stopPropagation();
|
|
48741
|
+
onClick();
|
|
48742
|
+
},
|
|
48743
|
+
className: cn("inline-flex h-5 w-5 items-center justify-center rounded text-foreground-subtle hover:text-foreground hover:bg-surface-hover transition-colors", active && "bg-primary text-primary-foreground", busy && "opacity-40 cursor-not-allowed"),
|
|
48744
|
+
children
|
|
48745
|
+
});
|
|
48746
|
+
}
|
|
48747
|
+
function ConsumableBadge({ label, level }) {
|
|
48748
|
+
if (level === null) return null;
|
|
48749
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
48750
|
+
className: cn("text-xs font-medium capitalize", consumableTone(level)),
|
|
48751
|
+
children: [
|
|
48752
|
+
label,
|
|
48753
|
+
": ",
|
|
48754
|
+
level
|
|
48755
|
+
]
|
|
48756
|
+
});
|
|
48757
|
+
}
|
|
48758
|
+
function DishwasherGraphic({ state, progressPercent }) {
|
|
48759
|
+
const running = state === "running";
|
|
48760
|
+
const pct = progressPercent != null ? Math.max(0, Math.min(100, progressPercent)) : running ? 40 : 0;
|
|
48761
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48762
|
+
"aria-hidden": "true",
|
|
48763
|
+
className: "relative w-28 h-36 rounded border-2 border-border bg-gradient-to-b from-surface-hover to-surface overflow-hidden flex-shrink-0",
|
|
48764
|
+
style: { boxShadow: "inset 0 0 0 2px rgba(255,255,255,.06)" },
|
|
48765
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
48766
|
+
className: cn("absolute inset-x-0 bottom-0 bg-primary/25 transition-[height] duration-500", running && "animate-pulse"),
|
|
48767
|
+
style: { height: `${pct}%` }
|
|
48768
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
48769
|
+
className: "absolute inset-0 flex items-center justify-center text-foreground-subtle",
|
|
48770
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Waves, { className: "h-10 w-10 opacity-70" })
|
|
48771
|
+
})]
|
|
48772
|
+
});
|
|
48773
|
+
}
|
|
48774
|
+
function DishwasherPanel({ trpc, deviceId, layout, optimistic }) {
|
|
48775
|
+
const t = useUiText();
|
|
48776
|
+
const { cap, slice, available } = useDishwasher(trpc, deviceId);
|
|
48777
|
+
const { display, applyOptimistic } = useOptimisticSlice(slice, { enabled: optimistic ?? true });
|
|
48778
|
+
if (cap === void 0) return null;
|
|
48779
|
+
if (display === void 0) return null;
|
|
48780
|
+
const busy = !available;
|
|
48781
|
+
const meta = dishwasherStateMeta(display.state, t);
|
|
48782
|
+
const running = display.state === "running";
|
|
48783
|
+
const paused = display.state === "paused";
|
|
48784
|
+
const press = (fn, patch) => {
|
|
48785
|
+
if (busy) return;
|
|
48786
|
+
applyOptimistic(patch);
|
|
48787
|
+
fn();
|
|
48788
|
+
};
|
|
48789
|
+
if (layout === "compact") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48790
|
+
className: "inline-flex items-center gap-1",
|
|
48791
|
+
children: [
|
|
48792
|
+
display.doorOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48793
|
+
className: "inline-flex text-warning",
|
|
48794
|
+
title: t("uiLibrary.dishwasher.doorOpen", "Door open"),
|
|
48795
|
+
"aria-label": t("uiLibrary.dishwasher.doorOpen", "Door open"),
|
|
48796
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DoorOpen, { className: "h-3.5 w-3.5" })
|
|
48797
|
+
}),
|
|
48798
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48799
|
+
className: cn("text-xs font-medium tabular-nums", meta.tone),
|
|
48800
|
+
children: meta.label
|
|
48801
|
+
}),
|
|
48802
|
+
display.remainingMinutes != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
48803
|
+
className: "inline-flex items-center gap-0.5 text-xs text-foreground-subtle tabular-nums",
|
|
48804
|
+
children: [
|
|
48805
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Timer, { className: "h-3 w-3" }),
|
|
48806
|
+
display.remainingMinutes,
|
|
48807
|
+
"m"
|
|
48808
|
+
]
|
|
48809
|
+
}),
|
|
48810
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
48811
|
+
label: t("uiLibrary.dishwasher.start", "Start"),
|
|
48812
|
+
busy: busy || !display.remoteStartAllowed,
|
|
48813
|
+
active: running,
|
|
48814
|
+
onClick: () => press(() => cap.start({}), {
|
|
48815
|
+
state: "running",
|
|
48816
|
+
remoteStartAllowed: false
|
|
48817
|
+
}),
|
|
48818
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Play, { className: "h-3 w-3" })
|
|
48819
|
+
}),
|
|
48820
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
48821
|
+
label: t("uiLibrary.dishwasher.pause", "Pause"),
|
|
48822
|
+
busy: busy || !running,
|
|
48823
|
+
active: paused,
|
|
48824
|
+
onClick: () => press(() => cap.pause({}), { state: "paused" }),
|
|
48825
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pause, { className: "h-3 w-3" })
|
|
48826
|
+
})
|
|
48827
|
+
]
|
|
48828
|
+
});
|
|
48829
|
+
const programs = display.availablePrograms;
|
|
48830
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48831
|
+
className: "flex flex-col gap-3 rounded-lg border border-border bg-surface p-4",
|
|
48832
|
+
children: [
|
|
48833
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48834
|
+
className: "flex items-start gap-4",
|
|
48835
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherGraphic, {
|
|
48836
|
+
state: display.state,
|
|
48837
|
+
progressPercent: display.progressPercent
|
|
48838
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48839
|
+
className: "flex min-w-0 flex-1 flex-col gap-2",
|
|
48840
|
+
children: [
|
|
48841
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48842
|
+
className: "flex flex-wrap items-center gap-2",
|
|
48843
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48844
|
+
className: cn("text-3xl font-semibold", meta.tone),
|
|
48845
|
+
children: meta.label
|
|
48846
|
+
}), !display.powerOn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48847
|
+
className: "text-xs font-medium text-foreground-subtle",
|
|
48848
|
+
children: t("uiLibrary.dishwasher.powerOff", "Power off")
|
|
48849
|
+
})]
|
|
48850
|
+
}),
|
|
48851
|
+
display.program != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48852
|
+
className: "text-sm text-foreground-subtle",
|
|
48853
|
+
children: display.program
|
|
48854
|
+
}),
|
|
48855
|
+
display.progressPercent != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48856
|
+
className: "flex items-center gap-2",
|
|
48857
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
48858
|
+
className: "h-1.5 flex-1 rounded bg-surface-hover overflow-hidden",
|
|
48859
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
48860
|
+
className: "h-full bg-primary transition-[width] duration-500",
|
|
48861
|
+
style: { width: `${Math.max(0, Math.min(100, display.progressPercent))}%` }
|
|
48862
|
+
})
|
|
48863
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
48864
|
+
className: "tabular-nums text-xs text-foreground-subtle",
|
|
48865
|
+
children: [Math.round(display.progressPercent), "%"]
|
|
48866
|
+
})]
|
|
48867
|
+
}),
|
|
48868
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48869
|
+
className: "flex flex-wrap gap-3",
|
|
48870
|
+
children: [
|
|
48871
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableBadge, {
|
|
48872
|
+
label: t("uiLibrary.dishwasher.salt", "Salt"),
|
|
48873
|
+
level: display.saltLevel
|
|
48874
|
+
}),
|
|
48875
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableBadge, {
|
|
48876
|
+
label: t("uiLibrary.dishwasher.rinseAid", "Rinse aid"),
|
|
48877
|
+
level: display.rinseAidLevel
|
|
48878
|
+
}),
|
|
48879
|
+
display.doorOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
48880
|
+
className: "inline-flex items-center gap-1 text-xs text-warning",
|
|
48881
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DoorOpen, { className: "h-3.5 w-3.5" }), t("uiLibrary.dishwasher.doorOpen", "Door open")]
|
|
48882
|
+
})
|
|
48883
|
+
]
|
|
48884
|
+
}),
|
|
48885
|
+
display.errorCode != null && display.errorCode !== 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48886
|
+
className: "inline-flex items-center gap-1 text-xs text-destructive",
|
|
48887
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TriangleAlert, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: display.errorLabel ?? t("uiLibrary.dishwasher.errorCode", "Error {{code}}", { code: display.errorCode }) })]
|
|
48888
|
+
}),
|
|
48889
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48890
|
+
className: "flex flex-wrap items-center gap-2",
|
|
48891
|
+
children: [
|
|
48892
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
48893
|
+
label: t("uiLibrary.dishwasher.start", "Start"),
|
|
48894
|
+
busy: busy || !display.remoteStartAllowed,
|
|
48895
|
+
active: running,
|
|
48896
|
+
onClick: () => press(() => cap.start({}), {
|
|
48897
|
+
state: "running",
|
|
48898
|
+
remoteStartAllowed: false
|
|
48899
|
+
}),
|
|
48900
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Play, { className: "h-4 w-4" })
|
|
48901
|
+
}),
|
|
48902
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
48903
|
+
label: t("uiLibrary.dishwasher.pause", "Pause"),
|
|
48904
|
+
busy: busy || !running,
|
|
48905
|
+
active: paused,
|
|
48906
|
+
onClick: () => press(() => cap.pause({}), { state: "paused" }),
|
|
48907
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pause, { className: "h-4 w-4" })
|
|
48908
|
+
}),
|
|
48909
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
48910
|
+
label: t("uiLibrary.dishwasher.stop", "Stop"),
|
|
48911
|
+
busy,
|
|
48912
|
+
active: false,
|
|
48913
|
+
onClick: () => press(() => cap.stop({}), {
|
|
48914
|
+
state: "idle",
|
|
48915
|
+
remainingMinutes: null
|
|
48916
|
+
}),
|
|
48917
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Square, { className: "h-3.5 w-3.5" })
|
|
48918
|
+
}),
|
|
48919
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
48920
|
+
label: t("uiLibrary.dishwasher.power", "Power"),
|
|
48921
|
+
busy,
|
|
48922
|
+
active: display.powerOn,
|
|
48923
|
+
onClick: () => {
|
|
48924
|
+
const next = !display.powerOn;
|
|
48925
|
+
press(() => cap.setPower({ on: next }), { powerOn: next });
|
|
48926
|
+
},
|
|
48927
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Power, { className: "h-4 w-4" })
|
|
48928
|
+
})
|
|
48929
|
+
]
|
|
48930
|
+
})
|
|
48931
|
+
]
|
|
48932
|
+
})]
|
|
48933
|
+
}),
|
|
48934
|
+
programs.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
48935
|
+
className: "flex items-center gap-2 text-xs text-foreground-subtle",
|
|
48936
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48937
|
+
className: "w-16 shrink-0",
|
|
48938
|
+
children: t("uiLibrary.dishwasher.program", "Program")
|
|
48939
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
48940
|
+
"aria-label": t("uiLibrary.dishwasher.program", "Program"),
|
|
48941
|
+
value: display.program ?? "",
|
|
48942
|
+
disabled: busy,
|
|
48943
|
+
onChange: (e) => {
|
|
48944
|
+
const program = e.target.value;
|
|
48945
|
+
if (!program) return;
|
|
48946
|
+
applyOptimistic({ program });
|
|
48947
|
+
cap.setProgram({ program });
|
|
48948
|
+
},
|
|
48949
|
+
onClick: (e) => {
|
|
48950
|
+
e.stopPropagation();
|
|
48951
|
+
},
|
|
48952
|
+
className: "flex-1 rounded border border-border bg-surface px-2 py-1 text-foreground disabled:opacity-50",
|
|
48953
|
+
children: [display.program == null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
48954
|
+
value: "",
|
|
48955
|
+
children: "—"
|
|
48956
|
+
}), programs.map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
48957
|
+
value: p,
|
|
48958
|
+
children: p
|
|
48959
|
+
}, p))]
|
|
48960
|
+
})]
|
|
48961
|
+
}),
|
|
48962
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48963
|
+
className: "flex flex-wrap gap-4 text-xs",
|
|
48964
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
48965
|
+
label: t("uiLibrary.dishwasher.halfLoad", "Half load"),
|
|
48966
|
+
checked: display.halfLoad,
|
|
48967
|
+
busy,
|
|
48968
|
+
onChange: (on) => {
|
|
48969
|
+
applyOptimistic({ halfLoad: on });
|
|
48970
|
+
cap.setHalfLoad({ on });
|
|
48971
|
+
}
|
|
48972
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
48973
|
+
label: t("uiLibrary.dishwasher.hygienePlus", "Hygiene plus"),
|
|
48974
|
+
checked: display.hygienePlus,
|
|
48975
|
+
busy,
|
|
48976
|
+
onChange: (on) => {
|
|
48977
|
+
applyOptimistic({ hygienePlus: on });
|
|
48978
|
+
cap.setHygienePlus({ on });
|
|
48979
|
+
}
|
|
48980
|
+
})]
|
|
48981
|
+
})
|
|
48982
|
+
]
|
|
48983
|
+
});
|
|
48984
|
+
}
|
|
48985
|
+
function ToggleRow({ label, checked, busy, onChange }) {
|
|
48986
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
48987
|
+
className: "inline-flex items-center gap-2 cursor-pointer",
|
|
48988
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
48989
|
+
type: "checkbox",
|
|
48990
|
+
checked,
|
|
48991
|
+
disabled: busy,
|
|
48992
|
+
onChange: (e) => {
|
|
48993
|
+
onChange(e.target.checked);
|
|
48994
|
+
},
|
|
48995
|
+
onClick: (e) => {
|
|
48996
|
+
e.stopPropagation();
|
|
48997
|
+
},
|
|
48998
|
+
className: "rounded border-border"
|
|
48999
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label })]
|
|
49000
|
+
});
|
|
49001
|
+
}
|
|
49002
|
+
function DishwasherInlineControl(props) {
|
|
49003
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherPanel, {
|
|
49004
|
+
...props,
|
|
49005
|
+
layout: "compact"
|
|
49006
|
+
});
|
|
49007
|
+
}
|
|
49008
|
+
function DishwasherHeroCard(props) {
|
|
49009
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherPanel, {
|
|
49010
|
+
...props,
|
|
49011
|
+
layout: "full"
|
|
49012
|
+
});
|
|
49013
|
+
}
|
|
49014
|
+
//#endregion
|
|
49015
|
+
//#region src/composites/device-controls/heating-valve-readout.tsx
|
|
49016
|
+
function formatHeatingValvePosition(position) {
|
|
49017
|
+
return position !== null ? `${position}%` : "—";
|
|
49018
|
+
}
|
|
49019
|
+
function HeatingValveReadout({ trpc, deviceId, layout }) {
|
|
49020
|
+
const t = useUiText();
|
|
49021
|
+
const { cap, slice } = useDeviceCapability({
|
|
49022
|
+
trpc,
|
|
49023
|
+
deviceId,
|
|
49024
|
+
cap: (d) => d.heatingValve,
|
|
49025
|
+
slice: (d) => d.state.heatingValve
|
|
49026
|
+
});
|
|
49027
|
+
if (cap === void 0 || slice === void 0) return null;
|
|
49028
|
+
const label = t("uiLibrary.heatingValveReadout.valve", "Valve");
|
|
49029
|
+
const value = formatHeatingValvePosition(slice.position);
|
|
49030
|
+
if (layout === "compact") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
49031
|
+
className: "inline-flex items-center text-xs tabular-nums text-foreground-subtle",
|
|
49032
|
+
"aria-label": `${label} ${value}`,
|
|
49033
|
+
children: value
|
|
49034
|
+
});
|
|
49035
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49036
|
+
className: "flex items-center gap-2 text-xs tabular-nums text-foreground-subtle",
|
|
49037
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
49038
|
+
className: "font-medium text-foreground",
|
|
49039
|
+
children: value
|
|
49040
|
+
})]
|
|
49041
|
+
});
|
|
49042
|
+
}
|
|
49043
|
+
//#endregion
|
|
48550
49044
|
//#region src/composites/device-controls/ac-panel.tsx
|
|
49045
|
+
/** Modes that mean the device can cool, dry, or move air — an air conditioner. */
|
|
49046
|
+
var COOLING_MODES = new Set([
|
|
49047
|
+
"cool",
|
|
49048
|
+
"heat_cool",
|
|
49049
|
+
"dry",
|
|
49050
|
+
"fan_only"
|
|
49051
|
+
]);
|
|
49052
|
+
/** A heater offers heat (and maybe auto/off) and none of the cooling modes. */
|
|
49053
|
+
function climateOffersCooling(modes) {
|
|
49054
|
+
return modes.some((mode) => COOLING_MODES.has(mode));
|
|
49055
|
+
}
|
|
49056
|
+
/** Setpoint track bounds. Absent when the integration did not report both ends. */
|
|
49057
|
+
function climateSetpointBounds(status) {
|
|
49058
|
+
const min = status.minTemp;
|
|
49059
|
+
const max = status.maxTemp;
|
|
49060
|
+
if (typeof min !== "number" || typeof max !== "number") return null;
|
|
49061
|
+
if (!Number.isFinite(min) || !Number.isFinite(max) || max <= min) return null;
|
|
49062
|
+
return {
|
|
49063
|
+
min,
|
|
49064
|
+
max
|
|
49065
|
+
};
|
|
49066
|
+
}
|
|
49067
|
+
/** Map a 0–1 drag fraction onto the setpoint, snapped to `tempStep`. */
|
|
49068
|
+
function climateTargetFromFraction(fraction, bounds, tempStep) {
|
|
49069
|
+
const step = resolveClimateTempStep(tempStep);
|
|
49070
|
+
const clamped = Math.min(1, Math.max(0, fraction));
|
|
49071
|
+
const raw = bounds.min + clamped * (bounds.max - bounds.min);
|
|
49072
|
+
const steps = Math.round((raw - bounds.min) / step);
|
|
49073
|
+
const snapped = bounds.min + steps * step;
|
|
49074
|
+
const rounded = Math.round(snapped * 1e3) / 1e3;
|
|
49075
|
+
return Math.min(bounds.max, Math.max(bounds.min, rounded));
|
|
49076
|
+
}
|
|
48551
49077
|
/** AC-appropriate short mode labels. Exhaustive over HvacMode. */
|
|
48552
49078
|
function modeLabel(t, mode) {
|
|
48553
49079
|
switch (mode) {
|
|
@@ -48581,9 +49107,6 @@ var MODE_ACCENT = {
|
|
|
48581
49107
|
dry: "#22d3ee",
|
|
48582
49108
|
off: "#a1a1aa"
|
|
48583
49109
|
};
|
|
48584
|
-
/** Practical AC setpoint range (Celsius) for the target range track. */
|
|
48585
|
-
var TARGET_MIN = 16;
|
|
48586
|
-
var TARGET_MAX = 30;
|
|
48587
49110
|
/** Title-case a snake/lower token for a selector option ('medium_low' → 'Medium
|
|
48588
49111
|
* low', 'turbo' → 'Turbo'). Shared by the fan + preset selectors. */
|
|
48589
49112
|
function fanLabel(token) {
|
|
@@ -48670,7 +49193,7 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48670
49193
|
onClick: (e) => {
|
|
48671
49194
|
e.stopPropagation();
|
|
48672
49195
|
if (!busy && display.target !== null) {
|
|
48673
|
-
const next = display.target -
|
|
49196
|
+
const next = nextClimateTarget(display.target, -1, display);
|
|
48674
49197
|
applyOptimistic({ target: next });
|
|
48675
49198
|
cap.setTarget({ target: next });
|
|
48676
49199
|
}
|
|
@@ -48684,7 +49207,7 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48684
49207
|
onClick: (e) => {
|
|
48685
49208
|
e.stopPropagation();
|
|
48686
49209
|
if (!busy && display.target !== null) {
|
|
48687
|
-
const next = display.target
|
|
49210
|
+
const next = nextClimateTarget(display.target, 1, display);
|
|
48688
49211
|
applyOptimistic({ target: next });
|
|
48689
49212
|
cap.setTarget({ target: next });
|
|
48690
49213
|
}
|
|
@@ -48714,6 +49237,8 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48714
49237
|
});
|
|
48715
49238
|
const accent = MODE_ACCENT[display.mode];
|
|
48716
49239
|
const on = display.mode !== "off";
|
|
49240
|
+
const heatingOnly = !climateOffersCooling(display.availableModes);
|
|
49241
|
+
const setpointBounds = climateSetpointBounds(display);
|
|
48717
49242
|
const CurrentPresetIcon = presetIcon(display.preset);
|
|
48718
49243
|
const boost = display.preset === "turbo";
|
|
48719
49244
|
const period = Math.max(.55, airflowPeriodSec(display.fanMode) * (boost ? .6 : 1));
|
|
@@ -48730,11 +49255,12 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48730
49255
|
children: [
|
|
48731
49256
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
48732
49257
|
className: "pointer-events-none absolute inset-0 opacity-60",
|
|
48733
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AirflowViz, {
|
|
49258
|
+
children: heatingOnly && !on ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RadiatorFins, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AirflowViz, {
|
|
48734
49259
|
mode: display.mode,
|
|
48735
49260
|
accent,
|
|
48736
49261
|
periodSec: period,
|
|
48737
|
-
swaying
|
|
49262
|
+
swaying,
|
|
49263
|
+
forceRising: heatingOnly
|
|
48738
49264
|
})
|
|
48739
49265
|
}),
|
|
48740
49266
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "pointer-events-none absolute inset-0 bg-gradient-to-b from-surface/50 via-transparent to-surface/50" }),
|
|
@@ -48752,6 +49278,7 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48752
49278
|
children: [
|
|
48753
49279
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModeIcon, {
|
|
48754
49280
|
mode: display.mode,
|
|
49281
|
+
heatingOnly,
|
|
48755
49282
|
className: "h-3.5 w-3.5"
|
|
48756
49283
|
}),
|
|
48757
49284
|
modeLabel(t, display.mode),
|
|
@@ -48778,7 +49305,7 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48778
49305
|
disabled: busy,
|
|
48779
49306
|
onClick: () => {
|
|
48780
49307
|
if (busy || display.target === null) return;
|
|
48781
|
-
const next = display.target -
|
|
49308
|
+
const next = nextClimateTarget(display.target, -1, display);
|
|
48782
49309
|
applyOptimistic({ target: next });
|
|
48783
49310
|
cap.setTarget({ target: next });
|
|
48784
49311
|
}
|
|
@@ -48800,19 +49327,29 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48800
49327
|
disabled: busy,
|
|
48801
49328
|
onClick: () => {
|
|
48802
49329
|
if (busy || display.target === null) return;
|
|
48803
|
-
const next = display.target
|
|
49330
|
+
const next = nextClimateTarget(display.target, 1, display);
|
|
48804
49331
|
applyOptimistic({ target: next });
|
|
48805
49332
|
cap.setTarget({ target: next });
|
|
48806
49333
|
}
|
|
48807
49334
|
})
|
|
48808
49335
|
]
|
|
48809
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
48810
|
-
className: "relative z-10 w-
|
|
49336
|
+
}), setpointBounds !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49337
|
+
className: "relative z-10 w-full px-1",
|
|
48811
49338
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RangeTrack, {
|
|
48812
49339
|
value: display.target,
|
|
48813
|
-
min:
|
|
48814
|
-
max:
|
|
48815
|
-
|
|
49340
|
+
min: setpointBounds.min,
|
|
49341
|
+
max: setpointBounds.max,
|
|
49342
|
+
step: display.tempStep,
|
|
49343
|
+
accent,
|
|
49344
|
+
disabled: busy,
|
|
49345
|
+
label: t("uiLibrary.acPanel.setpoint", "Setpoint"),
|
|
49346
|
+
onPreview: (next) => {
|
|
49347
|
+
applyOptimistic({ target: next });
|
|
49348
|
+
},
|
|
49349
|
+
onCommit: (next) => {
|
|
49350
|
+
applyOptimistic({ target: next });
|
|
49351
|
+
cap.setTarget({ target: next });
|
|
49352
|
+
}
|
|
48816
49353
|
})
|
|
48817
49354
|
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48818
49355
|
className: "relative z-10 flex items-baseline gap-0.5",
|
|
@@ -48836,6 +49373,7 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48836
49373
|
className: "flex",
|
|
48837
49374
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModeIcon, {
|
|
48838
49375
|
mode: display.mode,
|
|
49376
|
+
heatingOnly,
|
|
48839
49377
|
className: "h-4 w-4"
|
|
48840
49378
|
})
|
|
48841
49379
|
}),
|
|
@@ -48919,9 +49457,8 @@ function Field({ label, children }) {
|
|
|
48919
49457
|
}), children]
|
|
48920
49458
|
});
|
|
48921
49459
|
}
|
|
48922
|
-
function ModeIcon({ mode, className }) {
|
|
48923
|
-
|
|
48924
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
49460
|
+
function ModeIcon({ mode, heatingOnly = false, className }) {
|
|
49461
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(heatingOnly && mode === "heat" ? Heater : MODE_ICON[mode], {
|
|
48925
49462
|
className,
|
|
48926
49463
|
"aria-hidden": true
|
|
48927
49464
|
});
|
|
@@ -48971,11 +49508,89 @@ function RoundStep({ label, symbol, disabled, onClick }) {
|
|
|
48971
49508
|
children: symbol
|
|
48972
49509
|
});
|
|
48973
49510
|
}
|
|
48974
|
-
/**
|
|
48975
|
-
function RangeTrack({ value, min, max, accent }) {
|
|
48976
|
-
const
|
|
49511
|
+
/** Setpoint track. Drag previews; the command is sent when the pointer is released. */
|
|
49512
|
+
function RangeTrack({ value, min, max, step, accent, disabled, label, onPreview, onCommit }) {
|
|
49513
|
+
const trackRef = (0, react$1.useRef)(null);
|
|
49514
|
+
const draggingRef = (0, react$1.useRef)(false);
|
|
49515
|
+
const [draft, setDraft] = (0, react$1.useState)(null);
|
|
49516
|
+
const shown = draft ?? value;
|
|
49517
|
+
const pct = `${Math.min(1, Math.max(0, (shown - min) / (max - min))) * 100}%`;
|
|
49518
|
+
const tempAt = (clientX) => {
|
|
49519
|
+
const el = trackRef.current;
|
|
49520
|
+
if (el === null) return value;
|
|
49521
|
+
const rect = el.getBoundingClientRect();
|
|
49522
|
+
if (rect.width <= 0) return value;
|
|
49523
|
+
return climateTargetFromFraction((clientX - rect.left) / rect.width, {
|
|
49524
|
+
min,
|
|
49525
|
+
max
|
|
49526
|
+
}, step);
|
|
49527
|
+
};
|
|
49528
|
+
const onPointerDown = (event) => {
|
|
49529
|
+
if (disabled) return;
|
|
49530
|
+
event.stopPropagation();
|
|
49531
|
+
event.preventDefault();
|
|
49532
|
+
draggingRef.current = true;
|
|
49533
|
+
if (typeof event.currentTarget.setPointerCapture === "function") event.currentTarget.setPointerCapture(event.pointerId);
|
|
49534
|
+
const next = tempAt(event.clientX);
|
|
49535
|
+
setDraft(next);
|
|
49536
|
+
onPreview(next);
|
|
49537
|
+
};
|
|
49538
|
+
const onPointerMove = (event) => {
|
|
49539
|
+
if (disabled || !draggingRef.current) return;
|
|
49540
|
+
event.stopPropagation();
|
|
49541
|
+
const next = tempAt(event.clientX);
|
|
49542
|
+
setDraft(next);
|
|
49543
|
+
onPreview(next);
|
|
49544
|
+
};
|
|
49545
|
+
const endDrag = (event) => {
|
|
49546
|
+
if (!draggingRef.current) return;
|
|
49547
|
+
event.stopPropagation();
|
|
49548
|
+
draggingRef.current = false;
|
|
49549
|
+
const next = tempAt(event.clientX);
|
|
49550
|
+
setDraft(null);
|
|
49551
|
+
if (event.currentTarget.hasPointerCapture?.(event.pointerId) === true) event.currentTarget.releasePointerCapture(event.pointerId);
|
|
49552
|
+
onCommit(next);
|
|
49553
|
+
};
|
|
49554
|
+
const onKeyDown = (event) => {
|
|
49555
|
+
if (disabled) return;
|
|
49556
|
+
const current = draft ?? value;
|
|
49557
|
+
if (event.key === "ArrowRight" || event.key === "ArrowUp") {
|
|
49558
|
+
event.preventDefault();
|
|
49559
|
+
onCommit(nextClimateTarget(current, 1, {
|
|
49560
|
+
tempStep: step,
|
|
49561
|
+
minTemp: min,
|
|
49562
|
+
maxTemp: max
|
|
49563
|
+
}));
|
|
49564
|
+
} else if (event.key === "ArrowLeft" || event.key === "ArrowDown") {
|
|
49565
|
+
event.preventDefault();
|
|
49566
|
+
onCommit(nextClimateTarget(current, -1, {
|
|
49567
|
+
tempStep: step,
|
|
49568
|
+
minTemp: min,
|
|
49569
|
+
maxTemp: max
|
|
49570
|
+
}));
|
|
49571
|
+
} else if (event.key === "Home") {
|
|
49572
|
+
event.preventDefault();
|
|
49573
|
+
onCommit(min);
|
|
49574
|
+
} else if (event.key === "End") {
|
|
49575
|
+
event.preventDefault();
|
|
49576
|
+
onCommit(max);
|
|
49577
|
+
}
|
|
49578
|
+
};
|
|
48977
49579
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48978
|
-
|
|
49580
|
+
ref: trackRef,
|
|
49581
|
+
role: "slider",
|
|
49582
|
+
"aria-label": label,
|
|
49583
|
+
"aria-valuemin": min,
|
|
49584
|
+
"aria-valuemax": max,
|
|
49585
|
+
"aria-valuenow": shown,
|
|
49586
|
+
"aria-disabled": disabled,
|
|
49587
|
+
tabIndex: disabled ? -1 : 0,
|
|
49588
|
+
onPointerDown,
|
|
49589
|
+
onPointerMove,
|
|
49590
|
+
onPointerUp: endDrag,
|
|
49591
|
+
onPointerCancel: endDrag,
|
|
49592
|
+
onKeyDown,
|
|
49593
|
+
className: cn("mt-1 w-full touch-none select-none py-2", disabled ? "cursor-not-allowed opacity-40" : "cursor-ew-resize"),
|
|
48979
49594
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48980
49595
|
className: "relative h-1.5 w-full rounded-full bg-surface-subtle",
|
|
48981
49596
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -48985,7 +49600,7 @@ function RangeTrack({ value, min, max, accent }) {
|
|
|
48985
49600
|
background: accent
|
|
48986
49601
|
}
|
|
48987
49602
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
48988
|
-
className: "absolute top-1/2 h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-surface shadow",
|
|
49603
|
+
className: "absolute top-1/2 h-3.5 w-3.5 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-surface shadow",
|
|
48989
49604
|
style: {
|
|
48990
49605
|
left: pct,
|
|
48991
49606
|
background: accent
|
|
@@ -49002,7 +49617,7 @@ function RangeTrack({ value, min, max, accent }) {
|
|
|
49002
49617
|
* vent; every active mode renders drifting particles that FALL (cool/dry/auto)
|
|
49003
49618
|
* or RISE (heat), with a horizontal sweep overlaid when a swing axis is active.
|
|
49004
49619
|
*/
|
|
49005
|
-
function AirflowViz({ mode, accent, periodSec, swaying }) {
|
|
49620
|
+
function AirflowViz({ mode, accent, periodSec, swaying, forceRising = false }) {
|
|
49006
49621
|
const columns = [
|
|
49007
49622
|
0,
|
|
49008
49623
|
1,
|
|
@@ -49011,7 +49626,7 @@ function AirflowViz({ mode, accent, periodSec, swaying }) {
|
|
|
49011
49626
|
4,
|
|
49012
49627
|
5
|
|
49013
49628
|
];
|
|
49014
|
-
const rising = mode === "heat";
|
|
49629
|
+
const rising = forceRising || mode === "heat";
|
|
49015
49630
|
const isDroplet = mode === "dry";
|
|
49016
49631
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49017
49632
|
"aria-hidden": true,
|
|
@@ -49027,6 +49642,7 @@ function AirflowViz({ mode, accent, periodSec, swaying }) {
|
|
|
49027
49642
|
size: 56
|
|
49028
49643
|
})
|
|
49029
49644
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49645
|
+
"data-testid": forceRising ? "climate-heat-rise" : void 0,
|
|
49030
49646
|
className: cn("acv-sway-wrap absolute inset-0", swaying && "acv-sway"),
|
|
49031
49647
|
style: swaying ? { animation: "acv-sway 2.6s ease-in-out infinite" } : void 0,
|
|
49032
49648
|
children: columns.map((i) => {
|
|
@@ -49049,6 +49665,23 @@ function AirflowViz({ mode, accent, periodSec, swaying }) {
|
|
|
49049
49665
|
})
|
|
49050
49666
|
});
|
|
49051
49667
|
}
|
|
49668
|
+
/** Still radiator fins — the off state of a heating-only climate. */
|
|
49669
|
+
function RadiatorFins() {
|
|
49670
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49671
|
+
"data-testid": "climate-radiator",
|
|
49672
|
+
"aria-hidden": true,
|
|
49673
|
+
className: "flex h-full items-end justify-center gap-1.5 px-8 pb-3 opacity-40",
|
|
49674
|
+
children: [
|
|
49675
|
+
0,
|
|
49676
|
+
1,
|
|
49677
|
+
2,
|
|
49678
|
+
3,
|
|
49679
|
+
4,
|
|
49680
|
+
5,
|
|
49681
|
+
6
|
|
49682
|
+
].map((i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "h-10 w-1.5 rounded-t-full bg-foreground-subtle" }, i))
|
|
49683
|
+
});
|
|
49684
|
+
}
|
|
49052
49685
|
/** Big spinning fan for `fan_only` mode. */
|
|
49053
49686
|
function FanBlades({ color, periodSec, size }) {
|
|
49054
49687
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
@@ -49215,16 +49848,33 @@ var MODE_ARC_COLOR = {
|
|
|
49215
49848
|
*/
|
|
49216
49849
|
function ThermostatHeroCard({ trpc, deviceId, features: _features, optimistic }) {
|
|
49217
49850
|
const t = useUiText();
|
|
49218
|
-
const
|
|
49851
|
+
const climate = useDeviceCapability({
|
|
49219
49852
|
trpc,
|
|
49220
49853
|
deviceId,
|
|
49221
49854
|
cap: (d) => d.climateControl,
|
|
49222
49855
|
slice: (d) => d.state.climateControl
|
|
49223
49856
|
});
|
|
49224
|
-
const
|
|
49225
|
-
|
|
49226
|
-
|
|
49227
|
-
|
|
49857
|
+
const valve = useDeviceCapability({
|
|
49858
|
+
trpc,
|
|
49859
|
+
deviceId,
|
|
49860
|
+
cap: (d) => d.heatingValve,
|
|
49861
|
+
slice: (d) => d.state.heatingValve
|
|
49862
|
+
});
|
|
49863
|
+
const { display, applyOptimistic } = useOptimisticSlice(climate.slice, { enabled: optimistic ?? true });
|
|
49864
|
+
const hasClimate = climate.cap !== void 0 && display !== void 0;
|
|
49865
|
+
const hasValve = valve.cap !== void 0;
|
|
49866
|
+
if (!hasClimate && !hasValve) return null;
|
|
49867
|
+
if (!hasClimate && hasValve) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49868
|
+
className: "flex flex-col items-stretch gap-3 rounded-lg border border-border bg-surface p-4",
|
|
49869
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeatingValveReadout, {
|
|
49870
|
+
trpc,
|
|
49871
|
+
deviceId,
|
|
49872
|
+
layout: "full"
|
|
49873
|
+
})
|
|
49874
|
+
});
|
|
49875
|
+
if (climate.cap === void 0 || display === void 0) return null;
|
|
49876
|
+
const cap = climate.cap;
|
|
49877
|
+
const busy = !climate.available;
|
|
49228
49878
|
const color = MODE_ARC_COLOR[display.mode];
|
|
49229
49879
|
const isRange = display.mode === "heat_cool" || display.targetLow !== null && display.targetHigh !== null;
|
|
49230
49880
|
const min = DEFAULT_TEMP_MIN;
|
|
@@ -49235,132 +49885,140 @@ function ThermostatHeroCard({ trpc, deviceId, features: _features, optimistic })
|
|
|
49235
49885
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49236
49886
|
"data-mode": display.mode,
|
|
49237
49887
|
className: cn("flex flex-col items-stretch gap-4 rounded-lg border border-border bg-surface p-4 transition-colors", MODE_COLOR[display.mode]),
|
|
49238
|
-
children: [
|
|
49239
|
-
|
|
49240
|
-
|
|
49241
|
-
|
|
49242
|
-
|
|
49243
|
-
|
|
49244
|
-
|
|
49245
|
-
|
|
49246
|
-
|
|
49247
|
-
|
|
49248
|
-
|
|
49249
|
-
|
|
49250
|
-
|
|
49251
|
-
|
|
49252
|
-
|
|
49253
|
-
|
|
49254
|
-
|
|
49255
|
-
|
|
49256
|
-
|
|
49257
|
-
color
|
|
49258
|
-
}),
|
|
49259
|
-
isRange ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49260
|
-
"data-testid": "arc-knob",
|
|
49261
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArcKnob, {
|
|
49262
|
-
fraction: fractionLow,
|
|
49263
|
-
color,
|
|
49264
|
-
knobTopPx: THERMOSTAT_KNOB_TOP_PX,
|
|
49265
|
-
onDragFraction: (f) => {
|
|
49266
|
-
if (busy || display.targetHigh === null) return;
|
|
49267
|
-
const next = Math.min(fractionToTemp(f, min, max), display.targetHigh);
|
|
49268
|
-
applyOptimistic({ targetLow: next });
|
|
49269
|
-
cap.setTargetRange({
|
|
49270
|
-
targetLow: next,
|
|
49271
|
-
targetHigh: display.targetHigh
|
|
49272
|
-
});
|
|
49273
|
-
}
|
|
49274
|
-
})
|
|
49275
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49276
|
-
"data-testid": "arc-knob",
|
|
49277
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArcKnob, {
|
|
49278
|
-
fraction: fractionHigh,
|
|
49279
|
-
color,
|
|
49280
|
-
knobTopPx: THERMOSTAT_KNOB_TOP_PX,
|
|
49281
|
-
onDragFraction: (f) => {
|
|
49282
|
-
if (busy || display.targetLow === null) return;
|
|
49283
|
-
const next = Math.max(fractionToTemp(f, min, max), display.targetLow);
|
|
49284
|
-
applyOptimistic({ targetHigh: next });
|
|
49285
|
-
cap.setTargetRange({
|
|
49286
|
-
targetLow: display.targetLow,
|
|
49287
|
-
targetHigh: next
|
|
49288
|
-
});
|
|
49289
|
-
}
|
|
49290
|
-
})
|
|
49291
|
-
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49292
|
-
"data-testid": "arc-knob",
|
|
49293
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArcKnob, {
|
|
49888
|
+
children: [
|
|
49889
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49890
|
+
className: "flex flex-col items-center gap-3",
|
|
49891
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49892
|
+
className: "relative",
|
|
49893
|
+
style: {
|
|
49894
|
+
width: GAUGE_SIZE,
|
|
49895
|
+
height: GAUGE_SIZE
|
|
49896
|
+
},
|
|
49897
|
+
children: [
|
|
49898
|
+
isRange ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeRing, {
|
|
49899
|
+
mode: "band",
|
|
49900
|
+
band: {
|
|
49901
|
+
from: fractionLow,
|
|
49902
|
+
to: fractionHigh
|
|
49903
|
+
},
|
|
49904
|
+
color
|
|
49905
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeRing, {
|
|
49906
|
+
mode: "fill",
|
|
49294
49907
|
fraction: singleFraction,
|
|
49295
|
-
color
|
|
49296
|
-
|
|
49297
|
-
|
|
49298
|
-
|
|
49299
|
-
|
|
49300
|
-
|
|
49301
|
-
|
|
49302
|
-
|
|
49303
|
-
|
|
49304
|
-
|
|
49305
|
-
|
|
49306
|
-
|
|
49307
|
-
|
|
49308
|
-
|
|
49309
|
-
|
|
49310
|
-
|
|
49311
|
-
|
|
49312
|
-
className: "text-lg text-foreground-subtle font-normal ml-0.5",
|
|
49313
|
-
children: "°"
|
|
49314
|
-
})]
|
|
49315
|
-
}),
|
|
49316
|
-
!isRange && display.target !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49317
|
-
className: "text-xs font-semibold",
|
|
49318
|
-
style: { color },
|
|
49319
|
-
children: t("uiLibrary.thermostatHeroCard.setTo", "set {{value}}°", { value: display.target })
|
|
49320
|
-
}),
|
|
49321
|
-
isRange && display.targetLow !== null && display.targetHigh !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49322
|
-
className: "text-xs font-semibold",
|
|
49323
|
-
style: { color },
|
|
49324
|
-
children: [
|
|
49325
|
-
display.targetLow,
|
|
49326
|
-
"° – ",
|
|
49327
|
-
display.targetHigh,
|
|
49328
|
-
"°"
|
|
49329
|
-
]
|
|
49908
|
+
color
|
|
49909
|
+
}),
|
|
49910
|
+
isRange ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49911
|
+
"data-testid": "arc-knob",
|
|
49912
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArcKnob, {
|
|
49913
|
+
fraction: fractionLow,
|
|
49914
|
+
color,
|
|
49915
|
+
knobTopPx: THERMOSTAT_KNOB_TOP_PX,
|
|
49916
|
+
onDragFraction: (f) => {
|
|
49917
|
+
if (busy || display.targetHigh === null) return;
|
|
49918
|
+
const next = Math.min(fractionToTemp(f, min, max), display.targetHigh);
|
|
49919
|
+
applyOptimistic({ targetLow: next });
|
|
49920
|
+
cap.setTargetRange({
|
|
49921
|
+
targetLow: next,
|
|
49922
|
+
targetHigh: display.targetHigh
|
|
49923
|
+
});
|
|
49924
|
+
}
|
|
49330
49925
|
})
|
|
49331
|
-
|
|
49926
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49927
|
+
"data-testid": "arc-knob",
|
|
49928
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArcKnob, {
|
|
49929
|
+
fraction: fractionHigh,
|
|
49930
|
+
color,
|
|
49931
|
+
knobTopPx: THERMOSTAT_KNOB_TOP_PX,
|
|
49932
|
+
onDragFraction: (f) => {
|
|
49933
|
+
if (busy || display.targetLow === null) return;
|
|
49934
|
+
const next = Math.max(fractionToTemp(f, min, max), display.targetLow);
|
|
49935
|
+
applyOptimistic({ targetHigh: next });
|
|
49936
|
+
cap.setTargetRange({
|
|
49937
|
+
targetLow: display.targetLow,
|
|
49938
|
+
targetHigh: next
|
|
49939
|
+
});
|
|
49940
|
+
}
|
|
49941
|
+
})
|
|
49942
|
+
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49943
|
+
"data-testid": "arc-knob",
|
|
49944
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArcKnob, {
|
|
49945
|
+
fraction: singleFraction,
|
|
49946
|
+
color,
|
|
49947
|
+
knobTopPx: THERMOSTAT_KNOB_TOP_PX,
|
|
49948
|
+
onDragFraction: (f) => {
|
|
49949
|
+
if (busy) return;
|
|
49950
|
+
const next = fractionToTemp(f, min, max);
|
|
49951
|
+
applyOptimistic({ target: next });
|
|
49952
|
+
cap.setTarget({ target: next });
|
|
49953
|
+
}
|
|
49954
|
+
})
|
|
49955
|
+
}),
|
|
49956
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49957
|
+
className: "absolute inset-0 flex flex-col items-center justify-center gap-0.5 pointer-events-none",
|
|
49958
|
+
children: [
|
|
49959
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49960
|
+
"data-testid": "current-temp",
|
|
49961
|
+
className: "text-4xl font-bold tabular-nums text-foreground leading-none",
|
|
49962
|
+
children: [display.currentTemp !== null ? display.currentTemp : "—", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
49963
|
+
className: "text-lg text-foreground-subtle font-normal ml-0.5",
|
|
49964
|
+
children: "°"
|
|
49965
|
+
})]
|
|
49966
|
+
}),
|
|
49967
|
+
!isRange && display.target !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49968
|
+
className: "text-xs font-semibold",
|
|
49969
|
+
style: { color },
|
|
49970
|
+
children: t("uiLibrary.thermostatHeroCard.setTo", "set {{value}}°", { value: display.target })
|
|
49971
|
+
}),
|
|
49972
|
+
isRange && display.targetLow !== null && display.targetHigh !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49973
|
+
className: "text-xs font-semibold",
|
|
49974
|
+
style: { color },
|
|
49975
|
+
children: [
|
|
49976
|
+
display.targetLow,
|
|
49977
|
+
"° – ",
|
|
49978
|
+
display.targetHigh,
|
|
49979
|
+
"°"
|
|
49980
|
+
]
|
|
49981
|
+
})
|
|
49982
|
+
]
|
|
49983
|
+
})
|
|
49984
|
+
]
|
|
49985
|
+
}), !isRange && display.target !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49986
|
+
className: "w-[200px]",
|
|
49987
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stepper, {
|
|
49988
|
+
value: `${display.target}°`,
|
|
49989
|
+
onDelta: (d) => {
|
|
49990
|
+
if (busy || display.target === null) return;
|
|
49991
|
+
const next = display.target + d;
|
|
49992
|
+
applyOptimistic({ target: next });
|
|
49993
|
+
cap.setTarget({ target: next });
|
|
49994
|
+
}
|
|
49332
49995
|
})
|
|
49333
|
-
]
|
|
49334
|
-
}),
|
|
49335
|
-
|
|
49336
|
-
|
|
49337
|
-
|
|
49338
|
-
|
|
49339
|
-
|
|
49340
|
-
|
|
49341
|
-
|
|
49342
|
-
|
|
49996
|
+
})]
|
|
49997
|
+
}),
|
|
49998
|
+
hasValve ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeatingValveReadout, {
|
|
49999
|
+
trpc,
|
|
50000
|
+
deviceId,
|
|
50001
|
+
layout: "full"
|
|
50002
|
+
}) : null,
|
|
50003
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
50004
|
+
"aria-label": t("uiLibrary.thermostatHeroCard.mode", "Mode"),
|
|
50005
|
+
value: display.mode,
|
|
50006
|
+
disabled: busy,
|
|
50007
|
+
onChange: (e) => {
|
|
50008
|
+
e.stopPropagation();
|
|
50009
|
+
const mode = e.target.value;
|
|
50010
|
+
if (!busy && mode) {
|
|
50011
|
+
applyOptimistic({ mode: display.availableModes.find((m) => m === mode) ?? display.mode });
|
|
50012
|
+
cap.setMode({ mode: display.availableModes.find((m) => m === mode) ?? display.mode });
|
|
49343
50013
|
}
|
|
49344
|
-
}
|
|
49345
|
-
|
|
49346
|
-
|
|
49347
|
-
|
|
49348
|
-
|
|
49349
|
-
|
|
49350
|
-
|
|
49351
|
-
|
|
49352
|
-
const mode = e.target.value;
|
|
49353
|
-
if (!busy && mode) {
|
|
49354
|
-
applyOptimistic({ mode: display.availableModes.find((m) => m === mode) ?? display.mode });
|
|
49355
|
-
cap.setMode({ mode: display.availableModes.find((m) => m === mode) ?? display.mode });
|
|
49356
|
-
}
|
|
49357
|
-
},
|
|
49358
|
-
className: cn("w-full rounded border border-border bg-surface px-3 py-1.5 text-sm", busy && "opacity-40 cursor-not-allowed"),
|
|
49359
|
-
children: display.availableModes.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
49360
|
-
value: m,
|
|
49361
|
-
children: m
|
|
49362
|
-
}, m))
|
|
49363
|
-
})]
|
|
50014
|
+
},
|
|
50015
|
+
className: cn("w-full rounded border border-border bg-surface px-3 py-1.5 text-sm", busy && "opacity-40 cursor-not-allowed"),
|
|
50016
|
+
children: display.availableModes.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
50017
|
+
value: m,
|
|
50018
|
+
children: m
|
|
50019
|
+
}, m))
|
|
50020
|
+
})
|
|
50021
|
+
]
|
|
49364
50022
|
});
|
|
49365
50023
|
}
|
|
49366
50024
|
//#endregion
|
|
@@ -50868,14 +51526,32 @@ function useClimateCapGuard(trpc, deviceId) {
|
|
|
50868
51526
|
});
|
|
50869
51527
|
return cap !== void 0;
|
|
50870
51528
|
}
|
|
50871
|
-
function
|
|
50872
|
-
|
|
50873
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClimatePanel, {
|
|
51529
|
+
function useHeatingValveCapGuard(trpc, deviceId) {
|
|
51530
|
+
const { cap } = useDeviceCapability({
|
|
50874
51531
|
trpc,
|
|
50875
51532
|
deviceId,
|
|
50876
|
-
|
|
50877
|
-
|
|
50878
|
-
|
|
51533
|
+
cap: (d) => d.heatingValve,
|
|
51534
|
+
slice: (d) => d.state.heatingValve
|
|
51535
|
+
});
|
|
51536
|
+
return cap !== void 0;
|
|
51537
|
+
}
|
|
51538
|
+
function ThermostatInlineControl({ trpc, deviceId, features, optimistic }) {
|
|
51539
|
+
const hasClimate = useClimateCapGuard(trpc, deviceId);
|
|
51540
|
+
const hasValve = useHeatingValveCapGuard(trpc, deviceId);
|
|
51541
|
+
if (!hasClimate && !hasValve) return null;
|
|
51542
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
51543
|
+
className: "inline-flex items-center gap-2",
|
|
51544
|
+
children: [hasClimate ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClimatePanel, {
|
|
51545
|
+
trpc,
|
|
51546
|
+
deviceId,
|
|
51547
|
+
features,
|
|
51548
|
+
layout: "compact",
|
|
51549
|
+
optimistic
|
|
51550
|
+
}) : null, hasValve ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeatingValveReadout, {
|
|
51551
|
+
trpc,
|
|
51552
|
+
deviceId,
|
|
51553
|
+
layout: "compact"
|
|
51554
|
+
}) : null]
|
|
50879
51555
|
});
|
|
50880
51556
|
}
|
|
50881
51557
|
function AcInlineControl({ trpc, deviceId, features, optimistic }) {
|
|
@@ -51040,6 +51716,10 @@ var CONTROL_KIND_COMPONENTS = {
|
|
|
51040
51716
|
InlineControl: PetFeederInlineControl,
|
|
51041
51717
|
HeroCard: PetFeederHeroCard
|
|
51042
51718
|
},
|
|
51719
|
+
dishwasher: {
|
|
51720
|
+
InlineControl: DishwasherInlineControl,
|
|
51721
|
+
HeroCard: DishwasherHeroCard
|
|
51722
|
+
},
|
|
51043
51723
|
script: {
|
|
51044
51724
|
InlineControl: ScriptInlineControl,
|
|
51045
51725
|
HeroCard: ScriptHeroCard
|
|
@@ -51116,7 +51796,8 @@ var CONTROL_FILLS = {
|
|
|
51116
51796
|
[_camstack_types.DeviceType.Weather]: false,
|
|
51117
51797
|
[_camstack_types.DeviceType.Container]: false,
|
|
51118
51798
|
[_camstack_types.DeviceType.Image]: false,
|
|
51119
|
-
[_camstack_types.DeviceType.PetFeeder]: false
|
|
51799
|
+
[_camstack_types.DeviceType.PetFeeder]: false,
|
|
51800
|
+
[_camstack_types.DeviceType.Dishwasher]: false
|
|
51120
51801
|
};
|
|
51121
51802
|
var DEVICE_TYPE_VALUES = new Set(Object.values(_camstack_types.DeviceType));
|
|
51122
51803
|
/**
|
|
@@ -63964,6 +64645,7 @@ exports.Hammer = Hammer;
|
|
|
63964
64645
|
exports.Hand = Hand;
|
|
63965
64646
|
exports.HardDrive = HardDrive;
|
|
63966
64647
|
exports.Hash = Hash;
|
|
64648
|
+
exports.Heater = Heater;
|
|
63967
64649
|
exports.HlsVideo = HlsVideo;
|
|
63968
64650
|
exports.House = House;
|
|
63969
64651
|
exports.HoverZoomImage = HoverZoomImage;
|
|
@@ -64239,6 +64921,7 @@ exports.ThemeProvider = require_theme_index.ThemeProvider$1;
|
|
|
64239
64921
|
exports.Thermometer = Thermometer;
|
|
64240
64922
|
exports.ThermostatHeroCard = ThermostatHeroCard;
|
|
64241
64923
|
exports.ThermostatInlineControl = ThermostatInlineControl;
|
|
64924
|
+
exports.Timer = Timer;
|
|
64242
64925
|
exports.TimezoneSelector = TimezoneSelector;
|
|
64243
64926
|
exports.ToggleLeft = ToggleLeft;
|
|
64244
64927
|
exports.ToggleRight = ToggleRight;
|
|
@@ -64919,6 +65602,15 @@ exports.useDeviceStateSlice$1 = useDeviceStateSlice;
|
|
|
64919
65602
|
exports.useDeviceStatusGetStatus = useDeviceStatusGetStatus;
|
|
64920
65603
|
exports.useDeviceWebrtc = useDeviceWebrtc;
|
|
64921
65604
|
exports.useDevices = useDevices;
|
|
65605
|
+
exports.useDishwasherGetStatus = useDishwasherGetStatus;
|
|
65606
|
+
exports.useDishwasherPause = useDishwasherPause;
|
|
65607
|
+
exports.useDishwasherSetHalfLoad = useDishwasherSetHalfLoad;
|
|
65608
|
+
exports.useDishwasherSetHygienePlus = useDishwasherSetHygienePlus;
|
|
65609
|
+
exports.useDishwasherSetOption = useDishwasherSetOption;
|
|
65610
|
+
exports.useDishwasherSetPower = useDishwasherSetPower;
|
|
65611
|
+
exports.useDishwasherSetProgram = useDishwasherSetProgram;
|
|
65612
|
+
exports.useDishwasherStart = useDishwasherStart;
|
|
65613
|
+
exports.useDishwasherStop = useDishwasherStop;
|
|
64922
65614
|
exports.useDisplayIconResolver = useDisplayIconResolver;
|
|
64923
65615
|
exports.useDoorbellEvents = useDoorbellEvents;
|
|
64924
65616
|
exports.useDoorbellGetStatus = useDoorbellGetStatus;
|
|
@@ -64951,6 +65643,7 @@ exports.useFanControlSetPreset = useFanControlSetPreset;
|
|
|
64951
65643
|
exports.useFeatureProbeGetStatus = useFeatureProbeGetStatus;
|
|
64952
65644
|
exports.useFloodGetStatus = useFloodGetStatus;
|
|
64953
65645
|
exports.useGasGetStatus = useGasGetStatus;
|
|
65646
|
+
exports.useHeatingValveGetStatus = useHeatingValveGetStatus;
|
|
64954
65647
|
exports.useHumidifierGetStatus = useHumidifierGetStatus;
|
|
64955
65648
|
exports.useHumidifierSetMode = useHumidifierSetMode;
|
|
64956
65649
|
exports.useHumidifierSetOn = useHumidifierSetOn;
|