@camstack/ui-library 1.2.226 → 1.2.228

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
@@ -6762,8 +6762,10 @@ var useButtonPress = trpc.button.press.useMutation;
6762
6762
  var useCalendarsListCalendars = trpc.calendars.listCalendars.useQuery;
6763
6763
  /** Generated alias around `trpc.calendars.listEvents.useQuery`. */
6764
6764
  var useCalendarsListEvents = trpc.calendars.listEvents.useQuery;
6765
- /** Generated alias around `trpc.calendars.importFromHomeAssistant.useMutation`. */
6766
- var useCalendarsImportFromHomeAssistant = trpc.calendars.importFromHomeAssistant.useMutation;
6765
+ /** Generated alias around `trpc.calendars.getPreferences.useQuery`. */
6766
+ var useCalendarsGetPreferences = trpc.calendars.getPreferences.useQuery;
6767
+ /** Generated alias around `trpc.calendars.setCalendarPreference.useMutation`. */
6768
+ var useCalendarsSetCalendarPreference = trpc.calendars.setCalendarPreference.useMutation;
6767
6769
  /** Generated alias around `trpc.cameraCredentials.getCredentials.useQuery`. */
6768
6770
  var useCameraCredentialsGetCredentials = trpc.cameraCredentials.getCredentials.useQuery;
6769
6771
  /** Generated alias around `trpc.cameraCredentials.getStatus.useQuery`. */
@@ -6850,6 +6852,10 @@ var useCoreBlocksSetEnabled = trpc.coreBlocks.setEnabled.useMutation;
6850
6852
  var useCoreBlocksRestart = trpc.coreBlocks.restart.useMutation;
6851
6853
  /** Generated alias around `trpc.coreBlocks.compile.useMutation`. */
6852
6854
  var useCoreBlocksCompile = trpc.coreBlocks.compile.useMutation;
6855
+ /** Generated alias around `trpc.coreBlocks.getBlockSettings.useQuery`. */
6856
+ var useCoreBlocksGetBlockSettings = trpc.coreBlocks.getBlockSettings.useQuery;
6857
+ /** Generated alias around `trpc.coreBlocks.updateBlockSettings.useMutation`. */
6858
+ var useCoreBlocksUpdateBlockSettings = trpc.coreBlocks.updateBlockSettings.useMutation;
6853
6859
  /** Generated alias around `trpc.coreBlocks.getTypeDefs.useQuery`. */
6854
6860
  var useCoreBlocksGetTypeDefs = trpc.coreBlocks.getTypeDefs.useQuery;
6855
6861
  /** Generated alias around `trpc.coreBlocks.createComposition.useMutation`. */
@@ -7590,8 +7596,12 @@ var useNodesExecuteQuery = trpc.nodes.executeQuery.useMutation;
7590
7596
  var useNotificationOutputListTargetKinds = trpc.notificationOutput.listTargetKinds.useQuery;
7591
7597
  /** Generated alias around `trpc.notificationOutput.listTargets.useQuery`. */
7592
7598
  var useNotificationOutputListTargets = trpc.notificationOutput.listTargets.useQuery;
7599
+ /** Generated alias around `trpc.notificationOutput.getTarget.useQuery`. */
7600
+ var useNotificationOutputGetTarget = trpc.notificationOutput.getTarget.useQuery;
7593
7601
  /** Generated alias around `trpc.notificationOutput.discoverTargets.useQuery`. */
7594
7602
  var useNotificationOutputDiscoverTargets = trpc.notificationOutput.discoverTargets.useQuery;
7603
+ /** Generated alias around `trpc.notificationOutput.probeTargetConfig.useMutation`. */
7604
+ var useNotificationOutputProbeTargetConfig = trpc.notificationOutput.probeTargetConfig.useMutation;
7595
7605
  /** Generated alias around `trpc.notificationOutput.send.useMutation`. */
7596
7606
  var useNotificationOutputSend = trpc.notificationOutput.send.useMutation;
7597
7607
  /** Generated alias around `trpc.notificationOutput.testTarget.useMutation`. */
@@ -7614,6 +7624,16 @@ var useNotificationRulesUpdateRule = trpc.notificationRules.updateRule.useMutati
7614
7624
  var useNotificationRulesDeleteRule = trpc.notificationRules.deleteRule.useMutation;
7615
7625
  /** Generated alias around `trpc.notificationRules.setRuleEnabled.useMutation`. */
7616
7626
  var useNotificationRulesSetRuleEnabled = trpc.notificationRules.setRuleEnabled.useMutation;
7627
+ /** Generated alias around `trpc.notificationRules.listSequences.useQuery`. */
7628
+ var useNotificationRulesListSequences = trpc.notificationRules.listSequences.useQuery;
7629
+ /** Generated alias around `trpc.notificationRules.getSequence.useQuery`. */
7630
+ var useNotificationRulesGetSequence = trpc.notificationRules.getSequence.useQuery;
7631
+ /** Generated alias around `trpc.notificationRules.createSequence.useMutation`. */
7632
+ var useNotificationRulesCreateSequence = trpc.notificationRules.createSequence.useMutation;
7633
+ /** Generated alias around `trpc.notificationRules.updateSequence.useMutation`. */
7634
+ var useNotificationRulesUpdateSequence = trpc.notificationRules.updateSequence.useMutation;
7635
+ /** Generated alias around `trpc.notificationRules.deleteSequence.useMutation`. */
7636
+ var useNotificationRulesDeleteSequence = trpc.notificationRules.deleteSequence.useMutation;
7617
7637
  /** Generated alias around `trpc.notificationRules.getClipRetentionAsks.useQuery`. */
7618
7638
  var useNotificationRulesGetClipRetentionAsks = trpc.notificationRules.getClipRetentionAsks.useQuery;
7619
7639
  /** Generated alias around `trpc.notificationRules.listDeviceMutes.useQuery`. */
@@ -12376,8 +12396,8 @@ function selectedDeviceOptions(options, selected) {
12376
12396
  */
12377
12397
  var INPUT_CLASS$1 = "w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-xs text-foreground focus:border-primary focus:ring-1 focus:ring-primary/30 outline-none disabled:opacity-50 disabled:cursor-not-allowed";
12378
12398
  var INPUT_SHELL$1 = "flex items-center gap-1.5 rounded-md border border-border bg-background px-2 py-1.5 focus-within:border-primary focus-within:ring-1 focus-within:ring-primary/30";
12379
- var LABEL_CLASS$2 = "block text-[11px] font-medium text-foreground mb-1 break-words";
12380
- var DESC_CLASS$1 = "text-[10px] text-foreground-subtle mt-0.5 break-words";
12399
+ var LABEL_CLASS$3 = "block text-[11px] font-medium text-foreground mb-1 break-words";
12400
+ var DESC_CLASS$2 = "text-[10px] text-foreground-subtle mt-0.5 break-words";
12381
12401
  /** The compact projection of the listAll rows the device fields consume. */
12382
12402
  function toCandidates(rows) {
12383
12403
  if (!Array.isArray(rows)) return [];
@@ -12464,14 +12484,14 @@ function FacetDropdown$1({ label, values, chosen, onChange }) {
12464
12484
  })]
12465
12485
  })] });
12466
12486
  }
12467
- function FieldShell({ label, description, required, translationFn, children }) {
12487
+ function FieldShell$1({ label, description, required, translationFn, children }) {
12468
12488
  const resolvedLabel = resolveLabel(label, translationFn);
12469
12489
  const resolvedDescription = resolveLabel(description, translationFn);
12470
12490
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
12471
12491
  className: "min-w-0",
12472
12492
  children: [
12473
12493
  resolvedLabel !== void 0 && resolvedLabel !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
12474
- className: LABEL_CLASS$2,
12494
+ className: LABEL_CLASS$3,
12475
12495
  children: [resolvedLabel, required && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
12476
12496
  className: "text-danger ml-0.5",
12477
12497
  children: "*"
@@ -12479,7 +12499,7 @@ function FieldShell({ label, description, required, translationFn, children }) {
12479
12499
  }),
12480
12500
  children,
12481
12501
  resolvedDescription && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
12482
- className: DESC_CLASS$1,
12502
+ className: DESC_CLASS$2,
12483
12503
  children: resolvedDescription
12484
12504
  })
12485
12505
  ]
@@ -12489,7 +12509,7 @@ function DeviceSelectField({ field, value, onChange, disabled, translationFn })
12489
12509
  const { data } = useDeviceManagerListAll({});
12490
12510
  const options = (0, react$1.useMemo)(() => devicesToOptions(toCandidates(data), field.deviceFilter), [data, field.deviceFilter]);
12491
12511
  const current = value === void 0 || value === null ? "" : String(value);
12492
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldShell, {
12512
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldShell$1, {
12493
12513
  label: field.label,
12494
12514
  description: field.description,
12495
12515
  required: field.required,
@@ -12567,7 +12587,7 @@ function DeviceMultiSelectField({ field, value, onChange, disabled, translationF
12567
12587
  className: "min-w-0",
12568
12588
  children: [
12569
12589
  label !== void 0 && label !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
12570
- className: LABEL_CLASS$2,
12590
+ className: LABEL_CLASS$3,
12571
12591
  children: label
12572
12592
  }),
12573
12593
  selectedOptions.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -12690,6 +12710,720 @@ function DeviceMultiSelectField({ field, value, onChange, disabled, translationF
12690
12710
  })
12691
12711
  }),
12692
12712
  description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
12713
+ className: DESC_CLASS$2,
12714
+ children: description
12715
+ })
12716
+ ]
12717
+ });
12718
+ }
12719
+ //#endregion
12720
+ //#region ../../node_modules/class-variance-authority/dist/index.mjs
12721
+ /**
12722
+ * Copyright 2022 Joe Bell. All rights reserved.
12723
+ *
12724
+ * This file is licensed to you under the Apache License, Version 2.0
12725
+ * (the "License"); you may not use this file except in compliance with the
12726
+ * License. You may obtain a copy of the License at
12727
+ *
12728
+ * http://www.apache.org/licenses/LICENSE-2.0
12729
+ *
12730
+ * Unless required by applicable law or agreed to in writing, software
12731
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12732
+ * WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or implied. See the
12733
+ * License for the specific language governing permissions and limitations under
12734
+ * the License.
12735
+ */ var falsyToString = (value) => typeof value === "boolean" ? `${value}` : value === 0 ? "0" : value;
12736
+ var cx = clsx;
12737
+ var cva = (base, config) => (props) => {
12738
+ var _config_compoundVariants;
12739
+ if ((config === null || config === void 0 ? void 0 : config.variants) == null) return cx(base, props === null || props === void 0 ? void 0 : props.class, props === null || props === void 0 ? void 0 : props.className);
12740
+ const { variants, defaultVariants } = config;
12741
+ const getVariantClassNames = Object.keys(variants).map((variant) => {
12742
+ const variantProp = props === null || props === void 0 ? void 0 : props[variant];
12743
+ const defaultVariantProp = defaultVariants === null || defaultVariants === void 0 ? void 0 : defaultVariants[variant];
12744
+ if (variantProp === null) return null;
12745
+ const variantKey = falsyToString(variantProp) || falsyToString(defaultVariantProp);
12746
+ return variants[variant][variantKey];
12747
+ });
12748
+ const propsWithoutUndefined = props && Object.entries(props).reduce((acc, param) => {
12749
+ let [key, value] = param;
12750
+ if (value === void 0) return acc;
12751
+ acc[key] = value;
12752
+ return acc;
12753
+ }, {});
12754
+ return cx(base, getVariantClassNames, config === null || config === void 0 ? void 0 : (_config_compoundVariants = config.compoundVariants) === null || _config_compoundVariants === void 0 ? void 0 : _config_compoundVariants.reduce((acc, param) => {
12755
+ let { class: cvClass, className: cvClassName, ...compoundVariantOptions } = param;
12756
+ return Object.entries(compoundVariantOptions).every((param) => {
12757
+ let [key, value] = param;
12758
+ return Array.isArray(value) ? value.includes({
12759
+ ...defaultVariants,
12760
+ ...propsWithoutUndefined
12761
+ }[key]) : {
12762
+ ...defaultVariants,
12763
+ ...propsWithoutUndefined
12764
+ }[key] === value;
12765
+ }) ? [
12766
+ ...acc,
12767
+ cvClass,
12768
+ cvClassName
12769
+ ] : acc;
12770
+ }, []), props === null || props === void 0 ? void 0 : props.class, props === null || props === void 0 ? void 0 : props.className);
12771
+ };
12772
+ //#endregion
12773
+ //#region src/primitives/badge.tsx
12774
+ var badgeVariants = cva("inline-flex items-center rounded-full text-xs font-medium px-2 py-0.5", {
12775
+ variants: {
12776
+ variant: {
12777
+ default: "bg-surface-hover text-foreground",
12778
+ success: "bg-success/15 text-success",
12779
+ warning: "bg-warning/15 text-warning",
12780
+ danger: "bg-danger/15 text-danger",
12781
+ info: "bg-info/15 text-info"
12782
+ },
12783
+ styleVariant: {
12784
+ solid: "",
12785
+ outline: "border bg-transparent"
12786
+ }
12787
+ },
12788
+ defaultVariants: {
12789
+ variant: "default",
12790
+ styleVariant: "solid"
12791
+ }
12792
+ });
12793
+ var Badge = (0, react$1.forwardRef)(({ className, variant, style, ...props }, ref) => {
12794
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
12795
+ ref,
12796
+ className: cn(badgeVariants({
12797
+ variant,
12798
+ styleVariant: style
12799
+ }), className),
12800
+ ...props
12801
+ });
12802
+ });
12803
+ Badge.displayName = "Badge";
12804
+ //#endregion
12805
+ //#region src/composites/event-kind-visuals.tsx
12806
+ /**
12807
+ * Event-kind VISUALS — the UI-side half of the unified taxonomy dictionary
12808
+ * (`@camstack/types` `EVENT_TAXONOMY` owns the serializable data:
12809
+ * color / iconId / labelKey; this owns `iconId → lucide component` and
12810
+ * `labelKey → t()`).
12811
+ *
12812
+ * Every UI that renders an event kind (timeline, events tree, chips,
12813
+ * detection canvas/tree) resolves its glyph + translated label here — no
12814
+ * per-component hardcoded icon or label.
12815
+ */
12816
+ /**
12817
+ * `iconId` → lucide component. Built ONLY from statically-imported icons
12818
+ * (bundle-safe). Unknown ids resolve to the neutral `Tag` fallback.
12819
+ */
12820
+ var EVENT_KIND_ICONS = {
12821
+ motion: Activity,
12822
+ audio: Volume2,
12823
+ person: User,
12824
+ vehicle: Car,
12825
+ animal: PawPrint,
12826
+ package: Package,
12827
+ sensor: Radio,
12828
+ control: SlidersHorizontal,
12829
+ car: Car,
12830
+ truck: Truck,
12831
+ bus: Bus,
12832
+ motorcycle: Bike,
12833
+ bicycle: Bike,
12834
+ airplane: Plane,
12835
+ boat: Sailboat,
12836
+ train: TrainFront,
12837
+ bird: Bird,
12838
+ cat: Cat,
12839
+ dog: Dog,
12840
+ horse: PawPrint,
12841
+ sheep: PawPrint,
12842
+ cow: PawPrint,
12843
+ elephant: PawPrint,
12844
+ bear: PawPrint,
12845
+ zebra: PawPrint,
12846
+ giraffe: PawPrint,
12847
+ door: DoorOpen,
12848
+ pir: Radar,
12849
+ smoke: Flame,
12850
+ water: Droplet,
12851
+ gas: Wind,
12852
+ vibration: Vibrate,
12853
+ tamper: ShieldAlert,
12854
+ presence: UserCheck,
12855
+ generic: Tag,
12856
+ lock: Lock,
12857
+ siren: Siren,
12858
+ switch: ToggleRight,
12859
+ doorbell: BellRing,
12860
+ button: CircleDot,
12861
+ "audio-speech": MessageSquare,
12862
+ "audio-scream": Megaphone,
12863
+ "audio-crying": Baby,
12864
+ "audio-laughter": Laugh,
12865
+ "audio-music": Music,
12866
+ "audio-dog": Dog,
12867
+ "audio-cat": Cat,
12868
+ "audio-bird": Bird,
12869
+ "audio-animal": PawPrint,
12870
+ "audio-alarm": BellRing,
12871
+ "audio-doorbell": BellRing,
12872
+ "audio-glass_breaking": TriangleAlert,
12873
+ "audio-gunshot": Crosshair,
12874
+ "audio-vehicle": Car,
12875
+ "audio-siren": Siren,
12876
+ "audio-fire": Flame,
12877
+ "audio-water": Droplets,
12878
+ "audio-wind": Wind,
12879
+ "audio-door": DoorOpen,
12880
+ "audio-footsteps": Footprints,
12881
+ "audio-crowd": Users,
12882
+ "audio-telephone": Phone,
12883
+ "audio-engine": Cog,
12884
+ "audio-tools": Hammer,
12885
+ "audio-silence": VolumeX
12886
+ };
12887
+ /** Resolve an `iconId` to a lucide component (falls back to `Tag`). */
12888
+ function resolveEventKindIcon(iconId) {
12889
+ if (Object.hasOwn(EVENT_KIND_ICONS, iconId)) return EVENT_KIND_ICONS[iconId];
12890
+ if (iconId.startsWith("audio-")) return Volume2;
12891
+ return Tag;
12892
+ }
12893
+ /**
12894
+ * Translated label for an event kind — `t(labelKey)` when a translation
12895
+ * exists, else the English `label` fallback. Robust to a missing key: an
12896
+ * i18n backend that echoes the key unchanged (react-i18next default) is
12897
+ * treated as "no translation" and the fallback label is returned, so the UI
12898
+ * never renders a raw `eventKind.*` key.
12899
+ */
12900
+ function eventKindLabel(descriptor, t) {
12901
+ if (t === void 0) return descriptor.label;
12902
+ const translated = t(descriptor.labelKey);
12903
+ return translated === descriptor.labelKey || translated.length === 0 ? descriptor.label : translated;
12904
+ }
12905
+ /** Small convenience component: renders the lucide glyph for an `iconId`. */
12906
+ function EventKindGlyph({ iconId, size = 16, color, className }) {
12907
+ return (0, react$1.createElement)(resolveEventKindIcon(iconId), {
12908
+ size,
12909
+ color,
12910
+ className
12911
+ });
12912
+ }
12913
+ //#endregion
12914
+ //#region src/composites/device-picker/SelectedChip.tsx
12915
+ /**
12916
+ * Removable selection chip + the search-box shell shared by DevicePicker and
12917
+ * SearchableMultiSelectField (admin-ui). Lives here so addon MFEs can render
12918
+ * the same picker without importing admin-ui.
12919
+ */
12920
+ /** The search box's shell — shared with multiselect fields that mirror this picker. */
12921
+ var INPUT_SHELL = "flex items-center gap-1.5 rounded-md border border-border bg-background px-2 py-1.5 focus-within:border-primary focus-within:ring-1 focus-within:ring-primary/30";
12922
+ /** One selected entry as a removable chip. */
12923
+ function SelectedChip({ label, onRemove, warning = false, iconId, iconUrl, testId }) {
12924
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
12925
+ "data-testid": testId,
12926
+ className: warning ? "inline-flex items-center gap-1 rounded-md bg-warning/10 px-1.5 py-0.5 text-[11px] text-warning max-w-full" : "inline-flex items-center gap-1 rounded-md bg-primary/10 px-1.5 py-0.5 text-[11px] text-primary max-w-full",
12927
+ children: [
12928
+ iconUrl !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
12929
+ src: iconUrl,
12930
+ alt: "",
12931
+ className: "h-3 w-3 shrink-0 rounded-sm object-contain"
12932
+ }) : iconId !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EventKindGlyph, {
12933
+ iconId,
12934
+ size: 12
12935
+ }) : null,
12936
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
12937
+ className: "truncate",
12938
+ children: label
12939
+ }),
12940
+ onRemove !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
12941
+ type: "button",
12942
+ className: "shrink-0 hover:text-danger",
12943
+ "aria-label": `Remove ${label}`,
12944
+ onClick: onRemove,
12945
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(X, { className: "h-2.5 w-2.5" })
12946
+ })
12947
+ ]
12948
+ });
12949
+ }
12950
+ //#endregion
12951
+ //#region src/composites/option-select/option-select-model.ts
12952
+ /** Number of options at/above which search is enabled by default (unless creatable). */
12953
+ var OPTION_SELECT_SEARCH_MIN_OPTIONS = 8;
12954
+ /** Fold an option into a single lower-cased haystack (value + label + description). */
12955
+ function optionSearchText(opt) {
12956
+ return [
12957
+ opt.value,
12958
+ opt.label,
12959
+ opt.description
12960
+ ].filter((p) => typeof p === "string" && p.length > 0).join(" ").toLowerCase();
12961
+ }
12962
+ /**
12963
+ * Filter options by a free-text query. An empty / whitespace-only query
12964
+ * returns every option (identity, preserving order).
12965
+ */
12966
+ function filterOptions(options, query) {
12967
+ const tokens = query.trim().toLowerCase().split(/\s+/).filter(Boolean);
12968
+ if (tokens.length === 0) return options;
12969
+ return options.filter((opt) => {
12970
+ const hay = optionSearchText(opt);
12971
+ return tokens.every((t) => hay.includes(t));
12972
+ });
12973
+ }
12974
+ /**
12975
+ * Split options into the currently-selected set (in selection order) and the
12976
+ * rest. Selected values with no matching option are surfaced as synthetic
12977
+ * options whose label falls back to the raw value.
12978
+ */
12979
+ function partitionSelected(options, selected) {
12980
+ const byValue = new Map(options.map((o) => [o.value, o]));
12981
+ const selectedSet = new Set(selected);
12982
+ return {
12983
+ selectedOptions: selected.map((v) => byValue.get(v) ?? {
12984
+ value: v,
12985
+ label: v
12986
+ }),
12987
+ unselected: options.filter((o) => !selectedSet.has(o.value))
12988
+ };
12989
+ }
12990
+ function resolveSearchable(optionCount, creatable, searchable) {
12991
+ if (searchable !== void 0) return searchable;
12992
+ return optionCount >= 8 || creatable;
12993
+ }
12994
+ /** Trimmed non-empty draft from the search box, or null when absent. */
12995
+ function draftFromQuery(query) {
12996
+ const trimmed = query.trim();
12997
+ return trimmed.length > 0 ? trimmed : null;
12998
+ }
12999
+ /**
13000
+ * True when an existing catalog option already represents this draft (value or
13001
+ * label, case-insensitive). Used to hide the “Use «query»” row when redundant.
13002
+ */
13003
+ function catalogHasDraft(options, draft) {
13004
+ const lower = draft.toLowerCase();
13005
+ return options.some((o) => o.value.toLowerCase() === lower || o.label.toLowerCase() === lower);
13006
+ }
13007
+ /**
13008
+ * Show the footer “Add new…” action only when creatable AND the caller
13009
+ * provided `onCreateRequest` (e.g. open a dedicated create dialog).
13010
+ *
13011
+ * Without that hook the footer was a no-op (it only focused the search box),
13012
+ * which read as “Add new is broken”. Free-form create already works via the
13013
+ * “Use «query»” row once the operator types — no footer needed.
13014
+ */
13015
+ function shouldShowAddNewFooter(creatable, hasCreateRequest) {
13016
+ return creatable && hasCreateRequest;
13017
+ }
13018
+ /**
13019
+ * Show the “Use «query»” commit row: creatable, a non-empty draft, and no
13020
+ * catalog option that already matches the draft.
13021
+ */
13022
+ function shouldShowUseQueryRow(creatable, query, options) {
13023
+ if (!creatable) return false;
13024
+ const draft = draftFromQuery(query);
13025
+ if (draft === null) return false;
13026
+ return !catalogHasDraft(options, draft);
13027
+ }
13028
+ //#endregion
13029
+ //#region src/composites/option-select/OptionSelect.tsx
13030
+ var LISTBOX_GAP_PX = 4;
13031
+ var LISTBOX_MAX_HEIGHT_PX = 224;
13032
+ var VIEWPORT_MARGIN_PX = 8;
13033
+ var LABEL_CLASS$2 = "block text-[11px] font-medium text-foreground mb-1 break-words";
13034
+ var DESC_CLASS$1 = "text-[10px] text-foreground-subtle mt-0.5 break-words";
13035
+ function OptionIcon({ opt }) {
13036
+ if (opt.iconUrl !== void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
13037
+ src: opt.iconUrl,
13038
+ alt: "",
13039
+ className: "h-3.5 w-3.5 shrink-0 rounded-sm object-contain"
13040
+ });
13041
+ if (opt.iconId !== void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EventKindGlyph, {
13042
+ iconId: opt.iconId,
13043
+ size: 13
13044
+ });
13045
+ return null;
13046
+ }
13047
+ function OptionSelect(props) {
13048
+ const { mode, options, placeholder = "Select…", disabled = false, emptyMessage = "No options available.", creatable = false, createLabel = "Add new…", onCreateRequest, label, description, testId, optionTestIdPrefix, listboxId: listboxIdProp } = props;
13049
+ const isDisabled = disabled;
13050
+ const searchable = resolveSearchable(options.length, creatable, props.searchable);
13051
+ const listboxId = listboxIdProp ?? testId ?? "option-select-listbox";
13052
+ const selectedValues = mode === "single" ? props.value.length > 0 ? [props.value] : [] : props.value;
13053
+ const maxItems = mode === "multi" ? props.maxItems : void 0;
13054
+ const atMax = mode === "multi" && maxItems !== void 0 && selectedValues.length >= maxItems;
13055
+ const [query, setQuery] = (0, react$1.useState)("");
13056
+ const [open, setOpen] = (0, react$1.useState)(false);
13057
+ const [highlight, setHighlight] = (0, react$1.useState)(0);
13058
+ const [coords, setCoords] = (0, react$1.useState)(null);
13059
+ const inputRef = (0, react$1.useRef)(null);
13060
+ const triggerRef = (0, react$1.useRef)(null);
13061
+ const listboxRef = (0, react$1.useRef)(null);
13062
+ const blurTimer = (0, react$1.useRef)(null);
13063
+ const computeListboxCoords = () => {
13064
+ const trigger = triggerRef.current;
13065
+ if (trigger === null) return null;
13066
+ const rect = trigger.getBoundingClientRect();
13067
+ const spaceBelow = window.innerHeight - rect.bottom - VIEWPORT_MARGIN_PX;
13068
+ const spaceAbove = rect.top - VIEWPORT_MARGIN_PX;
13069
+ const preferBelow = spaceBelow >= Math.min(LISTBOX_MAX_HEIGHT_PX, spaceAbove) || spaceBelow >= 120;
13070
+ const maxHeight = Math.max(96, Math.min(LISTBOX_MAX_HEIGHT_PX, preferBelow ? spaceBelow : spaceAbove));
13071
+ const left = Math.max(VIEWPORT_MARGIN_PX, Math.min(rect.left, window.innerWidth - rect.width - VIEWPORT_MARGIN_PX));
13072
+ if (preferBelow) return {
13073
+ top: rect.bottom + LISTBOX_GAP_PX,
13074
+ left,
13075
+ width: rect.width,
13076
+ maxHeight,
13077
+ placement: "below"
13078
+ };
13079
+ return {
13080
+ top: Math.max(VIEWPORT_MARGIN_PX, rect.top - LISTBOX_GAP_PX - maxHeight),
13081
+ left,
13082
+ width: rect.width,
13083
+ maxHeight,
13084
+ placement: "above"
13085
+ };
13086
+ };
13087
+ const { selectedOptions } = (0, react$1.useMemo)(() => partitionSelected(options, selectedValues), [options, selectedValues]);
13088
+ const filtered = (0, react$1.useMemo)(() => filterOptions(options, query), [options, query]);
13089
+ const showUseQuery = shouldShowUseQueryRow(creatable, query, options);
13090
+ const showAddNewFooter = shouldShowAddNewFooter(creatable, onCreateRequest !== void 0);
13091
+ const dropdownRows = (0, react$1.useMemo)(() => {
13092
+ const rows = filtered.map((opt, index) => ({
13093
+ kind: "option",
13094
+ opt,
13095
+ index
13096
+ }));
13097
+ if (showUseQuery) {
13098
+ const draft = draftFromQuery(query);
13099
+ if (draft !== null) rows.push({
13100
+ kind: "use-query",
13101
+ draft
13102
+ });
13103
+ }
13104
+ return rows;
13105
+ }, [
13106
+ filtered,
13107
+ showUseQuery,
13108
+ query
13109
+ ]);
13110
+ (0, react$1.useLayoutEffect)(() => {
13111
+ if (!open || isDisabled) {
13112
+ setCoords(null);
13113
+ return;
13114
+ }
13115
+ const update = () => {
13116
+ setCoords(computeListboxCoords());
13117
+ };
13118
+ update();
13119
+ window.addEventListener("resize", update);
13120
+ window.addEventListener("scroll", update, true);
13121
+ return () => {
13122
+ window.removeEventListener("resize", update);
13123
+ window.removeEventListener("scroll", update, true);
13124
+ };
13125
+ }, [
13126
+ open,
13127
+ isDisabled,
13128
+ selectedValues.length,
13129
+ query,
13130
+ dropdownRows.length
13131
+ ]);
13132
+ (0, react$1.useEffect)(() => {
13133
+ if (!open) return;
13134
+ const onPointer = (e) => {
13135
+ const target = e.target;
13136
+ if (listboxRef.current?.contains(target)) return;
13137
+ if (triggerRef.current?.contains(target)) return;
13138
+ setOpen(false);
13139
+ };
13140
+ document.addEventListener("mousedown", onPointer);
13141
+ return () => {
13142
+ document.removeEventListener("mousedown", onPointer);
13143
+ };
13144
+ }, [open]);
13145
+ const commitDraft = (draft) => {
13146
+ const trimmed = draft.trim();
13147
+ if (trimmed.length === 0) return;
13148
+ if (onCreateRequest !== void 0) {
13149
+ onCreateRequest(trimmed);
13150
+ setQuery("");
13151
+ setOpen(false);
13152
+ return;
13153
+ }
13154
+ if (mode === "single") {
13155
+ props.onChange(trimmed);
13156
+ setQuery("");
13157
+ setOpen(false);
13158
+ return;
13159
+ }
13160
+ if (props.value.includes(trimmed)) return;
13161
+ if (maxItems !== void 0 && props.value.length >= maxItems) return;
13162
+ props.onChange([...props.value, trimmed]);
13163
+ setQuery("");
13164
+ };
13165
+ const toggleMulti = (optValue) => {
13166
+ if (mode !== "multi" || isDisabled) return;
13167
+ if (props.value.includes(optValue)) props.onChange(props.value.filter((v) => v !== optValue));
13168
+ else {
13169
+ if (maxItems !== void 0 && props.value.length >= maxItems) return;
13170
+ props.onChange([...props.value, optValue]);
13171
+ }
13172
+ };
13173
+ const selectSingle = (optValue) => {
13174
+ if (mode !== "single" || isDisabled) return;
13175
+ props.onChange(optValue);
13176
+ setQuery("");
13177
+ setOpen(false);
13178
+ };
13179
+ const activateRow = (row) => {
13180
+ if (row.kind === "option") {
13181
+ if (row.opt.disabled === true) return;
13182
+ if (mode === "single") selectSingle(row.opt.value);
13183
+ else toggleMulti(row.opt.value);
13184
+ } else if (row.kind === "use-query") commitDraft(row.draft);
13185
+ };
13186
+ const rowCount = dropdownRows.length + (showAddNewFooter && !showUseQuery ? 1 : 0);
13187
+ const handleKeyDown = (e) => {
13188
+ if (e.key === "ArrowDown") {
13189
+ e.preventDefault();
13190
+ setOpen(true);
13191
+ setHighlight((h) => rowCount === 0 ? 0 : (h + 1) % rowCount);
13192
+ } else if (e.key === "ArrowUp") {
13193
+ e.preventDefault();
13194
+ setOpen(true);
13195
+ setHighlight((h) => rowCount === 0 ? 0 : (h - 1 + rowCount) % rowCount);
13196
+ } else if (e.key === "Enter") {
13197
+ e.preventDefault();
13198
+ if (showUseQuery && highlight === dropdownRows.length) {
13199
+ const draft = draftFromQuery(query);
13200
+ if (draft !== null) commitDraft(draft);
13201
+ return;
13202
+ }
13203
+ if (showAddNewFooter && !showUseQuery && highlight === dropdownRows.length) {
13204
+ inputRef.current?.focus();
13205
+ return;
13206
+ }
13207
+ const row = dropdownRows[highlight];
13208
+ if (row) activateRow(row);
13209
+ else if (creatable) {
13210
+ const draft = draftFromQuery(query);
13211
+ if (draft !== null) commitDraft(draft);
13212
+ }
13213
+ } else if (e.key === "Escape") setOpen(false);
13214
+ else if (e.key === "Backspace" && query === "" && mode === "multi" && props.value.length > 0) props.onChange(props.value.slice(0, -1));
13215
+ };
13216
+ const handleNonSearchKeyDown = (e) => {
13217
+ if (e.key === "Enter" || e.key === " ") {
13218
+ e.preventDefault();
13219
+ setOpen((o) => !o);
13220
+ } else if (e.key === "Escape") setOpen(false);
13221
+ };
13222
+ const removeChip = (value) => {
13223
+ if (mode === "single") props.onChange("");
13224
+ else props.onChange(props.value.filter((v) => v !== value));
13225
+ };
13226
+ const triggerPlaceholder = atMax === true ? `Max ${maxItems} selected` : placeholder;
13227
+ const showEmptyCatalog = options.length === 0 && !showUseQuery && dropdownRows.length === 0 && open;
13228
+ let dropdownBody;
13229
+ if (showEmptyCatalog) dropdownBody = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
13230
+ className: "px-2.5 py-2 text-[11px] text-foreground-subtle",
13231
+ children: emptyMessage
13232
+ });
13233
+ else if (dropdownRows.length === 0 && !creatable) dropdownBody = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
13234
+ className: "px-2.5 py-2 text-[11px] text-foreground-subtle italic",
13235
+ children: [
13236
+ "No matches for “",
13237
+ query,
13238
+ "”"
13239
+ ]
13240
+ });
13241
+ else dropdownBody = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: dropdownRows.map((row, i) => {
13242
+ if (row.kind === "use-query") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
13243
+ type: "button",
13244
+ role: "option",
13245
+ "aria-selected": false,
13246
+ className: [
13247
+ "flex w-full items-center gap-2 px-2.5 py-1.5 text-left text-xs transition-colors",
13248
+ i === highlight ? "bg-primary/10" : "hover:bg-surface-hover",
13249
+ "cursor-pointer"
13250
+ ].join(" "),
13251
+ onMouseEnter: () => setHighlight(i),
13252
+ onClick: () => commitDraft(row.draft),
13253
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Plus, { className: "h-3.5 w-3.5 shrink-0 text-primary" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
13254
+ className: "truncate text-foreground",
13255
+ children: [
13256
+ "Use “",
13257
+ row.draft,
13258
+ "”"
13259
+ ]
13260
+ })]
13261
+ }, "__use-query__");
13262
+ if (row.kind !== "option") return null;
13263
+ const { opt } = row;
13264
+ const checked = mode === "single" ? props.value === opt.value : props.value.includes(opt.value);
13265
+ const blockedByMax = mode === "multi" && !checked && atMax === true;
13266
+ const optionDisabled = opt.disabled === true || blockedByMax;
13267
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
13268
+ type: "button",
13269
+ role: "option",
13270
+ "aria-selected": checked,
13271
+ disabled: optionDisabled,
13272
+ "data-testid": optionTestIdPrefix !== void 0 ? `${optionTestIdPrefix}-${opt.value}` : void 0,
13273
+ className: [
13274
+ "flex w-full items-center gap-2 px-2.5 py-1.5 text-left text-xs transition-colors",
13275
+ i === highlight ? "bg-primary/10" : "hover:bg-surface-hover",
13276
+ optionDisabled ? "opacity-40 cursor-not-allowed" : "cursor-pointer"
13277
+ ].join(" "),
13278
+ onMouseEnter: () => setHighlight(i),
13279
+ onClick: () => activateRow(row),
13280
+ children: [
13281
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
13282
+ className: ["flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded border", checked ? "border-primary bg-primary text-white" : "border-border"].join(" "),
13283
+ children: checked && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Check, { className: "h-2.5 w-2.5" })
13284
+ }),
13285
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OptionIcon, { opt }),
13286
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
13287
+ className: "min-w-0 flex-1 truncate text-foreground",
13288
+ children: opt.label
13289
+ }),
13290
+ opt.badge !== void 0 && opt.badge.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
13291
+ variant: "default",
13292
+ children: opt.badge
13293
+ }) : null
13294
+ ]
13295
+ }, opt.value);
13296
+ }) });
13297
+ const footerAddNewIndex = dropdownRows.length;
13298
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
13299
+ className: "min-w-0 space-y-1",
13300
+ "data-testid": testId,
13301
+ children: [
13302
+ label !== void 0 && label.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
13303
+ className: LABEL_CLASS$2,
13304
+ children: label
13305
+ }),
13306
+ mode === "multi" && selectedOptions.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
13307
+ className: "flex flex-wrap gap-1 mb-1.5",
13308
+ children: selectedOptions.map((opt) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectedChip, {
13309
+ label: opt.label,
13310
+ ...opt.iconUrl !== void 0 ? { iconUrl: opt.iconUrl } : {},
13311
+ ...opt.iconId !== void 0 ? { iconId: opt.iconId } : {},
13312
+ ...!isDisabled ? { onRemove: () => removeChip(opt.value) } : {},
13313
+ ...optionTestIdPrefix !== void 0 ? { testId: `${optionTestIdPrefix}-chip-${opt.value}` } : {}
13314
+ }, opt.value))
13315
+ }),
13316
+ mode === "single" && selectedOptions.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
13317
+ className: "flex flex-wrap gap-1 mb-1.5",
13318
+ children: selectedOptions.map((opt) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectedChip, {
13319
+ label: opt.label,
13320
+ ...opt.iconUrl !== void 0 ? { iconUrl: opt.iconUrl } : {},
13321
+ ...opt.iconId !== void 0 ? { iconId: opt.iconId } : {},
13322
+ ...!isDisabled ? { onRemove: () => removeChip(opt.value) } : {}
13323
+ }, opt.value))
13324
+ }),
13325
+ searchable ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
13326
+ ref: (el) => {
13327
+ triggerRef.current = el;
13328
+ },
13329
+ className: INPUT_SHELL + (isDisabled ? " opacity-50" : ""),
13330
+ children: [
13331
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Search, { className: "h-3.5 w-3.5 shrink-0 text-foreground-subtle" }),
13332
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
13333
+ ref: inputRef,
13334
+ type: "text",
13335
+ role: "combobox",
13336
+ "aria-expanded": open,
13337
+ "aria-controls": listboxId,
13338
+ className: "min-w-0 flex-1 bg-transparent text-xs text-foreground outline-none placeholder:text-foreground-disabled",
13339
+ value: query,
13340
+ placeholder: triggerPlaceholder,
13341
+ disabled: isDisabled,
13342
+ onChange: (e) => {
13343
+ setQuery(e.target.value);
13344
+ setOpen(true);
13345
+ setHighlight(0);
13346
+ },
13347
+ onFocus: () => setOpen(true),
13348
+ onBlur: () => {
13349
+ blurTimer.current = setTimeout(() => {
13350
+ const active = document.activeElement;
13351
+ if (listboxRef.current?.contains(active)) return;
13352
+ setOpen(false);
13353
+ }, 120);
13354
+ },
13355
+ onKeyDown: handleKeyDown
13356
+ }),
13357
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDown, {
13358
+ className: "h-3.5 w-3.5 shrink-0 cursor-pointer text-foreground-subtle",
13359
+ onMouseDown: (e) => {
13360
+ e.preventDefault();
13361
+ if (blurTimer.current) clearTimeout(blurTimer.current);
13362
+ setOpen((o) => !o);
13363
+ inputRef.current?.focus();
13364
+ }
13365
+ })
13366
+ ]
13367
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
13368
+ ref: (el) => {
13369
+ triggerRef.current = el;
13370
+ },
13371
+ type: "button",
13372
+ role: "combobox",
13373
+ "aria-expanded": open,
13374
+ "aria-controls": listboxId,
13375
+ disabled: isDisabled,
13376
+ className: [
13377
+ INPUT_SHELL,
13378
+ "w-full justify-between text-xs text-foreground",
13379
+ isDisabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"
13380
+ ].join(" "),
13381
+ onClick: () => !isDisabled && setOpen((o) => !o),
13382
+ onKeyDown: handleNonSearchKeyDown,
13383
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
13384
+ className: "truncate text-foreground-subtle",
13385
+ children: selectedOptions[0]?.label ?? triggerPlaceholder
13386
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDown, { className: "h-3.5 w-3.5 shrink-0 text-foreground-subtle" })]
13387
+ }),
13388
+ open && !isDisabled && coords !== null && typeof document !== "undefined" && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
13389
+ ref: listboxRef,
13390
+ id: listboxId,
13391
+ role: "listbox",
13392
+ style: {
13393
+ position: "fixed",
13394
+ top: coords.top,
13395
+ left: coords.left,
13396
+ width: coords.width,
13397
+ maxHeight: coords.maxHeight,
13398
+ zIndex: 9999
13399
+ },
13400
+ className: "overflow-y-auto rounded-md border border-border bg-surface shadow-lg",
13401
+ onMouseDown: (e) => {
13402
+ e.preventDefault();
13403
+ if (blurTimer.current) clearTimeout(blurTimer.current);
13404
+ },
13405
+ children: [dropdownBody, showAddNewFooter && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
13406
+ type: "button",
13407
+ className: [
13408
+ "flex w-full items-center gap-2 border-t border-border px-2.5 py-1.5 text-left text-xs transition-colors",
13409
+ footerAddNewIndex === highlight ? "bg-primary/10" : "hover:bg-surface-hover",
13410
+ "cursor-pointer text-primary"
13411
+ ].join(" "),
13412
+ onMouseEnter: () => setHighlight(footerAddNewIndex),
13413
+ onClick: () => {
13414
+ const draft = draftFromQuery(query);
13415
+ if (draft !== null) {
13416
+ onCreateRequest?.(draft);
13417
+ setQuery("");
13418
+ setOpen(false);
13419
+ return;
13420
+ }
13421
+ inputRef.current?.focus();
13422
+ },
13423
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Plus, { className: "h-3.5 w-3.5 shrink-0" }), createLabel]
13424
+ })]
13425
+ }), document.body),
13426
+ description !== void 0 && description.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
12693
13427
  className: DESC_CLASS$1,
12694
13428
  children: description
12695
13429
  })
@@ -12697,6 +13431,242 @@ function DeviceMultiSelectField({ field, value, onChange, disabled, translationF
12697
13431
  });
12698
13432
  }
12699
13433
  //#endregion
13434
+ //#region src/composites/notification-targets-options.ts
13435
+ function kindIconKey(addonId, kind) {
13436
+ return `${addonId}:${kind}`;
13437
+ }
13438
+ function targetsToOptions(targets, kindIconUrls, noteIds, noteLabel) {
13439
+ return targets.map((target) => {
13440
+ const iconUrl = target.addonId !== void 0 && target.kind !== void 0 ? kindIconUrls.get(kindIconKey(target.addonId, target.kind)) : void 0;
13441
+ let badge;
13442
+ if (noteIds?.has(target.id) === true) badge = noteLabel;
13443
+ else if (!target.enabled) badge = "disabled";
13444
+ return {
13445
+ value: target.id,
13446
+ label: target.name,
13447
+ ...iconUrl !== void 0 ? { iconUrl } : {},
13448
+ ...badge !== void 0 ? { badge } : {}
13449
+ };
13450
+ });
13451
+ }
13452
+ function buildKindIconMap(rows) {
13453
+ const map = /* @__PURE__ */ new Map();
13454
+ if (!Array.isArray(rows)) return map;
13455
+ for (const row of rows) {
13456
+ if (row === null || typeof row !== "object") continue;
13457
+ const kind = Reflect.get(row, "kind");
13458
+ const addonId = Reflect.get(row, "addonId");
13459
+ const iconUrl = Reflect.get(row, "iconUrl");
13460
+ if (typeof kind !== "string" || typeof addonId !== "string") continue;
13461
+ if (typeof iconUrl === "string" && iconUrl.length > 0) map.set(kindIconKey(addonId, kind), iconUrl);
13462
+ }
13463
+ return map;
13464
+ }
13465
+ function toTargetOptions(rows) {
13466
+ if (!Array.isArray(rows)) return [];
13467
+ const out = [];
13468
+ for (const row of rows) {
13469
+ if (row === null || typeof row !== "object") continue;
13470
+ const id = Reflect.get(row, "id");
13471
+ const name = Reflect.get(row, "name");
13472
+ const enabled = Reflect.get(row, "enabled");
13473
+ const kind = Reflect.get(row, "kind");
13474
+ const addonId = Reflect.get(row, "addonId");
13475
+ if (typeof id !== "string" || typeof name !== "string") continue;
13476
+ if (typeof kind !== "string" || typeof addonId !== "string") continue;
13477
+ out.push({
13478
+ id,
13479
+ name,
13480
+ enabled: enabled === true,
13481
+ kind,
13482
+ addonId
13483
+ });
13484
+ }
13485
+ return out;
13486
+ }
13487
+ //#endregion
13488
+ //#region src/composites/notification-targets-field.tsx
13489
+ /**
13490
+ * Shared notification-output target pickers.
13491
+ *
13492
+ * The Notification Center editors (Summaries, Timelapses, Rules, Calendar
13493
+ * events) and ConfigUISchema forms (script settings, addon settings) all
13494
+ * pick the same catalog — `notificationOutput.listTargets`. One presentational
13495
+ * control plus two ConfigUISchema wrappers so a script can declare
13496
+ * `notification-target-select` / `notification-target-multiselect` without
13497
+ * inventing a parallel picker.
13498
+ *
13499
+ * A DISABLED target is offered and marked, never hidden: it is a legitimate
13500
+ * choice (the operator re-enables it later), and hiding it would make a rule
13501
+ * whose only target is off look like a rule with no target at all.
13502
+ *
13503
+ * Single and multi both render through `OptionSelect` (dropdown + pills,
13504
+ * searchable, kind brand icons via `iconUrl`).
13505
+ */
13506
+ var DEFAULT_EMPTY = "No delivery target is configured. Add one on the Targets tab first — without one the notification is rendered and thrown away.";
13507
+ /**
13508
+ * Multi-select of delivery targets (NC Summaries / Timelapses / Rules shape).
13509
+ */
13510
+ function NotificationTargetsField({ targets, value, onChange, disabled = false, maxItems, testIdPrefix = "notification-target", emptyMessage = DEFAULT_EMPTY, noteIds, noteLabel = "note", label, description, kindIconUrls: kindIconUrlsProp }) {
13511
+ const kindsQuery = useNotificationOutputListTargetKinds({});
13512
+ const kindIconUrlsFromQuery = (0, react$1.useMemo)(() => buildKindIconMap(kindsQuery.data), [kindsQuery.data]);
13513
+ const kindIconUrls = kindIconUrlsProp ?? kindIconUrlsFromQuery;
13514
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OptionSelect, {
13515
+ mode: "multi",
13516
+ options: (0, react$1.useMemo)(() => targetsToOptions(targets, kindIconUrls, noteIds, noteLabel), [
13517
+ targets,
13518
+ kindIconUrls,
13519
+ noteIds,
13520
+ noteLabel
13521
+ ]),
13522
+ value,
13523
+ onChange,
13524
+ disabled,
13525
+ searchable: true,
13526
+ creatable: false,
13527
+ ...maxItems !== void 0 ? { maxItems } : {},
13528
+ emptyMessage,
13529
+ placeholder: "Search targets…",
13530
+ label,
13531
+ description,
13532
+ testId: `${testIdPrefix}s`,
13533
+ optionTestIdPrefix: testIdPrefix,
13534
+ listboxId: `${testIdPrefix}-listbox`
13535
+ });
13536
+ }
13537
+ /**
13538
+ * Single-select of one delivery target (Calendar events / script settings).
13539
+ */
13540
+ function NotificationTargetSelectField({ targets, value, onChange, disabled = false, emptyMessage = DEFAULT_EMPTY, placeholder = "Select…", testId = "notification-target-select", label, description, kindIconUrls: kindIconUrlsProp }) {
13541
+ const kindsQuery = useNotificationOutputListTargetKinds({});
13542
+ const kindIconUrlsFromQuery = (0, react$1.useMemo)(() => buildKindIconMap(kindsQuery.data), [kindsQuery.data]);
13543
+ const kindIconUrls = kindIconUrlsProp ?? kindIconUrlsFromQuery;
13544
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OptionSelect, {
13545
+ mode: "single",
13546
+ options: (0, react$1.useMemo)(() => targetsToOptions(targets, kindIconUrls, void 0, "note"), [targets, kindIconUrls]),
13547
+ value,
13548
+ onChange,
13549
+ disabled,
13550
+ searchable: true,
13551
+ creatable: false,
13552
+ emptyMessage,
13553
+ placeholder,
13554
+ label,
13555
+ description,
13556
+ testId,
13557
+ optionTestIdPrefix: testId,
13558
+ listboxId: `${testId}-listbox`
13559
+ });
13560
+ }
13561
+ function FieldShell({ label, description, required, translationFn, children }) {
13562
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
13563
+ className: "space-y-1",
13564
+ children: [
13565
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
13566
+ className: "block text-[11px] font-medium text-foreground mb-1 break-words",
13567
+ children: [resolveLabel(label, translationFn), required === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
13568
+ className: "text-danger",
13569
+ children: " *"
13570
+ }) : null]
13571
+ }),
13572
+ children,
13573
+ description !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
13574
+ className: "text-[10px] text-foreground-subtle mt-0.5 break-words",
13575
+ children: resolveLabel(description, translationFn)
13576
+ })
13577
+ ]
13578
+ });
13579
+ }
13580
+ /**
13581
+ * ConfigUISchema `notification-target-multiselect` — fetches listTargets and
13582
+ * stores a `string[]` of target ids.
13583
+ */
13584
+ function ConfigNotificationTargetMultiSelectFieldView({ field, value, onChange, disabled, translationFn }) {
13585
+ const targetsQuery = useNotificationOutputListTargets({});
13586
+ const kindsQuery = useNotificationOutputListTargetKinds({});
13587
+ const targets = (0, react$1.useMemo)(() => toTargetOptions(targetsQuery.data), [targetsQuery.data]);
13588
+ const kindIconUrls = (0, react$1.useMemo)(() => buildKindIconMap(kindsQuery.data), [kindsQuery.data]);
13589
+ const selected = Array.isArray(value) ? value.filter((v) => typeof v === "string") : typeof value === "string" && value.length > 0 ? [value] : [];
13590
+ const pending = targetsQuery.isPending || kindsQuery.isPending;
13591
+ const error = targetsQuery.isError ? targetsQuery.error : kindsQuery.isError ? kindsQuery.error : null;
13592
+ if (pending) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldShell, {
13593
+ label: field.label,
13594
+ description: field.description,
13595
+ required: field.required,
13596
+ translationFn,
13597
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
13598
+ className: "text-[11px] text-foreground-subtle",
13599
+ children: "Loading targets…"
13600
+ })
13601
+ });
13602
+ if (error !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldShell, {
13603
+ label: field.label,
13604
+ description: field.description,
13605
+ required: field.required,
13606
+ translationFn,
13607
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
13608
+ className: "text-[11px] text-danger",
13609
+ children: ["Could not load targets: ", error instanceof Error ? error.message : String(error)]
13610
+ })
13611
+ });
13612
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NotificationTargetsField, {
13613
+ targets,
13614
+ value: selected,
13615
+ onChange: (next) => onChange(next),
13616
+ disabled: disabled === true || field.disabled === true,
13617
+ ...field.maxItems !== void 0 ? { maxItems: field.maxItems } : {},
13618
+ kindIconUrls,
13619
+ label: resolveLabel(field.label, translationFn),
13620
+ description: field.description !== void 0 ? resolveLabel(field.description, translationFn) : void 0,
13621
+ emptyMessage: "No delivery target is configured. Add one under Notification Center → Targets first.",
13622
+ testIdPrefix: `config-target-${field.key}`
13623
+ });
13624
+ }
13625
+ /**
13626
+ * ConfigUISchema `notification-target-select` — fetches listTargets and stores
13627
+ * one target id string.
13628
+ */
13629
+ function ConfigNotificationTargetSelectFieldView({ field, value, onChange, disabled, translationFn }) {
13630
+ const targetsQuery = useNotificationOutputListTargets({});
13631
+ const kindsQuery = useNotificationOutputListTargetKinds({});
13632
+ const targets = (0, react$1.useMemo)(() => toTargetOptions(targetsQuery.data), [targetsQuery.data]);
13633
+ const kindIconUrls = (0, react$1.useMemo)(() => buildKindIconMap(kindsQuery.data), [kindsQuery.data]);
13634
+ const current = typeof value === "string" ? value : "";
13635
+ const pending = targetsQuery.isPending || kindsQuery.isPending;
13636
+ const error = targetsQuery.isError ? targetsQuery.error : kindsQuery.isError ? kindsQuery.error : null;
13637
+ if (pending) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldShell, {
13638
+ label: field.label,
13639
+ description: field.description,
13640
+ required: field.required,
13641
+ translationFn,
13642
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
13643
+ className: "text-[11px] text-foreground-subtle",
13644
+ children: "Loading targets…"
13645
+ })
13646
+ });
13647
+ if (error !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldShell, {
13648
+ label: field.label,
13649
+ description: field.description,
13650
+ required: field.required,
13651
+ translationFn,
13652
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
13653
+ className: "text-[11px] text-danger",
13654
+ children: ["Could not load targets: ", error instanceof Error ? error.message : String(error)]
13655
+ })
13656
+ });
13657
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NotificationTargetSelectField, {
13658
+ targets,
13659
+ value: current,
13660
+ onChange: (next) => onChange(next),
13661
+ disabled: disabled === true || field.disabled === true,
13662
+ kindIconUrls,
13663
+ label: resolveLabel(field.label, translationFn),
13664
+ description: field.description !== void 0 ? resolveLabel(field.description, translationFn) : void 0,
13665
+ emptyMessage: "No delivery target is configured. Add one under Notification Center → Targets first.",
13666
+ testId: `config-target-${field.key}`
13667
+ });
13668
+ }
13669
+ //#endregion
12700
13670
  //#region src/contexts/custom-field-renderers.tsx
12701
13671
  /**
12702
13672
  * Custom field renderer registry.
@@ -12750,115 +13720,6 @@ function useCustomFieldRenderer(type) {
12750
13720
  return (0, react$1.useContext)(CustomFieldRenderersContext)[type] ?? null;
12751
13721
  }
12752
13722
  //#endregion
12753
- //#region src/composites/event-kind-visuals.tsx
12754
- /**
12755
- * Event-kind VISUALS — the UI-side half of the unified taxonomy dictionary
12756
- * (`@camstack/types` `EVENT_TAXONOMY` owns the serializable data:
12757
- * color / iconId / labelKey; this owns `iconId → lucide component` and
12758
- * `labelKey → t()`).
12759
- *
12760
- * Every UI that renders an event kind (timeline, events tree, chips,
12761
- * detection canvas/tree) resolves its glyph + translated label here — no
12762
- * per-component hardcoded icon or label.
12763
- */
12764
- /**
12765
- * `iconId` → lucide component. Built ONLY from statically-imported icons
12766
- * (bundle-safe). Unknown ids resolve to the neutral `Tag` fallback.
12767
- */
12768
- var EVENT_KIND_ICONS = {
12769
- motion: Activity,
12770
- audio: Volume2,
12771
- person: User,
12772
- vehicle: Car,
12773
- animal: PawPrint,
12774
- package: Package,
12775
- sensor: Radio,
12776
- control: SlidersHorizontal,
12777
- car: Car,
12778
- truck: Truck,
12779
- bus: Bus,
12780
- motorcycle: Bike,
12781
- bicycle: Bike,
12782
- airplane: Plane,
12783
- boat: Sailboat,
12784
- train: TrainFront,
12785
- bird: Bird,
12786
- cat: Cat,
12787
- dog: Dog,
12788
- horse: PawPrint,
12789
- sheep: PawPrint,
12790
- cow: PawPrint,
12791
- elephant: PawPrint,
12792
- bear: PawPrint,
12793
- zebra: PawPrint,
12794
- giraffe: PawPrint,
12795
- door: DoorOpen,
12796
- pir: Radar,
12797
- smoke: Flame,
12798
- water: Droplet,
12799
- gas: Wind,
12800
- vibration: Vibrate,
12801
- tamper: ShieldAlert,
12802
- presence: UserCheck,
12803
- generic: Tag,
12804
- lock: Lock,
12805
- siren: Siren,
12806
- switch: ToggleRight,
12807
- doorbell: BellRing,
12808
- button: CircleDot,
12809
- "audio-speech": MessageSquare,
12810
- "audio-scream": Megaphone,
12811
- "audio-crying": Baby,
12812
- "audio-laughter": Laugh,
12813
- "audio-music": Music,
12814
- "audio-dog": Dog,
12815
- "audio-cat": Cat,
12816
- "audio-bird": Bird,
12817
- "audio-animal": PawPrint,
12818
- "audio-alarm": BellRing,
12819
- "audio-doorbell": BellRing,
12820
- "audio-glass_breaking": TriangleAlert,
12821
- "audio-gunshot": Crosshair,
12822
- "audio-vehicle": Car,
12823
- "audio-siren": Siren,
12824
- "audio-fire": Flame,
12825
- "audio-water": Droplets,
12826
- "audio-wind": Wind,
12827
- "audio-door": DoorOpen,
12828
- "audio-footsteps": Footprints,
12829
- "audio-crowd": Users,
12830
- "audio-telephone": Phone,
12831
- "audio-engine": Cog,
12832
- "audio-tools": Hammer,
12833
- "audio-silence": VolumeX
12834
- };
12835
- /** Resolve an `iconId` to a lucide component (falls back to `Tag`). */
12836
- function resolveEventKindIcon(iconId) {
12837
- if (Object.hasOwn(EVENT_KIND_ICONS, iconId)) return EVENT_KIND_ICONS[iconId];
12838
- if (iconId.startsWith("audio-")) return Volume2;
12839
- return Tag;
12840
- }
12841
- /**
12842
- * Translated label for an event kind — `t(labelKey)` when a translation
12843
- * exists, else the English `label` fallback. Robust to a missing key: an
12844
- * i18n backend that echoes the key unchanged (react-i18next default) is
12845
- * treated as "no translation" and the fallback label is returned, so the UI
12846
- * never renders a raw `eventKind.*` key.
12847
- */
12848
- function eventKindLabel(descriptor, t) {
12849
- if (t === void 0) return descriptor.label;
12850
- const translated = t(descriptor.labelKey);
12851
- return translated === descriptor.labelKey || translated.length === 0 ? descriptor.label : translated;
12852
- }
12853
- /** Small convenience component: renders the lucide glyph for an `iconId`. */
12854
- function EventKindGlyph({ iconId, size = 16, color, className }) {
12855
- return (0, react$1.createElement)(resolveEventKindIcon(iconId), {
12856
- size,
12857
- color,
12858
- className
12859
- });
12860
- }
12861
- //#endregion
12862
13723
  //#region src/composites/confirm-dialog.tsx
12863
13724
  /**
12864
13725
  * The ONE modal dialog of the product — confirm, alert and prompt.
@@ -19106,59 +19967,6 @@ function useCapabilityPresence(capabilityName) {
19106
19967
  return classifyCapability(query.data);
19107
19968
  }
19108
19969
  //#endregion
19109
- //#region ../../node_modules/class-variance-authority/dist/index.mjs
19110
- /**
19111
- * Copyright 2022 Joe Bell. All rights reserved.
19112
- *
19113
- * This file is licensed to you under the Apache License, Version 2.0
19114
- * (the "License"); you may not use this file except in compliance with the
19115
- * License. You may obtain a copy of the License at
19116
- *
19117
- * http://www.apache.org/licenses/LICENSE-2.0
19118
- *
19119
- * Unless required by applicable law or agreed to in writing, software
19120
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
19121
- * WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or implied. See the
19122
- * License for the specific language governing permissions and limitations under
19123
- * the License.
19124
- */ var falsyToString = (value) => typeof value === "boolean" ? `${value}` : value === 0 ? "0" : value;
19125
- var cx = clsx;
19126
- var cva = (base, config) => (props) => {
19127
- var _config_compoundVariants;
19128
- if ((config === null || config === void 0 ? void 0 : config.variants) == null) return cx(base, props === null || props === void 0 ? void 0 : props.class, props === null || props === void 0 ? void 0 : props.className);
19129
- const { variants, defaultVariants } = config;
19130
- const getVariantClassNames = Object.keys(variants).map((variant) => {
19131
- const variantProp = props === null || props === void 0 ? void 0 : props[variant];
19132
- const defaultVariantProp = defaultVariants === null || defaultVariants === void 0 ? void 0 : defaultVariants[variant];
19133
- if (variantProp === null) return null;
19134
- const variantKey = falsyToString(variantProp) || falsyToString(defaultVariantProp);
19135
- return variants[variant][variantKey];
19136
- });
19137
- const propsWithoutUndefined = props && Object.entries(props).reduce((acc, param) => {
19138
- let [key, value] = param;
19139
- if (value === void 0) return acc;
19140
- acc[key] = value;
19141
- return acc;
19142
- }, {});
19143
- return cx(base, getVariantClassNames, config === null || config === void 0 ? void 0 : (_config_compoundVariants = config.compoundVariants) === null || _config_compoundVariants === void 0 ? void 0 : _config_compoundVariants.reduce((acc, param) => {
19144
- let { class: cvClass, className: cvClassName, ...compoundVariantOptions } = param;
19145
- return Object.entries(compoundVariantOptions).every((param) => {
19146
- let [key, value] = param;
19147
- return Array.isArray(value) ? value.includes({
19148
- ...defaultVariants,
19149
- ...propsWithoutUndefined
19150
- }[key]) : {
19151
- ...defaultVariants,
19152
- ...propsWithoutUndefined
19153
- }[key] === value;
19154
- }) ? [
19155
- ...acc,
19156
- cvClass,
19157
- cvClassName
19158
- ] : acc;
19159
- }, []), props === null || props === void 0 ? void 0 : props.class, props === null || props === void 0 ? void 0 : props.className);
19160
- };
19161
- //#endregion
19162
19970
  //#region src/primitives/button.tsx
19163
19971
  var buttonVariants = cva("inline-flex items-center justify-center rounded-md font-medium transition-colors cursor-pointer disabled:opacity-50 disabled:pointer-events-none", {
19164
19972
  variants: {
@@ -20021,38 +20829,6 @@ function ClipPanel({ deviceId, clip, profile, onProfileChange }) {
20021
20829
  });
20022
20830
  }
20023
20831
  //#endregion
20024
- //#region src/primitives/badge.tsx
20025
- var badgeVariants = cva("inline-flex items-center rounded-full text-xs font-medium px-2 py-0.5", {
20026
- variants: {
20027
- variant: {
20028
- default: "bg-surface-hover text-foreground",
20029
- success: "bg-success/15 text-success",
20030
- warning: "bg-warning/15 text-warning",
20031
- danger: "bg-danger/15 text-danger",
20032
- info: "bg-info/15 text-info"
20033
- },
20034
- styleVariant: {
20035
- solid: "",
20036
- outline: "border bg-transparent"
20037
- }
20038
- },
20039
- defaultVariants: {
20040
- variant: "default",
20041
- styleVariant: "solid"
20042
- }
20043
- });
20044
- var Badge = (0, react$1.forwardRef)(({ className, variant, style, ...props }, ref) => {
20045
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
20046
- ref,
20047
- className: cn(badgeVariants({
20048
- variant,
20049
- styleVariant: style
20050
- }), className),
20051
- ...props
20052
- });
20053
- });
20054
- Badge.displayName = "Badge";
20055
- //#endregion
20056
20832
  //#region src/lib/format-duration.ts
20057
20833
  /**
20058
20834
  * How long something lasted, the way this product says it.
@@ -26083,6 +26859,7 @@ function MultiSelectField({ field, value, onChange, disabled, translationFn }) {
26083
26859
  function PasswordField({ field, value, onChange, disabled, translationFn }) {
26084
26860
  const [show, setShow] = (0, react$1.useState)(false);
26085
26861
  const showToggle = field.showToggle !== false;
26862
+ const text = value === void 0 || value === null ? "" : String(value);
26086
26863
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldWrapper, {
26087
26864
  label: field.label,
26088
26865
  description: field.description,
@@ -26094,14 +26871,20 @@ function PasswordField({ field, value, onChange, disabled, translationFn }) {
26094
26871
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
26095
26872
  type: show ? "text" : "password",
26096
26873
  className: INPUT_CLASS + (showToggle ? " pr-9" : ""),
26097
- value: value === void 0 || value === null ? "" : String(value),
26874
+ value: text,
26098
26875
  placeholder: field.placeholder,
26099
26876
  disabled: disabled || field.disabled,
26100
- onChange: (e) => onChange(e.target.value)
26877
+ autoComplete: "new-password",
26878
+ spellCheck: false,
26879
+ onChange: (e) => onChange(e.target.value),
26880
+ onFocus: (e) => {
26881
+ if (text.length > 0 && /^[•●*]{4,}$/u.test(text)) e.currentTarget.select();
26882
+ }
26101
26883
  }), showToggle && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
26102
26884
  type: "button",
26103
26885
  className: "absolute right-2 top-1/2 -translate-y-1/2 text-foreground-subtle hover:text-foreground",
26104
26886
  tabIndex: -1,
26887
+ "aria-label": show ? "Hide password" : "Show password",
26105
26888
  onClick: () => setShow((s) => !s),
26106
26889
  children: show ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EyeOff, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Eye, { className: "h-3.5 w-3.5" })
26107
26890
  })]
@@ -26247,70 +27030,30 @@ function SliderField({ field, value, onChange, disabled, translationFn }) {
26247
27030
  });
26248
27031
  }
26249
27032
  function TagsField({ field, value, onChange, disabled, translationFn }) {
26250
- const tags = Array.isArray(value) ? value : [];
26251
- const [input, setInput] = (0, react$1.useState)("");
26252
- const inputRef = (0, react$1.useRef)(null);
26253
- const addTag = (0, react$1.useCallback)((tag) => {
26254
- const trimmed = tag.trim();
26255
- if (!trimmed || tags.includes(trimmed)) return;
26256
- if (field.maxTags !== void 0 && tags.length >= field.maxTags) return;
26257
- onChange([...tags, trimmed]);
26258
- setInput("");
26259
- }, [
26260
- tags,
26261
- field.maxTags,
26262
- onChange
26263
- ]);
26264
- const removeTag = (tag) => onChange(tags.filter((t) => t !== tag));
26265
- const handleKeyDown = (e) => {
26266
- if (e.key === "Enter" || e.key === ",") {
26267
- e.preventDefault();
26268
- addTag(input);
26269
- } else if (e.key === "Backspace" && input === "" && tags.length > 0) removeTag(tags[tags.length - 1]);
26270
- };
26271
- const isDisabled = disabled || field.disabled;
27033
+ const tags = Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
27034
+ const creatable = field.creatable !== false;
27035
+ const options = (field.suggestions ?? []).map((s) => ({
27036
+ value: s,
27037
+ label: s
27038
+ }));
27039
+ const isDisabled = disabled === true || field.disabled === true;
26272
27040
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldWrapper, {
26273
27041
  label: field.label,
26274
27042
  description: field.description,
26275
27043
  required: field.required,
26276
27044
  span: field.span,
26277
27045
  translationFn,
26278
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
26279
- className: [
26280
- "flex flex-wrap gap-1.5 rounded-md border border-border bg-background px-2 py-1.5 min-h-[38px] cursor-text",
26281
- "focus-within:border-primary focus-within:ring-1 focus-within:ring-primary/30",
26282
- isDisabled ? "opacity-50 cursor-not-allowed" : ""
26283
- ].join(" "),
26284
- onClick: () => !isDisabled && inputRef.current?.focus(),
26285
- children: [
26286
- tags.map((tag) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
26287
- className: "inline-flex items-center gap-1 rounded-md bg-primary/10 px-1.5 py-0.5 text-xs text-primary",
26288
- children: [tag, !isDisabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
26289
- type: "button",
26290
- onClick: () => removeTag(tag),
26291
- className: "hover:text-danger",
26292
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(X, { className: "h-2.5 w-2.5" })
26293
- })]
26294
- }, tag)),
26295
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
26296
- ref: inputRef,
26297
- type: "text",
26298
- className: "flex-1 min-w-[80px] bg-transparent text-sm text-foreground outline-none placeholder:text-foreground-disabled",
26299
- value: input,
26300
- placeholder: tags.length === 0 ? field.placeholder ?? "Add tags…" : "",
26301
- disabled: isDisabled,
26302
- list: `tags-suggestions-${field.key}`,
26303
- onChange: (e) => setInput(e.target.value),
26304
- onKeyDown: handleKeyDown,
26305
- onBlur: () => {
26306
- if (input.trim()) addTag(input);
26307
- }
26308
- }),
26309
- field.suggestions && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("datalist", {
26310
- id: `tags-suggestions-${field.key}`,
26311
- children: field.suggestions.map((s) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: s }, s))
26312
- })
26313
- ]
27046
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OptionSelect, {
27047
+ mode: "multi",
27048
+ options,
27049
+ value: tags,
27050
+ onChange: (next) => onChange(next),
27051
+ disabled: isDisabled,
27052
+ creatable,
27053
+ ...field.createLabel !== void 0 ? { createLabel: field.createLabel } : {},
27054
+ ...field.maxTags !== void 0 ? { maxItems: field.maxTags } : {},
27055
+ placeholder: field.placeholder ?? "Search or add…",
27056
+ testId: `config-tags-${field.key}`
26314
27057
  })
26315
27058
  });
26316
27059
  }
@@ -27542,6 +28285,20 @@ function FormField({ field, values, onChange, disabled, translationFn, onTestFie
27542
28285
  disabled,
27543
28286
  translationFn
27544
28287
  });
28288
+ case "notification-target-select": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfigNotificationTargetSelectFieldView, {
28289
+ field,
28290
+ value,
28291
+ onChange: handleChange,
28292
+ disabled,
28293
+ translationFn
28294
+ });
28295
+ case "notification-target-multiselect": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfigNotificationTargetMultiSelectFieldView, {
28296
+ field,
28297
+ value,
28298
+ onChange: handleChange,
28299
+ disabled,
28300
+ translationFn
28301
+ });
27545
28302
  case "probe": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProbeField, {
27546
28303
  field,
27547
28304
  value,
@@ -29074,9 +29831,6 @@ var DialogContent = (0, react$1.forwardRef)(({ className, width, placement, scro
29074
29831
  if (open && !el.open) el.showModal();
29075
29832
  if (!open && el.open) el.close();
29076
29833
  }, [open, dialogRef]);
29077
- const handleClick = (e) => {
29078
- if (e.target === e.currentTarget) setOpen(false);
29079
- };
29080
29834
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dialog", {
29081
29835
  ref: dialogRef,
29082
29836
  id: contentId,
@@ -29085,7 +29839,6 @@ var DialogContent = (0, react$1.forwardRef)(({ className, width, placement, scro
29085
29839
  placement,
29086
29840
  scroll
29087
29841
  }), className),
29088
- onClick: handleClick,
29089
29842
  onClose: () => setOpen(false),
29090
29843
  ...props,
29091
29844
  children
@@ -39282,7 +40035,7 @@ function ControlWidget({ slice, busy, setValueFn, layout }) {
39282
40035
  value: e.target.value
39283
40036
  } });
39284
40037
  },
39285
- className: cn("w-full max-w-full min-w-0 truncate rounded border border-border bg-surface px-1 py-0.5 text-xs", busy && "opacity-40 cursor-not-allowed"),
40038
+ className: cn("w-full max-w-full min-w-0 truncate rounded bg-transparent px-0.5 py-0.5 text-xs", busy && "opacity-40 cursor-not-allowed"),
39286
40039
  children: slice.options.map((opt) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
39287
40040
  value: opt,
39288
40041
  children: opt
@@ -40361,12 +41114,13 @@ function BatteryBar({ percent }) {
40361
41114
  });
40362
41115
  }
40363
41116
  function SensorHeroInner({ trpc, device }) {
40364
- const roleDescriptor = device.role != null ? ROLE_DESCRIPTOR[device.role] : void 0;
41117
+ const roleDescriptor = device.role != null ? ROLE_DESCRIPTOR[device.role] : ROLE_DESCRIPTOR["temperature-sensor"];
40365
41118
  const isBattery = isBatterySensorRole(device.role);
40366
41119
  const { data: roleDefaultsData } = useDeviceManagerGetRoleDisplayDefaults({}, { staleTime: 6e4 });
40367
41120
  const dev = useDeviceProxy(trpc, device.id);
40368
41121
  const numericSlice = useDeviceStateSlice(dev !== null && roleDescriptor !== void 0 ? roleDescriptor.getSliceHandle(dev.state) : void 0);
40369
41122
  const binaryBatterySlice = useDeviceStateSlice(dev !== null && isBattery ? dev.state.binary : void 0);
41123
+ const occupancySlice = useDeviceStateSlice(dev !== null && !isBattery ? dev.state.binary : void 0);
40370
41124
  const useBinaryBattery = isBattery && roleDescriptor?.kind === "numeric" && !hasBatteryNumericValue(numericSlice, roleDescriptor.field) && binaryBatterySlice !== void 0;
40371
41125
  const descriptor = useBinaryBattery ? BINARY_BATTERY_DESCRIPTOR : roleDescriptor;
40372
41126
  const slice = useBinaryBattery ? binaryBatterySlice : numericSlice;
@@ -40390,12 +41144,14 @@ function SensorHeroInner({ trpc, device }) {
40390
41144
  const resolved = resolveSensorDisplay({
40391
41145
  display: device.display,
40392
41146
  capName: device.role === "numeric-sensor" ? "numeric-sensor" : void 0,
40393
- roleDefault: device.role != null ? roleDefaultsData?.defaults[device.role] : void 0,
41147
+ roleDefault: device.role != null ? roleDefaultsData?.defaults[device.role] : roleDefaultsData?.defaults["temperature-sensor"],
40394
41148
  sliceUnit: liveUnit,
40395
41149
  slicePrecision,
40396
41150
  canonicalUnit: descriptor.canonicalUnit
40397
41151
  });
40398
41152
  const unit = resolved.unit;
41153
+ const formatted = formatNumeric(resolved.toDisplayValue(rawValue), resolved.precision);
41154
+ const occupancyOn = occupancySlice !== void 0 && isBinarySliceValue(occupancySlice) && typeof occupancySlice["on"] === "boolean" ? Boolean(occupancySlice["on"]) : null;
40399
41155
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
40400
41156
  className: "flex flex-col items-center justify-center gap-2 rounded-lg border border-border bg-surface p-4",
40401
41157
  children: [
@@ -40403,13 +41159,18 @@ function SensorHeroInner({ trpc, device }) {
40403
41159
  className: "flex items-baseline gap-1",
40404
41160
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
40405
41161
  className: "text-3xl font-semibold tabular-nums text-foreground",
40406
- children: formatNumeric(resolved.toDisplayValue(rawValue), resolved.precision)
41162
+ children: formatted
40407
41163
  }), unit && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
40408
41164
  className: "text-base text-foreground-subtle",
40409
41165
  children: unit
40410
41166
  })]
40411
41167
  }),
40412
41168
  isBattery && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BatteryBar, { percent: rawValue }),
41169
+ occupancyOn !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
41170
+ className: cn("inline-flex items-center rounded px-2 py-0.5 text-xs font-medium", occupancyOn ? "bg-warning/15 text-warning" : "bg-surface-subtle text-foreground-subtle"),
41171
+ "data-testid": "sensor-hero-occupancy",
41172
+ children: occupancyOn ? "Occupied" : "Clear"
41173
+ }),
40413
41174
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
40414
41175
  className: "text-sm text-foreground-subtle",
40415
41176
  children: device.name
@@ -48239,11 +49000,11 @@ function useDeviceAdminLink(trpc, deviceId) {
48239
49000
  cap: (d) => d.deviceAdminLink,
48240
49001
  slice: () => void 0
48241
49002
  });
48242
- const bound = dev !== null && cap !== void 0 && cap !== null;
49003
+ const capBound = dev !== null && cap !== void 0 && cap !== null && (dev.binding?.entries.some((e) => e.capName === "device-admin-link") ?? true);
48243
49004
  const [state, setState] = (0, react$1.useState)({ kind: "pending" });
48244
49005
  (0, react$1.useEffect)(() => {
48245
- if (!bound || cap === void 0 || cap === null) {
48246
- setState({ kind: "pending" });
49006
+ if (!capBound || cap === void 0 || cap === null) {
49007
+ setState({ kind: "none" });
48247
49008
  return;
48248
49009
  }
48249
49010
  let live = true;
@@ -48264,7 +49025,7 @@ function useDeviceAdminLink(trpc, deviceId) {
48264
49025
  live = false;
48265
49026
  };
48266
49027
  }, [
48267
- bound,
49028
+ capBound,
48268
49029
  cap,
48269
49030
  deviceId
48270
49031
  ]);
@@ -49338,6 +50099,51 @@ function DeviceItemPreview({ trpc, device, status, enabled, showStatusPills = fa
49338
50099
  });
49339
50100
  }
49340
50101
  //#endregion
50102
+ //#region src/contexts/device-quick-detail.tsx
50103
+ /**
50104
+ * Optional host-provided "open quick detail for this device id" callback.
50105
+ *
50106
+ * The devices table (`DeviceItem` / `DeviceList`) draws a discreet quick-
50107
+ * detail glyph whenever a host has published a handler — admin-ui mounts
50108
+ * ONE provider at the app shell with the modal. Call sites never wire the
50109
+ * button themselves; absence of a provider means no glyph (unit tests,
50110
+ * embeds that do not host the modal).
50111
+ */
50112
+ var DeviceQuickDetailContext = createSharedContext("camstack:device-quick-detail", null);
50113
+ function DeviceQuickDetailProvider({ open, children }) {
50114
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceQuickDetailContext.Provider, {
50115
+ value: open,
50116
+ children
50117
+ });
50118
+ }
50119
+ /** The host's open callback, or null when no provider is mounted. */
50120
+ function useDeviceQuickDetailOpen() {
50121
+ return (0, react$1.useContext)(DeviceQuickDetailContext);
50122
+ }
50123
+ //#endregion
50124
+ //#region src/composites/device-item/quick-detail-action.tsx
50125
+ /**
50126
+ * Built-in row glyph for the host's quick-detail modal. Renders nothing
50127
+ * unless a `DeviceQuickDetailProvider` is mounted above — same "draw
50128
+ * nothing rather than something dead" rule as `AdminLinkQuickAction`.
50129
+ */
50130
+ function DeviceQuickDetailAction({ deviceId, className }) {
50131
+ const open = useDeviceQuickDetailOpen();
50132
+ if (open === null) return null;
50133
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
50134
+ type: "button",
50135
+ onClick: (e) => {
50136
+ e.stopPropagation();
50137
+ open(deviceId);
50138
+ },
50139
+ className: cn("inline-flex items-center justify-center rounded text-foreground-subtle hover:text-foreground transition-colors flex-shrink-0", className),
50140
+ title: "Quick detail",
50141
+ "aria-label": "Quick detail",
50142
+ "data-testid": "device-quick-detail",
50143
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Settings, { className: "h-3 w-3" })
50144
+ });
50145
+ }
50146
+ //#endregion
49341
50147
  //#region src/composites/device-item/status-dot.tsx
49342
50148
  /** The states that breathe (are "alive"). Offline + disabled are static. */
49343
50149
  function isAlive(status) {
@@ -49615,6 +50421,7 @@ function DeviceItem(props) {
49615
50421
  flags,
49616
50422
  controlPlacement: "right"
49617
50423
  }),
50424
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceQuickDetailAction, { deviceId: device.id }),
49618
50425
  renderRowAction && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
49619
50426
  onClick: (e) => e.stopPropagation(),
49620
50427
  children: renderRowAction(device)
@@ -49697,6 +50504,7 @@ function DeviceItem(props) {
49697
50504
  flags,
49698
50505
  controlPlacement: "right"
49699
50506
  }),
50507
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceQuickDetailAction, { deviceId: device.id }),
49700
50508
  renderRowAction && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
49701
50509
  onClick: (e) => e.stopPropagation(),
49702
50510
  children: renderRowAction(device)
@@ -49798,6 +50606,7 @@ function DeviceItemTableRow(props) {
49798
50606
  enabled: showSnapshot && isCamera,
49799
50607
  showStatusPills: true
49800
50608
  }),
50609
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceQuickDetailAction, { deviceId: device.id }),
49801
50610
  renderRowAction && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
49802
50611
  onClick: (e) => e.stopPropagation(),
49803
50612
  children: renderRowAction(device)
@@ -50895,10 +51704,10 @@ function useDeviceListRealtimeSync() {
50895
51704
  * `renderItem`.
50896
51705
  */
50897
51706
  function GenericModeList(props) {
50898
- const { items, getKey, getSearchText, renderItem, getFilterType, mapFilterOption, filterOptions, filterLabel = "Type", columns, renderExpanded, isExpanded, rowClassName, onRowClick, isLoading = false, searchPlaceholder = "Search", emptyLabel = "No items found.", pageSize = 100, toolbar } = props;
51707
+ const { items, getKey, getSearchText, renderItem, getFilterType, mapFilterOption, filterOptions, filterLabel = "Type", initialTypeFilters = [], columns, renderExpanded, isExpanded, rowClassName, onRowClick, isLoading = false, searchPlaceholder = "Search", emptyLabel = "No items found.", pageSize = 100, toolbar } = props;
50899
51708
  const [searchInput, setSearchInput] = (0, react$1.useState)("");
50900
51709
  const debouncedSearch = useDebouncedString(searchInput, 300);
50901
- const [typeFilters, setTypeFilters] = (0, react$1.useState)([]);
51710
+ const [typeFilters, setTypeFilters] = (0, react$1.useState)(() => [...initialTypeFilters]);
50902
51711
  const [page, setPage] = (0, react$1.useState)(1);
50903
51712
  (0, react$1.useEffect)(() => {
50904
51713
  setPage(1);
@@ -55764,39 +56573,6 @@ function VersionBadge({ version, preRelease, className }) {
55764
56573
  });
55765
56574
  }
55766
56575
  //#endregion
55767
- //#region src/composites/device-picker/SelectedChip.tsx
55768
- /**
55769
- * Removable selection chip + the search-box shell shared by DevicePicker and
55770
- * SearchableMultiSelectField (admin-ui). Lives here so addon MFEs can render
55771
- * the same picker without importing admin-ui.
55772
- */
55773
- /** The search box's shell — shared with multiselect fields that mirror this picker. */
55774
- var INPUT_SHELL = "flex items-center gap-1.5 rounded-md border border-border bg-background px-2 py-1.5 focus-within:border-primary focus-within:ring-1 focus-within:ring-primary/30";
55775
- /** One selected entry as a removable chip. */
55776
- function SelectedChip({ label, onRemove, warning = false, iconId, testId }) {
55777
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
55778
- "data-testid": testId,
55779
- className: warning ? "inline-flex items-center gap-1 rounded-md bg-warning/10 px-1.5 py-0.5 text-[11px] text-warning max-w-full" : "inline-flex items-center gap-1 rounded-md bg-primary/10 px-1.5 py-0.5 text-[11px] text-primary max-w-full",
55780
- children: [
55781
- iconId !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EventKindGlyph, {
55782
- iconId,
55783
- size: 12
55784
- }),
55785
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
55786
- className: "truncate",
55787
- children: label
55788
- }),
55789
- onRemove !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
55790
- type: "button",
55791
- className: "shrink-0 hover:text-danger",
55792
- "aria-label": `Remove ${label}`,
55793
- onClick: onRemove,
55794
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(X, { className: "h-2.5 w-2.5" })
55795
- })
55796
- ]
55797
- });
55798
- }
55799
- //#endregion
55800
56576
  //#region src/composites/device-picker/device-picker-model.ts
55801
56577
  /** Numbers numerically, anything else by its text — the name tie-break. */
55802
56578
  function compareIds(a, b) {
@@ -57249,6 +58025,42 @@ function useZoneEditing() {
57249
58025
  return (0, react$1.useContext)(ZoneEditingContext);
57250
58026
  }
57251
58027
  //#endregion
58028
+ //#region src/hooks/use-url-query.ts
58029
+ /**
58030
+ * Tiny URL query sync for surfaces that cannot assume react-router
58031
+ * (federated addon pages) or that only need a handful of params.
58032
+ *
58033
+ * Reads on mount + `popstate`; writes with `history.replaceState`. Empty /
58034
+ * null values are deleted so the URL stays short.
58035
+ */
58036
+ function readParam(key) {
58037
+ return new URLSearchParams(window.location.search).get(key);
58038
+ }
58039
+ function writeParam(key, value) {
58040
+ const params = new URLSearchParams(window.location.search);
58041
+ if (value === null || value === "") params.delete(key);
58042
+ else params.set(key, value);
58043
+ const qs = params.toString();
58044
+ const url = qs ? `${window.location.pathname}?${qs}` : window.location.pathname;
58045
+ window.history.replaceState(null, "", url);
58046
+ }
58047
+ /**
58048
+ * One query param as React state. `null` means absent.
58049
+ * Changing it replaces the URL without a navigation.
58050
+ */
58051
+ function useUrlQueryParam(key) {
58052
+ const [value, setValue] = (0, react$1.useState)(() => readParam(key));
58053
+ (0, react$1.useEffect)(() => {
58054
+ const onPop = () => setValue(readParam(key));
58055
+ window.addEventListener("popstate", onPop);
58056
+ return () => window.removeEventListener("popstate", onPop);
58057
+ }, [key]);
58058
+ return [value, (0, react$1.useCallback)((next) => {
58059
+ writeParam(key, next);
58060
+ setValue(next);
58061
+ }, [key])];
58062
+ }
58063
+ //#endregion
57252
58064
  //#region src/hooks/use-device-cap-slice.ts
57253
58065
  /**
57254
58066
  * useDeviceCapSlice — generic runtime-state slice reader for an arbitrary
@@ -58138,6 +58950,8 @@ exports.CodeBlock = CodeBlock;
58138
58950
  exports.CollapsibleCard = CollapsibleCard;
58139
58951
  exports.ConfigFormBuilder = ConfigFormBuilder;
58140
58952
  exports.ConfigFormField = FormField;
58953
+ exports.ConfigNotificationTargetMultiSelectFieldView = ConfigNotificationTargetMultiSelectFieldView;
58954
+ exports.ConfigNotificationTargetSelectFieldView = ConfigNotificationTargetSelectFieldView;
58141
58955
  exports.ConfigSchemaField = ConfigSchemaField;
58142
58956
  exports.ConfirmActionButton = ConfirmActionButton;
58143
58957
  exports.ConfirmDialogProvider = ConfirmDialogProvider;
@@ -58179,6 +58993,7 @@ exports.DeviceItem = DeviceItem;
58179
58993
  exports.DeviceList = DeviceList;
58180
58994
  exports.DeviceMultiSelectField = DeviceMultiSelectField;
58181
58995
  exports.DevicePicker = DevicePicker;
58996
+ exports.DeviceQuickDetailProvider = DeviceQuickDetailProvider;
58182
58997
  exports.DeviceSelectField = DeviceSelectField;
58183
58998
  exports.DeviceSelectorPicker = DeviceSelectorPicker;
58184
58999
  exports.DeviceStepMatrix = DeviceStepMatrix;
@@ -58272,7 +59087,11 @@ exports.NetworkLinkBadge = NetworkLinkBadge;
58272
59087
  exports.NodeMultiSelectField = NodeMultiSelectField;
58273
59088
  exports.NodePicker = NodePicker;
58274
59089
  exports.NodeSelectField = NodeSelectField;
59090
+ exports.NotificationTargetSelectField = NotificationTargetSelectField;
59091
+ exports.NotificationTargetsField = NotificationTargetsField;
59092
+ exports.OPTION_SELECT_SEARCH_MIN_OPTIONS = OPTION_SELECT_SEARCH_MIN_OPTIONS;
58275
59093
  exports.OfflineBadge = OfflineBadge;
59094
+ exports.OptionSelect = OptionSelect;
58276
59095
  exports.PHASE_CONFIG = PHASE_CONFIG;
58277
59096
  exports.PICKER_CHIP_LIMIT = PICKER_CHIP_LIMIT;
58278
59097
  exports.PICKER_DEFAULT_GROUP_BY = PICKER_DEFAULT_GROUP_BY;
@@ -58410,6 +59229,7 @@ exports.ariaSortForColumn = ariaSortForColumn;
58410
59229
  exports.badgeFor = badgeFor;
58411
59230
  exports.badgeFor$1 = badgeFor;
58412
59231
  exports.buildStepTreeFromSchema = buildStepTreeFromSchema;
59232
+ exports.catalogHasDraft = catalogHasDraft;
58413
59233
  exports.childEntityId = childEntityId;
58414
59234
  exports.childListName = childListName;
58415
59235
  exports.chipOverflow = chipOverflow;
@@ -58442,11 +59262,13 @@ exports.deviceTypeLabel = deviceTypeLabel;
58442
59262
  exports.deviceTypeMeta = deviceTypeMeta;
58443
59263
  exports.deviceTypeMetaOf = deviceTypeMetaOf;
58444
59264
  exports.devicesToOptions = devicesToOptions;
59265
+ exports.draftFromQuery = draftFromQuery;
58445
59266
  exports.emptyDevicesWidens = emptyDevicesWidens;
58446
59267
  exports.ensureMfHostInit = ensureMfHostInit;
58447
59268
  exports.eventKindLabel = eventKindLabel;
58448
59269
  exports.filterDataTableRows = filterDataTableRows;
58449
59270
  exports.filterDeviceOptions = filterDeviceOptions;
59271
+ exports.filterOptionSelectOptions = filterOptions;
58450
59272
  exports.filterPickerDevices = filterPickerDevices;
58451
59273
  exports.findTimezone = _camstack_types.findTimezone;
58452
59274
  exports.flattenPickerGrouping = flattenPickerGrouping;
@@ -58494,11 +59316,13 @@ exports.nextTableSort = nextTableSort;
58494
59316
  exports.normalizeForSearch = normalizeForSearch;
58495
59317
  exports.offListSelections = offListSelections;
58496
59318
  exports.openTopLayer = openTopLayer;
59319
+ exports.optionSelectSearchText = optionSearchText;
58497
59320
  exports.orphanDeviceLabel = orphanDeviceLabel;
58498
59321
  exports.overrideEntityIdFromLink = overrideEntityIdFromLink;
58499
59322
  exports.pageRows = pageRows;
58500
59323
  exports.parsePickerGroupBy = parsePickerGroupBy;
58501
59324
  exports.parseRecordedServerMessage = parseRecordedServerMessage;
59325
+ exports.partitionOptionSelectSelected = partitionSelected;
58502
59326
  exports.pickerFacetFilterShown = pickerFacetFilterShown;
58503
59327
  exports.pickerFacetKey = pickerFacetKey;
58504
59328
  exports.pickerFacetOptions = pickerFacetOptions;
@@ -58522,6 +59346,7 @@ exports.resolveDisplayIcon = resolveDisplayIcon;
58522
59346
  exports.resolveEffectiveDefaultModel = resolveEffectiveDefaultModel;
58523
59347
  exports.resolveEffectiveStepModel = resolveEffectiveStepModel;
58524
59348
  exports.resolveEventKindIcon = resolveEventKindIcon;
59349
+ exports.resolveOptionSelectSearchable = resolveSearchable;
58525
59350
  exports.resolvePrimaryChild = resolvePrimaryChild;
58526
59351
  exports.resolveSelectedDevices = resolveSelectedDevices;
58527
59352
  exports.resolveSensorDisplay = resolveSensorDisplay;
@@ -58536,6 +59361,8 @@ exports.selectedDeviceOptions = selectedDeviceOptions;
58536
59361
  exports.serializeRecordedCommand = serializeRecordedCommand;
58537
59362
  exports.shouldCommit = shouldCommit;
58538
59363
  exports.shouldEmit = shouldEmit;
59364
+ exports.shouldShowAddNewFooter = shouldShowAddNewFooter;
59365
+ exports.shouldShowUseQueryRow = shouldShowUseQueryRow;
58539
59366
  exports.shouldUseSingleNode = shouldUseSingleNode;
58540
59367
  exports.showsNoCamerasHint = showsNoCamerasHint;
58541
59368
  exports.sortRows = sortRows;
@@ -58677,9 +59504,10 @@ exports.useBrokerTestConnection = useBrokerTestConnection;
58677
59504
  exports.useBrokerTestSettings = useBrokerTestSettings;
58678
59505
  exports.useBrokerUnsubscribe = useBrokerUnsubscribe;
58679
59506
  exports.useButtonPress = useButtonPress;
58680
- exports.useCalendarsImportFromHomeAssistant = useCalendarsImportFromHomeAssistant;
59507
+ exports.useCalendarsGetPreferences = useCalendarsGetPreferences;
58681
59508
  exports.useCalendarsListCalendars = useCalendarsListCalendars;
58682
59509
  exports.useCalendarsListEvents = useCalendarsListEvents;
59510
+ exports.useCalendarsSetCalendarPreference = useCalendarsSetCalendarPreference;
58683
59511
  exports.useCameraCredentialsGetCredentials = useCameraCredentialsGetCredentials;
58684
59512
  exports.useCameraCredentialsGetStatus = useCameraCredentialsGetStatus;
58685
59513
  exports.useCameraGridLayoutApplyDeviceSettingsPatch = useCameraGridLayoutApplyDeviceSettingsPatch;
@@ -58725,6 +59553,7 @@ exports.useCoreBlocksCreate = useCoreBlocksCreate;
58725
59553
  exports.useCoreBlocksCreateComposition = useCoreBlocksCreateComposition;
58726
59554
  exports.useCoreBlocksDelete = useCoreBlocksDelete;
58727
59555
  exports.useCoreBlocksGet = useCoreBlocksGet;
59556
+ exports.useCoreBlocksGetBlockSettings = useCoreBlocksGetBlockSettings;
58728
59557
  exports.useCoreBlocksGetComposition = useCoreBlocksGetComposition;
58729
59558
  exports.useCoreBlocksGetCustomization = useCoreBlocksGetCustomization;
58730
59559
  exports.useCoreBlocksGetTypeDefs = useCoreBlocksGetTypeDefs;
@@ -58734,6 +59563,7 @@ exports.useCoreBlocksListCompositions = useCoreBlocksListCompositions;
58734
59563
  exports.useCoreBlocksRestart = useCoreBlocksRestart;
58735
59564
  exports.useCoreBlocksSetEnabled = useCoreBlocksSetEnabled;
58736
59565
  exports.useCoreBlocksUpdate = useCoreBlocksUpdate;
59566
+ exports.useCoreBlocksUpdateBlockSettings = useCoreBlocksUpdateBlockSettings;
58737
59567
  exports.useCoreBlocksUpdateComposition = useCoreBlocksUpdateComposition;
58738
59568
  exports.useCoreBlocksValidateComposition = useCoreBlocksValidateComposition;
58739
59569
  exports.useCoverClose = useCoverClose;
@@ -58915,6 +59745,7 @@ exports.useDeviceProviderSupportsManualCreation = useDeviceProviderSupportsManua
58915
59745
  exports.useDeviceProviderTestCreationField = useDeviceProviderTestCreationField;
58916
59746
  exports.useDeviceProxy = useDeviceProxy;
58917
59747
  exports.useDeviceProxy$1 = useDeviceProxy;
59748
+ exports.useDeviceQuickDetailOpen = useDeviceQuickDetailOpen;
58918
59749
  exports.useDeviceSnapshot = useDeviceSnapshot;
58919
59750
  exports.useDeviceSnapshotImage = useDeviceSnapshotImage;
58920
59751
  exports.useDeviceSnapshotImage$1 = useDeviceSnapshotImage;
@@ -59131,8 +59962,10 @@ exports.useNodesTopology = useNodesTopology;
59131
59962
  exports.useNodesUndeployAddon = useNodesUndeployAddon;
59132
59963
  exports.useNotificationOutputDeleteTarget = useNotificationOutputDeleteTarget;
59133
59964
  exports.useNotificationOutputDiscoverTargets = useNotificationOutputDiscoverTargets;
59965
+ exports.useNotificationOutputGetTarget = useNotificationOutputGetTarget;
59134
59966
  exports.useNotificationOutputListTargetKinds = useNotificationOutputListTargetKinds;
59135
59967
  exports.useNotificationOutputListTargets = useNotificationOutputListTargets;
59968
+ exports.useNotificationOutputProbeTargetConfig = useNotificationOutputProbeTargetConfig;
59136
59969
  exports.useNotificationOutputSend = useNotificationOutputSend;
59137
59970
  exports.useNotificationOutputSetTargetEnabled = useNotificationOutputSetTargetEnabled;
59138
59971
  exports.useNotificationOutputTestTarget = useNotificationOutputTestTarget;
@@ -59141,9 +59974,11 @@ exports.useNotificationRulesActivateAlarmProfile = useNotificationRulesActivateA
59141
59974
  exports.useNotificationRulesCancelSnooze = useNotificationRulesCancelSnooze;
59142
59975
  exports.useNotificationRulesCreateAlarmProfile = useNotificationRulesCreateAlarmProfile;
59143
59976
  exports.useNotificationRulesCreateRule = useNotificationRulesCreateRule;
59977
+ exports.useNotificationRulesCreateSequence = useNotificationRulesCreateSequence;
59144
59978
  exports.useNotificationRulesCreateSnooze = useNotificationRulesCreateSnooze;
59145
59979
  exports.useNotificationRulesDeleteAlarmProfile = useNotificationRulesDeleteAlarmProfile;
59146
59980
  exports.useNotificationRulesDeleteRule = useNotificationRulesDeleteRule;
59981
+ exports.useNotificationRulesDeleteSequence = useNotificationRulesDeleteSequence;
59147
59982
  exports.useNotificationRulesGetAlarmConfig = useNotificationRulesGetAlarmConfig;
59148
59983
  exports.useNotificationRulesGetAlarmSensors = useNotificationRulesGetAlarmSensors;
59149
59984
  exports.useNotificationRulesGetClipRetentionAsks = useNotificationRulesGetClipRetentionAsks;
@@ -59151,9 +59986,11 @@ exports.useNotificationRulesGetConditionCatalog = useNotificationRulesGetConditi
59151
59986
  exports.useNotificationRulesGetHistory = useNotificationRulesGetHistory;
59152
59987
  exports.useNotificationRulesGetRule = useNotificationRulesGetRule;
59153
59988
  exports.useNotificationRulesGetRuleLastRuns = useNotificationRulesGetRuleLastRuns;
59989
+ exports.useNotificationRulesGetSequence = useNotificationRulesGetSequence;
59154
59990
  exports.useNotificationRulesGetTemplateCatalog = useNotificationRulesGetTemplateCatalog;
59155
59991
  exports.useNotificationRulesListDeviceMutes = useNotificationRulesListDeviceMutes;
59156
59992
  exports.useNotificationRulesListRules = useNotificationRulesListRules;
59993
+ exports.useNotificationRulesListSequences = useNotificationRulesListSequences;
59157
59994
  exports.useNotificationRulesListSnoozes = useNotificationRulesListSnoozes;
59158
59995
  exports.useNotificationRulesPreviewStateSummary = useNotificationRulesPreviewStateSummary;
59159
59996
  exports.useNotificationRulesPreviewTemplate = useNotificationRulesPreviewTemplate;
@@ -59164,6 +60001,7 @@ exports.useNotificationRulesSetDeviceMuted = useNotificationRulesSetDeviceMuted;
59164
60001
  exports.useNotificationRulesSetRuleEnabled = useNotificationRulesSetRuleEnabled;
59165
60002
  exports.useNotificationRulesTestRule = useNotificationRulesTestRule;
59166
60003
  exports.useNotificationRulesUpdateRule = useNotificationRulesUpdateRule;
60004
+ exports.useNotificationRulesUpdateSequence = useNotificationRulesUpdateSequence;
59167
60005
  exports.useNotifierCancel = useNotifierCancel;
59168
60006
  exports.useNotifierGetStatus = useNotifierGetStatus;
59169
60007
  exports.useNotifierSend = useNotifierSend;
@@ -59645,6 +60483,7 @@ exports.useToastOnToast = useToastOnToast;
59645
60483
  exports.useTurnProviderGetTurnServers = useTurnProviderGetTurnServers;
59646
60484
  exports.useUpdateGetStatus = useUpdateGetStatus;
59647
60485
  exports.useUpdateInstallUpdate = useUpdateInstallUpdate;
60486
+ exports.useUrlQueryParam = useUrlQueryParam;
59648
60487
  exports.useUserManagementConfirmTotp = useUserManagementConfirmTotp;
59649
60488
  exports.useUserManagementCreateApiKey = useUserManagementCreateApiKey;
59650
60489
  exports.useUserManagementCreateScopedToken = useUserManagementCreateScopedToken;