@camstack/ui-library 1.2.356 → 1.2.358

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
@@ -5251,9 +5251,11 @@ function useEventStreamMap(category, keyOf, filter) {
5251
5251
  * `nodes.getNodeAddons`, which is one node and does not walk the fleet.
5252
5252
  */
5253
5253
  function useClusterTopology() {
5254
- const { data: bootstrapTopology, isLoading, isError } = trpc.nodes.topology.useQuery(void 0, { staleTime: Number.POSITIVE_INFINITY });
5254
+ const { data: bootstrapTopology, dataUpdatedAt, isLoading, isError } = trpc.nodes.topology.useQuery(void 0, { staleTime: Number.POSITIVE_INFINITY });
5255
+ const snapshot = useEventStreamLatest("cluster.topology-snapshot");
5256
+ const snapshotReceivedAt = (0, react$1.useMemo)(() => snapshot === null ? 0 : Date.now(), [snapshot]);
5255
5257
  return {
5256
- nodes: useEventStreamLatest("cluster.topology-snapshot")?.nodes ?? bootstrapTopology,
5258
+ nodes: snapshot !== null && !(bootstrapTopology !== void 0 && dataUpdatedAt > snapshotReceivedAt) ? snapshot.nodes : bootstrapTopology,
5257
5259
  isLoading,
5258
5260
  isError
5259
5261
  };
@@ -10446,6 +10448,14 @@ var Select = (0, react$1.forwardRef)(({ className, options, disabled, ...props }
10446
10448
  });
10447
10449
  Select.displayName = "Select";
10448
10450
  //#endregion
10451
+ //#region src/primitives/disclosure-chevron.tsx
10452
+ function DisclosureChevron({ expanded, className }) {
10453
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(expanded ? ChevronDown : ChevronRight, {
10454
+ "aria-hidden": "true",
10455
+ className: cn("h-4 w-4 shrink-0 text-foreground-subtle", className)
10456
+ });
10457
+ }
10458
+ //#endregion
10449
10459
  //#region src/composites/config-secret-field.tsx
10450
10460
  /**
10451
10461
  * A keychain-stored secret in a settings form (D787, D789) — drawn as ONE
@@ -10709,6 +10719,10 @@ var useAddonSettingsGetGlobalSettings = trpc.addonSettings.getGlobalSettings.use
10709
10719
  var useAddonSettingsUpdateGlobalSettings = trpc.addonSettings.updateGlobalSettings.useMutation;
10710
10720
  /** Generated alias around `trpc.addonSettings.getIntegrationSettings.useQuery`. */
10711
10721
  var useAddonSettingsGetIntegrationSettings = trpc.addonSettings.getIntegrationSettings.useQuery;
10722
+ /** Generated alias around `trpc.addonSettings.getDeclaredSettingsKeys.useQuery`. */
10723
+ var useAddonSettingsGetDeclaredSettingsKeys = trpc.addonSettings.getDeclaredSettingsKeys.useQuery;
10724
+ /** Generated alias around `trpc.addonSettings.unsetStoredSettingsKeys.useMutation`. */
10725
+ var useAddonSettingsUnsetStoredSettingsKeys = trpc.addonSettings.unsetStoredSettingsKeys.useMutation;
10712
10726
  /** Generated alias around `trpc.addonSettings.getDeviceSettings.useQuery`. */
10713
10727
  var useAddonSettingsGetDeviceSettings = trpc.addonSettings.getDeviceSettings.useQuery;
10714
10728
  /** Generated alias around `trpc.addonSettings.updateDeviceSettings.useMutation`. */
@@ -11345,6 +11359,10 @@ var useDeviceProviderReloadDevice = trpc.deviceProvider.reloadDevice.useMutation
11345
11359
  var useDeviceProviderSupportsDiscovery = trpc.deviceProvider.supportsDiscovery.useQuery;
11346
11360
  /** Generated alias around `trpc.deviceProvider.discoverDevices.useMutation`. */
11347
11361
  var useDeviceProviderDiscoverDevices = trpc.deviceProvider.discoverDevices.useMutation;
11362
+ /** Generated alias around `trpc.deviceProvider.getDiscoveryModes.useQuery`. */
11363
+ var useDeviceProviderGetDiscoveryModes = trpc.deviceProvider.getDiscoveryModes.useQuery;
11364
+ /** Generated alias around `trpc.deviceProvider.classifyDiscoveryHits.useMutation`. */
11365
+ var useDeviceProviderClassifyDiscoveryHits = trpc.deviceProvider.classifyDiscoveryHits.useMutation;
11348
11366
  /** Generated alias around `trpc.deviceProvider.getDiscoveryParamsSchema.useQuery`. */
11349
11367
  var useDeviceProviderGetDiscoveryParamsSchema = trpc.deviceProvider.getDiscoveryParamsSchema.useQuery;
11350
11368
  /** Generated alias around `trpc.deviceProvider.getManualCreationType.useQuery`. */
@@ -11651,6 +11669,12 @@ var useLocalNetworkUploadCertificate = trpc.localNetwork.uploadCertificate.useMu
11651
11669
  var useLocalNetworkDownloadCa = trpc.localNetwork.downloadCa.useQuery;
11652
11670
  /** Generated alias around `trpc.localNetwork.useStoredUploadedCertificate.useMutation`. */
11653
11671
  var useLocalNetworkUseStoredUploadedCertificate = trpc.localNetwork.useStoredUploadedCertificate.useMutation;
11672
+ /** Generated alias around `trpc.localNetwork.scanNetwork.useMutation`. */
11673
+ var useLocalNetworkScanNetwork = trpc.localNetwork.scanNetwork.useMutation;
11674
+ /** Generated alias around `trpc.localNetwork.getScanSubnets.useQuery`. */
11675
+ var useLocalNetworkGetScanSubnets = trpc.localNetwork.getScanSubnets.useQuery;
11676
+ /** Generated alias around `trpc.localNetwork.setScanSubnets.useMutation`. */
11677
+ var useLocalNetworkSetScanSubnets = trpc.localNetwork.setScanSubnets.useMutation;
11654
11678
  /** Generated alias around `trpc.localNetwork.revertToGeneratedCertificate.useMutation`. */
11655
11679
  var useLocalNetworkRevertToGeneratedCertificate = trpc.localNetwork.revertToGeneratedCertificate.useMutation;
11656
11680
  /** Generated alias around `trpc.lockControl.lock.useMutation`. */
@@ -29314,7 +29338,7 @@ function DialogTrigger({ children, ...props }) {
29314
29338
  * whenever they are asymmetric (they are: ~59px top vs ~34px bottom). Every
29315
29339
  * term collapses to the previous `100dvh - 2rem` where the insets are 0.
29316
29340
  */
29317
- var contentVariants = cva("relative fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 m-0 w-[calc(100vw-2rem)] bg-background-elevated text-foreground border border-border rounded-lg p-4 backdrop:bg-black/50 backdrop:backdrop-blur-sm max-h-[calc(100dvh-2rem-2*max(env(safe-area-inset-top),env(safe-area-inset-bottom)))] [&:not([open])]:hidden", {
29341
+ var contentVariants = cva("relative fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 m-0 w-[calc(100vw-2rem)] whitespace-normal bg-background-elevated text-foreground border border-border rounded-lg p-4 backdrop:bg-black/50 backdrop:backdrop-blur-sm max-h-[calc(100dvh-2rem-2*max(env(safe-area-inset-top),env(safe-area-inset-bottom)))] [&:not([open])]:hidden", {
29318
29342
  variants: {
29319
29343
  width: {
29320
29344
  sm: "max-w-sm",
@@ -31837,6 +31861,195 @@ function TimezoneSelector({ value, onChange, disabled }) {
31837
31861
  });
31838
31862
  }
31839
31863
  //#endregion
31864
+ //#region src/contexts/broker-picker-slot.tsx
31865
+ /**
31866
+ * The broker picker a host mounts for `broker-select` fields.
31867
+ *
31868
+ * Creating a broker needs the host's sign-in dialogs, which the library does
31869
+ * not carry, so the host supplies the picker (`BrokerPicker` in admin-ui) here.
31870
+ * Without one, `broker-select` still lists the existing brokers and lets the
31871
+ * operator choose; it just cannot create.
31872
+ */
31873
+ var BrokerPickerSlotContext = createSharedContext("camstack:broker-picker-slot", null);
31874
+ function BrokerPickerSlotProvider({ picker, children }) {
31875
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BrokerPickerSlotContext.Provider, {
31876
+ value: picker,
31877
+ children
31878
+ });
31879
+ }
31880
+ function useBrokerPickerSlot() {
31881
+ return (0, react$1.useContext)(BrokerPickerSlotContext);
31882
+ }
31883
+ //#endregion
31884
+ //#region src/composites/config-broker-select-field.tsx
31885
+ function toRows(data) {
31886
+ if (!Array.isArray(data)) return [];
31887
+ const rows = [];
31888
+ for (const entry of data) {
31889
+ if (typeof entry !== "object" || entry === null) continue;
31890
+ const { id, name, kind } = { ...entry };
31891
+ if (typeof id === "string" && typeof kind === "string") rows.push({
31892
+ id,
31893
+ kind,
31894
+ name: typeof name === "string" ? name : id
31895
+ });
31896
+ }
31897
+ return rows;
31898
+ }
31899
+ function PlainBrokerSelect({ kind, value, onChange, disabled, controlId }) {
31900
+ const t = useUiText();
31901
+ const query = useBrokerList({ kind });
31902
+ const rows = toRows(query.data).filter((row) => row.kind === kind);
31903
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Select, {
31904
+ id: controlId,
31905
+ disabled,
31906
+ value,
31907
+ onChange: (e) => onChange(e.target.value),
31908
+ options: [{
31909
+ value: "",
31910
+ label: query.isLoading ? t("uiLibrary.configFormField.loadingOptions", "Loading options…") : t("uiLibrary.configFormField.select", "— Select —")
31911
+ }, ...rows.map((row) => ({
31912
+ value: row.id,
31913
+ label: row.name
31914
+ }))]
31915
+ });
31916
+ }
31917
+ function ConfigBrokerSelectControl({ field, value, onChange, disabled, controlId }) {
31918
+ const Picker = useBrokerPickerSlot();
31919
+ const current = typeof value === "string" ? value : "";
31920
+ if (Picker !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Picker, {
31921
+ kind: field.brokerKind,
31922
+ value: current === "" ? null : current,
31923
+ onChange: (brokerId) => onChange(brokerId ?? "")
31924
+ });
31925
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlainBrokerSelect, {
31926
+ kind: field.brokerKind,
31927
+ value: current,
31928
+ onChange,
31929
+ disabled: disabled || field.disabled,
31930
+ controlId
31931
+ });
31932
+ }
31933
+ //#endregion
31934
+ //#region src/hooks/use-debounced-string.ts
31935
+ /**
31936
+ * useDebouncedString — return `value` after `delay` ms have passed
31937
+ * without further changes. Resets the timer on every change.
31938
+ *
31939
+ * Useful for search inputs that should NOT push to URL state on every
31940
+ * keystroke. The DOM input stays controlled by an external `value`
31941
+ * (caller's `useState`); this hook produces a debounced derivation.
31942
+ */
31943
+ function useDebouncedString(value, delayMs) {
31944
+ const [debounced, setDebounced] = (0, react$1.useState)(value);
31945
+ (0, react$1.useEffect)(() => {
31946
+ const t = setTimeout(() => setDebounced(value), delayMs);
31947
+ return () => clearTimeout(t);
31948
+ }, [value, delayMs]);
31949
+ return debounced;
31950
+ }
31951
+ //#endregion
31952
+ //#region src/composites/config-discovered-select-field.tsx
31953
+ /**
31954
+ * A `select` whose options come from a notification kind's `discoverTargets`,
31955
+ * asked for the form's CURRENT values.
31956
+ *
31957
+ * Nothing is fetched until every `dependsOn` field has a value, so a kind that
31958
+ * needs a broker or a token is never asked without it. Each state is its own
31959
+ * line (not yet / loading / failed / nothing found) and the field stays a free
31960
+ * text input in all of them but "found", so the operator is never stuck behind
31961
+ * a list that did not come.
31962
+ */
31963
+ var TYPING_DEBOUNCE_MS = 500;
31964
+ function isConfigValue(value) {
31965
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
31966
+ }
31967
+ function hasValue(value) {
31968
+ return typeof value === "string" ? value.trim().length > 0 : value !== void 0 && value !== null;
31969
+ }
31970
+ /** The non-empty scalar form values: what the provider is asked about. */
31971
+ function discoveryConfig(values) {
31972
+ const config = {};
31973
+ for (const [key, value] of Object.entries(values)) if (isConfigValue(value) && hasValue(value)) config[key] = value;
31974
+ return config;
31975
+ }
31976
+ function optionsFrom(candidates, key) {
31977
+ const seen = /* @__PURE__ */ new Set();
31978
+ const options = [];
31979
+ for (const candidate of candidates) {
31980
+ const raw = candidate.config[key];
31981
+ if (typeof raw !== "string" || raw === "" || seen.has(raw)) continue;
31982
+ seen.add(raw);
31983
+ options.push({
31984
+ value: raw,
31985
+ label: candidate.suggestedName
31986
+ });
31987
+ }
31988
+ return options;
31989
+ }
31990
+ function ConfigDiscoveredSelectControl({ field, values, value, onChange, disabled, controlId }) {
31991
+ const t = useUiText();
31992
+ const discover = field.discoverOptions;
31993
+ const kind = discover?.kind ?? "";
31994
+ const dependsOn = discover?.dependsOn ?? [];
31995
+ const ready = discover !== void 0 && dependsOn.every((key) => hasValue(values[key]));
31996
+ const config = (0, react$1.useMemo)(() => discoveryConfig(values), [values]);
31997
+ const configKey = JSON.stringify(config);
31998
+ const settled = configKey === useDebouncedString(configKey, TYPING_DEBOUNCE_MS);
31999
+ const query = useNotificationOutputDiscoverTargets({
32000
+ kind,
32001
+ config
32002
+ }, {
32003
+ enabled: ready && settled,
32004
+ retry: false
32005
+ });
32006
+ const current = value === void 0 || value === null ? "" : String(value);
32007
+ const found = ready && settled && query.data !== void 0 ? optionsFrom(query.data, field.key) : [];
32008
+ const disabledNow = disabled || field.disabled;
32009
+ if (found.length > 0) {
32010
+ const known = found.some((option) => option.value === current);
32011
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Select, {
32012
+ id: controlId,
32013
+ disabled: disabledNow,
32014
+ value: current,
32015
+ onChange: (e) => onChange(e.target.value),
32016
+ options: [
32017
+ {
32018
+ value: "",
32019
+ label: t("uiLibrary.configFormField.select", "— Select —")
32020
+ },
32021
+ ...current !== "" && !known ? [{
32022
+ value: current,
32023
+ label: current
32024
+ }] : [],
32025
+ ...found.map((option) => ({
32026
+ value: option.value,
32027
+ label: option.label
32028
+ }))
32029
+ ]
32030
+ });
32031
+ }
32032
+ let status;
32033
+ if (!ready) status = t("uiLibrary.configFormField.discoverNotReady", "Fill in the fields above to load the list, or type the value.");
32034
+ else if (!settled || query.isLoading || query.isFetching) status = t("uiLibrary.configFormField.loadingOptions", "Loading options…");
32035
+ else if (query.isError) status = t("uiLibrary.configFormField.discoverFailed", "Could not load the list: {{message}}. Type the value instead.", { message: query.error instanceof Error ? query.error.message : String(query.error) });
32036
+ else status = t("uiLibrary.configFormField.discoverEmpty", "Nothing was found. Type the value instead.");
32037
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
32038
+ className: "space-y-1",
32039
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Input, {
32040
+ id: controlId,
32041
+ value: current,
32042
+ placeholder: field.placeholder,
32043
+ disabled: disabledNow,
32044
+ onChange: (e) => onChange(e.target.value)
32045
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
32046
+ "data-testid": `${controlId}-status`,
32047
+ className: `text-[11px] ${query.isError ? "text-danger" : "text-foreground-subtle"}`,
32048
+ children: status
32049
+ })]
32050
+ });
32051
+ }
32052
+ //#endregion
31840
32053
  //#region src/composites/config-form-field.tsx
31841
32054
  var INPUT_CLASS = "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";
31842
32055
  var LABEL_CLASS$1 = "block text-[11px] font-medium text-foreground mb-1 break-words";
@@ -32021,7 +32234,7 @@ function BooleanField({ field, value, onChange, disabled, translationFn }) {
32021
32234
  })
32022
32235
  });
32023
32236
  }
32024
- function SelectField({ field, value, onChange, disabled, translationFn }) {
32237
+ function SelectField({ field, values, value, onChange, disabled, translationFn }) {
32025
32238
  const t = useUiText();
32026
32239
  const hasEmptyOption = field.options.some((o) => o.value === "");
32027
32240
  const options = [...!field.required && !hasEmptyOption ? [{
@@ -32039,7 +32252,14 @@ function SelectField({ field, value, onChange, disabled, translationFn }) {
32039
32252
  span: field.span,
32040
32253
  htmlFor: controlId,
32041
32254
  translationFn,
32042
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Select, {
32255
+ children: field.discoverOptions !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfigDiscoveredSelectControl, {
32256
+ field,
32257
+ values,
32258
+ value,
32259
+ onChange,
32260
+ disabled,
32261
+ controlId
32262
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Select, {
32043
32263
  id: controlId,
32044
32264
  options,
32045
32265
  value: value === void 0 || value === null ? "" : String(value),
@@ -32048,6 +32268,22 @@ function SelectField({ field, value, onChange, disabled, translationFn }) {
32048
32268
  })
32049
32269
  });
32050
32270
  }
32271
+ function BrokerSelectField({ field, value, onChange, disabled, translationFn }) {
32272
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldWrapper, {
32273
+ label: field.label,
32274
+ description: field.description,
32275
+ required: field.required,
32276
+ span: field.span,
32277
+ translationFn,
32278
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfigBrokerSelectControl, {
32279
+ field,
32280
+ value,
32281
+ onChange,
32282
+ disabled,
32283
+ controlId: `config-broker-select-${field.key}`
32284
+ })
32285
+ });
32286
+ }
32051
32287
  function TimezoneField({ field, value, onChange, disabled, translationFn }) {
32052
32288
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldWrapper, {
32053
32289
  label: field.label,
@@ -32520,16 +32756,24 @@ function GroupField({ field, values, onChange, onChangeMany, disabled, translati
32520
32756
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
32521
32757
  className: "@container rounded-lg border border-border bg-surface/50 overflow-hidden",
32522
32758
  style: { containerType: "inline-size" },
32523
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
32524
- className: ["px-3 py-2 flex items-center justify-between min-w-0", isAccordion ? "cursor-pointer hover:bg-surface" : ""].join(" "),
32525
- onClick: isAccordion ? () => setCollapsed((c) => !c) : void 0,
32759
+ children: [isAccordion ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
32760
+ type: "button",
32761
+ "aria-expanded": !collapsed,
32762
+ onClick: () => setCollapsed((c) => !c),
32763
+ className: "px-3 py-2 flex w-full items-center justify-between min-w-0 text-left hover:bg-surface",
32526
32764
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
32527
32765
  className: "text-xs font-semibold text-foreground min-w-0 break-words",
32528
32766
  children: resolveLabel(field.label, translationFn)
32529
- }), isAccordion && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
32530
- className: "text-foreground-subtle text-xs shrink-0 ml-2",
32531
- children: collapsed ? "▸" : "▾"
32767
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisclosureChevron, {
32768
+ expanded: !collapsed,
32769
+ className: "ml-2"
32532
32770
  })]
32771
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
32772
+ className: "px-3 py-2 flex items-center justify-between min-w-0",
32773
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
32774
+ className: "text-xs font-semibold text-foreground min-w-0 break-words",
32775
+ children: resolveLabel(field.label, translationFn)
32776
+ })
32533
32777
  }), !collapsed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
32534
32778
  className: "px-3 pb-3 grid grid-cols-1 @[480px]:grid-cols-2 gap-x-4 gap-y-3",
32535
32779
  children: field.fields.map((subField) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormField, {
@@ -33487,8 +33731,16 @@ function FormField({ field, values, onChange, onChangeMany, disabled, translatio
33487
33731
  disabled,
33488
33732
  translationFn
33489
33733
  });
33734
+ case "broker-select": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BrokerSelectField, {
33735
+ field,
33736
+ value,
33737
+ onChange: handleChange,
33738
+ disabled,
33739
+ translationFn
33740
+ });
33490
33741
  case "select": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectField, {
33491
33742
  field,
33743
+ values,
33492
33744
  value,
33493
33745
  onChange: handleChange,
33494
33746
  disabled,
@@ -33742,19 +33994,30 @@ function SectionCard$1({ section, values, onChange, disabled, translationFn, onT
33742
33994
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33743
33995
  className: "@container rounded-lg border border-border bg-surface p-3",
33744
33996
  style: { containerType: "inline-size" },
33745
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33746
- className: ["flex items-center justify-between mb-2", isAccordion ? "cursor-pointer" : ""].join(" "),
33747
- onClick: isAccordion ? () => setCollapsed((c) => !c) : void 0,
33997
+ children: [isAccordion ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
33998
+ type: "button",
33999
+ "aria-expanded": !collapsed,
34000
+ onClick: () => setCollapsed((c) => !c),
34001
+ className: "flex w-full items-center justify-between mb-2 text-left",
33748
34002
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
33749
34003
  className: "text-[11px] font-semibold text-foreground-subtle uppercase tracking-wider",
33750
34004
  children: resolveLabel(section.title, translationFn)
33751
34005
  }), section.description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
33752
34006
  className: "text-[10px] text-foreground-subtle mt-0.5",
33753
34007
  children: resolveLabel(section.description, translationFn)
33754
- })] }), isAccordion && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
33755
- className: "text-foreground-subtle text-xs ml-2",
33756
- children: collapsed ? "▸" : "▾"
34008
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisclosureChevron, {
34009
+ expanded: !collapsed,
34010
+ className: "ml-2"
33757
34011
  })]
34012
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
34013
+ className: "flex items-center justify-between mb-2",
34014
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
34015
+ className: "text-[11px] font-semibold text-foreground-subtle uppercase tracking-wider",
34016
+ children: resolveLabel(section.title, translationFn)
34017
+ }), section.description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
34018
+ className: "text-[10px] text-foreground-subtle mt-0.5",
34019
+ children: resolveLabel(section.description, translationFn)
34020
+ })] })
33758
34021
  }), !collapsed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
33759
34022
  className: `grid ${gridClass} gap-x-3 gap-y-2.5`,
33760
34023
  children: section.fields.map((field) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormField, {
@@ -43149,12 +43412,11 @@ function AlternateLabelsSection({ alternateLabels, colors }) {
43149
43412
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
43150
43413
  className: "mt-1",
43151
43414
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
43415
+ type: "button",
43416
+ "aria-expanded": expanded,
43152
43417
  onClick: () => setExpanded((v) => !v),
43153
43418
  className: "text-[9px] text-foreground-subtle hover:text-foreground transition-colors flex items-center gap-1",
43154
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
43155
- className: `transition-transform ${expanded ? "rotate-90" : ""}`,
43156
- children: "▶"
43157
- }), t("uiLibrary.detectionResultTree.alternates", {
43419
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisclosureChevron, { expanded }), t("uiLibrary.detectionResultTree.alternates", {
43158
43420
  one: "Alternates ({{count}} source)",
43159
43421
  other: "Alternates ({{count}} sources)"
43160
43422
  }, { count: sources.length })]
@@ -44267,24 +44529,6 @@ function useLiveBuffer(max) {
44267
44529
  ]);
44268
44530
  }
44269
44531
  //#endregion
44270
- //#region src/hooks/use-debounced-string.ts
44271
- /**
44272
- * useDebouncedString — return `value` after `delay` ms have passed
44273
- * without further changes. Resets the timer on every change.
44274
- *
44275
- * Useful for search inputs that should NOT push to URL state on every
44276
- * keystroke. The DOM input stays controlled by an external `value`
44277
- * (caller's `useState`); this hook produces a debounced derivation.
44278
- */
44279
- function useDebouncedString(value, delayMs) {
44280
- const [debounced, setDebounced] = (0, react$1.useState)(value);
44281
- (0, react$1.useEffect)(() => {
44282
- const t = setTimeout(() => setDebounced(value), delayMs);
44283
- return () => clearTimeout(t);
44284
- }, [value, delayMs]);
44285
- return debounced;
44286
- }
44287
- //#endregion
44288
44532
  //#region src/composites/log-history-paging.ts
44289
44533
  /**
44290
44534
  * Accumulate slices until `limit` entries, `done`, or an abort. An abort keeps
@@ -48299,7 +48543,7 @@ function MediaPlayerPanel({ trpc, deviceId, features: _features, layout, optimis
48299
48543
  }
48300
48544
  },
48301
48545
  className: cn("inline-flex h-5 w-5 items-center justify-center rounded text-foreground-subtle hover:text-foreground hover:bg-surface-hover transition-colors text-xs", busy && "opacity-40 cursor-not-allowed"),
48302
- children: action === "play" ? "▶" : "⏸"
48546
+ children: action === "play" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Play, { className: "h-3 w-3" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pause, { className: "h-3 w-3" })
48303
48547
  })]
48304
48548
  }), layout === "full" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
48305
48549
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -65995,15 +66239,14 @@ function ResponseLog({ data, label = "Response", className = "" }) {
65995
66239
  const json = JSON.stringify(data, null, 2);
65996
66240
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
65997
66241
  className: `rounded-xl border border-border bg-surface overflow-hidden ${className}`,
65998
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
65999
- className: "px-3 py-2 border-b border-border flex items-center justify-between cursor-pointer select-none hover:bg-surface-hover/30",
66242
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
66243
+ type: "button",
66244
+ "aria-expanded": expanded,
66245
+ className: "w-full px-3 py-2 border-b border-border flex items-center justify-between text-left select-none hover:bg-surface-hover/30",
66000
66246
  onClick: () => setExpanded((e) => !e),
66001
66247
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
66002
66248
  className: "flex items-center gap-2",
66003
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
66004
- className: "text-[10px] text-foreground-subtle",
66005
- children: expanded ? "▼" : "▶"
66006
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
66249
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisclosureChevron, { expanded }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
66007
66250
  className: "text-xs font-medium text-foreground",
66008
66251
  children: label
66009
66252
  })]
@@ -68971,7 +69214,7 @@ function noItemsSentence(noun, emptyMeansAll, t) {
68971
69214
  noun: pickerNounLabel(noun, t)
68972
69215
  });
68973
69216
  }
68974
- function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, selection = "multiple", groupBy: pinnedGroupBy, emptyMeansAll = false, savedHadDevices = false, noun = "camera", groupTitle: groupTitleProp, emptyLabel, annotate, disabled = false, testId, showAll = false, lockedIds, lockedNote: lockedNoteProp }) {
69217
+ function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, selection = "multiple", groupBy: pinnedGroupBy, emptyMeansAll = false, savedHadDevices = false, noun = "camera", groupTitle: groupTitleProp, emptyLabel, annotate, disabled = false, testId, showAll = false, fillHeight = false, lockedIds, lockedNote: lockedNoteProp }) {
68975
69218
  const t = useUiText();
68976
69219
  const groupTitle = groupTitleProp ?? ((group) => deviceGroupTitle(group, t));
68977
69220
  const lockedNote = lockedNoteProp ?? t("uiLibrary.devicePicker.alreadyAdded", "Already added");
@@ -68999,7 +69242,7 @@ function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, s
68999
69242
  }), widening && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DevicesWidenWarning, { noun })]
69000
69243
  });
69001
69244
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
69002
- className: "mt-1 space-y-1.5",
69245
+ className: fillHeight ? "mt-1 flex min-h-0 flex-1 flex-col gap-1.5" : "mt-1 space-y-1.5",
69003
69246
  "data-testid": testId,
69004
69247
  children: [
69005
69248
  listState === "pending" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DevicesLoadingLine, { noun: plural }),
@@ -69049,6 +69292,7 @@ function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, s
69049
69292
  noun,
69050
69293
  disabled,
69051
69294
  showAll,
69295
+ fillHeight,
69052
69296
  selection,
69053
69297
  ...pinnedGroupBy !== void 0 ? { pinnedGroupBy } : {}
69054
69298
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PickerPanel, {
@@ -69062,6 +69306,7 @@ function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, s
69062
69306
  noun,
69063
69307
  disabled,
69064
69308
  showAll,
69309
+ fillHeight,
69065
69310
  selection
69066
69311
  })),
69067
69312
  widening && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DevicesWidenWarning, { noun })
@@ -69110,7 +69355,7 @@ var NO_ZONES = {
69110
69355
  zones: []
69111
69356
  };
69112
69357
  /** The searchable, grouped list — rendered one page at a time. */
69113
- function PickerPanel({ devices, value, onChange, annotate, groupTitle, noun, disabled, locked, lockedNote, showAll, selection, grouping, providers = NO_PROVIDERS }) {
69358
+ function PickerPanel({ devices, value, onChange, annotate, groupTitle, noun, disabled, locked, lockedNote, showAll, fillHeight, selection, grouping, providers = NO_PROVIDERS }) {
69114
69359
  const t = useUiText();
69115
69360
  const radioName = (0, react$1.useId)();
69116
69361
  const [filters, setFilters] = (0, react$1.useState)(PICKER_NO_FILTERS);
@@ -69162,7 +69407,7 @@ function PickerPanel({ devices, value, onChange, annotate, groupTitle, noun, dis
69162
69407
  const page = pageRows(rows, showAll ? rows.length : shown);
69163
69408
  const toggleGroup = (group, expanded) => setToggles((current) => new Map([...current, [group.key, !expanded]]));
69164
69409
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
69165
- className: "rounded-md border border-border bg-surface p-2",
69410
+ className: fillHeight ? "flex min-h-0 flex-1 flex-col rounded-md border border-border bg-surface p-2" : "rounded-md border border-border bg-surface p-2",
69166
69411
  "data-testid": "device-picker-panel",
69167
69412
  children: [
69168
69413
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceFilterBar, {
@@ -69199,7 +69444,7 @@ function PickerPanel({ devices, value, onChange, annotate, groupTitle, noun, dis
69199
69444
  className: "py-2 text-[11px] text-foreground-subtle italic",
69200
69445
  children: noun === "camera" || noun === "device" ? t("uiLibrary.devicePicker.noDeviceMatches", "No device matches.") : t("uiLibrary.devicePicker.noNounMatches", "No {{noun}} matches.", { noun: pickerNounLabel(noun, t) })
69201
69446
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
69202
- className: showAll ? "mt-1" : "mt-1 max-h-72 overflow-y-auto",
69447
+ className: fillHeight ? "mt-1 min-h-0 flex-1 overflow-y-auto" : showAll ? "mt-1" : "mt-1 max-h-72 overflow-y-auto",
69203
69448
  "data-testid": "device-picker-rows",
69204
69449
  children: [page.rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PickerRowView, {
69205
69450
  row,
@@ -69259,7 +69504,7 @@ function PickerRowView({ row, value, onChange, onToggleGroup, annotate, groupTit
69259
69504
  onClick: () => onToggleGroup(group, expanded),
69260
69505
  className: "flex min-w-0 flex-1 items-center gap-1 text-left text-[10px] font-semibold uppercase tracking-wide text-foreground-subtle",
69261
69506
  children: [
69262
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: expanded ? "▾" : "▸" }),
69507
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisclosureChevron, { expanded }),
69263
69508
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
69264
69509
  className: "truncate",
69265
69510
  children: groupTitle(group)
@@ -72669,6 +72914,7 @@ exports.BottomSheet = BottomSheet;
72669
72914
  exports.Box = Box;
72670
72915
  exports.Breadcrumb = Breadcrumb;
72671
72916
  exports.BrightnessPanel = BrightnessPanel;
72917
+ exports.BrokerPickerSlotProvider = BrokerPickerSlotProvider;
72672
72918
  exports.Bug = Bug;
72673
72919
  exports.Bus = Bus;
72674
72920
  exports.Button = Button;
@@ -72810,6 +73056,7 @@ exports.DialogFooter = DialogFooter;
72810
73056
  exports.DialogHeader = DialogHeader;
72811
73057
  exports.DialogTitle = DialogTitle;
72812
73058
  exports.DialogTrigger = DialogTrigger;
73059
+ exports.DisclosureChevron = DisclosureChevron;
72813
73060
  exports.DiscoveryPanel = DiscoveryPanel;
72814
73061
  exports.DisplayPaletteProvider = DisplayPaletteProvider;
72815
73062
  exports.Dog = Dog;
@@ -73615,9 +73862,11 @@ exports.useActionsSetLinks = useActionsSetLinks;
73615
73862
  exports.useActionsStop = useActionsStop;
73616
73863
  exports.useActionsUpdate = useActionsUpdate;
73617
73864
  exports.useAddonPagesListPages = useAddonPagesListPages;
73865
+ exports.useAddonSettingsGetDeclaredSettingsKeys = useAddonSettingsGetDeclaredSettingsKeys;
73618
73866
  exports.useAddonSettingsGetDeviceSettings = useAddonSettingsGetDeviceSettings;
73619
73867
  exports.useAddonSettingsGetGlobalSettings = useAddonSettingsGetGlobalSettings;
73620
73868
  exports.useAddonSettingsGetIntegrationSettings = useAddonSettingsGetIntegrationSettings;
73869
+ exports.useAddonSettingsUnsetStoredSettingsKeys = useAddonSettingsUnsetStoredSettingsKeys;
73621
73870
  exports.useAddonSettingsUpdateDeviceSettings = useAddonSettingsUpdateDeviceSettings;
73622
73871
  exports.useAddonSettingsUpdateGlobalSettings = useAddonSettingsUpdateGlobalSettings;
73623
73872
  exports.useAddonT = useAddonT;
@@ -73734,6 +73983,7 @@ exports.useBrokerGetState = useBrokerGetState;
73734
73983
  exports.useBrokerGetStatus = useBrokerGetStatus;
73735
73984
  exports.useBrokerList = useBrokerList;
73736
73985
  exports.useBrokerListProviders = useBrokerListProviders;
73986
+ exports.useBrokerPickerSlot = useBrokerPickerSlot;
73737
73987
  exports.useBrokerPublish = useBrokerPublish;
73738
73988
  exports.useBrokerRemove = useBrokerRemove;
73739
73989
  exports.useBrokerSetSettings = useBrokerSetSettings;
@@ -73993,10 +74243,12 @@ exports.useDeviceOpsRemoveDevice = useDeviceOpsRemoveDevice;
73993
74243
  exports.useDeviceOpsRunAction = useDeviceOpsRunAction;
73994
74244
  exports.useDeviceOpsSetConfig = useDeviceOpsSetConfig;
73995
74245
  exports.useDeviceProviderAdoptDiscoveredDevice = useDeviceProviderAdoptDiscoveredDevice;
74246
+ exports.useDeviceProviderClassifyDiscoveryHits = useDeviceProviderClassifyDiscoveryHits;
73996
74247
  exports.useDeviceProviderCreateDevice = useDeviceProviderCreateDevice;
73997
74248
  exports.useDeviceProviderDiscoverDevices = useDeviceProviderDiscoverDevices;
73998
74249
  exports.useDeviceProviderGetChildCreationSchema = useDeviceProviderGetChildCreationSchema;
73999
74250
  exports.useDeviceProviderGetDevices = useDeviceProviderGetDevices;
74251
+ exports.useDeviceProviderGetDiscoveryModes = useDeviceProviderGetDiscoveryModes;
74000
74252
  exports.useDeviceProviderGetDiscoveryParamsSchema = useDeviceProviderGetDiscoveryParamsSchema;
74001
74253
  exports.useDeviceProviderGetManualCreationType = useDeviceProviderGetManualCreationType;
74002
74254
  exports.useDeviceProviderGetStatus = useDeviceProviderGetStatus;
@@ -74168,6 +74420,7 @@ exports.useLocalNetworkGetConnectionEndpoints = useLocalNetworkGetConnectionEndp
74168
74420
  exports.useLocalNetworkGetHubAddresses = useLocalNetworkGetHubAddresses;
74169
74421
  exports.useLocalNetworkGetNotificationEndpoint = useLocalNetworkGetNotificationEndpoint;
74170
74422
  exports.useLocalNetworkGetPreferred = useLocalNetworkGetPreferred;
74423
+ exports.useLocalNetworkGetScanSubnets = useLocalNetworkGetScanSubnets;
74171
74424
  exports.useLocalNetworkGetTlsStatus = useLocalNetworkGetTlsStatus;
74172
74425
  exports.useLocalNetworkGetViewerEndpoints = useLocalNetworkGetViewerEndpoints;
74173
74426
  exports.useLocalNetworkList = useLocalNetworkList;
@@ -74175,9 +74428,11 @@ exports.useLocalNetworkRegenerateCertificate = useLocalNetworkRegenerateCertific
74175
74428
  exports.useLocalNetworkRemoveCustomAddress = useLocalNetworkRemoveCustomAddress;
74176
74429
  exports.useLocalNetworkResetAllowlistToBestMatch = useLocalNetworkResetAllowlistToBestMatch;
74177
74430
  exports.useLocalNetworkRevertToGeneratedCertificate = useLocalNetworkRevertToGeneratedCertificate;
74431
+ exports.useLocalNetworkScanNetwork = useLocalNetworkScanNetwork;
74178
74432
  exports.useLocalNetworkSetAddressRole = useLocalNetworkSetAddressRole;
74179
74433
  exports.useLocalNetworkSetAllowedAddresses = useLocalNetworkSetAllowedAddresses;
74180
74434
  exports.useLocalNetworkSetNotificationEndpoint = useLocalNetworkSetNotificationEndpoint;
74435
+ exports.useLocalNetworkSetScanSubnets = useLocalNetworkSetScanSubnets;
74181
74436
  exports.useLocalNetworkSetViewerEndpoints = useLocalNetworkSetViewerEndpoints;
74182
74437
  exports.useLocalNetworkUploadCertificate = useLocalNetworkUploadCertificate;
74183
74438
  exports.useLocalNetworkUseStoredUploadedCertificate = useLocalNetworkUseStoredUploadedCertificate;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from "./composites";
2
2
  export * from "./hotkeys";
3
3
  export * from "./contexts/custom-field-renderers";
4
+ export * from "./contexts/broker-picker-slot";
4
5
  export * from "./contexts/device-context";
5
6
  export * from "./contexts/device-quick-detail";
6
7
  export * from "./contexts/player-overlays";