@camstack/ui-library 1.2.299 → 1.2.301
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 +28 -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 +929 -167
- package/dist/index.js +919 -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,32 @@ 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.setExtraDry.useMutation`. */
|
|
11030
|
+
var useDishwasherSetExtraDry = trpc.dishwasher.setExtraDry.useMutation;
|
|
11031
|
+
/** Generated alias around `trpc.dishwasher.setIntensiveZone.useMutation`. */
|
|
11032
|
+
var useDishwasherSetIntensiveZone = trpc.dishwasher.setIntensiveZone.useMutation;
|
|
11033
|
+
/** Generated alias around `trpc.dishwasher.setVarioSpeedPlus.useMutation`. */
|
|
11034
|
+
var useDishwasherSetVarioSpeedPlus = trpc.dishwasher.setVarioSpeedPlus.useMutation;
|
|
11035
|
+
/** Generated alias around `trpc.dishwasher.setChildLock.useMutation`. */
|
|
11036
|
+
var useDishwasherSetChildLock = trpc.dishwasher.setChildLock.useMutation;
|
|
11037
|
+
/** Generated alias around `trpc.dishwasher.setOption.useMutation`. */
|
|
11038
|
+
var useDishwasherSetOption = trpc.dishwasher.setOption.useMutation;
|
|
11039
|
+
/** Generated alias around `trpc.dishwasher.getStatus.useQuery`. */
|
|
11040
|
+
var useDishwasherGetStatus = trpc.dishwasher.getStatus.useQuery;
|
|
10934
11041
|
/** Generated alias around `trpc.doorbell.getStatus.useQuery`. */
|
|
10935
11042
|
var useDoorbellGetStatus = trpc.doorbell.getStatus.useQuery;
|
|
10936
11043
|
/** Generated alias around `trpc.enumSensor.getStatus.useQuery`. */
|
|
@@ -10985,6 +11092,8 @@ var useFeatureProbeGetStatus = trpc.featureProbe.getStatus.useQuery;
|
|
|
10985
11092
|
var useFloodGetStatus = trpc.flood.getStatus.useQuery;
|
|
10986
11093
|
/** Generated alias around `trpc.gas.getStatus.useQuery`. */
|
|
10987
11094
|
var useGasGetStatus = trpc.gas.getStatus.useQuery;
|
|
11095
|
+
/** Generated alias around `trpc.heatingValve.getStatus.useQuery`. */
|
|
11096
|
+
var useHeatingValveGetStatus = trpc.heatingValve.getStatus.useQuery;
|
|
10988
11097
|
/** Generated alias around `trpc.humidifier.setOn.useMutation`. */
|
|
10989
11098
|
var useHumidifierSetOn = trpc.humidifier.setOn.useMutation;
|
|
10990
11099
|
/** Generated alias around `trpc.humidifier.setTargetHumidity.useMutation`. */
|
|
@@ -28815,7 +28924,7 @@ function AddonActionSelectField({ field, value, values, onChange, disabled, onAc
|
|
|
28815
28924
|
})()
|
|
28816
28925
|
});
|
|
28817
28926
|
}
|
|
28818
|
-
function AddonActionButtonField({ field, values, disabled, onAction }) {
|
|
28927
|
+
function AddonActionButtonField({ field, values, disabled, onAction, onChange, onChangeMany }) {
|
|
28819
28928
|
const t = useUiText();
|
|
28820
28929
|
const [loading, setLoading] = (0, react$1.useState)(false);
|
|
28821
28930
|
const [status, setStatus] = (0, react$1.useState)(null);
|
|
@@ -28847,6 +28956,17 @@ function AddonActionButtonField({ field, values, disabled, onAction }) {
|
|
|
28847
28956
|
try {
|
|
28848
28957
|
const result = await onAction(field.action, field.key, params);
|
|
28849
28958
|
const resultRecord = typeof result === "object" && result !== null ? result : null;
|
|
28959
|
+
if (resultRecord !== null && field.assignResult && (onChange || onChangeMany)) {
|
|
28960
|
+
const patch = {};
|
|
28961
|
+
for (const [formKey, resultKey] of Object.entries(field.assignResult)) {
|
|
28962
|
+
const assigned = resultRecord[resultKey];
|
|
28963
|
+
if (typeof assigned === "string") patch[formKey] = assigned;
|
|
28964
|
+
}
|
|
28965
|
+
if (Object.keys(patch).length > 0) {
|
|
28966
|
+
if (onChangeMany) onChangeMany(patch);
|
|
28967
|
+
else if (onChange) for (const [formKey, assigned] of Object.entries(patch)) onChange(formKey, assigned);
|
|
28968
|
+
}
|
|
28969
|
+
}
|
|
28850
28970
|
setStatus({
|
|
28851
28971
|
kind: "ok",
|
|
28852
28972
|
text: field.successMessage ? field.successMessage.replace(/\{(\w+)\}/g, (_, k) => {
|
|
@@ -29195,7 +29315,7 @@ function isFieldVisible(field, values, allFields) {
|
|
|
29195
29315
|
if (condition.notIncludes !== void 0) return !Array.isArray(condVal) || !condVal.includes(condition.notIncludes);
|
|
29196
29316
|
return true;
|
|
29197
29317
|
}
|
|
29198
|
-
function FormField({ field, values, onChange, disabled, translationFn, onTestField, externalProbe, allFields, onAction }) {
|
|
29318
|
+
function FormField({ field, values, onChange, onChangeMany, disabled, translationFn, onTestField, externalProbe, allFields, onAction }) {
|
|
29199
29319
|
if (hasMultiple(field)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MultipleFieldWrapper, {
|
|
29200
29320
|
field,
|
|
29201
29321
|
values,
|
|
@@ -29431,7 +29551,9 @@ function FormField({ field, values, onChange, disabled, translationFn, onTestFie
|
|
|
29431
29551
|
field,
|
|
29432
29552
|
values,
|
|
29433
29553
|
disabled,
|
|
29434
|
-
onAction
|
|
29554
|
+
onAction,
|
|
29555
|
+
onChange,
|
|
29556
|
+
onChangeMany
|
|
29435
29557
|
});
|
|
29436
29558
|
case "device-action-button": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceActionButtonField, {
|
|
29437
29559
|
field,
|
|
@@ -42914,6 +43036,19 @@ function FanPanel({ trpc, deviceId, features, layout, optimistic }) {
|
|
|
42914
43036
|
}
|
|
42915
43037
|
//#endregion
|
|
42916
43038
|
//#region src/composites/device-controls/climate-panel.tsx
|
|
43039
|
+
/** Step size for target ± buttons; defaults to 1 °C when unset or invalid. */
|
|
43040
|
+
function resolveClimateTempStep(tempStep) {
|
|
43041
|
+
if (typeof tempStep === "number" && Number.isFinite(tempStep) && tempStep > 0) return tempStep;
|
|
43042
|
+
return 1;
|
|
43043
|
+
}
|
|
43044
|
+
function nextClimateTarget(current, deltaSteps, display) {
|
|
43045
|
+
let next = current + deltaSteps * resolveClimateTempStep(display.tempStep);
|
|
43046
|
+
const min = display.minTemp;
|
|
43047
|
+
const max = display.maxTemp;
|
|
43048
|
+
if (typeof min === "number" && Number.isFinite(min)) next = Math.max(next, min);
|
|
43049
|
+
if (typeof max === "number" && Number.isFinite(max)) next = Math.min(next, max);
|
|
43050
|
+
return next;
|
|
43051
|
+
}
|
|
42917
43052
|
/**
|
|
42918
43053
|
* Exhaustive colour map — one entry per HvacMode. Build will fail if a
|
|
42919
43054
|
* mode is added to the enum and not reflected here (Record<HvacMode, ...>
|
|
@@ -42994,7 +43129,7 @@ function ClimatePanel({ trpc, deviceId, features: _features, layout, optimistic
|
|
|
42994
43129
|
onClick: (e) => {
|
|
42995
43130
|
e.stopPropagation();
|
|
42996
43131
|
if (!busy && display.target !== null) {
|
|
42997
|
-
const next = display.target -
|
|
43132
|
+
const next = nextClimateTarget(display.target, -1, display);
|
|
42998
43133
|
applyOptimistic({ target: next });
|
|
42999
43134
|
cap.setTarget({ target: next });
|
|
43000
43135
|
}
|
|
@@ -43008,7 +43143,7 @@ function ClimatePanel({ trpc, deviceId, features: _features, layout, optimistic
|
|
|
43008
43143
|
onClick: (e) => {
|
|
43009
43144
|
e.stopPropagation();
|
|
43010
43145
|
if (!busy && display.target !== null) {
|
|
43011
|
-
const next = display.target
|
|
43146
|
+
const next = nextClimateTarget(display.target, 1, display);
|
|
43012
43147
|
applyOptimistic({ target: next });
|
|
43013
43148
|
cap.setTarget({ target: next });
|
|
43014
43149
|
}
|
|
@@ -43063,7 +43198,7 @@ function ClimatePanel({ trpc, deviceId, features: _features, layout, optimistic
|
|
|
43063
43198
|
onClick: (e) => {
|
|
43064
43199
|
e.stopPropagation();
|
|
43065
43200
|
if (!busy && display.target !== null) {
|
|
43066
|
-
const next = display.target -
|
|
43201
|
+
const next = nextClimateTarget(display.target, -1, display);
|
|
43067
43202
|
applyOptimistic({ target: next });
|
|
43068
43203
|
cap.setTarget({ target: next });
|
|
43069
43204
|
}
|
|
@@ -43078,7 +43213,7 @@ function ClimatePanel({ trpc, deviceId, features: _features, layout, optimistic
|
|
|
43078
43213
|
onClick: (e) => {
|
|
43079
43214
|
e.stopPropagation();
|
|
43080
43215
|
if (!busy && display.target !== null) {
|
|
43081
|
-
const next = display.target
|
|
43216
|
+
const next = nextClimateTarget(display.target, 1, display);
|
|
43082
43217
|
applyOptimistic({ target: next });
|
|
43083
43218
|
cap.setTarget({ target: next });
|
|
43084
43219
|
}
|
|
@@ -43736,7 +43871,8 @@ var CONTROL_CAP_NAMES = [
|
|
|
43736
43871
|
"climate-control",
|
|
43737
43872
|
"media-player",
|
|
43738
43873
|
"control",
|
|
43739
|
-
"pet-feeder"
|
|
43874
|
+
"pet-feeder",
|
|
43875
|
+
"dishwasher"
|
|
43740
43876
|
];
|
|
43741
43877
|
//#endregion
|
|
43742
43878
|
//#region src/composites/device-controls/popover-row-action.tsx
|
|
@@ -48547,7 +48683,462 @@ function PetFeederHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
48547
48683
|
});
|
|
48548
48684
|
}
|
|
48549
48685
|
//#endregion
|
|
48686
|
+
//#region src/composites/device-controls/dishwasher-control.tsx
|
|
48687
|
+
/**
|
|
48688
|
+
* Dishwasher controls for a `DeviceType.Dishwasher` device.
|
|
48689
|
+
*
|
|
48690
|
+
* Reads the `dishwasher` cap status slice and renders inline + hero surfaces
|
|
48691
|
+
* mirroring vacuum-control / pet-feeder patterns.
|
|
48692
|
+
*/
|
|
48693
|
+
function dishwasherStateMeta(state, t = defaultUiText) {
|
|
48694
|
+
switch (state) {
|
|
48695
|
+
case "running": return {
|
|
48696
|
+
label: t("uiLibrary.dishwasher.running", "Running"),
|
|
48697
|
+
tone: "text-primary"
|
|
48698
|
+
};
|
|
48699
|
+
case "paused": return {
|
|
48700
|
+
label: t("uiLibrary.dishwasher.paused", "Paused"),
|
|
48701
|
+
tone: "text-warning"
|
|
48702
|
+
};
|
|
48703
|
+
case "finished": return {
|
|
48704
|
+
label: t("uiLibrary.dishwasher.finished", "Finished"),
|
|
48705
|
+
tone: "text-success"
|
|
48706
|
+
};
|
|
48707
|
+
case "error": return {
|
|
48708
|
+
label: t("uiLibrary.dishwasher.error", "Error"),
|
|
48709
|
+
tone: "text-destructive"
|
|
48710
|
+
};
|
|
48711
|
+
case "unavailable": return {
|
|
48712
|
+
label: t("uiLibrary.dishwasher.unavailable", "Unavailable"),
|
|
48713
|
+
tone: "text-foreground-subtle"
|
|
48714
|
+
};
|
|
48715
|
+
case "idle": return {
|
|
48716
|
+
label: t("uiLibrary.dishwasher.idle", "Idle"),
|
|
48717
|
+
tone: "text-foreground-subtle"
|
|
48718
|
+
};
|
|
48719
|
+
case void 0: return {
|
|
48720
|
+
label: t("uiLibrary.dishwasher.unknown", "Unknown"),
|
|
48721
|
+
tone: "text-foreground-subtle"
|
|
48722
|
+
};
|
|
48723
|
+
}
|
|
48724
|
+
}
|
|
48725
|
+
function consumableTone(level) {
|
|
48726
|
+
switch (level) {
|
|
48727
|
+
case "ok": return "text-success";
|
|
48728
|
+
case "low": return "text-warning";
|
|
48729
|
+
case "empty": return "text-destructive";
|
|
48730
|
+
case null: return "text-foreground-subtle";
|
|
48731
|
+
}
|
|
48732
|
+
}
|
|
48733
|
+
function useDishwasher(trpc, deviceId) {
|
|
48734
|
+
return useDeviceCapability({
|
|
48735
|
+
trpc,
|
|
48736
|
+
deviceId,
|
|
48737
|
+
cap: (d) => d.dishwasher,
|
|
48738
|
+
slice: (d) => d.state.dishwasher
|
|
48739
|
+
});
|
|
48740
|
+
}
|
|
48741
|
+
function DishwasherIconButton({ label, busy, active, onClick, children }) {
|
|
48742
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
48743
|
+
type: "button",
|
|
48744
|
+
"aria-label": label,
|
|
48745
|
+
title: label,
|
|
48746
|
+
disabled: busy,
|
|
48747
|
+
onClick: (e) => {
|
|
48748
|
+
e.stopPropagation();
|
|
48749
|
+
onClick();
|
|
48750
|
+
},
|
|
48751
|
+
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"),
|
|
48752
|
+
children
|
|
48753
|
+
});
|
|
48754
|
+
}
|
|
48755
|
+
function ConsumableBadge({ label, level }) {
|
|
48756
|
+
if (level === null) return null;
|
|
48757
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
48758
|
+
className: cn("text-xs font-medium capitalize", consumableTone(level)),
|
|
48759
|
+
children: [
|
|
48760
|
+
label,
|
|
48761
|
+
": ",
|
|
48762
|
+
level
|
|
48763
|
+
]
|
|
48764
|
+
});
|
|
48765
|
+
}
|
|
48766
|
+
function DishwasherGraphic({ state, progressPercent }) {
|
|
48767
|
+
const running = state === "running";
|
|
48768
|
+
const pct = progressPercent != null ? Math.max(0, Math.min(100, progressPercent)) : running ? 40 : 0;
|
|
48769
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48770
|
+
"aria-hidden": "true",
|
|
48771
|
+
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",
|
|
48772
|
+
style: { boxShadow: "inset 0 0 0 2px rgba(255,255,255,.06)" },
|
|
48773
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
48774
|
+
className: cn("absolute inset-x-0 bottom-0 bg-primary/25 transition-[height] duration-500", running && "animate-pulse"),
|
|
48775
|
+
style: { height: `${pct}%` }
|
|
48776
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
48777
|
+
className: "absolute inset-0 flex items-center justify-center text-foreground-subtle",
|
|
48778
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Waves, { className: "h-10 w-10 opacity-70" })
|
|
48779
|
+
})]
|
|
48780
|
+
});
|
|
48781
|
+
}
|
|
48782
|
+
function DishwasherPanel({ trpc, deviceId, layout, optimistic }) {
|
|
48783
|
+
const t = useUiText();
|
|
48784
|
+
const { cap, slice, available } = useDishwasher(trpc, deviceId);
|
|
48785
|
+
const { display, applyOptimistic } = useOptimisticSlice(slice, { enabled: optimistic ?? true });
|
|
48786
|
+
if (cap === void 0) return null;
|
|
48787
|
+
if (display === void 0) return null;
|
|
48788
|
+
const busy = !available;
|
|
48789
|
+
const optionTogglesLocked = display.childLock === true;
|
|
48790
|
+
const meta = dishwasherStateMeta(display.state, t);
|
|
48791
|
+
const running = display.state === "running";
|
|
48792
|
+
const paused = display.state === "paused";
|
|
48793
|
+
const press = (fn, patch) => {
|
|
48794
|
+
if (busy) return;
|
|
48795
|
+
applyOptimistic(patch);
|
|
48796
|
+
fn();
|
|
48797
|
+
};
|
|
48798
|
+
if (layout === "compact") {
|
|
48799
|
+
const saltWarn = display.saltLevel === "low" || display.saltLevel === "empty";
|
|
48800
|
+
const rinseWarn = display.rinseAidLevel === "low" || display.rinseAidLevel === "empty";
|
|
48801
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48802
|
+
className: "inline-flex items-center gap-1.5 max-w-full",
|
|
48803
|
+
children: [
|
|
48804
|
+
display.doorOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48805
|
+
className: "inline-flex text-warning shrink-0",
|
|
48806
|
+
title: t("uiLibrary.dishwasher.doorOpen", "Door open"),
|
|
48807
|
+
"aria-label": t("uiLibrary.dishwasher.doorOpen", "Door open"),
|
|
48808
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DoorOpen, { className: "h-3.5 w-3.5" })
|
|
48809
|
+
}),
|
|
48810
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48811
|
+
className: cn("text-xs font-medium tabular-nums shrink-0", meta.tone),
|
|
48812
|
+
children: meta.label
|
|
48813
|
+
}),
|
|
48814
|
+
display.program != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48815
|
+
className: "text-xs text-foreground-subtle truncate max-w-[7rem]",
|
|
48816
|
+
title: display.program,
|
|
48817
|
+
children: display.program
|
|
48818
|
+
}),
|
|
48819
|
+
display.remainingMinutes != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
48820
|
+
className: "inline-flex items-center gap-0.5 text-xs text-foreground-subtle tabular-nums shrink-0",
|
|
48821
|
+
children: [
|
|
48822
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Timer, { className: "h-3 w-3" }),
|
|
48823
|
+
display.remainingMinutes,
|
|
48824
|
+
"m"
|
|
48825
|
+
]
|
|
48826
|
+
}),
|
|
48827
|
+
saltWarn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48828
|
+
className: cn("text-[10px] font-medium shrink-0", consumableTone(display.saltLevel)),
|
|
48829
|
+
children: t("uiLibrary.dishwasher.salt", "Salt")
|
|
48830
|
+
}),
|
|
48831
|
+
rinseWarn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48832
|
+
className: cn("text-[10px] font-medium shrink-0", consumableTone(display.rinseAidLevel)),
|
|
48833
|
+
children: t("uiLibrary.dishwasher.rinseAid", "Rinse")
|
|
48834
|
+
}),
|
|
48835
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
48836
|
+
label: t("uiLibrary.dishwasher.start", "Start"),
|
|
48837
|
+
busy: busy || !display.remoteStartAllowed,
|
|
48838
|
+
active: running,
|
|
48839
|
+
onClick: () => press(() => cap.start({}), {
|
|
48840
|
+
state: "running",
|
|
48841
|
+
remoteStartAllowed: false
|
|
48842
|
+
}),
|
|
48843
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Play, { className: "h-3 w-3" })
|
|
48844
|
+
}),
|
|
48845
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
48846
|
+
label: t("uiLibrary.dishwasher.pause", "Pause"),
|
|
48847
|
+
busy: busy || !running,
|
|
48848
|
+
active: paused,
|
|
48849
|
+
onClick: () => press(() => cap.pause({}), { state: "paused" }),
|
|
48850
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pause, { className: "h-3 w-3" })
|
|
48851
|
+
})
|
|
48852
|
+
]
|
|
48853
|
+
});
|
|
48854
|
+
}
|
|
48855
|
+
const programs = display.availablePrograms;
|
|
48856
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48857
|
+
className: "flex flex-col gap-3 rounded-lg border border-border bg-surface p-4",
|
|
48858
|
+
children: [
|
|
48859
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48860
|
+
className: "flex items-start gap-4",
|
|
48861
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherGraphic, {
|
|
48862
|
+
state: display.state,
|
|
48863
|
+
progressPercent: display.progressPercent
|
|
48864
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48865
|
+
className: "flex min-w-0 flex-1 flex-col gap-2",
|
|
48866
|
+
children: [
|
|
48867
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48868
|
+
className: "flex flex-wrap items-center gap-2",
|
|
48869
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48870
|
+
className: cn("text-3xl font-semibold", meta.tone),
|
|
48871
|
+
children: meta.label
|
|
48872
|
+
}), !display.powerOn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48873
|
+
className: "text-xs font-medium text-foreground-subtle",
|
|
48874
|
+
children: t("uiLibrary.dishwasher.powerOff", "Power off")
|
|
48875
|
+
})]
|
|
48876
|
+
}),
|
|
48877
|
+
display.program != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48878
|
+
className: "text-sm text-foreground-subtle",
|
|
48879
|
+
children: display.program
|
|
48880
|
+
}),
|
|
48881
|
+
display.progressPercent != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48882
|
+
className: "flex items-center gap-2",
|
|
48883
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
48884
|
+
className: "h-1.5 flex-1 rounded bg-surface-hover overflow-hidden",
|
|
48885
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
48886
|
+
className: "h-full bg-primary transition-[width] duration-500",
|
|
48887
|
+
style: { width: `${Math.max(0, Math.min(100, display.progressPercent))}%` }
|
|
48888
|
+
})
|
|
48889
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
48890
|
+
className: "tabular-nums text-xs text-foreground-subtle",
|
|
48891
|
+
children: [Math.round(display.progressPercent), "%"]
|
|
48892
|
+
})]
|
|
48893
|
+
}),
|
|
48894
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48895
|
+
className: "flex flex-wrap gap-3",
|
|
48896
|
+
children: [
|
|
48897
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableBadge, {
|
|
48898
|
+
label: t("uiLibrary.dishwasher.salt", "Salt"),
|
|
48899
|
+
level: display.saltLevel
|
|
48900
|
+
}),
|
|
48901
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableBadge, {
|
|
48902
|
+
label: t("uiLibrary.dishwasher.rinseAid", "Rinse aid"),
|
|
48903
|
+
level: display.rinseAidLevel
|
|
48904
|
+
}),
|
|
48905
|
+
display.doorOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
48906
|
+
className: "inline-flex items-center gap-1 text-xs text-warning",
|
|
48907
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DoorOpen, { className: "h-3.5 w-3.5" }), t("uiLibrary.dishwasher.doorOpen", "Door open")]
|
|
48908
|
+
})
|
|
48909
|
+
]
|
|
48910
|
+
}),
|
|
48911
|
+
display.errorCode != null && display.errorCode !== 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48912
|
+
className: "inline-flex items-center gap-1 text-xs text-destructive",
|
|
48913
|
+
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 }) })]
|
|
48914
|
+
}),
|
|
48915
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48916
|
+
className: "flex flex-wrap items-center gap-2",
|
|
48917
|
+
children: [
|
|
48918
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
48919
|
+
label: t("uiLibrary.dishwasher.start", "Start"),
|
|
48920
|
+
busy: busy || !display.remoteStartAllowed,
|
|
48921
|
+
active: running,
|
|
48922
|
+
onClick: () => press(() => cap.start({}), {
|
|
48923
|
+
state: "running",
|
|
48924
|
+
remoteStartAllowed: false
|
|
48925
|
+
}),
|
|
48926
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Play, { className: "h-4 w-4" })
|
|
48927
|
+
}),
|
|
48928
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
48929
|
+
label: t("uiLibrary.dishwasher.pause", "Pause"),
|
|
48930
|
+
busy: busy || !running,
|
|
48931
|
+
active: paused,
|
|
48932
|
+
onClick: () => press(() => cap.pause({}), { state: "paused" }),
|
|
48933
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pause, { className: "h-4 w-4" })
|
|
48934
|
+
}),
|
|
48935
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
48936
|
+
label: t("uiLibrary.dishwasher.stop", "Stop"),
|
|
48937
|
+
busy,
|
|
48938
|
+
active: false,
|
|
48939
|
+
onClick: () => press(() => cap.stop({}), {
|
|
48940
|
+
state: "idle",
|
|
48941
|
+
remainingMinutes: null
|
|
48942
|
+
}),
|
|
48943
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Square, { className: "h-3.5 w-3.5" })
|
|
48944
|
+
}),
|
|
48945
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
48946
|
+
label: t("uiLibrary.dishwasher.power", "Power"),
|
|
48947
|
+
busy,
|
|
48948
|
+
active: display.powerOn,
|
|
48949
|
+
onClick: () => {
|
|
48950
|
+
const next = !display.powerOn;
|
|
48951
|
+
press(() => cap.setPower({ on: next }), { powerOn: next });
|
|
48952
|
+
},
|
|
48953
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Power, { className: "h-4 w-4" })
|
|
48954
|
+
})
|
|
48955
|
+
]
|
|
48956
|
+
})
|
|
48957
|
+
]
|
|
48958
|
+
})]
|
|
48959
|
+
}),
|
|
48960
|
+
programs.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
48961
|
+
className: "flex items-center gap-2 text-xs text-foreground-subtle",
|
|
48962
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
48963
|
+
className: "w-16 shrink-0",
|
|
48964
|
+
children: t("uiLibrary.dishwasher.program", "Program")
|
|
48965
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
48966
|
+
"aria-label": t("uiLibrary.dishwasher.program", "Program"),
|
|
48967
|
+
value: display.program ?? "",
|
|
48968
|
+
disabled: busy,
|
|
48969
|
+
onChange: (e) => {
|
|
48970
|
+
const program = e.target.value;
|
|
48971
|
+
if (!program) return;
|
|
48972
|
+
applyOptimistic({ program });
|
|
48973
|
+
cap.setProgram({ program });
|
|
48974
|
+
},
|
|
48975
|
+
onClick: (e) => {
|
|
48976
|
+
e.stopPropagation();
|
|
48977
|
+
},
|
|
48978
|
+
className: "flex-1 rounded border border-border bg-surface px-2 py-1 text-foreground disabled:opacity-50",
|
|
48979
|
+
children: [display.program == null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
48980
|
+
value: "",
|
|
48981
|
+
children: "—"
|
|
48982
|
+
}), programs.map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
48983
|
+
value: p,
|
|
48984
|
+
children: p
|
|
48985
|
+
}, p))]
|
|
48986
|
+
})]
|
|
48987
|
+
}),
|
|
48988
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48989
|
+
className: "flex flex-wrap gap-4 text-xs",
|
|
48990
|
+
children: [
|
|
48991
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
48992
|
+
label: t("uiLibrary.dishwasher.halfLoad", "Half load"),
|
|
48993
|
+
checked: display.halfLoad,
|
|
48994
|
+
busy: busy || optionTogglesLocked,
|
|
48995
|
+
onChange: (on) => {
|
|
48996
|
+
applyOptimistic({ halfLoad: on });
|
|
48997
|
+
cap.setHalfLoad({ on });
|
|
48998
|
+
}
|
|
48999
|
+
}),
|
|
49000
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
49001
|
+
label: t("uiLibrary.dishwasher.hygienePlus", "Hygiene plus"),
|
|
49002
|
+
checked: display.hygienePlus,
|
|
49003
|
+
busy: busy || optionTogglesLocked,
|
|
49004
|
+
onChange: (on) => {
|
|
49005
|
+
applyOptimistic({ hygienePlus: on });
|
|
49006
|
+
cap.setHygienePlus({ on });
|
|
49007
|
+
}
|
|
49008
|
+
}),
|
|
49009
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
49010
|
+
label: t("uiLibrary.dishwasher.extraDry", "Extra dry"),
|
|
49011
|
+
checked: display.extraDry,
|
|
49012
|
+
busy: busy || optionTogglesLocked,
|
|
49013
|
+
onChange: (on) => {
|
|
49014
|
+
applyOptimistic({ extraDry: on });
|
|
49015
|
+
cap.setExtraDry({ on });
|
|
49016
|
+
}
|
|
49017
|
+
}),
|
|
49018
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
49019
|
+
label: t("uiLibrary.dishwasher.intensiveZone", "Intensive zone"),
|
|
49020
|
+
checked: display.intensiveZone,
|
|
49021
|
+
busy: busy || optionTogglesLocked,
|
|
49022
|
+
onChange: (on) => {
|
|
49023
|
+
applyOptimistic({ intensiveZone: on });
|
|
49024
|
+
cap.setIntensiveZone({ on });
|
|
49025
|
+
}
|
|
49026
|
+
}),
|
|
49027
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
49028
|
+
label: t("uiLibrary.dishwasher.varioSpeedPlus", "VarioSpeed+"),
|
|
49029
|
+
checked: display.varioSpeedPlus,
|
|
49030
|
+
busy: busy || optionTogglesLocked,
|
|
49031
|
+
onChange: (on) => {
|
|
49032
|
+
applyOptimistic({ varioSpeedPlus: on });
|
|
49033
|
+
cap.setVarioSpeedPlus({ on });
|
|
49034
|
+
}
|
|
49035
|
+
}),
|
|
49036
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
49037
|
+
label: t("uiLibrary.dishwasher.childLock", "Child lock"),
|
|
49038
|
+
checked: display.childLock,
|
|
49039
|
+
busy,
|
|
49040
|
+
onChange: (on) => {
|
|
49041
|
+
applyOptimistic({ childLock: on });
|
|
49042
|
+
cap.setChildLock({ on });
|
|
49043
|
+
}
|
|
49044
|
+
})
|
|
49045
|
+
]
|
|
49046
|
+
})
|
|
49047
|
+
]
|
|
49048
|
+
});
|
|
49049
|
+
}
|
|
49050
|
+
function ToggleRow({ label, checked, busy, onChange }) {
|
|
49051
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
49052
|
+
className: "inline-flex items-center gap-2 cursor-pointer",
|
|
49053
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
49054
|
+
type: "checkbox",
|
|
49055
|
+
checked,
|
|
49056
|
+
disabled: busy,
|
|
49057
|
+
onChange: (e) => {
|
|
49058
|
+
onChange(e.target.checked);
|
|
49059
|
+
},
|
|
49060
|
+
onClick: (e) => {
|
|
49061
|
+
e.stopPropagation();
|
|
49062
|
+
},
|
|
49063
|
+
className: "rounded border-border"
|
|
49064
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label })]
|
|
49065
|
+
});
|
|
49066
|
+
}
|
|
49067
|
+
function DishwasherInlineControl(props) {
|
|
49068
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherPanel, {
|
|
49069
|
+
...props,
|
|
49070
|
+
layout: "compact"
|
|
49071
|
+
});
|
|
49072
|
+
}
|
|
49073
|
+
function DishwasherHeroCard(props) {
|
|
49074
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherPanel, {
|
|
49075
|
+
...props,
|
|
49076
|
+
layout: "full"
|
|
49077
|
+
});
|
|
49078
|
+
}
|
|
49079
|
+
//#endregion
|
|
49080
|
+
//#region src/composites/device-controls/heating-valve-readout.tsx
|
|
49081
|
+
function formatHeatingValvePosition(position) {
|
|
49082
|
+
return position !== null ? `${position}%` : "—";
|
|
49083
|
+
}
|
|
49084
|
+
function HeatingValveReadout({ trpc, deviceId, layout }) {
|
|
49085
|
+
const t = useUiText();
|
|
49086
|
+
const { cap, slice } = useDeviceCapability({
|
|
49087
|
+
trpc,
|
|
49088
|
+
deviceId,
|
|
49089
|
+
cap: (d) => d.heatingValve,
|
|
49090
|
+
slice: (d) => d.state.heatingValve
|
|
49091
|
+
});
|
|
49092
|
+
if (cap === void 0 || slice === void 0) return null;
|
|
49093
|
+
const label = t("uiLibrary.heatingValveReadout.valve", "Valve");
|
|
49094
|
+
const value = formatHeatingValvePosition(slice.position);
|
|
49095
|
+
if (layout === "compact") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
49096
|
+
className: "inline-flex items-center text-xs tabular-nums text-foreground-subtle",
|
|
49097
|
+
"aria-label": `${label} ${value}`,
|
|
49098
|
+
children: value
|
|
49099
|
+
});
|
|
49100
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49101
|
+
className: "flex items-center gap-2 text-xs tabular-nums text-foreground-subtle",
|
|
49102
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
49103
|
+
className: "font-medium text-foreground",
|
|
49104
|
+
children: value
|
|
49105
|
+
})]
|
|
49106
|
+
});
|
|
49107
|
+
}
|
|
49108
|
+
//#endregion
|
|
48550
49109
|
//#region src/composites/device-controls/ac-panel.tsx
|
|
49110
|
+
/** Modes that mean the device can cool, dry, or move air — an air conditioner. */
|
|
49111
|
+
var COOLING_MODES = new Set([
|
|
49112
|
+
"cool",
|
|
49113
|
+
"heat_cool",
|
|
49114
|
+
"dry",
|
|
49115
|
+
"fan_only"
|
|
49116
|
+
]);
|
|
49117
|
+
/** A heater offers heat (and maybe auto/off) and none of the cooling modes. */
|
|
49118
|
+
function climateOffersCooling(modes) {
|
|
49119
|
+
return modes.some((mode) => COOLING_MODES.has(mode));
|
|
49120
|
+
}
|
|
49121
|
+
/** Setpoint track bounds. Absent when the integration did not report both ends. */
|
|
49122
|
+
function climateSetpointBounds(status) {
|
|
49123
|
+
const min = status.minTemp;
|
|
49124
|
+
const max = status.maxTemp;
|
|
49125
|
+
if (typeof min !== "number" || typeof max !== "number") return null;
|
|
49126
|
+
if (!Number.isFinite(min) || !Number.isFinite(max) || max <= min) return null;
|
|
49127
|
+
return {
|
|
49128
|
+
min,
|
|
49129
|
+
max
|
|
49130
|
+
};
|
|
49131
|
+
}
|
|
49132
|
+
/** Map a 0–1 drag fraction onto the setpoint, snapped to `tempStep`. */
|
|
49133
|
+
function climateTargetFromFraction(fraction, bounds, tempStep) {
|
|
49134
|
+
const step = resolveClimateTempStep(tempStep);
|
|
49135
|
+
const clamped = Math.min(1, Math.max(0, fraction));
|
|
49136
|
+
const raw = bounds.min + clamped * (bounds.max - bounds.min);
|
|
49137
|
+
const steps = Math.round((raw - bounds.min) / step);
|
|
49138
|
+
const snapped = bounds.min + steps * step;
|
|
49139
|
+
const rounded = Math.round(snapped * 1e3) / 1e3;
|
|
49140
|
+
return Math.min(bounds.max, Math.max(bounds.min, rounded));
|
|
49141
|
+
}
|
|
48551
49142
|
/** AC-appropriate short mode labels. Exhaustive over HvacMode. */
|
|
48552
49143
|
function modeLabel(t, mode) {
|
|
48553
49144
|
switch (mode) {
|
|
@@ -48581,9 +49172,6 @@ var MODE_ACCENT = {
|
|
|
48581
49172
|
dry: "#22d3ee",
|
|
48582
49173
|
off: "#a1a1aa"
|
|
48583
49174
|
};
|
|
48584
|
-
/** Practical AC setpoint range (Celsius) for the target range track. */
|
|
48585
|
-
var TARGET_MIN = 16;
|
|
48586
|
-
var TARGET_MAX = 30;
|
|
48587
49175
|
/** Title-case a snake/lower token for a selector option ('medium_low' → 'Medium
|
|
48588
49176
|
* low', 'turbo' → 'Turbo'). Shared by the fan + preset selectors. */
|
|
48589
49177
|
function fanLabel(token) {
|
|
@@ -48670,7 +49258,7 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48670
49258
|
onClick: (e) => {
|
|
48671
49259
|
e.stopPropagation();
|
|
48672
49260
|
if (!busy && display.target !== null) {
|
|
48673
|
-
const next = display.target -
|
|
49261
|
+
const next = nextClimateTarget(display.target, -1, display);
|
|
48674
49262
|
applyOptimistic({ target: next });
|
|
48675
49263
|
cap.setTarget({ target: next });
|
|
48676
49264
|
}
|
|
@@ -48684,7 +49272,7 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48684
49272
|
onClick: (e) => {
|
|
48685
49273
|
e.stopPropagation();
|
|
48686
49274
|
if (!busy && display.target !== null) {
|
|
48687
|
-
const next = display.target
|
|
49275
|
+
const next = nextClimateTarget(display.target, 1, display);
|
|
48688
49276
|
applyOptimistic({ target: next });
|
|
48689
49277
|
cap.setTarget({ target: next });
|
|
48690
49278
|
}
|
|
@@ -48714,6 +49302,8 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48714
49302
|
});
|
|
48715
49303
|
const accent = MODE_ACCENT[display.mode];
|
|
48716
49304
|
const on = display.mode !== "off";
|
|
49305
|
+
const heatingOnly = !climateOffersCooling(display.availableModes);
|
|
49306
|
+
const setpointBounds = climateSetpointBounds(display);
|
|
48717
49307
|
const CurrentPresetIcon = presetIcon(display.preset);
|
|
48718
49308
|
const boost = display.preset === "turbo";
|
|
48719
49309
|
const period = Math.max(.55, airflowPeriodSec(display.fanMode) * (boost ? .6 : 1));
|
|
@@ -48730,11 +49320,12 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48730
49320
|
children: [
|
|
48731
49321
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
48732
49322
|
className: "pointer-events-none absolute inset-0 opacity-60",
|
|
48733
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AirflowViz, {
|
|
49323
|
+
children: heatingOnly && !on ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RadiatorFins, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AirflowViz, {
|
|
48734
49324
|
mode: display.mode,
|
|
48735
49325
|
accent,
|
|
48736
49326
|
periodSec: period,
|
|
48737
|
-
swaying
|
|
49327
|
+
swaying,
|
|
49328
|
+
forceRising: heatingOnly
|
|
48738
49329
|
})
|
|
48739
49330
|
}),
|
|
48740
49331
|
/* @__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 +49343,7 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48752
49343
|
children: [
|
|
48753
49344
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModeIcon, {
|
|
48754
49345
|
mode: display.mode,
|
|
49346
|
+
heatingOnly,
|
|
48755
49347
|
className: "h-3.5 w-3.5"
|
|
48756
49348
|
}),
|
|
48757
49349
|
modeLabel(t, display.mode),
|
|
@@ -48778,7 +49370,7 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48778
49370
|
disabled: busy,
|
|
48779
49371
|
onClick: () => {
|
|
48780
49372
|
if (busy || display.target === null) return;
|
|
48781
|
-
const next = display.target -
|
|
49373
|
+
const next = nextClimateTarget(display.target, -1, display);
|
|
48782
49374
|
applyOptimistic({ target: next });
|
|
48783
49375
|
cap.setTarget({ target: next });
|
|
48784
49376
|
}
|
|
@@ -48800,19 +49392,29 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48800
49392
|
disabled: busy,
|
|
48801
49393
|
onClick: () => {
|
|
48802
49394
|
if (busy || display.target === null) return;
|
|
48803
|
-
const next = display.target
|
|
49395
|
+
const next = nextClimateTarget(display.target, 1, display);
|
|
48804
49396
|
applyOptimistic({ target: next });
|
|
48805
49397
|
cap.setTarget({ target: next });
|
|
48806
49398
|
}
|
|
48807
49399
|
})
|
|
48808
49400
|
]
|
|
48809
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
48810
|
-
className: "relative z-10 w-
|
|
49401
|
+
}), setpointBounds !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49402
|
+
className: "relative z-10 w-full px-1",
|
|
48811
49403
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RangeTrack, {
|
|
48812
49404
|
value: display.target,
|
|
48813
|
-
min:
|
|
48814
|
-
max:
|
|
48815
|
-
|
|
49405
|
+
min: setpointBounds.min,
|
|
49406
|
+
max: setpointBounds.max,
|
|
49407
|
+
step: display.tempStep,
|
|
49408
|
+
accent,
|
|
49409
|
+
disabled: busy,
|
|
49410
|
+
label: t("uiLibrary.acPanel.setpoint", "Setpoint"),
|
|
49411
|
+
onPreview: (next) => {
|
|
49412
|
+
applyOptimistic({ target: next });
|
|
49413
|
+
},
|
|
49414
|
+
onCommit: (next) => {
|
|
49415
|
+
applyOptimistic({ target: next });
|
|
49416
|
+
cap.setTarget({ target: next });
|
|
49417
|
+
}
|
|
48816
49418
|
})
|
|
48817
49419
|
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48818
49420
|
className: "relative z-10 flex items-baseline gap-0.5",
|
|
@@ -48836,6 +49438,7 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
48836
49438
|
className: "flex",
|
|
48837
49439
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModeIcon, {
|
|
48838
49440
|
mode: display.mode,
|
|
49441
|
+
heatingOnly,
|
|
48839
49442
|
className: "h-4 w-4"
|
|
48840
49443
|
})
|
|
48841
49444
|
}),
|
|
@@ -48919,9 +49522,8 @@ function Field({ label, children }) {
|
|
|
48919
49522
|
}), children]
|
|
48920
49523
|
});
|
|
48921
49524
|
}
|
|
48922
|
-
function ModeIcon({ mode, className }) {
|
|
48923
|
-
|
|
48924
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
49525
|
+
function ModeIcon({ mode, heatingOnly = false, className }) {
|
|
49526
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(heatingOnly && mode === "heat" ? Heater : MODE_ICON[mode], {
|
|
48925
49527
|
className,
|
|
48926
49528
|
"aria-hidden": true
|
|
48927
49529
|
});
|
|
@@ -48971,11 +49573,89 @@ function RoundStep({ label, symbol, disabled, onClick }) {
|
|
|
48971
49573
|
children: symbol
|
|
48972
49574
|
});
|
|
48973
49575
|
}
|
|
48974
|
-
/**
|
|
48975
|
-
function RangeTrack({ value, min, max, accent }) {
|
|
48976
|
-
const
|
|
49576
|
+
/** Setpoint track. Drag previews; the command is sent when the pointer is released. */
|
|
49577
|
+
function RangeTrack({ value, min, max, step, accent, disabled, label, onPreview, onCommit }) {
|
|
49578
|
+
const trackRef = (0, react$1.useRef)(null);
|
|
49579
|
+
const draggingRef = (0, react$1.useRef)(false);
|
|
49580
|
+
const [draft, setDraft] = (0, react$1.useState)(null);
|
|
49581
|
+
const shown = draft ?? value;
|
|
49582
|
+
const pct = `${Math.min(1, Math.max(0, (shown - min) / (max - min))) * 100}%`;
|
|
49583
|
+
const tempAt = (clientX) => {
|
|
49584
|
+
const el = trackRef.current;
|
|
49585
|
+
if (el === null) return value;
|
|
49586
|
+
const rect = el.getBoundingClientRect();
|
|
49587
|
+
if (rect.width <= 0) return value;
|
|
49588
|
+
return climateTargetFromFraction((clientX - rect.left) / rect.width, {
|
|
49589
|
+
min,
|
|
49590
|
+
max
|
|
49591
|
+
}, step);
|
|
49592
|
+
};
|
|
49593
|
+
const onPointerDown = (event) => {
|
|
49594
|
+
if (disabled) return;
|
|
49595
|
+
event.stopPropagation();
|
|
49596
|
+
event.preventDefault();
|
|
49597
|
+
draggingRef.current = true;
|
|
49598
|
+
if (typeof event.currentTarget.setPointerCapture === "function") event.currentTarget.setPointerCapture(event.pointerId);
|
|
49599
|
+
const next = tempAt(event.clientX);
|
|
49600
|
+
setDraft(next);
|
|
49601
|
+
onPreview(next);
|
|
49602
|
+
};
|
|
49603
|
+
const onPointerMove = (event) => {
|
|
49604
|
+
if (disabled || !draggingRef.current) return;
|
|
49605
|
+
event.stopPropagation();
|
|
49606
|
+
const next = tempAt(event.clientX);
|
|
49607
|
+
setDraft(next);
|
|
49608
|
+
onPreview(next);
|
|
49609
|
+
};
|
|
49610
|
+
const endDrag = (event) => {
|
|
49611
|
+
if (!draggingRef.current) return;
|
|
49612
|
+
event.stopPropagation();
|
|
49613
|
+
draggingRef.current = false;
|
|
49614
|
+
const next = tempAt(event.clientX);
|
|
49615
|
+
setDraft(null);
|
|
49616
|
+
if (event.currentTarget.hasPointerCapture?.(event.pointerId) === true) event.currentTarget.releasePointerCapture(event.pointerId);
|
|
49617
|
+
onCommit(next);
|
|
49618
|
+
};
|
|
49619
|
+
const onKeyDown = (event) => {
|
|
49620
|
+
if (disabled) return;
|
|
49621
|
+
const current = draft ?? value;
|
|
49622
|
+
if (event.key === "ArrowRight" || event.key === "ArrowUp") {
|
|
49623
|
+
event.preventDefault();
|
|
49624
|
+
onCommit(nextClimateTarget(current, 1, {
|
|
49625
|
+
tempStep: step,
|
|
49626
|
+
minTemp: min,
|
|
49627
|
+
maxTemp: max
|
|
49628
|
+
}));
|
|
49629
|
+
} else if (event.key === "ArrowLeft" || event.key === "ArrowDown") {
|
|
49630
|
+
event.preventDefault();
|
|
49631
|
+
onCommit(nextClimateTarget(current, -1, {
|
|
49632
|
+
tempStep: step,
|
|
49633
|
+
minTemp: min,
|
|
49634
|
+
maxTemp: max
|
|
49635
|
+
}));
|
|
49636
|
+
} else if (event.key === "Home") {
|
|
49637
|
+
event.preventDefault();
|
|
49638
|
+
onCommit(min);
|
|
49639
|
+
} else if (event.key === "End") {
|
|
49640
|
+
event.preventDefault();
|
|
49641
|
+
onCommit(max);
|
|
49642
|
+
}
|
|
49643
|
+
};
|
|
48977
49644
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48978
|
-
|
|
49645
|
+
ref: trackRef,
|
|
49646
|
+
role: "slider",
|
|
49647
|
+
"aria-label": label,
|
|
49648
|
+
"aria-valuemin": min,
|
|
49649
|
+
"aria-valuemax": max,
|
|
49650
|
+
"aria-valuenow": shown,
|
|
49651
|
+
"aria-disabled": disabled,
|
|
49652
|
+
tabIndex: disabled ? -1 : 0,
|
|
49653
|
+
onPointerDown,
|
|
49654
|
+
onPointerMove,
|
|
49655
|
+
onPointerUp: endDrag,
|
|
49656
|
+
onPointerCancel: endDrag,
|
|
49657
|
+
onKeyDown,
|
|
49658
|
+
className: cn("mt-1 w-full touch-none select-none py-2", disabled ? "cursor-not-allowed opacity-40" : "cursor-ew-resize"),
|
|
48979
49659
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
48980
49660
|
className: "relative h-1.5 w-full rounded-full bg-surface-subtle",
|
|
48981
49661
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -48985,7 +49665,7 @@ function RangeTrack({ value, min, max, accent }) {
|
|
|
48985
49665
|
background: accent
|
|
48986
49666
|
}
|
|
48987
49667
|
}), /* @__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",
|
|
49668
|
+
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
49669
|
style: {
|
|
48990
49670
|
left: pct,
|
|
48991
49671
|
background: accent
|
|
@@ -49002,7 +49682,7 @@ function RangeTrack({ value, min, max, accent }) {
|
|
|
49002
49682
|
* vent; every active mode renders drifting particles that FALL (cool/dry/auto)
|
|
49003
49683
|
* or RISE (heat), with a horizontal sweep overlaid when a swing axis is active.
|
|
49004
49684
|
*/
|
|
49005
|
-
function AirflowViz({ mode, accent, periodSec, swaying }) {
|
|
49685
|
+
function AirflowViz({ mode, accent, periodSec, swaying, forceRising = false }) {
|
|
49006
49686
|
const columns = [
|
|
49007
49687
|
0,
|
|
49008
49688
|
1,
|
|
@@ -49011,7 +49691,7 @@ function AirflowViz({ mode, accent, periodSec, swaying }) {
|
|
|
49011
49691
|
4,
|
|
49012
49692
|
5
|
|
49013
49693
|
];
|
|
49014
|
-
const rising = mode === "heat";
|
|
49694
|
+
const rising = forceRising || mode === "heat";
|
|
49015
49695
|
const isDroplet = mode === "dry";
|
|
49016
49696
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49017
49697
|
"aria-hidden": true,
|
|
@@ -49027,6 +49707,7 @@ function AirflowViz({ mode, accent, periodSec, swaying }) {
|
|
|
49027
49707
|
size: 56
|
|
49028
49708
|
})
|
|
49029
49709
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49710
|
+
"data-testid": forceRising ? "climate-heat-rise" : void 0,
|
|
49030
49711
|
className: cn("acv-sway-wrap absolute inset-0", swaying && "acv-sway"),
|
|
49031
49712
|
style: swaying ? { animation: "acv-sway 2.6s ease-in-out infinite" } : void 0,
|
|
49032
49713
|
children: columns.map((i) => {
|
|
@@ -49049,6 +49730,23 @@ function AirflowViz({ mode, accent, periodSec, swaying }) {
|
|
|
49049
49730
|
})
|
|
49050
49731
|
});
|
|
49051
49732
|
}
|
|
49733
|
+
/** Still radiator fins — the off state of a heating-only climate. */
|
|
49734
|
+
function RadiatorFins() {
|
|
49735
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49736
|
+
"data-testid": "climate-radiator",
|
|
49737
|
+
"aria-hidden": true,
|
|
49738
|
+
className: "flex h-full items-end justify-center gap-1.5 px-8 pb-3 opacity-40",
|
|
49739
|
+
children: [
|
|
49740
|
+
0,
|
|
49741
|
+
1,
|
|
49742
|
+
2,
|
|
49743
|
+
3,
|
|
49744
|
+
4,
|
|
49745
|
+
5,
|
|
49746
|
+
6
|
|
49747
|
+
].map((i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "h-10 w-1.5 rounded-t-full bg-foreground-subtle" }, i))
|
|
49748
|
+
});
|
|
49749
|
+
}
|
|
49052
49750
|
/** Big spinning fan for `fan_only` mode. */
|
|
49053
49751
|
function FanBlades({ color, periodSec, size }) {
|
|
49054
49752
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
@@ -49215,16 +49913,33 @@ var MODE_ARC_COLOR = {
|
|
|
49215
49913
|
*/
|
|
49216
49914
|
function ThermostatHeroCard({ trpc, deviceId, features: _features, optimistic }) {
|
|
49217
49915
|
const t = useUiText();
|
|
49218
|
-
const
|
|
49916
|
+
const climate = useDeviceCapability({
|
|
49219
49917
|
trpc,
|
|
49220
49918
|
deviceId,
|
|
49221
49919
|
cap: (d) => d.climateControl,
|
|
49222
49920
|
slice: (d) => d.state.climateControl
|
|
49223
49921
|
});
|
|
49224
|
-
const
|
|
49225
|
-
|
|
49226
|
-
|
|
49227
|
-
|
|
49922
|
+
const valve = useDeviceCapability({
|
|
49923
|
+
trpc,
|
|
49924
|
+
deviceId,
|
|
49925
|
+
cap: (d) => d.heatingValve,
|
|
49926
|
+
slice: (d) => d.state.heatingValve
|
|
49927
|
+
});
|
|
49928
|
+
const { display, applyOptimistic } = useOptimisticSlice(climate.slice, { enabled: optimistic ?? true });
|
|
49929
|
+
const hasClimate = climate.cap !== void 0 && display !== void 0;
|
|
49930
|
+
const hasValve = valve.cap !== void 0;
|
|
49931
|
+
if (!hasClimate && !hasValve) return null;
|
|
49932
|
+
if (!hasClimate && hasValve) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49933
|
+
className: "flex flex-col items-stretch gap-3 rounded-lg border border-border bg-surface p-4",
|
|
49934
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeatingValveReadout, {
|
|
49935
|
+
trpc,
|
|
49936
|
+
deviceId,
|
|
49937
|
+
layout: "full"
|
|
49938
|
+
})
|
|
49939
|
+
});
|
|
49940
|
+
if (climate.cap === void 0 || display === void 0) return null;
|
|
49941
|
+
const cap = climate.cap;
|
|
49942
|
+
const busy = !climate.available;
|
|
49228
49943
|
const color = MODE_ARC_COLOR[display.mode];
|
|
49229
49944
|
const isRange = display.mode === "heat_cool" || display.targetLow !== null && display.targetHigh !== null;
|
|
49230
49945
|
const min = DEFAULT_TEMP_MIN;
|
|
@@ -49235,132 +49950,140 @@ function ThermostatHeroCard({ trpc, deviceId, features: _features, optimistic })
|
|
|
49235
49950
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49236
49951
|
"data-mode": display.mode,
|
|
49237
49952
|
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, {
|
|
49953
|
+
children: [
|
|
49954
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49955
|
+
className: "flex flex-col items-center gap-3",
|
|
49956
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
49957
|
+
className: "relative",
|
|
49958
|
+
style: {
|
|
49959
|
+
width: GAUGE_SIZE,
|
|
49960
|
+
height: GAUGE_SIZE
|
|
49961
|
+
},
|
|
49962
|
+
children: [
|
|
49963
|
+
isRange ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeRing, {
|
|
49964
|
+
mode: "band",
|
|
49965
|
+
band: {
|
|
49966
|
+
from: fractionLow,
|
|
49967
|
+
to: fractionHigh
|
|
49968
|
+
},
|
|
49969
|
+
color
|
|
49970
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeRing, {
|
|
49971
|
+
mode: "fill",
|
|
49294
49972
|
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
|
-
]
|
|
49973
|
+
color
|
|
49974
|
+
}),
|
|
49975
|
+
isRange ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49976
|
+
"data-testid": "arc-knob",
|
|
49977
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArcKnob, {
|
|
49978
|
+
fraction: fractionLow,
|
|
49979
|
+
color,
|
|
49980
|
+
knobTopPx: THERMOSTAT_KNOB_TOP_PX,
|
|
49981
|
+
onDragFraction: (f) => {
|
|
49982
|
+
if (busy || display.targetHigh === null) return;
|
|
49983
|
+
const next = Math.min(fractionToTemp(f, min, max), display.targetHigh);
|
|
49984
|
+
applyOptimistic({ targetLow: next });
|
|
49985
|
+
cap.setTargetRange({
|
|
49986
|
+
targetLow: next,
|
|
49987
|
+
targetHigh: display.targetHigh
|
|
49988
|
+
});
|
|
49989
|
+
}
|
|
49330
49990
|
})
|
|
49331
|
-
|
|
49991
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
49992
|
+
"data-testid": "arc-knob",
|
|
49993
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArcKnob, {
|
|
49994
|
+
fraction: fractionHigh,
|
|
49995
|
+
color,
|
|
49996
|
+
knobTopPx: THERMOSTAT_KNOB_TOP_PX,
|
|
49997
|
+
onDragFraction: (f) => {
|
|
49998
|
+
if (busy || display.targetLow === null) return;
|
|
49999
|
+
const next = Math.max(fractionToTemp(f, min, max), display.targetLow);
|
|
50000
|
+
applyOptimistic({ targetHigh: next });
|
|
50001
|
+
cap.setTargetRange({
|
|
50002
|
+
targetLow: display.targetLow,
|
|
50003
|
+
targetHigh: next
|
|
50004
|
+
});
|
|
50005
|
+
}
|
|
50006
|
+
})
|
|
50007
|
+
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
50008
|
+
"data-testid": "arc-knob",
|
|
50009
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArcKnob, {
|
|
50010
|
+
fraction: singleFraction,
|
|
50011
|
+
color,
|
|
50012
|
+
knobTopPx: THERMOSTAT_KNOB_TOP_PX,
|
|
50013
|
+
onDragFraction: (f) => {
|
|
50014
|
+
if (busy) return;
|
|
50015
|
+
const next = fractionToTemp(f, min, max);
|
|
50016
|
+
applyOptimistic({ target: next });
|
|
50017
|
+
cap.setTarget({ target: next });
|
|
50018
|
+
}
|
|
50019
|
+
})
|
|
50020
|
+
}),
|
|
50021
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
50022
|
+
className: "absolute inset-0 flex flex-col items-center justify-center gap-0.5 pointer-events-none",
|
|
50023
|
+
children: [
|
|
50024
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
50025
|
+
"data-testid": "current-temp",
|
|
50026
|
+
className: "text-4xl font-bold tabular-nums text-foreground leading-none",
|
|
50027
|
+
children: [display.currentTemp !== null ? display.currentTemp : "—", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
50028
|
+
className: "text-lg text-foreground-subtle font-normal ml-0.5",
|
|
50029
|
+
children: "°"
|
|
50030
|
+
})]
|
|
50031
|
+
}),
|
|
50032
|
+
!isRange && display.target !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
50033
|
+
className: "text-xs font-semibold",
|
|
50034
|
+
style: { color },
|
|
50035
|
+
children: t("uiLibrary.thermostatHeroCard.setTo", "set {{value}}°", { value: display.target })
|
|
50036
|
+
}),
|
|
50037
|
+
isRange && display.targetLow !== null && display.targetHigh !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
50038
|
+
className: "text-xs font-semibold",
|
|
50039
|
+
style: { color },
|
|
50040
|
+
children: [
|
|
50041
|
+
display.targetLow,
|
|
50042
|
+
"° – ",
|
|
50043
|
+
display.targetHigh,
|
|
50044
|
+
"°"
|
|
50045
|
+
]
|
|
50046
|
+
})
|
|
50047
|
+
]
|
|
50048
|
+
})
|
|
50049
|
+
]
|
|
50050
|
+
}), !isRange && display.target !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
50051
|
+
className: "w-[200px]",
|
|
50052
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stepper, {
|
|
50053
|
+
value: `${display.target}°`,
|
|
50054
|
+
onDelta: (d) => {
|
|
50055
|
+
if (busy || display.target === null) return;
|
|
50056
|
+
const next = display.target + d;
|
|
50057
|
+
applyOptimistic({ target: next });
|
|
50058
|
+
cap.setTarget({ target: next });
|
|
50059
|
+
}
|
|
49332
50060
|
})
|
|
49333
|
-
]
|
|
49334
|
-
}),
|
|
49335
|
-
|
|
49336
|
-
|
|
49337
|
-
|
|
49338
|
-
|
|
49339
|
-
|
|
49340
|
-
|
|
49341
|
-
|
|
49342
|
-
|
|
50061
|
+
})]
|
|
50062
|
+
}),
|
|
50063
|
+
hasValve ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeatingValveReadout, {
|
|
50064
|
+
trpc,
|
|
50065
|
+
deviceId,
|
|
50066
|
+
layout: "full"
|
|
50067
|
+
}) : null,
|
|
50068
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
50069
|
+
"aria-label": t("uiLibrary.thermostatHeroCard.mode", "Mode"),
|
|
50070
|
+
value: display.mode,
|
|
50071
|
+
disabled: busy,
|
|
50072
|
+
onChange: (e) => {
|
|
50073
|
+
e.stopPropagation();
|
|
50074
|
+
const mode = e.target.value;
|
|
50075
|
+
if (!busy && mode) {
|
|
50076
|
+
applyOptimistic({ mode: display.availableModes.find((m) => m === mode) ?? display.mode });
|
|
50077
|
+
cap.setMode({ mode: display.availableModes.find((m) => m === mode) ?? display.mode });
|
|
49343
50078
|
}
|
|
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
|
-
})]
|
|
50079
|
+
},
|
|
50080
|
+
className: cn("w-full rounded border border-border bg-surface px-3 py-1.5 text-sm", busy && "opacity-40 cursor-not-allowed"),
|
|
50081
|
+
children: display.availableModes.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
50082
|
+
value: m,
|
|
50083
|
+
children: m
|
|
50084
|
+
}, m))
|
|
50085
|
+
})
|
|
50086
|
+
]
|
|
49364
50087
|
});
|
|
49365
50088
|
}
|
|
49366
50089
|
//#endregion
|
|
@@ -50868,14 +51591,32 @@ function useClimateCapGuard(trpc, deviceId) {
|
|
|
50868
51591
|
});
|
|
50869
51592
|
return cap !== void 0;
|
|
50870
51593
|
}
|
|
50871
|
-
function
|
|
50872
|
-
|
|
50873
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClimatePanel, {
|
|
51594
|
+
function useHeatingValveCapGuard(trpc, deviceId) {
|
|
51595
|
+
const { cap } = useDeviceCapability({
|
|
50874
51596
|
trpc,
|
|
50875
51597
|
deviceId,
|
|
50876
|
-
|
|
50877
|
-
|
|
50878
|
-
|
|
51598
|
+
cap: (d) => d.heatingValve,
|
|
51599
|
+
slice: (d) => d.state.heatingValve
|
|
51600
|
+
});
|
|
51601
|
+
return cap !== void 0;
|
|
51602
|
+
}
|
|
51603
|
+
function ThermostatInlineControl({ trpc, deviceId, features, optimistic }) {
|
|
51604
|
+
const hasClimate = useClimateCapGuard(trpc, deviceId);
|
|
51605
|
+
const hasValve = useHeatingValveCapGuard(trpc, deviceId);
|
|
51606
|
+
if (!hasClimate && !hasValve) return null;
|
|
51607
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
51608
|
+
className: "inline-flex items-center gap-2",
|
|
51609
|
+
children: [hasClimate ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClimatePanel, {
|
|
51610
|
+
trpc,
|
|
51611
|
+
deviceId,
|
|
51612
|
+
features,
|
|
51613
|
+
layout: "compact",
|
|
51614
|
+
optimistic
|
|
51615
|
+
}) : null, hasValve ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeatingValveReadout, {
|
|
51616
|
+
trpc,
|
|
51617
|
+
deviceId,
|
|
51618
|
+
layout: "compact"
|
|
51619
|
+
}) : null]
|
|
50879
51620
|
});
|
|
50880
51621
|
}
|
|
50881
51622
|
function AcInlineControl({ trpc, deviceId, features, optimistic }) {
|
|
@@ -51040,6 +51781,10 @@ var CONTROL_KIND_COMPONENTS = {
|
|
|
51040
51781
|
InlineControl: PetFeederInlineControl,
|
|
51041
51782
|
HeroCard: PetFeederHeroCard
|
|
51042
51783
|
},
|
|
51784
|
+
dishwasher: {
|
|
51785
|
+
InlineControl: DishwasherInlineControl,
|
|
51786
|
+
HeroCard: DishwasherHeroCard
|
|
51787
|
+
},
|
|
51043
51788
|
script: {
|
|
51044
51789
|
InlineControl: ScriptInlineControl,
|
|
51045
51790
|
HeroCard: ScriptHeroCard
|
|
@@ -51116,7 +51861,8 @@ var CONTROL_FILLS = {
|
|
|
51116
51861
|
[_camstack_types.DeviceType.Weather]: false,
|
|
51117
51862
|
[_camstack_types.DeviceType.Container]: false,
|
|
51118
51863
|
[_camstack_types.DeviceType.Image]: false,
|
|
51119
|
-
[_camstack_types.DeviceType.PetFeeder]: false
|
|
51864
|
+
[_camstack_types.DeviceType.PetFeeder]: false,
|
|
51865
|
+
[_camstack_types.DeviceType.Dishwasher]: false
|
|
51120
51866
|
};
|
|
51121
51867
|
var DEVICE_TYPE_VALUES = new Set(Object.values(_camstack_types.DeviceType));
|
|
51122
51868
|
/**
|
|
@@ -63964,6 +64710,7 @@ exports.Hammer = Hammer;
|
|
|
63964
64710
|
exports.Hand = Hand;
|
|
63965
64711
|
exports.HardDrive = HardDrive;
|
|
63966
64712
|
exports.Hash = Hash;
|
|
64713
|
+
exports.Heater = Heater;
|
|
63967
64714
|
exports.HlsVideo = HlsVideo;
|
|
63968
64715
|
exports.House = House;
|
|
63969
64716
|
exports.HoverZoomImage = HoverZoomImage;
|
|
@@ -64239,6 +64986,7 @@ exports.ThemeProvider = require_theme_index.ThemeProvider$1;
|
|
|
64239
64986
|
exports.Thermometer = Thermometer;
|
|
64240
64987
|
exports.ThermostatHeroCard = ThermostatHeroCard;
|
|
64241
64988
|
exports.ThermostatInlineControl = ThermostatInlineControl;
|
|
64989
|
+
exports.Timer = Timer;
|
|
64242
64990
|
exports.TimezoneSelector = TimezoneSelector;
|
|
64243
64991
|
exports.ToggleLeft = ToggleLeft;
|
|
64244
64992
|
exports.ToggleRight = ToggleRight;
|
|
@@ -64919,6 +65667,19 @@ exports.useDeviceStateSlice$1 = useDeviceStateSlice;
|
|
|
64919
65667
|
exports.useDeviceStatusGetStatus = useDeviceStatusGetStatus;
|
|
64920
65668
|
exports.useDeviceWebrtc = useDeviceWebrtc;
|
|
64921
65669
|
exports.useDevices = useDevices;
|
|
65670
|
+
exports.useDishwasherGetStatus = useDishwasherGetStatus;
|
|
65671
|
+
exports.useDishwasherPause = useDishwasherPause;
|
|
65672
|
+
exports.useDishwasherSetChildLock = useDishwasherSetChildLock;
|
|
65673
|
+
exports.useDishwasherSetExtraDry = useDishwasherSetExtraDry;
|
|
65674
|
+
exports.useDishwasherSetHalfLoad = useDishwasherSetHalfLoad;
|
|
65675
|
+
exports.useDishwasherSetHygienePlus = useDishwasherSetHygienePlus;
|
|
65676
|
+
exports.useDishwasherSetIntensiveZone = useDishwasherSetIntensiveZone;
|
|
65677
|
+
exports.useDishwasherSetOption = useDishwasherSetOption;
|
|
65678
|
+
exports.useDishwasherSetPower = useDishwasherSetPower;
|
|
65679
|
+
exports.useDishwasherSetProgram = useDishwasherSetProgram;
|
|
65680
|
+
exports.useDishwasherSetVarioSpeedPlus = useDishwasherSetVarioSpeedPlus;
|
|
65681
|
+
exports.useDishwasherStart = useDishwasherStart;
|
|
65682
|
+
exports.useDishwasherStop = useDishwasherStop;
|
|
64922
65683
|
exports.useDisplayIconResolver = useDisplayIconResolver;
|
|
64923
65684
|
exports.useDoorbellEvents = useDoorbellEvents;
|
|
64924
65685
|
exports.useDoorbellGetStatus = useDoorbellGetStatus;
|
|
@@ -64951,6 +65712,7 @@ exports.useFanControlSetPreset = useFanControlSetPreset;
|
|
|
64951
65712
|
exports.useFeatureProbeGetStatus = useFeatureProbeGetStatus;
|
|
64952
65713
|
exports.useFloodGetStatus = useFloodGetStatus;
|
|
64953
65714
|
exports.useGasGetStatus = useGasGetStatus;
|
|
65715
|
+
exports.useHeatingValveGetStatus = useHeatingValveGetStatus;
|
|
64954
65716
|
exports.useHumidifierGetStatus = useHumidifierGetStatus;
|
|
64955
65717
|
exports.useHumidifierSetMode = useHumidifierSetMode;
|
|
64956
65718
|
exports.useHumidifierSetOn = useHumidifierSetOn;
|