@camstack/ui-library 1.2.163 → 1.2.165

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.
@@ -1,13 +1,58 @@
1
1
  import { ReactNode } from 'react';
2
- interface ConfirmOptions {
3
- title: string;
4
- message: string;
5
- confirmLabel?: string;
6
- cancelLabel?: string;
7
- variant?: 'danger' | 'warning' | 'default';
2
+ export type DialogVariant = 'danger' | 'warning' | 'default';
3
+ export interface ConfirmOptions {
4
+ readonly title: string;
5
+ readonly message: string;
6
+ readonly confirmLabel?: string;
7
+ readonly cancelLabel?: string;
8
+ readonly variant?: DialogVariant;
9
+ }
10
+ export interface AlertOptions {
11
+ readonly title: string;
12
+ readonly message: string;
13
+ readonly acknowledgeLabel?: string;
14
+ readonly variant?: DialogVariant;
15
+ }
16
+ export type PromptInputType = 'text' | 'password';
17
+ export interface PromptOptions {
18
+ readonly title: string;
19
+ /** Explanatory paragraph above the field. */
20
+ readonly message?: string;
21
+ /** Label rendered against the input. Defaults to the title. */
22
+ readonly label?: string;
23
+ readonly defaultValue?: string;
24
+ /** `password` masks the value and opts out of autofill/remember. */
25
+ readonly inputType?: PromptInputType;
26
+ readonly placeholder?: string;
27
+ readonly confirmLabel?: string;
28
+ readonly cancelLabel?: string;
29
+ /**
30
+ * A field the operator reads/copies rather than edits (the clipboard
31
+ * fallback). Rendered unmasked, selected on open, with no validation.
32
+ */
33
+ readonly readOnly?: boolean;
34
+ /**
35
+ * Returns an error message to REFUSE the value and keep the dialog open, or
36
+ * `null` to accept. Runs on submit — the dialog is the validation surface, so
37
+ * a refusal never needs a second `alert`.
38
+ */
39
+ readonly validate?: (value: string) => string | null;
40
+ }
41
+ export interface DialogContextValue {
42
+ readonly confirm: (options: ConfirmOptions) => Promise<boolean>;
43
+ readonly alert: (options: AlertOptions) => Promise<void>;
44
+ readonly prompt: (options: PromptOptions) => Promise<string | null>;
8
45
  }
9
46
  export declare function useConfirm(): (options: ConfirmOptions) => Promise<boolean>;
47
+ /** An error/notice with a single acknowledge. Replaces `window.alert`. */
48
+ export declare function useAlert(): (options: AlertOptions) => Promise<void>;
49
+ /** One text input with OK/Cancel. Replaces `window.prompt`. */
50
+ export declare function usePrompt(): (options: PromptOptions) => Promise<string | null>;
10
51
  export declare function ConfirmDialogProvider({ children }: {
11
52
  children: ReactNode;
12
53
  }): import("react").JSX.Element;
13
- export {};
54
+ /**
55
+ * Promotes `el` to the top layer. jsdom has `<dialog>` but no `showModal`, so
56
+ * the attribute is the fallback — see the file docblock.
57
+ */
58
+ export declare function openTopLayer(el: HTMLDialogElement): void;
@@ -1039,6 +1039,8 @@ export declare const useNotificationRulesUpdateRule: typeof trpc.notificationRul
1039
1039
  export declare const useNotificationRulesDeleteRule: typeof trpc.notificationRules.deleteRule.useMutation;
1040
1040
  /** Generated alias around `trpc.notificationRules.setRuleEnabled.useMutation`. */
1041
1041
  export declare const useNotificationRulesSetRuleEnabled: typeof trpc.notificationRules.setRuleEnabled.useMutation;
1042
+ /** Generated alias around `trpc.notificationRules.getClipRetentionAsks.useQuery`. */
1043
+ export declare const useNotificationRulesGetClipRetentionAsks: typeof trpc.notificationRules.getClipRetentionAsks.useQuery;
1042
1044
  /** Generated alias around `trpc.notificationRules.listDeviceMutes.useQuery`. */
1043
1045
  export declare const useNotificationRulesListDeviceMutes: typeof trpc.notificationRules.listDeviceMutes.useQuery;
1044
1046
  /** Generated alias around `trpc.notificationRules.setDeviceMuted.useMutation`. */
@@ -1443,6 +1445,8 @@ export declare const usePipelineRunnerGetCameraMetrics: typeof trpc.pipelineRunn
1443
1445
  export declare const usePipelineRunnerGetAllCameraMetrics: typeof trpc.pipelineRunner.getAllCameraMetrics.useQuery;
1444
1446
  /** Generated alias around `trpc.pipelineRunner.getLocalCameras.useQuery`. */
1445
1447
  export declare const usePipelineRunnerGetLocalCameras: typeof trpc.pipelineRunner.getLocalCameras.useQuery;
1448
+ /** Generated alias around `trpc.pipelineRunner.requestOccupancyBurst.useMutation`. */
1449
+ export declare const usePipelineRunnerRequestOccupancyBurst: typeof trpc.pipelineRunner.requestOccupancyBurst.useMutation;
1446
1450
  /** Generated alias around `trpc.pipelineRunner.getNativeCrop.useQuery`. */
1447
1451
  export declare const usePipelineRunnerGetNativeCrop: typeof trpc.pipelineRunner.getNativeCrop.useQuery;
1448
1452
  /** Generated alias around `trpc.pipelineRunner.runDetailSubtree.useMutation`. */
@@ -1,5 +1,25 @@
1
1
  import { StationaryOverlayBox } from '@camstack/types';
2
- import { UseDeviceProxyTrpc } from './use-device-proxy';
2
+ /**
3
+ * The ONE route this hook needs.
4
+ *
5
+ * Deliberately NOT the device proxy. The proxy exposes only the caps BOUND to
6
+ * a device, and `zone-analytics` is not one of them — on device 617 the
7
+ * bindings are `pipeline-analytics` and `motion-zones`. Reading the census
8
+ * through `dev?.zoneAnalytics?.…` therefore resolved to `undefined`, the hook
9
+ * returned an empty census, and the overlay drew nothing while reporting
10
+ * nothing: an unreachable cap was indistinguishable from an empty lot. The cap
11
+ * ROUTE is mounted system-wide and takes the `deviceId` as input, which is how
12
+ * every other caller reaches it.
13
+ */
14
+ export interface UseStationaryOverlayTrpc {
15
+ readonly zoneAnalytics: {
16
+ readonly getCurrentSnapshot: {
17
+ readonly query: (input: {
18
+ deviceId: number;
19
+ }) => Promise<unknown>;
20
+ };
21
+ };
22
+ }
3
23
  interface SnapshotStationary {
4
24
  readonly id: string;
5
25
  readonly className: string;
@@ -22,5 +42,5 @@ interface SnapshotStationary {
22
42
  * reads as a real detection in the wrong place.
23
43
  */
24
44
  export declare function toStationaryOverlayBox(row: SnapshotStationary): StationaryOverlayBox | null;
25
- export declare function useStationaryOverlay(trpc: UseDeviceProxyTrpc, deviceId: number | null): readonly StationaryOverlayBox[] | undefined;
45
+ export declare function useStationaryOverlay(trpc: UseStationaryOverlayTrpc, deviceId: number | null): readonly StationaryOverlayBox[] | undefined;
26
46
  export {};
package/dist/index.cjs CHANGED
@@ -6091,6 +6091,8 @@ var useNotificationRulesUpdateRule = trpc.notificationRules.updateRule.useMutati
6091
6091
  var useNotificationRulesDeleteRule = trpc.notificationRules.deleteRule.useMutation;
6092
6092
  /** Generated alias around `trpc.notificationRules.setRuleEnabled.useMutation`. */
6093
6093
  var useNotificationRulesSetRuleEnabled = trpc.notificationRules.setRuleEnabled.useMutation;
6094
+ /** Generated alias around `trpc.notificationRules.getClipRetentionAsks.useQuery`. */
6095
+ var useNotificationRulesGetClipRetentionAsks = trpc.notificationRules.getClipRetentionAsks.useQuery;
6094
6096
  /** Generated alias around `trpc.notificationRules.listDeviceMutes.useQuery`. */
6095
6097
  var useNotificationRulesListDeviceMutes = trpc.notificationRules.listDeviceMutes.useQuery;
6096
6098
  /** Generated alias around `trpc.notificationRules.setDeviceMuted.useMutation`. */
@@ -6495,6 +6497,8 @@ var usePipelineRunnerGetCameraMetrics = trpc.pipelineRunner.getCameraMetrics.use
6495
6497
  var usePipelineRunnerGetAllCameraMetrics = trpc.pipelineRunner.getAllCameraMetrics.useQuery;
6496
6498
  /** Generated alias around `trpc.pipelineRunner.getLocalCameras.useQuery`. */
6497
6499
  var usePipelineRunnerGetLocalCameras = trpc.pipelineRunner.getLocalCameras.useQuery;
6500
+ /** Generated alias around `trpc.pipelineRunner.requestOccupancyBurst.useMutation`. */
6501
+ var usePipelineRunnerRequestOccupancyBurst = trpc.pipelineRunner.requestOccupancyBurst.useMutation;
6498
6502
  /** Generated alias around `trpc.pipelineRunner.getNativeCrop.useQuery`. */
6499
6503
  var usePipelineRunnerGetNativeCrop = trpc.pipelineRunner.getNativeCrop.useQuery;
6500
6504
  /** Generated alias around `trpc.pipelineRunner.runDetailSubtree.useMutation`. */
@@ -11269,82 +11273,340 @@ function EventKindGlyph({ iconId, size = 16, color, className }) {
11269
11273
  }
11270
11274
  //#endregion
11271
11275
  //#region src/composites/confirm-dialog.tsx
11276
+ /**
11277
+ * The ONE modal dialog of the product — confirm, alert and prompt.
11278
+ *
11279
+ * ## Why all three live here
11280
+ *
11281
+ * The admin UI used to reach for `window.confirm` / `window.alert` /
11282
+ * `window.prompt` wherever a `useConfirm()` call was inconvenient. A native
11283
+ * dialog is wrong on three counts, and the operator hit the worst one on
11284
+ * Identity → Users: resetting a password opened `window.prompt`, which
11285
+ * **cannot mask its input**. A new password was typed in clear text, into a
11286
+ * box the browser may offer to remember. On top of that a native dialog
11287
+ * matches none of the product's visual language and the host can neither style
11288
+ * nor test it.
11289
+ *
11290
+ * So the fix is not "a password modal" — a second dialog component is the
11291
+ * defect, not the cure. The provider that already served every confirm in the
11292
+ * admin UI (and in the viewer, which documents its own copy as "deliberately
11293
+ * the same one `@camstack/ui-library` exposes to the admin UI") grew two more
11294
+ * intents. One provider, one backdrop, one visual language, one set of
11295
+ * keyboard rules.
11296
+ *
11297
+ * ## The three intents are NOT one control
11298
+ *
11299
+ * - `confirm` — a destructive yes/no. It names what is destroyed, and for the
11300
+ * `danger` variant the destructive button is the NON-default: opening focus
11301
+ * lands on Cancel, so a reflexive Enter cancels.
11302
+ * - `alert` — an error/notice with a single acknowledge.
11303
+ * - `prompt` — one text input with OK/Cancel. `inputType: 'password'` masks it
11304
+ * (and opts out of autofill/remember); `readOnly` turns it into a readable,
11305
+ * select-all-on-open field, which is the shape the copy-to-clipboard
11306
+ * fallback needs — a password box would be actively wrong there.
11307
+ *
11308
+ * ## Why the surface is a native `<dialog>`
11309
+ *
11310
+ * These dialogs open OVER surfaces that are themselves native `<dialog>`s
11311
+ * opened with `showModal()` — the event detail panel, the similar-groups
11312
+ * modal. A `showModal()` dialog is promoted to the TOP LAYER and everything
11313
+ * outside it is inert, so a plain `fixed inset-0` overlay rendered at the app
11314
+ * root would be drawn behind that backdrop and could not be clicked at all.
11315
+ * Confirming a track delete from the detail panel would have been impossible.
11316
+ * So this surface is a `<dialog>` of its own: the top layer stacks, the newest
11317
+ * `showModal()` wins, and the confirm sits above the panel that asked for it.
11318
+ *
11319
+ * jsdom implements `<dialog>` but not `showModal()`, so the open falls back to
11320
+ * the `open` attribute there. Everything this component does — focus, keys,
11321
+ * resolution — is identical on both paths; only the top-layer promotion, which
11322
+ * jsdom cannot model at all, is missing. The fallback is asserted below.
11323
+ *
11324
+ * ## Focus, and why there is NO focus trap
11325
+ *
11326
+ * A `showModal()` dialog already scopes the tab ring to itself, and the
11327
+ * surfaces underneath (`SlideOverPanel`) implement no trap of their own. A
11328
+ * second, hand-rolled focus trap on top of that is how a page becomes
11329
+ * unescapable, so this component deliberately does not trap. What it does do:
11330
+ *
11331
+ * - focuses the input (prompt) or the DEFAULT button on open,
11332
+ * - restores focus to the previously focused element on close,
11333
+ * - handles Escape/Enter from a `keydown` on the dialog itself and calls
11334
+ * `stopPropagation()`. React attaches its listeners at the root container, so
11335
+ * stopping there keeps the event from reaching the `window` Escape listener
11336
+ * of whatever modal is underneath: one Escape closes ONE dialog.
11337
+ */
11272
11338
  var ConfirmContext = createSharedContext("camstack:confirm-dialog", null);
11273
- function useConfirm() {
11339
+ function useDialogContext$1() {
11274
11340
  const ctx = (0, react$1.useContext)(ConfirmContext);
11275
11341
  if (!ctx) throw new Error("useConfirm must be used within ConfirmDialogProvider");
11276
- return ctx.confirm;
11342
+ return ctx;
11343
+ }
11344
+ function useConfirm() {
11345
+ return useDialogContext$1().confirm;
11346
+ }
11347
+ /** An error/notice with a single acknowledge. Replaces `window.alert`. */
11348
+ function useAlert() {
11349
+ return useDialogContext$1().alert;
11350
+ }
11351
+ /** One text input with OK/Cancel. Replaces `window.prompt`. */
11352
+ function usePrompt() {
11353
+ return useDialogContext$1().prompt;
11354
+ }
11355
+ var VARIANT_STYLES$2 = {
11356
+ danger: { button: "bg-red-500 hover:bg-red-600 text-white" },
11357
+ warning: { button: "bg-orange-500 hover:bg-orange-600 text-white" },
11358
+ default: { button: "bg-primary hover:bg-primary/90 text-white" }
11359
+ };
11360
+ var CANCEL_CLASS = "px-3 py-1.5 text-xs font-medium rounded-md border border-border text-foreground-subtle hover:text-foreground hover:bg-surface-hover transition-colors";
11361
+ function variantOf(state) {
11362
+ if (state.kind === "prompt") return "default";
11363
+ return state.options.variant ?? "default";
11364
+ }
11365
+ /**
11366
+ * Whether the affirmative button is the DEFAULT (initially focused) control.
11367
+ * A `danger` confirm says no: the destructive action must be deliberate.
11368
+ */
11369
+ function affirmativeIsDefault(state) {
11370
+ return variantOf(state) !== "danger";
11277
11371
  }
11278
11372
  function ConfirmDialogProvider({ children }) {
11279
11373
  const [dialog, setDialog] = (0, react$1.useState)(null);
11280
- const confirm = (0, react$1.useCallback)((options) => {
11281
- return new Promise((resolve) => {
11282
- setDialog({
11283
- ...options,
11284
- resolve
11285
- });
11374
+ const confirm = (0, react$1.useCallback)((options) => new Promise((resolve) => {
11375
+ setDialog({
11376
+ kind: "confirm",
11377
+ options,
11378
+ resolve
11379
+ });
11380
+ }), []);
11381
+ const alert = (0, react$1.useCallback)((options) => new Promise((resolve) => {
11382
+ setDialog({
11383
+ kind: "alert",
11384
+ options,
11385
+ resolve: () => resolve()
11286
11386
  });
11387
+ }), []);
11388
+ const prompt = (0, react$1.useCallback)((options) => new Promise((resolve) => {
11389
+ setDialog({
11390
+ kind: "prompt",
11391
+ options,
11392
+ resolve
11393
+ });
11394
+ }), []);
11395
+ const close = (0, react$1.useCallback)(() => setDialog(null), []);
11396
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ConfirmContext.Provider, {
11397
+ value: {
11398
+ confirm,
11399
+ alert,
11400
+ prompt
11401
+ },
11402
+ children: [children, dialog && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogSurface, {
11403
+ state: dialog,
11404
+ onClose: close
11405
+ }, dialogKey(dialog))]
11406
+ });
11407
+ }
11408
+ /**
11409
+ * A key that changes per OPENED dialog, so a second dialog opened while one is
11410
+ * showing remounts the surface (fresh input value, fresh focus) instead of
11411
+ * inheriting the previous one's state.
11412
+ */
11413
+ function dialogKey(state) {
11414
+ return `${state.kind}:${state.options.title}`;
11415
+ }
11416
+ /**
11417
+ * Promotes `el` to the top layer. jsdom has `<dialog>` but no `showModal`, so
11418
+ * the attribute is the fallback — see the file docblock.
11419
+ */
11420
+ function openTopLayer(el) {
11421
+ if (typeof el.showModal === "function") el.showModal();
11422
+ else el.setAttribute("open", "");
11423
+ }
11424
+ function closeTopLayer(el) {
11425
+ if (!el.open) return;
11426
+ if (typeof el.close === "function") el.close();
11427
+ else el.removeAttribute("open");
11428
+ }
11429
+ function DialogSurface({ state, onClose }) {
11430
+ const dialogRef = (0, react$1.useRef)(null);
11431
+ const inputRef = (0, react$1.useRef)(null);
11432
+ const defaultButtonRef = (0, react$1.useRef)(null);
11433
+ const settled = (0, react$1.useRef)(false);
11434
+ const [value, setValue] = (0, react$1.useState)(state.kind === "prompt" ? state.options.defaultValue ?? "" : "");
11435
+ const [error, setError] = (0, react$1.useState)(null);
11436
+ (0, react$1.useEffect)(() => {
11437
+ const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;
11438
+ const el = dialogRef.current;
11439
+ if (el) openTopLayer(el);
11440
+ const input = inputRef.current;
11441
+ if (input) {
11442
+ input.focus();
11443
+ if (state.kind === "prompt" && state.options.readOnly === true) input.select();
11444
+ } else defaultButtonRef.current?.focus();
11445
+ return () => {
11446
+ if (el) closeTopLayer(el);
11447
+ previous?.focus();
11448
+ };
11287
11449
  }, []);
11288
- const handleConfirm = () => {
11289
- dialog?.resolve(true);
11290
- setDialog(null);
11291
- };
11292
- const handleCancel = () => {
11293
- dialog?.resolve(false);
11294
- setDialog(null);
11295
- };
11296
- const variantStyles = {
11297
- danger: {
11298
- icon: "text-red-400",
11299
- button: "bg-red-500 hover:bg-red-600 text-white"
11450
+ const cancel = (0, react$1.useCallback)(() => {
11451
+ if (settled.current) return;
11452
+ settled.current = true;
11453
+ if (state.kind === "confirm") state.resolve(false);
11454
+ else if (state.kind === "prompt") state.resolve(null);
11455
+ else state.resolve();
11456
+ onClose();
11457
+ }, [state, onClose]);
11458
+ const accept = (0, react$1.useCallback)(() => {
11459
+ if (settled.current) return;
11460
+ if (state.kind === "confirm") {
11461
+ settled.current = true;
11462
+ state.resolve(true);
11463
+ onClose();
11464
+ return;
11465
+ }
11466
+ if (state.kind === "alert") {
11467
+ settled.current = true;
11468
+ state.resolve();
11469
+ onClose();
11470
+ return;
11471
+ }
11472
+ if (state.options.readOnly !== true) {
11473
+ const problem = state.options.validate?.(value) ?? null;
11474
+ if (problem !== null) {
11475
+ setError(problem);
11476
+ inputRef.current?.focus();
11477
+ return;
11478
+ }
11479
+ }
11480
+ settled.current = true;
11481
+ state.resolve(state.options.readOnly === true ? null : value);
11482
+ onClose();
11483
+ }, [
11484
+ state,
11485
+ onClose,
11486
+ value
11487
+ ]);
11488
+ const onKeyDown = (0, react$1.useCallback)((e) => {
11489
+ if (e.key === "Escape") {
11490
+ e.stopPropagation();
11491
+ e.preventDefault();
11492
+ cancel();
11493
+ return;
11494
+ }
11495
+ if (e.key !== "Enter") return;
11496
+ if (e.target === inputRef.current) return;
11497
+ if (!affirmativeIsDefault(state)) return;
11498
+ e.stopPropagation();
11499
+ e.preventDefault();
11500
+ accept();
11501
+ }, [
11502
+ state,
11503
+ cancel,
11504
+ accept
11505
+ ]);
11506
+ const onSubmit = (0, react$1.useCallback)((e) => {
11507
+ e.preventDefault();
11508
+ e.stopPropagation();
11509
+ accept();
11510
+ }, [accept]);
11511
+ const style = VARIANT_STYLES$2[variantOf(state)];
11512
+ const titleId = "camstack-dialog-title";
11513
+ const body = state.kind === "prompt" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PromptBody, {
11514
+ options: state.options,
11515
+ value,
11516
+ error,
11517
+ inputRef,
11518
+ onChange: (next) => {
11519
+ setValue(next);
11520
+ setError(null);
11521
+ }
11522
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11523
+ className: "mt-2 text-xs text-foreground-subtle leading-relaxed",
11524
+ children: state.options.message
11525
+ });
11526
+ const footer = state.kind === "alert" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11527
+ type: "button",
11528
+ ref: defaultButtonRef,
11529
+ onClick: accept,
11530
+ className: `px-3 py-1.5 text-xs font-medium rounded-md transition-colors ${style.button}`,
11531
+ children: state.options.acknowledgeLabel ?? "OK"
11532
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11533
+ type: "button",
11534
+ ref: affirmativeIsDefault(state) ? null : defaultButtonRef,
11535
+ onClick: cancel,
11536
+ className: CANCEL_CLASS,
11537
+ children: state.options.cancelLabel ?? "Cancel"
11538
+ }), !(state.kind === "prompt" && state.options.readOnly === true) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11539
+ type: state.kind === "prompt" ? "submit" : "button",
11540
+ ref: affirmativeIsDefault(state) ? defaultButtonRef : null,
11541
+ onClick: state.kind === "prompt" ? void 0 : accept,
11542
+ className: `px-3 py-1.5 text-xs font-medium rounded-md transition-colors ${style.button}`,
11543
+ children: state.options.confirmLabel ?? (state.kind === "prompt" ? "OK" : "Confirm")
11544
+ })] });
11545
+ const inner = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11546
+ className: "px-5 pt-5 pb-4",
11547
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
11548
+ id: titleId,
11549
+ className: "text-sm font-semibold text-foreground",
11550
+ children: state.options.title
11551
+ }), body]
11552
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11553
+ className: "flex justify-end gap-2 px-5 py-3 border-t border-border bg-surface-hover/30",
11554
+ children: footer
11555
+ })] });
11556
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dialog", {
11557
+ ref: dialogRef,
11558
+ "aria-modal": "true",
11559
+ "aria-labelledby": titleId,
11560
+ onKeyDown,
11561
+ onCancel: (e) => {
11562
+ e.preventDefault();
11563
+ cancel();
11300
11564
  },
11301
- warning: {
11302
- icon: "text-orange-400",
11303
- button: "bg-orange-500 hover:bg-orange-600 text-white"
11565
+ onClick: (e) => {
11566
+ if (e.target === e.currentTarget) cancel();
11304
11567
  },
11305
- default: {
11306
- icon: "text-primary",
11307
- button: "bg-primary hover:bg-primary/90 text-white"
11308
- }
11309
- };
11310
- const style = dialog ? variantStyles[dialog.variant ?? "default"] : variantStyles.default;
11311
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ConfirmContext.Provider, {
11312
- value: { confirm },
11313
- children: [children, dialog && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11314
- className: "fixed inset-0 z-50 flex items-center justify-center",
11315
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11316
- className: "absolute inset-0 bg-black/50 backdrop-blur-sm",
11317
- onClick: handleCancel
11318
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11319
- className: "relative z-10 w-full max-w-sm mx-4 rounded-xl border border-border bg-surface shadow-2xl overflow-hidden animate-in fade-in zoom-in-95 duration-150",
11320
- onClick: (e) => e.stopPropagation(),
11321
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11322
- className: "px-5 pt-5 pb-4",
11323
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
11324
- className: "text-sm font-semibold text-foreground",
11325
- children: dialog.title
11326
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11327
- className: "mt-2 text-xs text-foreground-subtle leading-relaxed",
11328
- children: dialog.message
11329
- })]
11330
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11331
- className: "flex justify-end gap-2 px-5 py-3 border-t border-border bg-surface-hover/30",
11332
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11333
- type: "button",
11334
- onClick: handleCancel,
11335
- className: "px-3 py-1.5 text-xs font-medium rounded-md border border-border text-foreground-subtle hover:text-foreground hover:bg-surface-hover transition-colors",
11336
- children: dialog.cancelLabel ?? "Cancel"
11337
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11338
- type: "button",
11339
- onClick: handleConfirm,
11340
- className: `px-3 py-1.5 text-xs font-medium rounded-md transition-colors ${style.button}`,
11341
- children: dialog.confirmLabel ?? "Confirm"
11342
- })]
11343
- })]
11344
- })]
11345
- })]
11568
+ className: "fixed left-1/2 top-1/2 z-50 m-0 w-[calc(100vw-2rem)] max-w-sm -translate-x-1/2 -translate-y-1/2 overflow-hidden rounded-xl border border-border bg-surface p-0 text-foreground shadow-2xl backdrop:bg-black/50 backdrop:backdrop-blur-sm [&:not([open])]:hidden",
11569
+ children: state.kind === "prompt" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("form", {
11570
+ onSubmit,
11571
+ children: inner
11572
+ }) : inner
11346
11573
  });
11347
11574
  }
11575
+ function PromptBody({ options, value, error, inputRef, onChange }) {
11576
+ const readOnly = options.readOnly === true;
11577
+ const isPassword = !readOnly && options.inputType === "password";
11578
+ const inputId = "camstack-dialog-input";
11579
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
11580
+ options.message !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11581
+ className: "mt-2 text-xs text-foreground-subtle leading-relaxed",
11582
+ children: options.message
11583
+ }),
11584
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
11585
+ htmlFor: inputId,
11586
+ className: "mt-3 block text-[11px] font-medium text-foreground-subtle",
11587
+ children: options.label ?? options.title
11588
+ }),
11589
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
11590
+ id: inputId,
11591
+ ref: inputRef,
11592
+ type: isPassword ? "password" : "text",
11593
+ value,
11594
+ readOnly,
11595
+ placeholder: options.placeholder,
11596
+ onChange: (e) => onChange(e.target.value),
11597
+ onFocus: readOnly ? (e) => e.currentTarget.select() : void 0,
11598
+ autoComplete: isPassword ? "new-password" : "off",
11599
+ autoCorrect: "off",
11600
+ autoCapitalize: "off",
11601
+ spellCheck: false,
11602
+ className: "mt-1 w-full rounded-md border border-border bg-surface px-2 py-1.5 text-xs text-foreground outline-none focus:border-primary"
11603
+ }),
11604
+ error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11605
+ className: "mt-1.5 text-[11px] text-red-400",
11606
+ children: error
11607
+ })
11608
+ ] });
11609
+ }
11348
11610
  //#endregion
11349
11611
  //#region src/contexts/device-context.tsx
11350
11612
  /**
@@ -27977,13 +28239,18 @@ function StationaryBoxes({ boxes }) {
27977
28239
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: boxes.map((b) => {
27978
28240
  const color = getClassColor(b.className);
27979
28241
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
27980
- className: "absolute border-2 border-dashed rounded-sm opacity-70",
28242
+ "data-census-box": "",
28243
+ className: "absolute",
27981
28244
  style: {
27982
28245
  left: `${String(b.x1 * 100)}%`,
27983
28246
  top: `${String(b.y1 * 100)}%`,
27984
28247
  width: `${String((b.x2 - b.x1) * 100)}%`,
27985
28248
  height: `${String((b.y2 - b.y1) * 100)}%`,
27986
- borderColor: color
28249
+ borderColor: color,
28250
+ borderWidth: _camstack_types.STATIONARY_OVERLAY_STYLE.borderWidth,
28251
+ borderStyle: _camstack_types.STATIONARY_OVERLAY_STYLE.dashed ? "dashed" : "solid",
28252
+ borderRadius: _camstack_types.STATIONARY_OVERLAY_STYLE.borderRadius,
28253
+ opacity: _camstack_types.STATIONARY_OVERLAY_STYLE.opacity
27987
28254
  },
27988
28255
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
27989
28256
  className: "absolute -top-5 left-0 px-1 text-[10px] font-medium rounded-sm whitespace-nowrap",
@@ -27994,19 +28261,12 @@ function StationaryBoxes({ boxes }) {
27994
28261
  children: [
27995
28262
  b.label ?? b.className,
27996
28263
  " · ",
27997
- formatParkedFor(now - b.becameStationaryAt)
28264
+ (0, _camstack_types.formatParkedFor)(now - b.becameStationaryAt)
27998
28265
  ]
27999
28266
  })
28000
28267
  }, b.id);
28001
28268
  }) });
28002
28269
  }
28003
- /** "3 h", "12 min", "45 s" — coarse on purpose: nobody reads a parked car to
28004
- * the second, and a ticking label redraws the overlay for nothing. */
28005
- function formatParkedFor(ms) {
28006
- if (ms < 6e4) return `${String(Math.max(0, Math.round(ms / 1e3)))} s`;
28007
- if (ms < 36e5) return `${String(Math.round(ms / 6e4))} min`;
28008
- return `${String(Math.round(ms / 36e5))} h`;
28009
- }
28010
28270
  function DetectionBoxes({ detections, frameWidth, frameHeight }) {
28011
28271
  if (frameWidth === 0 || frameHeight === 0) return null;
28012
28272
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: detections.map((d) => {
@@ -49767,18 +50027,17 @@ function toStationaryOverlayBox(row) {
49767
50027
  };
49768
50028
  }
49769
50029
  function useStationaryOverlay(trpc, deviceId) {
49770
- const dev = useDeviceProxy(trpc, deviceId);
49771
- return (0, _tanstack_react_query.useQuery)({
50030
+ const query = (0, _tanstack_react_query.useQuery)({
49772
50031
  queryKey: [
49773
50032
  "zone-analytics",
49774
50033
  deviceId,
49775
50034
  "stationaryOverlay"
49776
50035
  ],
49777
- enabled: deviceId !== null && !!dev,
50036
+ enabled: deviceId !== null,
49778
50037
  refetchInterval: STATIONARY_POLL_MS,
49779
50038
  staleTime: STATIONARY_POLL_MS / 2,
49780
50039
  queryFn: async () => {
49781
- const rows = (await dev?.zoneAnalytics?.getCurrentSnapshot({ deviceId }))?.stationaryObjects ?? [];
50040
+ const rows = (await trpc.zoneAnalytics.getCurrentSnapshot.query({ deviceId }))?.stationaryObjects ?? [];
49782
50041
  const out = [];
49783
50042
  for (const r of rows) {
49784
50043
  const box = toStationaryOverlayBox(r);
@@ -49786,7 +50045,8 @@ function useStationaryOverlay(trpc, deviceId) {
49786
50045
  }
49787
50046
  return out;
49788
50047
  }
49789
- }).data;
50048
+ });
50049
+ return query.isError ? void 0 : query.data;
49790
50050
  }
49791
50051
  //#endregion
49792
50052
  //#region src/hooks/turn-server-cache.ts
@@ -50798,6 +51058,7 @@ exports.nextSort = nextSort;
50798
51058
  exports.nextSortDirection = nextSortDirection;
50799
51059
  exports.nextTableSort = nextTableSort;
50800
51060
  exports.normalizeForSearch = normalizeForSearch;
51061
+ exports.openTopLayer = openTopLayer;
50801
51062
  exports.overrideEntityIdFromLink = overrideEntityIdFromLink;
50802
51063
  exports.parseRecordedServerMessage = parseRecordedServerMessage;
50803
51064
  exports.providerIcons = providerIcons;
@@ -50882,6 +51143,7 @@ exports.useAlarmPanelArm = useAlarmPanelArm;
50882
51143
  exports.useAlarmPanelDisarm = useAlarmPanelDisarm;
50883
51144
  exports.useAlarmPanelGetStatus = useAlarmPanelGetStatus;
50884
51145
  exports.useAlarmPanelTrigger = useAlarmPanelTrigger;
51146
+ exports.useAlert = useAlert;
50885
51147
  exports.useAlertsDismiss = useAlertsDismiss;
50886
51148
  exports.useAlertsEmit = useAlertsEmit;
50887
51149
  exports.useAlertsGetUnreadCount = useAlertsGetUnreadCount;
@@ -51390,6 +51652,7 @@ exports.useNotificationRulesCreateRule = useNotificationRulesCreateRule;
51390
51652
  exports.useNotificationRulesCreateSnooze = useNotificationRulesCreateSnooze;
51391
51653
  exports.useNotificationRulesDeleteRule = useNotificationRulesDeleteRule;
51392
51654
  exports.useNotificationRulesGetAlarmConfig = useNotificationRulesGetAlarmConfig;
51655
+ exports.useNotificationRulesGetClipRetentionAsks = useNotificationRulesGetClipRetentionAsks;
51393
51656
  exports.useNotificationRulesGetConditionCatalog = useNotificationRulesGetConditionCatalog;
51394
51657
  exports.useNotificationRulesGetHistory = useNotificationRulesGetHistory;
51395
51658
  exports.useNotificationRulesGetRule = useNotificationRulesGetRule;
@@ -51601,6 +51864,7 @@ exports.usePipelineRunnerGetParkedTrackFrame = usePipelineRunnerGetParkedTrackFr
51601
51864
  exports.usePipelineRunnerParkTrackFrame = usePipelineRunnerParkTrackFrame;
51602
51865
  exports.usePipelineRunnerReleaseParkedTrackFrames = usePipelineRunnerReleaseParkedTrackFrames;
51603
51866
  exports.usePipelineRunnerReportMotion = usePipelineRunnerReportMotion;
51867
+ exports.usePipelineRunnerRequestOccupancyBurst = usePipelineRunnerRequestOccupancyBurst;
51604
51868
  exports.usePipelineRunnerRunDetailSubtree = usePipelineRunnerRunDetailSubtree;
51605
51869
  exports.usePipelineRunnerRunStatelessStep = usePipelineRunnerRunStatelessStep;
51606
51870
  exports.usePlateGalleryAssignPlate = usePlateGalleryAssignPlate;
@@ -51631,6 +51895,7 @@ exports.usePrivacyMaskGetOptions = usePrivacyMaskGetOptions;
51631
51895
  exports.usePrivacyMaskGetStatus = usePrivacyMaskGetStatus;
51632
51896
  exports.usePrivacyMaskSetAudioEnabled = usePrivacyMaskSetAudioEnabled;
51633
51897
  exports.usePrivacyMaskSetMask = usePrivacyMaskSetMask;
51898
+ exports.usePrompt = usePrompt;
51634
51899
  exports.usePtzAutotrackGetSettings = usePtzAutotrackGetSettings;
51635
51900
  exports.usePtzAutotrackGetStatus = usePtzAutotrackGetStatus;
51636
51901
  exports.usePtzAutotrackSetEnabled = usePtzAutotrackSetEnabled;