@camstack/ui-library 1.2.194 → 1.2.196

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
@@ -375,6 +375,30 @@ var Bath = createLucideIcon("bath", [
375
375
  * This source code is licensed under the ISC license.
376
376
  * See the LICENSE file in the root directory of this source tree.
377
377
  */
378
+ var BatteryCharging = createLucideIcon("battery-charging", [
379
+ ["path", {
380
+ d: "m11 7-3 5h4l-3 5",
381
+ key: "b4a64w"
382
+ }],
383
+ ["path", {
384
+ d: "M14.856 6H16a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.935",
385
+ key: "lre1cr"
386
+ }],
387
+ ["path", {
388
+ d: "M22 14v-4",
389
+ key: "14q9d5"
390
+ }],
391
+ ["path", {
392
+ d: "M5.14 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2.936",
393
+ key: "13q5k0"
394
+ }]
395
+ ]);
396
+ /**
397
+ * @license lucide-react v0.576.0 - ISC
398
+ *
399
+ * This source code is licensed under the ISC license.
400
+ * See the LICENSE file in the root directory of this source tree.
401
+ */
378
402
  var BatteryFull = createLucideIcon("battery-full", [
379
403
  ["path", {
380
404
  d: "M10 10v4",
@@ -504,6 +528,78 @@ var Battery = createLucideIcon("battery", [["path", {
504
528
  * This source code is licensed under the ISC license.
505
529
  * See the LICENSE file in the root directory of this source tree.
506
530
  */
531
+ var BedDouble = createLucideIcon("bed-double", [
532
+ ["path", {
533
+ d: "M2 20v-8a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v8",
534
+ key: "1k78r4"
535
+ }],
536
+ ["path", {
537
+ d: "M4 10V6a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v4",
538
+ key: "fb3tl2"
539
+ }],
540
+ ["path", {
541
+ d: "M12 4v6",
542
+ key: "1dcgq2"
543
+ }],
544
+ ["path", {
545
+ d: "M2 18h20",
546
+ key: "ajqnye"
547
+ }]
548
+ ]);
549
+ /**
550
+ * @license lucide-react v0.576.0 - ISC
551
+ *
552
+ * This source code is licensed under the ISC license.
553
+ * See the LICENSE file in the root directory of this source tree.
554
+ */
555
+ var Bed = createLucideIcon("bed", [
556
+ ["path", {
557
+ d: "M2 4v16",
558
+ key: "vw9hq8"
559
+ }],
560
+ ["path", {
561
+ d: "M2 8h18a2 2 0 0 1 2 2v10",
562
+ key: "1dgv2r"
563
+ }],
564
+ ["path", {
565
+ d: "M2 17h20",
566
+ key: "18nfp3"
567
+ }],
568
+ ["path", {
569
+ d: "M6 8v9",
570
+ key: "1yriud"
571
+ }]
572
+ ]);
573
+ /**
574
+ * @license lucide-react v0.576.0 - ISC
575
+ *
576
+ * This source code is licensed under the ISC license.
577
+ * See the LICENSE file in the root directory of this source tree.
578
+ */
579
+ var BellOff = createLucideIcon("bell-off", [
580
+ ["path", {
581
+ d: "M10.268 21a2 2 0 0 0 3.464 0",
582
+ key: "vwvbt9"
583
+ }],
584
+ ["path", {
585
+ d: "M17 17H4a1 1 0 0 1-.74-1.673C4.59 13.956 6 12.499 6 8a6 6 0 0 1 .258-1.742",
586
+ key: "178tsu"
587
+ }],
588
+ ["path", {
589
+ d: "m2 2 20 20",
590
+ key: "1ooewy"
591
+ }],
592
+ ["path", {
593
+ d: "M8.668 3.01A6 6 0 0 1 18 8c0 2.687.77 4.653 1.707 6.05",
594
+ key: "1hqiys"
595
+ }]
596
+ ]);
597
+ /**
598
+ * @license lucide-react v0.576.0 - ISC
599
+ *
600
+ * This source code is licensed under the ISC license.
601
+ * See the LICENSE file in the root directory of this source tree.
602
+ */
507
603
  var BellRing = createLucideIcon("bell-ring", [
508
604
  ["path", {
509
605
  d: "M10.268 21a2 2 0 0 0 3.464 0",
@@ -641,6 +737,16 @@ var Blinds = createLucideIcon("blinds", [
641
737
  * This source code is licensed under the ISC license.
642
738
  * See the LICENSE file in the root directory of this source tree.
643
739
  */
740
+ var Bluetooth = createLucideIcon("bluetooth", [["path", {
741
+ d: "m7 7 10 10-5 5V2l5 5L7 17",
742
+ key: "1q5490"
743
+ }]]);
744
+ /**
745
+ * @license lucide-react v0.576.0 - ISC
746
+ *
747
+ * This source code is licensed under the ISC license.
748
+ * See the LICENSE file in the root directory of this source tree.
749
+ */
644
750
  var Bone = createLucideIcon("bone", [["path", {
645
751
  d: "M17 10c.7-.7 1.69 0 2.5 0a2.5 2.5 0 1 0 0-5 .5.5 0 0 1-.5-.5 2.5 2.5 0 1 0-5 0c0 .81.7 1.8 0 2.5l-7 7c-.7.7-1.69 0-2.5 0a2.5 2.5 0 0 0 0 5c.28 0 .5.22.5.5a2.5 2.5 0 1 0 5 0c0-.81-.7-1.8 0-2.5Z",
646
752
  key: "w610uw"
@@ -707,6 +813,23 @@ var Box = createLucideIcon("box", [
707
813
  * This source code is licensed under the ISC license.
708
814
  * See the LICENSE file in the root directory of this source tree.
709
815
  */
816
+ var Briefcase = createLucideIcon("briefcase", [["path", {
817
+ d: "M16 20V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16",
818
+ key: "jecpp"
819
+ }], ["rect", {
820
+ width: "20",
821
+ height: "14",
822
+ x: "2",
823
+ y: "6",
824
+ rx: "2",
825
+ key: "i6l2r4"
826
+ }]]);
827
+ /**
828
+ * @license lucide-react v0.576.0 - ISC
829
+ *
830
+ * This source code is licensed under the ISC license.
831
+ * See the LICENSE file in the root directory of this source tree.
832
+ */
710
833
  var Bug = createLucideIcon("bug", [
711
834
  ["path", {
712
835
  d: "M12 20v-9",
@@ -759,6 +882,90 @@ var Bug = createLucideIcon("bug", [
759
882
  * This source code is licensed under the ISC license.
760
883
  * See the LICENSE file in the root directory of this source tree.
761
884
  */
885
+ var Building2 = createLucideIcon("building-2", [
886
+ ["path", {
887
+ d: "M10 12h4",
888
+ key: "a56b0p"
889
+ }],
890
+ ["path", {
891
+ d: "M10 8h4",
892
+ key: "1sr2af"
893
+ }],
894
+ ["path", {
895
+ d: "M14 21v-3a2 2 0 0 0-4 0v3",
896
+ key: "1rgiei"
897
+ }],
898
+ ["path", {
899
+ d: "M6 10H4a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-2",
900
+ key: "secmi2"
901
+ }],
902
+ ["path", {
903
+ d: "M6 21V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v16",
904
+ key: "16ra0t"
905
+ }]
906
+ ]);
907
+ /**
908
+ * @license lucide-react v0.576.0 - ISC
909
+ *
910
+ * This source code is licensed under the ISC license.
911
+ * See the LICENSE file in the root directory of this source tree.
912
+ */
913
+ var Building = createLucideIcon("building", [
914
+ ["path", {
915
+ d: "M12 10h.01",
916
+ key: "1nrarc"
917
+ }],
918
+ ["path", {
919
+ d: "M12 14h.01",
920
+ key: "1etili"
921
+ }],
922
+ ["path", {
923
+ d: "M12 6h.01",
924
+ key: "1vi96p"
925
+ }],
926
+ ["path", {
927
+ d: "M16 10h.01",
928
+ key: "1m94wz"
929
+ }],
930
+ ["path", {
931
+ d: "M16 14h.01",
932
+ key: "1gbofw"
933
+ }],
934
+ ["path", {
935
+ d: "M16 6h.01",
936
+ key: "1x0f13"
937
+ }],
938
+ ["path", {
939
+ d: "M8 10h.01",
940
+ key: "19clt8"
941
+ }],
942
+ ["path", {
943
+ d: "M8 14h.01",
944
+ key: "6423bh"
945
+ }],
946
+ ["path", {
947
+ d: "M8 6h.01",
948
+ key: "1dz90k"
949
+ }],
950
+ ["path", {
951
+ d: "M9 22v-3a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3",
952
+ key: "cabbwy"
953
+ }],
954
+ ["rect", {
955
+ x: "4",
956
+ y: "2",
957
+ width: "16",
958
+ height: "20",
959
+ rx: "2",
960
+ key: "1uxh74"
961
+ }]
962
+ ]);
963
+ /**
964
+ * @license lucide-react v0.576.0 - ISC
965
+ *
966
+ * This source code is licensed under the ISC license.
967
+ * See the LICENSE file in the root directory of this source tree.
968
+ */
762
969
  var Bus = createLucideIcon("bus", [
763
970
  ["path", {
764
971
  d: "M8 6v6",
@@ -835,6 +1042,34 @@ var Cable = createLucideIcon("cable", [
835
1042
  * This source code is licensed under the ISC license.
836
1043
  * See the LICENSE file in the root directory of this source tree.
837
1044
  */
1045
+ var Calendar = createLucideIcon("calendar", [
1046
+ ["path", {
1047
+ d: "M8 2v4",
1048
+ key: "1cmpym"
1049
+ }],
1050
+ ["path", {
1051
+ d: "M16 2v4",
1052
+ key: "4m81vk"
1053
+ }],
1054
+ ["rect", {
1055
+ width: "18",
1056
+ height: "18",
1057
+ x: "3",
1058
+ y: "4",
1059
+ rx: "2",
1060
+ key: "1hopcy"
1061
+ }],
1062
+ ["path", {
1063
+ d: "M3 10h18",
1064
+ key: "8toen8"
1065
+ }]
1066
+ ]);
1067
+ /**
1068
+ * @license lucide-react v0.576.0 - ISC
1069
+ *
1070
+ * This source code is licensed under the ISC license.
1071
+ * See the LICENSE file in the root directory of this source tree.
1072
+ */
838
1073
  var Camera = createLucideIcon("camera", [["path", {
839
1074
  d: "M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z",
840
1075
  key: "18u6gg"
@@ -1078,6 +1313,21 @@ var CircleDot = createLucideIcon("circle-dot", [["circle", {
1078
1313
  * This source code is licensed under the ISC license.
1079
1314
  * See the LICENSE file in the root directory of this source tree.
1080
1315
  */
1316
+ var CircleParking = createLucideIcon("circle-parking", [["circle", {
1317
+ cx: "12",
1318
+ cy: "12",
1319
+ r: "10",
1320
+ key: "1mglay"
1321
+ }], ["path", {
1322
+ d: "M9 17V7h4a3 3 0 0 1 0 6H9",
1323
+ key: "1dfk2c"
1324
+ }]]);
1325
+ /**
1326
+ * @license lucide-react v0.576.0 - ISC
1327
+ *
1328
+ * This source code is licensed under the ISC license.
1329
+ * See the LICENSE file in the root directory of this source tree.
1330
+ */
1081
1331
  var CirclePause = createLucideIcon("circle-pause", [
1082
1332
  ["circle", {
1083
1333
  cx: "12",
@@ -1433,6 +1683,30 @@ var Cog = createLucideIcon("cog", [
1433
1683
  * This source code is licensed under the ISC license.
1434
1684
  * See the LICENSE file in the root directory of this source tree.
1435
1685
  */
1686
+ var CookingPot = createLucideIcon("cooking-pot", [
1687
+ ["path", {
1688
+ d: "M2 12h20",
1689
+ key: "9i4pu4"
1690
+ }],
1691
+ ["path", {
1692
+ d: "M20 12v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-8",
1693
+ key: "u0tga0"
1694
+ }],
1695
+ ["path", {
1696
+ d: "m4 8 16-4",
1697
+ key: "16g0ng"
1698
+ }],
1699
+ ["path", {
1700
+ d: "m8.86 6.78-.45-1.81a2 2 0 0 1 1.45-2.43l1.94-.48a2 2 0 0 1 2.43 1.46l.45 1.8",
1701
+ key: "12cejc"
1702
+ }]
1703
+ ]);
1704
+ /**
1705
+ * @license lucide-react v0.576.0 - ISC
1706
+ *
1707
+ * This source code is licensed under the ISC license.
1708
+ * See the LICENSE file in the root directory of this source tree.
1709
+ */
1436
1710
  var CopyPlus = createLucideIcon("copy-plus", [
1437
1711
  ["line", {
1438
1712
  x1: "15",
@@ -1719,6 +1993,38 @@ var Download = createLucideIcon("download", [
1719
1993
  * This source code is licensed under the ISC license.
1720
1994
  * See the LICENSE file in the root directory of this source tree.
1721
1995
  */
1996
+ var Drill = createLucideIcon("drill", [
1997
+ ["path", {
1998
+ d: "M10 18a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H5a3 3 0 0 1-3-3 1 1 0 0 1 1-1z",
1999
+ key: "ioqxb1"
2000
+ }],
2001
+ ["path", {
2002
+ d: "M13 10H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1l-.81 3.242a1 1 0 0 1-.97.758H8",
2003
+ key: "1rs59n"
2004
+ }],
2005
+ ["path", {
2006
+ d: "M14 4h3a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-3",
2007
+ key: "105ega"
2008
+ }],
2009
+ ["path", {
2010
+ d: "M18 6h4",
2011
+ key: "66u95g"
2012
+ }],
2013
+ ["path", {
2014
+ d: "m5 10-2 8",
2015
+ key: "xt2lic"
2016
+ }],
2017
+ ["path", {
2018
+ d: "m7 18 2-8",
2019
+ key: "1bzku2"
2020
+ }]
2021
+ ]);
2022
+ /**
2023
+ * @license lucide-react v0.576.0 - ISC
2024
+ *
2025
+ * This source code is licensed under the ISC license.
2026
+ * See the LICENSE file in the root directory of this source tree.
2027
+ */
1722
2028
  var Droplet = createLucideIcon("droplet", [["path", {
1723
2029
  d: "M12 22a7 7 0 0 0 7-7c0-2-1-3.9-3-5.5s-3.5-4-4-6.5c-.5 2.5-2 4.9-4 6.5C6 11.1 5 13 5 15a7 7 0 0 0 7 7z",
1724
2030
  key: "c7niix"
@@ -1742,6 +2048,34 @@ var Droplets = createLucideIcon("droplets", [["path", {
1742
2048
  * This source code is licensed under the ISC license.
1743
2049
  * See the LICENSE file in the root directory of this source tree.
1744
2050
  */
2051
+ var Dumbbell = createLucideIcon("dumbbell", [
2052
+ ["path", {
2053
+ d: "M17.596 12.768a2 2 0 1 0 2.829-2.829l-1.768-1.767a2 2 0 0 0 2.828-2.829l-2.828-2.828a2 2 0 0 0-2.829 2.828l-1.767-1.768a2 2 0 1 0-2.829 2.829z",
2054
+ key: "9m4mmf"
2055
+ }],
2056
+ ["path", {
2057
+ d: "m2.5 21.5 1.4-1.4",
2058
+ key: "17g3f0"
2059
+ }],
2060
+ ["path", {
2061
+ d: "m20.1 3.9 1.4-1.4",
2062
+ key: "1qn309"
2063
+ }],
2064
+ ["path", {
2065
+ d: "M5.343 21.485a2 2 0 1 0 2.829-2.828l1.767 1.768a2 2 0 1 0 2.829-2.829l-6.364-6.364a2 2 0 1 0-2.829 2.829l1.768 1.767a2 2 0 0 0-2.828 2.829z",
2066
+ key: "1t2c92"
2067
+ }],
2068
+ ["path", {
2069
+ d: "m9.6 14.4 4.8-4.8",
2070
+ key: "6umqxw"
2071
+ }]
2072
+ ]);
2073
+ /**
2074
+ * @license lucide-react v0.576.0 - ISC
2075
+ *
2076
+ * This source code is licensed under the ISC license.
2077
+ * See the LICENSE file in the root directory of this source tree.
2078
+ */
1745
2079
  var EllipsisVertical = createLucideIcon("ellipsis-vertical", [
1746
2080
  ["circle", {
1747
2081
  cx: "12",
@@ -1860,6 +2194,42 @@ var Feather = createLucideIcon("feather", [
1860
2194
  * This source code is licensed under the ISC license.
1861
2195
  * See the LICENSE file in the root directory of this source tree.
1862
2196
  */
2197
+ var Fence = createLucideIcon("fence", [
2198
+ ["path", {
2199
+ d: "M4 3 2 5v15c0 .6.4 1 1 1h2c.6 0 1-.4 1-1V5Z",
2200
+ key: "1n2rgs"
2201
+ }],
2202
+ ["path", {
2203
+ d: "M6 8h4",
2204
+ key: "utf9t1"
2205
+ }],
2206
+ ["path", {
2207
+ d: "M6 18h4",
2208
+ key: "12yh4b"
2209
+ }],
2210
+ ["path", {
2211
+ d: "m12 3-2 2v15c0 .6.4 1 1 1h2c.6 0 1-.4 1-1V5Z",
2212
+ key: "3ha7mj"
2213
+ }],
2214
+ ["path", {
2215
+ d: "M14 8h4",
2216
+ key: "1r8wg2"
2217
+ }],
2218
+ ["path", {
2219
+ d: "M14 18h4",
2220
+ key: "1t3kbu"
2221
+ }],
2222
+ ["path", {
2223
+ d: "m20 3-2 2v15c0 .6.4 1 1 1h2c.6 0 1-.4 1-1V5Z",
2224
+ key: "dfd4e2"
2225
+ }]
2226
+ ]);
2227
+ /**
2228
+ * @license lucide-react v0.576.0 - ISC
2229
+ *
2230
+ * This source code is licensed under the ISC license.
2231
+ * See the LICENSE file in the root directory of this source tree.
2232
+ */
1863
2233
  var Film = createLucideIcon("film", [
1864
2234
  ["rect", {
1865
2235
  width: "18",
@@ -1982,6 +2352,56 @@ var FlaskConical = createLucideIcon("flask-conical", [
1982
2352
  * This source code is licensed under the ISC license.
1983
2353
  * See the LICENSE file in the root directory of this source tree.
1984
2354
  */
2355
+ var Flower = createLucideIcon("flower", [
2356
+ ["circle", {
2357
+ cx: "12",
2358
+ cy: "12",
2359
+ r: "3",
2360
+ key: "1v7zrd"
2361
+ }],
2362
+ ["path", {
2363
+ d: "M12 16.5A4.5 4.5 0 1 1 7.5 12 4.5 4.5 0 1 1 12 7.5a4.5 4.5 0 1 1 4.5 4.5 4.5 4.5 0 1 1-4.5 4.5",
2364
+ key: "14wa3c"
2365
+ }],
2366
+ ["path", {
2367
+ d: "M12 7.5V9",
2368
+ key: "1oy5b0"
2369
+ }],
2370
+ ["path", {
2371
+ d: "M7.5 12H9",
2372
+ key: "eltsq1"
2373
+ }],
2374
+ ["path", {
2375
+ d: "M16.5 12H15",
2376
+ key: "vk5kw4"
2377
+ }],
2378
+ ["path", {
2379
+ d: "M12 16.5V15",
2380
+ key: "k7eayi"
2381
+ }],
2382
+ ["path", {
2383
+ d: "m8 8 1.88 1.88",
2384
+ key: "nxy4qf"
2385
+ }],
2386
+ ["path", {
2387
+ d: "M14.12 9.88 16 8",
2388
+ key: "1lst6k"
2389
+ }],
2390
+ ["path", {
2391
+ d: "m8 16 1.88-1.88",
2392
+ key: "h2eex1"
2393
+ }],
2394
+ ["path", {
2395
+ d: "M14.12 14.12 16 16",
2396
+ key: "uqkrx3"
2397
+ }]
2398
+ ]);
2399
+ /**
2400
+ * @license lucide-react v0.576.0 - ISC
2401
+ *
2402
+ * This source code is licensed under the ISC license.
2403
+ * See the LICENSE file in the root directory of this source tree.
2404
+ */
1985
2405
  var Footprints = createLucideIcon("footprints", [
1986
2406
  ["path", {
1987
2407
  d: "M4 16v-2.38C4 11.5 2.97 10.5 3 8c.03-2.72 1.49-6 4.5-6C9.37 2 10 3.8 10 5.5c0 3.11-2 5.66-2 8.68V16a2 2 0 1 1-4 0Z",
@@ -2241,6 +2661,77 @@ var Hash = createLucideIcon("hash", [
2241
2661
  * This source code is licensed under the ISC license.
2242
2662
  * See the LICENSE file in the root directory of this source tree.
2243
2663
  */
2664
+ var HeartPulse = createLucideIcon("heart-pulse", [["path", {
2665
+ d: "M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5",
2666
+ key: "mvr1a0"
2667
+ }], ["path", {
2668
+ d: "M3.22 13H9.5l.5-1 2 4.5 2-7 1.5 3.5h5.27",
2669
+ key: "auskq0"
2670
+ }]]);
2671
+ /**
2672
+ * @license lucide-react v0.576.0 - ISC
2673
+ *
2674
+ * This source code is licensed under the ISC license.
2675
+ * See the LICENSE file in the root directory of this source tree.
2676
+ */
2677
+ var Heart = createLucideIcon("heart", [["path", {
2678
+ d: "M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5",
2679
+ key: "mvr1a0"
2680
+ }]]);
2681
+ /**
2682
+ * @license lucide-react v0.576.0 - ISC
2683
+ *
2684
+ * This source code is licensed under the ISC license.
2685
+ * See the LICENSE file in the root directory of this source tree.
2686
+ */
2687
+ var Heater = createLucideIcon("heater", [
2688
+ ["path", {
2689
+ d: "M11 8c2-3-2-3 0-6",
2690
+ key: "1ldv5m"
2691
+ }],
2692
+ ["path", {
2693
+ d: "M15.5 8c2-3-2-3 0-6",
2694
+ key: "1otqoz"
2695
+ }],
2696
+ ["path", {
2697
+ d: "M6 10h.01",
2698
+ key: "1lbq93"
2699
+ }],
2700
+ ["path", {
2701
+ d: "M6 14h.01",
2702
+ key: "zudwn7"
2703
+ }],
2704
+ ["path", {
2705
+ d: "M10 16v-4",
2706
+ key: "1c25yv"
2707
+ }],
2708
+ ["path", {
2709
+ d: "M14 16v-4",
2710
+ key: "1dkbt8"
2711
+ }],
2712
+ ["path", {
2713
+ d: "M18 16v-4",
2714
+ key: "1yg9me"
2715
+ }],
2716
+ ["path", {
2717
+ d: "M20 6a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h3",
2718
+ key: "1ubg90"
2719
+ }],
2720
+ ["path", {
2721
+ d: "M5 20v2",
2722
+ key: "1abpe8"
2723
+ }],
2724
+ ["path", {
2725
+ d: "M19 20v2",
2726
+ key: "kqn6ft"
2727
+ }]
2728
+ ]);
2729
+ /**
2730
+ * @license lucide-react v0.576.0 - ISC
2731
+ *
2732
+ * This source code is licensed under the ISC license.
2733
+ * See the LICENSE file in the root directory of this source tree.
2734
+ */
2244
2735
  var House = createLucideIcon("house", [["path", {
2245
2736
  d: "M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8",
2246
2737
  key: "5wwlr5"
@@ -2357,6 +2848,141 @@ var Info = createLucideIcon("info", [
2357
2848
  * This source code is licensed under the ISC license.
2358
2849
  * See the LICENSE file in the root directory of this source tree.
2359
2850
  */
2851
+ var KeyRound = createLucideIcon("key-round", [["path", {
2852
+ d: "M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z",
2853
+ key: "1s6t7t"
2854
+ }], ["circle", {
2855
+ cx: "16.5",
2856
+ cy: "7.5",
2857
+ r: ".5",
2858
+ fill: "currentColor",
2859
+ key: "w0ekpg"
2860
+ }]]);
2861
+ /**
2862
+ * @license lucide-react v0.576.0 - ISC
2863
+ *
2864
+ * This source code is licensed under the ISC license.
2865
+ * See the LICENSE file in the root directory of this source tree.
2866
+ */
2867
+ var Key = createLucideIcon("key", [
2868
+ ["path", {
2869
+ d: "m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4",
2870
+ key: "g0fldk"
2871
+ }],
2872
+ ["path", {
2873
+ d: "m21 2-9.6 9.6",
2874
+ key: "1j0ho8"
2875
+ }],
2876
+ ["circle", {
2877
+ cx: "7.5",
2878
+ cy: "15.5",
2879
+ r: "5.5",
2880
+ key: "yqb3hr"
2881
+ }]
2882
+ ]);
2883
+ /**
2884
+ * @license lucide-react v0.576.0 - ISC
2885
+ *
2886
+ * This source code is licensed under the ISC license.
2887
+ * See the LICENSE file in the root directory of this source tree.
2888
+ */
2889
+ var LampCeiling = createLucideIcon("lamp-ceiling", [
2890
+ ["path", {
2891
+ d: "M12 2v5",
2892
+ key: "nd4vlx"
2893
+ }],
2894
+ ["path", {
2895
+ d: "M14.829 15.998a3 3 0 1 1-5.658 0",
2896
+ key: "1pybiy"
2897
+ }],
2898
+ ["path", {
2899
+ d: "M20.92 14.606A1 1 0 0 1 20 16H4a1 1 0 0 1-.92-1.394l3-7A1 1 0 0 1 7 7h10a1 1 0 0 1 .92.606z",
2900
+ key: "ma1wor"
2901
+ }]
2902
+ ]);
2903
+ /**
2904
+ * @license lucide-react v0.576.0 - ISC
2905
+ *
2906
+ * This source code is licensed under the ISC license.
2907
+ * See the LICENSE file in the root directory of this source tree.
2908
+ */
2909
+ var LampDesk = createLucideIcon("lamp-desk", [
2910
+ ["path", {
2911
+ d: "M10.293 2.293a1 1 0 0 1 1.414 0l2.5 2.5 5.994 1.227a1 1 0 0 1 .506 1.687l-7 7a1 1 0 0 1-1.687-.506l-1.227-5.994-2.5-2.5a1 1 0 0 1 0-1.414z",
2912
+ key: "sb8slu"
2913
+ }],
2914
+ ["path", {
2915
+ d: "m14.207 4.793-3.414 3.414",
2916
+ key: "m2x3oj"
2917
+ }],
2918
+ ["path", {
2919
+ d: "M3 20a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1z",
2920
+ key: "8b3myj"
2921
+ }],
2922
+ ["path", {
2923
+ d: "m9.086 6.5-4.793 4.793a1 1 0 0 0-.18 1.17L7 18",
2924
+ key: "43s6cu"
2925
+ }]
2926
+ ]);
2927
+ /**
2928
+ * @license lucide-react v0.576.0 - ISC
2929
+ *
2930
+ * This source code is licensed under the ISC license.
2931
+ * See the LICENSE file in the root directory of this source tree.
2932
+ */
2933
+ var LampFloor = createLucideIcon("lamp-floor", [
2934
+ ["path", {
2935
+ d: "M12 10v12",
2936
+ key: "6ubwww"
2937
+ }],
2938
+ ["path", {
2939
+ d: "M17.929 7.629A1 1 0 0 1 17 9H7a1 1 0 0 1-.928-1.371l2-5A1 1 0 0 1 9 2h6a1 1 0 0 1 .928.629z",
2940
+ key: "1o95gh"
2941
+ }],
2942
+ ["path", {
2943
+ d: "M9 22h6",
2944
+ key: "1rlq3v"
2945
+ }]
2946
+ ]);
2947
+ /**
2948
+ * @license lucide-react v0.576.0 - ISC
2949
+ *
2950
+ * This source code is licensed under the ISC license.
2951
+ * See the LICENSE file in the root directory of this source tree.
2952
+ */
2953
+ var Lamp = createLucideIcon("lamp", [
2954
+ ["path", {
2955
+ d: "M12 12v6",
2956
+ key: "3ahymv"
2957
+ }],
2958
+ ["path", {
2959
+ d: "M4.077 10.615A1 1 0 0 0 5 12h14a1 1 0 0 0 .923-1.385l-3.077-7.384A2 2 0 0 0 15 2H9a2 2 0 0 0-1.846 1.23Z",
2960
+ key: "1l7kg2"
2961
+ }],
2962
+ ["path", {
2963
+ d: "M8 20a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1z",
2964
+ key: "1mmzpi"
2965
+ }]
2966
+ ]);
2967
+ /**
2968
+ * @license lucide-react v0.576.0 - ISC
2969
+ *
2970
+ * This source code is licensed under the ISC license.
2971
+ * See the LICENSE file in the root directory of this source tree.
2972
+ */
2973
+ var Laptop = createLucideIcon("laptop", [["path", {
2974
+ d: "M18 5a2 2 0 0 1 2 2v8.526a2 2 0 0 0 .212.897l1.068 2.127a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45l1.068-2.127A2 2 0 0 0 4 15.526V7a2 2 0 0 1 2-2z",
2975
+ key: "1pdavp"
2976
+ }], ["path", {
2977
+ d: "M20.054 15.987H3.946",
2978
+ key: "14rxg9"
2979
+ }]]);
2980
+ /**
2981
+ * @license lucide-react v0.576.0 - ISC
2982
+ *
2983
+ * This source code is licensed under the ISC license.
2984
+ * See the LICENSE file in the root directory of this source tree.
2985
+ */
2360
2986
  var Laugh = createLucideIcon("laugh", [
2361
2987
  ["circle", {
2362
2988
  cx: "12",
@@ -2656,6 +3282,33 @@ var Mail = createLucideIcon("mail", [["path", {
2656
3282
  * This source code is licensed under the ISC license.
2657
3283
  * See the LICENSE file in the root directory of this source tree.
2658
3284
  */
3285
+ var Mailbox = createLucideIcon("mailbox", [
3286
+ ["path", {
3287
+ d: "M22 17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9.5C2 7 4 5 6.5 5H18c2.2 0 4 1.8 4 4v8Z",
3288
+ key: "1lbycx"
3289
+ }],
3290
+ ["polyline", {
3291
+ points: "15,9 18,9 18,11",
3292
+ key: "1pm9c0"
3293
+ }],
3294
+ ["path", {
3295
+ d: "M6.5 5C9 5 11 7 11 9.5V17a2 2 0 0 1-2 2",
3296
+ key: "15i455"
3297
+ }],
3298
+ ["line", {
3299
+ x1: "6",
3300
+ x2: "7",
3301
+ y1: "10",
3302
+ y2: "10",
3303
+ key: "1e2scm"
3304
+ }]
3305
+ ]);
3306
+ /**
3307
+ * @license lucide-react v0.576.0 - ISC
3308
+ *
3309
+ * This source code is licensed under the ISC license.
3310
+ * See the LICENSE file in the root directory of this source tree.
3311
+ */
2659
3312
  var MapPin = createLucideIcon("map-pin", [["path", {
2660
3313
  d: "M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0",
2661
3314
  key: "1r0f0z"
@@ -2745,6 +3398,38 @@ var MessageSquare = createLucideIcon("message-square", [["path", {
2745
3398
  * This source code is licensed under the ISC license.
2746
3399
  * See the LICENSE file in the root directory of this source tree.
2747
3400
  */
3401
+ var MicOff = createLucideIcon("mic-off", [
3402
+ ["path", {
3403
+ d: "M12 19v3",
3404
+ key: "npa21l"
3405
+ }],
3406
+ ["path", {
3407
+ d: "M15 9.34V5a3 3 0 0 0-5.68-1.33",
3408
+ key: "1gzdoj"
3409
+ }],
3410
+ ["path", {
3411
+ d: "M16.95 16.95A7 7 0 0 1 5 12v-2",
3412
+ key: "cqa7eg"
3413
+ }],
3414
+ ["path", {
3415
+ d: "M18.89 13.23A7 7 0 0 0 19 12v-2",
3416
+ key: "16hl24"
3417
+ }],
3418
+ ["path", {
3419
+ d: "m2 2 20 20",
3420
+ key: "1ooewy"
3421
+ }],
3422
+ ["path", {
3423
+ d: "M9 9v3a3 3 0 0 0 5.12 2.12",
3424
+ key: "r2i35w"
3425
+ }]
3426
+ ]);
3427
+ /**
3428
+ * @license lucide-react v0.576.0 - ISC
3429
+ *
3430
+ * This source code is licensed under the ISC license.
3431
+ * See the LICENSE file in the root directory of this source tree.
3432
+ */
2748
3433
  var Mic = createLucideIcon("mic", [
2749
3434
  ["path", {
2750
3435
  d: "M12 19v3",
@@ -2769,6 +3454,42 @@ var Mic = createLucideIcon("mic", [
2769
3454
  * This source code is licensed under the ISC license.
2770
3455
  * See the LICENSE file in the root directory of this source tree.
2771
3456
  */
3457
+ var Microwave = createLucideIcon("microwave", [
3458
+ ["rect", {
3459
+ width: "20",
3460
+ height: "15",
3461
+ x: "2",
3462
+ y: "4",
3463
+ rx: "2",
3464
+ key: "2no95f"
3465
+ }],
3466
+ ["rect", {
3467
+ width: "8",
3468
+ height: "7",
3469
+ x: "6",
3470
+ y: "8",
3471
+ rx: "1",
3472
+ key: "zh9wx"
3473
+ }],
3474
+ ["path", {
3475
+ d: "M18 8v7",
3476
+ key: "o5zi4n"
3477
+ }],
3478
+ ["path", {
3479
+ d: "M6 19v2",
3480
+ key: "1loha6"
3481
+ }],
3482
+ ["path", {
3483
+ d: "M18 19v2",
3484
+ key: "1dawf0"
3485
+ }]
3486
+ ]);
3487
+ /**
3488
+ * @license lucide-react v0.576.0 - ISC
3489
+ *
3490
+ * This source code is licensed under the ISC license.
3491
+ * See the LICENSE file in the root directory of this source tree.
3492
+ */
2772
3493
  var Minimize2 = createLucideIcon("minimize-2", [
2773
3494
  ["path", {
2774
3495
  d: "m14 10 7-7",
@@ -2831,6 +3552,36 @@ var MonitorPlay = createLucideIcon("monitor-play", [
2831
3552
  * This source code is licensed under the ISC license.
2832
3553
  * See the LICENSE file in the root directory of this source tree.
2833
3554
  */
3555
+ var Monitor = createLucideIcon("monitor", [
3556
+ ["rect", {
3557
+ width: "20",
3558
+ height: "14",
3559
+ x: "2",
3560
+ y: "3",
3561
+ rx: "2",
3562
+ key: "48i651"
3563
+ }],
3564
+ ["line", {
3565
+ x1: "8",
3566
+ x2: "16",
3567
+ y1: "21",
3568
+ y2: "21",
3569
+ key: "1svkeh"
3570
+ }],
3571
+ ["line", {
3572
+ x1: "12",
3573
+ x2: "12",
3574
+ y1: "17",
3575
+ y2: "21",
3576
+ key: "vw1qmm"
3577
+ }]
3578
+ ]);
3579
+ /**
3580
+ * @license lucide-react v0.576.0 - ISC
3581
+ *
3582
+ * This source code is licensed under the ISC license.
3583
+ * See the LICENSE file in the root directory of this source tree.
3584
+ */
2834
3585
  var Moon = createLucideIcon("moon", [["path", {
2835
3586
  d: "M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401",
2836
3587
  key: "kfwtm"
@@ -2841,6 +3592,16 @@ var Moon = createLucideIcon("moon", [["path", {
2841
3592
  * This source code is licensed under the ISC license.
2842
3593
  * See the LICENSE file in the root directory of this source tree.
2843
3594
  */
3595
+ var Mountain = createLucideIcon("mountain", [["path", {
3596
+ d: "m8 3 4 8 5-5 5 15H2L8 3z",
3597
+ key: "otkl63"
3598
+ }]]);
3599
+ /**
3600
+ * @license lucide-react v0.576.0 - ISC
3601
+ *
3602
+ * This source code is licensed under the ISC license.
3603
+ * See the LICENSE file in the root directory of this source tree.
3604
+ */
2844
3605
  var MousePointerClick = createLucideIcon("mouse-pointer-click", [
2845
3606
  ["path", {
2846
3607
  d: "M14 4.1 12 6",
@@ -3103,6 +3864,32 @@ var Pencil = createLucideIcon("pencil", [["path", {
3103
3864
  * This source code is licensed under the ISC license.
3104
3865
  * See the LICENSE file in the root directory of this source tree.
3105
3866
  */
3867
+ var PersonStanding = createLucideIcon("person-standing", [
3868
+ ["circle", {
3869
+ cx: "12",
3870
+ cy: "5",
3871
+ r: "1",
3872
+ key: "gxeob9"
3873
+ }],
3874
+ ["path", {
3875
+ d: "m9 20 3-6 3 6",
3876
+ key: "se2kox"
3877
+ }],
3878
+ ["path", {
3879
+ d: "m6 8 6 2 6-2",
3880
+ key: "4o3us4"
3881
+ }],
3882
+ ["path", {
3883
+ d: "M12 10v4",
3884
+ key: "1kjpxc"
3885
+ }]
3886
+ ]);
3887
+ /**
3888
+ * @license lucide-react v0.576.0 - ISC
3889
+ *
3890
+ * This source code is licensed under the ISC license.
3891
+ * See the LICENSE file in the root directory of this source tree.
3892
+ */
3106
3893
  var Phone = createLucideIcon("phone", [["path", {
3107
3894
  d: "M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384",
3108
3895
  key: "9njp5v"
@@ -3153,6 +3940,34 @@ var Play = createLucideIcon("play", [["path", {
3153
3940
  * This source code is licensed under the ISC license.
3154
3941
  * See the LICENSE file in the root directory of this source tree.
3155
3942
  */
3943
+ var PlugZap = createLucideIcon("plug-zap", [
3944
+ ["path", {
3945
+ d: "M6.3 20.3a2.4 2.4 0 0 0 3.4 0L12 18l-6-6-2.3 2.3a2.4 2.4 0 0 0 0 3.4Z",
3946
+ key: "goz73y"
3947
+ }],
3948
+ ["path", {
3949
+ d: "m2 22 3-3",
3950
+ key: "19mgm9"
3951
+ }],
3952
+ ["path", {
3953
+ d: "M7.5 13.5 10 11",
3954
+ key: "7xgeeb"
3955
+ }],
3956
+ ["path", {
3957
+ d: "M10.5 16.5 13 14",
3958
+ key: "10btkg"
3959
+ }],
3960
+ ["path", {
3961
+ d: "m18 3-4 4h6l-4 4",
3962
+ key: "16psg9"
3963
+ }]
3964
+ ]);
3965
+ /**
3966
+ * @license lucide-react v0.576.0 - ISC
3967
+ *
3968
+ * This source code is licensed under the ISC license.
3969
+ * See the LICENSE file in the root directory of this source tree.
3970
+ */
3156
3971
  var Plus = createLucideIcon("plus", [["path", {
3157
3972
  d: "M5 12h14",
3158
3973
  key: "1ays0h"
@@ -3227,6 +4042,64 @@ var Power = createLucideIcon("power", [["path", {
3227
4042
  * This source code is licensed under the ISC license.
3228
4043
  * See the LICENSE file in the root directory of this source tree.
3229
4044
  */
4045
+ var Printer = createLucideIcon("printer", [
4046
+ ["path", {
4047
+ d: "M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2",
4048
+ key: "143wyd"
4049
+ }],
4050
+ ["path", {
4051
+ d: "M6 9V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6",
4052
+ key: "1itne7"
4053
+ }],
4054
+ ["rect", {
4055
+ x: "6",
4056
+ y: "14",
4057
+ width: "12",
4058
+ height: "8",
4059
+ rx: "1",
4060
+ key: "1ue0tg"
4061
+ }]
4062
+ ]);
4063
+ /**
4064
+ * @license lucide-react v0.576.0 - ISC
4065
+ *
4066
+ * This source code is licensed under the ISC license.
4067
+ * See the LICENSE file in the root directory of this source tree.
4068
+ */
4069
+ var Projector = createLucideIcon("projector", [
4070
+ ["path", {
4071
+ d: "M5 7 3 5",
4072
+ key: "1yys58"
4073
+ }],
4074
+ ["path", {
4075
+ d: "M9 6V3",
4076
+ key: "1ptz9u"
4077
+ }],
4078
+ ["path", {
4079
+ d: "m13 7 2-2",
4080
+ key: "1w3vmq"
4081
+ }],
4082
+ ["circle", {
4083
+ cx: "9",
4084
+ cy: "13",
4085
+ r: "3",
4086
+ key: "1mma13"
4087
+ }],
4088
+ ["path", {
4089
+ d: "M11.83 12H20a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h2.17",
4090
+ key: "2frwzc"
4091
+ }],
4092
+ ["path", {
4093
+ d: "M16 16h2",
4094
+ key: "dnq2od"
4095
+ }]
4096
+ ]);
4097
+ /**
4098
+ * @license lucide-react v0.576.0 - ISC
4099
+ *
4100
+ * This source code is licensed under the ISC license.
4101
+ * See the LICENSE file in the root directory of this source tree.
4102
+ */
3230
4103
  var QrCode$1 = createLucideIcon("qr-code", [
3231
4104
  ["rect", {
3232
4105
  width: "5",
@@ -3429,6 +4302,26 @@ var RefreshCw = createLucideIcon("refresh-cw", [
3429
4302
  * This source code is licensed under the ISC license.
3430
4303
  * See the LICENSE file in the root directory of this source tree.
3431
4304
  */
4305
+ var Refrigerator = createLucideIcon("refrigerator", [
4306
+ ["path", {
4307
+ d: "M5 6a4 4 0 0 1 4-4h6a4 4 0 0 1 4 4v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6Z",
4308
+ key: "fpq118"
4309
+ }],
4310
+ ["path", {
4311
+ d: "M5 10h14",
4312
+ key: "elsbfy"
4313
+ }],
4314
+ ["path", {
4315
+ d: "M15 7v6",
4316
+ key: "1nx30x"
4317
+ }]
4318
+ ]);
4319
+ /**
4320
+ * @license lucide-react v0.576.0 - ISC
4321
+ *
4322
+ * This source code is licensed under the ISC license.
4323
+ * See the LICENSE file in the root directory of this source tree.
4324
+ */
3432
4325
  var RotateCcw = createLucideIcon("rotate-ccw", [["path", {
3433
4326
  d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",
3434
4327
  key: "1357e3"
@@ -3455,6 +4348,66 @@ var RotateCw = createLucideIcon("rotate-cw", [["path", {
3455
4348
  * This source code is licensed under the ISC license.
3456
4349
  * See the LICENSE file in the root directory of this source tree.
3457
4350
  */
4351
+ var Route = createLucideIcon("route", [
4352
+ ["circle", {
4353
+ cx: "6",
4354
+ cy: "19",
4355
+ r: "3",
4356
+ key: "1kj8tv"
4357
+ }],
4358
+ ["path", {
4359
+ d: "M9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15",
4360
+ key: "1d8sl"
4361
+ }],
4362
+ ["circle", {
4363
+ cx: "18",
4364
+ cy: "5",
4365
+ r: "3",
4366
+ key: "gq8acd"
4367
+ }]
4368
+ ]);
4369
+ /**
4370
+ * @license lucide-react v0.576.0 - ISC
4371
+ *
4372
+ * This source code is licensed under the ISC license.
4373
+ * See the LICENSE file in the root directory of this source tree.
4374
+ */
4375
+ var Router = createLucideIcon("router", [
4376
+ ["rect", {
4377
+ width: "20",
4378
+ height: "8",
4379
+ x: "2",
4380
+ y: "14",
4381
+ rx: "2",
4382
+ key: "w68u3i"
4383
+ }],
4384
+ ["path", {
4385
+ d: "M6.01 18H6",
4386
+ key: "19vcac"
4387
+ }],
4388
+ ["path", {
4389
+ d: "M10.01 18H10",
4390
+ key: "uamcmx"
4391
+ }],
4392
+ ["path", {
4393
+ d: "M15 10v4",
4394
+ key: "qjz1xs"
4395
+ }],
4396
+ ["path", {
4397
+ d: "M17.84 7.17a4 4 0 0 0-5.66 0",
4398
+ key: "1rif40"
4399
+ }],
4400
+ ["path", {
4401
+ d: "M20.66 4.34a8 8 0 0 0-11.31 0",
4402
+ key: "6a5xfq"
4403
+ }]
4404
+ ]);
4405
+ /**
4406
+ * @license lucide-react v0.576.0 - ISC
4407
+ *
4408
+ * This source code is licensed under the ISC license.
4409
+ * See the LICENSE file in the root directory of this source tree.
4410
+ */
3458
4411
  var Sailboat = createLucideIcon("sailboat", [
3459
4412
  ["path", {
3460
4413
  d: "M10 2v15",
@@ -3928,6 +4881,64 @@ var Snowflake = createLucideIcon("snowflake", [
3928
4881
  * This source code is licensed under the ISC license.
3929
4882
  * See the LICENSE file in the root directory of this source tree.
3930
4883
  */
4884
+ var Sofa = createLucideIcon("sofa", [
4885
+ ["path", {
4886
+ d: "M20 9V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v3",
4887
+ key: "1dgpiv"
4888
+ }],
4889
+ ["path", {
4890
+ d: "M2 16a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5a2 2 0 0 0-4 0v1.5a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5V11a2 2 0 0 0-4 0z",
4891
+ key: "xacw8m"
4892
+ }],
4893
+ ["path", {
4894
+ d: "M4 18v2",
4895
+ key: "jwo5n2"
4896
+ }],
4897
+ ["path", {
4898
+ d: "M20 18v2",
4899
+ key: "1ar1qi"
4900
+ }],
4901
+ ["path", {
4902
+ d: "M12 4v9",
4903
+ key: "oqhhn3"
4904
+ }]
4905
+ ]);
4906
+ /**
4907
+ * @license lucide-react v0.576.0 - ISC
4908
+ *
4909
+ * This source code is licensed under the ISC license.
4910
+ * See the LICENSE file in the root directory of this source tree.
4911
+ */
4912
+ var Speaker = createLucideIcon("speaker", [
4913
+ ["rect", {
4914
+ width: "16",
4915
+ height: "20",
4916
+ x: "4",
4917
+ y: "2",
4918
+ rx: "2",
4919
+ key: "1nb95v"
4920
+ }],
4921
+ ["path", {
4922
+ d: "M12 6h.01",
4923
+ key: "1vi96p"
4924
+ }],
4925
+ ["circle", {
4926
+ cx: "12",
4927
+ cy: "14",
4928
+ r: "4",
4929
+ key: "1jruaj"
4930
+ }],
4931
+ ["path", {
4932
+ d: "M12 14h.01",
4933
+ key: "1etili"
4934
+ }]
4935
+ ]);
4936
+ /**
4937
+ * @license lucide-react v0.576.0 - ISC
4938
+ *
4939
+ * This source code is licensed under the ISC license.
4940
+ * See the LICENSE file in the root directory of this source tree.
4941
+ */
3931
4942
  var Sparkles = createLucideIcon("sparkles", [
3932
4943
  ["path", {
3933
4944
  d: "M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z",
@@ -3998,6 +5009,26 @@ var Star = createLucideIcon("star", [["path", {
3998
5009
  * This source code is licensed under the ISC license.
3999
5010
  * See the LICENSE file in the root directory of this source tree.
4000
5011
  */
5012
+ var Store = createLucideIcon("store", [
5013
+ ["path", {
5014
+ d: "M15 21v-5a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v5",
5015
+ key: "slp6dd"
5016
+ }],
5017
+ ["path", {
5018
+ d: "M17.774 10.31a1.12 1.12 0 0 0-1.549 0 2.5 2.5 0 0 1-3.451 0 1.12 1.12 0 0 0-1.548 0 2.5 2.5 0 0 1-3.452 0 1.12 1.12 0 0 0-1.549 0 2.5 2.5 0 0 1-3.77-3.248l2.889-4.184A2 2 0 0 1 7 2h10a2 2 0 0 1 1.653.873l2.895 4.192a2.5 2.5 0 0 1-3.774 3.244",
5019
+ key: "o0xfot"
5020
+ }],
5021
+ ["path", {
5022
+ d: "M4 10.95V19a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8.05",
5023
+ key: "wn3emo"
5024
+ }]
5025
+ ]);
5026
+ /**
5027
+ * @license lucide-react v0.576.0 - ISC
5028
+ *
5029
+ * This source code is licensed under the ISC license.
5030
+ * See the LICENSE file in the root directory of this source tree.
5031
+ */
4001
5032
  var SunMedium = createLucideIcon("sun-medium", [
4002
5033
  ["circle", {
4003
5034
  cx: "12",
@@ -4130,6 +5161,27 @@ var Sunrise = createLucideIcon("sunrise", [
4130
5161
  * This source code is licensed under the ISC license.
4131
5162
  * See the LICENSE file in the root directory of this source tree.
4132
5163
  */
5164
+ var Tablet = createLucideIcon("tablet", [["rect", {
5165
+ width: "16",
5166
+ height: "20",
5167
+ x: "4",
5168
+ y: "2",
5169
+ rx: "2",
5170
+ ry: "2",
5171
+ key: "76otgf"
5172
+ }], ["line", {
5173
+ x1: "12",
5174
+ x2: "12.01",
5175
+ y1: "18",
5176
+ y2: "18",
5177
+ key: "1dp563"
5178
+ }]]);
5179
+ /**
5180
+ * @license lucide-react v0.576.0 - ISC
5181
+ *
5182
+ * This source code is licensed under the ISC license.
5183
+ * See the LICENSE file in the root directory of this source tree.
5184
+ */
4133
5185
  var Tag = createLucideIcon("tag", [["path", {
4134
5186
  d: "M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z",
4135
5187
  key: "vktsd0"
@@ -4172,6 +5224,46 @@ var Target = createLucideIcon("target", [
4172
5224
  * This source code is licensed under the ISC license.
4173
5225
  * See the LICENSE file in the root directory of this source tree.
4174
5226
  */
5227
+ var ThermometerSnowflake = createLucideIcon("thermometer-snowflake", [
5228
+ ["path", {
5229
+ d: "m10 20-1.25-2.5L6 18",
5230
+ key: "18frcb"
5231
+ }],
5232
+ ["path", {
5233
+ d: "M10 4 8.75 6.5 6 6",
5234
+ key: "7mghy3"
5235
+ }],
5236
+ ["path", {
5237
+ d: "M10.585 15H10",
5238
+ key: "4nqulp"
5239
+ }],
5240
+ ["path", {
5241
+ d: "M2 12h6.5L10 9",
5242
+ key: "kv9z4n"
5243
+ }],
5244
+ ["path", {
5245
+ d: "M20 14.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0z",
5246
+ key: "yu0u2z"
5247
+ }],
5248
+ ["path", {
5249
+ d: "m4 10 1.5 2L4 14",
5250
+ key: "k9enpj"
5251
+ }],
5252
+ ["path", {
5253
+ d: "m7 21 3-6-1.5-3",
5254
+ key: "j8hb9u"
5255
+ }],
5256
+ ["path", {
5257
+ d: "m7 3 3 6h2",
5258
+ key: "1bbqgq"
5259
+ }]
5260
+ ]);
5261
+ /**
5262
+ * @license lucide-react v0.576.0 - ISC
5263
+ *
5264
+ * This source code is licensed under the ISC license.
5265
+ * See the LICENSE file in the root directory of this source tree.
5266
+ */
4175
5267
  var Thermometer = createLucideIcon("thermometer", [["path", {
4176
5268
  d: "M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z",
4177
5269
  key: "17jzev"
@@ -4182,6 +5274,66 @@ var Thermometer = createLucideIcon("thermometer", [["path", {
4182
5274
  * This source code is licensed under the ISC license.
4183
5275
  * See the LICENSE file in the root directory of this source tree.
4184
5276
  */
5277
+ var ThermometerSun = createLucideIcon("thermometer-sun", [
5278
+ ["path", {
5279
+ d: "M12 2v2",
5280
+ key: "tus03m"
5281
+ }],
5282
+ ["path", {
5283
+ d: "M12 8a4 4 0 0 0-1.645 7.647",
5284
+ key: "wz5p04"
5285
+ }],
5286
+ ["path", {
5287
+ d: "M2 12h2",
5288
+ key: "1t8f8n"
5289
+ }],
5290
+ ["path", {
5291
+ d: "M20 14.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0z",
5292
+ key: "yu0u2z"
5293
+ }],
5294
+ ["path", {
5295
+ d: "m4.93 4.93 1.41 1.41",
5296
+ key: "149t6j"
5297
+ }],
5298
+ ["path", {
5299
+ d: "m6.34 17.66-1.41 1.41",
5300
+ key: "1m8zz5"
5301
+ }]
5302
+ ]);
5303
+ /**
5304
+ * @license lucide-react v0.576.0 - ISC
5305
+ *
5306
+ * This source code is licensed under the ISC license.
5307
+ * See the LICENSE file in the root directory of this source tree.
5308
+ */
5309
+ var Timer = createLucideIcon("timer", [
5310
+ ["line", {
5311
+ x1: "10",
5312
+ x2: "14",
5313
+ y1: "2",
5314
+ y2: "2",
5315
+ key: "14vaq8"
5316
+ }],
5317
+ ["line", {
5318
+ x1: "12",
5319
+ x2: "15",
5320
+ y1: "14",
5321
+ y2: "11",
5322
+ key: "17fdiu"
5323
+ }],
5324
+ ["circle", {
5325
+ cx: "12",
5326
+ cy: "14",
5327
+ r: "8",
5328
+ key: "1e1u0o"
5329
+ }]
5330
+ ]);
5331
+ /**
5332
+ * @license lucide-react v0.576.0 - ISC
5333
+ *
5334
+ * This source code is licensed under the ISC license.
5335
+ * See the LICENSE file in the root directory of this source tree.
5336
+ */
4185
5337
  var ToggleLeft = createLucideIcon("toggle-left", [["circle", {
4186
5338
  cx: "9",
4187
5339
  cy: "12",
@@ -4328,6 +5480,43 @@ var Trash2 = createLucideIcon("trash-2", [
4328
5480
  * This source code is licensed under the ISC license.
4329
5481
  * See the LICENSE file in the root directory of this source tree.
4330
5482
  */
5483
+ var TreePine = createLucideIcon("tree-pine", [["path", {
5484
+ d: "m17 14 3 3.3a1 1 0 0 1-.7 1.7H4.7a1 1 0 0 1-.7-1.7L7 14h-.3a1 1 0 0 1-.7-1.7L9 9h-.2A1 1 0 0 1 8 7.3L12 3l4 4.3a1 1 0 0 1-.8 1.7H15l3 3.3a1 1 0 0 1-.7 1.7H17Z",
5485
+ key: "cpyugq"
5486
+ }], ["path", {
5487
+ d: "M12 22v-3",
5488
+ key: "kmzjlo"
5489
+ }]]);
5490
+ /**
5491
+ * @license lucide-react v0.576.0 - ISC
5492
+ *
5493
+ * This source code is licensed under the ISC license.
5494
+ * See the LICENSE file in the root directory of this source tree.
5495
+ */
5496
+ var Trees = createLucideIcon("trees", [
5497
+ ["path", {
5498
+ d: "M10 10v.2A3 3 0 0 1 8.9 16H5a3 3 0 0 1-1-5.8V10a3 3 0 0 1 6 0Z",
5499
+ key: "1l6gj6"
5500
+ }],
5501
+ ["path", {
5502
+ d: "M7 16v6",
5503
+ key: "1a82de"
5504
+ }],
5505
+ ["path", {
5506
+ d: "M13 19v3",
5507
+ key: "13sx9i"
5508
+ }],
5509
+ ["path", {
5510
+ d: "M12 19h8.3a1 1 0 0 0 .7-1.7L18 14h.3a1 1 0 0 0 .7-1.7L16 9h.2a1 1 0 0 0 .8-1.7L13 3l-1.4 1.5",
5511
+ key: "1sj9kv"
5512
+ }]
5513
+ ]);
5514
+ /**
5515
+ * @license lucide-react v0.576.0 - ISC
5516
+ *
5517
+ * This source code is licensed under the ISC license.
5518
+ * See the LICENSE file in the root directory of this source tree.
5519
+ */
4331
5520
  var TrendingUp = createLucideIcon("trending-up", [["path", {
4332
5521
  d: "M16 7h6v6",
4333
5522
  key: "box55l"
@@ -4406,6 +5595,23 @@ var Truck = createLucideIcon("truck", [
4406
5595
  * This source code is licensed under the ISC license.
4407
5596
  * See the LICENSE file in the root directory of this source tree.
4408
5597
  */
5598
+ var Tv = createLucideIcon("tv", [["path", {
5599
+ d: "m17 2-5 5-5-5",
5600
+ key: "16satq"
5601
+ }], ["rect", {
5602
+ width: "20",
5603
+ height: "15",
5604
+ x: "2",
5605
+ y: "7",
5606
+ rx: "2",
5607
+ key: "1e6viu"
5608
+ }]]);
5609
+ /**
5610
+ * @license lucide-react v0.576.0 - ISC
5611
+ *
5612
+ * This source code is licensed under the ISC license.
5613
+ * See the LICENSE file in the root directory of this source tree.
5614
+ */
4409
5615
  var Type = createLucideIcon("type", [
4410
5616
  ["path", {
4411
5617
  d: "M12 4v16",
@@ -4426,6 +5632,26 @@ var Type = createLucideIcon("type", [
4426
5632
  * This source code is licensed under the ISC license.
4427
5633
  * See the LICENSE file in the root directory of this source tree.
4428
5634
  */
5635
+ var Umbrella = createLucideIcon("umbrella", [
5636
+ ["path", {
5637
+ d: "M12 13v7a2 2 0 0 0 4 0",
5638
+ key: "rpgb42"
5639
+ }],
5640
+ ["path", {
5641
+ d: "M12 2v2",
5642
+ key: "tus03m"
5643
+ }],
5644
+ ["path", {
5645
+ d: "M20.992 13a1 1 0 0 0 .97-1.274 10.284 10.284 0 0 0-19.923 0A1 1 0 0 0 3 13z",
5646
+ key: "124nyo"
5647
+ }]
5648
+ ]);
5649
+ /**
5650
+ * @license lucide-react v0.576.0 - ISC
5651
+ *
5652
+ * This source code is licensed under the ISC license.
5653
+ * See the LICENSE file in the root directory of this source tree.
5654
+ */
4429
5655
  var Undo2 = createLucideIcon("undo-2", [["path", {
4430
5656
  d: "M9 14 4 9l5-5",
4431
5657
  key: "102s5s"
@@ -4556,6 +5782,26 @@ var UtensilsCrossed = createLucideIcon("utensils-crossed", [
4556
5782
  * This source code is licensed under the ISC license.
4557
5783
  * See the LICENSE file in the root directory of this source tree.
4558
5784
  */
5785
+ var Utensils = createLucideIcon("utensils", [
5786
+ ["path", {
5787
+ d: "M3 2v7c0 1.1.9 2 2 2h4a2 2 0 0 0 2-2V2",
5788
+ key: "cjf0a3"
5789
+ }],
5790
+ ["path", {
5791
+ d: "M7 2v20",
5792
+ key: "1473qp"
5793
+ }],
5794
+ ["path", {
5795
+ d: "M21 15V2a5 5 0 0 0-5 5v6c0 1.1.9 2 2 2h3Zm0 0v7",
5796
+ key: "j28e5"
5797
+ }]
5798
+ ]);
5799
+ /**
5800
+ * @license lucide-react v0.576.0 - ISC
5801
+ *
5802
+ * This source code is licensed under the ISC license.
5803
+ * See the LICENSE file in the root directory of this source tree.
5804
+ */
4559
5805
  var Vibrate = createLucideIcon("vibrate", [
4560
5806
  ["path", {
4561
5807
  d: "m2 8 2 2-2 2 2 2-2 2",
@@ -4580,6 +5826,43 @@ var Vibrate = createLucideIcon("vibrate", [
4580
5826
  * This source code is licensed under the ISC license.
4581
5827
  * See the LICENSE file in the root directory of this source tree.
4582
5828
  */
5829
+ var VideoOff = createLucideIcon("video-off", [
5830
+ ["path", {
5831
+ d: "M10.66 6H14a2 2 0 0 1 2 2v2.5l5.248-3.062A.5.5 0 0 1 22 7.87v8.196",
5832
+ key: "w8jjjt"
5833
+ }],
5834
+ ["path", {
5835
+ d: "M16 16a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2",
5836
+ key: "1xawa7"
5837
+ }],
5838
+ ["path", {
5839
+ d: "m2 2 20 20",
5840
+ key: "1ooewy"
5841
+ }]
5842
+ ]);
5843
+ /**
5844
+ * @license lucide-react v0.576.0 - ISC
5845
+ *
5846
+ * This source code is licensed under the ISC license.
5847
+ * See the LICENSE file in the root directory of this source tree.
5848
+ */
5849
+ var Video = createLucideIcon("video", [["path", {
5850
+ d: "m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5",
5851
+ key: "ftymec"
5852
+ }], ["rect", {
5853
+ x: "2",
5854
+ y: "6",
5855
+ width: "14",
5856
+ height: "12",
5857
+ rx: "2",
5858
+ key: "158x01"
5859
+ }]]);
5860
+ /**
5861
+ * @license lucide-react v0.576.0 - ISC
5862
+ *
5863
+ * This source code is licensed under the ISC license.
5864
+ * See the LICENSE file in the root directory of this source tree.
5865
+ */
4583
5866
  var Volume2 = createLucideIcon("volume-2", [
4584
5867
  ["path", {
4585
5868
  d: "M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z",
@@ -4626,6 +5909,92 @@ var VolumeX = createLucideIcon("volume-x", [
4626
5909
  * This source code is licensed under the ISC license.
4627
5910
  * See the LICENSE file in the root directory of this source tree.
4628
5911
  */
5912
+ var Warehouse = createLucideIcon("warehouse", [
5913
+ ["path", {
5914
+ d: "M18 21V10a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1v11",
5915
+ key: "pb2vm6"
5916
+ }],
5917
+ ["path", {
5918
+ d: "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 1.132-1.803l7.95-3.974a2 2 0 0 1 1.837 0l7.948 3.974A2 2 0 0 1 22 8z",
5919
+ key: "doq5xv"
5920
+ }],
5921
+ ["path", {
5922
+ d: "M6 13h12",
5923
+ key: "yf64js"
5924
+ }],
5925
+ ["path", {
5926
+ d: "M6 17h12",
5927
+ key: "1jwigz"
5928
+ }]
5929
+ ]);
5930
+ /**
5931
+ * @license lucide-react v0.576.0 - ISC
5932
+ *
5933
+ * This source code is licensed under the ISC license.
5934
+ * See the LICENSE file in the root directory of this source tree.
5935
+ */
5936
+ var WashingMachine = createLucideIcon("washing-machine", [
5937
+ ["path", {
5938
+ d: "M3 6h3",
5939
+ key: "155dbl"
5940
+ }],
5941
+ ["path", {
5942
+ d: "M17 6h.01",
5943
+ key: "e2y6kg"
5944
+ }],
5945
+ ["rect", {
5946
+ width: "18",
5947
+ height: "20",
5948
+ x: "3",
5949
+ y: "2",
5950
+ rx: "2",
5951
+ key: "od3kk9"
5952
+ }],
5953
+ ["circle", {
5954
+ cx: "12",
5955
+ cy: "13",
5956
+ r: "5",
5957
+ key: "nlbqau"
5958
+ }],
5959
+ ["path", {
5960
+ d: "M12 18a2.5 2.5 0 0 0 0-5 2.5 2.5 0 0 1 0-5",
5961
+ key: "17lach"
5962
+ }]
5963
+ ]);
5964
+ /**
5965
+ * @license lucide-react v0.576.0 - ISC
5966
+ *
5967
+ * This source code is licensed under the ISC license.
5968
+ * See the LICENSE file in the root directory of this source tree.
5969
+ */
5970
+ var WavesLadder = createLucideIcon("waves-ladder", [
5971
+ ["path", {
5972
+ d: "M19 5a2 2 0 0 0-2 2v11",
5973
+ key: "s41o68"
5974
+ }],
5975
+ ["path", {
5976
+ d: "M2 18c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1",
5977
+ key: "rd2r6e"
5978
+ }],
5979
+ ["path", {
5980
+ d: "M7 13h10",
5981
+ key: "1rwob1"
5982
+ }],
5983
+ ["path", {
5984
+ d: "M7 9h10",
5985
+ key: "12czzb"
5986
+ }],
5987
+ ["path", {
5988
+ d: "M9 5a2 2 0 0 0-2 2v11",
5989
+ key: "x0q4gh"
5990
+ }]
5991
+ ]);
5992
+ /**
5993
+ * @license lucide-react v0.576.0 - ISC
5994
+ *
5995
+ * This source code is licensed under the ISC license.
5996
+ * See the LICENSE file in the root directory of this source tree.
5997
+ */
4629
5998
  var Waves = createLucideIcon("waves", [
4630
5999
  ["path", {
4631
6000
  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",
@@ -7215,6 +8584,8 @@ var useVideoclipsListSources = trpc.videoclips.listSources.useQuery;
7215
8584
  var useVideoclipsGetClipPlayback = trpc.videoclips.getClipPlayback.useQuery;
7216
8585
  /** Generated alias around `trpc.videoclips.readClipBytes.useQuery`. */
7217
8586
  var useVideoclipsReadClipBytes = trpc.videoclips.readClipBytes.useQuery;
8587
+ /** Generated alias around `trpc.videoclips.dialClipStream.useQuery`. */
8588
+ var useVideoclipsDialClipStream = trpc.videoclips.dialClipStream.useQuery;
7218
8589
  /** Generated alias around `trpc.waterHeater.setTargetTemp.useMutation`. */
7219
8590
  var useWaterHeaterSetTargetTemp = trpc.waterHeater.setTargetTemp.useMutation;
7220
8591
  /** Generated alias around `trpc.waterHeater.setOperationMode.useMutation`. */
@@ -17686,38 +19057,6 @@ var cva = (base, config) => (props) => {
17686
19057
  }, []), props === null || props === void 0 ? void 0 : props.class, props === null || props === void 0 ? void 0 : props.className);
17687
19058
  };
17688
19059
  //#endregion
17689
- //#region src/primitives/badge.tsx
17690
- var badgeVariants = cva("inline-flex items-center rounded-full text-xs font-medium px-2 py-0.5", {
17691
- variants: {
17692
- variant: {
17693
- default: "bg-surface-hover text-foreground",
17694
- success: "bg-success/15 text-success",
17695
- warning: "bg-warning/15 text-warning",
17696
- danger: "bg-danger/15 text-danger",
17697
- info: "bg-info/15 text-info"
17698
- },
17699
- styleVariant: {
17700
- solid: "",
17701
- outline: "border bg-transparent"
17702
- }
17703
- },
17704
- defaultVariants: {
17705
- variant: "default",
17706
- styleVariant: "solid"
17707
- }
17708
- });
17709
- var Badge = (0, react$1.forwardRef)(({ className, variant, style, ...props }, ref) => {
17710
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
17711
- ref,
17712
- className: cn(badgeVariants({
17713
- variant,
17714
- styleVariant: style
17715
- }), className),
17716
- ...props
17717
- });
17718
- });
17719
- Badge.displayName = "Badge";
17720
- //#endregion
17721
19060
  //#region src/primitives/button.tsx
17722
19061
  var buttonVariants = cva("inline-flex items-center justify-center rounded-md font-medium transition-colors cursor-pointer disabled:opacity-50 disabled:pointer-events-none", {
17723
19062
  variants: {
@@ -17993,590 +19332,123 @@ function axisTicks(fromMs, toMs, count) {
17993
19332
  return ticks;
17994
19333
  }
17995
19334
  //#endregion
17996
- //#region src/hooks/use-is-mobile.ts
17997
- var MOBILE_QUERY = "(max-width: 767px)";
17998
- /**
17999
- * Mid breakpoint — below this width the sidebar collapses to an
18000
- * icons-only rail to reclaim canvas space. Above it (and outside
18001
- * mobile) the full sidebar renders. Threshold matches Tailwind `lg`
18002
- * so sidebar fans out at the same point grids switch to multi-column.
18003
- */
18004
- var MID_QUERY = "(min-width: 768px) and (max-width: 1023px)";
18005
- /**
18006
- * `matchMedia` is universal in browsers but absent in bare DOM environments.
18007
- * These hooks are called from shared primitives (`DataTable` picks its narrow
18008
- * layout with one), so throwing here takes down the whole page that merely
18009
- * rendered a table. Where the capability is missing, report the desktop
18010
- * layout — a widescreen rendering is wrong-looking; a crashed page is gone.
18011
- */
18012
- function matchQuery(query) {
18013
- if (typeof window.matchMedia !== "function") return null;
18014
- return window.matchMedia(query);
18015
- }
18016
- function subscribeQuery(query) {
18017
- return (callback) => {
18018
- const mql = matchQuery(query);
18019
- if (mql === null) return () => {};
18020
- mql.addEventListener("change", callback);
18021
- return () => mql.removeEventListener("change", callback);
18022
- };
18023
- }
18024
- function getSnapshot(query) {
18025
- return () => matchQuery(query)?.matches ?? false;
18026
- }
18027
- function getServerSnapshot() {
18028
- return false;
18029
- }
18030
- function useIsMobile() {
18031
- return (0, react$1.useSyncExternalStore)(subscribeQuery(MOBILE_QUERY), getSnapshot(MOBILE_QUERY), getServerSnapshot);
18032
- }
18033
- /**
18034
- * True when the viewport is in the medium (`md`) Tailwind range —
18035
- * 768px ≤ width < 1024px. Use to drive layouts that should pack denser
18036
- * than full-desktop but more legibly than mobile (e.g. icons-only
18037
- * sidebar rail, narrower stat tiles).
18038
- */
18039
- function useIsMidWidth() {
18040
- return (0, react$1.useSyncExternalStore)(subscribeQuery(MID_QUERY), getSnapshot(MID_QUERY), getServerSnapshot);
18041
- }
18042
- //#endregion
18043
- //#region src/composites/data-table-layout.ts
18044
- /**
18045
- * At this many columns and above, `auto` switches a narrow viewport to cards.
18046
- * Three columns still fit a phone at a readable size; four do not.
18047
- */
18048
- var CARD_MODE_MIN_COLUMNS = 4;
18049
- function resolveTableLayout({ mode, columnCount, isNarrow }) {
18050
- if (!isNarrow) return "table";
18051
- if (mode === "scroll") return "table";
18052
- if (mode === "cards") return "cards";
18053
- if (columnCount === 0) return "table";
18054
- return columnCount >= 4 ? "cards" : "table";
19335
+ //#region src/composites/device-list/pagination.tsx
19336
+ var NEIGHBORS = 2;
19337
+ function computePageButtons(currentPage, totalPages) {
19338
+ if (totalPages <= 1) return [{
19339
+ kind: "page",
19340
+ n: 1
19341
+ }];
19342
+ if (totalPages <= 7) {
19343
+ const all = [];
19344
+ for (let i = 1; i <= totalPages; i++) all.push({
19345
+ kind: "page",
19346
+ n: i
19347
+ });
19348
+ return all;
19349
+ }
19350
+ const result = [];
19351
+ const leftBound = Math.max(2, currentPage - NEIGHBORS);
19352
+ const rightBound = Math.min(totalPages - 1, currentPage + NEIGHBORS);
19353
+ result.push({
19354
+ kind: "page",
19355
+ n: 1
19356
+ });
19357
+ if (leftBound > 2) result.push({
19358
+ kind: "ellipsis",
19359
+ side: "left"
19360
+ });
19361
+ for (let i = leftBound; i <= rightBound; i++) result.push({
19362
+ kind: "page",
19363
+ n: i
19364
+ });
19365
+ if (rightBound < totalPages - 1) result.push({
19366
+ kind: "ellipsis",
19367
+ side: "right"
19368
+ });
19369
+ result.push({
19370
+ kind: "page",
19371
+ n: totalPages
19372
+ });
19373
+ return result;
18055
19374
  }
18056
- //#endregion
18057
- //#region src/lib/responsive.ts
18058
- /**
18059
- * Responsive design tokens — three-tier system.
18060
- *
18061
- * The admin-ui targets three width tiers; every component should pick
18062
- * the right token from this file instead of hand-coding breakpoints.
18063
- * Adopting the tokens uniformly is what makes the app feel consistent
18064
- * across operator devices (mobile / tablet / desktop) and what unlocks
18065
- * targeted tuning (e.g. shrink every label by 1px in one place).
18066
- *
18067
- * Tier definitions (Tailwind breakpoints):
18068
- *
18069
- * • **L1 — Compact** (`< 640px`, default = mobile portrait)
18070
- * Single column, tight padding, smallest text, icon-only buttons
18071
- * where labels can be inferred. The operator can still see every
18072
- * stat / action — no horizontal scroll, no clipped text. The user
18073
- * directive: "molta informazione in poco spazio quando la width è
18074
- * bassa."
18075
- *
18076
- * • **L2 — Standard** (`sm` — `640–1023px`, tablet + small laptop)
18077
- * 2-column grids, side panels become visible, button labels return.
18078
- * Targets ~768px–1024px viewports — the most common operator sizes.
18079
- *
18080
- * • **L3 — Spacious** (`lg+` — `≥ 1024px`, desktop)
18081
- * 4-column grids, generous padding, full-width header text,
18082
- * side-by-side composite layouts. The operator has elbow room.
18083
- *
18084
- * Conventions:
18085
- * - **Always start from L1** — the bare class is always the compact
18086
- * variant, then `sm:` adds room at 640px, `lg:` at 1024px. No
18087
- * desktop-first breakpoints; no max-width queries.
18088
- * - **Token strings are static** — kept as `as const` so callers can
18089
- * compose them via `cn()` / template literals without running a
18090
- * class generator at runtime.
18091
- * - **Pair the token with a comment** — the comment in the file
18092
- * names every breakpoint it crosses, so a reader can predict the
18093
- * layout without going to Tailwind docs.
18094
- */
18095
- /** Container padding for a card-style section (header + body). */
18096
- var SECTION_CARD = "rounded-lg border border-border bg-surface overflow-hidden";
18097
- /** Header strip inside a card (between border-b lines). */
18098
- var SECTION_HEADER = "border-b border-border px-3 sm:px-4 py-1.5 sm:py-2";
18099
- /** Body padding for a card-style section. */
18100
- var SECTION_BODY = "p-2 sm:p-3 lg:p-4";
18101
- /** Gap between cards in a vertical stack. */
18102
- var STACK_GAP = "space-y-3 sm:space-y-4";
18103
- /** Gap between cards in a grid (4 cells / row at lg). */
18104
- var GRID_GAP = "gap-2 sm:gap-3";
18105
- /** Row padding inside a divided list (e.g. InfoRow). */
18106
- var LIST_ROW = "px-3 sm:px-4 py-1.5 sm:py-2";
18107
- /** 1-up at L1, 2-up at L2, 4-up at L3 — quick-stat row. */
18108
- var GRID_QUICK_STATS = "grid grid-cols-2 sm:grid-cols-2 lg:grid-cols-4";
18109
- /** 1-up at L1, 2-up at L2+ — paired panels (motion + detection rules). */
18110
- var GRID_PAIRED = "grid grid-cols-1 lg:grid-cols-2";
18111
- /** Side-by-side layout that stacks below md (canvas + side panel). */
18112
- var SPLIT_PANEL_OUTER = "flex flex-col-reverse md:flex-row";
18113
- /** Sidebar narrow lane in a SPLIT_PANEL_OUTER layout. */
18114
- var SPLIT_PANEL_SIDE = "w-full md:w-44 lg:w-52 md:flex-shrink-0 border-b md:border-b-0 md:border-l border-border";
18115
- /**
18116
- * The tier boundary at which a settings label/value row stops stacking.
18117
- *
18118
- * Below it (L1 — compact) the row is ONE column: label on its own line, value
18119
- * on the next at full width. Above it the row is label-left / value-right as
18120
- * before. The two-column form needs `SETTING_ROW_LABEL`'s lane plus a readable
18121
- * value beside it; a phone has neither, and the failure mode is not "cramped"
18122
- * but "the value is the part that gets cut" — the one thing the operator
18123
- * opened the page to read.
18124
- */
18125
- var SETTING_ROW_STACK_BREAKPOINT = "sm";
18126
- /** Outer row: single column at L1, label/value columns from `sm` up. */
18127
- var SETTING_ROW = "flex min-w-0 flex-col gap-0.5 py-1.5 sm:flex-row sm:items-center";
18128
- /** Label lane. Full width (and free to wrap) at L1; a fixed lane from `sm` up. */
18129
- var SETTING_ROW_LABEL = "min-w-0 text-[11px] leading-tight text-foreground-subtle sm:w-32 sm:shrink-0 sm:pr-2";
18130
- /**
18131
- * Value lane — the value text plus its trailing affordances (copy / reveal).
18132
- * `w-full` at L1 so the value owns the whole row once the label is above it;
18133
- * `sm:flex-1` makes it share the row again from `sm` up.
18134
- */
18135
- var SETTING_ROW_VALUE = "flex w-full min-w-0 items-center gap-1 sm:flex-1";
18136
- /**
18137
- * The value text itself. `break-all` lets an opaque token (a key, a URL) wrap
18138
- * at L1 instead of being cut mid-word; `sm:truncate` restores the single-line
18139
- * desktop form, where the row is wide enough for truncation to be a choice
18140
- * rather than data loss.
18141
- */
18142
- var SETTING_ROW_VALUE_TEXT = "min-w-0 break-all text-xs text-foreground sm:truncate";
18143
- /** Section header label (uppercase tracking-wider). */
18144
- var TEXT_SECTION_LABEL = "text-[10px] sm:text-[11px] font-semibold text-foreground uppercase tracking-wider";
18145
- /** Field label inside a row. */
18146
- var TEXT_FIELD_LABEL = "text-[11px] sm:text-xs text-foreground";
18147
- /** Sub-label / hint text. */
18148
- var TEXT_HINT = "text-[10px] text-foreground-subtle";
18149
- /** Primary value (a metric, badge, …). */
18150
- var TEXT_VALUE = "text-[11px] sm:text-xs font-medium text-foreground";
18151
- /** Big metric value (StatCard). */
18152
- var TEXT_METRIC = "text-base sm:text-lg font-semibold leading-tight text-foreground";
18153
- /** Default compact button. */
18154
- var BTN_COMPACT = "inline-flex items-center justify-center gap-1 rounded border border-border bg-surface px-1.5 py-0.5 sm:px-2 sm:py-1 text-[10px] sm:text-[11px] font-medium text-foreground hover:bg-surface-hover transition-colors disabled:opacity-40";
18155
- /** Primary compact button. */
18156
- var BTN_COMPACT_PRIMARY = "inline-flex items-center justify-center gap-1 rounded border border-primary/40 bg-primary/15 px-1.5 py-0.5 sm:px-2 sm:py-1 text-[10px] sm:text-[11px] font-medium text-primary hover:bg-primary/25 transition-colors disabled:opacity-40";
18157
- /** Danger compact button. */
18158
- var BTN_COMPACT_DANGER = "inline-flex items-center justify-center gap-1 rounded border border-danger/40 bg-danger/10 px-1.5 py-0.5 sm:px-2 sm:py-1 text-[10px] sm:text-[11px] font-medium text-danger hover:bg-danger/20 transition-colors disabled:opacity-40";
18159
- /** Warning compact button (e.g. cancel-drawing). */
18160
- var BTN_COMPACT_WARNING = "inline-flex items-center justify-center gap-1 rounded border border-warning/40 bg-warning/10 px-1.5 py-0.5 sm:px-2 sm:py-1 text-[10px] sm:text-[11px] font-medium text-warning hover:bg-warning/20 transition-colors disabled:opacity-40";
18161
- /** Compact text/select/number input. */
18162
- var INPUT_COMPACT = "w-full rounded border border-border bg-background px-1.5 py-0.5 sm:px-2 sm:py-1 text-[11px] sm:text-xs text-foreground focus:border-primary focus:ring-1 focus:ring-primary/30 outline-none";
18163
- /** Chip-style toggle (zone / class selectors in ZoneRulesEditor). */
18164
- var CHIP_BASE = "inline-flex items-center gap-1 rounded-full border px-1.5 py-0.5 text-[10px] font-medium transition-colors";
18165
- var CHIP_ACTIVE = "border-primary/50 bg-primary/15 text-primary";
18166
- var CHIP_INACTIVE = "border-border bg-surface text-foreground-subtle hover:bg-surface-hover";
18167
- //#endregion
18168
- //#region src/composites/setting-row.tsx
18169
- function SettingRow({ label, children, actions, elements = "plain", className, valueClassName }) {
18170
- const LabelTag = elements === "description" ? "dt" : "span";
18171
- const ValueTag = elements === "description" ? "dd" : "div";
19375
+ function Pagination({ currentPage, totalItems, pageSize, onPageChange, itemNoun = "devices" }) {
19376
+ if (totalItems <= pageSize) return null;
19377
+ const totalPages = Math.ceil(totalItems / pageSize);
19378
+ const startIdx = (currentPage - 1) * pageSize + 1;
19379
+ const endIdx = Math.min(currentPage * pageSize, totalItems);
19380
+ const buttons = computePageButtons(currentPage, totalPages);
18172
19381
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
18173
- "data-setting-row": "",
18174
- className: cn(SETTING_ROW, className),
18175
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LabelTag, {
18176
- "data-setting-row-label": "",
18177
- className: SETTING_ROW_LABEL,
18178
- children: label
18179
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ValueTag, {
18180
- "data-setting-row-value": "",
18181
- className: SETTING_ROW_VALUE,
18182
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
18183
- "data-setting-row-value-text": "",
18184
- className: cn(SETTING_ROW_VALUE_TEXT, valueClassName),
18185
- children
18186
- }), actions !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
18187
- className: "flex shrink-0 items-center gap-0.5",
18188
- children: actions
18189
- })]
19382
+ className: "flex items-center justify-between gap-4 flex-wrap pt-2 text-[11px] text-foreground-subtle",
19383
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
19384
+ "Showing ",
19385
+ startIdx,
19386
+ "–",
19387
+ endIdx,
19388
+ " of ",
19389
+ totalItems,
19390
+ " ",
19391
+ itemNoun
19392
+ ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
19393
+ className: "inline-flex items-center gap-1",
19394
+ children: [
19395
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
19396
+ type: "button",
19397
+ "aria-label": "Previous page",
19398
+ disabled: currentPage === 1,
19399
+ onClick: () => onPageChange(currentPage - 1),
19400
+ className: cn("inline-flex items-center justify-center h-7 w-7 rounded border border-border bg-surface text-foreground-subtle", "hover:text-foreground hover:bg-surface-hover transition-colors", "disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-surface"),
19401
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronLeft, { className: "h-3 w-3" })
19402
+ }),
19403
+ buttons.map((b, i) => {
19404
+ if (b.kind === "ellipsis") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
19405
+ className: "px-1 text-foreground-subtle/60 select-none",
19406
+ children: "…"
19407
+ }, `e-${i}`);
19408
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
19409
+ type: "button",
19410
+ onClick: () => onPageChange(b.n),
19411
+ className: cn("inline-flex items-center justify-center h-7 min-w-7 px-1.5 rounded border text-[10.5px] font-medium transition-colors", b.n === currentPage ? "border-primary/40 bg-primary/10 text-primary" : "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface-hover"),
19412
+ children: b.n
19413
+ }, `p-${b.n}`);
19414
+ }),
19415
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
19416
+ type: "button",
19417
+ "aria-label": "Next page",
19418
+ disabled: currentPage === totalPages,
19419
+ onClick: () => onPageChange(currentPage + 1),
19420
+ className: cn("inline-flex items-center justify-center h-7 w-7 rounded border border-border bg-surface text-foreground-subtle", "hover:text-foreground hover:bg-surface-hover transition-colors", "disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-surface"),
19421
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronRight, { className: "h-3 w-3" })
19422
+ })
19423
+ ]
18190
19424
  })]
18191
19425
  });
18192
19426
  }
18193
19427
  //#endregion
18194
- //#region src/composites/sort-header-button.tsx
18195
- function SortHeaderButton({ label, active, dir, onSort }) {
18196
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
18197
- type: "button",
18198
- onClick: onSort,
18199
- className: "group/sort inline-flex items-center gap-0.5 hover:text-foreground transition-colors",
18200
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }), active ? dir === "asc" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronUp, { className: "h-3 w-3 flex-shrink-0" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDown, { className: "h-3 w-3 flex-shrink-0" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronUp, { className: cn("h-3 w-3 flex-shrink-0 opacity-0 transition-opacity", "group-hover/sort:opacity-30") })]
18201
- });
18202
- }
18203
- //#endregion
18204
- //#region src/composites/table-sort.ts
18205
- /**
18206
- * The toggle rule, and the only copy of it.
18207
- *
18208
- * none → asc → desc → none, and a click on a DIFFERENT column starts that
18209
- * column fresh at `asc`. `sameColumn` says whether the clicked column is the
18210
- * one currently sorted; `currentDir` is its direction (`null` when nothing is
18211
- * sorted). Returns the next direction, or `null` for "unsorted".
18212
- *
18213
- * Split out from {@link nextTableSort} so the device list — whose sort state
18214
- * keys columns by `columnId`, not `columnKey` — can reuse the rule without a
18215
- * cast and without a second copy of it.
18216
- */
18217
- function nextSortDirection(sameColumn, currentDir) {
18218
- if (!sameColumn || currentDir === null) return "asc";
18219
- return currentDir === "asc" ? "desc" : null;
18220
- }
18221
- /**
18222
- * Toggle a column's sort on header click: none → asc → desc → none.
18223
- * Clicking a DIFFERENT column starts it fresh at `asc`.
18224
- */
18225
- function nextTableSort(current, columnKey) {
18226
- const dir = nextSortDirection(current !== null && current.columnKey === columnKey, current === null ? null : current.dir);
18227
- return dir === null ? null : {
18228
- columnKey,
18229
- dir
18230
- };
18231
- }
18232
- /**
18233
- * Ascending comparison of two cell values.
18234
- *
18235
- * Numbers compare numerically and strings compare with `localeCompare` — a
18236
- * numeric column must never be ordered as text ("100" before "9"), and a text
18237
- * column must respect the locale's collation. A mixed column (one row a
18238
- * number, another a string) falls back to string comparison of both, which is
18239
- * the only total order available; it is not a shape any caller should build on
18240
- * purpose.
18241
- *
18242
- * `null` is NOT handled here — {@link sortRowsByColumn} pulls it out first,
18243
- * because its placement is direction-independent and a comparator that is
18244
- * multiplied by the direction factor cannot express that.
18245
- */
18246
- function compareValues(a, b) {
18247
- if (typeof a === "number" && typeof b === "number") return a - b;
18248
- return String(a).localeCompare(String(b));
18249
- }
18250
- /**
18251
- * Return a NEW array of `rows` ordered by `sort`.
18252
- *
18253
- * `valueOf` is the column's `sortValue` — it is called once per row per
18254
- * comparison, so it must be cheap (read a field, do not format a string).
18255
- * `sort === null` ⇒ a fresh shallow copy in the incoming order.
18256
- */
18257
- function sortRowsByColumn(rows, sort, valueOf) {
18258
- if (sort === null) return [...rows];
18259
- const factor = sort.dir === "asc" ? 1 : -1;
18260
- return rows.toSorted((a, b) => {
18261
- const av = valueOf(a);
18262
- const bv = valueOf(b);
18263
- if (av === null || bv === null) {
18264
- if (av === bv) return 0;
18265
- return av === null ? 1 : -1;
18266
- }
18267
- return factor * compareValues(av, bv);
18268
- });
18269
- }
18270
- /** Derive the `<th>` `aria-sort` for `columnKey` from the table's sort state. */
18271
- function ariaSortForColumn(sort, columnKey) {
18272
- if (sort === null || sort.columnKey !== columnKey) return "none";
18273
- return sort.dir === "asc" ? "ascending" : "descending";
18274
- }
18275
- //#endregion
18276
- //#region src/composites/data-table.tsx
18277
- /**
18278
- * DataTable — shared responsive table for admin pages.
18279
- *
18280
- * Recipes baked in:
18281
- * - `overflow-x-auto` wrapper so narrow viewports don't clip the
18282
- * right edge (the rounded border survives the scroll).
18283
- * - `min-w-[Npx]` on the inner table so columns don't squash below
18284
- * readable width before the scroll kicks in.
18285
- * - `whitespace-nowrap` on headers — column titles never wrap to
18286
- * two lines (`AUTO-KILL` was the regression that triggered this).
18287
- * - Themed `bg-surface` + `border-border` so contrast works in both
18288
- * light and dark themes.
18289
- *
18290
- * Column model: caller supplies `columns: DataColumn<T>[]` and
18291
- * `rows: T[]`. Each column declares `header`, optional `align`,
18292
- * optional `nowrap`, and a `render(row) => ReactNode` that produces
18293
- * the cell content.
18294
- *
18295
- * <DataTable
18296
- * columns={[
18297
- * { key: 'username', header: 'Username', render: (u) => u.username },
18298
- * { key: 'role', header: 'Role', render: (u) => <RoleBadge .../> },
18299
- * { key: 'actions', header: '', align: 'right', render: (u) => <Actions .../> },
18300
- * ]}
18301
- * rows={users}
18302
- * minWidthPx={560}
18303
- * />
18304
- *
18305
- * If finer header markup is needed, pass `headerRender` instead of
18306
- * `header` (e.g. for a sortable click affordance).
18307
- *
18308
- * ── Narrow viewports ──────────────────────────────────────────────────────
18309
- * The table stays a table. The wrapper is `overflow-x-auto`, so a phone
18310
- * scrolls the columns sideways INSIDE the table — the page body never moves
18311
- * sideways, and the cells stay one line (`whitespace-nowrap`). That is the
18312
- * default (`mobileMode="scroll"`). Cards are opt-in via `mobileMode="cards"`;
18313
- * `auto` still applies the old column-count rule for a surface that wants it.
18314
- * See `resolveTableLayout`.
18315
- *
18316
- * ── Scrolling the BODY, not the page ──────────────────────────────────────
18317
- * `maxBodyHeight` caps the table and scrolls its rows INSIDE the container
18318
- * with the column header pinned. It is ONE prop, not two, because "this list
18319
- * scrolls internally" and "its header stays visible" are the same decision: a
18320
- * capped table whose header scrolls away is a list of unlabelled cells.
18321
- *
18322
- * Use it wherever the row count is open-ended (rules, targets, delivery
18323
- * history) so the page keeps its own header, filters and actions in view while
18324
- * the operator moves through the rows.
18325
- */
18326
- /**
18327
- * The default cap for an internally-scrolling table.
18328
- *
18329
- * `60vh` leaves the page header, its filters and its actions on screen at
18330
- * every viewport while still showing ~10 rows — and it is a fraction, so it
18331
- * does not need a breakpoint of its own.
18332
- */
18333
- var TABLE_SCROLL_MAX_H = "max-h-[60vh]";
18334
- var ALIGN_CLASS = {
18335
- left: "text-left",
18336
- right: "text-right",
18337
- center: "text-center"
18338
- };
18339
- function DataTable({ columns, rows, rowKey, onRowClick, minWidthPx = 480, emptyMessage, className, bordered = true, rowClassName, mobileMode = "scroll", maxBodyHeight, defaultSort }) {
18340
- const isNarrow = useIsMobile();
18341
- const [sort, setSort] = (0, react$1.useState)(defaultSort ?? null);
18342
- const layout = resolveTableLayout({
18343
- mode: mobileMode,
18344
- columnCount: columns.length,
18345
- isNarrow
18346
- });
18347
- const scrolls = maxBodyHeight !== void 0;
18348
- const sortedColumn = (0, react$1.useMemo)(() => sort === null ? void 0 : columns.find((col) => col.key === sort.columnKey && col.sortValue !== void 0), [columns, sort]);
18349
- const sortValue = sortedColumn?.sortValue;
18350
- const displayRows = (0, react$1.useMemo)(() => sortValue === void 0 ? rows : sortRowsByColumn(rows, sort, sortValue), [
18351
- rows,
18352
- sort,
18353
- sortValue
18354
- ]);
18355
- const activeSort = sortedColumn === void 0 ? null : sort;
18356
- if (rows.length === 0 && emptyMessage) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
18357
- className: `rounded-lg ${bordered ? "border border-border" : ""} bg-surface px-3 py-4 text-xs text-foreground-subtle text-center ${className ?? ""}`,
18358
- children: emptyMessage
18359
- });
18360
- if (rows.length === 0) return null;
18361
- if (layout === "cards") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
18362
- className: `space-y-2 ${scrolls ? `${maxBodyHeight} overflow-y-auto` : ""} ${className ?? ""}`,
18363
- children: displayRows.map((row, rowIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTableCard, {
18364
- row,
18365
- rowIndex,
18366
- columns,
18367
- bordered,
18368
- onRowClick,
18369
- rowClassName
18370
- }, rowKey ? rowKey(row, rowIndex) : rowIndex))
18371
- });
18372
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
18373
- className: [
18374
- "rounded-lg",
18375
- bordered ? "border border-border" : "",
18376
- "bg-surface overflow-x-auto",
18377
- scrolls ? `${maxBodyHeight} overflow-y-auto` : "",
18378
- className ?? ""
18379
- ].filter(Boolean).join(" "),
18380
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
18381
- className: `w-full text-xs ${scrolls ? "border-separate border-spacing-0" : ""}`,
18382
- style: { minWidth: `${minWidthPx}px` },
18383
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", {
18384
- className: "bg-surface",
18385
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: columns.map((col) => {
18386
- const align = col.align ?? "left";
18387
- const nowrap = col.nowrap ?? true;
18388
- const sortable = col.sortValue !== void 0;
18389
- const isSorted = activeSort !== null && activeSort.columnKey === col.key;
18390
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
18391
- "aria-sort": sortable ? ariaSortForColumn(activeSort, col.key) : void 0,
18392
- title: sortable ? void 0 : col.notSortableReason,
18393
- className: [
18394
- "px-3 py-2 text-foreground-subtle font-medium bg-surface border-b border-border",
18395
- scrolls ? "sticky top-0 z-10" : "",
18396
- ALIGN_CLASS[align],
18397
- nowrap ? "whitespace-nowrap" : "",
18398
- col.width ?? ""
18399
- ].filter(Boolean).join(" "),
18400
- children: sortable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SortHeaderButton, {
18401
- label: col.headerRender ? col.headerRender() : col.header,
18402
- active: isSorted,
18403
- dir: isSorted && activeSort !== null ? activeSort.dir : "asc",
18404
- onSort: () => setSort((prev) => nextTableSort(prev, col.key))
18405
- }) : col.headerRender ? col.headerRender() : col.header
18406
- }, col.key);
18407
- }) })
18408
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: displayRows.map((row, rowIndex) => {
18409
- const key = rowKey ? rowKey(row, rowIndex) : rowIndex;
18410
- const extra = rowClassName?.(row, rowIndex) ?? "";
18411
- const interactive = onRowClick !== void 0;
18412
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", {
18413
- onClick: interactive ? () => onRowClick(row, rowIndex) : void 0,
18414
- className: [interactive ? "cursor-pointer hover:bg-primary/5" : "hover:bg-primary/5", extra].filter(Boolean).join(" "),
18415
- children: columns.map((col) => {
18416
- const align = col.align ?? "left";
18417
- const nowrap = col.nowrap ?? true;
18418
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
18419
- className: [
18420
- "px-3 py-2 text-foreground border-b border-border",
18421
- ALIGN_CLASS[align],
18422
- nowrap ? "whitespace-nowrap" : ""
18423
- ].filter(Boolean).join(" "),
18424
- children: col.render(row, rowIndex)
18425
- }, col.key);
18426
- })
18427
- }, key);
18428
- }) })]
18429
- })
18430
- });
18431
- }
18432
- /**
18433
- * One row as a card. Columns that carry a plain-text `header` become labelled
18434
- * `SettingRow`s — the label is the column title and the value gets the full
18435
- * card width. Columns with no text header (an actions column, a
18436
- * `headerRender`-only column) have no label to show, so they render as a
18437
- * full-width strip at the foot of the card.
18438
- */
18439
- function DataTableCard({ row, rowIndex, columns, bordered, onRowClick, rowClassName }) {
18440
- const labelled = columns.filter((col) => col.header !== void 0 && col.header !== "");
18441
- const unlabelled = columns.filter((col) => col.header === void 0 || col.header === "");
18442
- const interactive = onRowClick !== void 0;
18443
- const extra = rowClassName?.(row, rowIndex) ?? "";
18444
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
18445
- "data-data-table-card": "",
18446
- onClick: interactive ? () => onRowClick(row, rowIndex) : void 0,
18447
- className: [
18448
- "rounded-lg bg-surface px-3 py-1.5 divide-y divide-border-subtle",
18449
- bordered ? "border border-border" : "",
18450
- interactive ? "cursor-pointer hover:bg-primary/5" : "",
18451
- extra
18452
- ].filter(Boolean).join(" "),
18453
- children: [labelled.map((col) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingRow, {
18454
- label: col.header,
18455
- children: col.render(row, rowIndex)
18456
- }, col.key)), unlabelled.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
18457
- className: "flex flex-wrap items-center justify-end gap-2 py-1.5",
18458
- children: unlabelled.map((col) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: col.render(row, rowIndex) }, col.key))
18459
- })]
18460
- });
18461
- }
18462
- //#endregion
18463
- //#region src/composites/device-list/pagination.tsx
18464
- var NEIGHBORS = 2;
18465
- function computePageButtons(currentPage, totalPages) {
18466
- if (totalPages <= 1) return [{
18467
- kind: "page",
18468
- n: 1
18469
- }];
18470
- if (totalPages <= 7) {
18471
- const all = [];
18472
- for (let i = 1; i <= totalPages; i++) all.push({
18473
- kind: "page",
18474
- n: i
18475
- });
18476
- return all;
18477
- }
18478
- const result = [];
18479
- const leftBound = Math.max(2, currentPage - NEIGHBORS);
18480
- const rightBound = Math.min(totalPages - 1, currentPage + NEIGHBORS);
18481
- result.push({
18482
- kind: "page",
18483
- n: 1
18484
- });
18485
- if (leftBound > 2) result.push({
18486
- kind: "ellipsis",
18487
- side: "left"
18488
- });
18489
- for (let i = leftBound; i <= rightBound; i++) result.push({
18490
- kind: "page",
18491
- n: i
18492
- });
18493
- if (rightBound < totalPages - 1) result.push({
18494
- kind: "ellipsis",
18495
- side: "right"
18496
- });
18497
- result.push({
18498
- kind: "page",
18499
- n: totalPages
18500
- });
18501
- return result;
18502
- }
18503
- function Pagination({ currentPage, totalItems, pageSize, onPageChange, itemNoun = "devices" }) {
18504
- if (totalItems <= pageSize) return null;
18505
- const totalPages = Math.ceil(totalItems / pageSize);
18506
- const startIdx = (currentPage - 1) * pageSize + 1;
18507
- const endIdx = Math.min(currentPage * pageSize, totalItems);
18508
- const buttons = computePageButtons(currentPage, totalPages);
18509
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
18510
- className: "flex items-center justify-between gap-4 flex-wrap pt-2 text-[11px] text-foreground-subtle",
18511
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
18512
- "Showing ",
18513
- startIdx,
18514
- "–",
18515
- endIdx,
18516
- " of ",
18517
- totalItems,
18518
- " ",
18519
- itemNoun
18520
- ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
18521
- className: "inline-flex items-center gap-1",
18522
- children: [
18523
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
18524
- type: "button",
18525
- "aria-label": "Previous page",
18526
- disabled: currentPage === 1,
18527
- onClick: () => onPageChange(currentPage - 1),
18528
- className: cn("inline-flex items-center justify-center h-7 w-7 rounded border border-border bg-surface text-foreground-subtle", "hover:text-foreground hover:bg-surface-hover transition-colors", "disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-surface"),
18529
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronLeft, { className: "h-3 w-3" })
18530
- }),
18531
- buttons.map((b, i) => {
18532
- if (b.kind === "ellipsis") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
18533
- className: "px-1 text-foreground-subtle/60 select-none",
18534
- children: "…"
18535
- }, `e-${i}`);
18536
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
18537
- type: "button",
18538
- onClick: () => onPageChange(b.n),
18539
- className: cn("inline-flex items-center justify-center h-7 min-w-7 px-1.5 rounded border text-[10.5px] font-medium transition-colors", b.n === currentPage ? "border-primary/40 bg-primary/10 text-primary" : "border-border bg-surface text-foreground-subtle hover:text-foreground hover:bg-surface-hover"),
18540
- children: b.n
18541
- }, `p-${b.n}`);
18542
- }),
18543
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
18544
- type: "button",
18545
- "aria-label": "Next page",
18546
- disabled: currentPage === totalPages,
18547
- onClick: () => onPageChange(currentPage + 1),
18548
- className: cn("inline-flex items-center justify-center h-7 w-7 rounded border border-border bg-surface text-foreground-subtle", "hover:text-foreground hover:bg-surface-hover transition-colors", "disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-surface"),
18549
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronRight, { className: "h-3 w-3" })
18550
- })
18551
- ]
18552
- })]
18553
- });
18554
- }
18555
- //#endregion
18556
- //#region src/composites/empty-state.tsx
18557
- function EmptyState({ icon: Icon, title, description, action, className }) {
18558
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
18559
- className: cn("flex flex-col items-center justify-center gap-3 py-12", className),
18560
- children: [
18561
- Icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
18562
- className: "h-12 w-12 text-foreground-subtle",
18563
- "aria-hidden": "true"
18564
- }),
18565
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
18566
- className: "flex flex-col items-center gap-1 text-center",
18567
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
18568
- className: "text-foreground-muted text-sm font-medium",
18569
- children: title
18570
- }), description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
18571
- className: "text-foreground-subtle text-xs max-w-xs",
18572
- children: description
18573
- })]
18574
- }),
18575
- action && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
18576
- className: "mt-1",
18577
- children: action
18578
- })
18579
- ]
19428
+ //#region src/composites/empty-state.tsx
19429
+ function EmptyState({ icon: Icon, title, description, action, className }) {
19430
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
19431
+ className: cn("flex flex-col items-center justify-center gap-3 py-12", className),
19432
+ children: [
19433
+ Icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
19434
+ className: "h-12 w-12 text-foreground-subtle",
19435
+ "aria-hidden": "true"
19436
+ }),
19437
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
19438
+ className: "flex flex-col items-center gap-1 text-center",
19439
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
19440
+ className: "text-foreground-muted text-sm font-medium",
19441
+ children: title
19442
+ }), description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
19443
+ className: "text-foreground-subtle text-xs max-w-xs",
19444
+ children: description
19445
+ })]
19446
+ }),
19447
+ action && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
19448
+ className: "mt-1",
19449
+ children: action
19450
+ })
19451
+ ]
18580
19452
  });
18581
19453
  }
18582
19454
  //#endregion
@@ -19047,6 +19919,38 @@ function ClipPanel({ deviceId, clip, profile, onProfileChange }) {
19047
19919
  });
19048
19920
  }
19049
19921
  //#endregion
19922
+ //#region src/primitives/badge.tsx
19923
+ var badgeVariants = cva("inline-flex items-center rounded-full text-xs font-medium px-2 py-0.5", {
19924
+ variants: {
19925
+ variant: {
19926
+ default: "bg-surface-hover text-foreground",
19927
+ success: "bg-success/15 text-success",
19928
+ warning: "bg-warning/15 text-warning",
19929
+ danger: "bg-danger/15 text-danger",
19930
+ info: "bg-info/15 text-info"
19931
+ },
19932
+ styleVariant: {
19933
+ solid: "",
19934
+ outline: "border bg-transparent"
19935
+ }
19936
+ },
19937
+ defaultVariants: {
19938
+ variant: "default",
19939
+ styleVariant: "solid"
19940
+ }
19941
+ });
19942
+ var Badge = (0, react$1.forwardRef)(({ className, variant, style, ...props }, ref) => {
19943
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
19944
+ ref,
19945
+ className: cn(badgeVariants({
19946
+ variant,
19947
+ styleVariant: style
19948
+ }), className),
19949
+ ...props
19950
+ });
19951
+ });
19952
+ Badge.displayName = "Badge";
19953
+ //#endregion
19050
19954
  //#region src/lib/format-duration.ts
19051
19955
  /**
19052
19956
  * How long something lasted, the way this product says it.
@@ -19560,24 +20464,6 @@ function describeSource(source) {
19560
20464
  answered: copy.answered
19561
20465
  };
19562
20466
  }
19563
- var MINUTE_MS$1 = 6e4;
19564
- var HOUR_MS$1 = 36e5;
19565
- var DAY_MS$2 = 864e5;
19566
- /**
19567
- * How old a source's catalog is, or null when it did not say.
19568
- *
19569
- * Null is NOT "now". A source that omits `catalogAsOf` was read live and has
19570
- * no age to show; inventing "just now" for a source that never told us would
19571
- * be the same lie as drawing a cached list as current (D549 decision 13).
19572
- */
19573
- function formatCatalogAge(catalogAsOf, now) {
19574
- if (catalogAsOf === void 0) return null;
19575
- const age = Math.max(0, now - catalogAsOf);
19576
- if (age < MINUTE_MS$1) return "under a minute ago";
19577
- if (age < HOUR_MS$1) return `${String(Math.floor(age / MINUTE_MS$1))} min ago`;
19578
- if (age < DAY_MS$2) return `${String(Math.floor(age / HOUR_MS$1))} h ago`;
19579
- return `${String(Math.floor(age / DAY_MS$2))} d ago`;
19580
- }
19581
20467
  //#endregion
19582
20468
  //#region src/composites/clips/clip-view-state.ts
19583
20469
  function resolveClipsView(input) {
@@ -19731,56 +20617,6 @@ function resolveClipsView(input) {
19731
20617
  var CLIP_PAGE_LIMIT = 500;
19732
20618
  /** Tiles per page. The rest are a `Pagination` click away, never dropped. */
19733
20619
  var TILES_PER_PAGE = 60;
19734
- var TONE_BADGE = {
19735
- ok: "success",
19736
- warn: "warning",
19737
- error: "danger"
19738
- };
19739
- var SOURCE_COLUMNS = [
19740
- {
19741
- key: "source",
19742
- header: "Source",
19743
- render: (row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
19744
- className: "min-w-0",
19745
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
19746
- className: "font-medium text-foreground",
19747
- children: row.notice.label
19748
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
19749
- className: "text-[10px] text-foreground-subtle",
19750
- children: row.notice.source
19751
- })]
19752
- })
19753
- },
19754
- {
19755
- key: "state",
19756
- header: "State",
19757
- nowrap: false,
19758
- render: (row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
19759
- variant: TONE_BADGE[row.notice.tone],
19760
- children: row.notice.headline
19761
- })
19762
- },
19763
- {
19764
- key: "reason",
19765
- header: "Reason",
19766
- nowrap: false,
19767
- render: (row) => row.notice.detail === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
19768
- className: "text-foreground-subtle",
19769
- children: "—"
19770
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
19771
- className: "break-words",
19772
- children: row.notice.detail
19773
- })
19774
- },
19775
- {
19776
- key: "read",
19777
- header: "Catalog read",
19778
- render: (row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
19779
- className: "text-foreground-subtle",
19780
- children: row.catalogAge ?? "live"
19781
- })
19782
- }
19783
- ];
19784
20620
  /** The sources that contributed NOTHING to what is on screen, and why. */
19785
20621
  function SilentSources({ silent }) {
19786
20622
  if (silent.length === 0) return null;
@@ -19867,7 +20703,6 @@ function ClipsSection({ deviceId }) {
19867
20703
  ...clips.error ? { clipsError: clips.error.message } : {},
19868
20704
  ...sources.error ? { sourcesError: sources.error.message } : {}
19869
20705
  });
19870
- const now = Date.now();
19871
20706
  const options = (0, react$1.useMemo)(() => {
19872
20707
  const byAddon = /* @__PURE__ */ new Map();
19873
20708
  for (const row of sourceRows) {
@@ -19882,10 +20717,6 @@ function ClipsSection({ deviceId }) {
19882
20717
  label: labels.join(" · ")
19883
20718
  }));
19884
20719
  }, [sourceRows]);
19885
- const tableRows = (0, react$1.useMemo)(() => sourceRows.map((row) => ({
19886
- notice: describeSource(row),
19887
- catalogAge: formatCatalogAge(row.availability.catalogAsOf, now)
19888
- })), [sourceRows]);
19889
20720
  (0, react$1.useEffect)(() => {
19890
20721
  setPage(1);
19891
20722
  }, [band.fromMs, band.toMs]);
@@ -19991,19 +20822,6 @@ function ClipsSection({ deviceId }) {
19991
20822
  onSelectBucket: handleSelectBucket,
19992
20823
  densityLoading: density.status === "pending"
19993
20824
  }),
19994
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
19995
- "aria-label": "Clip sources",
19996
- className: "space-y-1",
19997
- children: sources.status === "pending" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, { className: "h-16 w-full" }) : tableRows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptyState, {
19998
- icon: Film,
19999
- title: "No provider answered for this camera",
20000
- description: "Nothing claims to hold clips for it."
20001
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTable, {
20002
- columns: SOURCE_COLUMNS,
20003
- rows: [...tableRows],
20004
- minWidthPx: 520
20005
- })
20006
- }),
20007
20825
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
20008
20826
  "aria-label": "Clips",
20009
20827
  className: "space-y-2",
@@ -22977,7 +23795,7 @@ function RecordingPanel({ deviceId }) {
22977
23795
  * Module-Federation `WidgetRegistry`. ids are namespaced `host/<name>`. */
22978
23796
  var HOST_WIDGETS = {
22979
23797
  "host/motion-zones-grid": (0, react$1.lazy)(() => Promise.resolve().then(() => require("./MotionZonesSettings-BK9R9NIr.cjs")).then((m) => ({ default: m.MotionZonesSettings }))),
22980
- "host/privacy-mask-grid": (0, react$1.lazy)(() => Promise.resolve().then(() => require("./PrivacyMaskSettings-DfZAFg32.cjs")).then((m) => ({ default: m.PrivacyMaskSettings }))),
23798
+ "host/privacy-mask-grid": (0, react$1.lazy)(() => Promise.resolve().then(() => require("./PrivacyMaskSettings-BqjXsXZR.cjs")).then((m) => ({ default: m.PrivacyMaskSettings }))),
22981
23799
  "host/ptz-panel": PtzPanel,
22982
23800
  "host/navigation-panel": NavigationPanel,
22983
23801
  "host/consumables-panel": ConsumablesPanel,
@@ -27682,6 +28500,53 @@ function TooltipContent({ className, children, ...props }) {
27682
28500
  });
27683
28501
  }
27684
28502
  //#endregion
28503
+ //#region src/hooks/use-is-mobile.ts
28504
+ var MOBILE_QUERY = "(max-width: 767px)";
28505
+ /**
28506
+ * Mid breakpoint — below this width the sidebar collapses to an
28507
+ * icons-only rail to reclaim canvas space. Above it (and outside
28508
+ * mobile) the full sidebar renders. Threshold matches Tailwind `lg`
28509
+ * so sidebar fans out at the same point grids switch to multi-column.
28510
+ */
28511
+ var MID_QUERY = "(min-width: 768px) and (max-width: 1023px)";
28512
+ /**
28513
+ * `matchMedia` is universal in browsers but absent in bare DOM environments.
28514
+ * These hooks are called from shared primitives (`DataTable` picks its narrow
28515
+ * layout with one), so throwing here takes down the whole page that merely
28516
+ * rendered a table. Where the capability is missing, report the desktop
28517
+ * layout — a widescreen rendering is wrong-looking; a crashed page is gone.
28518
+ */
28519
+ function matchQuery(query) {
28520
+ if (typeof window.matchMedia !== "function") return null;
28521
+ return window.matchMedia(query);
28522
+ }
28523
+ function subscribeQuery(query) {
28524
+ return (callback) => {
28525
+ const mql = matchQuery(query);
28526
+ if (mql === null) return () => {};
28527
+ mql.addEventListener("change", callback);
28528
+ return () => mql.removeEventListener("change", callback);
28529
+ };
28530
+ }
28531
+ function getSnapshot(query) {
28532
+ return () => matchQuery(query)?.matches ?? false;
28533
+ }
28534
+ function getServerSnapshot() {
28535
+ return false;
28536
+ }
28537
+ function useIsMobile() {
28538
+ return (0, react$1.useSyncExternalStore)(subscribeQuery(MOBILE_QUERY), getSnapshot(MOBILE_QUERY), getServerSnapshot);
28539
+ }
28540
+ /**
28541
+ * True when the viewport is in the medium (`md`) Tailwind range —
28542
+ * 768px ≤ width < 1024px. Use to drive layouts that should pack denser
28543
+ * than full-desktop but more legibly than mobile (e.g. icons-only
28544
+ * sidebar rail, narrower stat tiles).
28545
+ */
28546
+ function useIsMidWidth() {
28547
+ return (0, react$1.useSyncExternalStore)(subscribeQuery(MID_QUERY), getSnapshot(MID_QUERY), getServerSnapshot);
28548
+ }
28549
+ //#endregion
27685
28550
  //#region src/primitives/bottom-sheet.tsx
27686
28551
  function BottomSheet({ open, onClose, title, children, className }) {
27687
28552
  (0, react$1.useEffect)(() => {
@@ -30830,290 +31695,916 @@ function ToolbarButton$1({ onClick, title, children }) {
30830
31695
  children
30831
31696
  });
30832
31697
  }
30833
- async function waitForIceGathering(pc) {
30834
- if (pc.iceGatheringState === "complete") return;
30835
- return new Promise((resolve) => {
30836
- let settled = false;
30837
- let poll;
30838
- const finish = () => {
30839
- if (settled) return;
30840
- settled = true;
30841
- if (poll) clearInterval(poll);
30842
- pc.removeEventListener("icegatheringstatechange", handler);
30843
- resolve();
30844
- };
30845
- const handler = () => {
30846
- if (pc.iceGatheringState === "complete") finish();
30847
- };
30848
- pc.addEventListener("icegatheringstatechange", handler);
30849
- const start = Date.now();
30850
- poll = setInterval(() => {
30851
- const sdp = pc.localDescription?.sdp ?? "";
30852
- if (/ typ host/.test(sdp) && Date.now() - start >= 600) finish();
30853
- }, 100);
30854
- setTimeout(finish, 2500);
31698
+ async function waitForIceGathering(pc) {
31699
+ if (pc.iceGatheringState === "complete") return;
31700
+ return new Promise((resolve) => {
31701
+ let settled = false;
31702
+ let poll;
31703
+ const finish = () => {
31704
+ if (settled) return;
31705
+ settled = true;
31706
+ if (poll) clearInterval(poll);
31707
+ pc.removeEventListener("icegatheringstatechange", handler);
31708
+ resolve();
31709
+ };
31710
+ const handler = () => {
31711
+ if (pc.iceGatheringState === "complete") finish();
31712
+ };
31713
+ pc.addEventListener("icegatheringstatechange", handler);
31714
+ const start = Date.now();
31715
+ poll = setInterval(() => {
31716
+ const sdp = pc.localDescription?.sdp ?? "";
31717
+ if (/ typ host/.test(sdp) && Date.now() - start >= 600) finish();
31718
+ }, 100);
31719
+ setTimeout(finish, 2500);
31720
+ });
31721
+ }
31722
+ //#endregion
31723
+ //#region src/composites/cap-settings/scene-monitor-copy.ts
31724
+ /** The lighting variants an operator is OFFERED. The resolver can also report
31725
+ * `night`, `dawn` and `dusk`; those are shown when present but never demanded,
31726
+ * because most installs only ever see day and IR. */
31727
+ var SCENE_CAPTURE_VARIANTS = ["day", "ir"];
31728
+ var VARIANT_LABEL = {
31729
+ day: "Daylight",
31730
+ ir: "Night (IR)",
31731
+ night: "Night",
31732
+ dawn: "Dawn",
31733
+ dusk: "Dusk"
31734
+ };
31735
+ function variantLabel(condition) {
31736
+ return VARIANT_LABEL[condition] ?? condition;
31737
+ }
31738
+ /**
31739
+ * "Daylight ✓ · Night (IR) ✗ — optional; checks in night (ir) are skipped."
31740
+ *
31741
+ * Converts the silent cross-condition fallback this engine used to have into an
31742
+ * operator-visible fact. Without it, a scene with only a daylight reference
31743
+ * simply stops answering after sunset and nothing anywhere says why.
31744
+ *
31745
+ * It used to end *"reports 'can't tell' in night (ir)"*, which was accurate and
31746
+ * read as a defect: the operator saw a ✗ and a complaint for a scene that was
31747
+ * configured exactly as they meant it. The missing half is now named as what it
31748
+ * is — the part you do not have to do — and the sentence says what actually
31749
+ * happens instead, which differs by the scene's own policy.
31750
+ */
31751
+ function coverageLine(monitor) {
31752
+ const covered = new Set(monitor.coveredConditions);
31753
+ const parts = SCENE_CAPTURE_VARIANTS.map((v) => `${variantLabel(v)} ${covered.has(v) ? "✓" : "✗"}`);
31754
+ const extra = [...covered].filter((c) => !SCENE_CAPTURE_VARIANTS.some((v) => v === c));
31755
+ for (const c of extra) parts.push(`${variantLabel(c)} ✓`);
31756
+ const missing = SCENE_CAPTURE_VARIANTS.filter((v) => !covered.has(v));
31757
+ if (missing.length === 0) return parts.join(" · ");
31758
+ if (covered.size === 0) return `${parts.join(" · ")} — nothing captured yet, so this scene is not watching.`;
31759
+ const names = missing.map((v) => variantLabel(v).toLowerCase()).join(" and ");
31760
+ return monitor.onUncoveredCondition === "judge-anyway" ? `${parts.join(" · ")} — ${names} judged against the references you do have.` : `${parts.join(" · ")} — optional; checks in ${names} are skipped.`;
31761
+ }
31762
+ /**
31763
+ * The calm sentence for a scene that is sitting the current light out — `null`
31764
+ * whenever it is checking normally.
31765
+ *
31766
+ * Separate from {@link statusLine} because it is not a verdict and must not
31767
+ * read like one. A paused scene has not failed, has not lost its flag, and
31768
+ * needs nothing from the operator; the only thing worth saying is which light
31769
+ * it is waiting out and that it will pick itself back up.
31770
+ */
31771
+ function suspensionLine(monitor) {
31772
+ if (monitor.suspendedCondition === null) return null;
31773
+ const light = variantLabel(monitor.suspendedCondition).toLowerCase();
31774
+ return `Paused — nothing was captured in ${light}, so checks are on hold until the light changes back. Capture ${light} if you want it watched around the clock.`;
31775
+ }
31776
+ var UNAVAILABLE_SENTENCE = {
31777
+ "no-reference-for-condition": "can’t tell — nothing has been captured for this scene yet. Press “Capture daylight” to photograph what normal looks like.",
31778
+ "view-shifted": "can’t tell — the camera view has moved since the reference was captured. Re-capture it.",
31779
+ "no-vision-profile": "can’t tell — the vision model is unreachable, so the change is on hold.",
31780
+ "encoder-model-changed": "can’t tell — the embedding model changed, so the stored references are unreadable. Re-capture them.",
31781
+ "no-snapshot": "can’t tell — this camera did not return a frame."
31782
+ };
31783
+ /**
31784
+ * The one line under a scene's name. It answers, in order: can it judge, what
31785
+ * does it say, and how long has it said it.
31786
+ */
31787
+ function statusLine(monitor, now) {
31788
+ if (monitor.states.length === 0) return "Not armed yet — capture the initial state to start watching.";
31789
+ if (monitor.verdict === "unknown") return monitor.unavailable !== null ? UNAVAILABLE_SENTENCE[monitor.unavailable] : "can’t tell right now.";
31790
+ const since = monitor.verdict === "diverged" ? monitor.divergedAt : monitor.armedAt;
31791
+ const held = since !== null ? ` for ${humanDuration(Math.max(0, now - since))}` : "";
31792
+ if (monitor.latched) return monitor.verdict === "diverged" ? `CHANGED${held} — press Reset when you have dealt with it.` : `Back to normal, but still flagged as changed${held}. Press Reset to clear it.`;
31793
+ const paused = suspensionLine(monitor);
31794
+ if (paused !== null) return paused;
31795
+ return monitor.verdict === "diverged" ? `CHANGED${held}` : `Normal${held}`;
31796
+ }
31797
+ /** The badge word. `latched` wins over `verdict`, because the latch is the
31798
+ * thing the operator asked to be told and it outlives the live state.
31799
+ *
31800
+ * A scene sitting out an uncovered light reads `Paused`, in the NEUTRAL tone —
31801
+ * never `alarm`, and never the `danger` variant the page maps that to. A
31802
+ * missing IR reference must not be able to paint a red badge on a scene that
31803
+ * is doing exactly what it was configured to do. */
31804
+ function badgeFor(monitor) {
31805
+ if (monitor.states.length === 0) return {
31806
+ text: "Not armed",
31807
+ tone: "unknown"
31808
+ };
31809
+ if (monitor.latched) return {
31810
+ text: "Changed",
31811
+ tone: "alarm"
31812
+ };
31813
+ if (monitor.suspendedCondition !== null) return {
31814
+ text: "Paused",
31815
+ tone: "unknown"
31816
+ };
31817
+ if (monitor.verdict === "unknown") return {
31818
+ text: "Can’t tell",
31819
+ tone: "unknown"
31820
+ };
31821
+ return monitor.verdict === "diverged" ? {
31822
+ text: "Changed",
31823
+ tone: "alarm"
31824
+ } : {
31825
+ text: "Normal",
31826
+ tone: "ok"
31827
+ };
31828
+ }
31829
+ /** A rect covering most of the frame is dominated by weather, sky and parked
31830
+ * cars: the bin is 2 % of the pixels and CLIP will not notice it leaving. The
31831
+ * failure is silent INSENSITIVITY, which is the worst kind — hence a warning
31832
+ * rather than a refusal. */
31833
+ var ROI_AREA_WARN_FRACTION = .4;
31834
+ function roiTooLarge(roi) {
31835
+ return roi.width * roi.height > ROI_AREA_WARN_FRACTION;
31836
+ }
31837
+ function humanDuration(ms) {
31838
+ const s = Math.floor(ms / 1e3);
31839
+ if (s < 60) return `${s}s`;
31840
+ const m = Math.floor(s / 60);
31841
+ if (m < 60) return `${m}m`;
31842
+ const h = Math.floor(m / 60);
31843
+ if (h < 48) return `${h}h`;
31844
+ return `${Math.floor(h / 24)}d`;
31845
+ }
31846
+ //#endregion
31847
+ //#region src/composites/code-block.tsx
31848
+ function CodeBlock({ children, maxHeight = 300, className }) {
31849
+ const [copied, setCopied] = (0, react$1.useState)(false);
31850
+ const handleCopy = (0, react$1.useCallback)(() => {
31851
+ navigator.clipboard.writeText(children).then(() => {
31852
+ setCopied(true);
31853
+ setTimeout(() => setCopied(false), 2e3);
31854
+ });
31855
+ }, [children]);
31856
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
31857
+ className: cn("relative group", className),
31858
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollArea, {
31859
+ style: { maxHeight },
31860
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
31861
+ className: "font-mono text-xs bg-surface p-3 rounded-md border border-border-subtle",
31862
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children })
31863
+ })
31864
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
31865
+ className: "absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity",
31866
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
31867
+ icon: copied ? Check : Copy,
31868
+ "aria-label": "Copy code",
31869
+ variant: "ghost",
31870
+ size: "sm",
31871
+ onClick: handleCopy
31872
+ })
31873
+ })]
31874
+ });
31875
+ }
31876
+ //#endregion
31877
+ //#region src/composites/confirm-action-button.tsx
31878
+ /**
31879
+ * ConfirmActionButton — destructive-action button with a modal confirm.
31880
+ *
31881
+ * Two-step UX for any operation operators shouldn't trigger by accident:
31882
+ * reboot device, restart addon, regenerate token, etc. The trigger is a
31883
+ * normal Button that opens a Dialog; confirming runs the async action,
31884
+ * the trigger spinner-disables itself for the duration, and the dialog
31885
+ * closes on success. Errors surface inline at the dialog's bottom.
31886
+ *
31887
+ * Generic on `TResult` so callers don't have to discard the return
31888
+ * value. Pass an `icon` to render it inside the trigger (e.g. RotateCw
31889
+ * for reboot, RefreshCw for restart).
31890
+ */
31891
+ function ConfirmActionButton({ label, icon: Icon, title = "Confirm action", description, confirmLabel, triggerVariant = "outline", confirmVariant = "danger", size = "sm", disabled, className, action, onSuccess }) {
31892
+ const [open, setOpen] = (0, react$1.useState)(false);
31893
+ const [running, setRunning] = (0, react$1.useState)(false);
31894
+ const [error, setError] = (0, react$1.useState)(null);
31895
+ const handleConfirm = async () => {
31896
+ setError(null);
31897
+ setRunning(true);
31898
+ try {
31899
+ const result = await action();
31900
+ onSuccess?.(result);
31901
+ setOpen(false);
31902
+ } catch (err) {
31903
+ setError(err instanceof Error ? err.message : String(err));
31904
+ } finally {
31905
+ setRunning(false);
31906
+ }
31907
+ };
31908
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
31909
+ type: "button",
31910
+ variant: triggerVariant,
31911
+ size,
31912
+ disabled,
31913
+ onClick: () => {
31914
+ setError(null);
31915
+ setOpen(true);
31916
+ },
31917
+ className,
31918
+ children: [Icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { className: "h-3.5 w-3.5" }), label]
31919
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dialog, {
31920
+ open,
31921
+ onOpenChange: (next) => !running && setOpen(next),
31922
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogContent, { children: [
31923
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogTitle, {
31924
+ className: "flex items-center gap-2",
31925
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TriangleAlert, { className: "h-4 w-4 text-amber-400" }), title]
31926
+ }), typeof description === "string" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogDescription, { children: description }) : description] }),
31927
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
31928
+ className: cn("mt-2 px-3 py-2 rounded border border-danger/40 bg-danger/10", "text-xs text-danger"),
31929
+ children: error
31930
+ }),
31931
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogFooter, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
31932
+ type: "button",
31933
+ variant: "ghost",
31934
+ size,
31935
+ disabled: running,
31936
+ onClick: () => setOpen(false),
31937
+ children: "Cancel"
31938
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
31939
+ type: "button",
31940
+ variant: confirmVariant,
31941
+ size,
31942
+ disabled: running,
31943
+ onClick: () => {
31944
+ handleConfirm();
31945
+ },
31946
+ children: [running && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin" }), confirmLabel ?? label]
31947
+ })] })
31948
+ ] })
31949
+ })] });
31950
+ }
31951
+ //#endregion
31952
+ //#region src/composites/copy-button.tsx
31953
+ /**
31954
+ * `CopyButton` — a compact button that copies a string `value` to the
31955
+ * clipboard via `navigator.clipboard.writeText`, showing a brief
31956
+ * "copied" check state. Addon-agnostic; used wherever a secret / URL
31957
+ * needs a one-click copy affordance (export setup panels, etc.).
31958
+ */
31959
+ var COPIED_RESET_MS = 2e3;
31960
+ function CopyButton({ value, label, srLabel, className, disabled }) {
31961
+ const [copied, setCopied] = (0, react$1.useState)(false);
31962
+ const handleCopy = (0, react$1.useCallback)(() => {
31963
+ if (!value) return;
31964
+ navigator.clipboard.writeText(value).then(() => {
31965
+ setCopied(true);
31966
+ setTimeout(() => setCopied(false), COPIED_RESET_MS);
31967
+ });
31968
+ }, [value]);
31969
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
31970
+ size: "sm",
31971
+ variant: "ghost",
31972
+ type: "button",
31973
+ disabled: disabled || value.length === 0,
31974
+ onClick: handleCopy,
31975
+ className: cn(className),
31976
+ "aria-label": copied ? "Copied" : `Copy ${srLabel ?? label ?? "value"}`,
31977
+ children: [copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Check, { className: "h-3.5 w-3.5 text-success" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Copy, { className: "h-3.5 w-3.5" }), label ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
31978
+ className: "ml-1",
31979
+ children: copied ? "Copied" : label
31980
+ }) : null]
31981
+ });
31982
+ }
31983
+ //#endregion
31984
+ //#region src/composites/data-table-layout.ts
31985
+ /**
31986
+ * At this many columns and above, `auto` switches a narrow viewport to cards.
31987
+ * Three columns still fit a phone at a readable size; four do not.
31988
+ */
31989
+ var CARD_MODE_MIN_COLUMNS = 4;
31990
+ function resolveTableLayout({ mode, columnCount, isNarrow }) {
31991
+ if (!isNarrow) return "table";
31992
+ if (mode === "scroll") return "table";
31993
+ if (mode === "cards") return "cards";
31994
+ if (columnCount === 0) return "table";
31995
+ return columnCount >= 4 ? "cards" : "table";
31996
+ }
31997
+ //#endregion
31998
+ //#region src/lib/responsive.ts
31999
+ /**
32000
+ * Responsive design tokens — three-tier system.
32001
+ *
32002
+ * The admin-ui targets three width tiers; every component should pick
32003
+ * the right token from this file instead of hand-coding breakpoints.
32004
+ * Adopting the tokens uniformly is what makes the app feel consistent
32005
+ * across operator devices (mobile / tablet / desktop) and what unlocks
32006
+ * targeted tuning (e.g. shrink every label by 1px in one place).
32007
+ *
32008
+ * Tier definitions (Tailwind breakpoints):
32009
+ *
32010
+ * • **L1 — Compact** (`< 640px`, default = mobile portrait)
32011
+ * Single column, tight padding, smallest text, icon-only buttons
32012
+ * where labels can be inferred. The operator can still see every
32013
+ * stat / action — no horizontal scroll, no clipped text. The user
32014
+ * directive: "molta informazione in poco spazio quando la width è
32015
+ * bassa."
32016
+ *
32017
+ * • **L2 — Standard** (`sm` — `640–1023px`, tablet + small laptop)
32018
+ * 2-column grids, side panels become visible, button labels return.
32019
+ * Targets ~768px–1024px viewports — the most common operator sizes.
32020
+ *
32021
+ * • **L3 — Spacious** (`lg+` — `≥ 1024px`, desktop)
32022
+ * 4-column grids, generous padding, full-width header text,
32023
+ * side-by-side composite layouts. The operator has elbow room.
32024
+ *
32025
+ * Conventions:
32026
+ * - **Always start from L1** — the bare class is always the compact
32027
+ * variant, then `sm:` adds room at 640px, `lg:` at 1024px. No
32028
+ * desktop-first breakpoints; no max-width queries.
32029
+ * - **Token strings are static** — kept as `as const` so callers can
32030
+ * compose them via `cn()` / template literals without running a
32031
+ * class generator at runtime.
32032
+ * - **Pair the token with a comment** — the comment in the file
32033
+ * names every breakpoint it crosses, so a reader can predict the
32034
+ * layout without going to Tailwind docs.
32035
+ */
32036
+ /** Container padding for a card-style section (header + body). */
32037
+ var SECTION_CARD = "rounded-lg border border-border bg-surface overflow-hidden";
32038
+ /** Header strip inside a card (between border-b lines). */
32039
+ var SECTION_HEADER = "border-b border-border px-3 sm:px-4 py-1.5 sm:py-2";
32040
+ /** Body padding for a card-style section. */
32041
+ var SECTION_BODY = "p-2 sm:p-3 lg:p-4";
32042
+ /** Gap between cards in a vertical stack. */
32043
+ var STACK_GAP = "space-y-3 sm:space-y-4";
32044
+ /** Gap between cards in a grid (4 cells / row at lg). */
32045
+ var GRID_GAP = "gap-2 sm:gap-3";
32046
+ /** Row padding inside a divided list (e.g. InfoRow). */
32047
+ var LIST_ROW = "px-3 sm:px-4 py-1.5 sm:py-2";
32048
+ /** 1-up at L1, 2-up at L2, 4-up at L3 — quick-stat row. */
32049
+ var GRID_QUICK_STATS = "grid grid-cols-2 sm:grid-cols-2 lg:grid-cols-4";
32050
+ /** 1-up at L1, 2-up at L2+ — paired panels (motion + detection rules). */
32051
+ var GRID_PAIRED = "grid grid-cols-1 lg:grid-cols-2";
32052
+ /** Side-by-side layout that stacks below md (canvas + side panel). */
32053
+ var SPLIT_PANEL_OUTER = "flex flex-col-reverse md:flex-row";
32054
+ /** Sidebar narrow lane in a SPLIT_PANEL_OUTER layout. */
32055
+ var SPLIT_PANEL_SIDE = "w-full md:w-44 lg:w-52 md:flex-shrink-0 border-b md:border-b-0 md:border-l border-border";
32056
+ /**
32057
+ * The tier boundary at which a settings label/value row stops stacking.
32058
+ *
32059
+ * Below it (L1 — compact) the row is ONE column: label on its own line, value
32060
+ * on the next at full width. Above it the row is label-left / value-right as
32061
+ * before. The two-column form needs `SETTING_ROW_LABEL`'s lane plus a readable
32062
+ * value beside it; a phone has neither, and the failure mode is not "cramped"
32063
+ * but "the value is the part that gets cut" — the one thing the operator
32064
+ * opened the page to read.
32065
+ */
32066
+ var SETTING_ROW_STACK_BREAKPOINT = "sm";
32067
+ /** Outer row: single column at L1, label/value columns from `sm` up. */
32068
+ var SETTING_ROW = "flex min-w-0 flex-col gap-0.5 py-1.5 sm:flex-row sm:items-center";
32069
+ /** Label lane. Full width (and free to wrap) at L1; a fixed lane from `sm` up. */
32070
+ var SETTING_ROW_LABEL = "min-w-0 text-[11px] leading-tight text-foreground-subtle sm:w-32 sm:shrink-0 sm:pr-2";
32071
+ /**
32072
+ * Value lane — the value text plus its trailing affordances (copy / reveal).
32073
+ * `w-full` at L1 so the value owns the whole row once the label is above it;
32074
+ * `sm:flex-1` makes it share the row again from `sm` up.
32075
+ */
32076
+ var SETTING_ROW_VALUE = "flex w-full min-w-0 items-center gap-1 sm:flex-1";
32077
+ /**
32078
+ * The value text itself. `break-all` lets an opaque token (a key, a URL) wrap
32079
+ * at L1 instead of being cut mid-word; `sm:truncate` restores the single-line
32080
+ * desktop form, where the row is wide enough for truncation to be a choice
32081
+ * rather than data loss.
32082
+ */
32083
+ var SETTING_ROW_VALUE_TEXT = "min-w-0 break-all text-xs text-foreground sm:truncate";
32084
+ /** Section header label (uppercase tracking-wider). */
32085
+ var TEXT_SECTION_LABEL = "text-[10px] sm:text-[11px] font-semibold text-foreground uppercase tracking-wider";
32086
+ /** Field label inside a row. */
32087
+ var TEXT_FIELD_LABEL = "text-[11px] sm:text-xs text-foreground";
32088
+ /** Sub-label / hint text. */
32089
+ var TEXT_HINT = "text-[10px] text-foreground-subtle";
32090
+ /** Primary value (a metric, badge, …). */
32091
+ var TEXT_VALUE = "text-[11px] sm:text-xs font-medium text-foreground";
32092
+ /** Big metric value (StatCard). */
32093
+ var TEXT_METRIC = "text-base sm:text-lg font-semibold leading-tight text-foreground";
32094
+ /** Default compact button. */
32095
+ var BTN_COMPACT = "inline-flex items-center justify-center gap-1 rounded border border-border bg-surface px-1.5 py-0.5 sm:px-2 sm:py-1 text-[10px] sm:text-[11px] font-medium text-foreground hover:bg-surface-hover transition-colors disabled:opacity-40";
32096
+ /** Primary compact button. */
32097
+ var BTN_COMPACT_PRIMARY = "inline-flex items-center justify-center gap-1 rounded border border-primary/40 bg-primary/15 px-1.5 py-0.5 sm:px-2 sm:py-1 text-[10px] sm:text-[11px] font-medium text-primary hover:bg-primary/25 transition-colors disabled:opacity-40";
32098
+ /** Danger compact button. */
32099
+ var BTN_COMPACT_DANGER = "inline-flex items-center justify-center gap-1 rounded border border-danger/40 bg-danger/10 px-1.5 py-0.5 sm:px-2 sm:py-1 text-[10px] sm:text-[11px] font-medium text-danger hover:bg-danger/20 transition-colors disabled:opacity-40";
32100
+ /** Warning compact button (e.g. cancel-drawing). */
32101
+ var BTN_COMPACT_WARNING = "inline-flex items-center justify-center gap-1 rounded border border-warning/40 bg-warning/10 px-1.5 py-0.5 sm:px-2 sm:py-1 text-[10px] sm:text-[11px] font-medium text-warning hover:bg-warning/20 transition-colors disabled:opacity-40";
32102
+ /** Compact text/select/number input. */
32103
+ var INPUT_COMPACT = "w-full rounded border border-border bg-background px-1.5 py-0.5 sm:px-2 sm:py-1 text-[11px] sm:text-xs text-foreground focus:border-primary focus:ring-1 focus:ring-primary/30 outline-none";
32104
+ /** Chip-style toggle (zone / class selectors in ZoneRulesEditor). */
32105
+ var CHIP_BASE = "inline-flex items-center gap-1 rounded-full border px-1.5 py-0.5 text-[10px] font-medium transition-colors";
32106
+ var CHIP_ACTIVE = "border-primary/50 bg-primary/15 text-primary";
32107
+ var CHIP_INACTIVE = "border-border bg-surface text-foreground-subtle hover:bg-surface-hover";
32108
+ //#endregion
32109
+ //#region src/composites/setting-row.tsx
32110
+ function SettingRow({ label, children, actions, elements = "plain", className, valueClassName }) {
32111
+ const LabelTag = elements === "description" ? "dt" : "span";
32112
+ const ValueTag = elements === "description" ? "dd" : "div";
32113
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
32114
+ "data-setting-row": "",
32115
+ className: cn(SETTING_ROW, className),
32116
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LabelTag, {
32117
+ "data-setting-row-label": "",
32118
+ className: SETTING_ROW_LABEL,
32119
+ children: label
32120
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ValueTag, {
32121
+ "data-setting-row-value": "",
32122
+ className: SETTING_ROW_VALUE,
32123
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
32124
+ "data-setting-row-value-text": "",
32125
+ className: cn(SETTING_ROW_VALUE_TEXT, valueClassName),
32126
+ children
32127
+ }), actions !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
32128
+ className: "flex shrink-0 items-center gap-0.5",
32129
+ children: actions
32130
+ })]
32131
+ })]
32132
+ });
32133
+ }
32134
+ //#endregion
32135
+ //#region src/composites/sort-header-button.tsx
32136
+ function SortHeaderButton({ label, active, dir, onSort }) {
32137
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
32138
+ type: "button",
32139
+ onClick: onSort,
32140
+ className: "group/sort inline-flex items-center gap-0.5 hover:text-foreground transition-colors",
32141
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }), active ? dir === "asc" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronUp, { className: "h-3 w-3 flex-shrink-0" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDown, { className: "h-3 w-3 flex-shrink-0" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronUp, { className: cn("h-3 w-3 flex-shrink-0 opacity-0 transition-opacity", "group-hover/sort:opacity-30") })]
32142
+ });
32143
+ }
32144
+ //#endregion
32145
+ //#region src/composites/table-sort.ts
32146
+ /**
32147
+ * The toggle rule, and the only copy of it.
32148
+ *
32149
+ * none → asc → desc → none, and a click on a DIFFERENT column starts that
32150
+ * column fresh at `asc`. `sameColumn` says whether the clicked column is the
32151
+ * one currently sorted; `currentDir` is its direction (`null` when nothing is
32152
+ * sorted). Returns the next direction, or `null` for "unsorted".
32153
+ *
32154
+ * Split out from {@link nextTableSort} so the device list — whose sort state
32155
+ * keys columns by `columnId`, not `columnKey` — can reuse the rule without a
32156
+ * cast and without a second copy of it.
32157
+ */
32158
+ function nextSortDirection(sameColumn, currentDir) {
32159
+ if (!sameColumn || currentDir === null) return "asc";
32160
+ return currentDir === "asc" ? "desc" : null;
32161
+ }
32162
+ /**
32163
+ * Toggle a column's sort on header click: none → asc → desc → none.
32164
+ * Clicking a DIFFERENT column starts it fresh at `asc`.
32165
+ */
32166
+ function nextTableSort(current, columnKey) {
32167
+ const dir = nextSortDirection(current !== null && current.columnKey === columnKey, current === null ? null : current.dir);
32168
+ return dir === null ? null : {
32169
+ columnKey,
32170
+ dir
32171
+ };
32172
+ }
32173
+ /**
32174
+ * Ascending comparison of two cell values.
32175
+ *
32176
+ * Numbers compare numerically and strings compare with `localeCompare` — a
32177
+ * numeric column must never be ordered as text ("100" before "9"), and a text
32178
+ * column must respect the locale's collation. A mixed column (one row a
32179
+ * number, another a string) falls back to string comparison of both, which is
32180
+ * the only total order available; it is not a shape any caller should build on
32181
+ * purpose.
32182
+ *
32183
+ * `null` is NOT handled here — {@link sortRowsByColumn} pulls it out first,
32184
+ * because its placement is direction-independent and a comparator that is
32185
+ * multiplied by the direction factor cannot express that.
32186
+ */
32187
+ function compareValues(a, b) {
32188
+ if (typeof a === "number" && typeof b === "number") return a - b;
32189
+ return String(a).localeCompare(String(b));
32190
+ }
32191
+ /**
32192
+ * Return a NEW array of `rows` ordered by `sort`.
32193
+ *
32194
+ * `valueOf` is the column's `sortValue` — it is called once per row per
32195
+ * comparison, so it must be cheap (read a field, do not format a string).
32196
+ * `sort === null` ⇒ a fresh shallow copy in the incoming order.
32197
+ */
32198
+ function sortRowsByColumn(rows, sort, valueOf) {
32199
+ if (sort === null) return [...rows];
32200
+ const factor = sort.dir === "asc" ? 1 : -1;
32201
+ return rows.toSorted((a, b) => {
32202
+ const av = valueOf(a);
32203
+ const bv = valueOf(b);
32204
+ if (av === null || bv === null) {
32205
+ if (av === bv) return 0;
32206
+ return av === null ? 1 : -1;
32207
+ }
32208
+ return factor * compareValues(av, bv);
32209
+ });
32210
+ }
32211
+ /** Derive the `<th>` `aria-sort` for `columnKey` from the table's sort state. */
32212
+ function ariaSortForColumn(sort, columnKey) {
32213
+ if (sort === null || sort.columnKey !== columnKey) return "none";
32214
+ return sort.dir === "asc" ? "ascending" : "descending";
32215
+ }
32216
+ //#endregion
32217
+ //#region src/composites/data-table.tsx
32218
+ /**
32219
+ * DataTable — shared responsive table for admin pages.
32220
+ *
32221
+ * Recipes baked in:
32222
+ * - `overflow-x-auto` wrapper so narrow viewports don't clip the
32223
+ * right edge (the rounded border survives the scroll).
32224
+ * - `min-w-[Npx]` on the inner table so columns don't squash below
32225
+ * readable width before the scroll kicks in.
32226
+ * - `whitespace-nowrap` on headers — column titles never wrap to
32227
+ * two lines (`AUTO-KILL` was the regression that triggered this).
32228
+ * - Themed `bg-surface` + `border-border` so contrast works in both
32229
+ * light and dark themes.
32230
+ *
32231
+ * Column model: caller supplies `columns: DataColumn<T>[]` and
32232
+ * `rows: T[]`. Each column declares `header`, optional `align`,
32233
+ * optional `nowrap`, and a `render(row) => ReactNode` that produces
32234
+ * the cell content.
32235
+ *
32236
+ * <DataTable
32237
+ * columns={[
32238
+ * { key: 'username', header: 'Username', render: (u) => u.username },
32239
+ * { key: 'role', header: 'Role', render: (u) => <RoleBadge .../> },
32240
+ * { key: 'actions', header: '', align: 'right', render: (u) => <Actions .../> },
32241
+ * ]}
32242
+ * rows={users}
32243
+ * minWidthPx={560}
32244
+ * />
32245
+ *
32246
+ * If finer header markup is needed, pass `headerRender` instead of
32247
+ * `header` (e.g. for a sortable click affordance).
32248
+ *
32249
+ * ── Narrow viewports ──────────────────────────────────────────────────────
32250
+ * The table stays a table. The wrapper is `overflow-x-auto`, so a phone
32251
+ * scrolls the columns sideways INSIDE the table — the page body never moves
32252
+ * sideways, and the cells stay one line (`whitespace-nowrap`). That is the
32253
+ * default (`mobileMode="scroll"`). Cards are opt-in via `mobileMode="cards"`;
32254
+ * `auto` still applies the old column-count rule for a surface that wants it.
32255
+ * See `resolveTableLayout`.
32256
+ *
32257
+ * ── Scrolling the BODY, not the page ──────────────────────────────────────
32258
+ * `maxBodyHeight` caps the table and scrolls its rows INSIDE the container
32259
+ * with the column header pinned. It is ONE prop, not two, because "this list
32260
+ * scrolls internally" and "its header stays visible" are the same decision: a
32261
+ * capped table whose header scrolls away is a list of unlabelled cells.
32262
+ *
32263
+ * Use it wherever the row count is open-ended (rules, targets, delivery
32264
+ * history) so the page keeps its own header, filters and actions in view while
32265
+ * the operator moves through the rows.
32266
+ */
32267
+ /**
32268
+ * The default cap for an internally-scrolling table.
32269
+ *
32270
+ * `60vh` leaves the page header, its filters and its actions on screen at
32271
+ * every viewport while still showing ~10 rows — and it is a fraction, so it
32272
+ * does not need a breakpoint of its own.
32273
+ */
32274
+ var TABLE_SCROLL_MAX_H = "max-h-[60vh]";
32275
+ var ALIGN_CLASS = {
32276
+ left: "text-left",
32277
+ right: "text-right",
32278
+ center: "text-center"
32279
+ };
32280
+ function DataTable({ columns, rows, rowKey, onRowClick, minWidthPx = 480, emptyMessage, className, bordered = true, rowClassName, mobileMode = "scroll", maxBodyHeight, defaultSort }) {
32281
+ const isNarrow = useIsMobile();
32282
+ const [sort, setSort] = (0, react$1.useState)(defaultSort ?? null);
32283
+ const layout = resolveTableLayout({
32284
+ mode: mobileMode,
32285
+ columnCount: columns.length,
32286
+ isNarrow
32287
+ });
32288
+ const scrolls = maxBodyHeight !== void 0;
32289
+ const sortedColumn = (0, react$1.useMemo)(() => sort === null ? void 0 : columns.find((col) => col.key === sort.columnKey && col.sortValue !== void 0), [columns, sort]);
32290
+ const sortValue = sortedColumn?.sortValue;
32291
+ const displayRows = (0, react$1.useMemo)(() => sortValue === void 0 ? rows : sortRowsByColumn(rows, sort, sortValue), [
32292
+ rows,
32293
+ sort,
32294
+ sortValue
32295
+ ]);
32296
+ const activeSort = sortedColumn === void 0 ? null : sort;
32297
+ if (rows.length === 0 && emptyMessage) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
32298
+ className: `rounded-lg ${bordered ? "border border-border" : ""} bg-surface px-3 py-4 text-xs text-foreground-subtle text-center ${className ?? ""}`,
32299
+ children: emptyMessage
32300
+ });
32301
+ if (rows.length === 0) return null;
32302
+ if (layout === "cards") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
32303
+ className: `space-y-2 ${scrolls ? `${maxBodyHeight} overflow-y-auto` : ""} ${className ?? ""}`,
32304
+ children: displayRows.map((row, rowIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTableCard, {
32305
+ row,
32306
+ rowIndex,
32307
+ columns,
32308
+ bordered,
32309
+ onRowClick,
32310
+ rowClassName
32311
+ }, rowKey ? rowKey(row, rowIndex) : rowIndex))
32312
+ });
32313
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
32314
+ className: [
32315
+ "rounded-lg",
32316
+ bordered ? "border border-border" : "",
32317
+ "bg-surface overflow-x-auto",
32318
+ scrolls ? `${maxBodyHeight} overflow-y-auto` : "",
32319
+ className ?? ""
32320
+ ].filter(Boolean).join(" "),
32321
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
32322
+ className: `w-full text-xs ${scrolls ? "border-separate border-spacing-0" : ""}`,
32323
+ style: { minWidth: `${minWidthPx}px` },
32324
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", {
32325
+ className: "bg-surface",
32326
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: columns.map((col) => {
32327
+ const align = col.align ?? "left";
32328
+ const nowrap = col.nowrap ?? true;
32329
+ const sortable = col.sortValue !== void 0;
32330
+ const isSorted = activeSort !== null && activeSort.columnKey === col.key;
32331
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
32332
+ "aria-sort": sortable ? ariaSortForColumn(activeSort, col.key) : void 0,
32333
+ title: sortable ? void 0 : col.notSortableReason,
32334
+ className: [
32335
+ "px-3 py-2 text-foreground-subtle font-medium bg-surface border-b border-border",
32336
+ scrolls ? "sticky top-0 z-10" : "",
32337
+ ALIGN_CLASS[align],
32338
+ nowrap ? "whitespace-nowrap" : "",
32339
+ col.width ?? ""
32340
+ ].filter(Boolean).join(" "),
32341
+ children: sortable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SortHeaderButton, {
32342
+ label: col.headerRender ? col.headerRender() : col.header,
32343
+ active: isSorted,
32344
+ dir: isSorted && activeSort !== null ? activeSort.dir : "asc",
32345
+ onSort: () => setSort((prev) => nextTableSort(prev, col.key))
32346
+ }) : col.headerRender ? col.headerRender() : col.header
32347
+ }, col.key);
32348
+ }) })
32349
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: displayRows.map((row, rowIndex) => {
32350
+ const key = rowKey ? rowKey(row, rowIndex) : rowIndex;
32351
+ const extra = rowClassName?.(row, rowIndex) ?? "";
32352
+ const interactive = onRowClick !== void 0;
32353
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", {
32354
+ onClick: interactive ? () => onRowClick(row, rowIndex) : void 0,
32355
+ className: [interactive ? "cursor-pointer hover:bg-primary/5" : "hover:bg-primary/5", extra].filter(Boolean).join(" "),
32356
+ children: columns.map((col) => {
32357
+ const align = col.align ?? "left";
32358
+ const nowrap = col.nowrap ?? true;
32359
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
32360
+ className: [
32361
+ "px-3 py-2 text-foreground border-b border-border",
32362
+ ALIGN_CLASS[align],
32363
+ nowrap ? "whitespace-nowrap" : ""
32364
+ ].filter(Boolean).join(" "),
32365
+ children: col.render(row, rowIndex)
32366
+ }, col.key);
32367
+ })
32368
+ }, key);
32369
+ }) })]
32370
+ })
32371
+ });
32372
+ }
32373
+ /**
32374
+ * One row as a card. Columns that carry a plain-text `header` become labelled
32375
+ * `SettingRow`s — the label is the column title and the value gets the full
32376
+ * card width. Columns with no text header (an actions column, a
32377
+ * `headerRender`-only column) have no label to show, so they render as a
32378
+ * full-width strip at the foot of the card.
32379
+ */
32380
+ function DataTableCard({ row, rowIndex, columns, bordered, onRowClick, rowClassName }) {
32381
+ const labelled = columns.filter((col) => col.header !== void 0 && col.header !== "");
32382
+ const unlabelled = columns.filter((col) => col.header === void 0 || col.header === "");
32383
+ const interactive = onRowClick !== void 0;
32384
+ const extra = rowClassName?.(row, rowIndex) ?? "";
32385
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
32386
+ "data-data-table-card": "",
32387
+ onClick: interactive ? () => onRowClick(row, rowIndex) : void 0,
32388
+ className: [
32389
+ "rounded-lg bg-surface px-3 py-1.5 divide-y divide-border-subtle",
32390
+ bordered ? "border border-border" : "",
32391
+ interactive ? "cursor-pointer hover:bg-primary/5" : "",
32392
+ extra
32393
+ ].filter(Boolean).join(" "),
32394
+ children: [labelled.map((col) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingRow, {
32395
+ label: col.header,
32396
+ children: col.render(row, rowIndex)
32397
+ }, col.key)), unlabelled.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
32398
+ className: "flex flex-wrap items-center justify-end gap-2 py-1.5",
32399
+ children: unlabelled.map((col) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: col.render(row, rowIndex) }, col.key))
32400
+ })]
30855
32401
  });
30856
32402
  }
30857
32403
  //#endregion
30858
- //#region src/composites/cap-settings/scene-monitor-copy.ts
30859
- /** The lighting variants an operator is OFFERED. The resolver can also report
30860
- * `night`, `dawn` and `dusk`; those are shown when present but never demanded,
30861
- * because most installs only ever see day and IR. */
30862
- var SCENE_CAPTURE_VARIANTS = ["day", "ir"];
30863
- var VARIANT_LABEL = {
30864
- day: "Daylight",
30865
- ir: "Night (IR)",
30866
- night: "Night",
30867
- dawn: "Dawn",
30868
- dusk: "Dusk"
30869
- };
30870
- function variantLabel(condition) {
30871
- return VARIANT_LABEL[condition] ?? condition;
30872
- }
32404
+ //#region src/composites/device-list/fuzzy-match.ts
30873
32405
  /**
30874
- * "Daylight ✓ · Night (IR) ✗ — optional; checks in night (ir) are skipped."
32406
+ * fuzzy-match — case and separator-insensitive text search helper.
30875
32407
  *
30876
- * Converts the silent cross-condition fallback this engine used to have into an
30877
- * operator-visible fact. Without it, a scene with only a daylight reference
30878
- * simply stops answering after sunset and nothing anywhere says why.
32408
+ * Normalization rule: lowercase + strip all runs of hyphens, underscores and
32409
+ * whitespace entirely. This makes `Tapparella cucina` and `tapparella-cucina`
32410
+ * both normalize to `tapparellacucina`, so a substring match succeeds in
32411
+ * either direction.
32412
+ */
32413
+ /**
32414
+ * Strips hyphens, underscores and whitespace runs from `s` and lowercases it.
30879
32415
  *
30880
- * It used to end *"reports 'can't tell' in night (ir)"*, which was accurate and
30881
- * read as a defect: the operator saw a ✗ and a complaint for a scene that was
30882
- * configured exactly as they meant it. The missing half is now named as what it
30883
- * is — the part you do not have to do — and the sentence says what actually
30884
- * happens instead, which differs by the scene's own policy.
32416
+ * @example
32417
+ * normalizeForSearch('Tapparella cucina') // 'tapparellacucina'
32418
+ * normalizeForSearch('tapparella-cucina') // 'tapparellacucina'
32419
+ * normalizeForSearch('tapparella_cucina') // 'tapparellacucina'
30885
32420
  */
30886
- function coverageLine(monitor) {
30887
- const covered = new Set(monitor.coveredConditions);
30888
- const parts = SCENE_CAPTURE_VARIANTS.map((v) => `${variantLabel(v)} ${covered.has(v) ? "✓" : "✗"}`);
30889
- const extra = [...covered].filter((c) => !SCENE_CAPTURE_VARIANTS.some((v) => v === c));
30890
- for (const c of extra) parts.push(`${variantLabel(c)} ✓`);
30891
- const missing = SCENE_CAPTURE_VARIANTS.filter((v) => !covered.has(v));
30892
- if (missing.length === 0) return parts.join(" · ");
30893
- if (covered.size === 0) return `${parts.join(" · ")} — nothing captured yet, so this scene is not watching.`;
30894
- const names = missing.map((v) => variantLabel(v).toLowerCase()).join(" and ");
30895
- return monitor.onUncoveredCondition === "judge-anyway" ? `${parts.join(" · ")} — ${names} judged against the references you do have.` : `${parts.join(" · ")} — optional; checks in ${names} are skipped.`;
32421
+ function normalizeForSearch(s) {
32422
+ return s.toLowerCase().replace(/[-_\s]+/g, "");
30896
32423
  }
30897
32424
  /**
30898
- * The calm sentence for a scene that is sitting the current light out — `null`
30899
- * whenever it is checking normally.
32425
+ * Returns `true` when the normalized form of `target` contains the normalized
32426
+ * form of `query`. An empty query always matches.
30900
32427
  *
30901
- * Separate from {@link statusLine} because it is not a verdict and must not
30902
- * read like one. A paused scene has not failed, has not lost its flag, and
30903
- * needs nothing from the operator; the only thing worth saying is which light
30904
- * it is waiting out and that it will pick itself back up.
32428
+ * @example
32429
+ * fuzzyMatch('Tapparella cucina', 'tapparella-cucina') // true
32430
+ * fuzzyMatch('tapparella cucina', 'Tapparella-Cucina') // true
32431
+ * fuzzyMatch('xyz', 'tapparella-cucina') // false
30905
32432
  */
30906
- function suspensionLine(monitor) {
30907
- if (monitor.suspendedCondition === null) return null;
30908
- const light = variantLabel(monitor.suspendedCondition).toLowerCase();
30909
- return `Paused — nothing was captured in ${light}, so checks are on hold until the light changes back. Capture ${light} if you want it watched around the clock.`;
32433
+ function fuzzyMatch(query, target) {
32434
+ const nq = normalizeForSearch(query);
32435
+ if (nq === "") return true;
32436
+ return normalizeForSearch(target).includes(nq);
30910
32437
  }
30911
- var UNAVAILABLE_SENTENCE = {
30912
- "no-reference-for-condition": "can’t tell — nothing has been captured for this scene yet. Press “Capture daylight” to photograph what normal looks like.",
30913
- "view-shifted": "can’t tell — the camera view has moved since the reference was captured. Re-capture it.",
30914
- "no-vision-profile": "can’t tell — the vision model is unreachable, so the change is on hold.",
30915
- "encoder-model-changed": "can’t tell — the embedding model changed, so the stored references are unreadable. Re-capture them.",
30916
- "no-snapshot": "can’t tell — this camera did not return a frame."
32438
+ //#endregion
32439
+ //#region src/composites/data-table-filter.ts
32440
+ /**
32441
+ * Per-instance filtering for a `DataTable` (or any row list): a search box
32442
+ * and a set of facet chips, both DERIVED from the rows the instance renders.
32443
+ *
32444
+ * Pure — no React. The state lives with the caller (`useDataTableFilter`), so
32445
+ * two tables on one page each carry their own filter, and the caller keeps the
32446
+ * FILTERED set it renders: a table whose selection acts on "what is on
32447
+ * screen" has to know what that is, which a filter hidden inside the table
32448
+ * could not tell it.
32449
+ *
32450
+ * Two rules, both load-bearing:
32451
+ * - **Facets are derived from the rows present**, most common first. A
32452
+ * hand-listed set goes stale the day a provider adds a type and offers
32453
+ * chips that match nothing.
32454
+ * - **A selected facet no row carries is ignored**, not applied. The chip is
32455
+ * not drawn (there is no row to derive it from), and an invisible filter
32456
+ * that empties the table is a table that looks broken.
32457
+ */
32458
+ var EMPTY_DATA_TABLE_FILTER = {
32459
+ search: "",
32460
+ facets: []
30917
32461
  };
30918
32462
  /**
30919
- * The one line under a scene's name. It answers, in order: can it judge, what
30920
- * does it say, and how long has it said it.
32463
+ * The facet chips for these rows: one per distinct value, most common first,
32464
+ * ties by value.
30921
32465
  */
30922
- function statusLine(monitor, now) {
30923
- if (monitor.states.length === 0) return "Not armed yet — capture the initial state to start watching.";
30924
- if (monitor.verdict === "unknown") return monitor.unavailable !== null ? UNAVAILABLE_SENTENCE[monitor.unavailable] : "can’t tell right now.";
30925
- const since = monitor.verdict === "diverged" ? monitor.divergedAt : monitor.armedAt;
30926
- const held = since !== null ? ` for ${humanDuration(Math.max(0, now - since))}` : "";
30927
- if (monitor.latched) return monitor.verdict === "diverged" ? `CHANGED${held} — press Reset when you have dealt with it.` : `Back to normal, but still flagged as changed${held}. Press Reset to clear it.`;
30928
- const paused = suspensionLine(monitor);
30929
- if (paused !== null) return paused;
30930
- return monitor.verdict === "diverged" ? `CHANGED${held}` : `Normal${held}`;
30931
- }
30932
- /** The badge word. `latched` wins over `verdict`, because the latch is the
30933
- * thing the operator asked to be told and it outlives the live state.
30934
- *
30935
- * A scene sitting out an uncovered light reads `Paused`, in the NEUTRAL tone —
30936
- * never `alarm`, and never the `danger` variant the page maps that to. A
30937
- * missing IR reference must not be able to paint a red badge on a scene that
30938
- * is doing exactly what it was configured to do. */
30939
- function badgeFor(monitor) {
30940
- if (monitor.states.length === 0) return {
30941
- text: "Not armed",
30942
- tone: "unknown"
30943
- };
30944
- if (monitor.latched) return {
30945
- text: "Changed",
30946
- tone: "alarm"
30947
- };
30948
- if (monitor.suspendedCondition !== null) return {
30949
- text: "Paused",
30950
- tone: "unknown"
30951
- };
30952
- if (monitor.verdict === "unknown") return {
30953
- text: "Can’t tell",
30954
- tone: "unknown"
30955
- };
30956
- return monitor.verdict === "diverged" ? {
30957
- text: "Changed",
30958
- tone: "alarm"
30959
- } : {
30960
- text: "Normal",
30961
- tone: "ok"
30962
- };
32466
+ function dataTableFacets(rows, facetValue) {
32467
+ if (facetValue === void 0) return [];
32468
+ const counts = /* @__PURE__ */ new Map();
32469
+ for (const row of rows) {
32470
+ const value = facetValue(row);
32471
+ counts.set(value, (counts.get(value) ?? 0) + 1);
32472
+ }
32473
+ return [...counts.entries()].map(([value, count]) => ({
32474
+ value,
32475
+ count
32476
+ })).toSorted((a, b) => b.count - a.count || a.value.localeCompare(b.value));
30963
32477
  }
30964
- /** A rect covering most of the frame is dominated by weather, sky and parked
30965
- * cars: the bin is 2 % of the pixels and CLIP will not notice it leaving. The
30966
- * failure is silent INSENSITIVITY, which is the worst kind — hence a warning
30967
- * rather than a refusal. */
30968
- var ROI_AREA_WARN_FRACTION = .4;
30969
- function roiTooLarge(roi) {
30970
- return roi.width * roi.height > ROI_AREA_WARN_FRACTION;
32478
+ function isDataTableFilterActive(state) {
32479
+ return state.search.trim() !== "" || state.facets.length > 0;
30971
32480
  }
30972
- function humanDuration(ms) {
30973
- const s = Math.floor(ms / 1e3);
30974
- if (s < 60) return `${s}s`;
30975
- const m = Math.floor(s / 60);
30976
- if (m < 60) return `${m}m`;
30977
- const h = Math.floor(m / 60);
30978
- if (h < 48) return `${h}h`;
30979
- return `${Math.floor(h / 24)}d`;
32481
+ /** A new state with `value` added to, or removed from, the selected facets. */
32482
+ function toggleDataTableFacet(state, value) {
32483
+ return {
32484
+ ...state,
32485
+ facets: state.facets.includes(value) ? state.facets.filter((v) => v !== value) : [...state.facets, value]
32486
+ };
30980
32487
  }
30981
- //#endregion
30982
- //#region src/composites/code-block.tsx
30983
- function CodeBlock({ children, maxHeight = 300, className }) {
30984
- const [copied, setCopied] = (0, react$1.useState)(false);
30985
- const handleCopy = (0, react$1.useCallback)(() => {
30986
- navigator.clipboard.writeText(children).then(() => {
30987
- setCopied(true);
30988
- setTimeout(() => setCopied(false), 2e3);
30989
- });
30990
- }, [children]);
30991
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
30992
- className: cn("relative group", className),
30993
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollArea, {
30994
- style: { maxHeight },
30995
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
30996
- className: "font-mono text-xs bg-surface p-3 rounded-md border border-border-subtle",
30997
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children })
30998
- })
30999
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
31000
- className: "absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity",
31001
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
31002
- icon: copied ? Check : Copy,
31003
- "aria-label": "Copy code",
31004
- variant: "ghost",
31005
- size: "sm",
31006
- onClick: handleCopy
31007
- })
31008
- })]
32488
+ /**
32489
+ * The rows that survive the filter, in their incoming order.
32490
+ *
32491
+ * A selected facet that none of `rows` carries is dropped from the effective
32492
+ * selection (see the header), so a chip that is not on screen never filters.
32493
+ */
32494
+ function filterDataTableRows(rows, state, config) {
32495
+ const needle = state.search.trim();
32496
+ const facetValue = config.facetValue;
32497
+ const present = new Set(dataTableFacets(rows, facetValue).map((f) => f.value));
32498
+ const selected = new Set(state.facets.filter((v) => present.has(v)));
32499
+ return rows.filter((row) => {
32500
+ if (selected.size > 0 && facetValue !== void 0 && !selected.has(facetValue(row))) return false;
32501
+ if (needle !== "" && !config.searchText(row).some((text) => fuzzyMatch(needle, text))) return false;
32502
+ return true;
31009
32503
  });
31010
32504
  }
31011
32505
  //#endregion
31012
- //#region src/composites/confirm-action-button.tsx
32506
+ //#region src/composites/data-table-filter-bar.tsx
31013
32507
  /**
31014
- * ConfirmActionButton — destructive-action button with a modal confirm.
32508
+ * The controls of one table's filter: a search box and the facet chips.
31015
32509
  *
31016
- * Two-step UX for any operation operators shouldn't trigger by accident:
31017
- * reboot device, restart addon, regenerate token, etc. The trigger is a
31018
- * normal Button that opens a Dialog; confirming runs the async action,
31019
- * the trigger spinner-disables itself for the duration, and the dialog
31020
- * closes on success. Errors surface inline at the dialog's bottom.
32510
+ * Controlled — the state comes from `useDataTableFilter` and goes back through
32511
+ * the two callbacks; this component owns nothing, so it can sit above a
32512
+ * `DataTable`, a card's list, or anything else that renders the filtered rows.
31021
32513
  *
31022
- * Generic on `TResult` so callers don't have to discard the return
31023
- * value. Pass an `icon` to render it inside the trigger (e.g. RotateCw
31024
- * for reboot, RefreshCw for restart).
32514
+ * Every visible string is a prop: this library carries no translations, and
32515
+ * the caller's i18n does.
31025
32516
  */
31026
- function ConfirmActionButton({ label, icon: Icon, title = "Confirm action", description, confirmLabel, triggerVariant = "outline", confirmVariant = "danger", size = "sm", disabled, className, action, onSuccess }) {
31027
- const [open, setOpen] = (0, react$1.useState)(false);
31028
- const [running, setRunning] = (0, react$1.useState)(false);
31029
- const [error, setError] = (0, react$1.useState)(null);
31030
- const handleConfirm = async () => {
31031
- setError(null);
31032
- setRunning(true);
31033
- try {
31034
- const result = await action();
31035
- onSuccess?.(result);
31036
- setOpen(false);
31037
- } catch (err) {
31038
- setError(err instanceof Error ? err.message : String(err));
31039
- } finally {
31040
- setRunning(false);
31041
- }
31042
- };
31043
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
31044
- type: "button",
31045
- variant: triggerVariant,
31046
- size,
31047
- disabled,
31048
- onClick: () => {
31049
- setError(null);
31050
- setOpen(true);
31051
- },
31052
- className,
31053
- children: [Icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { className: "h-3.5 w-3.5" }), label]
31054
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dialog, {
31055
- open,
31056
- onOpenChange: (next) => !running && setOpen(next),
31057
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogContent, { children: [
31058
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogTitle, {
31059
- className: "flex items-center gap-2",
31060
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TriangleAlert, { className: "h-4 w-4 text-amber-400" }), title]
31061
- }), typeof description === "string" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogDescription, { children: description }) : description] }),
31062
- error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
31063
- className: cn("mt-2 px-3 py-2 rounded border border-danger/40 bg-danger/10", "text-xs text-danger"),
31064
- children: error
32517
+ var CHIP_ON = "rounded-full border border-primary bg-primary/10 px-2 py-0.5 text-[10px] text-foreground";
32518
+ var CHIP_OFF = "rounded-full border border-border px-2 py-0.5 text-[10px] text-foreground-muted hover:bg-surface-hover";
32519
+ function DataTableFilterBar({ state, facets, labels, onSearch, onToggleFacet, onClear, facetLabel }) {
32520
+ const active = state.search.trim() !== "" || state.facets.length > 0;
32521
+ const showFacets = facets.length > 1;
32522
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
32523
+ className: "flex flex-wrap items-center gap-2",
32524
+ children: [
32525
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
32526
+ className: "relative min-w-[12rem] flex-1",
32527
+ 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", {
32528
+ type: "search",
32529
+ value: state.search,
32530
+ onChange: (e) => onSearch(e.target.value),
32531
+ placeholder: labels.searchPlaceholder,
32532
+ "aria-label": labels.searchLabel,
32533
+ className: "w-full rounded border border-border bg-background py-1.5 pl-7 pr-2 text-xs text-foreground"
32534
+ })]
31065
32535
  }),
31066
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogFooter, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
31067
- type: "button",
31068
- variant: "ghost",
31069
- size,
31070
- disabled: running,
31071
- onClick: () => setOpen(false),
31072
- children: "Cancel"
31073
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
32536
+ showFacets && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
32537
+ role: "group",
32538
+ "aria-label": labels.facetsLabel,
32539
+ className: "flex flex-wrap gap-1",
32540
+ children: facets.map((facet) => {
32541
+ const pressed = state.facets.includes(facet.value);
32542
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
32543
+ type: "button",
32544
+ onClick: () => onToggleFacet(facet.value),
32545
+ "aria-pressed": pressed,
32546
+ className: pressed ? CHIP_ON : CHIP_OFF,
32547
+ children: [
32548
+ facetLabel ? facetLabel(facet.value) : facet.value,
32549
+ " ",
32550
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
32551
+ className: "text-foreground-subtle tabular-nums",
32552
+ children: facet.count
32553
+ })
32554
+ ]
32555
+ }, facet.value);
32556
+ })
32557
+ }),
32558
+ active && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
31074
32559
  type: "button",
31075
- variant: confirmVariant,
31076
- size,
31077
- disabled: running,
31078
- onClick: () => {
31079
- handleConfirm();
31080
- },
31081
- children: [running && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoaderCircle, { className: "h-3.5 w-3.5 animate-spin" }), confirmLabel ?? label]
31082
- })] })
31083
- ] })
31084
- })] });
32560
+ onClick: onClear,
32561
+ className: "flex items-center gap-1 text-[11px] text-foreground-subtle hover:text-foreground",
32562
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(X, { className: "h-3 w-3" }), labels.clearLabel]
32563
+ })
32564
+ ]
32565
+ });
31085
32566
  }
31086
32567
  //#endregion
31087
- //#region src/composites/copy-button.tsx
32568
+ //#region src/composites/use-data-table-filter.ts
31088
32569
  /**
31089
- * `CopyButton` — a compact button that copies a string `value` to the
31090
- * clipboard via `navigator.clipboard.writeText`, showing a brief
31091
- * "copied" check state. Addon-agnostic; used wherever a secret / URL
31092
- * needs a one-click copy affordance (export setup panels, etc.).
32570
+ * The React half of `data-table-filter.ts`: one filter state per call site,
32571
+ * so every table on a page filters on its own.
32572
+ *
32573
+ * The caller renders `rows` (the filtered set) and hands `state` + the two
32574
+ * setters to a `DataTableFilterBar`. It keeps the filtered set because
32575
+ * anything that acts on "the rows on screen" — a select-all, a bulk action —
32576
+ * must act on exactly these and nothing the filter hid.
31093
32577
  */
31094
- var COPIED_RESET_MS = 2e3;
31095
- function CopyButton({ value, label, srLabel, className, disabled }) {
31096
- const [copied, setCopied] = (0, react$1.useState)(false);
31097
- const handleCopy = (0, react$1.useCallback)(() => {
31098
- if (!value) return;
31099
- navigator.clipboard.writeText(value).then(() => {
31100
- setCopied(true);
31101
- setTimeout(() => setCopied(false), COPIED_RESET_MS);
31102
- });
31103
- }, [value]);
31104
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
31105
- size: "sm",
31106
- variant: "ghost",
31107
- type: "button",
31108
- disabled: disabled || value.length === 0,
31109
- onClick: handleCopy,
31110
- className: cn(className),
31111
- "aria-label": copied ? "Copied" : `Copy ${srLabel ?? label ?? "value"}`,
31112
- children: [copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Check, { className: "h-3.5 w-3.5 text-success" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Copy, { className: "h-3.5 w-3.5" }), label ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
31113
- className: "ml-1",
31114
- children: copied ? "Copied" : label
31115
- }) : null]
31116
- });
32578
+ function useDataTableFilter(rows, config) {
32579
+ const [state, setState] = (0, react$1.useState)(EMPTY_DATA_TABLE_FILTER);
32580
+ const setSearch = (0, react$1.useCallback)((search) => {
32581
+ setState((prev) => ({
32582
+ ...prev,
32583
+ search
32584
+ }));
32585
+ }, []);
32586
+ const toggleFacet = (0, react$1.useCallback)((value) => {
32587
+ setState((prev) => toggleDataTableFacet(prev, value));
32588
+ }, []);
32589
+ const clear = (0, react$1.useCallback)(() => setState(EMPTY_DATA_TABLE_FILTER), []);
32590
+ const { searchText, facetValue } = config;
32591
+ return {
32592
+ state,
32593
+ setSearch,
32594
+ toggleFacet,
32595
+ clear,
32596
+ facets: (0, react$1.useMemo)(() => dataTableFacets(rows, facetValue), [rows, facetValue]),
32597
+ rows: (0, react$1.useMemo)(() => filterDataTableRows(rows, state, {
32598
+ searchText,
32599
+ facetValue
32600
+ }), [
32601
+ rows,
32602
+ state,
32603
+ searchText,
32604
+ facetValue
32605
+ ]),
32606
+ active: isDataTableFilterActive(state)
32607
+ };
31117
32608
  }
31118
32609
  //#endregion
31119
32610
  //#region src/composites/detection-colors.ts
@@ -44891,35 +46382,129 @@ function deviceRoleMetaOf(role) {
44891
46382
  return DEVICE_ROLE_META[role] ?? FALLBACK_DEVICE_ROLE_META;
44892
46383
  }
44893
46384
  /**
44894
- * Icons an operator may pick for a per-device `display.icon` override, keyed by
44895
- * a stable kebab-case name. Built ONLY from the lucide icons already imported by
44896
- * this module — no new imports, no dynamic indexing — so it stays bundle-size
44897
- * neutral and the picker offers a curated, sensor-relevant set. Unknown keys
44898
- * resolve to `undefined` (caller keeps the role/type default).
46385
+ * Icons an operator may pick for a per-device `display.icon` override (and for
46386
+ * a per-role default), keyed by a stable kebab-case name. Named imports only —
46387
+ * no dynamic indexing into lucide, so the bundle carries exactly this set.
46388
+ * Unknown keys resolve to `undefined` (caller falls through to the next level).
46389
+ *
46390
+ * The vocabulary is the one the VIEWER draws from too (its own icon module,
46391
+ * keyed by these same strings): a key added here that the phone cannot draw
46392
+ * falls through to the next resolution level there, which reads as "my change
46393
+ * did nothing". So the KEY is the contract — a stable kebab-case string, never
46394
+ * a glyph identity — and `device-meta.test.ts` pins that every key resolves
46395
+ * and that the set never loses a key an operator may already have persisted.
46396
+ *
46397
+ * Grouped by what an operator is naming: a device, a light or appliance, a
46398
+ * sensor or the weather it reads, a room, or what moves through it. `gauge2`
46399
+ * is a legacy alias kept for rows that persisted it.
44899
46400
  */
44900
46401
  var DISPLAY_ICON_REGISTRY = {
46402
+ camera: Camera,
46403
+ network: Network,
46404
+ "toggle-left": ToggleLeft,
46405
+ "toggle-right": ToggleRight,
46406
+ "circle-dot": CircleDot,
46407
+ "refresh-cw": RefreshCw,
46408
+ box: Box,
46409
+ "scroll-text": ScrollText,
46410
+ workflow: Workflow,
46411
+ lock: Lock,
46412
+ "lock-open": LockOpen,
46413
+ blinds: Blinds,
46414
+ "air-vent": AirVent,
46415
+ "monitor-play": MonitorPlay,
46416
+ "sliders-horizontal": SlidersHorizontal,
46417
+ "map-pin": MapPin,
46418
+ bot: Bot,
46419
+ tractor: Tractor,
46420
+ image: Image,
46421
+ bone: Bone,
46422
+ "door-closed": DoorClosed,
46423
+ "door-open": DoorOpen,
46424
+ crosshair: Crosshair,
46425
+ eye: Eye,
46426
+ "eye-off": EyeOff,
46427
+ smartphone: Smartphone,
46428
+ phone: Phone,
46429
+ tablet: Tablet,
46430
+ laptop: Laptop,
46431
+ monitor: Monitor,
46432
+ tv: Tv,
46433
+ speaker: Speaker,
46434
+ printer: Printer,
46435
+ projector: Projector,
46436
+ "gamepad-2": Gamepad2,
46437
+ radio: Radio,
46438
+ router: Router,
46439
+ bluetooth: Bluetooth,
46440
+ cpu: Cpu,
46441
+ "hard-drive": HardDrive,
46442
+ server: Server,
46443
+ mic: Mic,
46444
+ "mic-off": MicOff,
46445
+ video: Video,
46446
+ "video-off": VideoOff,
46447
+ music: Music,
46448
+ plug: Plug,
46449
+ "plug-zap": PlugZap,
46450
+ power: Power,
46451
+ "power-off": PowerOff,
46452
+ cable: Cable,
46453
+ battery: Battery,
46454
+ "battery-charging": BatteryCharging,
46455
+ "battery-full": BatteryFull,
46456
+ "battery-low": BatteryLow,
46457
+ key: Key,
46458
+ "key-round": KeyRound,
46459
+ mailbox: Mailbox,
46460
+ package: Package,
46461
+ shield: Shield,
46462
+ "shield-check": ShieldCheck,
46463
+ "shield-off": ShieldOff,
46464
+ lightbulb: Lightbulb,
46465
+ lamp: Lamp,
46466
+ "lamp-ceiling": LampCeiling,
46467
+ "lamp-desk": LampDesk,
46468
+ "lamp-floor": LampFloor,
46469
+ flashlight: Flashlight,
46470
+ heater: Heater,
46471
+ snowflake: Snowflake,
46472
+ "thermometer-sun": ThermometerSun,
46473
+ "thermometer-snowflake": ThermometerSnowflake,
46474
+ refrigerator: Refrigerator,
46475
+ microwave: Microwave,
46476
+ "washing-machine": WashingMachine,
46477
+ "cooking-pot": CookingPot,
46478
+ utensils: Utensils,
44901
46479
  thermometer: Thermometer,
44902
46480
  droplets: Droplets,
44903
46481
  droplet: Droplet,
44904
46482
  gauge: Gauge,
46483
+ gauge2: Gauge,
44905
46484
  sun: Sun,
44906
46485
  "sun-medium": SunMedium,
44907
46486
  sunrise: Sunrise,
44908
46487
  moon: Moon,
44909
46488
  wind: Wind,
46489
+ cloud: Cloud,
44910
46490
  "cloud-rain": CloudRain,
44911
46491
  "cloud-sun": CloudSun,
44912
46492
  waves: Waves,
44913
46493
  sprout: Sprout,
46494
+ leaf: Leaf,
46495
+ flower: Flower,
44914
46496
  zap: Zap,
44915
46497
  flame: Flame,
44916
46498
  bell: Bell,
46499
+ "bell-ring": BellRing,
46500
+ "bell-off": BellOff,
44917
46501
  siren: Siren,
44918
46502
  radar: Radar,
44919
46503
  activity: Activity,
44920
46504
  hash: Hash,
44921
46505
  clock: Clock,
44922
- gauge2: Gauge,
46506
+ timer: Timer,
46507
+ calendar: Calendar,
44923
46508
  pipette: Pipette,
44924
46509
  fan: Fan,
44925
46510
  bath: Bath,
@@ -44928,9 +46513,49 @@ var DISPLAY_ICON_REGISTRY = {
44928
46513
  "shield-alert": ShieldAlert,
44929
46514
  vibrate: Vibrate,
44930
46515
  wifi: Wifi,
46516
+ "wifi-off": WifiOff,
44931
46517
  "volume-2": Volume2,
44932
- lightbulb: Lightbulb,
44933
- flashlight: Flashlight
46518
+ "volume-x": VolumeX,
46519
+ heart: Heart,
46520
+ "heart-pulse": HeartPulse,
46521
+ star: Star,
46522
+ sparkles: Sparkles,
46523
+ house: House,
46524
+ sofa: Sofa,
46525
+ "bed-double": BedDouble,
46526
+ bed: Bed,
46527
+ warehouse: Warehouse,
46528
+ trees: Trees,
46529
+ "tree-pine": TreePine,
46530
+ fence: Fence,
46531
+ store: Store,
46532
+ building: Building,
46533
+ "building-2": Building2,
46534
+ mountain: Mountain,
46535
+ umbrella: Umbrella,
46536
+ dumbbell: Dumbbell,
46537
+ wrench: Wrench,
46538
+ hammer: Hammer,
46539
+ drill: Drill,
46540
+ "waves-ladder": WavesLadder,
46541
+ "circle-parking": CircleParking,
46542
+ briefcase: Briefcase,
46543
+ route: Route,
46544
+ car: Car,
46545
+ bike: Bike,
46546
+ bus: Bus,
46547
+ truck: Truck,
46548
+ plane: Plane,
46549
+ "train-front": TrainFront,
46550
+ sailboat: Sailboat,
46551
+ dog: Dog,
46552
+ cat: Cat,
46553
+ bird: Bird,
46554
+ "paw-print": PawPrint,
46555
+ baby: Baby,
46556
+ user: User,
46557
+ users: Users,
46558
+ "person-standing": PersonStanding
44934
46559
  };
44935
46560
  /** Resolve a `display.icon` name to a lucide component, or `undefined` when the
44936
46561
  * name is not in the curated `DISPLAY_ICON_REGISTRY`. */
@@ -45115,12 +46740,35 @@ function resolveFlags(props) {
45115
46740
  showChildrenAccordion: props.showChildrenAccordion ?? d.showChildrenAccordion
45116
46741
  };
45117
46742
  }
45118
- function deviceLeadIcon(device) {
46743
+ /**
46744
+ * The row's lead icon — the ONE resolution order, the same one
46745
+ * `resolveSensorDisplay` applies to unit and precision:
46746
+ *
46747
+ * device override → operator role default → built-in role glyph → type glyph
46748
+ *
46749
+ * `roleDefaults` is what the nearest `RoleDisplayDefaultsProvider` publishes;
46750
+ * callers outside a provider pass nothing and the second level is skipped.
46751
+ * Each level FALLS THROUGH when it names a glyph the registry cannot draw —
46752
+ * an unknown key at any level must never leave the row without an icon, and
46753
+ * must never shadow a lower level that would have drawn one.
46754
+ *
46755
+ * "Set as default for all X sensors" writes the second level. It was written
46756
+ * for a year and read by nothing: this function went straight from the device
46757
+ * override to `DEVICE_ROLE_META`.
46758
+ */
46759
+ function deviceLeadIcon(device, roleDefaults) {
45119
46760
  if (device.display?.icon) {
45120
46761
  const overridden = resolveDisplayIcon(device.display.icon);
45121
46762
  if (overridden) return overridden;
45122
46763
  }
45123
- if (device.role) return deviceRoleMetaOf(device.role).icon;
46764
+ if (device.role) {
46765
+ const roleDefaultKey = roleDefaults?.[device.role]?.icon;
46766
+ if (roleDefaultKey !== void 0) {
46767
+ const fromRoleDefault = resolveDisplayIcon(roleDefaultKey);
46768
+ if (fromRoleDefault) return fromRoleDefault;
46769
+ }
46770
+ return deviceRoleMetaOf(device.role).icon;
46771
+ }
45124
46772
  return deviceTypeMetaOf(device.type).icon;
45125
46773
  }
45126
46774
  /**
@@ -45277,6 +46925,7 @@ function effectiveType(device, containerChildren) {
45277
46925
  */
45278
46926
  function effectiveIconDevice(device, containerChildren) {
45279
46927
  if (device.type !== _camstack_types.DeviceType.Container) return device;
46928
+ if (device.display?.icon !== void 0 && resolveDisplayIcon(device.display.icon) !== void 0) return device;
45280
46929
  const children = containerChildren ?? [];
45281
46930
  if (children.length === 0) return device;
45282
46931
  const primary = resolveContainerPrimary(children, device.linkDeviceId, device.primaryChildEntityId);
@@ -45656,7 +47305,7 @@ function RebootQuickAction({ trpc, deviceId, deviceName }) {
45656
47305
  * or a standalone test) the hook returns a stable empty array — the same
45657
47306
  * graceful default the old optional prop had.
45658
47307
  */
45659
- var EMPTY$1 = [];
47308
+ var EMPTY$2 = [];
45660
47309
  var ContainerChildrenContext = (0, react$1.createContext)(null);
45661
47310
  function ContainerChildrenProvider({ value, children }) {
45662
47311
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContainerChildrenContext.Provider, {
@@ -45671,7 +47320,7 @@ function ContainerChildrenProvider({ value, children }) {
45671
47320
  * rendered outside a list, degrades exactly like the old optional prop.
45672
47321
  */
45673
47322
  function useContainerChildren(deviceId) {
45674
- return (0, react$1.useContext)(ContainerChildrenContext)?.get(deviceId) ?? EMPTY$1;
47323
+ return (0, react$1.useContext)(ContainerChildrenContext)?.get(deviceId) ?? EMPTY$2;
45675
47324
  }
45676
47325
  //#endregion
45677
47326
  //#region src/composites/device-item/actions.tsx
@@ -45897,6 +47546,33 @@ function PtzQuickAction({ trpc, deviceId }) {
45897
47546
  }), document.body)] });
45898
47547
  }
45899
47548
  //#endregion
47549
+ //#region src/composites/device-item/role-display-defaults-context.tsx
47550
+ var EMPTY$1 = {};
47551
+ var RoleDisplayDefaultsContext = (0, react$1.createContext)(EMPTY$1);
47552
+ function RoleDisplayDefaultsProvider({ value, children }) {
47553
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoleDisplayDefaultsContext.Provider, {
47554
+ value,
47555
+ children
47556
+ });
47557
+ }
47558
+ /** The defaults published by the nearest provider — an empty record outside
47559
+ * one, never `undefined`, so a caller can index it without a guard. */
47560
+ function useRoleDisplayDefaults() {
47561
+ return (0, react$1.useContext)(RoleDisplayDefaultsContext);
47562
+ }
47563
+ /**
47564
+ * Reads the defaults from the hub and publishes them. Until the query lands
47565
+ * the children see no defaults — the built-in role glyph, which is what they
47566
+ * would see with no operator default at all, so nothing flashes wrong.
47567
+ */
47568
+ function RoleDisplayDefaultsFromServer({ children }) {
47569
+ const { data } = useDeviceManagerGetRoleDisplayDefaults({}, { staleTime: 6e4 });
47570
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoleDisplayDefaultsProvider, {
47571
+ value: data?.defaults ?? EMPTY$1,
47572
+ children
47573
+ });
47574
+ }
47575
+ //#endregion
45900
47576
  //#region src/composites/device-item/children-accordion.tsx
45901
47577
  /**
45902
47578
  * <ChildrenAccordion> — accessory + adopted children rows shown when
@@ -45949,7 +47625,7 @@ function ChildRow$1({ trpc, child, onNavigate, parentName }) {
45949
47625
  },
45950
47626
  className: "w-full flex items-center gap-2 px-3 py-1.5 text-left hover:bg-surface-hover transition-colors cursor-pointer",
45951
47627
  children: [
45952
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(deviceLeadIcon(child), { className: "h-3 w-3 text-foreground-subtle flex-shrink-0" }),
47628
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(deviceLeadIcon(child, useRoleDisplayDefaults()), { className: "h-3 w-3 text-foreground-subtle flex-shrink-0" }),
45953
47629
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
45954
47630
  className: "text-[10.5px] text-foreground flex-1 truncate",
45955
47631
  children: childListName(child, parentName)
@@ -46328,7 +48004,9 @@ var CHEVRON_SLOT_CLASS = {
46328
48004
  stacked: "h-4 w-4 mt-0.5"
46329
48005
  };
46330
48006
  function DeviceItemHeader({ device, variant, flags, parent, integrationIcon, hasChildren, expanded, onToggleExpand, onNavigateToParent, stacked = false, nameOverflow = "truncate" }) {
46331
- const Icon = deviceLeadIcon(effectiveIconDevice(device, useContainerChildren(device.id)));
48007
+ const containerChildren = useContainerChildren(device.id);
48008
+ const roleDefaults = useRoleDisplayDefaults();
48009
+ const Icon = deviceLeadIcon(effectiveIconDevice(device, containerChildren), roleDefaults);
46332
48010
  const slotClass = stacked ? CHEVRON_SLOT_CLASS.stacked : CHEVRON_SLOT_CLASS.horizontal;
46333
48011
  const lineClass = nameOverflow === "scroll" ? NAME_TEXT_CLASS : "truncate";
46334
48012
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -47135,41 +48813,6 @@ function groupTopLevelAndAccessories(devices, options) {
47135
48813
  };
47136
48814
  }
47137
48815
  //#endregion
47138
- //#region src/composites/device-list/fuzzy-match.ts
47139
- /**
47140
- * fuzzy-match — case and separator-insensitive text search helper.
47141
- *
47142
- * Normalization rule: lowercase + strip all runs of hyphens, underscores and
47143
- * whitespace entirely. This makes `Tapparella cucina` and `tapparella-cucina`
47144
- * both normalize to `tapparellacucina`, so a substring match succeeds in
47145
- * either direction.
47146
- */
47147
- /**
47148
- * Strips hyphens, underscores and whitespace runs from `s` and lowercases it.
47149
- *
47150
- * @example
47151
- * normalizeForSearch('Tapparella cucina') // 'tapparellacucina'
47152
- * normalizeForSearch('tapparella-cucina') // 'tapparellacucina'
47153
- * normalizeForSearch('tapparella_cucina') // 'tapparellacucina'
47154
- */
47155
- function normalizeForSearch(s) {
47156
- return s.toLowerCase().replace(/[-_\s]+/g, "");
47157
- }
47158
- /**
47159
- * Returns `true` when the normalized form of `target` contains the normalized
47160
- * form of `query`. An empty query always matches.
47161
- *
47162
- * @example
47163
- * fuzzyMatch('Tapparella cucina', 'tapparella-cucina') // true
47164
- * fuzzyMatch('tapparella cucina', 'Tapparella-Cucina') // true
47165
- * fuzzyMatch('xyz', 'tapparella-cucina') // false
47166
- */
47167
- function fuzzyMatch(query, target) {
47168
- const nq = normalizeForSearch(query);
47169
- if (nq === "") return true;
47170
- return normalizeForSearch(target).includes(nq);
47171
- }
47172
- //#endregion
47173
48816
  //#region src/composites/device-list/multi-select.tsx
47174
48817
  /**
47175
48818
  * Compact multi-select dropdown — a single chip-sized trigger ("Label · N")
@@ -47612,7 +49255,7 @@ function topLevelIndentLevel(device, parentType) {
47612
49255
  if (HUB_LIKE_PARENT_TYPES.has(parentType.toLowerCase())) return 1;
47613
49256
  return 0;
47614
49257
  }
47615
- function TableLayout({ rows, accessoriesByParent, autoExpandedParents, devices, visibleColumns, trpc, resolveIntegrationIcon, resolveParent, onNavigate, renderRowAction, onDeleteDevice, selectable, rowClickSelects, activeSelected, onToggleSelection, sort, onSort }) {
49258
+ function TableLayout({ rows, accessoriesByParent, expandedParents, onToggleExpand, devices, visibleColumns, trpc, resolveIntegrationIcon, resolveParent, onNavigate, renderRowAction, onDeleteDevice, selectable, rowClickSelects, activeSelected, onToggleSelection, sort, onSort }) {
47616
49259
  const showType = visibleColumns.includes("type");
47617
49260
  const showFeatures = visibleColumns.includes("features");
47618
49261
  const showManufacturer = visibleColumns.includes("manufacturer");
@@ -47671,7 +49314,6 @@ function TableLayout({ rows, accessoriesByParent, autoExpandedParents, devices,
47671
49314
  }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: rows.map((d) => {
47672
49315
  const accessories = accessoriesByParent.get(d.id) ?? [];
47673
49316
  const hasAccessories = accessories.length > 0;
47674
- const autoExpand = autoExpandedParents.has(d.id);
47675
49317
  const parentRowParent = d.parentDeviceId !== null ? resolveParent?.(d.parentDeviceId) ?? null : null;
47676
49318
  const parentType = d.parentDeviceId !== null ? parentTypeById.get(d.parentDeviceId) ?? null : null;
47677
49319
  const selection = selectable ? {
@@ -47682,7 +49324,8 @@ function TableLayout({ rows, accessoriesByParent, autoExpandedParents, devices,
47682
49324
  parent: d,
47683
49325
  accessories,
47684
49326
  hasAccessories,
47685
- forceExpanded: autoExpand,
49327
+ expanded: expandedParents.has(d.id),
49328
+ onToggleExpand: () => onToggleExpand(d.id),
47686
49329
  trpc,
47687
49330
  integrationIcon: showIcon ? resolveIntegrationIcon?.(d.addonId) ?? null : null,
47688
49331
  showType,
@@ -47700,16 +49343,7 @@ function TableLayout({ rows, accessoriesByParent, autoExpandedParents, devices,
47700
49343
  })
47701
49344
  });
47702
49345
  }
47703
- function TableRowGroup({ parent, accessories, hasAccessories, forceExpanded, trpc, integrationIcon, showType, showManufacturer, showFeatures, parentRowParent, indentLevel, onNavigate, renderRowAction, onDeleteDevice, selection, rowClickSelects }) {
47704
- const [expanded, setExpanded] = (0, react$1.useState)(forceExpanded);
47705
- const prevForce = (0, react$1.useRef)(forceExpanded);
47706
- (0, react$1.useEffect)(() => {
47707
- if (prevForce.current !== forceExpanded) {
47708
- setExpanded(forceExpanded);
47709
- prevForce.current = forceExpanded;
47710
- }
47711
- }, [forceExpanded]);
47712
- const onToggleExpand = () => setExpanded((v) => !v);
49346
+ function TableRowGroup({ parent, accessories, hasAccessories, expanded, onToggleExpand, trpc, integrationIcon, showType, showManufacturer, showFeatures, parentRowParent, indentLevel, onNavigate, renderRowAction, onDeleteDevice, selection, rowClickSelects }) {
47713
49347
  const showAccessoryRows = hasAccessories && expanded;
47714
49348
  const grouped = (0, react$1.useMemo)(() => groupChildrenByLayout(parent.stableId, accessories, parent.childLayout), [
47715
49349
  parent.stableId,
@@ -47976,13 +49610,15 @@ function DeviceModeList(props) {
47976
49610
  ]);
47977
49611
  const grouped = (0, react$1.useMemo)(() => groupTopLevelAndAccessories(filtered), [filtered]);
47978
49612
  const sortedTopLevel = (0, react$1.useMemo)(() => sortRows(grouped.topLevel, sort), [grouped.topLevel, sort]);
47979
- const autoExpandedParents = (0, react$1.useMemo)(() => {
47980
- if (url.state.q.trim() === "") return /* @__PURE__ */ new Set();
47981
- const q = url.state.q;
47982
- const matching = /* @__PURE__ */ new Set();
47983
- for (const [parentId, accessories] of grouped.accessoriesByParent.entries()) if (accessories.some((a) => fuzzyMatch(q, a.name) || fuzzyMatch(q, a.stableId))) matching.add(parentId);
47984
- return matching;
47985
- }, [grouped.accessoriesByParent, url.state.q]);
49613
+ const [openParents, setOpenParents] = (0, react$1.useState)(() => /* @__PURE__ */ new Set());
49614
+ const toggleParent = (0, react$1.useCallback)((id) => {
49615
+ setOpenParents((prev) => {
49616
+ const next = new Set(prev);
49617
+ if (next.has(id)) next.delete(id);
49618
+ else next.add(id);
49619
+ return next;
49620
+ });
49621
+ }, []);
47986
49622
  const totalItems = grouped.topLevel.length;
47987
49623
  const totalPages = Math.max(1, Math.ceil(totalItems / currentPageSize));
47988
49624
  const safePage = Math.min(url.state.page, totalPages);
@@ -48052,7 +49688,8 @@ function DeviceModeList(props) {
48052
49688
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TableLayout, {
48053
49689
  rows: pageRows,
48054
49690
  accessoriesByParent: grouped.accessoriesByParent,
48055
- autoExpandedParents,
49691
+ expandedParents: openParents,
49692
+ onToggleExpand: toggleParent,
48056
49693
  visibleColumns,
48057
49694
  sort,
48058
49695
  onSort,
@@ -48820,6 +50457,145 @@ function DeviceGrid({ children, minCardWidth = 220, gap = 3, className }) {
48820
50457
  });
48821
50458
  }
48822
50459
  //#endregion
50460
+ //#region src/composites/icon-picker.tsx
50461
+ /**
50462
+ * icon-picker — the ONE control for choosing an icon, wherever admin-ui asks.
50463
+ *
50464
+ * There were two: a device one over `DISPLAY_ICON_REGISTRY` and a room one
50465
+ * over `LOCATION_ICON_IDS`, identical apart from the vocabulary they walked.
50466
+ * That was tolerable while the device set was 56 glyphs in a flat grid. It is
50467
+ * not at 157: a picker that only lets the operator HUNT is a picker they stop
50468
+ * using, and the two copies were already drifting — only one of them had an
50469
+ * i18n'd label.
50470
+ *
50471
+ * So the vocabulary, the grouping and the search all come from
50472
+ * `@camstack/types`' `display-icon-vocabulary`, which is the same module the
50473
+ * viewer's sheet searches. What is left here is the web half: a popover, a
50474
+ * grid, and a text field.
50475
+ *
50476
+ * ## What it does NOT own
50477
+ *
50478
+ * - **The glyphs.** `resolveGlyph` is a prop, because this package's table is
50479
+ * `lucide-react` and the viewer's is `lucide-react-native`. A key this build
50480
+ * cannot draw is SKIPPED rather than rendered as a hole — an empty tile
50481
+ * reads as a rendering bug, not as a missing choice.
50482
+ * - **The words.** Every string is a prop. This package has no i18n (one file
50483
+ * in it imports react-i18next, and that is the exception), and a control
50484
+ * that reached for a translation would force it on every consumer.
50485
+ * - **The write.** `onPick` is called and that is all; whether it is a form
50486
+ * value or an immediate mutation is the caller's business, as it already was
50487
+ * in both pickers this replaces.
50488
+ *
50489
+ * ## The clear tile is never filtered away
50490
+ *
50491
+ * The "inherit" / "no icon" choice sits above the sections and ignores the
50492
+ * search. A query that hid it would strand the operator on a glyph they picked
50493
+ * by accident with no way back to the default — the one action a picker must
50494
+ * always offer.
50495
+ */
50496
+ function IconPicker({ value, onPick, resolveGlyph, fallbackGlyph, labels, only, extraWords, clearGlyph, labelFor, disabled = false }) {
50497
+ const [open, setOpen] = (0, react$1.useState)(false);
50498
+ const [query, setQuery] = (0, react$1.useState)("");
50499
+ const allowed = (0, react$1.useMemo)(() => only === void 0 ? null : new Set(only), [only]);
50500
+ const sections = (0, react$1.useMemo)(() => {
50501
+ const hits = (0, _camstack_types.searchDisplayIcons)(query, extraWords);
50502
+ const byGroup = /* @__PURE__ */ new Map();
50503
+ for (const hit of hits) {
50504
+ const { key, group } = hit.entry;
50505
+ if (allowed !== null && !allowed.has(key)) continue;
50506
+ if (resolveGlyph(key) === void 0) continue;
50507
+ const bucket = byGroup.get(group);
50508
+ if (bucket === void 0) byGroup.set(group, [key]);
50509
+ else bucket.push(key);
50510
+ }
50511
+ return _camstack_types.DISPLAY_ICON_GROUPS.flatMap((group) => {
50512
+ const keys = byGroup.get(group);
50513
+ return keys === void 0 || keys.length === 0 ? [] : [{
50514
+ group,
50515
+ keys
50516
+ }];
50517
+ });
50518
+ }, [
50519
+ query,
50520
+ extraWords,
50521
+ allowed,
50522
+ resolveGlyph
50523
+ ]);
50524
+ const Current = (value === null ? void 0 : resolveGlyph(value)) ?? fallbackGlyph;
50525
+ const choose = async (next) => {
50526
+ setOpen(false);
50527
+ setQuery("");
50528
+ await onPick(next);
50529
+ };
50530
+ const Clear = clearGlyph ?? fallbackGlyph;
50531
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
50532
+ className: "relative flex shrink-0 items-center gap-2",
50533
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
50534
+ type: "button",
50535
+ disabled,
50536
+ onClick: () => setOpen((o) => !o),
50537
+ "aria-expanded": open,
50538
+ "aria-label": labels.trigger,
50539
+ title: labels.trigger,
50540
+ className: "rounded border border-border p-1 text-foreground-muted hover:bg-surface-hover disabled:opacity-50",
50541
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Current, { className: "h-4 w-4" })
50542
+ }), open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
50543
+ type: "button",
50544
+ "aria-label": labels.close,
50545
+ onClick: () => setOpen(false),
50546
+ className: "fixed inset-0 z-10 cursor-default"
50547
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
50548
+ role: "group",
50549
+ "aria-label": labels.panel,
50550
+ className: "absolute left-0 top-full z-20 mt-1 max-h-80 w-72 overflow-y-auto rounded-md border border-border bg-surface p-2 shadow-lg",
50551
+ children: [
50552
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
50553
+ type: "search",
50554
+ value: query,
50555
+ autoFocus: true,
50556
+ onChange: (e) => setQuery(e.target.value),
50557
+ placeholder: labels.searchPlaceholder,
50558
+ "aria-label": labels.searchPlaceholder,
50559
+ className: "mb-2 w-full rounded border border-border bg-surface-raised px-2 py-1 text-xs text-foreground placeholder:text-foreground-subtle"
50560
+ }),
50561
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
50562
+ type: "button",
50563
+ onClick: () => void choose(null),
50564
+ "aria-pressed": value === null,
50565
+ className: "mb-2 flex w-full items-center gap-1.5 rounded px-1.5 py-1 text-[11px] text-foreground-subtle hover:bg-surface-hover",
50566
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Clear, { className: "h-3.5 w-3.5" }), labels.clear]
50567
+ }),
50568
+ sections.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
50569
+ className: "px-1.5 py-2 text-[11px] text-foreground-subtle",
50570
+ children: labels.noMatches
50571
+ }) : sections.map(({ group, keys }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
50572
+ className: "mb-2 last:mb-0",
50573
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
50574
+ className: "px-1.5 pb-1 text-[10px] uppercase tracking-wide text-foreground-subtle",
50575
+ children: labels.groups[group]
50576
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
50577
+ className: "grid grid-cols-7 gap-1",
50578
+ children: keys.map((key) => {
50579
+ const Glyph = resolveGlyph(key);
50580
+ if (Glyph === void 0) return null;
50581
+ const selected = value === key;
50582
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
50583
+ type: "button",
50584
+ onClick: () => void choose(key),
50585
+ "aria-pressed": selected,
50586
+ "aria-label": labelFor?.(key) ?? key,
50587
+ title: labelFor?.(key) ?? key,
50588
+ className: selected ? "flex items-center justify-center rounded border border-primary bg-primary/10 p-1.5 text-foreground" : "flex items-center justify-center rounded border border-transparent p-1.5 text-foreground-muted hover:bg-surface-hover",
50589
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Glyph, { className: "h-4 w-4" })
50590
+ }, key);
50591
+ })
50592
+ })]
50593
+ }, group))
50594
+ ]
50595
+ })] })]
50596
+ });
50597
+ }
50598
+ //#endregion
48823
50599
  //#region src/composites/device-selector-picker.tsx
48824
50600
  /**
48825
50601
  * <DeviceSelectorPicker> — the operator-facing editor for a v3
@@ -53773,7 +55549,7 @@ var MotionZonesSettings = (0, react$1.lazy)(() => Promise.resolve().then(() => r
53773
55549
  * inside `<Suspense>`; the admin-ui `/scenes` dialog is its only caller. */
53774
55550
  var SceneMonitorEditor = (0, react$1.lazy)(() => Promise.resolve().then(() => require("./SceneMonitorEditor-D0mXx_SD.cjs")).then((m) => ({ default: m.SceneMonitorEditor })));
53775
55551
  /** Lazy-wrapped `PrivacyMaskSettings` — code-split off the main bundle. */
53776
- var PrivacyMaskSettings = (0, react$1.lazy)(() => Promise.resolve().then(() => require("./PrivacyMaskSettings-DfZAFg32.cjs")).then((m) => ({ default: m.PrivacyMaskSettings })));
55552
+ var PrivacyMaskSettings = (0, react$1.lazy)(() => Promise.resolve().then(() => require("./PrivacyMaskSettings-BqjXsXZR.cjs")).then((m) => ({ default: m.PrivacyMaskSettings })));
53777
55553
  //#endregion
53778
55554
  exports.AddonGlobalSettingsForm = AddonGlobalSettingsForm;
53779
55555
  exports.AgentStepEditor = AgentStepEditor;
@@ -53845,6 +55621,7 @@ exports.DEVICE_TYPE_CONTROL = DEVICE_TYPE_CONTROL;
53845
55621
  exports.DEVICE_TYPE_META = DEVICE_TYPE_META;
53846
55622
  exports.DISPLAY_ICON_REGISTRY = DISPLAY_ICON_REGISTRY;
53847
55623
  exports.DataTable = DataTable;
55624
+ exports.DataTableFilterBar = DataTableFilterBar;
53848
55625
  exports.DetectionCanvas = DetectionCanvas;
53849
55626
  exports.DetectionOverlay = DetectionOverlay;
53850
55627
  exports.DetectionResultTree = DetectionResultTree;
@@ -53877,6 +55654,7 @@ exports.DropdownItem = DropdownItem;
53877
55654
  exports.DropdownTrigger = DropdownTrigger;
53878
55655
  exports.DummyHeroCard = DummyHeroCard;
53879
55656
  exports.DummyInline = DummyInline;
55657
+ exports.EMPTY_DATA_TABLE_FILTER = EMPTY_DATA_TABLE_FILTER;
53880
55658
  exports.EVENT_KIND_ICONS = EVENT_KIND_ICONS;
53881
55659
  exports.EmptyState = EmptyState;
53882
55660
  exports.ErrorBox = ErrorBox;
@@ -53909,6 +55687,7 @@ exports.HumidifierInlineControl = HumidifierInlineControl;
53909
55687
  exports.INPUT_COMPACT = INPUT_COMPACT;
53910
55688
  exports.IconAction = IconAction;
53911
55689
  exports.IconButton = IconButton;
55690
+ exports.IconPicker = IconPicker;
53912
55691
  exports.ImageHeroCard = ImageHeroCard;
53913
55692
  exports.ImageInlineControl = ImageInlineControl;
53914
55693
  exports.ImageOff = ImageOff;
@@ -53934,6 +55713,7 @@ exports.MediaPlayerHeroCard = MediaPlayerHeroCard;
53934
55713
  exports.MediaPlayerInlineControl = MediaPlayerInlineControl;
53935
55714
  exports.MediaPlayerPanel = MediaPlayerPanel;
53936
55715
  exports.Mic = Mic;
55716
+ exports.MicOff = MicOff;
53937
55717
  exports.MobileDrawer = MobileDrawer;
53938
55718
  exports.ModelPicker = ModelPicker;
53939
55719
  exports.MotionZonesSettings = MotionZonesSettings;
@@ -53972,6 +55752,8 @@ exports.RadialGauge = RadialGauge;
53972
55752
  exports.RecordedPlaybackProvider = RecordedPlaybackProvider;
53973
55753
  exports.RecordingPanel = RecordingPanel;
53974
55754
  exports.ResponseLog = ResponseLog;
55755
+ exports.RoleDisplayDefaultsFromServer = RoleDisplayDefaultsFromServer;
55756
+ exports.RoleDisplayDefaultsProvider = RoleDisplayDefaultsProvider;
53975
55757
  exports.RotateCcw = RotateCcw;
53976
55758
  exports.SCENE_CAPTURE_VARIANTS = SCENE_CAPTURE_VARIANTS;
53977
55759
  exports.SCENE_CAPTURE_VARIANTS$1 = SCENE_CAPTURE_VARIANTS;
@@ -54080,6 +55862,7 @@ exports.createSharedContext = createSharedContext;
54080
55862
  exports.createTheme = require_theme_index.createTheme$1;
54081
55863
  exports.cursorFractionFor = cursorFractionFor;
54082
55864
  exports.darkColors = require_theme_index.darkColors$1;
55865
+ exports.dataTableFacets = dataTableFacets;
54083
55866
  exports.defaultTheme = require_theme_index.defaultTheme$1;
54084
55867
  exports.deriveDeviceKind = deriveDeviceKind;
54085
55868
  exports.deriveWebrtcDebugStats = deriveWebrtcDebugStats;
@@ -54094,6 +55877,7 @@ exports.deviceTypeMetaOf = deviceTypeMetaOf;
54094
55877
  exports.devicesToOptions = devicesToOptions;
54095
55878
  exports.ensureMfHostInit = ensureMfHostInit;
54096
55879
  exports.eventKindLabel = eventKindLabel;
55880
+ exports.filterDataTableRows = filterDataTableRows;
54097
55881
  exports.filterDeviceOptions = filterDeviceOptions;
54098
55882
  exports.findTimezone = _camstack_types.findTimezone;
54099
55883
  exports.formatControlDateTime = formatControlDateTime;
@@ -54116,6 +55900,7 @@ exports.humidifierTint = humidifierTint;
54116
55900
  exports.initialScrubState = initialScrubState;
54117
55901
  exports.isAbsentProvider = isAbsentProvider;
54118
55902
  exports.isAbsentProvider$1 = isAbsentProvider;
55903
+ exports.isDataTableFilterActive = isDataTableFilterActive;
54119
55904
  exports.isFieldVisible = isFieldVisible;
54120
55905
  exports.lawnMowerActivityMeta = lawnMowerActivityMeta;
54121
55906
  exports.lightColors = require_theme_index.lightColors$1;
@@ -54168,6 +55953,7 @@ exports.tabForSelector = tabForSelector;
54168
55953
  exports.tankAlert = tankAlert;
54169
55954
  exports.themeToCss = require_theme_index.themeToCss$1;
54170
55955
  exports.toStationaryOverlayBox = toStationaryOverlayBox;
55956
+ exports.toggleDataTableFacet = toggleDataTableFacet;
54171
55957
  exports.trpc = trpc;
54172
55958
  exports.useAccessoriesGetStatus = useAccessoriesGetStatus;
54173
55959
  exports.useAccessoriesSetChildHidden = useAccessoriesSetChildHidden;
@@ -54347,6 +56133,7 @@ exports.useCoverSetPosition = useCoverSetPosition;
54347
56133
  exports.useCoverSetTiltPosition = useCoverSetTiltPosition;
54348
56134
  exports.useCoverStop = useCoverStop;
54349
56135
  exports.useCustomFieldRenderer = useCustomFieldRenderer;
56136
+ exports.useDataTableFilter = useDataTableFilter;
54350
56137
  exports.useDayNightGetOptions = useDayNightGetOptions;
54351
56138
  exports.useDayNightGetStatus = useDayNightGetStatus;
54352
56139
  exports.useDayNightSetSettings = useDayNightSetSettings;
@@ -55054,6 +56841,7 @@ exports.useRecordingSignalGetStatus = useRecordingSignalGetStatus;
55054
56841
  exports.useRecordingStartStorageMigrationMove = useRecordingStartStorageMigrationMove;
55055
56842
  exports.useRecordingStartStorageRebalance = useRecordingStartStorageRebalance;
55056
56843
  exports.useRemoteComponent = useRemoteComponent;
56844
+ exports.useRoleDisplayDefaults = useRoleDisplayDefaults;
55057
56845
  exports.useSceneMonitorCaptureReference = useSceneMonitorCaptureReference;
55058
56846
  exports.useSceneMonitorCreateScene = useSceneMonitorCreateScene;
55059
56847
  exports.useSceneMonitorDeleteReference = useSceneMonitorDeleteReference;
@@ -55259,6 +57047,7 @@ exports.useValveOpen = useValveOpen;
55259
57047
  exports.useValveSetPosition = useValveSetPosition;
55260
57048
  exports.useValveStop = useValveStop;
55261
57049
  exports.useVibrationGetStatus = useVibrationGetStatus;
57050
+ exports.useVideoclipsDialClipStream = useVideoclipsDialClipStream;
55262
57051
  exports.useVideoclipsGetClipPlayback = useVideoclipsGetClipPlayback;
55263
57052
  exports.useVideoclipsListClips = useVideoclipsListClips;
55264
57053
  exports.useVideoclipsListSources = useVideoclipsListSources;