@camstack/ui-library 1.2.343 → 1.2.345
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/breadcrumb.d.ts +1 -1
- 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/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 +356 -17
- package/dist/index.d.ts +1 -0
- package/dist/index.js +349 -18
- package/dist/primitives/input.d.ts +6 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -5950,6 +5950,191 @@ function useDisplayIconResolver() {
|
|
|
5950
5950
|
return (0, react$1.useMemo)(() => (name) => resolveDisplayIcon(name), [useIconGlyphsVersion()]);
|
|
5951
5951
|
}
|
|
5952
5952
|
//#endregion
|
|
5953
|
+
//#region src/hotkeys/combo.ts
|
|
5954
|
+
function parseCombo(combo) {
|
|
5955
|
+
const parts = combo.split("+");
|
|
5956
|
+
const key = parts[parts.length - 1] ?? "";
|
|
5957
|
+
const modifiers = parts.slice(0, -1);
|
|
5958
|
+
return {
|
|
5959
|
+
mod: modifiers.includes("mod"),
|
|
5960
|
+
shift: modifiers.includes("shift"),
|
|
5961
|
+
alt: modifiers.includes("alt"),
|
|
5962
|
+
key
|
|
5963
|
+
};
|
|
5964
|
+
}
|
|
5965
|
+
function detectPlatform() {
|
|
5966
|
+
if (typeof navigator === "undefined") return "other";
|
|
5967
|
+
return /mac|iphone|ipad/i.test(navigator.platform) ? "mac" : "other";
|
|
5968
|
+
}
|
|
5969
|
+
function matchesCombo(event, combo, platform) {
|
|
5970
|
+
const want = parseCombo(combo);
|
|
5971
|
+
const modDown = platform === "mac" ? event.metaKey : event.ctrlKey;
|
|
5972
|
+
const strayDown = platform === "mac" ? event.ctrlKey : event.metaKey;
|
|
5973
|
+
return event.key.toLowerCase() === want.key && modDown === want.mod && !strayDown && event.shiftKey === want.shift && event.altKey === want.alt;
|
|
5974
|
+
}
|
|
5975
|
+
//#endregion
|
|
5976
|
+
//#region src/hotkeys/hotkey-provider.tsx
|
|
5977
|
+
var PAGE_SCOPE = "page";
|
|
5978
|
+
/** Asked last, after the page: a global handler that yields to whatever the page registers. */
|
|
5979
|
+
var FALLBACK_SCOPE = "fallback";
|
|
5980
|
+
var RegistryContext = (0, react$1.createContext)(null);
|
|
5981
|
+
var ScopeContext = (0, react$1.createContext)(PAGE_SCOPE);
|
|
5982
|
+
function isTypingTarget(target) {
|
|
5983
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
5984
|
+
if (target.isContentEditable) return true;
|
|
5985
|
+
return target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT";
|
|
5986
|
+
}
|
|
5987
|
+
/**
|
|
5988
|
+
* Scopes stack in mount order; the newest enabled one is asked first. A `modal`
|
|
5989
|
+
* scope (an open dialog) ends the walk: what sits under it is not reachable.
|
|
5990
|
+
* Inside a scope the newest binding wins; a handler that returns `false` hands
|
|
5991
|
+
* over to the next one.
|
|
5992
|
+
*/
|
|
5993
|
+
function dispatch(state, event, platform) {
|
|
5994
|
+
if (event.isComposing || event.keyCode === 229) return;
|
|
5995
|
+
const typing = isTypingTarget(event.target);
|
|
5996
|
+
const order = [
|
|
5997
|
+
...[...state.scopes].reverse().map((s) => s.id),
|
|
5998
|
+
PAGE_SCOPE,
|
|
5999
|
+
FALLBACK_SCOPE
|
|
6000
|
+
];
|
|
6001
|
+
for (const scopeId of order) {
|
|
6002
|
+
const candidates = state.bindings.filter((b) => b.scope === scopeId && matchesCombo(event, b.combo, platform)).reverse();
|
|
6003
|
+
for (const binding of candidates) {
|
|
6004
|
+
if (typing && !binding.allowInInput) continue;
|
|
6005
|
+
if (binding.handlerRef.current(event) === false) continue;
|
|
6006
|
+
if (binding.preventDefault) event.preventDefault();
|
|
6007
|
+
return;
|
|
6008
|
+
}
|
|
6009
|
+
if (state.scopes.find((s) => s.id === scopeId)?.modal) return;
|
|
6010
|
+
}
|
|
6011
|
+
}
|
|
6012
|
+
function HotkeyProvider({ children, platform }) {
|
|
6013
|
+
const stateRef = (0, react$1.useRef)({
|
|
6014
|
+
bindings: [],
|
|
6015
|
+
scopes: []
|
|
6016
|
+
});
|
|
6017
|
+
const effectivePlatform = platform ?? detectPlatform();
|
|
6018
|
+
const registry = (0, react$1.useMemo)(() => ({
|
|
6019
|
+
addBinding: (binding) => {
|
|
6020
|
+
stateRef.current = {
|
|
6021
|
+
...stateRef.current,
|
|
6022
|
+
bindings: [...stateRef.current.bindings, binding]
|
|
6023
|
+
};
|
|
6024
|
+
return () => {
|
|
6025
|
+
stateRef.current = {
|
|
6026
|
+
...stateRef.current,
|
|
6027
|
+
bindings: stateRef.current.bindings.filter((b) => b.id !== binding.id)
|
|
6028
|
+
};
|
|
6029
|
+
};
|
|
6030
|
+
},
|
|
6031
|
+
addScope: (scope) => {
|
|
6032
|
+
stateRef.current = {
|
|
6033
|
+
...stateRef.current,
|
|
6034
|
+
scopes: [...stateRef.current.scopes, scope]
|
|
6035
|
+
};
|
|
6036
|
+
return () => {
|
|
6037
|
+
stateRef.current = {
|
|
6038
|
+
...stateRef.current,
|
|
6039
|
+
scopes: stateRef.current.scopes.filter((s) => s !== scope)
|
|
6040
|
+
};
|
|
6041
|
+
};
|
|
6042
|
+
}
|
|
6043
|
+
}), []);
|
|
6044
|
+
(0, react$1.useEffect)(() => {
|
|
6045
|
+
const onKeyDown = (event) => dispatch(stateRef.current, event, effectivePlatform);
|
|
6046
|
+
window.addEventListener("keydown", onKeyDown, true);
|
|
6047
|
+
return () => window.removeEventListener("keydown", onKeyDown, true);
|
|
6048
|
+
}, [effectivePlatform]);
|
|
6049
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RegistryContext.Provider, {
|
|
6050
|
+
value: registry,
|
|
6051
|
+
children
|
|
6052
|
+
});
|
|
6053
|
+
}
|
|
6054
|
+
var nextBindingId = 0;
|
|
6055
|
+
/**
|
|
6056
|
+
* Bind a combo for the life of the component. Without a `HotkeyProvider` above
|
|
6057
|
+
* it does nothing, so the browser keeps its own shortcut.
|
|
6058
|
+
*/
|
|
6059
|
+
function useHotkey(combo, handler, options = {}) {
|
|
6060
|
+
const registry = (0, react$1.useContext)(RegistryContext);
|
|
6061
|
+
const inheritedScope = (0, react$1.useContext)(ScopeContext);
|
|
6062
|
+
const handlerRef = (0, react$1.useRef)(handler);
|
|
6063
|
+
handlerRef.current = handler;
|
|
6064
|
+
const scope = options.scope ?? inheritedScope;
|
|
6065
|
+
const enabled = options.enabled ?? true;
|
|
6066
|
+
const preventDefault = options.preventDefault ?? true;
|
|
6067
|
+
const allowInInput = options.allowInInput ?? parseCombo(combo).mod;
|
|
6068
|
+
(0, react$1.useEffect)(() => {
|
|
6069
|
+
if (!registry || !enabled) return void 0;
|
|
6070
|
+
nextBindingId += 1;
|
|
6071
|
+
return registry.addBinding({
|
|
6072
|
+
id: nextBindingId,
|
|
6073
|
+
combo,
|
|
6074
|
+
scope,
|
|
6075
|
+
handlerRef,
|
|
6076
|
+
preventDefault,
|
|
6077
|
+
allowInInput
|
|
6078
|
+
});
|
|
6079
|
+
}, [
|
|
6080
|
+
registry,
|
|
6081
|
+
enabled,
|
|
6082
|
+
combo,
|
|
6083
|
+
scope,
|
|
6084
|
+
preventDefault,
|
|
6085
|
+
allowInInput
|
|
6086
|
+
]);
|
|
6087
|
+
}
|
|
6088
|
+
/** Bindings made below this component belong to `id`; while enabled it is the topmost scope. */
|
|
6089
|
+
function HotkeyScope({ id, children, enabled = true, modal = false }) {
|
|
6090
|
+
const registry = (0, react$1.useContext)(RegistryContext);
|
|
6091
|
+
(0, react$1.useEffect)(() => {
|
|
6092
|
+
if (!registry || !enabled) return void 0;
|
|
6093
|
+
return registry.addScope({
|
|
6094
|
+
id,
|
|
6095
|
+
modal
|
|
6096
|
+
});
|
|
6097
|
+
}, [
|
|
6098
|
+
registry,
|
|
6099
|
+
enabled,
|
|
6100
|
+
id,
|
|
6101
|
+
modal
|
|
6102
|
+
]);
|
|
6103
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScopeContext.Provider, {
|
|
6104
|
+
value: id,
|
|
6105
|
+
children
|
|
6106
|
+
});
|
|
6107
|
+
}
|
|
6108
|
+
//#endregion
|
|
6109
|
+
//#region src/hotkeys/use-search-field-hotkey.ts
|
|
6110
|
+
function isReachable(el) {
|
|
6111
|
+
return el.isConnected && !el.disabled && el.getClientRects().length > 0;
|
|
6112
|
+
}
|
|
6113
|
+
/**
|
|
6114
|
+
* Register a search field: ⌘F / Ctrl+F and ⌘K / Ctrl+K focus and select it.
|
|
6115
|
+
* A field that is hidden or disabled declines, so the browser's find still runs.
|
|
6116
|
+
* The library's search inputs call this themselves.
|
|
6117
|
+
*/
|
|
6118
|
+
function useSearchFieldHotkey(ref, options = {}) {
|
|
6119
|
+
const enabled = options.enabled ?? true;
|
|
6120
|
+
const scope = options.scope;
|
|
6121
|
+
const focusField = () => {
|
|
6122
|
+
const el = ref.current;
|
|
6123
|
+
if (!el || !isReachable(el)) return false;
|
|
6124
|
+
el.focus();
|
|
6125
|
+
el.select();
|
|
6126
|
+
return true;
|
|
6127
|
+
};
|
|
6128
|
+
useHotkey("mod+f", focusField, {
|
|
6129
|
+
enabled,
|
|
6130
|
+
scope
|
|
6131
|
+
});
|
|
6132
|
+
useHotkey("mod+k", focusField, {
|
|
6133
|
+
enabled,
|
|
6134
|
+
scope
|
|
6135
|
+
});
|
|
6136
|
+
}
|
|
6137
|
+
//#endregion
|
|
5953
6138
|
//#region ../../node_modules/clsx/dist/clsx.mjs
|
|
5954
6139
|
function r(e) {
|
|
5955
6140
|
var t, f, n = "";
|
|
@@ -9689,6 +9874,8 @@ function windowRows(rows, offsets, total, scrollTop) {
|
|
|
9689
9874
|
};
|
|
9690
9875
|
}
|
|
9691
9876
|
function IconPicker({ value, onPick, fallbackGlyph, labels, extraWords, clearGlyph, labelFor, categoryLabel = _camstack_types.iconCategoryLabel, disabled = false }) {
|
|
9877
|
+
const searchFieldRef = (0, react$1.useRef)(null);
|
|
9878
|
+
useSearchFieldHotkey(searchFieldRef);
|
|
9692
9879
|
const [open, setOpen] = (0, react$1.useState)(false);
|
|
9693
9880
|
const [query, setQuery] = (0, react$1.useState)("");
|
|
9694
9881
|
const [scrollTop, setScrollTop] = (0, react$1.useState)(0);
|
|
@@ -9749,6 +9936,7 @@ function IconPicker({ value, onPick, fallbackGlyph, labels, extraWords, clearGly
|
|
|
9749
9936
|
className: "w-full",
|
|
9750
9937
|
children: [
|
|
9751
9938
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
9939
|
+
ref: searchFieldRef,
|
|
9752
9940
|
type: "search",
|
|
9753
9941
|
value: query,
|
|
9754
9942
|
autoFocus: true,
|
|
@@ -10007,7 +10195,14 @@ var inputVariants = cva("h-7 w-full px-2.5 text-xs bg-surface border rounded-md
|
|
|
10007
10195
|
} },
|
|
10008
10196
|
defaultVariants: { state: "default" }
|
|
10009
10197
|
});
|
|
10010
|
-
var Input = (0, react$1.forwardRef)(({ className, state, leftSlot, rightSlot, ...props }, ref) => {
|
|
10198
|
+
var Input = (0, react$1.forwardRef)(({ className, state, leftSlot, rightSlot, searchHotkey, ...props }, ref) => {
|
|
10199
|
+
const innerRef = (0, react$1.useRef)(null);
|
|
10200
|
+
const setRef = (0, react$1.useCallback)((node) => {
|
|
10201
|
+
innerRef.current = node;
|
|
10202
|
+
if (typeof ref === "function") ref(node);
|
|
10203
|
+
else if (ref) ref.current = node;
|
|
10204
|
+
}, [ref]);
|
|
10205
|
+
useSearchFieldHotkey(innerRef, { enabled: searchHotkey ?? props.type === "search" });
|
|
10011
10206
|
if (leftSlot || rightSlot) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10012
10207
|
className: cn("relative flex items-center", className),
|
|
10013
10208
|
children: [
|
|
@@ -10016,7 +10211,7 @@ var Input = (0, react$1.forwardRef)(({ className, state, leftSlot, rightSlot, ..
|
|
|
10016
10211
|
children: leftSlot
|
|
10017
10212
|
}),
|
|
10018
10213
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
10019
|
-
ref,
|
|
10214
|
+
ref: setRef,
|
|
10020
10215
|
className: cn(inputVariants({ state }), leftSlot && "pl-8", rightSlot && "pr-8", className),
|
|
10021
10216
|
...props
|
|
10022
10217
|
}),
|
|
@@ -10027,7 +10222,7 @@ var Input = (0, react$1.forwardRef)(({ className, state, leftSlot, rightSlot, ..
|
|
|
10027
10222
|
]
|
|
10028
10223
|
});
|
|
10029
10224
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
10030
|
-
ref,
|
|
10225
|
+
ref: setRef,
|
|
10031
10226
|
className: cn(inputVariants({ state }), className),
|
|
10032
10227
|
...props
|
|
10033
10228
|
});
|
|
@@ -26510,6 +26705,8 @@ function TrackedToggles({ options, tracked, nameOf, onToggle }) {
|
|
|
26510
26705
|
});
|
|
26511
26706
|
}
|
|
26512
26707
|
function AssociationSlotCard({ deviceId, row, onSaved, readOnly = false }) {
|
|
26708
|
+
const searchFieldRef = (0, react$1.useRef)(null);
|
|
26709
|
+
useSearchFieldHotkey(searchFieldRef);
|
|
26513
26710
|
const t = useUiText();
|
|
26514
26711
|
const descriptor = _camstack_types.EXTENSION_SLOTS[row.slot].descriptor;
|
|
26515
26712
|
const saved = row.value.deviceIds;
|
|
@@ -26641,6 +26838,7 @@ function AssociationSlotCard({ deviceId, row, onSaved, readOnly = false }) {
|
|
|
26641
26838
|
})]
|
|
26642
26839
|
}),
|
|
26643
26840
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
26841
|
+
ref: searchFieldRef,
|
|
26644
26842
|
type: "search",
|
|
26645
26843
|
"aria-label": t("uiLibrary.associationSlotCard.searchDevices", "Search devices"),
|
|
26646
26844
|
value: search,
|
|
@@ -29014,8 +29212,8 @@ var contentVariants = cva("relative fixed left-1/2 top-1/2 -translate-x-1/2 -tra
|
|
|
29014
29212
|
* flex chain does not resolve without it on iOS Safari.
|
|
29015
29213
|
*/
|
|
29016
29214
|
scroll: {
|
|
29017
|
-
content: "overflow-y-auto",
|
|
29018
|
-
body: "flex flex-col overflow-hidden"
|
|
29215
|
+
content: "overflow-y-auto overscroll-contain",
|
|
29216
|
+
body: "flex flex-col overflow-hidden overscroll-contain"
|
|
29019
29217
|
},
|
|
29020
29218
|
placement: {
|
|
29021
29219
|
center: "",
|
|
@@ -29089,7 +29287,12 @@ var DialogContent = (0, react$1.forwardRef)(({ className, width, placement, scro
|
|
|
29089
29287
|
"aria-label": t("uiLibrary.dialog.close", "Close"),
|
|
29090
29288
|
className: "absolute right-4 top-4 z-10 shrink-0",
|
|
29091
29289
|
onClick: () => setOpen(false)
|
|
29092
|
-
}),
|
|
29290
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HotkeyScope, {
|
|
29291
|
+
id: contentId,
|
|
29292
|
+
modal: true,
|
|
29293
|
+
enabled: open,
|
|
29294
|
+
children
|
|
29295
|
+
})]
|
|
29093
29296
|
});
|
|
29094
29297
|
});
|
|
29095
29298
|
DialogContent.displayName = "DialogContent";
|
|
@@ -29119,7 +29322,7 @@ function DialogHeader({ className, ...props }) {
|
|
|
29119
29322
|
*/
|
|
29120
29323
|
function DialogBody({ className, ...props }) {
|
|
29121
29324
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
29122
|
-
className: cn("flex-1 min-h-0 overflow-y-auto", className),
|
|
29325
|
+
className: cn("flex-1 min-h-0 overflow-y-auto overscroll-contain", className),
|
|
29123
29326
|
...props
|
|
29124
29327
|
});
|
|
29125
29328
|
}
|
|
@@ -36348,7 +36551,7 @@ function RoleNodePill({ roleLabel, roleId, nodeName, colorId, emphasized = false
|
|
|
36348
36551
|
* ```tsx
|
|
36349
36552
|
* <Breadcrumb items={[
|
|
36350
36553
|
* { label: 'Integrations', onClick: () => navigate('/integrations') },
|
|
36351
|
-
* { label: 'RTSP Cameras', onClick: () => navigate('/integrations/int_0001') },
|
|
36554
|
+
* { label: 'RTSP Cameras', onClick: () => navigate('/integrations/provider-rtsp?instance=int_0001') },
|
|
36352
36555
|
* { label: 'Front Door' },
|
|
36353
36556
|
* ]} />
|
|
36354
36557
|
* ```
|
|
@@ -41956,6 +42159,8 @@ function MultiSelectDropdown({ label, options, selected, onChange }) {
|
|
|
41956
42159
|
var CHIP_ON = "rounded-full border border-primary bg-primary/10 px-2 py-0.5 text-[10px] text-foreground";
|
|
41957
42160
|
var CHIP_OFF = "rounded-full border border-border px-2 py-0.5 text-[10px] text-foreground-muted hover:bg-surface-hover";
|
|
41958
42161
|
function DataTableFilterBar({ state, facets, labels, onSearch, onToggleFacet, onClear, facetLabel, groups = [], onToggleGroupFacet }) {
|
|
42162
|
+
const searchRef = (0, react$1.useRef)(null);
|
|
42163
|
+
useSearchFieldHotkey(searchRef);
|
|
41959
42164
|
const active = isDataTableFilterActive(state);
|
|
41960
42165
|
const showFacets = facets.length > 1;
|
|
41961
42166
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -41964,6 +42169,7 @@ function DataTableFilterBar({ state, facets, labels, onSearch, onToggleFacet, on
|
|
|
41964
42169
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
41965
42170
|
className: "relative min-w-[12rem] flex-1",
|
|
41966
42171
|
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", {
|
|
42172
|
+
ref: searchRef,
|
|
41967
42173
|
type: "search",
|
|
41968
42174
|
value: state.search,
|
|
41969
42175
|
onChange: (e) => onSearch(e.target.value),
|
|
@@ -44194,6 +44400,8 @@ var TEXT_DEBOUNCE_MS = 300;
|
|
|
44194
44400
|
var TOP_LOAD_THRESHOLD_PX = 24;
|
|
44195
44401
|
var BYTES_PER_MB = 1024 * 1024;
|
|
44196
44402
|
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 }) {
|
|
44403
|
+
const searchFieldRef = (0, react$1.useRef)(null);
|
|
44404
|
+
useSearchFieldHotkey(searchFieldRef);
|
|
44197
44405
|
const t = useUiText();
|
|
44198
44406
|
const sourceSelectOn = showSourceSelect ?? showFilters;
|
|
44199
44407
|
const [localAddonId, setLocalAddonId] = (0, react$1.useState)("");
|
|
@@ -44471,6 +44679,7 @@ function LogStream({ agentId: propsAgentId, addonId: propsAddonId, deviceId: pro
|
|
|
44471
44679
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
44472
44680
|
className: "relative",
|
|
44473
44681
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
44682
|
+
ref: searchFieldRef,
|
|
44474
44683
|
type: "text",
|
|
44475
44684
|
value: searchText,
|
|
44476
44685
|
onChange: (e) => setSearchText(e.target.value),
|
|
@@ -45122,6 +45331,8 @@ function hasContent(entry) {
|
|
|
45122
45331
|
}
|
|
45123
45332
|
var MAX_LIVE_ENTRIES = 300;
|
|
45124
45333
|
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 }) {
|
|
45334
|
+
const searchFieldRef = (0, react$1.useRef)(null);
|
|
45335
|
+
useSearchFieldHotkey(searchFieldRef);
|
|
45125
45336
|
const t = useUiText();
|
|
45126
45337
|
const defaultsArray = (0, react$1.useMemo)(() => defaultCategories ?? legacyCategories ?? DEFAULT_EVENT_CATEGORIES, [defaultCategories, legacyCategories]);
|
|
45127
45338
|
const defaultsKey = (0, react$1.useMemo)(() => [...defaultsArray].toSorted().join(","), [defaultsArray]);
|
|
@@ -45398,6 +45609,7 @@ function EventStream({ agentId, addonId, deviceId, defaultCategories, categories
|
|
|
45398
45609
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
45399
45610
|
className: "relative",
|
|
45400
45611
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
45612
|
+
ref: searchFieldRef,
|
|
45401
45613
|
type: "text",
|
|
45402
45614
|
value: searchText,
|
|
45403
45615
|
onChange: (e) => setSearchText(e.target.value),
|
|
@@ -45896,6 +46108,8 @@ function coldRuntimeStateSlice(capName) {
|
|
|
45896
46108
|
* deeper opens on demand. */
|
|
45897
46109
|
var DEFAULT_EXPAND_DEPTH = 1;
|
|
45898
46110
|
function StateValuesStream({ deviceId, defaultCaps, maxHeight = "max-h-96", onClose, className }) {
|
|
46111
|
+
const searchFieldRef = (0, react$1.useRef)(null);
|
|
46112
|
+
useSearchFieldHotkey(searchFieldRef);
|
|
45899
46113
|
const t = useUiText();
|
|
45900
46114
|
const [activeCaps, setActiveCaps] = (0, react$1.useState)((0, react$1.useMemo)(() => new Set(defaultCaps ?? []), [defaultCaps]));
|
|
45901
46115
|
const [searchText, setSearchText] = (0, react$1.useState)("");
|
|
@@ -46123,6 +46337,7 @@ function StateValuesStream({ deviceId, defaultCaps, maxHeight = "max-h-96", onCl
|
|
|
46123
46337
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
46124
46338
|
className: "relative",
|
|
46125
46339
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
46340
|
+
ref: searchFieldRef,
|
|
46126
46341
|
type: "text",
|
|
46127
46342
|
value: searchText,
|
|
46128
46343
|
onChange: (e) => setSearchText(e.target.value),
|
|
@@ -47512,6 +47727,42 @@ function resolveClimateTempStep(tempStep) {
|
|
|
47512
47727
|
if (typeof tempStep === "number" && Number.isFinite(tempStep) && tempStep > 0) return tempStep;
|
|
47513
47728
|
return 1;
|
|
47514
47729
|
}
|
|
47730
|
+
var TARGET_FALLBACK_INT_DIGITS = 2;
|
|
47731
|
+
var TARGET_MAX_DECIMALS = 3;
|
|
47732
|
+
function decimalPlaces(n) {
|
|
47733
|
+
if (!Number.isFinite(n)) return 0;
|
|
47734
|
+
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;
|
|
47735
|
+
return TARGET_MAX_DECIMALS;
|
|
47736
|
+
}
|
|
47737
|
+
/**
|
|
47738
|
+
* Character count of the longest set-temperature the device can show, derived
|
|
47739
|
+
* from its range and step (5-30 step 0.5 reserves "30.5" = 4). The big target
|
|
47740
|
+
* number reserves this width so the − / + buttons do not move when the value
|
|
47741
|
+
* gains decimals or loses a digit.
|
|
47742
|
+
*/
|
|
47743
|
+
function climateTargetWidthChars(display) {
|
|
47744
|
+
const step = resolveClimateTempStep(display.tempStep);
|
|
47745
|
+
const bounds = [display.minTemp, display.maxTemp].filter((v) => typeof v === "number" && Number.isFinite(v));
|
|
47746
|
+
const intDigits = bounds.length === 0 ? TARGET_FALLBACK_INT_DIGITS : Math.max(...bounds.map((v) => String(Math.trunc(Math.abs(v))).length));
|
|
47747
|
+
const sign = bounds.some((v) => v < 0) ? 1 : 0;
|
|
47748
|
+
const decimals = Math.max(decimalPlaces(step), ...bounds.map(decimalPlaces));
|
|
47749
|
+
return sign + intDigits + (decimals > 0 ? 1 + decimals : 0);
|
|
47750
|
+
}
|
|
47751
|
+
/** Static class names so Tailwind generates them (no runtime-built classes). */
|
|
47752
|
+
var TARGET_MIN_WIDTH_CLASS = {
|
|
47753
|
+
1: "min-w-[1ch]",
|
|
47754
|
+
2: "min-w-[2ch]",
|
|
47755
|
+
3: "min-w-[3ch]",
|
|
47756
|
+
4: "min-w-[4ch]",
|
|
47757
|
+
5: "min-w-[5ch]",
|
|
47758
|
+
6: "min-w-[6ch]",
|
|
47759
|
+
7: "min-w-[7ch]",
|
|
47760
|
+
8: "min-w-[8ch]"
|
|
47761
|
+
};
|
|
47762
|
+
/** `min-w-[Nch]` + right alignment for the big target number. */
|
|
47763
|
+
function climateTargetWidthClass(display) {
|
|
47764
|
+
return `${TARGET_MIN_WIDTH_CLASS[Math.min(Math.max(climateTargetWidthChars(display), 1), 8)]} text-center`;
|
|
47765
|
+
}
|
|
47515
47766
|
function nextClimateTarget(current, deltaSteps, display) {
|
|
47516
47767
|
let next = current + deltaSteps * resolveClimateTempStep(display.tempStep);
|
|
47517
47768
|
const min = display.minTemp;
|
|
@@ -53899,7 +54150,7 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
53899
54150
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
53900
54151
|
className: "flex items-baseline gap-0.5",
|
|
53901
54152
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
53902
|
-
className: "text-4xl font-bold leading-none tabular-nums",
|
|
54153
|
+
className: cn("text-4xl font-bold leading-none tabular-nums", climateTargetWidthClass(display)),
|
|
53903
54154
|
style: { color: accent },
|
|
53904
54155
|
children: display.target
|
|
53905
54156
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -53940,7 +54191,7 @@ function AcPanel({ trpc, deviceId, layout, optimistic }) {
|
|
|
53940
54191
|
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
53941
54192
|
className: "relative z-10 flex items-baseline gap-0.5",
|
|
53942
54193
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
53943
|
-
className: "text-4xl font-bold leading-none tabular-nums text-foreground",
|
|
54194
|
+
className: cn("text-4xl font-bold leading-none tabular-nums text-foreground", climateTargetWidthClass(display)),
|
|
53944
54195
|
children: display.currentTemp ?? "—"
|
|
53945
54196
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
53946
54197
|
className: "text-lg text-foreground-subtle",
|
|
@@ -54058,7 +54309,7 @@ function CompactSelect({ label, icon, value, options, disabled, onChange }) {
|
|
|
54058
54309
|
className: "text-[10px] font-semibold uppercase tracking-wider text-foreground-subtle",
|
|
54059
54310
|
children: label
|
|
54060
54311
|
}), /* @__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"),
|
|
54312
|
+
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
54313
|
children: [
|
|
54063
54314
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
54064
54315
|
className: "flex shrink-0",
|
|
@@ -60142,6 +60393,8 @@ function GenericModeList(props) {
|
|
|
60142
60393
|
defaultSize: fixedPageSize
|
|
60143
60394
|
});
|
|
60144
60395
|
const [searchInput, setSearchInput] = (0, react$1.useState)("");
|
|
60396
|
+
const searchRef = (0, react$1.useRef)(null);
|
|
60397
|
+
useSearchFieldHotkey(searchRef);
|
|
60145
60398
|
const debouncedSearch = useDebouncedString(searchInput, 300);
|
|
60146
60399
|
const [typeFilters, setTypeFilters] = (0, react$1.useState)(() => [...initialTypeFilters]);
|
|
60147
60400
|
const [page, setPage] = (0, react$1.useState)(1);
|
|
@@ -60212,6 +60465,7 @@ function GenericModeList(props) {
|
|
|
60212
60465
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
60213
60466
|
className: "relative",
|
|
60214
60467
|
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", {
|
|
60468
|
+
ref: searchRef,
|
|
60215
60469
|
type: "text",
|
|
60216
60470
|
value: searchInput,
|
|
60217
60471
|
onChange: (e) => setSearchInput(e.target.value),
|
|
@@ -61095,6 +61349,8 @@ function DeviceSelectorPicker({ value, onChange, includeLinked, onIncludeLinkedC
|
|
|
61095
61349
|
const t = useUiText();
|
|
61096
61350
|
const { data: rawDevices } = useDeviceManagerListAll({});
|
|
61097
61351
|
const [search, setSearch] = (0, react$1.useState)("");
|
|
61352
|
+
const searchRef = (0, react$1.useRef)(null);
|
|
61353
|
+
useSearchFieldHotkey(searchRef);
|
|
61098
61354
|
const devices = (0, react$1.useMemo)(() => {
|
|
61099
61355
|
const all = (rawDevices ?? []).map((d) => ({
|
|
61100
61356
|
id: d.id,
|
|
@@ -61225,6 +61481,7 @@ function DeviceSelectorPicker({ value, onChange, includeLinked, onIncludeLinkedC
|
|
|
61225
61481
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
61226
61482
|
className: "relative",
|
|
61227
61483
|
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", {
|
|
61484
|
+
ref: searchRef,
|
|
61228
61485
|
type: "search",
|
|
61229
61486
|
value: search,
|
|
61230
61487
|
onChange: (e) => setSearch(e.target.value),
|
|
@@ -62327,6 +62584,7 @@ function FilterBar({ filters, values, onChange, className }) {
|
|
|
62327
62584
|
placeholder: searchFilter.placeholder ?? t("uiLibrary.filterBar.search", "Search..."),
|
|
62328
62585
|
value: values[searchFilter.key] ?? "",
|
|
62329
62586
|
onChange: (e) => handleChange(searchFilter.key, e.target.value),
|
|
62587
|
+
searchHotkey: true,
|
|
62330
62588
|
leftSlot: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Search, { className: "h-3 w-3 text-foreground-subtle" }),
|
|
62331
62589
|
className: "flex-1"
|
|
62332
62590
|
}), hasNonSearchFilters && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
@@ -62388,6 +62646,7 @@ function FilterBar({ filters, values, onChange, className }) {
|
|
|
62388
62646
|
placeholder: filter.placeholder ?? t("uiLibrary.filterBar.search", "Search..."),
|
|
62389
62647
|
value: values[filter.key] ?? "",
|
|
62390
62648
|
onChange: (e) => handleChange(filter.key, e.target.value),
|
|
62649
|
+
searchHotkey: true,
|
|
62391
62650
|
leftSlot: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Search, { className: "h-3 w-3 text-foreground-subtle" }),
|
|
62392
62651
|
className: "w-48"
|
|
62393
62652
|
}, filter.key);
|
|
@@ -62921,7 +63180,7 @@ function mountAddonPage(PageComponent, options = {}) {
|
|
|
62921
63180
|
console.error(`[mountAddonPage] Element #${rootId} not found`);
|
|
62922
63181
|
return;
|
|
62923
63182
|
}
|
|
62924
|
-
(0, react_dom_client.createRoot)(root).render((0, react$1.createElement)(DevShell, {
|
|
63183
|
+
(0, react_dom_client.createRoot)(root).render((0, react$1.createElement)(HotkeyProvider, null, (0, react$1.createElement)(DevShell, {
|
|
62925
63184
|
serverUrl,
|
|
62926
63185
|
title,
|
|
62927
63186
|
children: ({ trpc, theme }) => (0, react$1.createElement)(PageComponent, {
|
|
@@ -62931,7 +63190,7 @@ function mountAddonPage(PageComponent, options = {}) {
|
|
|
62931
63190
|
console.log("[dev] navigate:", path);
|
|
62932
63191
|
}
|
|
62933
63192
|
})
|
|
62934
|
-
}));
|
|
63193
|
+
})));
|
|
62935
63194
|
}
|
|
62936
63195
|
//#endregion
|
|
62937
63196
|
//#region src/composites/node-picker.tsx
|
|
@@ -67243,6 +67502,8 @@ function zoneUnavailable(zones, t) {
|
|
|
67243
67502
|
if (zones.state === "failed") return t("uiLibrary.deviceFilterBar.zonesUnavailable", "zones unavailable");
|
|
67244
67503
|
}
|
|
67245
67504
|
function DeviceFilterBar({ filters, onFiltersChange, typeOptions, zoneOptions, locationOptions, zones, grouping, searchPlaceholder, searchLabel, disabled = false }) {
|
|
67505
|
+
const searchFieldRef = (0, react$1.useRef)(null);
|
|
67506
|
+
useSearchFieldHotkey(searchFieldRef);
|
|
67246
67507
|
const t = useUiText();
|
|
67247
67508
|
const [text, setText] = (0, react$1.useState)(filters.query);
|
|
67248
67509
|
const latest = (0, react$1.useRef)({
|
|
@@ -67272,6 +67533,7 @@ function DeviceFilterBar({ filters, onFiltersChange, typeOptions, zoneOptions, l
|
|
|
67272
67533
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
67273
67534
|
className: INPUT_SHELL,
|
|
67274
67535
|
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", {
|
|
67536
|
+
ref: searchFieldRef,
|
|
67275
67537
|
type: "text",
|
|
67276
67538
|
value: text,
|
|
67277
67539
|
placeholder: searchPlaceholder,
|
|
@@ -67397,6 +67659,64 @@ function withResolvedParentDetail(devices) {
|
|
|
67397
67659
|
return changed ? next : devices;
|
|
67398
67660
|
}
|
|
67399
67661
|
//#endregion
|
|
67662
|
+
//#region src/composites/device-picker/device-provider-label.ts
|
|
67663
|
+
/**
|
|
67664
|
+
* Who offers a device, as the picker draws it: "{addon name} ({integration
|
|
67665
|
+
* name})" — the integration part only when the device comes from a named
|
|
67666
|
+
* integration instance — otherwise just "{addon name}".
|
|
67667
|
+
*
|
|
67668
|
+
* Derived from what the UI already reads: the device's providing `addonId` and
|
|
67669
|
+
* its `integrationId` (fleet listing), the addon's manifest name
|
|
67670
|
+
* (`addons.list`) and the instance's name (`integrations.list`). Nothing here
|
|
67671
|
+
* knows a kind or an addon; a row whose provider cannot be resolved shows no
|
|
67672
|
+
* line at all rather than a guess.
|
|
67673
|
+
*/
|
|
67674
|
+
/** "Home Assistant (Casa)", or "Home Assistant" without an instance name. */
|
|
67675
|
+
function formatProviderLabel(addonName, integrationName) {
|
|
67676
|
+
const instance = integrationName?.trim() ?? "";
|
|
67677
|
+
return instance === "" || instance === addonName ? addonName : `${addonName} (${instance})`;
|
|
67678
|
+
}
|
|
67679
|
+
/** `deviceId → provider label`; a device whose addon is unknown is absent. */
|
|
67680
|
+
function providerLabelsOf(fleet, addons, integrations) {
|
|
67681
|
+
const addonName = /* @__PURE__ */ new Map();
|
|
67682
|
+
for (const a of addons) if (a.name !== void 0 && a.name !== "") addonName.set(a.id, a.name);
|
|
67683
|
+
const instanceName = new Map(integrations.map((i) => [i.id, i.name]));
|
|
67684
|
+
const out = /* @__PURE__ */ new Map();
|
|
67685
|
+
for (const d of fleet) {
|
|
67686
|
+
const addon = addonName.get(d.addonId);
|
|
67687
|
+
if (addon === void 0) continue;
|
|
67688
|
+
const instance = d.integrationId === void 0 ? null : instanceName.get(d.integrationId) ?? null;
|
|
67689
|
+
out.set(d.id, formatProviderLabel(addon, instance));
|
|
67690
|
+
}
|
|
67691
|
+
return out;
|
|
67692
|
+
}
|
|
67693
|
+
var NONE = /* @__PURE__ */ new Map();
|
|
67694
|
+
var PROVIDER_LABELS_STALE_MS = 6e4;
|
|
67695
|
+
/**
|
|
67696
|
+
* The labels for every device in the fleet; empty until the reads answer (a
|
|
67697
|
+
* failed read draws no provider line — it is a hint, never a gate).
|
|
67698
|
+
*/
|
|
67699
|
+
function useDeviceProviderLabels(enabled) {
|
|
67700
|
+
const trpcClient = useSystem().trpcClient;
|
|
67701
|
+
const query = (0, _tanstack_react_query.useQuery)({
|
|
67702
|
+
queryKey: ["ui-library", "device-provider-labels"],
|
|
67703
|
+
enabled,
|
|
67704
|
+
staleTime: PROVIDER_LABELS_STALE_MS,
|
|
67705
|
+
queryFn: async () => {
|
|
67706
|
+
const [fleet, addons, integrations] = await Promise.all([
|
|
67707
|
+
trpcClient.deviceManager.listAll.query({ projection: "slim" }),
|
|
67708
|
+
trpcClient.addons.list.query(),
|
|
67709
|
+
trpcClient.integrations.list.query()
|
|
67710
|
+
]);
|
|
67711
|
+
return providerLabelsOf(fleet, addons.map((a) => ({
|
|
67712
|
+
id: a.manifest.id,
|
|
67713
|
+
name: a.manifest.name
|
|
67714
|
+
})), integrations);
|
|
67715
|
+
}
|
|
67716
|
+
});
|
|
67717
|
+
return (0, react$1.useMemo)(() => query.data ?? NONE, [query.data]);
|
|
67718
|
+
}
|
|
67719
|
+
//#endregion
|
|
67400
67720
|
//#region src/composites/device-picker/device-picker-grouping.ts
|
|
67401
67721
|
/**
|
|
67402
67722
|
* The admin side of the device picker's "Group by" (Zone | Location | None):
|
|
@@ -67761,7 +68081,7 @@ function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, s
|
|
|
67761
68081
|
children: [
|
|
67762
68082
|
listState === "pending" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DevicesLoadingLine, { noun: plural }),
|
|
67763
68083
|
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", {
|
|
68084
|
+
!(selection === "single" && chips.length === 0 && showAll) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
67765
68085
|
className: "flex flex-wrap items-center gap-1",
|
|
67766
68086
|
children: [
|
|
67767
68087
|
chips.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -67825,6 +68145,7 @@ function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, s
|
|
|
67825
68145
|
]
|
|
67826
68146
|
});
|
|
67827
68147
|
}
|
|
68148
|
+
var NO_PROVIDERS = /* @__PURE__ */ new Map();
|
|
67828
68149
|
/**
|
|
67829
68150
|
* The device picker's panel: mounted only while the list is open, so the zone
|
|
67830
68151
|
* read below happens only then (for Zone grouping and the Zone filter).
|
|
@@ -67842,8 +68163,10 @@ function GroupedPickerPanel(props) {
|
|
|
67842
68163
|
stored.setGroupBy(next);
|
|
67843
68164
|
};
|
|
67844
68165
|
const zones = usePickerSiteZones(true);
|
|
68166
|
+
const providers = useDeviceProviderLabels(true);
|
|
67845
68167
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PickerPanel, {
|
|
67846
68168
|
...props,
|
|
68169
|
+
providers,
|
|
67847
68170
|
grouping: {
|
|
67848
68171
|
groupBy,
|
|
67849
68172
|
onGroupByChange,
|
|
@@ -67864,7 +68187,7 @@ var NO_ZONES = {
|
|
|
67864
68187
|
zones: []
|
|
67865
68188
|
};
|
|
67866
68189
|
/** 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 }) {
|
|
68190
|
+
function PickerPanel({ devices, value, onChange, annotate, groupTitle, noun, disabled, locked, lockedNote, showAll, selection, grouping, providers = NO_PROVIDERS }) {
|
|
67868
68191
|
const t = useUiText();
|
|
67869
68192
|
const radioName = (0, react$1.useId)();
|
|
67870
68193
|
const [filters, setFilters] = (0, react$1.useState)(PICKER_NO_FILTERS);
|
|
@@ -67967,7 +68290,8 @@ function PickerPanel({ devices, value, onChange, annotate, groupTitle, noun, dis
|
|
|
67967
68290
|
locked,
|
|
67968
68291
|
lockedNote,
|
|
67969
68292
|
selection,
|
|
67970
|
-
radioName
|
|
68293
|
+
radioName,
|
|
68294
|
+
providers
|
|
67971
68295
|
}, row.key)), page.remaining > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
67972
68296
|
type: "button",
|
|
67973
68297
|
className: "mt-1 w-full rounded border border-border py-1 text-[11px] text-foreground-subtle hover:bg-surface-hover",
|
|
@@ -67997,7 +68321,7 @@ function secondLine(device, groupBy) {
|
|
|
67997
68321
|
].filter((part) => part !== null);
|
|
67998
68322
|
return parts.length > 0 ? parts.join(" · ") : void 0;
|
|
67999
68323
|
}
|
|
68000
|
-
function PickerRowView({ row, value, onChange, onToggleGroup, annotate, groupTitle, groupBy, disabled, locked, lockedNote, selection, radioName }) {
|
|
68324
|
+
function PickerRowView({ row, value, onChange, onToggleGroup, annotate, groupTitle, groupBy, disabled, locked, lockedNote, selection, radioName, providers }) {
|
|
68001
68325
|
const t = useUiText();
|
|
68002
68326
|
const single = selection === "single";
|
|
68003
68327
|
if (row.kind === "header") {
|
|
@@ -68049,6 +68373,8 @@ function PickerRowView({ row, value, onChange, onToggleGroup, annotate, groupTit
|
|
|
68049
68373
|
const isLocked = locked.has(row.device.id);
|
|
68050
68374
|
const note = isLocked ? lockedNote : annotate?.(row.device.id);
|
|
68051
68375
|
const second = secondLine(row.device, groupBy);
|
|
68376
|
+
const rowId = row.device.id;
|
|
68377
|
+
const provider = typeof rowId === "number" ? providers.get(rowId) : void 0;
|
|
68052
68378
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
68053
68379
|
className: "flex cursor-pointer items-center gap-2.5 px-3 py-1 text-xs text-foreground hover:bg-surface-hover",
|
|
68054
68380
|
"data-testid": "device-picker-row",
|
|
@@ -68072,6 +68398,11 @@ function PickerRowView({ row, value, onChange, onToggleGroup, annotate, groupTit
|
|
|
68072
68398
|
className: "truncate text-[10px] text-foreground-subtle",
|
|
68073
68399
|
children: second
|
|
68074
68400
|
}),
|
|
68401
|
+
provider !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
68402
|
+
className: "truncate text-[10px] text-foreground-subtle",
|
|
68403
|
+
"data-testid": "device-picker-provider",
|
|
68404
|
+
children: provider
|
|
68405
|
+
}),
|
|
68075
68406
|
note !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
68076
68407
|
className: isLocked ? "text-[10px] text-foreground-subtle" : "text-[10px] text-warning",
|
|
68077
68408
|
children: note
|
|
@@ -71235,6 +71566,7 @@ exports.ExtensionsPanel = ExtensionsPanel;
|
|
|
71235
71566
|
exports.ExternalLink = ExternalLink;
|
|
71236
71567
|
exports.Eye = Eye;
|
|
71237
71568
|
exports.EyeOff = EyeOff;
|
|
71569
|
+
exports.FALLBACK_SCOPE = FALLBACK_SCOPE;
|
|
71238
71570
|
exports.FAST_ICON_GLYPHS = FAST_ICON_GLYPHS;
|
|
71239
71571
|
exports.FILL = FILL;
|
|
71240
71572
|
exports.FLEET_LIST_GC_TIME_MS = FLEET_LIST_GC_TIME_MS;
|
|
@@ -71286,6 +71618,8 @@ exports.History = History;
|
|
|
71286
71618
|
exports.HistoryChart = HistoryChart;
|
|
71287
71619
|
exports.HistoryWindowBar = HistoryWindowBar;
|
|
71288
71620
|
exports.HlsVideo = HlsVideo;
|
|
71621
|
+
exports.HotkeyProvider = HotkeyProvider;
|
|
71622
|
+
exports.HotkeyScope = HotkeyScope;
|
|
71289
71623
|
exports.House = House;
|
|
71290
71624
|
exports.HoverZoomImage = HoverZoomImage;
|
|
71291
71625
|
exports.HubSessionTestCard = HubSessionTestCard;
|
|
@@ -71717,6 +72051,7 @@ exports.defaultUiText = defaultUiText;
|
|
|
71717
72051
|
exports.deriveDeviceKind = deriveDeviceKind;
|
|
71718
72052
|
exports.deriveWebrtcDebugStats = deriveWebrtcDebugStats;
|
|
71719
72053
|
exports.describeDeviceSelector = describeDeviceSelector;
|
|
72054
|
+
exports.detectPlatform = detectPlatform;
|
|
71720
72055
|
exports.deviceIntegrationResolver = deviceIntegrationResolver;
|
|
71721
72056
|
exports.deviceListStateOf = deviceListStateOf;
|
|
71722
72057
|
exports.deviceMatchesFilter = deviceMatchesFilter;
|
|
@@ -71813,6 +72148,7 @@ exports.loadRemoteBundle = loadRemoteBundle;
|
|
|
71813
72148
|
exports.lucideExportName = lucideExportName;
|
|
71814
72149
|
exports.makeScrubBridge = makeScrubBridge;
|
|
71815
72150
|
exports.mapRootDetectorPlacements = mapRootDetectorPlacements;
|
|
72151
|
+
exports.matchesCombo = matchesCombo;
|
|
71816
72152
|
exports.mergeSecretForSubmit = mergeSecretForSubmit;
|
|
71817
72153
|
exports.metadataEntries = metadataEntries;
|
|
71818
72154
|
exports.metadataString = metadataString;
|
|
@@ -71844,6 +72180,7 @@ exports.pageSizeLabel = pageSizeLabel;
|
|
|
71844
72180
|
exports.paginateRows = paginateRows;
|
|
71845
72181
|
exports.parentDetailOf = parentDetailOf;
|
|
71846
72182
|
exports.parseCapKey = parseCapKey;
|
|
72183
|
+
exports.parseCombo = parseCombo;
|
|
71847
72184
|
exports.parseConsumableLevel = parseConsumableLevel;
|
|
71848
72185
|
exports.parseIceCandidate = parseIceCandidate;
|
|
71849
72186
|
exports.parsePickerGroupBy = parsePickerGroupBy;
|
|
@@ -72454,6 +72791,7 @@ exports.useHeatingValveGetStatus = useHeatingValveGetStatus;
|
|
|
72454
72791
|
exports.useHistoryPanels = useHistoryPanels;
|
|
72455
72792
|
exports.useHistoryReader = useHistoryReader;
|
|
72456
72793
|
exports.useHistoryReads = useHistoryReads;
|
|
72794
|
+
exports.useHotkey = useHotkey;
|
|
72457
72795
|
exports.useHumidifierGetStatus = useHumidifierGetStatus;
|
|
72458
72796
|
exports.useHumidifierSetMode = useHumidifierSetMode;
|
|
72459
72797
|
exports.useHumidifierSetOn = useHumidifierSetOn;
|
|
@@ -73048,6 +73386,7 @@ exports.useScriptRunnerGetStatus = useScriptRunnerGetStatus;
|
|
|
73048
73386
|
exports.useScriptRunnerRun = useScriptRunnerRun;
|
|
73049
73387
|
exports.useScriptRunnerStop = useScriptRunnerStop;
|
|
73050
73388
|
exports.useScrubController = useScrubController;
|
|
73389
|
+
exports.useSearchFieldHotkey = useSearchFieldHotkey;
|
|
73051
73390
|
exports.useServerManagementApplyServerUpdate = useServerManagementApplyServerUpdate;
|
|
73052
73391
|
exports.useServerManagementCheckServerUpdate = useServerManagementCheckServerUpdate;
|
|
73053
73392
|
exports.useServerManagementGetServerPackageStatus = useServerManagementGetServerPackageStatus;
|