@camstack/ui-library 1.2.341 → 1.2.343
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/composites/battery-badge.d.ts +1 -1
- package/dist/composites/network-link-badge.d.ts +1 -1
- package/dist/composites/option-select/option-select-model.d.ts +6 -0
- package/dist/hooks/use-device-network-link.d.ts +2 -2
- package/dist/index.cjs +69 -57
- package/dist/index.js +69 -57
- package/dist/primitives/dropdown.d.ts +11 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.343] - 2026-10-28
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Device value fields are named after their quantity instead of their unit; run the quantity-field migration after updating
|
|
15
|
+
|
|
16
|
+
## [1.2.342] - 2026-10-28
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Option lists can group options under headings, and a dropdown can be opened and closed from outside
|
|
21
|
+
|
|
10
22
|
## [1.2.341] - 2026-10-28
|
|
11
23
|
|
|
12
24
|
### Changed
|
|
@@ -15,7 +15,7 @@ import type { JSX } from "react";
|
|
|
15
15
|
export interface BatteryStatusLike {
|
|
16
16
|
/** `null` means no reading has landed yet (cold seed) — NOT a flat 0 %
|
|
17
17
|
* (D315). Render "Unknown", never a red critical badge. */
|
|
18
|
-
readonly
|
|
18
|
+
readonly level: number | null;
|
|
19
19
|
readonly charging: "dc" | "solar" | "none" | "unknown";
|
|
20
20
|
readonly sleeping: boolean;
|
|
21
21
|
/** True for a binary LOW_BAT source with no real % — render "Normal"/"Low"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type JSX } from "react";
|
|
2
2
|
export interface NetworkLinkStatusLike {
|
|
3
3
|
readonly type: "wifi" | "ethernet" | "cellular" | "unknown";
|
|
4
|
-
readonly
|
|
4
|
+
readonly signal: number | null;
|
|
5
5
|
readonly ssid?: string;
|
|
6
6
|
}
|
|
7
7
|
export interface NetworkLinkBadgeProps {
|
|
@@ -13,6 +13,12 @@ export type OptionSelectOption = {
|
|
|
13
13
|
iconId?: string;
|
|
14
14
|
disabled?: boolean;
|
|
15
15
|
badge?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Heading this option is listed under. Options of one group are expected
|
|
18
|
+
* together (the list keeps its order); a heading is drawn where the group
|
|
19
|
+
* changes.
|
|
20
|
+
*/
|
|
21
|
+
group?: string;
|
|
16
22
|
};
|
|
17
23
|
/** Number of options at/above which search is enabled by default (unless creatable). */
|
|
18
24
|
export declare const OPTION_SELECT_SEARCH_MIN_OPTIONS = 8;
|
|
@@ -10,8 +10,8 @@ import { type UseDeviceProxyTrpc } from "./use-device-proxy";
|
|
|
10
10
|
export interface NetworkLinkStatus {
|
|
11
11
|
readonly type: "wifi" | "ethernet" | "cellular" | "unknown";
|
|
12
12
|
/** `null` = not known or not applicable (a wired link has no signal). */
|
|
13
|
-
readonly
|
|
14
|
-
readonly
|
|
13
|
+
readonly signal: number | null;
|
|
14
|
+
readonly rssi?: number;
|
|
15
15
|
readonly ssid?: string;
|
|
16
16
|
readonly lastUpdated: number;
|
|
17
17
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -12818,8 +12818,14 @@ function useDropdownContext() {
|
|
|
12818
12818
|
if (!ctx) throw new Error("Dropdown compound components must be used within <Dropdown>");
|
|
12819
12819
|
return ctx;
|
|
12820
12820
|
}
|
|
12821
|
-
function Dropdown({ children }) {
|
|
12822
|
-
const [
|
|
12821
|
+
function Dropdown({ children, open: controlledOpen, onOpenChange }) {
|
|
12822
|
+
const [innerOpen, setInnerOpen] = (0, react$1.useState)(false);
|
|
12823
|
+
const controlled = controlledOpen !== void 0 && onOpenChange !== void 0;
|
|
12824
|
+
const open = controlled ? controlledOpen : innerOpen;
|
|
12825
|
+
const setOpen = (0, react$1.useCallback)((next) => {
|
|
12826
|
+
if (controlled) onOpenChange(next);
|
|
12827
|
+
else setInnerOpen(next);
|
|
12828
|
+
}, [controlled, onOpenChange]);
|
|
12823
12829
|
const triggerId = (0, react$1.useId)();
|
|
12824
12830
|
const contentId = (0, react$1.useId)();
|
|
12825
12831
|
const triggerRef = (0, react$1.useRef)(null);
|
|
@@ -14010,7 +14016,13 @@ function OptionSelect(props) {
|
|
|
14010
14016
|
const checked = mode === "single" ? props.value === opt.value : props.value.includes(opt.value);
|
|
14011
14017
|
const blockedByMax = mode === "multi" && !checked && atMax === true;
|
|
14012
14018
|
const optionDisabled = opt.disabled === true || blockedByMax;
|
|
14013
|
-
|
|
14019
|
+
const previous = dropdownRows[i - 1];
|
|
14020
|
+
const previousGroup = previous !== void 0 && previous.kind === "option" ? previous.opt.group : void 0;
|
|
14021
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react$1.Fragment, { children: [opt.group !== void 0 && opt.group !== previousGroup ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
14022
|
+
role: "presentation",
|
|
14023
|
+
className: "px-2.5 pb-0.5 pt-1.5 text-[10px] font-medium uppercase tracking-wide text-foreground-subtle",
|
|
14024
|
+
children: opt.group
|
|
14025
|
+
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
14014
14026
|
type: "button",
|
|
14015
14027
|
role: "option",
|
|
14016
14028
|
"aria-selected": checked,
|
|
@@ -14038,7 +14050,7 @@ function OptionSelect(props) {
|
|
|
14038
14050
|
children: opt.badge
|
|
14039
14051
|
}) : null
|
|
14040
14052
|
]
|
|
14041
|
-
}, opt.value);
|
|
14053
|
+
})] }, opt.value);
|
|
14042
14054
|
}) });
|
|
14043
14055
|
const footerAddNewIndex = dropdownRows.length;
|
|
14044
14056
|
const fieldShowsChoice = mode === "single" && props.singleDisplay === "field" && selectedOptions.length > 0;
|
|
@@ -35994,7 +36006,7 @@ function levelColor(percentage, charging) {
|
|
|
35994
36006
|
function BatteryBadge({ status, variant = "compact", className }) {
|
|
35995
36007
|
const t = useUiText();
|
|
35996
36008
|
if (!status) return null;
|
|
35997
|
-
const { percentage, charging, sleeping, binary } = status;
|
|
36009
|
+
const { level: percentage, charging, sleeping, binary } = status;
|
|
35998
36010
|
if (percentage === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
35999
36011
|
className: cn("inline-flex items-center gap-1 text-[10px] font-medium text-foreground-subtle", className),
|
|
36000
36012
|
title: t("uiLibrary.batteryBadge.titleUnknown", "Battery: unknown"),
|
|
@@ -36157,15 +36169,15 @@ function NetworkLinkBadge({ status, className }) {
|
|
|
36157
36169
|
title: t("uiLibrary.networkLinkBadge.ethernet", "Network: ethernet"),
|
|
36158
36170
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Cable, { className: "h-3.5 w-3.5" })
|
|
36159
36171
|
});
|
|
36160
|
-
const Icon = status.type === "cellular" ? RadioTower : pickWifiIcon(status.
|
|
36161
|
-
const signal = status.
|
|
36172
|
+
const Icon = status.type === "cellular" ? RadioTower : pickWifiIcon(status.signal);
|
|
36173
|
+
const signal = status.signal === null ? null : Math.round(status.signal);
|
|
36162
36174
|
const networkText = status.ssid ? t("uiLibrary.networkLinkBadge.typeSsid", "Network: {{type}} ({{ssid}})", {
|
|
36163
36175
|
type: status.type,
|
|
36164
36176
|
ssid: status.ssid
|
|
36165
36177
|
}) : t("uiLibrary.networkLinkBadge.type", "Network: {{type}}", { type: status.type });
|
|
36166
36178
|
const title = signal === null ? networkText : `${networkText} · ${t("uiLibrary.networkLinkBadge.signal", "signal {{percent}}%", { percent: signal })}`;
|
|
36167
36179
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
36168
|
-
className: cn("inline-flex items-center gap-1 text-[10px] font-medium", signalColor(status.
|
|
36180
|
+
className: cn("inline-flex items-center gap-1 text-[10px] font-medium", signalColor(status.signal), className),
|
|
36169
36181
|
title,
|
|
36170
36182
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { className: "h-3.5 w-3.5" }), signal !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [signal, "%"] })]
|
|
36171
36183
|
});
|
|
@@ -47415,16 +47427,16 @@ function BrightnessPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
47415
47427
|
min: 0,
|
|
47416
47428
|
max: 100,
|
|
47417
47429
|
step: 1,
|
|
47418
|
-
value: display.
|
|
47430
|
+
value: display.level,
|
|
47419
47431
|
disabled: busy,
|
|
47420
47432
|
onChange: (e) => {
|
|
47421
47433
|
const pct = Number(e.target.value);
|
|
47422
|
-
applyOptimistic({
|
|
47423
|
-
if (!busy) cap?.setBrightness({
|
|
47434
|
+
applyOptimistic({ level: pct });
|
|
47435
|
+
if (!busy) cap?.setBrightness({ level: pct });
|
|
47424
47436
|
}
|
|
47425
47437
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
47426
47438
|
className: "tabular-nums w-8 text-right",
|
|
47427
|
-
children: [display.
|
|
47439
|
+
children: [display.level, "%"]
|
|
47428
47440
|
})]
|
|
47429
47441
|
});
|
|
47430
47442
|
}
|
|
@@ -47455,17 +47467,17 @@ function FanPanel({ trpc, deviceId, features, layout, optimistic }) {
|
|
|
47455
47467
|
min: 0,
|
|
47456
47468
|
max: 100,
|
|
47457
47469
|
step,
|
|
47458
|
-
value: display.
|
|
47470
|
+
value: display.speed ?? 0,
|
|
47459
47471
|
disabled: busy,
|
|
47460
47472
|
onChange: (e) => {
|
|
47461
47473
|
const raw = Number(e.target.value);
|
|
47462
47474
|
const pct = Math.min(100, Math.max(0, Math.round(raw / step) * step));
|
|
47463
|
-
applyOptimistic({
|
|
47464
|
-
if (!busy) cap?.setPercentage({
|
|
47475
|
+
applyOptimistic({ speed: pct });
|
|
47476
|
+
if (!busy) cap?.setPercentage({ speed: pct });
|
|
47465
47477
|
}
|
|
47466
47478
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
47467
47479
|
className: "tabular-nums w-8 text-right",
|
|
47468
|
-
children: [display.
|
|
47480
|
+
children: [display.speed ?? "—", "%"]
|
|
47469
47481
|
})]
|
|
47470
47482
|
}), showPreset && display.availablePresets.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
47471
47483
|
className: "flex items-center gap-2 text-xs text-foreground-subtle",
|
|
@@ -48553,25 +48565,25 @@ function hasBatteryNumericValue(slice, field) {
|
|
|
48553
48565
|
var ROLE_DESCRIPTOR = {
|
|
48554
48566
|
"temperature-sensor": {
|
|
48555
48567
|
kind: "numeric",
|
|
48556
|
-
field: "
|
|
48568
|
+
field: "temperature",
|
|
48557
48569
|
quantity: "temperature",
|
|
48558
48570
|
getSliceHandle: (s) => s.temperatureSensor
|
|
48559
48571
|
},
|
|
48560
48572
|
"humidity-sensor": {
|
|
48561
48573
|
kind: "numeric",
|
|
48562
|
-
field: "
|
|
48574
|
+
field: "humidity",
|
|
48563
48575
|
quantity: "humidity",
|
|
48564
48576
|
getSliceHandle: (s) => s.humiditySensor
|
|
48565
48577
|
},
|
|
48566
48578
|
"ambient-light-sensor": {
|
|
48567
48579
|
kind: "numeric",
|
|
48568
|
-
field: "
|
|
48580
|
+
field: "illuminance",
|
|
48569
48581
|
quantity: "illuminance",
|
|
48570
48582
|
getSliceHandle: (s) => s.ambientLightSensor
|
|
48571
48583
|
},
|
|
48572
48584
|
"pressure-sensor": {
|
|
48573
48585
|
kind: "numeric",
|
|
48574
|
-
field: "
|
|
48586
|
+
field: "pressure",
|
|
48575
48587
|
quantity: "pressure",
|
|
48576
48588
|
getSliceHandle: (s) => s.pressureSensor
|
|
48577
48589
|
},
|
|
@@ -48613,25 +48625,25 @@ var ROLE_DESCRIPTOR = {
|
|
|
48613
48625
|
},
|
|
48614
48626
|
"power-sensor": {
|
|
48615
48627
|
kind: "numeric",
|
|
48616
|
-
field: "
|
|
48628
|
+
field: "power",
|
|
48617
48629
|
quantity: "power",
|
|
48618
48630
|
getSliceHandle: (s) => s.powerMeter
|
|
48619
48631
|
},
|
|
48620
48632
|
"energy-sensor": {
|
|
48621
48633
|
kind: "numeric",
|
|
48622
|
-
field: "
|
|
48634
|
+
field: "energy",
|
|
48623
48635
|
quantity: "energy",
|
|
48624
48636
|
getSliceHandle: (s) => s.powerMeter
|
|
48625
48637
|
},
|
|
48626
48638
|
"voltage-sensor": {
|
|
48627
48639
|
kind: "numeric",
|
|
48628
|
-
field: "
|
|
48640
|
+
field: "voltage",
|
|
48629
48641
|
quantity: "voltage",
|
|
48630
48642
|
getSliceHandle: (s) => s.powerMeter
|
|
48631
48643
|
},
|
|
48632
48644
|
"current-sensor": {
|
|
48633
48645
|
kind: "numeric",
|
|
48634
|
-
field: "
|
|
48646
|
+
field: "current",
|
|
48635
48647
|
quantity: "current",
|
|
48636
48648
|
getSliceHandle: (s) => s.powerMeter
|
|
48637
48649
|
},
|
|
@@ -49403,16 +49415,16 @@ function capSlice(caps, capName) {
|
|
|
49403
49415
|
return caps[capName];
|
|
49404
49416
|
}
|
|
49405
49417
|
/**
|
|
49406
|
-
* A battery group writes `battery.
|
|
49418
|
+
* A battery group writes `battery.level`. The role's usual cap is
|
|
49407
49419
|
* `numeric-sensor.value`. Hand the renderer a slice whose `value` is that
|
|
49408
49420
|
* percentage so one numeric path draws both.
|
|
49409
49421
|
*/
|
|
49410
49422
|
function batteryPercentageSlice(caps) {
|
|
49411
49423
|
const battery = capSlice(caps, "battery");
|
|
49412
|
-
if (!isRecord$2(battery) || typeof battery["
|
|
49424
|
+
if (!isRecord$2(battery) || typeof battery["level"] !== "number") return void 0;
|
|
49413
49425
|
return {
|
|
49414
49426
|
...battery,
|
|
49415
|
-
value: battery["
|
|
49427
|
+
value: battery["level"]
|
|
49416
49428
|
};
|
|
49417
49429
|
}
|
|
49418
49430
|
function snapshotSlice(input) {
|
|
@@ -50172,17 +50184,17 @@ function VacuumHeroCard({ trpc, deviceId }) {
|
|
|
50172
50184
|
children: meta.label
|
|
50173
50185
|
}), slice?.batteryLevel != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BatteryBadge$2, { level: slice.batteryLevel })]
|
|
50174
50186
|
}),
|
|
50175
|
-
slice?.
|
|
50187
|
+
slice?.progress != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
50176
50188
|
className: "flex items-center gap-2",
|
|
50177
50189
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
50178
50190
|
className: "h-1.5 flex-1 rounded bg-surface-hover overflow-hidden",
|
|
50179
50191
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
50180
50192
|
className: "h-full bg-primary transition-[width] duration-500",
|
|
50181
|
-
style: { width: `${Math.max(0, Math.min(100, slice.
|
|
50193
|
+
style: { width: `${Math.max(0, Math.min(100, slice.progress))}%` }
|
|
50182
50194
|
})
|
|
50183
50195
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
50184
50196
|
className: "tabular-nums text-xs text-foreground-subtle",
|
|
50185
|
-
children: [Math.round(slice.
|
|
50197
|
+
children: [Math.round(slice.progress), "%"]
|
|
50186
50198
|
})]
|
|
50187
50199
|
}),
|
|
50188
50200
|
slice?.errorCode != null && slice.errorCode !== 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -50691,7 +50703,7 @@ function LawnMowerHeroCard({ trpc, deviceId }) {
|
|
|
50691
50703
|
children: meta.label
|
|
50692
50704
|
}), slice?.batteryLevel != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BatteryBadge$1, { level: slice.batteryLevel })]
|
|
50693
50705
|
}),
|
|
50694
|
-
slice?.
|
|
50706
|
+
slice?.progress != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProgressBar, { percent: slice.progress }),
|
|
50695
50707
|
slice?.currentCode != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodeLine, {
|
|
50696
50708
|
code: slice.currentCode,
|
|
50697
50709
|
label: slice.currentCodeLabel,
|
|
@@ -53342,9 +53354,9 @@ function DishwasherPanel({ trpc, deviceId, layout }) {
|
|
|
53342
53354
|
title: display.program,
|
|
53343
53355
|
children: featureLabel(display.program)
|
|
53344
53356
|
}),
|
|
53345
|
-
display.
|
|
53357
|
+
display.remaining != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
53346
53358
|
className: "inline-flex items-center gap-0.5 text-xs text-foreground-subtle tabular-nums shrink-0",
|
|
53347
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Timer, { className: "h-3 w-3" }), formatDuration(display.
|
|
53359
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Timer, { className: "h-3 w-3" }), formatDuration(display.remaining, t)]
|
|
53348
53360
|
}),
|
|
53349
53361
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherIconButton, {
|
|
53350
53362
|
label: t("uiLibrary.dishwasher.start", "Start"),
|
|
@@ -53370,7 +53382,7 @@ function DishwasherPanel({ trpc, deviceId, layout }) {
|
|
|
53370
53382
|
className: "flex items-start gap-4",
|
|
53371
53383
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DishwasherGraphic, {
|
|
53372
53384
|
running,
|
|
53373
|
-
progressPercent: display.
|
|
53385
|
+
progressPercent: display.progress
|
|
53374
53386
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
53375
53387
|
className: "flex min-w-0 flex-1 flex-col gap-2",
|
|
53376
53388
|
children: [
|
|
@@ -53389,25 +53401,25 @@ function DishwasherPanel({ trpc, deviceId, layout }) {
|
|
|
53389
53401
|
title: display.program,
|
|
53390
53402
|
children: featureLabel(display.program)
|
|
53391
53403
|
}),
|
|
53392
|
-
display.
|
|
53404
|
+
display.remaining != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
53393
53405
|
className: "inline-flex items-center gap-1 text-sm text-foreground-subtle tabular-nums",
|
|
53394
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Timer, { className: "h-3.5 w-3.5" }), t("uiLibrary.dishwasher.remaining", "{{time}} left", { time: formatDuration(display.
|
|
53406
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Timer, { className: "h-3.5 w-3.5" }), t("uiLibrary.dishwasher.remaining", "{{time}} left", { time: formatDuration(display.remaining, t) })]
|
|
53395
53407
|
}),
|
|
53396
|
-
display.
|
|
53408
|
+
display.delayedStart != null && display.delayedStart > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
53397
53409
|
className: "text-xs text-foreground-subtle tabular-nums",
|
|
53398
|
-
children: t("uiLibrary.dishwasher.startsIn", "Starts in {{time}}", { time: formatDuration(display.
|
|
53410
|
+
children: t("uiLibrary.dishwasher.startsIn", "Starts in {{time}}", { time: formatDuration(display.delayedStart, t) })
|
|
53399
53411
|
}),
|
|
53400
|
-
display.
|
|
53412
|
+
display.progress != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
53401
53413
|
className: "flex items-center gap-2",
|
|
53402
53414
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
53403
53415
|
className: "h-1.5 flex-1 rounded bg-surface-hover overflow-hidden",
|
|
53404
53416
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
53405
53417
|
className: "h-full bg-primary transition-[width] duration-500",
|
|
53406
|
-
style: { width: `${Math.max(0, Math.min(100, display.
|
|
53418
|
+
style: { width: `${Math.max(0, Math.min(100, display.progress))}%` }
|
|
53407
53419
|
})
|
|
53408
53420
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
53409
53421
|
className: "tabular-nums text-xs text-foreground-subtle",
|
|
53410
|
-
children: [Math.round(display.
|
|
53422
|
+
children: [Math.round(display.progress), "%"]
|
|
53411
53423
|
})]
|
|
53412
53424
|
}),
|
|
53413
53425
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -54802,12 +54814,12 @@ function LightHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
54802
54814
|
if (swOpt.display === void 0) return null;
|
|
54803
54815
|
const on = swOpt.display.on === true;
|
|
54804
54816
|
const swBusy = !sw.available;
|
|
54805
|
-
const percentage = brightOpt.display?.
|
|
54817
|
+
const percentage = brightOpt.display?.level ?? 0;
|
|
54806
54818
|
const brightBusy = !bright.available;
|
|
54807
54819
|
const fraction = on ? percentage / 100 : 0;
|
|
54808
54820
|
const arcColor = on ? ARC_COLOR_ON$1 : ARC_COLOR_OFF$1;
|
|
54809
54821
|
const supportsCt = (features?.includes(_camstack_types.DeviceFeature.LightColorMired) ?? false) && color.cap !== void 0;
|
|
54810
|
-
const ctMireds = colorOpt.display?.
|
|
54822
|
+
const ctMireds = colorOpt.display?.colorTemperature;
|
|
54811
54823
|
const ctBusy = !color.available;
|
|
54812
54824
|
const handleToggle = () => {
|
|
54813
54825
|
if (swBusy) return;
|
|
@@ -54852,8 +54864,8 @@ function LightHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
54852
54864
|
disabled: brightBusy || !on,
|
|
54853
54865
|
onChange: (e) => {
|
|
54854
54866
|
const pct = Number(e.target.value);
|
|
54855
|
-
brightOpt.applyOptimistic({
|
|
54856
|
-
if (!brightBusy) bright.cap?.setBrightness({
|
|
54867
|
+
brightOpt.applyOptimistic({ level: pct });
|
|
54868
|
+
if (!brightBusy) bright.cap?.setBrightness({ level: pct });
|
|
54857
54869
|
}
|
|
54858
54870
|
}),
|
|
54859
54871
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -54900,11 +54912,11 @@ function LightHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
54900
54912
|
const mireds = Number(e.target.value);
|
|
54901
54913
|
colorOpt.applyOptimistic({
|
|
54902
54914
|
mode: "mired",
|
|
54903
|
-
mireds
|
|
54915
|
+
colorTemperature: mireds
|
|
54904
54916
|
});
|
|
54905
54917
|
if (!ctBusy) color.cap?.setColor({ color: {
|
|
54906
54918
|
mode: "mired",
|
|
54907
|
-
mireds
|
|
54919
|
+
colorTemperature: mireds
|
|
54908
54920
|
} });
|
|
54909
54921
|
}
|
|
54910
54922
|
}),
|
|
@@ -54964,7 +54976,7 @@ function FanHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
54964
54976
|
if (swOpt.display === void 0) return null;
|
|
54965
54977
|
const on = swOpt.display.on === true;
|
|
54966
54978
|
const swBusy = !sw.available;
|
|
54967
|
-
const percentage = fanOpt.display?.
|
|
54979
|
+
const percentage = fanOpt.display?.speed ?? 0;
|
|
54968
54980
|
const fanBusy = !fan.available;
|
|
54969
54981
|
const fraction = on ? percentage / 100 : 0;
|
|
54970
54982
|
const arcColor = on ? ARC_COLOR_ON : ARC_COLOR_OFF;
|
|
@@ -54993,8 +55005,8 @@ function FanHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
54993
55005
|
if (slice === void 0) return;
|
|
54994
55006
|
const step = slice.percentageStep !== void 0 && slice.percentageStep > 0 ? slice.percentageStep : 1;
|
|
54995
55007
|
const pct = Math.min(100, Math.max(0, Math.round(f * 100 / step) * step));
|
|
54996
|
-
fanOpt.applyOptimistic({
|
|
54997
|
-
fan.cap?.setPercentage({
|
|
55008
|
+
fanOpt.applyOptimistic({ speed: pct });
|
|
55009
|
+
fan.cap?.setPercentage({ speed: pct });
|
|
54998
55010
|
};
|
|
54999
55011
|
const handlePreset = (preset) => {
|
|
55000
55012
|
if (fanBusy || preset === "") return;
|
|
@@ -55216,7 +55228,7 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
55216
55228
|
if (display === void 0) return null;
|
|
55217
55229
|
const busy = !available;
|
|
55218
55230
|
const action = toggleAction(display.state);
|
|
55219
|
-
const supportsSeek = (features?.includes(_camstack_types.DeviceFeature.MediaPlayerSeek) ?? false) && display.
|
|
55231
|
+
const supportsSeek = (features?.includes(_camstack_types.DeviceFeature.MediaPlayerSeek) ?? false) && display.duration !== null && display.duration > 0;
|
|
55220
55232
|
const supportsNext = features?.includes(_camstack_types.DeviceFeature.MediaPlayerNext) ?? false;
|
|
55221
55233
|
const supportsPrevious = features?.includes(_camstack_types.DeviceFeature.MediaPlayerPrevious) ?? false;
|
|
55222
55234
|
const supportsVolume = (features?.includes(_camstack_types.DeviceFeature.MediaPlayerVolume) ?? false) && display.volumeLevel !== null;
|
|
@@ -55231,7 +55243,7 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
55231
55243
|
}
|
|
55232
55244
|
};
|
|
55233
55245
|
const subtitle = display.currentMedia?.artist ?? display.currentMedia?.album ?? null;
|
|
55234
|
-
const seekFraction = progressFraction(display.
|
|
55246
|
+
const seekFraction = progressFraction(display.position, display.duration);
|
|
55235
55247
|
const volumeLevel = display.volumeLevel ?? 0;
|
|
55236
55248
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
55237
55249
|
className: "flex flex-col items-stretch gap-4 rounded-lg border border-border bg-surface p-6",
|
|
@@ -55254,16 +55266,16 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
55254
55266
|
"aria-label": t("uiLibrary.mediaPlayerHero.seek", "Seek"),
|
|
55255
55267
|
type: "range",
|
|
55256
55268
|
min: 0,
|
|
55257
|
-
max: display.
|
|
55269
|
+
max: display.duration ?? 0,
|
|
55258
55270
|
step: 1e3,
|
|
55259
55271
|
className: "absolute inset-0 h-4 w-full cursor-pointer opacity-0",
|
|
55260
|
-
value: display.
|
|
55272
|
+
value: display.position ?? 0,
|
|
55261
55273
|
disabled: busy,
|
|
55262
55274
|
onChange: (e) => {
|
|
55263
55275
|
if (busy) return;
|
|
55264
55276
|
const positionMs = Number(e.target.value);
|
|
55265
|
-
applyOptimistic({ positionMs });
|
|
55266
|
-
cap.seek({ positionMs });
|
|
55277
|
+
applyOptimistic({ position: positionMs });
|
|
55278
|
+
cap.seek({ position: positionMs });
|
|
55267
55279
|
}
|
|
55268
55280
|
}),
|
|
55269
55281
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -55273,7 +55285,7 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
55273
55285
|
]
|
|
55274
55286
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
55275
55287
|
className: "flex justify-between text-[11px] tabular-nums text-foreground-subtle",
|
|
55276
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatClock(display.
|
|
55288
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatClock(display.position) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatClock(display.duration) })]
|
|
55277
55289
|
})]
|
|
55278
55290
|
}),
|
|
55279
55291
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
package/dist/index.js
CHANGED
|
@@ -12794,8 +12794,14 @@ function useDropdownContext() {
|
|
|
12794
12794
|
if (!ctx) throw new Error("Dropdown compound components must be used within <Dropdown>");
|
|
12795
12795
|
return ctx;
|
|
12796
12796
|
}
|
|
12797
|
-
function Dropdown({ children }) {
|
|
12798
|
-
const [
|
|
12797
|
+
function Dropdown({ children, open: controlledOpen, onOpenChange }) {
|
|
12798
|
+
const [innerOpen, setInnerOpen] = useState(false);
|
|
12799
|
+
const controlled = controlledOpen !== void 0 && onOpenChange !== void 0;
|
|
12800
|
+
const open = controlled ? controlledOpen : innerOpen;
|
|
12801
|
+
const setOpen = useCallback((next) => {
|
|
12802
|
+
if (controlled) onOpenChange(next);
|
|
12803
|
+
else setInnerOpen(next);
|
|
12804
|
+
}, [controlled, onOpenChange]);
|
|
12799
12805
|
const triggerId = useId();
|
|
12800
12806
|
const contentId = useId();
|
|
12801
12807
|
const triggerRef = useRef(null);
|
|
@@ -13986,7 +13992,13 @@ function OptionSelect(props) {
|
|
|
13986
13992
|
const checked = mode === "single" ? props.value === opt.value : props.value.includes(opt.value);
|
|
13987
13993
|
const blockedByMax = mode === "multi" && !checked && atMax === true;
|
|
13988
13994
|
const optionDisabled = opt.disabled === true || blockedByMax;
|
|
13989
|
-
|
|
13995
|
+
const previous = dropdownRows[i - 1];
|
|
13996
|
+
const previousGroup = previous !== void 0 && previous.kind === "option" ? previous.opt.group : void 0;
|
|
13997
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [opt.group !== void 0 && opt.group !== previousGroup ? /* @__PURE__ */ jsx("div", {
|
|
13998
|
+
role: "presentation",
|
|
13999
|
+
className: "px-2.5 pb-0.5 pt-1.5 text-[10px] font-medium uppercase tracking-wide text-foreground-subtle",
|
|
14000
|
+
children: opt.group
|
|
14001
|
+
}) : null, /* @__PURE__ */ jsxs("button", {
|
|
13990
14002
|
type: "button",
|
|
13991
14003
|
role: "option",
|
|
13992
14004
|
"aria-selected": checked,
|
|
@@ -14014,7 +14026,7 @@ function OptionSelect(props) {
|
|
|
14014
14026
|
children: opt.badge
|
|
14015
14027
|
}) : null
|
|
14016
14028
|
]
|
|
14017
|
-
}, opt.value);
|
|
14029
|
+
})] }, opt.value);
|
|
14018
14030
|
}) });
|
|
14019
14031
|
const footerAddNewIndex = dropdownRows.length;
|
|
14020
14032
|
const fieldShowsChoice = mode === "single" && props.singleDisplay === "field" && selectedOptions.length > 0;
|
|
@@ -35970,7 +35982,7 @@ function levelColor(percentage, charging) {
|
|
|
35970
35982
|
function BatteryBadge({ status, variant = "compact", className }) {
|
|
35971
35983
|
const t = useUiText();
|
|
35972
35984
|
if (!status) return null;
|
|
35973
|
-
const { percentage, charging, sleeping, binary } = status;
|
|
35985
|
+
const { level: percentage, charging, sleeping, binary } = status;
|
|
35974
35986
|
if (percentage === null) return /* @__PURE__ */ jsxs("span", {
|
|
35975
35987
|
className: cn("inline-flex items-center gap-1 text-[10px] font-medium text-foreground-subtle", className),
|
|
35976
35988
|
title: t("uiLibrary.batteryBadge.titleUnknown", "Battery: unknown"),
|
|
@@ -36133,15 +36145,15 @@ function NetworkLinkBadge({ status, className }) {
|
|
|
36133
36145
|
title: t("uiLibrary.networkLinkBadge.ethernet", "Network: ethernet"),
|
|
36134
36146
|
children: /* @__PURE__ */ jsx(Cable, { className: "h-3.5 w-3.5" })
|
|
36135
36147
|
});
|
|
36136
|
-
const Icon = status.type === "cellular" ? RadioTower : pickWifiIcon(status.
|
|
36137
|
-
const signal = status.
|
|
36148
|
+
const Icon = status.type === "cellular" ? RadioTower : pickWifiIcon(status.signal);
|
|
36149
|
+
const signal = status.signal === null ? null : Math.round(status.signal);
|
|
36138
36150
|
const networkText = status.ssid ? t("uiLibrary.networkLinkBadge.typeSsid", "Network: {{type}} ({{ssid}})", {
|
|
36139
36151
|
type: status.type,
|
|
36140
36152
|
ssid: status.ssid
|
|
36141
36153
|
}) : t("uiLibrary.networkLinkBadge.type", "Network: {{type}}", { type: status.type });
|
|
36142
36154
|
const title = signal === null ? networkText : `${networkText} · ${t("uiLibrary.networkLinkBadge.signal", "signal {{percent}}%", { percent: signal })}`;
|
|
36143
36155
|
return /* @__PURE__ */ jsxs("span", {
|
|
36144
|
-
className: cn("inline-flex items-center gap-1 text-[10px] font-medium", signalColor(status.
|
|
36156
|
+
className: cn("inline-flex items-center gap-1 text-[10px] font-medium", signalColor(status.signal), className),
|
|
36145
36157
|
title,
|
|
36146
36158
|
children: [/* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" }), signal !== null && /* @__PURE__ */ jsxs("span", { children: [signal, "%"] })]
|
|
36147
36159
|
});
|
|
@@ -47391,16 +47403,16 @@ function BrightnessPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
47391
47403
|
min: 0,
|
|
47392
47404
|
max: 100,
|
|
47393
47405
|
step: 1,
|
|
47394
|
-
value: display.
|
|
47406
|
+
value: display.level,
|
|
47395
47407
|
disabled: busy,
|
|
47396
47408
|
onChange: (e) => {
|
|
47397
47409
|
const pct = Number(e.target.value);
|
|
47398
|
-
applyOptimistic({
|
|
47399
|
-
if (!busy) cap?.setBrightness({
|
|
47410
|
+
applyOptimistic({ level: pct });
|
|
47411
|
+
if (!busy) cap?.setBrightness({ level: pct });
|
|
47400
47412
|
}
|
|
47401
47413
|
}), /* @__PURE__ */ jsxs("span", {
|
|
47402
47414
|
className: "tabular-nums w-8 text-right",
|
|
47403
|
-
children: [display.
|
|
47415
|
+
children: [display.level, "%"]
|
|
47404
47416
|
})]
|
|
47405
47417
|
});
|
|
47406
47418
|
}
|
|
@@ -47431,17 +47443,17 @@ function FanPanel({ trpc, deviceId, features, layout, optimistic }) {
|
|
|
47431
47443
|
min: 0,
|
|
47432
47444
|
max: 100,
|
|
47433
47445
|
step,
|
|
47434
|
-
value: display.
|
|
47446
|
+
value: display.speed ?? 0,
|
|
47435
47447
|
disabled: busy,
|
|
47436
47448
|
onChange: (e) => {
|
|
47437
47449
|
const raw = Number(e.target.value);
|
|
47438
47450
|
const pct = Math.min(100, Math.max(0, Math.round(raw / step) * step));
|
|
47439
|
-
applyOptimistic({
|
|
47440
|
-
if (!busy) cap?.setPercentage({
|
|
47451
|
+
applyOptimistic({ speed: pct });
|
|
47452
|
+
if (!busy) cap?.setPercentage({ speed: pct });
|
|
47441
47453
|
}
|
|
47442
47454
|
}), /* @__PURE__ */ jsxs("span", {
|
|
47443
47455
|
className: "tabular-nums w-8 text-right",
|
|
47444
|
-
children: [display.
|
|
47456
|
+
children: [display.speed ?? "—", "%"]
|
|
47445
47457
|
})]
|
|
47446
47458
|
}), showPreset && display.availablePresets.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
47447
47459
|
className: "flex items-center gap-2 text-xs text-foreground-subtle",
|
|
@@ -48529,25 +48541,25 @@ function hasBatteryNumericValue(slice, field) {
|
|
|
48529
48541
|
var ROLE_DESCRIPTOR = {
|
|
48530
48542
|
"temperature-sensor": {
|
|
48531
48543
|
kind: "numeric",
|
|
48532
|
-
field: "
|
|
48544
|
+
field: "temperature",
|
|
48533
48545
|
quantity: "temperature",
|
|
48534
48546
|
getSliceHandle: (s) => s.temperatureSensor
|
|
48535
48547
|
},
|
|
48536
48548
|
"humidity-sensor": {
|
|
48537
48549
|
kind: "numeric",
|
|
48538
|
-
field: "
|
|
48550
|
+
field: "humidity",
|
|
48539
48551
|
quantity: "humidity",
|
|
48540
48552
|
getSliceHandle: (s) => s.humiditySensor
|
|
48541
48553
|
},
|
|
48542
48554
|
"ambient-light-sensor": {
|
|
48543
48555
|
kind: "numeric",
|
|
48544
|
-
field: "
|
|
48556
|
+
field: "illuminance",
|
|
48545
48557
|
quantity: "illuminance",
|
|
48546
48558
|
getSliceHandle: (s) => s.ambientLightSensor
|
|
48547
48559
|
},
|
|
48548
48560
|
"pressure-sensor": {
|
|
48549
48561
|
kind: "numeric",
|
|
48550
|
-
field: "
|
|
48562
|
+
field: "pressure",
|
|
48551
48563
|
quantity: "pressure",
|
|
48552
48564
|
getSliceHandle: (s) => s.pressureSensor
|
|
48553
48565
|
},
|
|
@@ -48589,25 +48601,25 @@ var ROLE_DESCRIPTOR = {
|
|
|
48589
48601
|
},
|
|
48590
48602
|
"power-sensor": {
|
|
48591
48603
|
kind: "numeric",
|
|
48592
|
-
field: "
|
|
48604
|
+
field: "power",
|
|
48593
48605
|
quantity: "power",
|
|
48594
48606
|
getSliceHandle: (s) => s.powerMeter
|
|
48595
48607
|
},
|
|
48596
48608
|
"energy-sensor": {
|
|
48597
48609
|
kind: "numeric",
|
|
48598
|
-
field: "
|
|
48610
|
+
field: "energy",
|
|
48599
48611
|
quantity: "energy",
|
|
48600
48612
|
getSliceHandle: (s) => s.powerMeter
|
|
48601
48613
|
},
|
|
48602
48614
|
"voltage-sensor": {
|
|
48603
48615
|
kind: "numeric",
|
|
48604
|
-
field: "
|
|
48616
|
+
field: "voltage",
|
|
48605
48617
|
quantity: "voltage",
|
|
48606
48618
|
getSliceHandle: (s) => s.powerMeter
|
|
48607
48619
|
},
|
|
48608
48620
|
"current-sensor": {
|
|
48609
48621
|
kind: "numeric",
|
|
48610
|
-
field: "
|
|
48622
|
+
field: "current",
|
|
48611
48623
|
quantity: "current",
|
|
48612
48624
|
getSliceHandle: (s) => s.powerMeter
|
|
48613
48625
|
},
|
|
@@ -49379,16 +49391,16 @@ function capSlice(caps, capName) {
|
|
|
49379
49391
|
return caps[capName];
|
|
49380
49392
|
}
|
|
49381
49393
|
/**
|
|
49382
|
-
* A battery group writes `battery.
|
|
49394
|
+
* A battery group writes `battery.level`. The role's usual cap is
|
|
49383
49395
|
* `numeric-sensor.value`. Hand the renderer a slice whose `value` is that
|
|
49384
49396
|
* percentage so one numeric path draws both.
|
|
49385
49397
|
*/
|
|
49386
49398
|
function batteryPercentageSlice(caps) {
|
|
49387
49399
|
const battery = capSlice(caps, "battery");
|
|
49388
|
-
if (!isRecord$2(battery) || typeof battery["
|
|
49400
|
+
if (!isRecord$2(battery) || typeof battery["level"] !== "number") return void 0;
|
|
49389
49401
|
return {
|
|
49390
49402
|
...battery,
|
|
49391
|
-
value: battery["
|
|
49403
|
+
value: battery["level"]
|
|
49392
49404
|
};
|
|
49393
49405
|
}
|
|
49394
49406
|
function snapshotSlice(input) {
|
|
@@ -50148,17 +50160,17 @@ function VacuumHeroCard({ trpc, deviceId }) {
|
|
|
50148
50160
|
children: meta.label
|
|
50149
50161
|
}), slice?.batteryLevel != null && /* @__PURE__ */ jsx(BatteryBadge$2, { level: slice.batteryLevel })]
|
|
50150
50162
|
}),
|
|
50151
|
-
slice?.
|
|
50163
|
+
slice?.progress != null && /* @__PURE__ */ jsxs("div", {
|
|
50152
50164
|
className: "flex items-center gap-2",
|
|
50153
50165
|
children: [/* @__PURE__ */ jsx("div", {
|
|
50154
50166
|
className: "h-1.5 flex-1 rounded bg-surface-hover overflow-hidden",
|
|
50155
50167
|
children: /* @__PURE__ */ jsx("div", {
|
|
50156
50168
|
className: "h-full bg-primary transition-[width] duration-500",
|
|
50157
|
-
style: { width: `${Math.max(0, Math.min(100, slice.
|
|
50169
|
+
style: { width: `${Math.max(0, Math.min(100, slice.progress))}%` }
|
|
50158
50170
|
})
|
|
50159
50171
|
}), /* @__PURE__ */ jsxs("span", {
|
|
50160
50172
|
className: "tabular-nums text-xs text-foreground-subtle",
|
|
50161
|
-
children: [Math.round(slice.
|
|
50173
|
+
children: [Math.round(slice.progress), "%"]
|
|
50162
50174
|
})]
|
|
50163
50175
|
}),
|
|
50164
50176
|
slice?.errorCode != null && slice.errorCode !== 0 && /* @__PURE__ */ jsxs("div", {
|
|
@@ -50667,7 +50679,7 @@ function LawnMowerHeroCard({ trpc, deviceId }) {
|
|
|
50667
50679
|
children: meta.label
|
|
50668
50680
|
}), slice?.batteryLevel != null && /* @__PURE__ */ jsx(BatteryBadge$1, { level: slice.batteryLevel })]
|
|
50669
50681
|
}),
|
|
50670
|
-
slice?.
|
|
50682
|
+
slice?.progress != null && /* @__PURE__ */ jsx(ProgressBar, { percent: slice.progress }),
|
|
50671
50683
|
slice?.currentCode != null && /* @__PURE__ */ jsx(CodeLine, {
|
|
50672
50684
|
code: slice.currentCode,
|
|
50673
50685
|
label: slice.currentCodeLabel,
|
|
@@ -53318,9 +53330,9 @@ function DishwasherPanel({ trpc, deviceId, layout }) {
|
|
|
53318
53330
|
title: display.program,
|
|
53319
53331
|
children: featureLabel(display.program)
|
|
53320
53332
|
}),
|
|
53321
|
-
display.
|
|
53333
|
+
display.remaining != null && /* @__PURE__ */ jsxs("span", {
|
|
53322
53334
|
className: "inline-flex items-center gap-0.5 text-xs text-foreground-subtle tabular-nums shrink-0",
|
|
53323
|
-
children: [/* @__PURE__ */ jsx(Timer, { className: "h-3 w-3" }), formatDuration(display.
|
|
53335
|
+
children: [/* @__PURE__ */ jsx(Timer, { className: "h-3 w-3" }), formatDuration(display.remaining, t)]
|
|
53324
53336
|
}),
|
|
53325
53337
|
/* @__PURE__ */ jsx(DishwasherIconButton, {
|
|
53326
53338
|
label: t("uiLibrary.dishwasher.start", "Start"),
|
|
@@ -53346,7 +53358,7 @@ function DishwasherPanel({ trpc, deviceId, layout }) {
|
|
|
53346
53358
|
className: "flex items-start gap-4",
|
|
53347
53359
|
children: [/* @__PURE__ */ jsx(DishwasherGraphic, {
|
|
53348
53360
|
running,
|
|
53349
|
-
progressPercent: display.
|
|
53361
|
+
progressPercent: display.progress
|
|
53350
53362
|
}), /* @__PURE__ */ jsxs("div", {
|
|
53351
53363
|
className: "flex min-w-0 flex-1 flex-col gap-2",
|
|
53352
53364
|
children: [
|
|
@@ -53365,25 +53377,25 @@ function DishwasherPanel({ trpc, deviceId, layout }) {
|
|
|
53365
53377
|
title: display.program,
|
|
53366
53378
|
children: featureLabel(display.program)
|
|
53367
53379
|
}),
|
|
53368
|
-
display.
|
|
53380
|
+
display.remaining != null && /* @__PURE__ */ jsxs("span", {
|
|
53369
53381
|
className: "inline-flex items-center gap-1 text-sm text-foreground-subtle tabular-nums",
|
|
53370
|
-
children: [/* @__PURE__ */ jsx(Timer, { className: "h-3.5 w-3.5" }), t("uiLibrary.dishwasher.remaining", "{{time}} left", { time: formatDuration(display.
|
|
53382
|
+
children: [/* @__PURE__ */ jsx(Timer, { className: "h-3.5 w-3.5" }), t("uiLibrary.dishwasher.remaining", "{{time}} left", { time: formatDuration(display.remaining, t) })]
|
|
53371
53383
|
}),
|
|
53372
|
-
display.
|
|
53384
|
+
display.delayedStart != null && display.delayedStart > 0 && /* @__PURE__ */ jsx("span", {
|
|
53373
53385
|
className: "text-xs text-foreground-subtle tabular-nums",
|
|
53374
|
-
children: t("uiLibrary.dishwasher.startsIn", "Starts in {{time}}", { time: formatDuration(display.
|
|
53386
|
+
children: t("uiLibrary.dishwasher.startsIn", "Starts in {{time}}", { time: formatDuration(display.delayedStart, t) })
|
|
53375
53387
|
}),
|
|
53376
|
-
display.
|
|
53388
|
+
display.progress != null && /* @__PURE__ */ jsxs("div", {
|
|
53377
53389
|
className: "flex items-center gap-2",
|
|
53378
53390
|
children: [/* @__PURE__ */ jsx("div", {
|
|
53379
53391
|
className: "h-1.5 flex-1 rounded bg-surface-hover overflow-hidden",
|
|
53380
53392
|
children: /* @__PURE__ */ jsx("div", {
|
|
53381
53393
|
className: "h-full bg-primary transition-[width] duration-500",
|
|
53382
|
-
style: { width: `${Math.max(0, Math.min(100, display.
|
|
53394
|
+
style: { width: `${Math.max(0, Math.min(100, display.progress))}%` }
|
|
53383
53395
|
})
|
|
53384
53396
|
}), /* @__PURE__ */ jsxs("span", {
|
|
53385
53397
|
className: "tabular-nums text-xs text-foreground-subtle",
|
|
53386
|
-
children: [Math.round(display.
|
|
53398
|
+
children: [Math.round(display.progress), "%"]
|
|
53387
53399
|
})]
|
|
53388
53400
|
}),
|
|
53389
53401
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -54778,12 +54790,12 @@ function LightHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
54778
54790
|
if (swOpt.display === void 0) return null;
|
|
54779
54791
|
const on = swOpt.display.on === true;
|
|
54780
54792
|
const swBusy = !sw.available;
|
|
54781
|
-
const percentage = brightOpt.display?.
|
|
54793
|
+
const percentage = brightOpt.display?.level ?? 0;
|
|
54782
54794
|
const brightBusy = !bright.available;
|
|
54783
54795
|
const fraction = on ? percentage / 100 : 0;
|
|
54784
54796
|
const arcColor = on ? ARC_COLOR_ON$1 : ARC_COLOR_OFF$1;
|
|
54785
54797
|
const supportsCt = (features?.includes(DeviceFeature.LightColorMired) ?? false) && color.cap !== void 0;
|
|
54786
|
-
const ctMireds = colorOpt.display?.
|
|
54798
|
+
const ctMireds = colorOpt.display?.colorTemperature;
|
|
54787
54799
|
const ctBusy = !color.available;
|
|
54788
54800
|
const handleToggle = () => {
|
|
54789
54801
|
if (swBusy) return;
|
|
@@ -54828,8 +54840,8 @@ function LightHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
54828
54840
|
disabled: brightBusy || !on,
|
|
54829
54841
|
onChange: (e) => {
|
|
54830
54842
|
const pct = Number(e.target.value);
|
|
54831
|
-
brightOpt.applyOptimistic({
|
|
54832
|
-
if (!brightBusy) bright.cap?.setBrightness({
|
|
54843
|
+
brightOpt.applyOptimistic({ level: pct });
|
|
54844
|
+
if (!brightBusy) bright.cap?.setBrightness({ level: pct });
|
|
54833
54845
|
}
|
|
54834
54846
|
}),
|
|
54835
54847
|
/* @__PURE__ */ jsxs("span", {
|
|
@@ -54876,11 +54888,11 @@ function LightHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
54876
54888
|
const mireds = Number(e.target.value);
|
|
54877
54889
|
colorOpt.applyOptimistic({
|
|
54878
54890
|
mode: "mired",
|
|
54879
|
-
mireds
|
|
54891
|
+
colorTemperature: mireds
|
|
54880
54892
|
});
|
|
54881
54893
|
if (!ctBusy) color.cap?.setColor({ color: {
|
|
54882
54894
|
mode: "mired",
|
|
54883
|
-
mireds
|
|
54895
|
+
colorTemperature: mireds
|
|
54884
54896
|
} });
|
|
54885
54897
|
}
|
|
54886
54898
|
}),
|
|
@@ -54940,7 +54952,7 @@ function FanHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
54940
54952
|
if (swOpt.display === void 0) return null;
|
|
54941
54953
|
const on = swOpt.display.on === true;
|
|
54942
54954
|
const swBusy = !sw.available;
|
|
54943
|
-
const percentage = fanOpt.display?.
|
|
54955
|
+
const percentage = fanOpt.display?.speed ?? 0;
|
|
54944
54956
|
const fanBusy = !fan.available;
|
|
54945
54957
|
const fraction = on ? percentage / 100 : 0;
|
|
54946
54958
|
const arcColor = on ? ARC_COLOR_ON : ARC_COLOR_OFF;
|
|
@@ -54969,8 +54981,8 @@ function FanHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
54969
54981
|
if (slice === void 0) return;
|
|
54970
54982
|
const step = slice.percentageStep !== void 0 && slice.percentageStep > 0 ? slice.percentageStep : 1;
|
|
54971
54983
|
const pct = Math.min(100, Math.max(0, Math.round(f * 100 / step) * step));
|
|
54972
|
-
fanOpt.applyOptimistic({
|
|
54973
|
-
fan.cap?.setPercentage({
|
|
54984
|
+
fanOpt.applyOptimistic({ speed: pct });
|
|
54985
|
+
fan.cap?.setPercentage({ speed: pct });
|
|
54974
54986
|
};
|
|
54975
54987
|
const handlePreset = (preset) => {
|
|
54976
54988
|
if (fanBusy || preset === "") return;
|
|
@@ -55192,7 +55204,7 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
55192
55204
|
if (display === void 0) return null;
|
|
55193
55205
|
const busy = !available;
|
|
55194
55206
|
const action = toggleAction(display.state);
|
|
55195
|
-
const supportsSeek = (features?.includes(DeviceFeature.MediaPlayerSeek) ?? false) && display.
|
|
55207
|
+
const supportsSeek = (features?.includes(DeviceFeature.MediaPlayerSeek) ?? false) && display.duration !== null && display.duration > 0;
|
|
55196
55208
|
const supportsNext = features?.includes(DeviceFeature.MediaPlayerNext) ?? false;
|
|
55197
55209
|
const supportsPrevious = features?.includes(DeviceFeature.MediaPlayerPrevious) ?? false;
|
|
55198
55210
|
const supportsVolume = (features?.includes(DeviceFeature.MediaPlayerVolume) ?? false) && display.volumeLevel !== null;
|
|
@@ -55207,7 +55219,7 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
55207
55219
|
}
|
|
55208
55220
|
};
|
|
55209
55221
|
const subtitle = display.currentMedia?.artist ?? display.currentMedia?.album ?? null;
|
|
55210
|
-
const seekFraction = progressFraction(display.
|
|
55222
|
+
const seekFraction = progressFraction(display.position, display.duration);
|
|
55211
55223
|
const volumeLevel = display.volumeLevel ?? 0;
|
|
55212
55224
|
return /* @__PURE__ */ jsxs("div", {
|
|
55213
55225
|
className: "flex flex-col items-stretch gap-4 rounded-lg border border-border bg-surface p-6",
|
|
@@ -55230,16 +55242,16 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
55230
55242
|
"aria-label": t("uiLibrary.mediaPlayerHero.seek", "Seek"),
|
|
55231
55243
|
type: "range",
|
|
55232
55244
|
min: 0,
|
|
55233
|
-
max: display.
|
|
55245
|
+
max: display.duration ?? 0,
|
|
55234
55246
|
step: 1e3,
|
|
55235
55247
|
className: "absolute inset-0 h-4 w-full cursor-pointer opacity-0",
|
|
55236
|
-
value: display.
|
|
55248
|
+
value: display.position ?? 0,
|
|
55237
55249
|
disabled: busy,
|
|
55238
55250
|
onChange: (e) => {
|
|
55239
55251
|
if (busy) return;
|
|
55240
55252
|
const positionMs = Number(e.target.value);
|
|
55241
|
-
applyOptimistic({ positionMs });
|
|
55242
|
-
cap.seek({ positionMs });
|
|
55253
|
+
applyOptimistic({ position: positionMs });
|
|
55254
|
+
cap.seek({ position: positionMs });
|
|
55243
55255
|
}
|
|
55244
55256
|
}),
|
|
55245
55257
|
/* @__PURE__ */ jsx("div", {
|
|
@@ -55249,7 +55261,7 @@ function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }) {
|
|
|
55249
55261
|
]
|
|
55250
55262
|
}), /* @__PURE__ */ jsxs("div", {
|
|
55251
55263
|
className: "flex justify-between text-[11px] tabular-nums text-foreground-subtle",
|
|
55252
|
-
children: [/* @__PURE__ */ jsx("span", { children: formatClock(display.
|
|
55264
|
+
children: [/* @__PURE__ */ jsx("span", { children: formatClock(display.position) }), /* @__PURE__ */ jsx("span", { children: formatClock(display.duration) })]
|
|
55253
55265
|
})]
|
|
55254
55266
|
}),
|
|
55255
55267
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { type JSX, type ButtonHTMLAttributes, type HTMLAttributes, type ReactNode } from "react";
|
|
2
2
|
import { type LucideIcon } from "lucide-react";
|
|
3
|
-
export
|
|
4
|
-
children: ReactNode;
|
|
5
|
-
|
|
3
|
+
export interface DropdownProps {
|
|
4
|
+
readonly children: ReactNode;
|
|
5
|
+
/**
|
|
6
|
+
* Controlled open state. Give both `open` and `onOpenChange` to drive the menu
|
|
7
|
+
* from outside (a "{" typed in a field opens the variable list); give neither
|
|
8
|
+
* and the dropdown keeps its own state.
|
|
9
|
+
*/
|
|
10
|
+
readonly open?: boolean;
|
|
11
|
+
readonly onOpenChange?: (open: boolean) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare function Dropdown({ children, open: controlledOpen, onOpenChange }: DropdownProps): JSX.Element | null;
|
|
6
14
|
export declare function DropdownTrigger({ children, ...props }: ButtonHTMLAttributes<HTMLButtonElement>): JSX.Element | null;
|
|
7
15
|
interface DropdownContentProps extends HTMLAttributes<HTMLDivElement> {
|
|
8
16
|
/** Horizontal alignment relative to the trigger. Default: 'start' (left-aligned). */
|