@camstack/ui-library 1.2.332 → 1.2.333

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
@@ -40,6 +40,7 @@ let react_dom_client = require("react-dom/client");
40
40
  react_dom_client = __toESM(react_dom_client, 1);
41
41
  let _trpc_client = require("@trpc/client");
42
42
  _trpc_client = __toESM(_trpc_client, 1);
43
+ let _camstack_types_client = require("@camstack/types/client");
43
44
  let _camstack_sdk = require("@camstack/sdk");
44
45
  let zod = require("zod");
45
46
  //#region src/contexts/ui-text.tsx
@@ -4706,6 +4707,40 @@ var VolumeX = require_createLucideIcon.createLucideIcon("volume-x", [
4706
4707
  * This source code is licensed under the ISC license.
4707
4708
  * See the LICENSE file in the root directory of this source tree.
4708
4709
  */
4710
+ var WashingMachine = require_createLucideIcon.createLucideIcon("washing-machine", [
4711
+ ["path", {
4712
+ d: "M3 6h3",
4713
+ key: "155dbl"
4714
+ }],
4715
+ ["path", {
4716
+ d: "M17 6h.01",
4717
+ key: "e2y6kg"
4718
+ }],
4719
+ ["rect", {
4720
+ width: "18",
4721
+ height: "20",
4722
+ x: "3",
4723
+ y: "2",
4724
+ rx: "2",
4725
+ key: "od3kk9"
4726
+ }],
4727
+ ["circle", {
4728
+ cx: "12",
4729
+ cy: "13",
4730
+ r: "5",
4731
+ key: "nlbqau"
4732
+ }],
4733
+ ["path", {
4734
+ d: "M12 18a2.5 2.5 0 0 0 0-5 2.5 2.5 0 0 1 0-5",
4735
+ key: "17lach"
4736
+ }]
4737
+ ]);
4738
+ /**
4739
+ * @license lucide-react v0.576.0 - ISC
4740
+ *
4741
+ * This source code is licensed under the ISC license.
4742
+ * See the LICENSE file in the root directory of this source tree.
4743
+ */
4709
4744
  var Waves = require_createLucideIcon.createLucideIcon("waves", [
4710
4745
  ["path", {
4711
4746
  d: "M2 6c.6.5 1.2 1 2.5 1C7 7 7 5 9.5 5c2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1",
@@ -5369,6 +5404,16 @@ var DEVICE_TYPE_META = {
5369
5404
  icon: Waves,
5370
5405
  labelKey: "uiLibrary.deviceMeta.type.dishwasher",
5371
5406
  labelFallback: "Dishwasher"
5407
+ },
5408
+ [_camstack_types.DeviceType.Washer]: {
5409
+ icon: WashingMachine,
5410
+ labelKey: "uiLibrary.deviceMeta.type.washer",
5411
+ labelFallback: "Washing machine"
5412
+ },
5413
+ [_camstack_types.DeviceType.Dryer]: {
5414
+ icon: Wind,
5415
+ labelKey: "uiLibrary.deviceMeta.type.dryer",
5416
+ labelFallback: "Dryer"
5372
5417
  }
5373
5418
  };
5374
5419
  var DEVICE_ROLE_META = {
@@ -5850,7 +5895,7 @@ function lucideExportName(key) {
5850
5895
  function loadIconGlyphs() {
5851
5896
  if (fullTable !== void 0) return Promise.resolve();
5852
5897
  if (failure$1 !== void 0) return Promise.reject(failure$1);
5853
- pending ??= Promise.resolve().then(() => require("./icons-pqV1UOV_.cjs")).then((module) => {
5898
+ pending ??= Promise.resolve().then(() => require("./icons-CWmwnulO.cjs")).then((module) => {
5854
5899
  fullTable = module;
5855
5900
  publish();
5856
5901
  }, (error) => {
@@ -19627,6 +19672,1151 @@ function AutotrackSection({ deviceId }) {
19627
19672
  });
19628
19673
  }
19629
19674
  //#endregion
19675
+ //#region src/composites/device-name/device-fleet.tsx
19676
+ /**
19677
+ * One slim fleet, shared. A row reads its parent from this index — never a
19678
+ * `getDevice` per row (the Customize bounded-read rule). `null` is "not
19679
+ * answered yet": a label then keeps the stored name, so a list does not flash
19680
+ * `#id` while the fleet is in flight. An empty array is an answer.
19681
+ */
19682
+ var DeviceNameIndexContext = (0, react$1.createContext)(null);
19683
+ function useDeviceNameIndex() {
19684
+ return (0, react$1.useContext)(DeviceNameIndexContext);
19685
+ }
19686
+ function copyDevice(device) {
19687
+ const label = device.display?.label;
19688
+ const trimmed = label === void 0 || label === null ? "" : label.trim();
19689
+ return {
19690
+ id: device.id,
19691
+ name: device.name,
19692
+ type: device.type,
19693
+ stableId: device.stableId,
19694
+ role: device.role ?? null,
19695
+ parentDeviceId: device.parentDeviceId ?? null,
19696
+ location: device.location ?? null,
19697
+ ...trimmed.length > 0 ? { display: { label: trimmed } } : {},
19698
+ ...device.primaryChildEntityId !== void 0 ? { primaryChildEntityId: device.primaryChildEntityId } : {},
19699
+ ...device.userNamed === void 0 ? {} : { userNamed: device.userNamed }
19700
+ };
19701
+ }
19702
+ /** Build the parent/child maps once per fleet answer. */
19703
+ function indexDeviceNames(devices) {
19704
+ const byId = /* @__PURE__ */ new Map();
19705
+ const children = /* @__PURE__ */ new Map();
19706
+ for (const device of devices) {
19707
+ const fields = copyDevice(device);
19708
+ byId.set(fields.id, fields);
19709
+ const parentId = fields.parentDeviceId;
19710
+ if (parentId === null || parentId === void 0 || parentId === fields.id) continue;
19711
+ const list = children.get(parentId);
19712
+ if (list === void 0) children.set(parentId, [fields.id]);
19713
+ else list.push(fields.id);
19714
+ }
19715
+ return {
19716
+ byId,
19717
+ childIds: children
19718
+ };
19719
+ }
19720
+ function DeviceFleetProvider({ devices, children }) {
19721
+ const index = (0, react$1.useMemo)(() => devices === null ? null : indexDeviceNames(devices), [devices]);
19722
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceNameIndexContext.Provider, {
19723
+ value: index,
19724
+ children
19725
+ });
19726
+ }
19727
+ /**
19728
+ * The one `deviceManager.listAll` subscriber for names. Same query key the
19729
+ * pickers already use, so a second observer does not fetch a second fleet.
19730
+ */
19731
+ function DeviceFleetFromServer({ children }) {
19732
+ const query = useDeviceManagerListAll({ projection: "slim" });
19733
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceFleetProvider, {
19734
+ devices: query.data === void 0 ? null : query.data,
19735
+ children
19736
+ });
19737
+ }
19738
+ //#endregion
19739
+ //#region src/composites/device-name/format-device-name.ts
19740
+ /**
19741
+ * One display name for a device.
19742
+ *
19743
+ * Canonical copy: `packages/ui-library/src/composites/device-name/format-device-name.ts`.
19744
+ * The viewer mirrors this file; `scripts/check-device-name-in-sync.ts` diffs the two.
19745
+ * Noun strings stay outside — this file only decides which noun to ask for.
19746
+ */
19747
+ /** Middle dot used between a container and its noun, and before a room. */
19748
+ var DEVICE_NAME_JOIN = " · ";
19749
+ var DEVICE_NAME_FORMS = [
19750
+ "compact",
19751
+ "full",
19752
+ "nested",
19753
+ "with-location"
19754
+ ];
19755
+ var PARENT_SEPARATORS = [" — ", " "];
19756
+ /**
19757
+ * Readings that are never what a container is. Matches the comment on
19758
+ * `CONTAINER_CHILD_ROLE_PRIORITY`: battery, temperature, humidity, illuminance,
19759
+ * signal, last-seen, plus numeric and enum. `update` is the same kind of row.
19760
+ */
19761
+ var DIAGNOSTIC_ROLES = new Set([
19762
+ "battery-sensor",
19763
+ "temperature-sensor",
19764
+ "humidity-sensor",
19765
+ "ambient-light-sensor",
19766
+ "connectivity-sensor",
19767
+ "datetime-sensor",
19768
+ "numeric-sensor",
19769
+ "enum-sensor"
19770
+ ]);
19771
+ /** Hyphen, underscore and whitespace fold together — the namesake comparison. */
19772
+ function foldDeviceName(value) {
19773
+ return value.toLowerCase().replace(/[-_\s]+/g, " ").trim();
19774
+ }
19775
+ function sameDeviceName(left, right) {
19776
+ return foldDeviceName(left) === foldDeviceName(right);
19777
+ }
19778
+ /**
19779
+ * Drop a parent prefix. `" — "` before a space, case-insensitive, so a provider
19780
+ * that capitalised the parent differently still strips, and an em-dash child
19781
+ * does not keep a dangling dash.
19782
+ */
19783
+ function stripDeviceParentPrefix(name, parentName) {
19784
+ if (parentName.length === 0) return name;
19785
+ const nameFold = name.toLowerCase();
19786
+ const parentFold = parentName.toLowerCase();
19787
+ for (const separator of PARENT_SEPARATORS) {
19788
+ const prefix = `${parentFold}${separator}`;
19789
+ if (!nameFold.startsWith(prefix)) continue;
19790
+ return name.slice(prefix.length).trim();
19791
+ }
19792
+ return name;
19793
+ }
19794
+ function roleOf(device) {
19795
+ const role = device.role;
19796
+ if (role === void 0 || role === null || role.length === 0) return null;
19797
+ return role;
19798
+ }
19799
+ function isDiagnostic(device) {
19800
+ if (device.type === "update") return true;
19801
+ const role = roleOf(device);
19802
+ return role !== null && DIAGNOSTIC_ROLES.has(role);
19803
+ }
19804
+ function displayLabel(device) {
19805
+ const label = device.display?.label;
19806
+ if (label === void 0 || label === null) return "";
19807
+ return label.trim();
19808
+ }
19809
+ function nounOf(device, parent, nouns) {
19810
+ const caption = displayLabel(device);
19811
+ if (caption.length > 0) return caption;
19812
+ const stripped = stripDeviceParentPrefix(device.name, parent.name);
19813
+ const shown = stripped.length > 0 ? stripped : device.name;
19814
+ if (device.userNamed === true) return shown;
19815
+ if (stripped.length === 0 || sameDeviceName(stripped, parent.name) || nouns.isStockPhrase(shown, roleOf(device), device.type)) return nouns.shortNoun(roleOf(device), device.type);
19816
+ return shown;
19817
+ }
19818
+ function foldsIntoParent(device, parent, noun) {
19819
+ if (sameDeviceName(noun, parent.name)) return true;
19820
+ if (displayLabel(device).length > 0 || device.userNamed === true) return false;
19821
+ return sameDeviceName(stripDeviceParentPrefix(device.name, parent.name), parent.name);
19822
+ }
19823
+ function meaningfulChildCount(siblings) {
19824
+ let count = 0;
19825
+ for (const sibling of siblings) if (!isDiagnostic(sibling)) count += 1;
19826
+ return count;
19827
+ }
19828
+ function isContainerPrimary(device, parent, siblings) {
19829
+ const picked = (0, _camstack_types_client.resolveContainerPrimaryChild)(siblings.map((sibling) => {
19830
+ const role = roleOf(sibling);
19831
+ return {
19832
+ id: sibling.id,
19833
+ stableId: sibling.stableId,
19834
+ type: sibling.type,
19835
+ name: sibling.name,
19836
+ ...role === null ? {} : { role }
19837
+ };
19838
+ }), parent.primaryChildEntityId, parent.name);
19839
+ return picked !== null && picked.id === device.id;
19840
+ }
19841
+ function childLine(device, parent, siblings, nouns, form) {
19842
+ const noun = nounOf(device, parent, nouns);
19843
+ if (foldsIntoParent(device, parent, noun)) return parent.name;
19844
+ if (form === "compact" && parent.type === "container" && isContainerPrimary(device, parent, siblings) && meaningfulChildCount(siblings) === 1) return parent.name;
19845
+ return `${parent.name}${DEVICE_NAME_JOIN}${noun}`;
19846
+ }
19847
+ function joinLocation(label, location) {
19848
+ if (location === void 0 || location === null) return label;
19849
+ const room = location.trim();
19850
+ if (room.length === 0) return label;
19851
+ if (label.split(" · ").some((segment) => sameDeviceName(segment, room))) return label;
19852
+ return `${label}${DEVICE_NAME_JOIN}${room}`;
19853
+ }
19854
+ function hasParent(device, parent) {
19855
+ if (parent === null) return false;
19856
+ const parentId = device.parentDeviceId;
19857
+ return parentId !== null && parentId !== void 0;
19858
+ }
19859
+ /**
19860
+ * The label for `form`. A top-level device keeps its stored name. A child uses
19861
+ * the noun: `compact` may stand in for the container when this child is what
19862
+ * the container is, `full` always names both unless they fold together,
19863
+ * `nested` is the noun alone, `with-location` is `compact` plus the room.
19864
+ */
19865
+ function formatDeviceName(device, form, context, nouns) {
19866
+ if (!hasParent(device, context.parent)) return form === "with-location" ? joinLocation(device.name, device.location) : device.name;
19867
+ if (form === "nested") return nounOf(device, context.parent, nouns);
19868
+ const line = childLine(device, context.parent, context.siblings, nouns, form === "full" ? "full" : "compact");
19869
+ return form === "with-location" ? joinLocation(line, device.location) : line;
19870
+ }
19871
+ //#endregion
19872
+ //#region src/composites/device-name/device-name-nouns.ts
19873
+ /**
19874
+ * Short nouns and the stock words they replace.
19875
+ *
19876
+ * Long labels are the existing role and type names, both locales, so a stored
19877
+ * "Motion sensor" and a stored "Sensore di movimento" are the same stock word.
19878
+ * The short noun is what a list shows. Where the long label is already one
19879
+ * word it is the short noun. Aliases are provider stock words that are not the
19880
+ * label ("Illumination", "Last seen").
19881
+ */
19882
+ var DEVICE_ROLE_NOUNS = [
19883
+ {
19884
+ id: "siren",
19885
+ key: "uiLibrary.deviceMeta.noun.role.siren",
19886
+ en: "Siren",
19887
+ it: "Sirena",
19888
+ shortEn: "Siren",
19889
+ shortIt: "Sirena",
19890
+ aliases: []
19891
+ },
19892
+ {
19893
+ id: "floodlight",
19894
+ key: "uiLibrary.deviceMeta.noun.role.floodlight",
19895
+ en: "Floodlight",
19896
+ it: "Faro",
19897
+ shortEn: "Floodlight",
19898
+ shortIt: "Faro",
19899
+ aliases: []
19900
+ },
19901
+ {
19902
+ id: "spotlight",
19903
+ key: "uiLibrary.deviceMeta.noun.role.spotlight",
19904
+ en: "Spotlight",
19905
+ it: "Faretto",
19906
+ shortEn: "Spotlight",
19907
+ shortIt: "Faretto",
19908
+ aliases: []
19909
+ },
19910
+ {
19911
+ id: "pir-sensor",
19912
+ key: "uiLibrary.deviceMeta.noun.role.pirSensor",
19913
+ en: "PIR sensor",
19914
+ it: "Sensore PIR",
19915
+ shortEn: "PIR",
19916
+ shortIt: "PIR",
19917
+ aliases: []
19918
+ },
19919
+ {
19920
+ id: "chime",
19921
+ key: "uiLibrary.deviceMeta.noun.role.chime",
19922
+ en: "Chime",
19923
+ it: "Campanello",
19924
+ shortEn: "Chime",
19925
+ shortIt: "Campanello",
19926
+ aliases: []
19927
+ },
19928
+ {
19929
+ id: "autotrack",
19930
+ key: "uiLibrary.deviceMeta.noun.role.autotrack",
19931
+ en: "Auto-track",
19932
+ it: "Auto-tracking",
19933
+ shortEn: "Auto-track",
19934
+ shortIt: "Auto-tracking",
19935
+ aliases: []
19936
+ },
19937
+ {
19938
+ id: "nightvision",
19939
+ key: "uiLibrary.deviceMeta.noun.role.nightvision",
19940
+ en: "Night vision",
19941
+ it: "Visione notturna",
19942
+ shortEn: "Night",
19943
+ shortIt: "Notturna",
19944
+ aliases: []
19945
+ },
19946
+ {
19947
+ id: "privacy-mask",
19948
+ key: "uiLibrary.deviceMeta.noun.role.privacyMask",
19949
+ en: "Privacy mask",
19950
+ it: "Maschera privacy",
19951
+ shortEn: "Privacy",
19952
+ shortIt: "Privacy",
19953
+ aliases: []
19954
+ },
19955
+ {
19956
+ id: "doorbell",
19957
+ key: "uiLibrary.deviceMeta.noun.role.doorbell",
19958
+ en: "Doorbell",
19959
+ it: "Campanello video",
19960
+ shortEn: "Doorbell",
19961
+ shortIt: "Campanello video",
19962
+ aliases: []
19963
+ },
19964
+ {
19965
+ id: "cover-blind",
19966
+ key: "uiLibrary.deviceMeta.noun.role.coverBlind",
19967
+ en: "Blind",
19968
+ it: "Tapparella",
19969
+ shortEn: "Blind",
19970
+ shortIt: "Tapparella",
19971
+ aliases: []
19972
+ },
19973
+ {
19974
+ id: "cover-shutter",
19975
+ key: "uiLibrary.deviceMeta.noun.role.coverShutter",
19976
+ en: "Shutter",
19977
+ it: "Persiana",
19978
+ shortEn: "Shutter",
19979
+ shortIt: "Persiana",
19980
+ aliases: []
19981
+ },
19982
+ {
19983
+ id: "cover-awning",
19984
+ key: "uiLibrary.deviceMeta.noun.role.coverAwning",
19985
+ en: "Awning",
19986
+ it: "Tenda da sole",
19987
+ shortEn: "Awning",
19988
+ shortIt: "Tenda da sole",
19989
+ aliases: []
19990
+ },
19991
+ {
19992
+ id: "cover-curtain",
19993
+ key: "uiLibrary.deviceMeta.noun.role.coverCurtain",
19994
+ en: "Curtain",
19995
+ it: "Tenda",
19996
+ shortEn: "Curtain",
19997
+ shortIt: "Tenda",
19998
+ aliases: []
19999
+ },
20000
+ {
20001
+ id: "cover-garage",
20002
+ key: "uiLibrary.deviceMeta.noun.role.coverGarage",
20003
+ en: "Garage door",
20004
+ it: "Porta del garage",
20005
+ shortEn: "Garage",
20006
+ shortIt: "Garage",
20007
+ aliases: []
20008
+ },
20009
+ {
20010
+ id: "cover-gate",
20011
+ key: "uiLibrary.deviceMeta.noun.role.coverGate",
20012
+ en: "Gate",
20013
+ it: "Cancello",
20014
+ shortEn: "Gate",
20015
+ shortIt: "Cancello",
20016
+ aliases: []
20017
+ },
20018
+ {
20019
+ id: "cover-door",
20020
+ key: "uiLibrary.deviceMeta.noun.role.coverDoor",
20021
+ en: "Door",
20022
+ it: "Porta",
20023
+ shortEn: "Door",
20024
+ shortIt: "Porta",
20025
+ aliases: []
20026
+ },
20027
+ {
20028
+ id: "cover-window",
20029
+ key: "uiLibrary.deviceMeta.noun.role.coverWindow",
20030
+ en: "Window",
20031
+ it: "Finestra",
20032
+ shortEn: "Window",
20033
+ shortIt: "Finestra",
20034
+ aliases: []
20035
+ },
20036
+ {
20037
+ id: "binary-helper",
20038
+ key: "uiLibrary.deviceMeta.noun.role.binaryHelper",
20039
+ en: "Binary helper",
20040
+ it: "Helper binario",
20041
+ shortEn: "Helper",
20042
+ shortIt: "Helper",
20043
+ aliases: []
20044
+ },
20045
+ {
20046
+ id: "outlet",
20047
+ key: "uiLibrary.deviceMeta.noun.role.outlet",
20048
+ en: "Outlet",
20049
+ it: "Presa",
20050
+ shortEn: "Outlet",
20051
+ shortIt: "Presa",
20052
+ aliases: []
20053
+ },
20054
+ {
20055
+ id: "motion-sensor",
20056
+ key: "uiLibrary.deviceMeta.noun.role.motionSensor",
20057
+ en: "Motion sensor",
20058
+ it: "Sensore di movimento",
20059
+ shortEn: "Motion",
20060
+ shortIt: "Movimento",
20061
+ aliases: []
20062
+ },
20063
+ {
20064
+ id: "contact-sensor",
20065
+ key: "uiLibrary.deviceMeta.noun.role.contactSensor",
20066
+ en: "Contact sensor",
20067
+ it: "Sensore di contatto",
20068
+ shortEn: "Contact",
20069
+ shortIt: "Contatto",
20070
+ aliases: []
20071
+ },
20072
+ {
20073
+ id: "leak-sensor",
20074
+ key: "uiLibrary.deviceMeta.noun.role.leakSensor",
20075
+ en: "Leak sensor",
20076
+ it: "Sensore di perdite",
20077
+ shortEn: "Leak",
20078
+ shortIt: "Perdita",
20079
+ aliases: []
20080
+ },
20081
+ {
20082
+ id: "smoke-sensor",
20083
+ key: "uiLibrary.deviceMeta.noun.role.smokeSensor",
20084
+ en: "Smoke sensor",
20085
+ it: "Sensore di fumo",
20086
+ shortEn: "Smoke",
20087
+ shortIt: "Fumo",
20088
+ aliases: []
20089
+ },
20090
+ {
20091
+ id: "co-sensor",
20092
+ key: "uiLibrary.deviceMeta.noun.role.cOSensor",
20093
+ en: "CO sensor",
20094
+ it: "Sensore di CO",
20095
+ shortEn: "CO",
20096
+ shortIt: "CO",
20097
+ aliases: []
20098
+ },
20099
+ {
20100
+ id: "gas-sensor",
20101
+ key: "uiLibrary.deviceMeta.noun.role.gasSensor",
20102
+ en: "Gas sensor",
20103
+ it: "Sensore di gas",
20104
+ shortEn: "Gas",
20105
+ shortIt: "Gas",
20106
+ aliases: []
20107
+ },
20108
+ {
20109
+ id: "tamper-sensor",
20110
+ key: "uiLibrary.deviceMeta.noun.role.tamperSensor",
20111
+ en: "Tamper sensor",
20112
+ it: "Sensore antimanomissione",
20113
+ shortEn: "Tamper",
20114
+ shortIt: "Manomissione",
20115
+ aliases: []
20116
+ },
20117
+ {
20118
+ id: "vibration-sensor",
20119
+ key: "uiLibrary.deviceMeta.noun.role.vibrationSensor",
20120
+ en: "Vibration sensor",
20121
+ it: "Sensore di vibrazione",
20122
+ shortEn: "Vibration",
20123
+ shortIt: "Vibrazione",
20124
+ aliases: []
20125
+ },
20126
+ {
20127
+ id: "connectivity-sensor",
20128
+ key: "uiLibrary.deviceMeta.noun.role.connectivitySensor",
20129
+ en: "Connectivity",
20130
+ it: "Connettività",
20131
+ shortEn: "Connectivity",
20132
+ shortIt: "Connettività",
20133
+ aliases: []
20134
+ },
20135
+ {
20136
+ id: "sound-sensor",
20137
+ key: "uiLibrary.deviceMeta.noun.role.soundSensor",
20138
+ en: "Sound sensor",
20139
+ it: "Sensore acustico",
20140
+ shortEn: "Sound",
20141
+ shortIt: "Suono",
20142
+ aliases: []
20143
+ },
20144
+ {
20145
+ id: "binary-sensor",
20146
+ key: "uiLibrary.deviceMeta.noun.role.binarySensor",
20147
+ en: "Binary sensor",
20148
+ it: "Sensore binario",
20149
+ shortEn: "Binary",
20150
+ shortIt: "Binario",
20151
+ aliases: []
20152
+ },
20153
+ {
20154
+ id: "temperature-sensor",
20155
+ key: "uiLibrary.deviceMeta.noun.role.temperatureSensor",
20156
+ en: "Temperature",
20157
+ it: "Temperatura",
20158
+ shortEn: "Temperature",
20159
+ shortIt: "Temperatura",
20160
+ aliases: []
20161
+ },
20162
+ {
20163
+ id: "humidity-sensor",
20164
+ key: "uiLibrary.deviceMeta.noun.role.humiditySensor",
20165
+ en: "Humidity",
20166
+ it: "Umidità",
20167
+ shortEn: "Humidity",
20168
+ shortIt: "Umidità",
20169
+ aliases: []
20170
+ },
20171
+ {
20172
+ id: "ambient-light-sensor",
20173
+ key: "uiLibrary.deviceMeta.noun.role.ambientLightSensor",
20174
+ en: "Ambient light",
20175
+ it: "Luce ambientale",
20176
+ shortEn: "Light",
20177
+ shortIt: "Luce",
20178
+ aliases: ["Illumination"]
20179
+ },
20180
+ {
20181
+ id: "pressure-sensor",
20182
+ key: "uiLibrary.deviceMeta.noun.role.pressureSensor",
20183
+ en: "Pressure",
20184
+ it: "Pressione",
20185
+ shortEn: "Pressure",
20186
+ shortIt: "Pressione",
20187
+ aliases: []
20188
+ },
20189
+ {
20190
+ id: "wind-sensor",
20191
+ key: "uiLibrary.deviceMeta.noun.role.windSensor",
20192
+ en: "Wind",
20193
+ it: "Vento",
20194
+ shortEn: "Wind",
20195
+ shortIt: "Vento",
20196
+ aliases: []
20197
+ },
20198
+ {
20199
+ id: "rain-sensor",
20200
+ key: "uiLibrary.deviceMeta.noun.role.rainSensor",
20201
+ en: "Rain",
20202
+ it: "Pioggia",
20203
+ shortEn: "Rain",
20204
+ shortIt: "Pioggia",
20205
+ aliases: []
20206
+ },
20207
+ {
20208
+ id: "uv-sensor",
20209
+ key: "uiLibrary.deviceMeta.noun.role.uvSensor",
20210
+ en: "UV index",
20211
+ it: "Indice UV",
20212
+ shortEn: "UV index",
20213
+ shortIt: "Indice UV",
20214
+ aliases: []
20215
+ },
20216
+ {
20217
+ id: "solar-radiation-sensor",
20218
+ key: "uiLibrary.deviceMeta.noun.role.solarRadiationSensor",
20219
+ en: "Solar radiation",
20220
+ it: "Radiazione solare",
20221
+ shortEn: "Solar radiation",
20222
+ shortIt: "Radiazione solare",
20223
+ aliases: []
20224
+ },
20225
+ {
20226
+ id: "soil-moisture-sensor",
20227
+ key: "uiLibrary.deviceMeta.noun.role.soilMoistureSensor",
20228
+ en: "Soil moisture",
20229
+ it: "Umidità del suolo",
20230
+ shortEn: "Soil moisture",
20231
+ shortIt: "Umidità del suolo",
20232
+ aliases: []
20233
+ },
20234
+ {
20235
+ id: "power-sensor",
20236
+ key: "uiLibrary.deviceMeta.noun.role.powerSensor",
20237
+ en: "Power",
20238
+ it: "Potenza",
20239
+ shortEn: "Power",
20240
+ shortIt: "Potenza",
20241
+ aliases: []
20242
+ },
20243
+ {
20244
+ id: "energy-sensor",
20245
+ key: "uiLibrary.deviceMeta.noun.role.energySensor",
20246
+ en: "Energy",
20247
+ it: "Energia",
20248
+ shortEn: "Energy",
20249
+ shortIt: "Energia",
20250
+ aliases: []
20251
+ },
20252
+ {
20253
+ id: "voltage-sensor",
20254
+ key: "uiLibrary.deviceMeta.noun.role.voltageSensor",
20255
+ en: "Voltage",
20256
+ it: "Tensione",
20257
+ shortEn: "Voltage",
20258
+ shortIt: "Tensione",
20259
+ aliases: []
20260
+ },
20261
+ {
20262
+ id: "current-sensor",
20263
+ key: "uiLibrary.deviceMeta.noun.role.currentSensor",
20264
+ en: "Current",
20265
+ it: "Corrente",
20266
+ shortEn: "Current",
20267
+ shortIt: "Corrente",
20268
+ aliases: []
20269
+ },
20270
+ {
20271
+ id: "air-quality-sensor",
20272
+ key: "uiLibrary.deviceMeta.noun.role.airQualitySensor",
20273
+ en: "Air quality",
20274
+ it: "Qualità dell’aria",
20275
+ shortEn: "Air quality",
20276
+ shortIt: "Qualità dell’aria",
20277
+ aliases: []
20278
+ },
20279
+ {
20280
+ id: "battery-sensor",
20281
+ key: "uiLibrary.deviceMeta.noun.role.batterySensor",
20282
+ en: "Battery",
20283
+ it: "Batteria",
20284
+ shortEn: "Battery",
20285
+ shortIt: "Batteria",
20286
+ aliases: []
20287
+ },
20288
+ {
20289
+ id: "numeric-sensor",
20290
+ key: "uiLibrary.deviceMeta.noun.role.numericSensor",
20291
+ en: "Numeric sensor",
20292
+ it: "Sensore numerico",
20293
+ shortEn: "Value",
20294
+ shortIt: "Numerico",
20295
+ aliases: []
20296
+ },
20297
+ {
20298
+ id: "enum-sensor",
20299
+ key: "uiLibrary.deviceMeta.noun.role.enumSensor",
20300
+ en: "Enum sensor",
20301
+ it: "Sensore a stati",
20302
+ shortEn: "State",
20303
+ shortIt: "Stato",
20304
+ aliases: []
20305
+ },
20306
+ {
20307
+ id: "datetime-sensor",
20308
+ key: "uiLibrary.deviceMeta.noun.role.dateTimeSensor",
20309
+ en: "Date / time",
20310
+ it: "Data / ora",
20311
+ shortEn: "Time",
20312
+ shortIt: "Ora",
20313
+ aliases: ["Last seen"]
20314
+ },
20315
+ {
20316
+ id: "generic-sensor",
20317
+ key: "uiLibrary.deviceMeta.noun.role.genericSensor",
20318
+ en: "Sensor",
20319
+ it: "Sensore",
20320
+ shortEn: "Sensor",
20321
+ shortIt: "Sensore",
20322
+ aliases: []
20323
+ },
20324
+ {
20325
+ id: "numeric-control",
20326
+ key: "uiLibrary.deviceMeta.noun.role.numericControl",
20327
+ en: "Numeric control",
20328
+ it: "Controllo numerico",
20329
+ shortEn: "Number",
20330
+ shortIt: "Numero",
20331
+ aliases: []
20332
+ },
20333
+ {
20334
+ id: "select-control",
20335
+ key: "uiLibrary.deviceMeta.noun.role.selectControl",
20336
+ en: "Select control",
20337
+ it: "Controllo a selezione",
20338
+ shortEn: "Select",
20339
+ shortIt: "Selezione",
20340
+ aliases: []
20341
+ },
20342
+ {
20343
+ id: "text-control",
20344
+ key: "uiLibrary.deviceMeta.noun.role.textControl",
20345
+ en: "Text control",
20346
+ it: "Controllo testo",
20347
+ shortEn: "Text",
20348
+ shortIt: "Testo",
20349
+ aliases: []
20350
+ },
20351
+ {
20352
+ id: "datetime-control",
20353
+ key: "uiLibrary.deviceMeta.noun.role.dateTimeControl",
20354
+ en: "Date / time control",
20355
+ it: "Controllo data / ora",
20356
+ shortEn: "Time",
20357
+ shortIt: "Ora",
20358
+ aliases: []
20359
+ },
20360
+ {
20361
+ id: "mobile-push-notifier",
20362
+ key: "uiLibrary.deviceMeta.noun.role.mobilePushNotifier",
20363
+ en: "Mobile push",
20364
+ it: "Push su mobile",
20365
+ shortEn: "Push",
20366
+ shortIt: "Push",
20367
+ aliases: []
20368
+ },
20369
+ {
20370
+ id: "messaging-notifier",
20371
+ key: "uiLibrary.deviceMeta.noun.role.messagingNotifier",
20372
+ en: "Messaging",
20373
+ it: "Messaggistica",
20374
+ shortEn: "Messaging",
20375
+ shortIt: "Messaggistica",
20376
+ aliases: []
20377
+ },
20378
+ {
20379
+ id: "email-notifier",
20380
+ key: "uiLibrary.deviceMeta.noun.role.emailNotifier",
20381
+ en: "Email",
20382
+ it: "Email",
20383
+ shortEn: "Email",
20384
+ shortIt: "Email",
20385
+ aliases: []
20386
+ },
20387
+ {
20388
+ id: "generic-notifier",
20389
+ key: "uiLibrary.deviceMeta.noun.role.genericNotifier",
20390
+ en: "Notifier",
20391
+ it: "Notificatore",
20392
+ shortEn: "Notifier",
20393
+ shortIt: "Notificatore",
20394
+ aliases: []
20395
+ }
20396
+ ];
20397
+ var DEVICE_TYPE_NOUNS = [
20398
+ {
20399
+ id: "camera",
20400
+ key: "uiLibrary.deviceMeta.noun.type.camera",
20401
+ en: "Camera",
20402
+ it: "Telecamera",
20403
+ shortEn: "Camera",
20404
+ shortIt: "Telecamera",
20405
+ aliases: []
20406
+ },
20407
+ {
20408
+ id: "hub",
20409
+ key: "uiLibrary.deviceMeta.noun.type.hub",
20410
+ en: "Hub",
20411
+ it: "Hub",
20412
+ shortEn: "Hub",
20413
+ shortIt: "Hub",
20414
+ aliases: []
20415
+ },
20416
+ {
20417
+ id: "light",
20418
+ key: "uiLibrary.deviceMeta.noun.type.light",
20419
+ en: "Light",
20420
+ it: "Luce",
20421
+ shortEn: "Light",
20422
+ shortIt: "Luce",
20423
+ aliases: []
20424
+ },
20425
+ {
20426
+ id: "siren",
20427
+ key: "uiLibrary.deviceMeta.noun.type.siren",
20428
+ en: "Siren",
20429
+ it: "Sirena",
20430
+ shortEn: "Siren",
20431
+ shortIt: "Sirena",
20432
+ aliases: []
20433
+ },
20434
+ {
20435
+ id: "switch",
20436
+ key: "uiLibrary.deviceMeta.noun.type.switch",
20437
+ en: "Switch",
20438
+ it: "Interruttore",
20439
+ shortEn: "Switch",
20440
+ shortIt: "Interruttore",
20441
+ aliases: []
20442
+ },
20443
+ {
20444
+ id: "sensor",
20445
+ key: "uiLibrary.deviceMeta.noun.type.sensor",
20446
+ en: "Sensor",
20447
+ it: "Sensore",
20448
+ shortEn: "Sensor",
20449
+ shortIt: "Sensore",
20450
+ aliases: []
20451
+ },
20452
+ {
20453
+ id: "thermostat",
20454
+ key: "uiLibrary.deviceMeta.noun.type.thermostat",
20455
+ en: "Thermostat",
20456
+ it: "Termostato",
20457
+ shortEn: "Thermostat",
20458
+ shortIt: "Termostato",
20459
+ aliases: []
20460
+ },
20461
+ {
20462
+ id: "climate",
20463
+ key: "uiLibrary.deviceMeta.noun.type.climate",
20464
+ en: "Air conditioner",
20465
+ it: "Condizionatore",
20466
+ shortEn: "Climate",
20467
+ shortIt: "Condizionatore",
20468
+ aliases: []
20469
+ },
20470
+ {
20471
+ id: "button",
20472
+ key: "uiLibrary.deviceMeta.noun.type.button",
20473
+ en: "Button",
20474
+ it: "Pulsante",
20475
+ shortEn: "Button",
20476
+ shortIt: "Pulsante",
20477
+ aliases: []
20478
+ },
20479
+ {
20480
+ id: "event-emitter",
20481
+ key: "uiLibrary.deviceMeta.noun.type.eventEmitter",
20482
+ en: "Event Emitter",
20483
+ it: "Emettitore di eventi",
20484
+ shortEn: "Event",
20485
+ shortIt: "Evento",
20486
+ aliases: []
20487
+ },
20488
+ {
20489
+ id: "update",
20490
+ key: "uiLibrary.deviceMeta.noun.type.update",
20491
+ en: "Update",
20492
+ it: "Aggiornamento",
20493
+ shortEn: "Update",
20494
+ shortIt: "Aggiornamento",
20495
+ aliases: []
20496
+ },
20497
+ {
20498
+ id: "generic",
20499
+ key: "uiLibrary.deviceMeta.noun.type.generic",
20500
+ en: "Generic",
20501
+ it: "Generico",
20502
+ shortEn: "Generic",
20503
+ shortIt: "Generico",
20504
+ aliases: []
20505
+ },
20506
+ {
20507
+ id: "notifier",
20508
+ key: "uiLibrary.deviceMeta.noun.type.notifier",
20509
+ en: "Notifier",
20510
+ it: "Notificatore",
20511
+ shortEn: "Notifier",
20512
+ shortIt: "Notificatore",
20513
+ aliases: []
20514
+ },
20515
+ {
20516
+ id: "script",
20517
+ key: "uiLibrary.deviceMeta.noun.type.script",
20518
+ en: "Script",
20519
+ it: "Script",
20520
+ shortEn: "Script",
20521
+ shortIt: "Script",
20522
+ aliases: []
20523
+ },
20524
+ {
20525
+ id: "automation",
20526
+ key: "uiLibrary.deviceMeta.noun.type.automation",
20527
+ en: "Automation",
20528
+ it: "Automazione",
20529
+ shortEn: "Automation",
20530
+ shortIt: "Automazione",
20531
+ aliases: []
20532
+ },
20533
+ {
20534
+ id: "lock",
20535
+ key: "uiLibrary.deviceMeta.noun.type.lock",
20536
+ en: "Lock",
20537
+ it: "Serratura",
20538
+ shortEn: "Lock",
20539
+ shortIt: "Serratura",
20540
+ aliases: []
20541
+ },
20542
+ {
20543
+ id: "cover",
20544
+ key: "uiLibrary.deviceMeta.noun.type.cover",
20545
+ en: "Cover",
20546
+ it: "Copertura",
20547
+ shortEn: "Cover",
20548
+ shortIt: "Copertura",
20549
+ aliases: []
20550
+ },
20551
+ {
20552
+ id: "valve",
20553
+ key: "uiLibrary.deviceMeta.noun.type.valve",
20554
+ en: "Valve",
20555
+ it: "Valvola",
20556
+ shortEn: "Valve",
20557
+ shortIt: "Valvola",
20558
+ aliases: []
20559
+ },
20560
+ {
20561
+ id: "humidifier",
20562
+ key: "uiLibrary.deviceMeta.noun.type.humidifier",
20563
+ en: "Humidifier",
20564
+ it: "Umidificatore",
20565
+ shortEn: "Humidifier",
20566
+ shortIt: "Umidificatore",
20567
+ aliases: []
20568
+ },
20569
+ {
20570
+ id: "water-heater",
20571
+ key: "uiLibrary.deviceMeta.noun.type.waterHeater",
20572
+ en: "Water heater",
20573
+ it: "Scaldabagno",
20574
+ shortEn: "Heater",
20575
+ shortIt: "Scaldabagno",
20576
+ aliases: []
20577
+ },
20578
+ {
20579
+ id: "fan",
20580
+ key: "uiLibrary.deviceMeta.noun.type.fan",
20581
+ en: "Fan",
20582
+ it: "Ventilatore",
20583
+ shortEn: "Fan",
20584
+ shortIt: "Ventilatore",
20585
+ aliases: []
20586
+ },
20587
+ {
20588
+ id: "media-player",
20589
+ key: "uiLibrary.deviceMeta.noun.type.mediaPlayer",
20590
+ en: "Media player",
20591
+ it: "Lettore multimediale",
20592
+ shortEn: "Player",
20593
+ shortIt: "Lettore",
20594
+ aliases: []
20595
+ },
20596
+ {
20597
+ id: "alarm-panel",
20598
+ key: "uiLibrary.deviceMeta.noun.type.alarmPanel",
20599
+ en: "Alarm panel",
20600
+ it: "Pannello allarme",
20601
+ shortEn: "Alarm",
20602
+ shortIt: "Allarme",
20603
+ aliases: []
20604
+ },
20605
+ {
20606
+ id: "control",
20607
+ key: "uiLibrary.deviceMeta.noun.type.control",
20608
+ en: "Control",
20609
+ it: "Controllo",
20610
+ shortEn: "Control",
20611
+ shortIt: "Controllo",
20612
+ aliases: []
20613
+ },
20614
+ {
20615
+ id: "presence",
20616
+ key: "uiLibrary.deviceMeta.noun.type.presence",
20617
+ en: "Presence",
20618
+ it: "Presenza",
20619
+ shortEn: "Presence",
20620
+ shortIt: "Presenza",
20621
+ aliases: []
20622
+ },
20623
+ {
20624
+ id: "weather",
20625
+ key: "uiLibrary.deviceMeta.noun.type.weather",
20626
+ en: "Weather",
20627
+ it: "Meteo",
20628
+ shortEn: "Weather",
20629
+ shortIt: "Meteo",
20630
+ aliases: []
20631
+ },
20632
+ {
20633
+ id: "vacuum",
20634
+ key: "uiLibrary.deviceMeta.noun.type.vacuum",
20635
+ en: "Vacuum",
20636
+ it: "Aspirapolvere",
20637
+ shortEn: "Vacuum",
20638
+ shortIt: "Aspirapolvere",
20639
+ aliases: []
20640
+ },
20641
+ {
20642
+ id: "lawn-mower",
20643
+ key: "uiLibrary.deviceMeta.noun.type.lawnMower",
20644
+ en: "Lawn mower",
20645
+ it: "Tagliaerba",
20646
+ shortEn: "Mower",
20647
+ shortIt: "Tagliaerba",
20648
+ aliases: []
20649
+ },
20650
+ {
20651
+ id: "container",
20652
+ key: "uiLibrary.deviceMeta.noun.type.container",
20653
+ en: "Container",
20654
+ it: "Contenitore",
20655
+ shortEn: "Container",
20656
+ shortIt: "Contenitore",
20657
+ aliases: []
20658
+ },
20659
+ {
20660
+ id: "image",
20661
+ key: "uiLibrary.deviceMeta.noun.type.image",
20662
+ en: "Image",
20663
+ it: "Immagine",
20664
+ shortEn: "Image",
20665
+ shortIt: "Immagine",
20666
+ aliases: []
20667
+ },
20668
+ {
20669
+ id: "pet-feeder",
20670
+ key: "uiLibrary.deviceMeta.noun.type.petFeeder",
20671
+ en: "Pet feeder",
20672
+ it: "Distributore di cibo per animali",
20673
+ shortEn: "Feeder",
20674
+ shortIt: "Distributore",
20675
+ aliases: []
20676
+ },
20677
+ {
20678
+ id: "dishwasher",
20679
+ key: "uiLibrary.deviceMeta.noun.type.dishwasher",
20680
+ en: "Dishwasher",
20681
+ it: "Lavastoviglie",
20682
+ shortEn: "Dishwasher",
20683
+ shortIt: "Lavastoviglie",
20684
+ aliases: []
20685
+ }
20686
+ ];
20687
+ var FALLBACK_NOUN = {
20688
+ id: "",
20689
+ key: "uiLibrary.deviceMeta.noun.fallback",
20690
+ en: "Device",
20691
+ it: "Dispositivo",
20692
+ shortEn: "Device",
20693
+ shortIt: "Dispositivo",
20694
+ aliases: []
20695
+ };
20696
+ function findNoun(list, id) {
20697
+ if (id === null || id.length === 0) return void 0;
20698
+ return list.find((spec) => spec.id === id);
20699
+ }
20700
+ /** Role wins when it has a noun; otherwise the type. Unknown types fall back. */
20701
+ function nounSpecFor(role, type) {
20702
+ if (role !== null) {
20703
+ const byRole = findNoun(DEVICE_ROLE_NOUNS, role);
20704
+ if (byRole !== void 0) return byRole;
20705
+ }
20706
+ return findNoun(DEVICE_TYPE_NOUNS, type) ?? FALLBACK_NOUN;
20707
+ }
20708
+ function stockPhrases(role, type) {
20709
+ const phrases = [];
20710
+ const specs = [findNoun(DEVICE_ROLE_NOUNS, role), findNoun(DEVICE_TYPE_NOUNS, type)];
20711
+ for (const spec of specs) {
20712
+ if (spec === void 0) continue;
20713
+ phrases.push(spec.en, spec.it, spec.shortEn, spec.shortIt, ...spec.aliases);
20714
+ }
20715
+ return phrases;
20716
+ }
20717
+ function isDeviceNameStock(text, role, type) {
20718
+ const folded = foldDeviceName(text);
20719
+ if (folded.length === 0) return true;
20720
+ return stockPhrases(role, type).some((phrase) => foldDeviceName(phrase) === folded);
20721
+ }
20722
+ /** Noun table for a locale. Tests and the viewer parity spec use this directly. */
20723
+ function deviceNameNouns(locale) {
20724
+ return {
20725
+ shortNoun(role, type) {
20726
+ const spec = nounSpecFor(role, type);
20727
+ return locale === "it" ? spec.shortIt : spec.shortEn;
20728
+ },
20729
+ isStockPhrase(text, role, type) {
20730
+ return isDeviceNameStock(text, role, type);
20731
+ }
20732
+ };
20733
+ }
20734
+ //#endregion
20735
+ //#region src/composites/device-name/use-device-label.ts
20736
+ /**
20737
+ * `useDeviceLabel` is the string `DeviceName` renders — sort keys, aria
20738
+ * labels, confirm copy and search titles call it instead of painting
20739
+ * `device.name`.
20740
+ */
20741
+ function useDeviceNameNouns() {
20742
+ const t = useUiText();
20743
+ return {
20744
+ shortNoun(role, type) {
20745
+ const spec = nounSpecFor(role, type);
20746
+ return t(spec.key, spec.shortEn);
20747
+ },
20748
+ isStockPhrase(text, role, type) {
20749
+ return isDeviceNameStock(text, role, type);
20750
+ }
20751
+ };
20752
+ }
20753
+ function placeholderParent(id) {
20754
+ return {
20755
+ id,
20756
+ name: `#${String(id)}`,
20757
+ type: "unknown",
20758
+ stableId: "",
20759
+ role: null,
20760
+ parentDeviceId: null
20761
+ };
20762
+ }
20763
+ function parentFromIndex(device, index) {
20764
+ const parentId = device.parentDeviceId;
20765
+ if (parentId === null || parentId === void 0) return null;
20766
+ return index.byId.get(parentId) ?? placeholderParent(parentId);
20767
+ }
20768
+ function siblingsFromIndex(device, index) {
20769
+ const parentId = device.parentDeviceId;
20770
+ if (parentId === null || parentId === void 0) return [];
20771
+ const ids = index.childIds.get(parentId) ?? [];
20772
+ const siblings = [];
20773
+ for (const id of ids) {
20774
+ const row = index.byId.get(id);
20775
+ if (row !== void 0) siblings.push(row);
20776
+ }
20777
+ if (!siblings.some((sibling) => sibling.id === device.id)) siblings.push(device);
20778
+ return siblings;
20779
+ }
20780
+ /**
20781
+ * Label for a device the caller already holds. While the fleet has not
20782
+ * answered and no parent was passed, this is the stored name — not `#id`.
20783
+ */
20784
+ function resolveDeviceLabel(device, form, index, nouns, scope) {
20785
+ const subject = index?.byId.get(device.id) ?? device;
20786
+ const parentId = subject.parentDeviceId;
20787
+ if (parentId !== null && parentId !== void 0 && index === null && scope?.parent === void 0) return subject.name;
20788
+ return formatDeviceName(subject, form, {
20789
+ parent: scope?.parent !== void 0 ? scope.parent : index === null ? null : parentFromIndex(subject, index),
20790
+ siblings: scope?.siblings ?? (index === null ? [] : siblingsFromIndex(subject, index))
20791
+ }, nouns);
20792
+ }
20793
+ function useDeviceLabel(device, form, scope) {
20794
+ return resolveDeviceLabel(device, form, useDeviceNameIndex(), useDeviceNameNouns(), scope);
20795
+ }
20796
+ /** `#id` until the fleet answers, and `#id` when the id is not in it. */
20797
+ function useDeviceLabelById(id, form, pendingName) {
20798
+ const index = useDeviceNameIndex();
20799
+ const nouns = useDeviceNameNouns();
20800
+ if (index === null) return pendingName ?? `#${String(id)}`;
20801
+ const device = index.byId.get(id);
20802
+ if (device === void 0) return `#${String(id)}`;
20803
+ return resolveDeviceLabel(device, form, index, nouns);
20804
+ }
20805
+ //#endregion
20806
+ //#region src/composites/device-name/DeviceName.tsx
20807
+ function DeviceName({ device, form, parent, siblings, className, testId }) {
20808
+ const label = useDeviceLabel(device, form, {
20809
+ ...parent !== void 0 ? { parent } : {},
20810
+ ...siblings !== void 0 ? { siblings } : {}
20811
+ });
20812
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
20813
+ className,
20814
+ title: label,
20815
+ "data-testid": testId,
20816
+ children: label
20817
+ });
20818
+ }
20819
+ //#endregion
19630
20820
  //#region src/hooks/use-device-proxy.ts
19631
20821
  /**
19632
20822
  * useDeviceProxy — canonical React entry point for a device.
@@ -20529,6 +21719,9 @@ function GridCellBox({ cell, selected, onSelect, onDragStart }) {
20529
21719
  }, handle))
20530
21720
  });
20531
21721
  }
21722
+ function CameraOptionLabel({ camera }) {
21723
+ return useDeviceLabel(camera, "compact");
21724
+ }
20532
21725
  function CellInspector({ cell, index, cameras, onChange, onRemove }) {
20533
21726
  const t = useUiText();
20534
21727
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -20559,7 +21752,7 @@ function CellInspector({ cell, index, cameras, onChange, onRemove }) {
20559
21752
  className: "flex-1 rounded border border-border bg-transparent px-1 py-0.5",
20560
21753
  children: cameras.map((camera) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
20561
21754
  value: String(camera.id),
20562
- children: camera.name
21755
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CameraOptionLabel, { camera })
20563
21756
  }, camera.id))
20564
21757
  })]
20565
21758
  }),
@@ -48247,17 +49440,19 @@ function SensorHeroInner({ trpc, device }) {
48247
49440
  if (resolution.kind === "quiet") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
48248
49441
  className: "flex flex-col items-center justify-center gap-2 rounded-lg border border-border bg-surface p-4 min-h-[96px]",
48249
49442
  "data-testid": "sensor-hero-quiet",
48250
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48251
- className: "text-sm text-foreground-subtle",
48252
- children: device.name
49443
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49444
+ device,
49445
+ form: "full",
49446
+ className: "text-sm text-foreground-subtle"
48253
49447
  })
48254
49448
  });
48255
49449
  const slice = resolution.kind === "value" ? resolution.slice : void 0;
48256
49450
  if (descriptor === void 0 || slice === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
48257
49451
  className: "flex flex-col items-center justify-center gap-2 rounded-lg border border-border bg-surface p-4 min-h-[96px]",
48258
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48259
- className: "text-sm text-foreground-subtle",
48260
- children: device.name
49452
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49453
+ device,
49454
+ form: "full",
49455
+ className: "text-sm text-foreground-subtle"
48261
49456
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48262
49457
  className: "text-xs text-foreground-subtle italic",
48263
49458
  children: t("uiLibrary.sensorHeroCard.awaiting", "Awaiting state…")
@@ -48297,9 +49492,10 @@ function SensorHeroInner({ trpc, device }) {
48297
49492
  "data-testid": "sensor-hero-occupancy",
48298
49493
  children: occupancyOn ? t("uiLibrary.sensorHeroCard.occupied", "Occupied") : t("uiLibrary.sensorHeroCard.clear", "Clear")
48299
49494
  }),
48300
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48301
- className: "text-sm text-foreground-subtle",
48302
- children: device.name
49495
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49496
+ device,
49497
+ form: "full",
49498
+ className: "text-sm text-foreground-subtle"
48303
49499
  })
48304
49500
  ]
48305
49501
  });
@@ -48314,9 +49510,10 @@ function SensorHeroInner({ trpc, device }) {
48314
49510
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48315
49511
  className: cn("inline-flex items-center rounded px-3 py-1.5 text-lg font-semibold", active ? "bg-warning/15 text-warning" : "bg-surface-subtle text-foreground-subtle"),
48316
49512
  children: label
48317
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48318
- className: "text-sm text-foreground-subtle",
48319
- children: device.name
49513
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49514
+ device,
49515
+ form: "full",
49516
+ className: "text-sm text-foreground-subtle"
48320
49517
  })]
48321
49518
  });
48322
49519
  }
@@ -48324,9 +49521,10 @@ function SensorHeroInner({ trpc, device }) {
48324
49521
  if (!isStringSliceValue(slice)) return null;
48325
49522
  if (slice.value === "") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
48326
49523
  className: "flex flex-col items-center justify-center gap-2 rounded-lg border border-border bg-surface p-4 min-h-[96px]",
48327
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48328
- className: "text-sm text-foreground-subtle",
48329
- children: device.name
49524
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49525
+ device,
49526
+ form: "full",
49527
+ className: "text-sm text-foreground-subtle"
48330
49528
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48331
49529
  className: "text-xs text-foreground-subtle italic",
48332
49530
  children: t("uiLibrary.sensorHeroCard.awaiting", "Awaiting state…")
@@ -48338,9 +49536,10 @@ function SensorHeroInner({ trpc, device }) {
48338
49536
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48339
49537
  className: "text-3xl font-semibold tabular-nums text-foreground",
48340
49538
  children: formatControlDateTime(slice.value, format)
48341
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48342
- className: "text-sm text-foreground-subtle",
48343
- children: device.name
49539
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49540
+ device,
49541
+ form: "full",
49542
+ className: "text-sm text-foreground-subtle"
48344
49543
  })]
48345
49544
  });
48346
49545
  }
@@ -48352,9 +49551,10 @@ function SensorHeroInner({ trpc, device }) {
48352
49551
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48353
49552
  className: "text-xl font-semibold text-foreground",
48354
49553
  children: slice.value
48355
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48356
- className: "text-sm text-foreground-subtle",
48357
- children: device.name
49554
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49555
+ device,
49556
+ form: "full",
49557
+ className: "text-sm text-foreground-subtle"
48358
49558
  })]
48359
49559
  });
48360
49560
  }
@@ -48365,9 +49565,10 @@ function SensorHeroInner({ trpc, device }) {
48365
49565
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48366
49566
  className: cn("inline-flex items-center rounded px-3 py-1.5 text-lg font-semibold", slice.state === "home" ? "bg-success/15 text-success" : "bg-surface-subtle text-foreground-subtle"),
48367
49567
  children: label
48368
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
48369
- className: "text-sm text-foreground-subtle",
48370
- children: device.name
49568
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
49569
+ device,
49570
+ form: "full",
49571
+ className: "text-sm text-foreground-subtle"
48371
49572
  })]
48372
49573
  });
48373
49574
  }
@@ -55122,7 +56323,9 @@ var CONTROL_FILLS = {
55122
56323
  [_camstack_types.DeviceType.Container]: false,
55123
56324
  [_camstack_types.DeviceType.Image]: false,
55124
56325
  [_camstack_types.DeviceType.PetFeeder]: false,
55125
- [_camstack_types.DeviceType.Dishwasher]: false
56326
+ [_camstack_types.DeviceType.Dishwasher]: false,
56327
+ [_camstack_types.DeviceType.Washer]: false,
56328
+ [_camstack_types.DeviceType.Dryer]: false
55126
56329
  };
55127
56330
  var DEVICE_TYPE_VALUES = new Set(Object.values(_camstack_types.DeviceType));
55128
56331
  /**
@@ -56378,6 +57581,7 @@ function useContainerChildren(deviceId) {
56378
57581
  var SENSOR_VALUE_TYPES = new Set([_camstack_types.DeviceType.Sensor, _camstack_types.DeviceType.Presence]);
56379
57582
  function DeviceItemActions({ trpc, device, controlPlacement }) {
56380
57583
  const containerChildren = useContainerChildren(device.id);
57584
+ const deviceLabel = useDeviceLabel(device, "compact");
56381
57585
  const placement = controlPlacement ?? "right";
56382
57586
  if (!!device.disabled) return null;
56383
57587
  if (device.type === _camstack_types.DeviceType.Container) {
@@ -56433,7 +57637,7 @@ function DeviceItemActions({ trpc, device, controlPlacement }) {
56433
57637
  showReboot && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RebootQuickAction, {
56434
57638
  trpc,
56435
57639
  deviceId: device.id,
56436
- deviceName: device.name
57640
+ deviceName: deviceLabel
56437
57641
  }),
56438
57642
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdminLinkQuickAction, {
56439
57643
  trpc,
@@ -56669,9 +57873,16 @@ function ChildRow$1({ trpc, child, onNavigate, parentName }) {
56669
57873
  running: runningSlice === void 0 || runningSlice.running === null ? null : runningSlice.running,
56670
57874
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChildIcon, { className: "h-3 w-3 text-foreground-subtle flex-shrink-0" })
56671
57875
  }),
56672
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
56673
- className: "text-[10.5px] text-foreground flex-1 truncate",
56674
- children: childListName(child, parentName)
57876
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
57877
+ device: child,
57878
+ form: "nested",
57879
+ parent: {
57880
+ id: child.parentDeviceId ?? 0,
57881
+ name: parentName,
57882
+ type: "unknown",
57883
+ stableId: ""
57884
+ },
57885
+ className: "text-[10.5px] text-foreground flex-1 truncate"
56675
57886
  }),
56676
57887
  showMotionTrigger && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChildMotionTriggerToggle, {
56677
57888
  trpc,
@@ -56778,8 +57989,9 @@ function IconAction({ icon: Icon, label, onClick, danger = false }) {
56778
57989
  function DeviceDeleteAction({ device, onDelete }) {
56779
57990
  const t = useUiText();
56780
57991
  const confirm = useConfirm();
57992
+ const deviceLabel = useDeviceLabel(device, "full");
56781
57993
  const requestDelete = async () => {
56782
- const label = device.name.trim() !== "" ? device.name : `#${String(device.id)}`;
57994
+ const label = deviceLabel.trim() !== "" ? deviceLabel : `#${String(device.id)}`;
56783
57995
  if (await confirm({
56784
57996
  title: t("uiLibrary.deviceDeleteAction.title", "Delete device?"),
56785
57997
  message: t("uiLibrary.deviceDeleteAction.message", "This will remove \"{{label}}\" and all its settings. This action cannot be undone.", { label }),
@@ -57062,7 +58274,7 @@ var CHEVRON_SLOT_CLASS = {
57062
58274
  horizontal: "h-3.5 w-3.5",
57063
58275
  stacked: "h-4 w-4 mt-0.5"
57064
58276
  };
57065
- function DeviceItemHeader({ trpc, device, variant, flags, parent, integrationIcon, hasChildren, expanded, onToggleExpand, onNavigateToParent, stacked = false, nameOverflow = "truncate" }) {
58277
+ function DeviceItemHeader({ trpc, device, variant, flags, parent, integrationIcon, hasChildren, expanded, onToggleExpand, onNavigateToParent, stacked = false, nameOverflow = "truncate", nameForm = "compact" }) {
57066
58278
  const t = useUiText();
57067
58279
  const containerChildren = useContainerChildren(device.id);
57068
58280
  const roleDefaults = useRoleDisplayDefaults();
@@ -57073,6 +58285,13 @@ function DeviceItemHeader({ trpc, device, variant, flags, parent, integrationIco
57073
58285
  const slotClass = stacked ? CHEVRON_SLOT_CLASS.stacked : CHEVRON_SLOT_CLASS.horizontal;
57074
58286
  const iconClass = cn("text-foreground-subtle flex-shrink-0", stacked ? "h-4 w-4 mt-0.5" : variant === "minimal" ? "h-3 w-3" : "h-3.5 w-3.5");
57075
58287
  const lineClass = nameOverflow === "scroll" ? NAME_TEXT_CLASS : "truncate";
58288
+ const nameParent = nameForm === "nested" && parent !== null ? {
58289
+ id: parent.id,
58290
+ name: parent.name,
58291
+ type: parent.type ?? "unknown",
58292
+ stableId: ""
58293
+ } : void 0;
58294
+ const parentLabel = useDeviceLabelById(parent?.id ?? 0, "compact", parent?.name);
57076
58295
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
57077
58296
  className: cn(stacked ? "flex items-start gap-2 w-full min-w-0" : "flex items-center gap-2 flex-1 min-w-0"),
57078
58297
  children: [
@@ -57103,11 +58322,12 @@ function DeviceItemHeader({ trpc, device, variant, flags, parent, integrationIco
57103
58322
  children: [flags.showIntegrationIcon && integrationIcon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
57104
58323
  className: "flex-shrink-0",
57105
58324
  children: integrationIcon
57106
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
57107
- "data-testid": "device-name",
57108
- className: cn("font-semibold text-foreground", stacked ? "text-[13px] leading-snug line-clamp-2 break-words" : cn(lineClass, variant === "minimal" ? "text-[10.5px]" : "text-[11px]")),
57109
- title: device.name,
57110
- children: device.name
58325
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
58326
+ device,
58327
+ form: nameForm,
58328
+ ...nameParent !== void 0 ? { parent: nameParent } : {},
58329
+ testId: "device-name",
58330
+ className: cn("font-semibold text-foreground", stacked ? "text-[13px] leading-snug line-clamp-2 break-words" : cn(lineClass, variant === "minimal" ? "text-[10.5px]" : "text-[11px]"))
57111
58331
  })]
57112
58332
  }),
57113
58333
  (flags.showId || flags.showStableId) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -57136,12 +58356,12 @@ function DeviceItemHeader({ trpc, device, variant, flags, parent, integrationIco
57136
58356
  },
57137
58357
  className: cn("mt-0.5 inline-flex items-center gap-1 text-[9px] text-foreground-subtle hover:text-primary transition-colors", nameOverflow === "scroll" ? "md:max-w-full" : "max-w-full"),
57138
58358
  title: t("uiLibrary.deviceItemHeader.openParent", "Open parent: {{name}} (#{{id}})", {
57139
- name: parent.name,
58359
+ name: parentLabel,
57140
58360
  id: parent.id
57141
58361
  }),
57142
58362
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Network, { className: "h-2.5 w-2.5 shrink-0" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
57143
58363
  className: lineClass,
57144
- children: t("uiLibrary.deviceItemHeader.under", "Under: {{name}}", { name: parent.name })
58364
+ children: t("uiLibrary.deviceItemHeader.under", "Under: {{name}}", { name: parentLabel })
57145
58365
  })]
57146
58366
  })
57147
58367
  ]
@@ -57270,7 +58490,9 @@ function HoverZoomImage({ src, alt, caption, placeholder = "No image", thumbClas
57270
58490
  * the 288 px hover zoom. */
57271
58491
  var PREVIEW_SNAPSHOT_WIDTH = 480;
57272
58492
  function DeviceItemPreview({ trpc, device, status, enabled, showStatusPills = false }) {
57273
- const snapshot = useDeviceSnapshotImage(enabled && device.online ? device.id : null, { width: PREVIEW_SNAPSHOT_WIDTH });
58493
+ const liveActive = enabled && device.online;
58494
+ const deviceLabel = useDeviceLabel(device, "compact");
58495
+ const snapshot = useDeviceSnapshotImage(liveActive ? device.id : null, { width: PREVIEW_SNAPSHOT_WIDTH });
57274
58496
  const isBatteryOperated = (device.features ?? []).includes("battery-operated");
57275
58497
  const wantPills = showStatusPills && isBatteryOperated && !device.disabled;
57276
58498
  const batteryStatus = useDeviceBattery(trpc, wantPills ? device.id : null);
@@ -57286,7 +58508,7 @@ function DeviceItemPreview({ trpc, device, status, enabled, showStatusPills = fa
57286
58508
  wantLinkPill && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NetworkLinkBadge, { status: linkStatus }),
57287
58509
  enabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HoverZoomImage, {
57288
58510
  src: offline ? null : snapshot.src,
57289
- alt: device.name,
58511
+ alt: deviceLabel,
57290
58512
  placeholder,
57291
58513
  thumbClassName: "h-7 w-7 rounded"
57292
58514
  })
@@ -57509,6 +58731,14 @@ function DeviceItem(props) {
57509
58731
  const flags = resolveFlags(props);
57510
58732
  const variant = props.variant ?? "compact";
57511
58733
  const view = props.view ?? "card";
58734
+ const nameForm = props.isAccessoryRow ? "nested" : "compact";
58735
+ const nameParent = nameForm === "nested" && parent ? {
58736
+ id: parent.id,
58737
+ name: parent.name,
58738
+ type: parent.type ?? "unknown",
58739
+ stableId: ""
58740
+ } : void 0;
58741
+ const rowLabel = useDeviceLabel(device, nameForm, nameParent !== void 0 ? { parent: nameParent } : void 0);
57512
58742
  const enabled = !device.disabled;
57513
58743
  const isCamera = device.isCamera ?? device.type.toLowerCase() === "camera";
57514
58744
  const childrenList = props.children ?? [];
@@ -57584,7 +58814,7 @@ function DeviceItem(props) {
57584
58814
  checked: selection.selected,
57585
58815
  onChange: selection.onToggle,
57586
58816
  onClick: (e) => e.stopPropagation(),
57587
- "aria-label": t("uiLibrary.deviceItem.select", "Select {{name}}", { name: device.name }),
58817
+ "aria-label": t("uiLibrary.deviceItem.select", "Select {{name}}", { name: rowLabel }),
57588
58818
  className: "flex-shrink-0"
57589
58819
  }),
57590
58820
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceItemHeader, {
@@ -57593,6 +58823,7 @@ function DeviceItem(props) {
57593
58823
  variant,
57594
58824
  flags,
57595
58825
  parent: parent ?? null,
58826
+ nameForm,
57596
58827
  integrationIcon: integrationIcon ?? null,
57597
58828
  hasChildren,
57598
58829
  expanded,
@@ -57661,7 +58892,7 @@ function DeviceItem(props) {
57661
58892
  checked: selection.selected,
57662
58893
  onChange: selection.onToggle,
57663
58894
  onClick: (e) => e.stopPropagation(),
57664
- "aria-label": t("uiLibrary.deviceItem.select", "Select {{name}}", { name: device.name }),
58895
+ "aria-label": t("uiLibrary.deviceItem.select", "Select {{name}}", { name: rowLabel }),
57665
58896
  className: "flex-shrink-0 mt-0.5"
57666
58897
  }),
57667
58898
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceItemHeader, {
@@ -57670,6 +58901,7 @@ function DeviceItem(props) {
57670
58901
  variant,
57671
58902
  flags,
57672
58903
  parent: parent ?? null,
58904
+ nameForm,
57673
58905
  integrationIcon: integrationIcon ?? null,
57674
58906
  hasChildren,
57675
58907
  expanded,
@@ -57730,6 +58962,14 @@ function DeviceItemTableRow(props) {
57730
58962
  const t = useUiText();
57731
58963
  const { trpc, device, variant, flags, parent, integrationIcon, accessoryCount, adoptedCount, hasChildren, expanded, onToggleExpand, onNavigate, onRowClick, rowSelects, rowSelected, displayStatus, isCamera, showSnapshot, isAccessoryRow, indentLevel, className, renderRowAction, onDelete, selection, showTypeCell, showManufacturerCell, showFeaturesCell } = props;
57732
58964
  const containerChildren = useContainerChildren(device.id);
58965
+ const nameForm = isAccessoryRow ? "nested" : "compact";
58966
+ const nameParent = nameForm === "nested" && parent ? {
58967
+ id: parent.id,
58968
+ name: parent.name,
58969
+ type: parent.type ?? "unknown",
58970
+ stableId: ""
58971
+ } : void 0;
58972
+ const rowLabel = useDeviceLabel(device, nameForm, nameParent !== void 0 ? { parent: nameParent } : void 0);
57733
58973
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
57734
58974
  onClick: onRowClick,
57735
58975
  ...rowSelects ? {
@@ -57754,7 +58994,7 @@ function DeviceItemTableRow(props) {
57754
58994
  checked: selection.selected,
57755
58995
  onChange: selection.onToggle,
57756
58996
  onClick: (e) => e.stopPropagation(),
57757
- "aria-label": t("uiLibrary.deviceItem.select", "Select {{name}}", { name: device.name }),
58997
+ "aria-label": t("uiLibrary.deviceItem.select", "Select {{name}}", { name: rowLabel }),
57758
58998
  className: "flex-shrink-0"
57759
58999
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceItemHeader, {
57760
59000
  trpc,
@@ -57762,6 +59002,7 @@ function DeviceItemTableRow(props) {
57762
59002
  variant,
57763
59003
  flags,
57764
59004
  parent,
59005
+ nameForm,
57765
59006
  integrationIcon,
57766
59007
  hasChildren,
57767
59008
  expanded,
@@ -58500,13 +59741,13 @@ function TableRowGroup({ parent, accessories, hasAccessories, expanded, onToggle
58500
59741
  function AccessorySubRow({ trpc, accessory, parentName, showType, showManufacturer, showFeatures, onNavigate, muted }) {
58501
59742
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceItem, {
58502
59743
  trpc,
58503
- device: {
58504
- ...accessory,
58505
- name: childListName(accessory, parentName)
58506
- },
59744
+ device: accessory,
58507
59745
  view: "table",
58508
59746
  integrationIcon: null,
58509
- parent: null,
59747
+ parent: accessory.parentDeviceId === null ? null : {
59748
+ id: accessory.parentDeviceId,
59749
+ name: parentName
59750
+ },
58510
59751
  isAccessoryRow: true,
58511
59752
  showChildrenAccordion: false,
58512
59753
  onNavigate: onNavigate ?? void 0,
@@ -59305,6 +60546,15 @@ function DeviceExportSetup({ addonId }) {
59305
60546
  }), status.setup && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SetupSection, { setup: status.setup })]
59306
60547
  });
59307
60548
  }
60549
+ function ExportSelectCell({ device, checked, onToggle }) {
60550
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
60551
+ type: "checkbox",
60552
+ checked,
60553
+ onChange: onToggle,
60554
+ onClick: (e) => e.stopPropagation(),
60555
+ "aria-label": useUiText()("uiLibrary.deviceExport.select", "Select {{name}}", { name: useDeviceLabel(device, "compact") })
60556
+ });
60557
+ }
59308
60558
  /**
59309
60559
  * Per-row Expose/Exposed toggle. Reflects whether the row's device is exposed
59310
60560
  * to THIS exporter and flips it through the SAME runner the bulk buttons use —
@@ -59316,6 +60566,7 @@ function DeviceExportSetup({ addonId }) {
59316
60566
  */
59317
60567
  function ExportRowToggle({ addonId, device, exposed, busy, onToggle }) {
59318
60568
  const t = useUiText();
60569
+ const deviceLabel = useDeviceLabel(device, "compact");
59319
60570
  const handleClick = (e) => {
59320
60571
  e.stopPropagation();
59321
60572
  if (busy) return;
@@ -59326,7 +60577,7 @@ function ExportRowToggle({ addonId, device, exposed, busy, onToggle }) {
59326
60577
  variant: exposed ? "ghost" : "primary",
59327
60578
  disabled: busy,
59328
60579
  onClick: handleClick,
59329
- "aria-label": exposed ? t("uiLibrary.deviceExport.unexposeDevice", "Unexpose {{name}}", { name: device.name }) : t("uiLibrary.deviceExport.exposeDevice", "Expose {{name}}", { name: device.name }),
60580
+ "aria-label": exposed ? t("uiLibrary.deviceExport.unexposeDevice", "Unexpose {{name}}", { name: deviceLabel }) : t("uiLibrary.deviceExport.exposeDevice", "Expose {{name}}", { name: deviceLabel }),
59330
60581
  title: exposed ? t("uiLibrary.deviceExport.stopTitle", "Stop exposing to \"{{addon}}\"", { addon: addonId }) : t("uiLibrary.deviceExport.exposeTitle", "Expose to \"{{addon}}\"", { addon: addonId }),
59331
60582
  children: exposed ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Unlink2, { className: "mr-1 h-3.5 w-3.5" }), t("uiLibrary.deviceExport.exposed", "Exposed")] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Share2, { className: "mr-1 h-3.5 w-3.5" }), t("uiLibrary.deviceExport.expose", "Expose")] })
59332
60583
  });
@@ -59542,12 +60793,10 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
59542
60793
  key: "select",
59543
60794
  header: "",
59544
60795
  headerClassName: "w-8",
59545
- cell: (device) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
59546
- type: "checkbox",
60796
+ cell: (device) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExportSelectCell, {
60797
+ device,
59547
60798
  checked: selectedIds.has(String(device.id)),
59548
- onChange: () => toggleSelection(String(device.id)),
59549
- onClick: (e) => e.stopPropagation(),
59550
- "aria-label": t("uiLibrary.deviceExport.select", "Select {{name}}", { name: device.name })
60799
+ onToggle: () => toggleSelection(String(device.id))
59551
60800
  })
59552
60801
  },
59553
60802
  {
@@ -59566,9 +60815,10 @@ function DeviceExportPanel({ addonId, onOpenDevice, showSetup = true }) {
59566
60815
  {
59567
60816
  key: "name",
59568
60817
  header: t("uiLibrary.deviceExport.colName", "Name"),
59569
- cell: (device) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
59570
- className: "text-xs font-medium text-foreground",
59571
- children: device.name
60818
+ cell: (device) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceName, {
60819
+ device,
60820
+ form: "compact",
60821
+ className: "text-xs font-medium text-foreground"
59572
60822
  })
59573
60823
  },
59574
60824
  {
@@ -66381,6 +67631,26 @@ function deviceGroupTitle(group, t = defaultUiText) {
66381
67631
  function isDeviceNoun(noun) {
66382
67632
  return noun === "camera" || noun === "device";
66383
67633
  }
67634
+ /** Compact label for a device id. String ids (nodes, packages) keep the stored text. */
67635
+ function usePickerDeviceLabel(id, stored) {
67636
+ const lookedUp = useDeviceLabelById(typeof id === "number" ? id : 0, "compact", stored);
67637
+ return typeof id === "number" ? lookedUp : stored;
67638
+ }
67639
+ function PickerDeviceName({ id, stored, className }) {
67640
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
67641
+ className,
67642
+ children: usePickerDeviceLabel(id, stored)
67643
+ });
67644
+ }
67645
+ function PickerChip({ id, stored, removed, onRemove }) {
67646
+ const label = usePickerDeviceLabel(id, stored);
67647
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectedChip, {
67648
+ testId: `picker-chip-${String(id)}`,
67649
+ label,
67650
+ warning: removed,
67651
+ ...onRemove !== void 0 ? { onRemove } : {}
67652
+ });
67653
+ }
66384
67654
  function chipsOf(value, devices, fleet, noun, t) {
66385
67655
  const byId = new Map(devices.map((device) => [device.id, device.name]));
66386
67656
  const orphans = new Map(offListSelections(value, devices, fleet.devices, fleet.state).map((row) => [row.id, row]));
@@ -66416,7 +67686,7 @@ function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, s
66416
67686
  const t = useUiText();
66417
67687
  const groupTitle = groupTitleProp ?? ((group) => deviceGroupTitle(group, t));
66418
67688
  const lockedNote = lockedNoteProp ?? t("uiLibrary.devicePicker.alreadyAdded", "Already added");
66419
- const devices = (0, react$1.useMemo)(() => withResolvedParentDetail(devicesIn), [devicesIn]);
67689
+ const devices = (0, react$1.useMemo)(() => isDeviceNoun(noun) ? devicesIn : withResolvedParentDetail(devicesIn), [devicesIn, noun]);
66420
67690
  const judge = fleet ?? {
66421
67691
  state: listState,
66422
67692
  devices: listState === "answered" ? devices : void 0
@@ -66451,10 +67721,10 @@ function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, s
66451
67721
  chips.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
66452
67722
  className: "text-[11px] text-foreground-subtle",
66453
67723
  children: emptyLabel ?? (emptyMeansAll ? t("uiLibrary.devicePicker.everyNoRestriction", "Every {{noun}} — no restriction", { noun: pickerNounLabel(noun, t) }) : t("uiLibrary.devicePicker.noneSelected", "None selected"))
66454
- }) : overflow.shown.map((chip) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectedChip, {
66455
- testId: `picker-chip-${String(chip.id)}`,
66456
- label: chip.label,
66457
- warning: chip.removed,
67724
+ }) : overflow.shown.map((chip) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PickerChip, {
67725
+ id: chip.id,
67726
+ stored: chip.label,
67727
+ removed: chip.removed,
66458
67728
  ...!disabled ? { onRemove: () => onChange(value.filter((id) => id !== chip.id)) } : {}
66459
67729
  }, chip.id)),
66460
67730
  overflow.hidden > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -66747,9 +68017,10 @@ function PickerRowView({ row, value, onChange, onToggleGroup, annotate, groupTit
66747
68017
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
66748
68018
  className: "flex min-w-0 flex-1 flex-col",
66749
68019
  children: [
66750
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
66751
- className: "truncate",
66752
- children: row.device.name
68020
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PickerDeviceName, {
68021
+ id: row.device.id,
68022
+ stored: row.device.name,
68023
+ className: "truncate"
66753
68024
  }),
66754
68025
  second !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
66755
68026
  className: "truncate text-[10px] text-foreground-subtle",
@@ -69840,10 +71111,14 @@ exports.DEVICE_COLUMNS = DEVICE_COLUMNS;
69840
71111
  exports.DEVICE_EXPORT_PAGE_SIZE_KEY = DEVICE_EXPORT_PAGE_SIZE_KEY;
69841
71112
  exports.DEVICE_LIST_PAGE_SIZE_KEY = DEVICE_LIST_PAGE_SIZE_KEY;
69842
71113
  exports.DEVICE_LIST_PAGE_SIZE_OPTIONS = DEVICE_LIST_PAGE_SIZE_OPTIONS;
71114
+ exports.DEVICE_NAME_FORMS = DEVICE_NAME_FORMS;
71115
+ exports.DEVICE_NAME_JOIN = DEVICE_NAME_JOIN;
69843
71116
  exports.DEVICE_PICKER_GROUP_STORAGE_KEY = DEVICE_PICKER_GROUP_STORAGE_KEY;
69844
71117
  exports.DEVICE_ROLE_META = DEVICE_ROLE_META;
71118
+ exports.DEVICE_ROLE_NOUNS = DEVICE_ROLE_NOUNS;
69845
71119
  exports.DEVICE_TYPE_CONTROL = DEVICE_TYPE_CONTROL;
69846
71120
  exports.DEVICE_TYPE_META = DEVICE_TYPE_META;
71121
+ exports.DEVICE_TYPE_NOUNS = DEVICE_TYPE_NOUNS;
69847
71122
  exports.DataRecordsHub = DataRecordsHub;
69848
71123
  exports.DataTable = DataTable;
69849
71124
  exports.DataTableFilterBar = DataTableFilterBar;
@@ -69860,11 +71135,14 @@ exports.DeviceContextProvider = DeviceContextProvider;
69860
71135
  exports.DeviceExportPanel = DeviceExportPanel;
69861
71136
  exports.DeviceExportSetup = DeviceExportSetup;
69862
71137
  exports.DeviceFilterBar = DeviceFilterBar;
71138
+ exports.DeviceFleetFromServer = DeviceFleetFromServer;
71139
+ exports.DeviceFleetProvider = DeviceFleetProvider;
69863
71140
  exports.DeviceGrid = DeviceGrid;
69864
71141
  exports.DeviceHistoryView = DeviceHistoryView;
69865
71142
  exports.DeviceItem = DeviceItem;
69866
71143
  exports.DeviceList = DeviceList;
69867
71144
  exports.DeviceMultiSelectField = DeviceMultiSelectField;
71145
+ exports.DeviceName = DeviceName;
69868
71146
  exports.DevicePicker = DevicePicker;
69869
71147
  exports.DeviceQuickDetailProvider = DeviceQuickDetailProvider;
69870
71148
  exports.DeviceSelectField = DeviceSelectField;
@@ -70293,6 +71571,7 @@ exports.ViewSwitch = ViewSwitch;
70293
71571
  exports.VodPlaybackProvider = VodPlaybackProvider;
70294
71572
  exports.Volume2 = Volume2;
70295
71573
  exports.VolumeX = VolumeX;
71574
+ exports.WashingMachine = WashingMachine;
70296
71575
  exports.WaterHeaterHeroCard = WaterHeaterHeroCard;
70297
71576
  exports.WaterHeaterInlineControl = WaterHeaterInlineControl;
70298
71577
  exports.Waves = Waves;
@@ -70395,6 +71674,7 @@ exports.describeDeviceSelector = describeDeviceSelector;
70395
71674
  exports.deviceIntegrationResolver = deviceIntegrationResolver;
70396
71675
  exports.deviceListStateOf = deviceListStateOf;
70397
71676
  exports.deviceMatchesFilter = deviceMatchesFilter;
71677
+ exports.deviceNameNouns = deviceNameNouns;
70398
71678
  exports.deviceOptionLabel = deviceOptionLabel;
70399
71679
  exports.deviceRoleMeta = deviceRoleMeta;
70400
71680
  exports.deviceRoleMetaOf = deviceRoleMetaOf;
@@ -70422,10 +71702,12 @@ exports.findTimezone = _camstack_types.findTimezone;
70422
71702
  exports.flattenPickerGrouping = flattenPickerGrouping;
70423
71703
  exports.flattenPickerRows = flattenPickerRows;
70424
71704
  exports.fleetViewOf = fleetViewOf;
71705
+ exports.foldDeviceName = foldDeviceName;
70425
71706
  exports.formatChartBytes = formatChartBytes;
70426
71707
  exports.formatChartNumber = formatChartNumber;
70427
71708
  exports.formatChartValue = formatChartValue;
70428
71709
  exports.formatControlDateTime = formatControlDateTime;
71710
+ exports.formatDeviceName = formatDeviceName;
70429
71711
  exports.formatLastSeen = formatLastSeen;
70430
71712
  exports.formatNumeric = formatNumeric;
70431
71713
  exports.formatSettingValue = formatSettingValue;
@@ -70453,6 +71735,7 @@ exports.humanizeName = humanizeName;
70453
71735
  exports.humidifierTint = humidifierTint;
70454
71736
  exports.iconGlyphsFailed = iconGlyphsFailed;
70455
71737
  exports.iconGlyphsLoaded = iconGlyphsLoaded;
71738
+ exports.indexDeviceNames = indexDeviceNames;
70456
71739
  exports.initialScrubState = initialScrubState;
70457
71740
  exports.insertAtSelection = insertAtSelection;
70458
71741
  exports.isAbsentProvider = isAbsentProvider;
@@ -70460,6 +71743,7 @@ exports.isAbsentProvider$1 = isAbsentProvider;
70460
71743
  exports.isByteUnit = isByteUnit;
70461
71744
  exports.isConsumableLevelField = isConsumableLevelField;
70462
71745
  exports.isDataTableFilterActive = isDataTableFilterActive;
71746
+ exports.isDeviceNameStock = isDeviceNameStock;
70463
71747
  Object.defineProperty(exports, "isDrawableSubjectPath", {
70464
71748
  enumerable: true,
70465
71749
  get: function() {
@@ -70501,6 +71785,7 @@ exports.nodeTextColorClass = nodeTextColorClass;
70501
71785
  exports.normalizeForSearch = normalizeForSearch;
70502
71786
  exports.normalizeLanguage = normalizeLanguage;
70503
71787
  exports.normalizeSelection = normalizeSelection;
71788
+ exports.nounSpecFor = nounSpecFor;
70504
71789
  exports.occupancyFacets = occupancyFacets;
70505
71790
  exports.occupancyLines = occupancyLines;
70506
71791
  exports.offListSelections = offListSelections;
@@ -70555,6 +71840,7 @@ exports.resolveContainerPrimary = resolveContainerPrimary;
70555
71840
  exports.resolveControlAlign = resolveControlAlign;
70556
71841
  exports.resolveDeviceControl = resolveDeviceControl;
70557
71842
  exports.resolveDeviceIntegration = resolveDeviceIntegration;
71843
+ exports.resolveDeviceLabel = resolveDeviceLabel;
70558
71844
  exports.resolveDisplayIcon = resolveDisplayIcon;
70559
71845
  exports.resolveEffectiveDefaultModel = resolveEffectiveDefaultModel;
70560
71846
  exports.resolveEffectiveStepModel = resolveEffectiveStepModel;
@@ -70573,6 +71859,7 @@ exports.rootSourceState = rootSourceState;
70573
71859
  exports.routerBasename = routerBasename;
70574
71860
  exports.runningDurationSince = runningDurationSince;
70575
71861
  exports.runningStatusParts = runningStatusParts;
71862
+ exports.sameDeviceName = sameDeviceName;
70576
71863
  exports.sameLocationSuggestions = sameLocationSuggestions;
70577
71864
  exports.scrubReducer = scrubReducer;
70578
71865
  exports.searchSteps = searchSteps;
@@ -70606,6 +71893,7 @@ exports.stepFieldDomId = stepFieldDomId;
70606
71893
  exports.stepHasModelForFormat = stepHasModelForFormat;
70607
71894
  exports.stepLocalFields = stepLocalFields;
70608
71895
  exports.stepModelOptions = stepModelOptions;
71896
+ exports.stripDeviceParentPrefix = stripDeviceParentPrefix;
70609
71897
  exports.stripParentNamePrefix = stripParentNamePrefix;
70610
71898
  Object.defineProperty(exports, "subjectPathSampleFromBox", {
70611
71899
  enumerable: true,
@@ -70903,6 +72191,8 @@ exports.useDeviceHistoryRecord = useDeviceHistoryRecord;
70903
72191
  exports.useDeviceHistoryRecordBuckets = useDeviceHistoryRecordBuckets;
70904
72192
  exports.useDeviceHistorySetDeviceEnabled = useDeviceHistorySetDeviceEnabled;
70905
72193
  exports.useDeviceId = useDeviceId;
72194
+ exports.useDeviceLabel = useDeviceLabel;
72195
+ exports.useDeviceLabelById = useDeviceLabelById;
70906
72196
  exports.useDeviceListPageSize = useDeviceListPageSize;
70907
72197
  exports.useDeviceManagerAddLocation = useDeviceManagerAddLocation;
70908
72198
  exports.useDeviceManagerAdoptDevice = useDeviceManagerAdoptDevice;
@@ -70996,6 +72286,8 @@ exports.useDeviceManagerTestField = useDeviceManagerTestField;
70996
72286
  exports.useDeviceManagerUpdateConfig = useDeviceManagerUpdateConfig;
70997
72287
  exports.useDeviceManagerUpdateDeviceField = useDeviceManagerUpdateDeviceField;
70998
72288
  exports.useDeviceManagerUpdateDeviceFieldsBatch = useDeviceManagerUpdateDeviceFieldsBatch;
72289
+ exports.useDeviceNameIndex = useDeviceNameIndex;
72290
+ exports.useDeviceNameNouns = useDeviceNameNouns;
70999
72291
  exports.useDeviceNetworkLink = useDeviceNetworkLink;
71000
72292
  exports.useDeviceOpsGetConfigEntries = useDeviceOpsGetConfigEntries;
71001
72293
  exports.useDeviceOpsGetRawState = useDeviceOpsGetRawState;