@camstack/ui-library 1.2.344 → 1.2.346
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 +15 -0
- package/dist/composites/data-table-filter-bar.d.ts +1 -1
- package/dist/composites/device-controls/climate-panel.d.ts +9 -0
- package/dist/composites/device-picker/device-provider-label.d.ts +22 -0
- package/dist/generated/system-hooks.d.ts +4 -0
- package/dist/hotkeys/combo.d.ts +24 -0
- package/dist/hotkeys/hotkey-provider.d.ts +37 -0
- package/dist/hotkeys/index.d.ts +6 -0
- package/dist/hotkeys/use-search-field-hotkey.d.ts +13 -0
- package/dist/index.cjs +385 -25
- package/dist/index.d.ts +1 -0
- package/dist/index.js +376 -26
- package/dist/primitives/input.d.ts +6 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -5701,6 +5701,11 @@ var DEVICE_ROLE_META = {
|
|
|
5701
5701
|
icon: Bell,
|
|
5702
5702
|
labelKey: "uiLibrary.deviceMeta.role.genericNotifier",
|
|
5703
5703
|
labelFallback: "Notifier"
|
|
5704
|
+
},
|
|
5705
|
+
[_camstack_types.DeviceRole.IrTransmitter]: {
|
|
5706
|
+
icon: RadioTower,
|
|
5707
|
+
labelKey: "uiLibrary.deviceMeta.role.irTransmitter",
|
|
5708
|
+
labelFallback: "IR/RF transmitter"
|
|
5704
5709
|
}
|
|
5705
5710
|
};
|
|
5706
5711
|
var DEVICE_TYPE_VALUES$1 = new Set(Object.values(_camstack_types.DeviceType));
|
|
@@ -5950,6 +5955,191 @@ function useDisplayIconResolver() {
|
|
|
5950
5955
|
return (0, react$1.useMemo)(() => (name) => resolveDisplayIcon(name), [useIconGlyphsVersion()]);
|
|
5951
5956
|
}
|
|
5952
5957
|
//#endregion
|
|
5958
|
+
//#region src/hotkeys/combo.ts
|
|
5959
|
+
function parseCombo(combo) {
|
|
5960
|
+
const parts = combo.split("+");
|
|
5961
|
+
const key = parts[parts.length - 1] ?? "";
|
|
5962
|
+
const modifiers = parts.slice(0, -1);
|
|
5963
|
+
return {
|
|
5964
|
+
mod: modifiers.includes("mod"),
|
|
5965
|
+
shift: modifiers.includes("shift"),
|
|
5966
|
+
alt: modifiers.includes("alt"),
|
|
5967
|
+
key
|
|
5968
|
+
};
|
|
5969
|
+
}
|
|
5970
|
+
function detectPlatform() {
|
|
5971
|
+
if (typeof navigator === "undefined") return "other";
|
|
5972
|
+
return /mac|iphone|ipad/i.test(navigator.platform) ? "mac" : "other";
|
|
5973
|
+
}
|
|
5974
|
+
function matchesCombo(event, combo, platform) {
|
|
5975
|
+
const want = parseCombo(combo);
|
|
5976
|
+
const modDown = platform === "mac" ? event.metaKey : event.ctrlKey;
|
|
5977
|
+
const strayDown = platform === "mac" ? event.ctrlKey : event.metaKey;
|
|
5978
|
+
return event.key.toLowerCase() === want.key && modDown === want.mod && !strayDown && event.shiftKey === want.shift && event.altKey === want.alt;
|
|
5979
|
+
}
|
|
5980
|
+
//#endregion
|
|
5981
|
+
//#region src/hotkeys/hotkey-provider.tsx
|
|
5982
|
+
var PAGE_SCOPE = "page";
|
|
5983
|
+
/** Asked last, after the page: a global handler that yields to whatever the page registers. */
|
|
5984
|
+
var FALLBACK_SCOPE = "fallback";
|
|
5985
|
+
var RegistryContext = (0, react$1.createContext)(null);
|
|
5986
|
+
var ScopeContext = (0, react$1.createContext)(PAGE_SCOPE);
|
|
5987
|
+
function isTypingTarget(target) {
|
|
5988
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
5989
|
+
if (target.isContentEditable) return true;
|
|
5990
|
+
return target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT";
|
|
5991
|
+
}
|
|
5992
|
+
/**
|
|
5993
|
+
* Scopes stack in mount order; the newest enabled one is asked first. A `modal`
|
|
5994
|
+
* scope (an open dialog) ends the walk: what sits under it is not reachable.
|
|
5995
|
+
* Inside a scope the newest binding wins; a handler that returns `false` hands
|
|
5996
|
+
* over to the next one.
|
|
5997
|
+
*/
|
|
5998
|
+
function dispatch(state, event, platform) {
|
|
5999
|
+
if (event.isComposing || event.keyCode === 229) return;
|
|
6000
|
+
const typing = isTypingTarget(event.target);
|
|
6001
|
+
const order = [
|
|
6002
|
+
...[...state.scopes].reverse().map((s) => s.id),
|
|
6003
|
+
PAGE_SCOPE,
|
|
6004
|
+
FALLBACK_SCOPE
|
|
6005
|
+
];
|
|
6006
|
+
for (const scopeId of order) {
|
|
6007
|
+
const candidates = state.bindings.filter((b) => b.scope === scopeId && matchesCombo(event, b.combo, platform)).reverse();
|
|
6008
|
+
for (const binding of candidates) {
|
|
6009
|
+
if (typing && !binding.allowInInput) continue;
|
|
6010
|
+
if (binding.handlerRef.current(event) === false) continue;
|
|
6011
|
+
if (binding.preventDefault) event.preventDefault();
|
|
6012
|
+
return;
|
|
6013
|
+
}
|
|
6014
|
+
if (state.scopes.find((s) => s.id === scopeId)?.modal) return;
|
|
6015
|
+
}
|
|
6016
|
+
}
|
|
6017
|
+
function HotkeyProvider({ children, platform }) {
|
|
6018
|
+
const stateRef = (0, react$1.useRef)({
|
|
6019
|
+
bindings: [],
|
|
6020
|
+
scopes: []
|
|
6021
|
+
});
|
|
6022
|
+
const effectivePlatform = platform ?? detectPlatform();
|
|
6023
|
+
const registry = (0, react$1.useMemo)(() => ({
|
|
6024
|
+
addBinding: (binding) => {
|
|
6025
|
+
stateRef.current = {
|
|
6026
|
+
...stateRef.current,
|
|
6027
|
+
bindings: [...stateRef.current.bindings, binding]
|
|
6028
|
+
};
|
|
6029
|
+
return () => {
|
|
6030
|
+
stateRef.current = {
|
|
6031
|
+
...stateRef.current,
|
|
6032
|
+
bindings: stateRef.current.bindings.filter((b) => b.id !== binding.id)
|
|
6033
|
+
};
|
|
6034
|
+
};
|
|
6035
|
+
},
|
|
6036
|
+
addScope: (scope) => {
|
|
6037
|
+
stateRef.current = {
|
|
6038
|
+
...stateRef.current,
|
|
6039
|
+
scopes: [...stateRef.current.scopes, scope]
|
|
6040
|
+
};
|
|
6041
|
+
return () => {
|
|
6042
|
+
stateRef.current = {
|
|
6043
|
+
...stateRef.current,
|
|
6044
|
+
scopes: stateRef.current.scopes.filter((s) => s !== scope)
|
|
6045
|
+
};
|
|
6046
|
+
};
|
|
6047
|
+
}
|
|
6048
|
+
}), []);
|
|
6049
|
+
(0, react$1.useEffect)(() => {
|
|
6050
|
+
const onKeyDown = (event) => dispatch(stateRef.current, event, effectivePlatform);
|
|
6051
|
+
window.addEventListener("keydown", onKeyDown, true);
|
|
6052
|
+
return () => window.removeEventListener("keydown", onKeyDown, true);
|
|
6053
|
+
}, [effectivePlatform]);
|
|
6054
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RegistryContext.Provider, {
|
|
6055
|
+
value: registry,
|
|
6056
|
+
children
|
|
6057
|
+
});
|
|
6058
|
+
}
|
|
6059
|
+
var nextBindingId = 0;
|
|
6060
|
+
/**
|
|
6061
|
+
* Bind a combo for the life of the component. Without a `HotkeyProvider` above
|
|
6062
|
+
* it does nothing, so the browser keeps its own shortcut.
|
|
6063
|
+
*/
|
|
6064
|
+
function useHotkey(combo, handler, options = {}) {
|
|
6065
|
+
const registry = (0, react$1.useContext)(RegistryContext);
|
|
6066
|
+
const inheritedScope = (0, react$1.useContext)(ScopeContext);
|
|
6067
|
+
const handlerRef = (0, react$1.useRef)(handler);
|
|
6068
|
+
handlerRef.current = handler;
|
|
6069
|
+
const scope = options.scope ?? inheritedScope;
|
|
6070
|
+
const enabled = options.enabled ?? true;
|
|
6071
|
+
const preventDefault = options.preventDefault ?? true;
|
|
6072
|
+
const allowInInput = options.allowInInput ?? parseCombo(combo).mod;
|
|
6073
|
+
(0, react$1.useEffect)(() => {
|
|
6074
|
+
if (!registry || !enabled) return void 0;
|
|
6075
|
+
nextBindingId += 1;
|
|
6076
|
+
return registry.addBinding({
|
|
6077
|
+
id: nextBindingId,
|
|
6078
|
+
combo,
|
|
6079
|
+
scope,
|
|
6080
|
+
handlerRef,
|
|
6081
|
+
preventDefault,
|
|
6082
|
+
allowInInput
|
|
6083
|
+
});
|
|
6084
|
+
}, [
|
|
6085
|
+
registry,
|
|
6086
|
+
enabled,
|
|
6087
|
+
combo,
|
|
6088
|
+
scope,
|
|
6089
|
+
preventDefault,
|
|
6090
|
+
allowInInput
|
|
6091
|
+
]);
|
|
6092
|
+
}
|
|
6093
|
+
/** Bindings made below this component belong to `id`; while enabled it is the topmost scope. */
|
|
6094
|
+
function HotkeyScope({ id, children, enabled = true, modal = false }) {
|
|
6095
|
+
const registry = (0, react$1.useContext)(RegistryContext);
|
|
6096
|
+
(0, react$1.useEffect)(() => {
|
|
6097
|
+
if (!registry || !enabled) return void 0;
|
|
6098
|
+
return registry.addScope({
|
|
6099
|
+
id,
|
|
6100
|
+
modal
|
|
6101
|
+
});
|
|
6102
|
+
}, [
|
|
6103
|
+
registry,
|
|
6104
|
+
enabled,
|
|
6105
|
+
id,
|
|
6106
|
+
modal
|
|
6107
|
+
]);
|
|
6108
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScopeContext.Provider, {
|
|
6109
|
+
value: id,
|
|
6110
|
+
children
|
|
6111
|
+
});
|
|
6112
|
+
}
|
|
6113
|
+
//#endregion
|
|
6114
|
+
//#region src/hotkeys/use-search-field-hotkey.ts
|
|
6115
|
+
function isReachable(el) {
|
|
6116
|
+
return el.isConnected && !el.disabled && el.getClientRects().length > 0;
|
|
6117
|
+
}
|
|
6118
|
+
/**
|
|
6119
|
+
* Register a search field: ⌘F / Ctrl+F and ⌘K / Ctrl+K focus and select it.
|
|
6120
|
+
* A field that is hidden or disabled declines, so the browser's find still runs.
|
|
6121
|
+
* The library's search inputs call this themselves.
|
|
6122
|
+
*/
|
|
6123
|
+
function useSearchFieldHotkey(ref, options = {}) {
|
|
6124
|
+
const enabled = options.enabled ?? true;
|
|
6125
|
+
const scope = options.scope;
|
|
6126
|
+
const focusField = () => {
|
|
6127
|
+
const el = ref.current;
|
|
6128
|
+
if (!el || !isReachable(el)) return false;
|
|
6129
|
+
el.focus();
|
|
6130
|
+
el.select();
|
|
6131
|
+
return true;
|
|
6132
|
+
};
|
|
6133
|
+
useHotkey("mod+f", focusField, {
|
|
6134
|
+
enabled,
|
|
6135
|
+
scope
|
|
6136
|
+
});
|
|
6137
|
+
useHotkey("mod+k", focusField, {
|
|
6138
|
+
enabled,
|
|
6139
|
+
scope
|
|
6140
|
+
});
|
|
6141
|
+
}
|
|
6142
|
+
//#endregion
|
|
5953
6143
|
//#region ../../node_modules/clsx/dist/clsx.mjs
|
|
5954
6144
|
function r(e) {
|
|
5955
6145
|
var t, f, n = "";
|
|
@@ -9689,6 +9879,8 @@ function windowRows(rows, offsets, total, scrollTop) {
|
|
|
9689
9879
|
};
|
|
9690
9880
|
}
|
|
9691
9881
|
function IconPicker({ value, onPick, fallbackGlyph, labels, extraWords, clearGlyph, labelFor, categoryLabel = _camstack_types.iconCategoryLabel, disabled = false }) {
|
|
9882
|
+
const searchFieldRef = (0, react$1.useRef)(null);
|
|
9883
|
+
useSearchFieldHotkey(searchFieldRef);
|
|
9692
9884
|
const [open, setOpen] = (0, react$1.useState)(false);
|
|
9693
9885
|
const [query, setQuery] = (0, react$1.useState)("");
|
|
9694
9886
|
const [scrollTop, setScrollTop] = (0, react$1.useState)(0);
|
|
@@ -9749,6 +9941,7 @@ function IconPicker({ value, onPick, fallbackGlyph, labels, extraWords, clearGly
|
|
|
9749
9941
|
className: "w-full",
|
|
9750
9942
|
children: [
|
|
9751
9943
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
9944
|
+
ref: searchFieldRef,
|
|
9752
9945
|
type: "search",
|
|
9753
9946
|
value: query,
|
|
9754
9947
|
autoFocus: true,
|
|
@@ -10007,7 +10200,14 @@ var inputVariants = cva("h-7 w-full px-2.5 text-xs bg-surface border rounded-md
|
|
|
10007
10200
|
} },
|
|
10008
10201
|
defaultVariants: { state: "default" }
|
|
10009
10202
|
});
|
|
10010
|
-
var Input = (0, react$1.forwardRef)(({ className, state, leftSlot, rightSlot, ...props }, ref) => {
|
|
10203
|
+
var Input = (0, react$1.forwardRef)(({ className, state, leftSlot, rightSlot, searchHotkey, ...props }, ref) => {
|
|
10204
|
+
const innerRef = (0, react$1.useRef)(null);
|
|
10205
|
+
const setRef = (0, react$1.useCallback)((node) => {
|
|
10206
|
+
innerRef.current = node;
|
|
10207
|
+
if (typeof ref === "function") ref(node);
|
|
10208
|
+
else if (ref) ref.current = node;
|
|
10209
|
+
}, [ref]);
|
|
10210
|
+
useSearchFieldHotkey(innerRef, { enabled: searchHotkey ?? props.type === "search" });
|
|
10011
10211
|
if (leftSlot || rightSlot) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10012
10212
|
className: cn("relative flex items-center", className),
|
|
10013
10213
|
children: [
|
|
@@ -10016,7 +10216,7 @@ var Input = (0, react$1.forwardRef)(({ className, state, leftSlot, rightSlot, ..
|
|
|
10016
10216
|
children: leftSlot
|
|
10017
10217
|
}),
|
|
10018
10218
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
10019
|
-
ref,
|
|
10219
|
+
ref: setRef,
|
|
10020
10220
|
className: cn(inputVariants({ state }), leftSlot && "pl-8", rightSlot && "pr-8", className),
|
|
10021
10221
|
...props
|
|
10022
10222
|
}),
|
|
@@ -10027,7 +10227,7 @@ var Input = (0, react$1.forwardRef)(({ className, state, leftSlot, rightSlot, ..
|
|
|
10027
10227
|
]
|
|
10028
10228
|
});
|
|
10029
10229
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
10030
|
-
ref,
|
|
10230
|
+
ref: setRef,
|
|
10031
10231
|
className: cn(inputVariants({ state }), className),
|
|
10032
10232
|
...props
|
|
10033
10233
|
});
|
|
@@ -11262,6 +11462,10 @@ var useIntercomPushTalkAudio = trpc.intercom.pushTalkAudio.useMutation;
|
|
|
11262
11462
|
var useIntercomEndTalkSession = trpc.intercom.endTalkSession.useMutation;
|
|
11263
11463
|
/** Generated alias around `trpc.intercom.getStatus.useQuery`. */
|
|
11264
11464
|
var useIntercomGetStatus = trpc.intercom.getStatus.useQuery;
|
|
11465
|
+
/** Generated alias around `trpc.irTransmitter.send.useMutation`. */
|
|
11466
|
+
var useIrTransmitterSend = trpc.irTransmitter.send.useMutation;
|
|
11467
|
+
/** Generated alias around `trpc.irTransmitter.getInfo.useQuery`. */
|
|
11468
|
+
var useIrTransmitterGetInfo = trpc.irTransmitter.getInfo.useQuery;
|
|
11265
11469
|
/** Generated alias around `trpc.keychainAdmin.status.useQuery`. */
|
|
11266
11470
|
var useKeychainAdminStatus = trpc.keychainAdmin.status.useQuery;
|
|
11267
11471
|
/** Generated alias around `trpc.keychainAdmin.listSecrets.useQuery`. */
|
|
@@ -26510,6 +26714,8 @@ function TrackedToggles({ options, tracked, nameOf, onToggle }) {
|
|
|
26510
26714
|
});
|
|
26511
26715
|
}
|
|
26512
26716
|
function AssociationSlotCard({ deviceId, row, onSaved, readOnly = false }) {
|
|
26717
|
+
const searchFieldRef = (0, react$1.useRef)(null);
|
|
26718
|
+
useSearchFieldHotkey(searchFieldRef);
|
|
26513
26719
|
const t = useUiText();
|
|
26514
26720
|
const descriptor = _camstack_types.EXTENSION_SLOTS[row.slot].descriptor;
|
|
26515
26721
|
const saved = row.value.deviceIds;
|
|
@@ -26641,6 +26847,7 @@ function AssociationSlotCard({ deviceId, row, onSaved, readOnly = false }) {
|
|
|
26641
26847
|
})]
|
|
26642
26848
|
}),
|
|
26643
26849
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
26850
|
+
ref: searchFieldRef,
|
|
26644
26851
|
type: "search",
|
|
26645
26852
|
"aria-label": t("uiLibrary.associationSlotCard.searchDevices", "Search devices"),
|
|
26646
26853
|
value: search,
|
|
@@ -29014,8 +29221,8 @@ var contentVariants = cva("relative fixed left-1/2 top-1/2 -translate-x-1/2 -tra
|
|
|
29014
29221
|
* flex chain does not resolve without it on iOS Safari.
|
|
29015
29222
|
*/
|
|
29016
29223
|
scroll: {
|
|
29017
|
-
content: "overflow-y-auto",
|
|
29018
|
-
body: "flex flex-col overflow-hidden"
|
|
29224
|
+
content: "overflow-y-auto overscroll-contain",
|
|
29225
|
+
body: "flex flex-col overflow-hidden overscroll-contain"
|
|
29019
29226
|
},
|
|
29020
29227
|
placement: {
|
|
29021
29228
|
center: "",
|
|
@@ -29089,7 +29296,12 @@ var DialogContent = (0, react$1.forwardRef)(({ className, width, placement, scro
|
|
|
29089
29296
|
"aria-label": t("uiLibrary.dialog.close", "Close"),
|
|
29090
29297
|
className: "absolute right-4 top-4 z-10 shrink-0",
|
|
29091
29298
|
onClick: () => setOpen(false)
|
|
29092
|
-
}),
|
|
29299
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HotkeyScope, {
|
|
29300
|
+
id: contentId,
|
|
29301
|
+
modal: true,
|
|
29302
|
+
enabled: open,
|
|
29303
|
+
children
|
|
29304
|
+
})]
|
|
29093
29305
|
});
|
|
29094
29306
|
});
|
|
29095
29307
|
DialogContent.displayName = "DialogContent";
|
|
@@ -29119,7 +29331,7 @@ function DialogHeader({ className, ...props }) {
|
|
|
29119
29331
|
*/
|
|
29120
29332
|
function DialogBody({ className, ...props }) {
|
|
29121
29333
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
29122
|
-
className: cn("flex-1 min-h-0 overflow-y-auto", className),
|
|
29334
|
+
className: cn("flex-1 min-h-0 overflow-y-auto overscroll-contain", className),
|
|
29123
29335
|
...props
|
|
29124
29336
|
});
|
|
29125
29337
|
}
|
|
@@ -41956,6 +42168,8 @@ function MultiSelectDropdown({ label, options, selected, onChange }) {
|
|
|
41956
42168
|
var CHIP_ON = "rounded-full border border-primary bg-primary/10 px-2 py-0.5 text-[10px] text-foreground";
|
|
41957
42169
|
var CHIP_OFF = "rounded-full border border-border px-2 py-0.5 text-[10px] text-foreground-muted hover:bg-surface-hover";
|
|
41958
42170
|
function DataTableFilterBar({ state, facets, labels, onSearch, onToggleFacet, onClear, facetLabel, groups = [], onToggleGroupFacet }) {
|
|
42171
|
+
const searchRef = (0, react$1.useRef)(null);
|
|
42172
|
+
useSearchFieldHotkey(searchRef);
|
|
41959
42173
|
const active = isDataTableFilterActive(state);
|
|
41960
42174
|
const showFacets = facets.length > 1;
|
|
41961
42175
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -41964,6 +42178,7 @@ function DataTableFilterBar({ state, facets, labels, onSearch, onToggleFacet, on
|
|
|
41964
42178
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
41965
42179
|
className: "relative min-w-[12rem] flex-1",
|
|
41966
42180
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Search, { className: "pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-foreground-subtle" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
42181
|
+
ref: searchRef,
|
|
41967
42182
|
type: "search",
|
|
41968
42183
|
value: state.search,
|
|
41969
42184
|
onChange: (e) => onSearch(e.target.value),
|
|
@@ -44194,6 +44409,8 @@ var TEXT_DEBOUNCE_MS = 300;
|
|
|
44194
44409
|
var TOP_LOAD_THRESHOLD_PX = 24;
|
|
44195
44410
|
var BYTES_PER_MB = 1024 * 1024;
|
|
44196
44411
|
function LogStream({ agentId: propsAgentId, addonId: propsAddonId, deviceId: propsDeviceId, containerDeviceId: propsContainerDeviceId, integrationId: propsIntegrationId, requestId: propsRequestId, level: initialLevel, maxHeight = "max-h-96", showScope = false, showDeviceChip = false, showFilters = true, showSourceSelect, limit = 100, liveBuffer: externalBuffer, onClose, className }) {
|
|
44412
|
+
const searchFieldRef = (0, react$1.useRef)(null);
|
|
44413
|
+
useSearchFieldHotkey(searchFieldRef);
|
|
44197
44414
|
const t = useUiText();
|
|
44198
44415
|
const sourceSelectOn = showSourceSelect ?? showFilters;
|
|
44199
44416
|
const [localAddonId, setLocalAddonId] = (0, react$1.useState)("");
|
|
@@ -44471,6 +44688,7 @@ function LogStream({ agentId: propsAgentId, addonId: propsAddonId, deviceId: pro
|
|
|
44471
44688
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
44472
44689
|
className: "relative",
|
|
44473
44690
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
44691
|
+
ref: searchFieldRef,
|
|
44474
44692
|
type: "text",
|
|
44475
44693
|
value: searchText,
|
|
44476
44694
|
onChange: (e) => setSearchText(e.target.value),
|
|
@@ -45122,6 +45340,8 @@ function hasContent(entry) {
|
|
|
45122
45340
|
}
|
|
45123
45341
|
var MAX_LIVE_ENTRIES = 300;
|
|
45124
45342
|
function EventStream({ agentId, addonId, deviceId, defaultCategories, categories: legacyCategories, category: propsCategory, maxHeight = "max-h-96", limit = 50, showCategoryFilter = true, showFilters: _showFilters = false, liveBuffer: externalBuffer, onClose, className }) {
|
|
45343
|
+
const searchFieldRef = (0, react$1.useRef)(null);
|
|
45344
|
+
useSearchFieldHotkey(searchFieldRef);
|
|
45125
45345
|
const t = useUiText();
|
|
45126
45346
|
const defaultsArray = (0, react$1.useMemo)(() => defaultCategories ?? legacyCategories ?? DEFAULT_EVENT_CATEGORIES, [defaultCategories, legacyCategories]);
|
|
45127
45347
|
const defaultsKey = (0, react$1.useMemo)(() => [...defaultsArray].toSorted().join(","), [defaultsArray]);
|
|
@@ -45398,6 +45618,7 @@ function EventStream({ agentId, addonId, deviceId, defaultCategories, categories
|
|
|
45398
45618
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
45399
45619
|
className: "relative",
|
|
45400
45620
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
45621
|
+
ref: searchFieldRef,
|
|
45401
45622
|
type: "text",
|
|
45402
45623
|
value: searchText,
|
|
45403
45624
|
onChange: (e) => setSearchText(e.target.value),
|
|
@@ -45896,6 +46117,8 @@ function coldRuntimeStateSlice(capName) {
|
|
|
45896
46117
|
* deeper opens on demand. */
|
|
45897
46118
|
var DEFAULT_EXPAND_DEPTH = 1;
|
|
45898
46119
|
function StateValuesStream({ deviceId, defaultCaps, maxHeight = "max-h-96", onClose, className }) {
|
|
46120
|
+
const searchFieldRef = (0, react$1.useRef)(null);
|
|
46121
|
+
useSearchFieldHotkey(searchFieldRef);
|
|
45899
46122
|
const t = useUiText();
|
|
45900
46123
|
const [activeCaps, setActiveCaps] = (0, react$1.useState)((0, react$1.useMemo)(() => new Set(defaultCaps ?? []), [defaultCaps]));
|
|
45901
46124
|
const [searchText, setSearchText] = (0, react$1.useState)("");
|
|
@@ -46123,6 +46346,7 @@ function StateValuesStream({ deviceId, defaultCaps, maxHeight = "max-h-96", onCl
|
|
|
46123
46346
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
46124
46347
|
className: "relative",
|
|
46125
46348
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
46349
|
+
ref: searchFieldRef,
|
|
46126
46350
|
type: "text",
|
|
46127
46351
|
value: searchText,
|
|
46128
46352
|
onChange: (e) => setSearchText(e.target.value),
|
|
@@ -47512,6 +47736,42 @@ function resolveClimateTempStep(tempStep) {
|
|
|
47512
47736
|
if (typeof tempStep === "number" && Number.isFinite(tempStep) && tempStep > 0) return tempStep;
|
|
47513
47737
|
return 1;
|
|
47514
47738
|
}
|
|
47739
|
+
var TARGET_FALLBACK_INT_DIGITS = 2;
|
|
47740
|
+
var TARGET_MAX_DECIMALS = 3;
|
|
47741
|
+
function decimalPlaces(n) {
|
|
47742
|
+
if (!Number.isFinite(n)) return 0;
|
|
47743
|
+
for (let d = 0; d < TARGET_MAX_DECIMALS; d += 1) if (Math.abs(n * 10 ** d - Math.round(n * 10 ** d)) < 1e-9) return d;
|
|
47744
|
+
return TARGET_MAX_DECIMALS;
|
|
47745
|
+
}
|
|
47746
|
+
/**
|
|
47747
|
+
* Character count of the longest set-temperature the device can show, derived
|
|
47748
|
+
* from its range and step (5-30 step 0.5 reserves "30.5" = 4). The big target
|
|
47749
|
+
* number reserves this width so the − / + buttons do not move when the value
|
|
47750
|
+
* gains decimals or loses a digit.
|
|
47751
|
+
*/
|
|
47752
|
+
function climateTargetWidthChars(display) {
|
|
47753
|
+
const step = resolveClimateTempStep(display.tempStep);
|
|
47754
|
+
const bounds = [display.minTemp, display.maxTemp].filter((v) => typeof v === "number" && Number.isFinite(v));
|
|
47755
|
+
const intDigits = bounds.length === 0 ? TARGET_FALLBACK_INT_DIGITS : Math.max(...bounds.map((v) => String(Math.trunc(Math.abs(v))).length));
|
|
47756
|
+
const sign = bounds.some((v) => v < 0) ? 1 : 0;
|
|
47757
|
+
const decimals = Math.max(decimalPlaces(step), ...bounds.map(decimalPlaces));
|
|
47758
|
+
return sign + intDigits + (decimals > 0 ? 1 + decimals : 0);
|
|
47759
|
+
}
|
|
47760
|
+
/** Static class names so Tailwind generates them (no runtime-built classes). */
|
|
47761
|
+
var TARGET_MIN_WIDTH_CLASS = {
|
|
47762
|
+
1: "min-w-[1ch]",
|
|
47763
|
+
2: "min-w-[2ch]",
|
|
47764
|
+
3: "min-w-[3ch]",
|
|
47765
|
+
4: "min-w-[4ch]",
|
|
47766
|
+
5: "min-w-[5ch]",
|
|
47767
|
+
6: "min-w-[6ch]",
|
|
47768
|
+
7: "min-w-[7ch]",
|
|
47769
|
+
8: "min-w-[8ch]"
|
|
47770
|
+
};
|
|
47771
|
+
/** `min-w-[Nch]` + right alignment for the big target number. */
|
|
47772
|
+
function climateTargetWidthClass(display) {
|
|
47773
|
+
return `${TARGET_MIN_WIDTH_CLASS[Math.min(Math.max(climateTargetWidthChars(display), 1), 8)]} text-center`;
|
|
47774
|
+
}
|
|
47515
47775
|
function nextClimateTarget(current, deltaSteps, display) {
|
|
47516
47776
|
let next = current + deltaSteps * resolveClimateTempStep(display.tempStep);
|
|
47517
47777
|
const min = display.minTemp;
|
|
@@ -52736,6 +52996,9 @@ function PetFeederPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
52736
52996
|
const faultMsg = display.error && display.error !== "no_error" ? display.error : null;
|
|
52737
52997
|
const offline = display.status === "offline";
|
|
52738
52998
|
const onBatteries = display.status === "on_batteries";
|
|
52999
|
+
const onMains = display.powerSource === "mains";
|
|
53000
|
+
const mainsLabel = t("uiLibrary.petFeeder.mains", "Mains");
|
|
53001
|
+
const backupLabel = display.batteryPower === null ? null : t("uiLibrary.petFeeder.backupBattery", "Backup {{percent}}%", { percent: display.batteryPower });
|
|
52739
53002
|
if (layout === "compact") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
52740
53003
|
className: "inline-flex items-center gap-2",
|
|
52741
53004
|
children: [
|
|
@@ -52758,16 +53021,19 @@ function PetFeederPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
52758
53021
|
"aria-hidden": true
|
|
52759
53022
|
}), display.bowlFood ?? "—"]
|
|
52760
53023
|
}),
|
|
53024
|
+
onMains && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
53025
|
+
className: "inline-flex items-center gap-0.5 text-xs text-foreground-subtle",
|
|
53026
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Plug, {
|
|
53027
|
+
className: "h-3.5 w-3.5",
|
|
53028
|
+
"aria-hidden": true
|
|
53029
|
+
}), mainsLabel]
|
|
53030
|
+
}),
|
|
52761
53031
|
display.batteryPower !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
52762
53032
|
className: "inline-flex items-center gap-0.5 text-xs tabular-nums text-foreground-subtle",
|
|
52763
|
-
children: [
|
|
52764
|
-
|
|
52765
|
-
|
|
52766
|
-
|
|
52767
|
-
}),
|
|
52768
|
-
display.batteryPower,
|
|
52769
|
-
"%"
|
|
52770
|
-
]
|
|
53033
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BatteryMedium, {
|
|
53034
|
+
className: "h-3.5 w-3.5",
|
|
53035
|
+
"aria-hidden": true
|
|
53036
|
+
}), onMains ? backupLabel : `${display.batteryPower}%`]
|
|
52771
53037
|
}),
|
|
52772
53038
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
52773
53039
|
type: "button",
|
|
@@ -52817,9 +53083,13 @@ function PetFeederPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
52817
53083
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
52818
53084
|
className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-foreground-subtle",
|
|
52819
53085
|
children: [
|
|
53086
|
+
onMains && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Readout, {
|
|
53087
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Plug, { className: "h-3.5 w-3.5" }),
|
|
53088
|
+
label: mainsLabel
|
|
53089
|
+
}),
|
|
52820
53090
|
display.batteryPower !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Readout, {
|
|
52821
53091
|
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BatteryMedium, { className: "h-3.5 w-3.5" }),
|
|
52822
|
-
label: `${display.batteryPower}%`
|
|
53092
|
+
label: onMains ? backupLabel ?? "" : `${display.batteryPower}%`
|
|
52823
53093
|
}),
|
|
52824
53094
|
onBatteries && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
52825
53095
|
className: "inline-flex items-center gap-1 font-medium text-amber-500",
|
|
@@ -53899,7 +54169,7 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
53899
54169
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
53900
54170
|
className: "flex items-baseline gap-0.5",
|
|
53901
54171
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
53902
|
-
className: "text-4xl font-bold leading-none tabular-nums",
|
|
54172
|
+
className: cn("text-4xl font-bold leading-none tabular-nums", climateTargetWidthClass(display)),
|
|
53903
54173
|
style: { color: accent },
|
|
53904
54174
|
children: display.target
|
|
53905
54175
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -53940,7 +54210,7 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
53940
54210
|
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
53941
54211
|
className: "relative z-10 flex items-baseline gap-0.5",
|
|
53942
54212
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
53943
|
-
className: "text-4xl font-bold leading-none tabular-nums text-foreground",
|
|
54213
|
+
className: cn("text-4xl font-bold leading-none tabular-nums text-foreground", climateTargetWidthClass(display)),
|
|
53944
54214
|
children: display.currentTemp ?? "—"
|
|
53945
54215
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
53946
54216
|
className: "text-lg text-foreground-subtle",
|
|
@@ -54058,7 +54328,7 @@ function CompactSelect({ label, icon, value, options, disabled, onChange }) {
|
|
|
54058
54328
|
className: "text-[10px] font-semibold uppercase tracking-wider text-foreground-subtle",
|
|
54059
54329
|
children: label
|
|
54060
54330
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
54061
|
-
className: cn("flex items-center gap-1 overflow-hidden rounded-lg border border-border/60 bg-surface-subtle px-1.5 py-1.5", disabled && "opacity-40"),
|
|
54331
|
+
className: cn("flex items-center gap-1 overflow-hidden rounded-lg border border-border/60 bg-surface-subtle px-1.5 py-1.5 has-[:focus-visible]:border-primary", disabled && "opacity-40"),
|
|
54062
54332
|
children: [
|
|
54063
54333
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
54064
54334
|
className: "flex shrink-0",
|
|
@@ -60142,6 +60412,8 @@ function GenericModeList(props) {
|
|
|
60142
60412
|
defaultSize: fixedPageSize
|
|
60143
60413
|
});
|
|
60144
60414
|
const [searchInput, setSearchInput] = (0, react$1.useState)("");
|
|
60415
|
+
const searchRef = (0, react$1.useRef)(null);
|
|
60416
|
+
useSearchFieldHotkey(searchRef);
|
|
60145
60417
|
const debouncedSearch = useDebouncedString(searchInput, 300);
|
|
60146
60418
|
const [typeFilters, setTypeFilters] = (0, react$1.useState)(() => [...initialTypeFilters]);
|
|
60147
60419
|
const [page, setPage] = (0, react$1.useState)(1);
|
|
@@ -60212,6 +60484,7 @@ function GenericModeList(props) {
|
|
|
60212
60484
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
60213
60485
|
className: "relative",
|
|
60214
60486
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Search, { className: "absolute left-2 top-1/2 -translate-y-1/2 h-3 w-3 text-foreground-subtle pointer-events-none" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
60487
|
+
ref: searchRef,
|
|
60215
60488
|
type: "text",
|
|
60216
60489
|
value: searchInput,
|
|
60217
60490
|
onChange: (e) => setSearchInput(e.target.value),
|
|
@@ -61095,6 +61368,8 @@ function DeviceSelectorPicker({ value, onChange, includeLinked, onIncludeLinkedC
|
|
|
61095
61368
|
const t = useUiText();
|
|
61096
61369
|
const { data: rawDevices } = useDeviceManagerListAll({});
|
|
61097
61370
|
const [search, setSearch] = (0, react$1.useState)("");
|
|
61371
|
+
const searchRef = (0, react$1.useRef)(null);
|
|
61372
|
+
useSearchFieldHotkey(searchRef);
|
|
61098
61373
|
const devices = (0, react$1.useMemo)(() => {
|
|
61099
61374
|
const all = (rawDevices ?? []).map((d) => ({
|
|
61100
61375
|
id: d.id,
|
|
@@ -61225,6 +61500,7 @@ function DeviceSelectorPicker({ value, onChange, includeLinked, onIncludeLinkedC
|
|
|
61225
61500
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
61226
61501
|
className: "relative",
|
|
61227
61502
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Search, { className: "pointer-events-none absolute left-2 top-1/2 h-3 w-3 -translate-y-1/2 text-foreground-subtle" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
61503
|
+
ref: searchRef,
|
|
61228
61504
|
type: "search",
|
|
61229
61505
|
value: search,
|
|
61230
61506
|
onChange: (e) => setSearch(e.target.value),
|
|
@@ -62327,6 +62603,7 @@ function FilterBar({ filters, values, onChange, className }) {
|
|
|
62327
62603
|
placeholder: searchFilter.placeholder ?? t("uiLibrary.filterBar.search", "Search..."),
|
|
62328
62604
|
value: values[searchFilter.key] ?? "",
|
|
62329
62605
|
onChange: (e) => handleChange(searchFilter.key, e.target.value),
|
|
62606
|
+
searchHotkey: true,
|
|
62330
62607
|
leftSlot: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Search, { className: "h-3 w-3 text-foreground-subtle" }),
|
|
62331
62608
|
className: "flex-1"
|
|
62332
62609
|
}), hasNonSearchFilters && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
@@ -62388,6 +62665,7 @@ function FilterBar({ filters, values, onChange, className }) {
|
|
|
62388
62665
|
placeholder: filter.placeholder ?? t("uiLibrary.filterBar.search", "Search..."),
|
|
62389
62666
|
value: values[filter.key] ?? "",
|
|
62390
62667
|
onChange: (e) => handleChange(filter.key, e.target.value),
|
|
62668
|
+
searchHotkey: true,
|
|
62391
62669
|
leftSlot: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Search, { className: "h-3 w-3 text-foreground-subtle" }),
|
|
62392
62670
|
className: "w-48"
|
|
62393
62671
|
}, filter.key);
|
|
@@ -62921,7 +63199,7 @@ function mountAddonPage(PageComponent, options = {}) {
|
|
|
62921
63199
|
console.error(`[mountAddonPage] Element #${rootId} not found`);
|
|
62922
63200
|
return;
|
|
62923
63201
|
}
|
|
62924
|
-
(0, react_dom_client.createRoot)(root).render((0, react$1.createElement)(DevShell, {
|
|
63202
|
+
(0, react_dom_client.createRoot)(root).render((0, react$1.createElement)(HotkeyProvider, null, (0, react$1.createElement)(DevShell, {
|
|
62925
63203
|
serverUrl,
|
|
62926
63204
|
title,
|
|
62927
63205
|
children: ({ trpc, theme }) => (0, react$1.createElement)(PageComponent, {
|
|
@@ -62931,7 +63209,7 @@ function mountAddonPage(PageComponent, options = {}) {
|
|
|
62931
63209
|
console.log("[dev] navigate:", path);
|
|
62932
63210
|
}
|
|
62933
63211
|
})
|
|
62934
|
-
}));
|
|
63212
|
+
})));
|
|
62935
63213
|
}
|
|
62936
63214
|
//#endregion
|
|
62937
63215
|
//#region src/composites/node-picker.tsx
|
|
@@ -67243,6 +67521,8 @@ function zoneUnavailable(zones, t) {
|
|
|
67243
67521
|
if (zones.state === "failed") return t("uiLibrary.deviceFilterBar.zonesUnavailable", "zones unavailable");
|
|
67244
67522
|
}
|
|
67245
67523
|
function DeviceFilterBar({ filters, onFiltersChange, typeOptions, zoneOptions, locationOptions, zones, grouping, searchPlaceholder, searchLabel, disabled = false }) {
|
|
67524
|
+
const searchFieldRef = (0, react$1.useRef)(null);
|
|
67525
|
+
useSearchFieldHotkey(searchFieldRef);
|
|
67246
67526
|
const t = useUiText();
|
|
67247
67527
|
const [text, setText] = (0, react$1.useState)(filters.query);
|
|
67248
67528
|
const latest = (0, react$1.useRef)({
|
|
@@ -67272,6 +67552,7 @@ function DeviceFilterBar({ filters, onFiltersChange, typeOptions, zoneOptions, l
|
|
|
67272
67552
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
67273
67553
|
className: INPUT_SHELL,
|
|
67274
67554
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Search, { className: "h-3.5 w-3.5 shrink-0 text-foreground-subtle" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
67555
|
+
ref: searchFieldRef,
|
|
67275
67556
|
type: "text",
|
|
67276
67557
|
value: text,
|
|
67277
67558
|
placeholder: searchPlaceholder,
|
|
@@ -67397,6 +67678,64 @@ function withResolvedParentDetail(devices) {
|
|
|
67397
67678
|
return changed ? next : devices;
|
|
67398
67679
|
}
|
|
67399
67680
|
//#endregion
|
|
67681
|
+
//#region src/composites/device-picker/device-provider-label.ts
|
|
67682
|
+
/**
|
|
67683
|
+
* Who offers a device, as the picker draws it: "{addon name} ({integration
|
|
67684
|
+
* name})" — the integration part only when the device comes from a named
|
|
67685
|
+
* integration instance — otherwise just "{addon name}".
|
|
67686
|
+
*
|
|
67687
|
+
* Derived from what the UI already reads: the device's providing `addonId` and
|
|
67688
|
+
* its `integrationId` (fleet listing), the addon's manifest name
|
|
67689
|
+
* (`addons.list`) and the instance's name (`integrations.list`). Nothing here
|
|
67690
|
+
* knows a kind or an addon; a row whose provider cannot be resolved shows no
|
|
67691
|
+
* line at all rather than a guess.
|
|
67692
|
+
*/
|
|
67693
|
+
/** "Home Assistant (Casa)", or "Home Assistant" without an instance name. */
|
|
67694
|
+
function formatProviderLabel(addonName, integrationName) {
|
|
67695
|
+
const instance = integrationName?.trim() ?? "";
|
|
67696
|
+
return instance === "" || instance === addonName ? addonName : `${addonName} (${instance})`;
|
|
67697
|
+
}
|
|
67698
|
+
/** `deviceId → provider label`; a device whose addon is unknown is absent. */
|
|
67699
|
+
function providerLabelsOf(fleet, addons, integrations) {
|
|
67700
|
+
const addonName = /* @__PURE__ */ new Map();
|
|
67701
|
+
for (const a of addons) if (a.name !== void 0 && a.name !== "") addonName.set(a.id, a.name);
|
|
67702
|
+
const instanceName = new Map(integrations.map((i) => [i.id, i.name]));
|
|
67703
|
+
const out = /* @__PURE__ */ new Map();
|
|
67704
|
+
for (const d of fleet) {
|
|
67705
|
+
const addon = addonName.get(d.addonId);
|
|
67706
|
+
if (addon === void 0) continue;
|
|
67707
|
+
const instance = d.integrationId === void 0 ? null : instanceName.get(d.integrationId) ?? null;
|
|
67708
|
+
out.set(d.id, formatProviderLabel(addon, instance));
|
|
67709
|
+
}
|
|
67710
|
+
return out;
|
|
67711
|
+
}
|
|
67712
|
+
var NONE = /* @__PURE__ */ new Map();
|
|
67713
|
+
var PROVIDER_LABELS_STALE_MS = 6e4;
|
|
67714
|
+
/**
|
|
67715
|
+
* The labels for every device in the fleet; empty until the reads answer (a
|
|
67716
|
+
* failed read draws no provider line — it is a hint, never a gate).
|
|
67717
|
+
*/
|
|
67718
|
+
function useDeviceProviderLabels(enabled) {
|
|
67719
|
+
const trpcClient = useSystem().trpcClient;
|
|
67720
|
+
const query = (0, _tanstack_react_query.useQuery)({
|
|
67721
|
+
queryKey: ["ui-library", "device-provider-labels"],
|
|
67722
|
+
enabled,
|
|
67723
|
+
staleTime: PROVIDER_LABELS_STALE_MS,
|
|
67724
|
+
queryFn: async () => {
|
|
67725
|
+
const [fleet, addons, integrations] = await Promise.all([
|
|
67726
|
+
trpcClient.deviceManager.listAll.query({ projection: "slim" }),
|
|
67727
|
+
trpcClient.addons.list.query(),
|
|
67728
|
+
trpcClient.integrations.list.query()
|
|
67729
|
+
]);
|
|
67730
|
+
return providerLabelsOf(fleet, addons.map((a) => ({
|
|
67731
|
+
id: a.manifest.id,
|
|
67732
|
+
name: a.manifest.name
|
|
67733
|
+
})), integrations);
|
|
67734
|
+
}
|
|
67735
|
+
});
|
|
67736
|
+
return (0, react$1.useMemo)(() => query.data ?? NONE, [query.data]);
|
|
67737
|
+
}
|
|
67738
|
+
//#endregion
|
|
67400
67739
|
//#region src/composites/device-picker/device-picker-grouping.ts
|
|
67401
67740
|
/**
|
|
67402
67741
|
* The admin side of the device picker's "Group by" (Zone | Location | None):
|
|
@@ -67761,7 +68100,7 @@ function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, s
|
|
|
67761
68100
|
children: [
|
|
67762
68101
|
listState === "pending" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DevicesLoadingLine, { noun: plural }),
|
|
67763
68102
|
listState === "failed" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DevicesFailedLine, { noun: noun === "camera" ? "devices" : plural }),
|
|
67764
|
-
!(selection === "single" && chips.length === 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
68103
|
+
!(selection === "single" && chips.length === 0 && showAll) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
67765
68104
|
className: "flex flex-wrap items-center gap-1",
|
|
67766
68105
|
children: [
|
|
67767
68106
|
chips.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -67825,6 +68164,7 @@ function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, s
|
|
|
67825
68164
|
]
|
|
67826
68165
|
});
|
|
67827
68166
|
}
|
|
68167
|
+
var NO_PROVIDERS = /* @__PURE__ */ new Map();
|
|
67828
68168
|
/**
|
|
67829
68169
|
* The device picker's panel: mounted only while the list is open, so the zone
|
|
67830
68170
|
* read below happens only then (for Zone grouping and the Zone filter).
|
|
@@ -67842,8 +68182,10 @@ function GroupedPickerPanel(props) {
|
|
|
67842
68182
|
stored.setGroupBy(next);
|
|
67843
68183
|
};
|
|
67844
68184
|
const zones = usePickerSiteZones(true);
|
|
68185
|
+
const providers = useDeviceProviderLabels(true);
|
|
67845
68186
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PickerPanel, {
|
|
67846
68187
|
...props,
|
|
68188
|
+
providers,
|
|
67847
68189
|
grouping: {
|
|
67848
68190
|
groupBy,
|
|
67849
68191
|
onGroupByChange,
|
|
@@ -67864,7 +68206,7 @@ var NO_ZONES = {
|
|
|
67864
68206
|
zones: []
|
|
67865
68207
|
};
|
|
67866
68208
|
/** The searchable, grouped list — rendered one page at a time. */
|
|
67867
|
-
function PickerPanel({ devices, value, onChange, annotate, groupTitle, noun, disabled, locked, lockedNote, showAll, selection, grouping }) {
|
|
68209
|
+
function PickerPanel({ devices, value, onChange, annotate, groupTitle, noun, disabled, locked, lockedNote, showAll, selection, grouping, providers = NO_PROVIDERS }) {
|
|
67868
68210
|
const t = useUiText();
|
|
67869
68211
|
const radioName = (0, react$1.useId)();
|
|
67870
68212
|
const [filters, setFilters] = (0, react$1.useState)(PICKER_NO_FILTERS);
|
|
@@ -67967,7 +68309,8 @@ function PickerPanel({ devices, value, onChange, annotate, groupTitle, noun, dis
|
|
|
67967
68309
|
locked,
|
|
67968
68310
|
lockedNote,
|
|
67969
68311
|
selection,
|
|
67970
|
-
radioName
|
|
68312
|
+
radioName,
|
|
68313
|
+
providers
|
|
67971
68314
|
}, row.key)), page.remaining > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
67972
68315
|
type: "button",
|
|
67973
68316
|
className: "mt-1 w-full rounded border border-border py-1 text-[11px] text-foreground-subtle hover:bg-surface-hover",
|
|
@@ -67997,7 +68340,7 @@ function secondLine(device, groupBy) {
|
|
|
67997
68340
|
].filter((part) => part !== null);
|
|
67998
68341
|
return parts.length > 0 ? parts.join(" · ") : void 0;
|
|
67999
68342
|
}
|
|
68000
|
-
function PickerRowView({ row, value, onChange, onToggleGroup, annotate, groupTitle, groupBy, disabled, locked, lockedNote, selection, radioName }) {
|
|
68343
|
+
function PickerRowView({ row, value, onChange, onToggleGroup, annotate, groupTitle, groupBy, disabled, locked, lockedNote, selection, radioName, providers }) {
|
|
68001
68344
|
const t = useUiText();
|
|
68002
68345
|
const single = selection === "single";
|
|
68003
68346
|
if (row.kind === "header") {
|
|
@@ -68049,6 +68392,8 @@ function PickerRowView({ row, value, onChange, onToggleGroup, annotate, groupTit
|
|
|
68049
68392
|
const isLocked = locked.has(row.device.id);
|
|
68050
68393
|
const note = isLocked ? lockedNote : annotate?.(row.device.id);
|
|
68051
68394
|
const second = secondLine(row.device, groupBy);
|
|
68395
|
+
const rowId = row.device.id;
|
|
68396
|
+
const provider = typeof rowId === "number" ? providers.get(rowId) : void 0;
|
|
68052
68397
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
68053
68398
|
className: "flex cursor-pointer items-center gap-2.5 px-3 py-1 text-xs text-foreground hover:bg-surface-hover",
|
|
68054
68399
|
"data-testid": "device-picker-row",
|
|
@@ -68072,6 +68417,11 @@ function PickerRowView({ row, value, onChange, onToggleGroup, annotate, groupTit
|
|
|
68072
68417
|
className: "truncate text-[10px] text-foreground-subtle",
|
|
68073
68418
|
children: second
|
|
68074
68419
|
}),
|
|
68420
|
+
provider !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
68421
|
+
className: "truncate text-[10px] text-foreground-subtle",
|
|
68422
|
+
"data-testid": "device-picker-provider",
|
|
68423
|
+
children: provider
|
|
68424
|
+
}),
|
|
68075
68425
|
note !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
68076
68426
|
className: isLocked ? "text-[10px] text-foreground-subtle" : "text-[10px] text-warning",
|
|
68077
68427
|
children: note
|
|
@@ -71235,6 +71585,7 @@ exports.ExtensionsPanel = ExtensionsPanel;
|
|
|
71235
71585
|
exports.ExternalLink = ExternalLink;
|
|
71236
71586
|
exports.Eye = Eye;
|
|
71237
71587
|
exports.EyeOff = EyeOff;
|
|
71588
|
+
exports.FALLBACK_SCOPE = FALLBACK_SCOPE;
|
|
71238
71589
|
exports.FAST_ICON_GLYPHS = FAST_ICON_GLYPHS;
|
|
71239
71590
|
exports.FILL = FILL;
|
|
71240
71591
|
exports.FLEET_LIST_GC_TIME_MS = FLEET_LIST_GC_TIME_MS;
|
|
@@ -71286,6 +71637,8 @@ exports.History = History;
|
|
|
71286
71637
|
exports.HistoryChart = HistoryChart;
|
|
71287
71638
|
exports.HistoryWindowBar = HistoryWindowBar;
|
|
71288
71639
|
exports.HlsVideo = HlsVideo;
|
|
71640
|
+
exports.HotkeyProvider = HotkeyProvider;
|
|
71641
|
+
exports.HotkeyScope = HotkeyScope;
|
|
71289
71642
|
exports.House = House;
|
|
71290
71643
|
exports.HoverZoomImage = HoverZoomImage;
|
|
71291
71644
|
exports.HubSessionTestCard = HubSessionTestCard;
|
|
@@ -71717,6 +72070,7 @@ exports.defaultUiText = defaultUiText;
|
|
|
71717
72070
|
exports.deriveDeviceKind = deriveDeviceKind;
|
|
71718
72071
|
exports.deriveWebrtcDebugStats = deriveWebrtcDebugStats;
|
|
71719
72072
|
exports.describeDeviceSelector = describeDeviceSelector;
|
|
72073
|
+
exports.detectPlatform = detectPlatform;
|
|
71720
72074
|
exports.deviceIntegrationResolver = deviceIntegrationResolver;
|
|
71721
72075
|
exports.deviceListStateOf = deviceListStateOf;
|
|
71722
72076
|
exports.deviceMatchesFilter = deviceMatchesFilter;
|
|
@@ -71813,6 +72167,7 @@ exports.loadRemoteBundle = loadRemoteBundle;
|
|
|
71813
72167
|
exports.lucideExportName = lucideExportName;
|
|
71814
72168
|
exports.makeScrubBridge = makeScrubBridge;
|
|
71815
72169
|
exports.mapRootDetectorPlacements = mapRootDetectorPlacements;
|
|
72170
|
+
exports.matchesCombo = matchesCombo;
|
|
71816
72171
|
exports.mergeSecretForSubmit = mergeSecretForSubmit;
|
|
71817
72172
|
exports.metadataEntries = metadataEntries;
|
|
71818
72173
|
exports.metadataString = metadataString;
|
|
@@ -71844,6 +72199,7 @@ exports.pageSizeLabel = pageSizeLabel;
|
|
|
71844
72199
|
exports.paginateRows = paginateRows;
|
|
71845
72200
|
exports.parentDetailOf = parentDetailOf;
|
|
71846
72201
|
exports.parseCapKey = parseCapKey;
|
|
72202
|
+
exports.parseCombo = parseCombo;
|
|
71847
72203
|
exports.parseConsumableLevel = parseConsumableLevel;
|
|
71848
72204
|
exports.parseIceCandidate = parseIceCandidate;
|
|
71849
72205
|
exports.parsePickerGroupBy = parsePickerGroupBy;
|
|
@@ -72454,6 +72810,7 @@ exports.useHeatingValveGetStatus = useHeatingValveGetStatus;
|
|
|
72454
72810
|
exports.useHistoryPanels = useHistoryPanels;
|
|
72455
72811
|
exports.useHistoryReader = useHistoryReader;
|
|
72456
72812
|
exports.useHistoryReads = useHistoryReads;
|
|
72813
|
+
exports.useHotkey = useHotkey;
|
|
72457
72814
|
exports.useHumidifierGetStatus = useHumidifierGetStatus;
|
|
72458
72815
|
exports.useHumidifierSetMode = useHumidifierSetMode;
|
|
72459
72816
|
exports.useHumidifierSetOn = useHumidifierSetOn;
|
|
@@ -72481,6 +72838,8 @@ exports.useIntercomPushTalkAudio = useIntercomPushTalkAudio;
|
|
|
72481
72838
|
exports.useIntercomStartSession = useIntercomStartSession;
|
|
72482
72839
|
exports.useIntercomStartTalkSession = useIntercomStartTalkSession;
|
|
72483
72840
|
exports.useIntercomStopSession = useIntercomStopSession;
|
|
72841
|
+
exports.useIrTransmitterGetInfo = useIrTransmitterGetInfo;
|
|
72842
|
+
exports.useIrTransmitterSend = useIrTransmitterSend;
|
|
72484
72843
|
exports.useIsMidWidth = useIsMidWidth;
|
|
72485
72844
|
exports.useIsMobile = useIsMobile;
|
|
72486
72845
|
exports.useItemFormUrl = useItemFormUrl;
|
|
@@ -73048,6 +73407,7 @@ exports.useScriptRunnerGetStatus = useScriptRunnerGetStatus;
|
|
|
73048
73407
|
exports.useScriptRunnerRun = useScriptRunnerRun;
|
|
73049
73408
|
exports.useScriptRunnerStop = useScriptRunnerStop;
|
|
73050
73409
|
exports.useScrubController = useScrubController;
|
|
73410
|
+
exports.useSearchFieldHotkey = useSearchFieldHotkey;
|
|
73051
73411
|
exports.useServerManagementApplyServerUpdate = useServerManagementApplyServerUpdate;
|
|
73052
73412
|
exports.useServerManagementCheckServerUpdate = useServerManagementCheckServerUpdate;
|
|
73053
73413
|
exports.useServerManagementGetServerPackageStatus = useServerManagementGetServerPackageStatus;
|