@camstack/ui-library 1.2.49 → 1.2.50

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
@@ -35162,255 +35162,6 @@ function useContextSafe(ctx) {
35162
35162
  return (0, react$1.useContext)(ctx);
35163
35163
  }
35164
35164
  //#endregion
35165
- //#region src/composites/widget-panel.tsx
35166
- function WidgetPanel({ title, icon, headerRight, collapsible = false, defaultOpen = true, bodyClassName = "p-3", children }) {
35167
- const [open, setOpen] = (0, react$1.useState)(defaultOpen);
35168
- const showBody = !collapsible || open;
35169
- const headerInner = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
35170
- className: "flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-foreground",
35171
- children: [icon, title]
35172
- }), collapsible ? open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDown, { className: "h-3.5 w-3.5 text-foreground-subtle" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronRight, { className: "h-3.5 w-3.5 text-foreground-subtle" }) : headerRight] });
35173
- const headerClass = `px-4 py-2 flex items-center justify-between ${showBody ? "border-b border-border" : ""}`;
35174
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
35175
- className: "rounded-lg border border-border bg-surface overflow-hidden",
35176
- children: [collapsible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
35177
- type: "button",
35178
- onClick: () => setOpen((v) => !v),
35179
- className: `w-full text-left ${headerClass}`,
35180
- children: headerInner
35181
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
35182
- className: headerClass,
35183
- children: headerInner
35184
- }), showBody ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
35185
- className: bodyClassName,
35186
- children
35187
- }) : null]
35188
- });
35189
- }
35190
- function WidgetMetricCard({ label, icon, children, footer }) {
35191
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
35192
- className: "rounded-md border border-border/60 bg-background/40 px-3 py-2",
35193
- children: [
35194
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
35195
- className: "mb-1 flex items-center gap-1.5 text-[11px] uppercase tracking-wider text-foreground-subtle",
35196
- children: [icon, label]
35197
- }),
35198
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
35199
- className: "text-sm font-medium text-foreground",
35200
- children
35201
- }),
35202
- footer ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
35203
- className: "mt-0.5 text-[10px] text-foreground-subtle",
35204
- children: footer
35205
- }) : null
35206
- ]
35207
- });
35208
- }
35209
- //#endregion
35210
- //#region src/composites/cap-settings/PtzPanel.tsx
35211
- /**
35212
- * PtzPanel — live pan/tilt/zoom controls for the `ptz` capability. A
35213
- * cap-settings component (ui-library), mounted as a cap-contributed
35214
- * top-level device-detail tab via the cap-UI contribution mechanism
35215
- * (the `ptz` cap declares `ui: { tab: 'ptz', kind: 'static', ... }`).
35216
- *
35217
- * Consolidates the former admin-ui `PTZPanelContent`: `usePTZ` drives
35218
- * the controls, `PTZOverlay` renders them (`mode='panel'`), and an
35219
- * Autofocus toggle is shown only when `getOptions().hasAutofocus`.
35220
- * Autotrack is its own cap-UI contribution (`ptz-autotrack`), mounted
35221
- * independently by the contribution mechanism — not nested here.
35222
- */
35223
- /** Autofocus toggle — only mounted when `hasAutofocus`. Reads the cap's
35224
- * `getStatus().autofocus` and drives `setAutofocus`. */
35225
- function AutofocusToggle({ deviceId }) {
35226
- const dev = useDeviceProxy(useSystem().trpcClient, deviceId);
35227
- const [enabled, setEnabled] = (0, react$1.useState)(null);
35228
- const [busy, setBusy] = (0, react$1.useState)(false);
35229
- (0, react$1.useEffect)(() => {
35230
- if (!dev) return void 0;
35231
- let cancelled = false;
35232
- (async () => {
35233
- try {
35234
- const status = await dev.ptz?.getStatus({});
35235
- if (cancelled || !status) return;
35236
- setEnabled(status.autofocus);
35237
- } catch {}
35238
- })();
35239
- return () => {
35240
- cancelled = true;
35241
- };
35242
- }, [dev]);
35243
- const toggle = (0, react$1.useCallback)(async () => {
35244
- if (!dev?.ptz || enabled === null) return;
35245
- setBusy(true);
35246
- try {
35247
- const next = !enabled;
35248
- await dev.ptz.setAutofocus({ enabled: next });
35249
- setEnabled(next);
35250
- } catch (err) {
35251
- console.error("ptz.setAutofocus failed", err);
35252
- } finally {
35253
- setBusy(false);
35254
- }
35255
- }, [dev, enabled]);
35256
- if (enabled === null) return null;
35257
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
35258
- className: "flex items-center justify-between border-t border-border/40 mt-2 pt-2",
35259
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
35260
- className: "text-[10.5px] font-semibold text-foreground",
35261
- children: "Autofocus"
35262
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
35263
- type: "button",
35264
- onClick: () => void toggle(),
35265
- disabled: busy,
35266
- "aria-pressed": enabled,
35267
- className: `flex items-center gap-1.5 rounded-md px-2 py-1 text-[10px] font-medium transition-colors ${enabled ? "bg-success/15 text-success hover:bg-success/25" : "bg-surface-elevated/40 text-foreground-subtle hover:bg-surface-hover hover:text-foreground"} disabled:opacity-50`,
35268
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `h-1.5 w-1.5 rounded-full ${enabled ? "bg-success" : "bg-foreground-subtle/40"}` }), enabled ? "On" : "Off"]
35269
- })]
35270
- });
35271
- }
35272
- function PtzPanel({ deviceId }) {
35273
- const system = useSystem();
35274
- const ready = Number.isFinite(deviceId);
35275
- const controls = usePTZ(system.trpcClient, ready ? deviceId : 0, { enabled: ready });
35276
- if (!ready) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
35277
- className: "flex items-center justify-center h-full text-[11px] text-foreground-subtle italic",
35278
- children: [
35279
- "Device #",
35280
- deviceId,
35281
- " not loaded."
35282
- ]
35283
- });
35284
- const hasAutofocus = controls.options?.hasAutofocus === true;
35285
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(WidgetPanel, {
35286
- title: "PTZ",
35287
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Move, { className: "h-3.5 w-3.5 text-foreground-subtle" }),
35288
- bodyClassName: "p-3 flex flex-col overflow-y-auto",
35289
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PTZOverlay, {
35290
- controls,
35291
- mode: "panel"
35292
- }), hasAutofocus && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutofocusToggle, { deviceId })]
35293
- });
35294
- }
35295
- //#endregion
35296
- //#region src/composites/cap-settings/ConsumablesPanel.tsx
35297
- /**
35298
- * ConsumablesPanel — maintenance-items surface for the generic
35299
- * `consumables` capability. A cap-settings component (ui-library),
35300
- * mounted as a cap-contributed top-level device-detail tab via the
35301
- * cap-UI contribution mechanism (the `consumables` cap declares
35302
- * `ui: { kind:'widget', widgetId:'host/consumables-panel', tab:'consumables', topTab:true, ... }`).
35303
- *
35304
- * Reads the cap's pushed runtime-state slice via `useDeviceCapability`
35305
- * and renders one row per item: a fill bar + percentage when `level` is
35306
- * known, else an `ok`/`replace` status pill when `status` is known, else
35307
- * a muted em-dash. A per-item "Replaced" button calls `cap.reset` when
35308
- * the item is `resettable`. The cap is inert until a provider sets
35309
- * items — an empty list renders a muted placeholder.
35310
- */
35311
- /** Remaining-life % at or below this is treated as a warning. */
35312
- var LOW_LEVEL_THRESHOLD = 15;
35313
- function isLowOrReplace(item) {
35314
- return item.status === "replace" || item.level !== null && item.level <= LOW_LEVEL_THRESHOLD;
35315
- }
35316
- /** Format a ms-epoch as a compact local date for the "replaced" hint. */
35317
- function formatResetAt(ms) {
35318
- try {
35319
- return new Date(ms).toLocaleDateString(void 0, {
35320
- year: "numeric",
35321
- month: "short",
35322
- day: "numeric"
35323
- });
35324
- } catch {
35325
- return "";
35326
- }
35327
- }
35328
- function ConsumableRow({ item, busy, canReset, onReset }) {
35329
- const warn = isLowOrReplace(item);
35330
- const barColor = warn ? "bg-warning" : "bg-success";
35331
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
35332
- className: `flex flex-col gap-1.5 rounded-md border p-3 ${warn ? "border-warning/40 bg-warning/5" : "border-border/40 bg-surface-elevated/30"}`,
35333
- children: [
35334
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
35335
- className: "flex items-center justify-between gap-2",
35336
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
35337
- className: `text-xs font-semibold ${warn ? "text-warning" : "text-foreground"}`,
35338
- children: item.label
35339
- }), item.resettable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
35340
- type: "button",
35341
- onClick: () => onReset(item.key),
35342
- disabled: busy || !canReset,
35343
- className: "rounded-md border border-border bg-surface px-2 py-0.5 text-[10px] font-medium text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors",
35344
- children: "Replaced"
35345
- })]
35346
- }),
35347
- item.level !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
35348
- className: "flex items-center gap-2",
35349
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
35350
- className: "h-1.5 flex-1 overflow-hidden rounded-full bg-surface-elevated/60",
35351
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
35352
- className: `h-full rounded-full ${barColor} transition-all`,
35353
- style: { width: `${Math.max(0, Math.min(100, item.level))}%` }
35354
- })
35355
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
35356
- className: "text-[10px] font-medium tabular-nums text-foreground-subtle",
35357
- children: [Math.round(item.level), "%"]
35358
- })]
35359
- }) : item.status !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
35360
- className: `inline-flex w-fit items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium ${item.status === "replace" ? "bg-warning/15 text-warning" : "bg-success/15 text-success"}`,
35361
- children: item.status === "replace" ? "Replace" : "OK"
35362
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
35363
- className: "text-[10px] text-foreground-subtle",
35364
- children: "—"
35365
- }),
35366
- item.lastResetAt !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
35367
- className: "text-[10px] text-foreground-subtle",
35368
- children: ["Replaced ", formatResetAt(item.lastResetAt)]
35369
- })
35370
- ]
35371
- });
35372
- }
35373
- function ConsumablesPanel({ deviceId }) {
35374
- const system = useSystem();
35375
- const ready = Number.isFinite(deviceId);
35376
- const { cap, slice, available } = useDeviceCapability({
35377
- trpc: system.trpcClient,
35378
- deviceId: ready ? deviceId : null,
35379
- cap: (d) => d.consumables,
35380
- slice: (d) => d.state.consumables
35381
- });
35382
- const [busyKey, setBusyKey] = (0, react$1.useState)(null);
35383
- const onReset = (0, react$1.useCallback)(async (key) => {
35384
- if (!cap) return;
35385
- setBusyKey(key);
35386
- try {
35387
- await cap.reset({ key });
35388
- } catch (err) {
35389
- console.error("consumables.reset failed", err);
35390
- } finally {
35391
- setBusyKey(null);
35392
- }
35393
- }, [cap]);
35394
- const items = slice?.items ?? [];
35395
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetPanel, {
35396
- title: "Consumables",
35397
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Gauge, { className: "h-3.5 w-3.5 text-foreground-subtle" }),
35398
- bodyClassName: "p-3 overflow-y-auto",
35399
- children: items.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
35400
- className: "text-[11px] text-foreground-subtle italic leading-relaxed",
35401
- children: "No consumables reported."
35402
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
35403
- className: "flex flex-col gap-2",
35404
- children: items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableRow, {
35405
- item,
35406
- busy: busyKey === item.key,
35407
- canReset: available,
35408
- onReset: (key) => void onReset(key)
35409
- }, item.key))
35410
- })
35411
- });
35412
- }
35413
- //#endregion
35414
35165
  //#region src/hooks/use-device-autotrack.ts
35415
35166
  /**
35416
35167
  * `useDeviceAutotrack` — typed wrapper around the `ptz-autotrack` cap.
@@ -35469,6 +35220,51 @@ function useDeviceAutotrack(deviceId) {
35469
35220
  };
35470
35221
  }
35471
35222
  //#endregion
35223
+ //#region src/composites/widget-panel.tsx
35224
+ function WidgetPanel({ title, icon, headerRight, collapsible = false, defaultOpen = true, bodyClassName = "p-3", children }) {
35225
+ const [open, setOpen] = (0, react$1.useState)(defaultOpen);
35226
+ const showBody = !collapsible || open;
35227
+ const headerInner = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
35228
+ className: "flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-foreground",
35229
+ children: [icon, title]
35230
+ }), collapsible ? open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDown, { className: "h-3.5 w-3.5 text-foreground-subtle" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronRight, { className: "h-3.5 w-3.5 text-foreground-subtle" }) : headerRight] });
35231
+ const headerClass = `px-4 py-2 flex items-center justify-between ${showBody ? "border-b border-border" : ""}`;
35232
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
35233
+ className: "rounded-lg border border-border bg-surface overflow-hidden",
35234
+ children: [collapsible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
35235
+ type: "button",
35236
+ onClick: () => setOpen((v) => !v),
35237
+ className: `w-full text-left ${headerClass}`,
35238
+ children: headerInner
35239
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
35240
+ className: headerClass,
35241
+ children: headerInner
35242
+ }), showBody ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
35243
+ className: bodyClassName,
35244
+ children
35245
+ }) : null]
35246
+ });
35247
+ }
35248
+ function WidgetMetricCard({ label, icon, children, footer }) {
35249
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
35250
+ className: "rounded-md border border-border/60 bg-background/40 px-3 py-2",
35251
+ children: [
35252
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
35253
+ className: "mb-1 flex items-center gap-1.5 text-[11px] uppercase tracking-wider text-foreground-subtle",
35254
+ children: [icon, label]
35255
+ }),
35256
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
35257
+ className: "text-sm font-medium text-foreground",
35258
+ children
35259
+ }),
35260
+ footer ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
35261
+ className: "mt-0.5 text-[10px] text-foreground-subtle",
35262
+ children: footer
35263
+ }) : null
35264
+ ]
35265
+ });
35266
+ }
35267
+ //#endregion
35472
35268
  //#region src/composites/cap-settings/AutotrackSection.tsx
35473
35269
  /**
35474
35270
  * Autotrack settings card — rendered inside the PTZ panel when the
@@ -35621,6 +35417,210 @@ function AutotrackSection({ deviceId }) {
35621
35417
  });
35622
35418
  }
35623
35419
  //#endregion
35420
+ //#region src/composites/cap-settings/ConsumablesPanel.tsx
35421
+ /**
35422
+ * ConsumablesPanel — maintenance-items surface for the generic
35423
+ * `consumables` capability. A cap-settings component (ui-library),
35424
+ * mounted as a cap-contributed top-level device-detail tab via the
35425
+ * cap-UI contribution mechanism (the `consumables` cap declares
35426
+ * `ui: { kind:'widget', widgetId:'host/consumables-panel', tab:'consumables', topTab:true, ... }`).
35427
+ *
35428
+ * Reads the cap's pushed runtime-state slice via `useDeviceCapability`
35429
+ * and renders one row per item: a fill bar + percentage when `level` is
35430
+ * known, else an `ok`/`replace` status pill when `status` is known, else
35431
+ * a muted em-dash. A per-item "Replaced" button calls `cap.reset` when
35432
+ * the item is `resettable`. The cap is inert until a provider sets
35433
+ * items — an empty list renders a muted placeholder.
35434
+ */
35435
+ /** Remaining-life % at or below this is treated as a warning. */
35436
+ var LOW_LEVEL_THRESHOLD = 15;
35437
+ function isLowOrReplace(item) {
35438
+ return item.status === "replace" || item.level !== null && item.level <= LOW_LEVEL_THRESHOLD;
35439
+ }
35440
+ /** Format a ms-epoch as a compact local date for the "replaced" hint. */
35441
+ function formatResetAt(ms) {
35442
+ try {
35443
+ return new Date(ms).toLocaleDateString(void 0, {
35444
+ year: "numeric",
35445
+ month: "short",
35446
+ day: "numeric"
35447
+ });
35448
+ } catch {
35449
+ return "";
35450
+ }
35451
+ }
35452
+ function ConsumableRow({ item, busy, canReset, onReset }) {
35453
+ const warn = isLowOrReplace(item);
35454
+ const barColor = warn ? "bg-warning" : "bg-success";
35455
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
35456
+ className: `flex flex-col gap-1.5 rounded-md border p-3 ${warn ? "border-warning/40 bg-warning/5" : "border-border/40 bg-surface-elevated/30"}`,
35457
+ children: [
35458
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
35459
+ className: "flex items-center justify-between gap-2",
35460
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
35461
+ className: `text-xs font-semibold ${warn ? "text-warning" : "text-foreground"}`,
35462
+ children: item.label
35463
+ }), item.resettable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
35464
+ type: "button",
35465
+ onClick: () => onReset(item.key),
35466
+ disabled: busy || !canReset,
35467
+ className: "rounded-md border border-border bg-surface px-2 py-0.5 text-[10px] font-medium text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors",
35468
+ children: "Replaced"
35469
+ })]
35470
+ }),
35471
+ item.level !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
35472
+ className: "flex items-center gap-2",
35473
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
35474
+ className: "h-1.5 flex-1 overflow-hidden rounded-full bg-surface-elevated/60",
35475
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
35476
+ className: `h-full rounded-full ${barColor} transition-all`,
35477
+ style: { width: `${Math.max(0, Math.min(100, item.level))}%` }
35478
+ })
35479
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
35480
+ className: "text-[10px] font-medium tabular-nums text-foreground-subtle",
35481
+ children: [Math.round(item.level), "%"]
35482
+ })]
35483
+ }) : item.status !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
35484
+ className: `inline-flex w-fit items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium ${item.status === "replace" ? "bg-warning/15 text-warning" : "bg-success/15 text-success"}`,
35485
+ children: item.status === "replace" ? "Replace" : "OK"
35486
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
35487
+ className: "text-[10px] text-foreground-subtle",
35488
+ children: "—"
35489
+ }),
35490
+ item.lastResetAt !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
35491
+ className: "text-[10px] text-foreground-subtle",
35492
+ children: ["Replaced ", formatResetAt(item.lastResetAt)]
35493
+ })
35494
+ ]
35495
+ });
35496
+ }
35497
+ function ConsumablesPanel({ deviceId }) {
35498
+ const system = useSystem();
35499
+ const ready = Number.isFinite(deviceId);
35500
+ const { cap, slice, available } = useDeviceCapability({
35501
+ trpc: system.trpcClient,
35502
+ deviceId: ready ? deviceId : null,
35503
+ cap: (d) => d.consumables,
35504
+ slice: (d) => d.state.consumables
35505
+ });
35506
+ const [busyKey, setBusyKey] = (0, react$1.useState)(null);
35507
+ const onReset = (0, react$1.useCallback)(async (key) => {
35508
+ if (!cap) return;
35509
+ setBusyKey(key);
35510
+ try {
35511
+ await cap.reset({ key });
35512
+ } catch (err) {
35513
+ console.error("consumables.reset failed", err);
35514
+ } finally {
35515
+ setBusyKey(null);
35516
+ }
35517
+ }, [cap]);
35518
+ const items = slice?.items ?? [];
35519
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetPanel, {
35520
+ title: "Consumables",
35521
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Gauge, { className: "h-3.5 w-3.5 text-foreground-subtle" }),
35522
+ bodyClassName: "p-3 overflow-y-auto",
35523
+ children: items.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
35524
+ className: "text-[11px] text-foreground-subtle italic leading-relaxed",
35525
+ children: "No consumables reported."
35526
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
35527
+ className: "flex flex-col gap-2",
35528
+ children: items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConsumableRow, {
35529
+ item,
35530
+ busy: busyKey === item.key,
35531
+ canReset: available,
35532
+ onReset: (key) => void onReset(key)
35533
+ }, item.key))
35534
+ })
35535
+ });
35536
+ }
35537
+ //#endregion
35538
+ //#region src/composites/cap-settings/PtzPanel.tsx
35539
+ /**
35540
+ * PtzPanel — live pan/tilt/zoom controls for the `ptz` capability. A
35541
+ * cap-settings component (ui-library), mounted as a cap-contributed
35542
+ * top-level device-detail tab via the cap-UI contribution mechanism
35543
+ * (the `ptz` cap declares `ui: { tab: 'ptz', kind: 'static', ... }`).
35544
+ *
35545
+ * Consolidates the former admin-ui `PTZPanelContent`: `usePTZ` drives
35546
+ * the controls, `PTZOverlay` renders them (`mode='panel'`), and an
35547
+ * Autofocus toggle is shown only when `getOptions().hasAutofocus`.
35548
+ * Autotrack is its own cap-UI contribution (`ptz-autotrack`), mounted
35549
+ * independently by the contribution mechanism — not nested here.
35550
+ */
35551
+ /** Autofocus toggle — only mounted when `hasAutofocus`. Reads the cap's
35552
+ * `getStatus().autofocus` and drives `setAutofocus`. */
35553
+ function AutofocusToggle({ deviceId }) {
35554
+ const dev = useDeviceProxy(useSystem().trpcClient, deviceId);
35555
+ const [enabled, setEnabled] = (0, react$1.useState)(null);
35556
+ const [busy, setBusy] = (0, react$1.useState)(false);
35557
+ (0, react$1.useEffect)(() => {
35558
+ if (!dev) return void 0;
35559
+ let cancelled = false;
35560
+ (async () => {
35561
+ try {
35562
+ const status = await dev.ptz?.getStatus({});
35563
+ if (cancelled || !status) return;
35564
+ setEnabled(status.autofocus);
35565
+ } catch {}
35566
+ })();
35567
+ return () => {
35568
+ cancelled = true;
35569
+ };
35570
+ }, [dev]);
35571
+ const toggle = (0, react$1.useCallback)(async () => {
35572
+ if (!dev?.ptz || enabled === null) return;
35573
+ setBusy(true);
35574
+ try {
35575
+ const next = !enabled;
35576
+ await dev.ptz.setAutofocus({ enabled: next });
35577
+ setEnabled(next);
35578
+ } catch (err) {
35579
+ console.error("ptz.setAutofocus failed", err);
35580
+ } finally {
35581
+ setBusy(false);
35582
+ }
35583
+ }, [dev, enabled]);
35584
+ if (enabled === null) return null;
35585
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
35586
+ className: "flex items-center justify-between border-t border-border/40 mt-2 pt-2",
35587
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
35588
+ className: "text-[10.5px] font-semibold text-foreground",
35589
+ children: "Autofocus"
35590
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
35591
+ type: "button",
35592
+ onClick: () => void toggle(),
35593
+ disabled: busy,
35594
+ "aria-pressed": enabled,
35595
+ className: `flex items-center gap-1.5 rounded-md px-2 py-1 text-[10px] font-medium transition-colors ${enabled ? "bg-success/15 text-success hover:bg-success/25" : "bg-surface-elevated/40 text-foreground-subtle hover:bg-surface-hover hover:text-foreground"} disabled:opacity-50`,
35596
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `h-1.5 w-1.5 rounded-full ${enabled ? "bg-success" : "bg-foreground-subtle/40"}` }), enabled ? "On" : "Off"]
35597
+ })]
35598
+ });
35599
+ }
35600
+ function PtzPanel({ deviceId }) {
35601
+ const system = useSystem();
35602
+ const ready = Number.isFinite(deviceId);
35603
+ const controls = usePTZ(system.trpcClient, ready ? deviceId : 0, { enabled: ready });
35604
+ if (!ready) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
35605
+ className: "flex items-center justify-center h-full text-[11px] text-foreground-subtle italic",
35606
+ children: [
35607
+ "Device #",
35608
+ deviceId,
35609
+ " not loaded."
35610
+ ]
35611
+ });
35612
+ const hasAutofocus = controls.options?.hasAutofocus === true;
35613
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(WidgetPanel, {
35614
+ title: "PTZ",
35615
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Move, { className: "h-3.5 w-3.5 text-foreground-subtle" }),
35616
+ bodyClassName: "p-3 flex flex-col overflow-y-auto",
35617
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PTZOverlay, {
35618
+ controls,
35619
+ mode: "panel"
35620
+ }), hasAutofocus && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutofocusToggle, { deviceId })]
35621
+ });
35622
+ }
35623
+ //#endregion
35624
35624
  //#region src/composites/cap-settings/recording-config-form.ts
35625
35625
  /** Every weekday — the `days` of a band that covers the whole week. */
35626
35626
  var ALL_WEEKDAYS = [
@@ -37527,12 +37527,17 @@ function RecordingPanel({ deviceId }) {
37527
37527
  })]
37528
37528
  });
37529
37529
  }
37530
+ //#endregion
37531
+ //#region src/widgets/host-widgets.ts
37532
+ var MotionZonesSettingsLazy = (0, react$1.lazy)(() => Promise.resolve().then(() => require("./MotionZonesSettings-BP9p0pFB.cjs")).then((m) => ({ default: m.MotionZonesSettings })));
37533
+ var SceneMonitorEditorLazy = (0, react$1.lazy)(() => Promise.resolve().then(() => require("./SceneMonitorEditor-BXwXLLch.cjs")).then((m) => ({ default: m.SceneMonitorEditor })));
37530
37534
  /** Host widgets — ui-library React components embeddable as a
37531
37535
  * `type:'widget'` ConfigField. `WidgetSlot` resolves these BEFORE the
37532
37536
  * Module-Federation `WidgetRegistry`. ids are namespaced `host/<name>`. */
37533
37537
  var HOST_WIDGETS = {
37534
- "host/motion-zones-grid": (0, react$1.lazy)(() => Promise.resolve().then(() => require("./MotionZonesSettings-BP9p0pFB.cjs")).then((m) => ({ default: m.MotionZonesSettings }))),
37538
+ "host/motion-zones-grid": MotionZonesSettingsLazy,
37535
37539
  "host/privacy-mask-grid": (0, react$1.lazy)(() => Promise.resolve().then(() => require("./PrivacyMaskSettings-Oo7gyB4E.cjs")).then((m) => ({ default: m.PrivacyMaskSettings }))),
37540
+ "host/scene-monitor-editor": SceneMonitorEditorLazy,
37536
37541
  "host/ptz-panel": PtzPanel,
37537
37542
  "host/consumables-panel": ConsumablesPanel,
37538
37543
  "host/ptz-autotrack": AutotrackSection,
@@ -48582,6 +48587,7 @@ exports.COMMIT_DEDUPE_TOLERANCE_MS = COMMIT_DEDUPE_TOLERANCE_MS;
48582
48587
  exports.COMMIT_DEDUPE_WINDOW_MS = COMMIT_DEDUPE_WINDOW_MS;
48583
48588
  exports.CONTROL_CAP_NAMES = CONTROL_CAP_NAMES;
48584
48589
  exports.CONTROL_FILLS = CONTROL_FILLS;
48590
+ exports.Camera = Camera;
48585
48591
  exports.CameraStreamPlayer = CameraStreamPlayer;
48586
48592
  exports.Card = Card;
48587
48593
  exports.Checkbox = Checkbox;
@@ -48733,6 +48739,7 @@ exports.RadialGauge = RadialGauge;
48733
48739
  exports.RecordedPlaybackProvider = RecordedPlaybackProvider;
48734
48740
  exports.RecordingPanel = RecordingPanel;
48735
48741
  exports.ResponseLog = ResponseLog;
48742
+ exports.RotateCcw = RotateCcw;
48736
48743
  exports.SECTION_BODY = SECTION_BODY;
48737
48744
  exports.SECTION_CARD = SECTION_CARD;
48738
48745
  exports.SECTION_HEADER = SECTION_HEADER;
@@ -49213,12 +49220,14 @@ exports.useDeviceProxy = useDeviceProxy;
49213
49220
  exports.useDeviceProxy$1 = useDeviceProxy;
49214
49221
  exports.useDeviceSnapshot = useDeviceSnapshot;
49215
49222
  exports.useDeviceSnapshotImage = useDeviceSnapshotImage;
49223
+ exports.useDeviceSnapshotImage$1 = useDeviceSnapshotImage;
49216
49224
  exports.useDeviceState = useDeviceState;
49217
49225
  exports.useDeviceStateGetAllSnapshots = useDeviceStateGetAllSnapshots;
49218
49226
  exports.useDeviceStateGetCapSlice = useDeviceStateGetCapSlice;
49219
49227
  exports.useDeviceStateGetSnapshot = useDeviceStateGetSnapshot;
49220
49228
  exports.useDeviceStateSetCapSlice = useDeviceStateSetCapSlice;
49221
49229
  exports.useDeviceStateSlice = useDeviceStateSlice;
49230
+ exports.useDeviceStateSlice$1 = useDeviceStateSlice;
49222
49231
  exports.useDeviceStatusGetStatus = useDeviceStatusGetStatus;
49223
49232
  exports.useDeviceWebrtc = useDeviceWebrtc;
49224
49233
  exports.useDevices = useDevices;