@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/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
- }), children]
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
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BatteryMedium, {
52765
- className: "h-3.5 w-3.5",
52766
- "aria-hidden": true
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;