@camstack/ui-library 0.1.57 → 0.1.58

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.
Files changed (120) hide show
  1. package/dist/composites/battery-badge.d.ts +3 -0
  2. package/dist/composites/camera-stream-player.d.ts +62 -2
  3. package/dist/composites/cap-settings/ConsumablesPanel.d.ts +2 -0
  4. package/dist/composites/cap-settings/CoverageTrack.d.ts +12 -0
  5. package/dist/composites/cap-settings/EventBucketStrip.d.ts +25 -0
  6. package/dist/composites/cap-settings/EventHeatmap.d.ts +10 -0
  7. package/dist/composites/cap-settings/RecordingPanel.d.ts +2 -0
  8. package/dist/composites/cap-settings/RecordingRulesEditor.d.ts +7 -0
  9. package/dist/composites/cap-settings/RecordingSettings.d.ts +8 -0
  10. package/dist/composites/cap-settings/RecordingTimeline.d.ts +5 -0
  11. package/dist/composites/cap-settings/TimelineControls.d.ts +21 -0
  12. package/dist/composites/cap-settings/event-thumb.d.ts +11 -0
  13. package/dist/composites/cap-settings/index.d.ts +4 -0
  14. package/dist/composites/cap-settings/recording-config-form.d.ts +5 -0
  15. package/dist/composites/cap-settings/recording-spans.d.ts +22 -0
  16. package/dist/composites/cap-settings/recording-timeline-model.d.ts +84 -0
  17. package/dist/composites/device-activity-panel.d.ts +5 -1
  18. package/dist/composites/device-controls/alarm-hero-card.d.ts +24 -0
  19. package/dist/composites/device-controls/atoms.d.ts +81 -0
  20. package/dist/composites/device-controls/brightness-panel.d.ts +3 -0
  21. package/dist/composites/device-controls/button-control.d.ts +18 -0
  22. package/dist/composites/device-controls/climate-panel.d.ts +16 -0
  23. package/dist/composites/device-controls/container-primary-hero.d.ts +24 -0
  24. package/dist/composites/device-controls/container-primary.d.ts +46 -0
  25. package/dist/composites/device-controls/control-panel.d.ts +10 -0
  26. package/dist/composites/device-controls/control-registry.d.ts +74 -0
  27. package/dist/composites/device-controls/cover-hero-card.d.ts +27 -0
  28. package/dist/composites/device-controls/cover-inline.d.ts +27 -0
  29. package/dist/composites/device-controls/cover-panel.d.ts +3 -0
  30. package/dist/composites/device-controls/dummy-hero-card.d.ts +6 -0
  31. package/dist/composites/device-controls/fan-hero-card.d.ts +21 -0
  32. package/dist/composites/device-controls/fan-panel.d.ts +3 -0
  33. package/dist/composites/device-controls/humidifier-control.d.ts +37 -0
  34. package/dist/composites/device-controls/image-control.d.ts +24 -0
  35. package/dist/composites/device-controls/index.d.ts +33 -0
  36. package/dist/composites/device-controls/lawn-mower-control.d.ts +24 -0
  37. package/dist/composites/device-controls/light-hero-card.d.ts +16 -0
  38. package/dist/composites/device-controls/lock-hero-card.d.ts +15 -0
  39. package/dist/composites/device-controls/lock-panel.d.ts +3 -0
  40. package/dist/composites/device-controls/media-player-hero-card.d.ts +17 -0
  41. package/dist/composites/device-controls/media-player-panel.d.ts +9 -0
  42. package/dist/composites/device-controls/offline-badge.d.ts +11 -0
  43. package/dist/composites/device-controls/panel-controls.d.ts +17 -0
  44. package/dist/composites/device-controls/popover-row-action.d.ts +9 -0
  45. package/dist/composites/device-controls/primary-child.d.ts +18 -0
  46. package/dist/composites/device-controls/radial-gauge.d.ts +20 -0
  47. package/dist/composites/device-controls/sensor-hero-card.d.ts +10 -0
  48. package/dist/composites/device-controls/sensor-inline-control.d.ts +11 -0
  49. package/dist/composites/device-controls/sensor-value-atom.d.ts +106 -0
  50. package/dist/composites/device-controls/switch-hero-card.d.ts +13 -0
  51. package/dist/composites/device-controls/switch-panel.d.ts +3 -0
  52. package/dist/composites/device-controls/tap-toggle.d.ts +17 -0
  53. package/dist/composites/device-controls/thermostat-hero-card.d.ts +17 -0
  54. package/dist/composites/device-controls/types.d.ts +17 -0
  55. package/dist/composites/device-controls/update-control.d.ts +11 -0
  56. package/dist/composites/device-controls/vacuum-control.d.ts +36 -0
  57. package/dist/composites/device-controls/valve-control.d.ts +46 -0
  58. package/dist/composites/device-controls/water-heater-control.d.ts +41 -0
  59. package/dist/composites/device-controls/weather-control.d.ts +41 -0
  60. package/dist/composites/device-item/actions.d.ts +12 -1
  61. package/dist/composites/device-item/child-layout.d.ts +32 -0
  62. package/dist/composites/device-item/child-section-accordion.d.ts +9 -0
  63. package/dist/composites/device-item/container-children-context.d.ts +15 -0
  64. package/dist/composites/device-item/device-delete-action.d.ts +8 -0
  65. package/dist/composites/device-item/header.d.ts +8 -1
  66. package/dist/composites/device-item/helpers.d.ts +108 -2
  67. package/dist/composites/device-item/index.d.ts +4 -0
  68. package/dist/composites/device-item/preview.d.ts +3 -3
  69. package/dist/composites/device-item/reboot-quick-action.d.ts +9 -0
  70. package/dist/composites/device-item/status-dot.d.ts +4 -1
  71. package/dist/composites/device-list/batch-actions-bar.d.ts +15 -0
  72. package/dist/composites/device-list/batch-toolbar.d.ts +15 -0
  73. package/dist/composites/device-list/cards-layout.d.ts +18 -0
  74. package/dist/composites/device-list/columns.d.ts +68 -0
  75. package/dist/composites/device-list/device-mode.d.ts +115 -0
  76. package/dist/composites/device-list/filter-chips.d.ts +15 -6
  77. package/dist/composites/device-list/fuzzy-match.d.ts +27 -0
  78. package/dist/composites/device-list/generic-mode.d.ts +81 -0
  79. package/dist/composites/device-list/group.d.ts +19 -0
  80. package/dist/composites/device-list/hardware-cell.d.ts +7 -0
  81. package/dist/composites/device-list/hardware.d.ts +26 -0
  82. package/dist/composites/device-list/icon-action.d.ts +17 -0
  83. package/dist/composites/device-list/index.d.ts +23 -39
  84. package/dist/composites/device-list/multi-select.d.ts +22 -0
  85. package/dist/composites/device-list/sort.d.ts +18 -0
  86. package/dist/composites/device-list/sortable-header.d.ts +10 -0
  87. package/dist/composites/device-list/table-layout.d.ts +25 -0
  88. package/dist/composites/device-list/type-filter.d.ts +19 -0
  89. package/dist/composites/device-list/url-state.d.ts +7 -2
  90. package/dist/composites/device-meta.d.ts +38 -0
  91. package/dist/composites/discovery-panel.d.ts +3 -3
  92. package/dist/composites/hls-quality.d.ts +35 -0
  93. package/dist/composites/hls-video.d.ts +26 -0
  94. package/dist/composites/hover-zoom-image.d.ts +18 -0
  95. package/dist/composites/index.d.ts +12 -1
  96. package/dist/composites/log-stream-scroll.d.ts +32 -0
  97. package/dist/composites/log-stream.d.ts +8 -1
  98. package/dist/composites/stream-panel.d.ts +45 -10
  99. package/dist/composites/timezone-selector.d.ts +18 -0
  100. package/dist/composites/widget-panel.d.ts +28 -0
  101. package/dist/contexts/vod-playback.d.ts +17 -0
  102. package/dist/generated/system-hooks.d.ts +330 -56
  103. package/dist/hooks/index.d.ts +6 -0
  104. package/dist/hooks/use-device-cap-slice.d.ts +19 -0
  105. package/dist/hooks/use-device-capability.d.ts +12 -0
  106. package/dist/hooks/use-device-list-page-size.d.ts +14 -0
  107. package/dist/hooks/use-device-webrtc.d.ts +36 -4
  108. package/dist/hooks/use-optimistic-slice.d.ts +11 -0
  109. package/dist/index.cjs +52175 -10927
  110. package/dist/index.cjs.map +1 -1
  111. package/dist/index.d.ts +1 -0
  112. package/dist/index.js +51796 -10813
  113. package/dist/index.js.map +1 -1
  114. package/dist/lib/format-control-datetime.d.ts +18 -0
  115. package/dist/lib/format-last-seen.d.ts +12 -0
  116. package/dist/lib/format-numeric.d.ts +9 -0
  117. package/dist/lib/index.d.ts +3 -0
  118. package/dist/primitives/dialog.d.ts +13 -0
  119. package/dist/primitives/tooltip.d.ts +9 -3
  120. package/package.json +3 -1
@@ -2,6 +2,9 @@ export interface BatteryStatusLike {
2
2
  readonly percentage: number;
3
3
  readonly charging: 'dc' | 'solar' | 'none';
4
4
  readonly sleeping: boolean;
5
+ /** True for a binary LOW_BAT source with no real % — render "Normal"/"Low"
6
+ * instead of a misleading exact percentage. */
7
+ readonly binary?: boolean;
5
8
  }
6
9
  export interface BatteryBadgeProps {
7
10
  readonly status: BatteryStatusLike | null | undefined;
@@ -1,5 +1,35 @@
1
1
  import { ReactNode } from 'react';
2
+ import { CamProfile } from '@camstack/types';
3
+ type WebkitPresentationMode = 'inline' | 'picture-in-picture' | 'fullscreen';
4
+ declare global {
5
+ interface HTMLVideoElement {
6
+ webkitSetPresentationMode?: (mode: WebkitPresentationMode) => void;
7
+ readonly webkitPresentationMode?: WebkitPresentationMode;
8
+ }
9
+ }
2
10
  export type PlayerConnectionState = 'connecting' | 'playing' | 'error' | 'disconnected' | 'idle';
11
+ /**
12
+ * Discriminated WebRTC target — same shape as `WebrtcStreamTarget` on the
13
+ * backend cap; the `profile` tier uses the canonical `CamProfile` enum.
14
+ * Carried by `getSessionState`'s `pendingRenegotiation` and handed back to
15
+ * `reoffer` verbatim during an adaptive re-offer.
16
+ */
17
+ export type PlayerWebrtcTarget = {
18
+ readonly kind: 'adaptive';
19
+ } | {
20
+ readonly kind: 'profile';
21
+ readonly profile: CamProfile;
22
+ } | {
23
+ readonly kind: 'cam-stream';
24
+ readonly camStreamId: string;
25
+ };
26
+ /** Live signaling state polled mid-stream — the adaptive re-offer signal. */
27
+ export interface SessionSignalingState {
28
+ readonly pendingRenegotiation: {
29
+ readonly target: PlayerWebrtcTarget;
30
+ readonly epoch: number;
31
+ } | null;
32
+ }
3
33
  /**
4
34
  * Signaling function for WebRTC connection.
5
35
  * Called by the player to establish the WebRTC session.
@@ -101,7 +131,7 @@ export interface CameraStreamPlayerProps {
101
131
  * Main/High (incl. 4K) source pass through with NO transcode — the only
102
132
  * path that lets iOS decode High. The caller closes over the target.
103
133
  */
104
- handleOffer?: (sdpOffer: string) => Promise<ClientOfferResult>;
134
+ handleOffer?: (sdpOffer: string, sessionId?: string) => Promise<ClientOfferResult>;
105
135
  /**
106
136
  * Fetch the browser-side ICE servers (STUN + TURN) for client-offer mode.
107
137
  * Called BEFORE the offer is built so a remote/CGNAT viewer can gather a
@@ -117,6 +147,35 @@ export interface CameraStreamPlayerProps {
117
147
  }>;
118
148
  /** Explicitly close the WebRTC session on server (cleanup on unmount). */
119
149
  closeSession?: (sessionId: string) => Promise<void>;
150
+ /**
151
+ * Poll the live signaling state of a session (client-offer mode only).
152
+ * After the server swaps the broker source to a different tier (adaptive
153
+ * downgrade), this returns a non-null `pendingRenegotiation` with the new
154
+ * target + a monotonic `epoch`. The player then transparently re-offers
155
+ * on the SAME sessionId — building a fresh PeerConnection in the
156
+ * background and only swapping the live `<video>` to the new stream once
157
+ * it delivers media, so the last decoded frame stays on screen with no
158
+ * loader. Best-effort: omit it to disable adaptive re-offer entirely.
159
+ */
160
+ getSessionState?: (sessionId: string) => Promise<SessionSignalingState>;
161
+ /**
162
+ * Re-offer signaling for an adaptive tier switch (client-offer mode).
163
+ * Posts a fresh browser-built offer for an EXISTING `sessionId` against
164
+ * the new `target` (from `getSessionState`'s `pendingRenegotiation`). The
165
+ * server closes the stale session and creates a fresh one under the same
166
+ * id, bound to the new tier's source. Distinct from `handleOffer`, whose
167
+ * target is fixed at the player's selected stream — a re-offer must carry
168
+ * the server-chosen downgrade target instead. Required (alongside
169
+ * `getSessionState`) for adaptive re-offer to run.
170
+ */
171
+ reoffer?: (sessionId: string, sdpOffer: string, target: PlayerWebrtcTarget) => Promise<ClientOfferResult>;
172
+ /**
173
+ * Optional poster image (snapshot data-URL) shown by the <video> before
174
+ * the FIRST-EVER frame is decoded. Only a cold-start fallback — once a
175
+ * frame is showing, the player keeps the last decoded frame across
176
+ * renegotiations and never falls back to the poster. Caller-supplied.
177
+ */
178
+ posterUrl?: string;
120
179
  /**
121
180
  * Override client hints sent to the server at session creation.
122
181
  * Values provided here replace the auto-computed browser values.
@@ -124,4 +183,5 @@ export interface CameraStreamPlayerProps {
124
183
  */
125
184
  hintsOverride?: Partial<ClientStreamHints>;
126
185
  }
127
- export declare function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay, muted: initialMuted, showControls, className, onStateChange, onError, overlay, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, hintsOverride, }: CameraStreamPlayerProps): import("react/jsx-runtime").JSX.Element;
186
+ export declare function CameraStreamPlayer({ serverUrl, streamKey, label, autoPlay, muted: initialMuted, showControls, className, onStateChange, onError, overlay, createSession, sendAnswer, handleOffer, getIceServers, addIceCandidate, getIceCandidates, closeSession, getSessionState, reoffer, posterUrl, hintsOverride, }: CameraStreamPlayerProps): import("react/jsx-runtime").JSX.Element;
187
+ export {};
@@ -0,0 +1,2 @@
1
+ import { CapSettingsComponentProps } from './index';
2
+ export declare function ConsumablesPanel({ deviceId }: CapSettingsComponentProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { PlaybackSpan } from './recording-spans';
2
+ interface CoverageTrackProps {
3
+ readonly windowFrom: number;
4
+ readonly windowTo: number;
5
+ readonly spans: readonly PlaybackSpan[];
6
+ /** Last-seeked position to mark with the playhead, or null. */
7
+ readonly playheadMs: number | null;
8
+ /** Called with the absolute ms a click maps to within the window. */
9
+ readonly onSeek: (clickMs: number) => void;
10
+ }
11
+ export declare function CoverageTrack({ windowFrom, windowTo, spans, playheadMs, onSeek, }: CoverageTrackProps): import("react/jsx-runtime").JSX.Element;
12
+ export {};
@@ -0,0 +1,25 @@
1
+ import { ClipKind } from './event-thumb';
2
+ export interface BucketClip {
3
+ readonly id: string;
4
+ readonly timestamp: number;
5
+ readonly kind: ClipKind;
6
+ readonly label: string;
7
+ readonly mediaUrl?: string;
8
+ }
9
+ interface EventBucketStripProps {
10
+ readonly clips: readonly BucketClip[];
11
+ readonly loading: boolean;
12
+ /** Key of the clip whose manifest is resolving, for the spinner. */
13
+ readonly busyKey: string | null;
14
+ readonly onPlay: (clip: BucketClip) => void;
15
+ /** Which kinds are currently enabled for display. */
16
+ readonly enabledKinds: ReadonlySet<ClipKind>;
17
+ /** Called when the user toggles a kind checkbox. */
18
+ readonly onToggleKind: (kind: ClipKind) => void;
19
+ /** True when more results may exist beyond the current page. */
20
+ readonly hasMore: boolean;
21
+ /** Called when the user clicks "Load more". */
22
+ readonly onLoadMore: () => void;
23
+ }
24
+ export declare function EventBucketStrip({ clips, loading, busyKey, onPlay, enabledKinds, onToggleKind, hasMore, onLoadMore }: EventBucketStripProps): import("react/jsx-runtime").JSX.Element;
25
+ export {};
@@ -0,0 +1,10 @@
1
+ import { DensityBucket } from './recording-timeline-model';
2
+ interface EventHeatmapProps {
3
+ readonly windowFrom: number;
4
+ readonly windowTo: number;
5
+ readonly buckets: readonly DensityBucket[];
6
+ readonly selectedIndex: number | null;
7
+ readonly onSelectBucket: (index: number) => void;
8
+ }
9
+ export declare function EventHeatmap({ windowFrom, windowTo, buckets, selectedIndex, onSelectBucket }: EventHeatmapProps): import("react/jsx-runtime").JSX.Element;
10
+ export {};
@@ -0,0 +1,2 @@
1
+ import { HostWidgetProps } from '../../widgets/host-widgets';
2
+ export declare function RecordingPanel({ deviceId }: HostWidgetProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { RecordingRule } from '@camstack/types';
2
+ interface RecordingRulesEditorProps {
3
+ readonly rules: readonly RecordingRule[];
4
+ readonly onChange: (rules: RecordingRule[]) => void;
5
+ }
6
+ export declare function RecordingRulesEditor({ rules, onChange }: RecordingRulesEditorProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -0,0 +1,8 @@
1
+ import { RecordingConfig } from '@camstack/types';
2
+ interface RecordingSettingsProps {
3
+ readonly initial: RecordingConfig;
4
+ readonly saving: boolean;
5
+ readonly onSave: (config: RecordingConfig) => void;
6
+ }
7
+ export declare function RecordingSettings({ initial, saving, onSave }: RecordingSettingsProps): import("react/jsx-runtime").JSX.Element;
8
+ export {};
@@ -0,0 +1,5 @@
1
+ interface RecordingTimelineProps {
2
+ readonly deviceId: number;
3
+ }
4
+ export declare function RecordingTimeline({ deviceId }: RecordingTimelineProps): import("react/jsx-runtime").JSX.Element;
5
+ export {};
@@ -0,0 +1,21 @@
1
+ import { DayWindow } from './recording-timeline-model';
2
+ interface TimelineControlsProps {
3
+ /** Currently selected calendar day. */
4
+ readonly day: Date;
5
+ /** Called when the user picks a new day. */
6
+ readonly onDayChange: (d: Date) => void;
7
+ /** Current zoom+pan view window. */
8
+ readonly view: DayWindow;
9
+ /** Full day bounds (used for zoom/pan clamping). */
10
+ readonly dayBounds: DayWindow;
11
+ /** Called when zoom changes the view. */
12
+ readonly onViewChange: (w: DayWindow) => void;
13
+ }
14
+ /**
15
+ * Date selector + zoom-in / zoom-out buttons for the recording timeline.
16
+ * Zoom cycles through ZOOM_LEVELS (widest → narrowest), centred on the
17
+ * current view midpoint. Horizontal pan is wired via wheel events in
18
+ * RecordingTimeline directly.
19
+ */
20
+ export declare function TimelineControls({ day, onDayChange, view, dayBounds, onViewChange, }: TimelineControlsProps): import("react/jsx-runtime").JSX.Element;
21
+ export {};
@@ -0,0 +1,11 @@
1
+ export type ClipKind = 'motion' | 'object' | 'audio';
2
+ interface EventThumbProps {
3
+ readonly mediaUrl?: string;
4
+ readonly kind: ClipKind;
5
+ }
6
+ export declare function EventThumb({ mediaUrl, kind }: EventThumbProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function EventKindIcon({ kind, large }: {
8
+ readonly kind: ClipKind;
9
+ readonly large?: boolean;
10
+ }): import("react/jsx-runtime").JSX.Element;
11
+ export {};
@@ -1,7 +1,11 @@
1
1
  export { PtzPanel } from './PtzPanel';
2
+ export { ConsumablesPanel } from './ConsumablesPanel';
2
3
  export { AutotrackSection } from './AutotrackSection';
3
4
  export { MotionZonesSettings } from './MotionZonesSettings';
4
5
  export { PrivacyMaskSettings } from './PrivacyMaskSettings';
6
+ export { RecordingPanel } from './RecordingPanel';
7
+ export { RecordingSettings } from './RecordingSettings';
8
+ export { RecordingRulesEditor } from './RecordingRulesEditor';
5
9
  export { MaskShapeCanvas } from './MaskShapeCanvas';
6
10
  export type { MaskShapeItem, MaskShapeCanvasProps } from './MaskShapeCanvas';
7
11
  /**
@@ -0,0 +1,5 @@
1
+ import { RecordingRule, RecordingMode } from '@camstack/types';
2
+ export declare function emptyRule(): RecordingRule;
3
+ export declare function isTriggerMode(mode: RecordingMode): boolean;
4
+ export declare function withRuleAt(rules: readonly RecordingRule[], index: number, patch: Partial<RecordingRule>): RecordingRule[];
5
+ export declare function removeRuleAt(rules: readonly RecordingRule[], index: number): RecordingRule[];
@@ -0,0 +1,22 @@
1
+ export interface AvailabilityRange {
2
+ readonly profile: string;
3
+ readonly startMs: number;
4
+ readonly endMs: number;
5
+ }
6
+ export interface PlaybackSpan {
7
+ readonly startMs: number;
8
+ readonly endMs: number;
9
+ }
10
+ /**
11
+ * Merge availability ranges (across every profile) into the union of distinct
12
+ * time windows, ascending by start. Overlapping or touching intervals coalesce;
13
+ * zero/negative-length inputs are dropped. Newest-first ordering is the caller's
14
+ * concern (it reverses for display).
15
+ */
16
+ export declare function mergeAvailabilitySpans(ranges: readonly AvailabilityRange[]): PlaybackSpan[];
17
+ /**
18
+ * Pick the right unit for a byte count and render it with a sensible number of
19
+ * significant digits (binary prefixes, matching recording sizes elsewhere).
20
+ * KB and up render one decimal under 100, none above; bytes never get a decimal.
21
+ */
22
+ export declare function formatBytes(bytes: number): string;
@@ -0,0 +1,84 @@
1
+ import { PlaybackSpan } from './recording-spans';
2
+ /** Fixed heatmap bucket size: 15 minutes → 96 buckets over 24h. */
3
+ export declare const BUCKET_MS: number;
4
+ /** Event kinds the timeline understands. */
5
+ export type TimelineKind = 'motion' | 'object' | 'audio';
6
+ export interface DensityBucket {
7
+ readonly index: number;
8
+ readonly startMs: number;
9
+ readonly endMs: number;
10
+ readonly counts: Readonly<Record<TimelineKind, number>>;
11
+ readonly total: number;
12
+ /** Highest-count kind, or null when the bucket is empty. */
13
+ readonly dominant: TimelineKind | null;
14
+ }
15
+ /** Row returned by the server `getEventDensity` call (one per non-empty bucket). */
16
+ export interface EventDensityRow {
17
+ readonly bucketStart: number;
18
+ readonly motion: number;
19
+ readonly object: number;
20
+ readonly audio: number;
21
+ }
22
+ /**
23
+ * Convert server-side density rows into `DensityBucket[]` for the timeline
24
+ * heatmap. Rows with total === 0 are skipped (defensive; server already omits
25
+ * them). Returns a sparse, ascending-by-index array — EventHeatmap positions
26
+ * each bar by `b.startMs` rather than array index, so gaps are fine.
27
+ */
28
+ export declare function densityRowsToBuckets(rows: readonly EventDensityRow[], windowFrom: number, bucketMs: number): DensityBucket[];
29
+ export interface SeekTarget {
30
+ readonly fromMs: number;
31
+ readonly toMs: number;
32
+ }
33
+ /**
34
+ * Resolve a playback window for a click at `clickMs` against recorded `spans`.
35
+ * Inside a span → play from the click to that span's end. In a gap / outside →
36
+ * snap to the WHOLE nearest span. No spans → null (nothing to play).
37
+ * `spans` are expected ascending by start (as `mergeAvailabilitySpans` returns).
38
+ */
39
+ export declare function seekTargetForSpans(clickMs: number, spans: readonly PlaybackSpan[]): SeekTarget | null;
40
+ /**
41
+ * Position `ms` inside the window `[fromMs, toMs]` as a clamped percentage
42
+ * (0–100). A zero/negative-width window yields 0. Callers use this for `left:`.
43
+ */
44
+ export declare function pctForMs(ms: number, fromMs: number, toMs: number): number;
45
+ /** A [fromMs, toMs) window spanning (at most) one calendar day. */
46
+ export interface DayWindow {
47
+ readonly fromMs: number;
48
+ readonly toMs: number;
49
+ }
50
+ /**
51
+ * Build a DayWindow for the given calendar `date` using local time.
52
+ * For any day strictly in the past: `fromMs` = local midnight, `toMs` =
53
+ * next local midnight (exactly 24 h).
54
+ * For today (or any future day that contains `nowMs`): `toMs = nowMs` so the
55
+ * window never extends into the future.
56
+ */
57
+ export declare function dayWindow(date: Date, nowMs: number): DayWindow;
58
+ /** Available zoom levels in ms, from widest (24h) to narrowest (15 min). */
59
+ export declare const ZOOM_LEVELS: readonly number[];
60
+ /**
61
+ * Produce a `levelMs`-wide DayWindow centred on `centerMs`, clamped within
62
+ * `dayBounds`. If `levelMs >= dayBounds span`, returns `dayBounds` unchanged.
63
+ */
64
+ export declare function zoomWindow(_current: DayWindow, dayBounds: DayWindow, levelMs: number, centerMs: number): DayWindow;
65
+ /**
66
+ * Shift `win` by `deltaMs`, preserving width, clamped within `dayBounds`.
67
+ */
68
+ export declare function panWindow(win: DayWindow, dayBounds: DayWindow, deltaMs: number): DayWindow;
69
+ /**
70
+ * Choose a sensible heatmap bucket size for a visible span of `spanMs`,
71
+ * targeting ~96-120 buckets. Always picks from `BUCKET_CANDIDATES`.
72
+ */
73
+ export declare function bucketMsForSpan(spanMs: number): number;
74
+ /** One labelled tick on the time axis. */
75
+ export interface AxisTick {
76
+ readonly pct: number;
77
+ readonly label: string;
78
+ }
79
+ /**
80
+ * Produce `count` evenly-spaced axis ticks spanning [fromMs, toMs].
81
+ * - `pct` is via pctForMs (first=0, last=100).
82
+ * - `label` is a localised HH:MM clock string.
83
+ */
84
+ export declare function axisTicks(fromMs: number, toMs: number, count: number): AxisTick[];
@@ -1,5 +1,9 @@
1
1
  export interface DeviceActivityPanelProps {
2
2
  readonly deviceId: number;
3
+ /** When this device is a Container, its own id — the Logs tab then shows the
4
+ * whole subtree (container + every child) by filtering `containerDeviceId`
5
+ * instead of `deviceId`. Omit/undefined for a non-container. */
6
+ readonly containerDeviceId?: number;
3
7
  /** Initial event categories the user wants tracked. Defaults to "all". */
4
8
  readonly defaultEventCategories?: readonly string[];
5
9
  /** Initial state-cap whitelist. Empty = show every cap that fires. */
@@ -25,5 +29,5 @@ declare const TABS: readonly [{
25
29
  readonly icon: import('react').ForwardRefExoticComponent<Omit<import('lucide-react').LucideProps, "ref"> & import('react').RefAttributes<SVGSVGElement>>;
26
30
  }];
27
31
  export type ActivityTabId = (typeof TABS)[number]['id'];
28
- export declare function DeviceActivityPanel({ deviceId, defaultEventCategories, defaultStateCaps, initialTab, className, maxHeight, }: DeviceActivityPanelProps): import("react/jsx-runtime").JSX.Element;
32
+ export declare function DeviceActivityPanel({ deviceId, containerDeviceId, defaultEventCategories, defaultStateCaps, initialTab, className, maxHeight, }: DeviceActivityPanelProps): import("react/jsx-runtime").JSX.Element;
29
33
  export {};
@@ -0,0 +1,24 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceControlProps } from './control-registry';
3
+ /**
4
+ * Row-sized alarm control: the shared Disarm + arm-mode segment strip only —
5
+ * no glyph, no state label, no card padding. Cap-gated on `alarm-panel`.
6
+ * Drops into a 200 px control column without towering over sibling rows.
7
+ */
8
+ export declare function AlarmInlineControl({ trpc, deviceId, optimistic }: DeviceControlProps): ReactNode;
9
+ /**
10
+ * AlarmHeroCard — segmented arm/disarm hero for AlarmPanel devices.
11
+ *
12
+ * Renders (Spec §3):
13
+ * - A shield glyph + state label tinted by lifecycle state (disarmed=muted,
14
+ * armed=green, arming/pending=amber, triggered=red).
15
+ * - The shared `AlarmSegments` strip: a Disarm button plus one arm button per
16
+ * `availableModes` entry (Home / Away / Night / …). The currently-active
17
+ * segment is highlighted; transitioning states highlight none.
18
+ *
19
+ * Cap-gated on `alarm-panel` — returns null when the device lacks the cap.
20
+ * PIN entry (when `requiresCode`) is deferred to a later task; the segments
21
+ * call arm/disarm without a code, and the provider rejects when a code is
22
+ * required — surfaced upstream.
23
+ */
24
+ export declare function AlarmHeroCard({ trpc, deviceId, optimistic }: DeviceControlProps): ReactNode;
@@ -0,0 +1,81 @@
1
+ import { ComponentPropsWithoutRef, ReactNode } from 'react';
2
+ export declare const FILL: "fill";
3
+ export declare const RIGHT: "right";
4
+ export declare const CENTER: "center";
5
+ export declare const FIT: "fit";
6
+ export type ControlAlign = typeof FILL | typeof RIGHT | typeof CENTER | typeof FIT;
7
+ interface ControlColumnProps extends ComponentPropsWithoutRef<'div'> {
8
+ readonly align: ControlAlign;
9
+ readonly children: ReactNode;
10
+ }
11
+ /** The one control column every device-row/hero control sits in.
12
+ * FILL → the child stretches to the full fixed column width (steppers, selects, buttons, cover triad).
13
+ * RIGHT → the child keeps its natural width, flush to the right edge of the fixed column (readouts, toggles — card view).
14
+ * CENTER → the child keeps its natural width, centered in the fixed column (readouts, toggles).
15
+ * FIT → the column DROPS its fixed width and sizes to its content (table view). The control takes
16
+ * its natural width and lays out in sequence with the trailing trash + status dot, so a wide
17
+ * control (climate stepper + mode select, cover triad + %) never overflows a fixed box and
18
+ * collides with the neighbours. FILL has no effect under FIT — a stretchy child in a
19
+ * content-width wrapper simply takes its natural width.
20
+ * FILL, RIGHT and CENTER share the fixed `w-[140px]` box (card / minimal / hero views). */
21
+ export declare function ControlColumn({ align, children, className, ...rest }: ControlColumnProps): ReactNode;
22
+ interface ValueReadoutProps {
23
+ readonly value: string;
24
+ readonly unit?: string;
25
+ }
26
+ /** A value + inline unit suffix (no separator). E.g. `60s`, `22%`. */
27
+ export declare function ValueReadout({ value, unit }: ValueReadoutProps): ReactNode;
28
+ interface StepperProps {
29
+ readonly value: number | string;
30
+ readonly unit?: string;
31
+ readonly onDelta: (d: 1 | -1) => void;
32
+ }
33
+ /** Increment/decrement control with value + unit display. */
34
+ export declare function Stepper({ value, unit, onDelta }: StepperProps): ReactNode;
35
+ interface SlideToggleProps {
36
+ readonly on: boolean;
37
+ readonly targetLabel: string;
38
+ readonly onConfirm: () => void;
39
+ }
40
+ /** Safety slide: onConfirm fires only after the thumb is dragged past threshold, never on a plain tap. */
41
+ export declare function SlideToggle({ on, targetLabel, onConfirm }: SlideToggleProps): ReactNode;
42
+ interface ArcKnobProps {
43
+ readonly fraction: number;
44
+ readonly color: string;
45
+ /**
46
+ * Distance in px from the rotating container's top edge to the knob's top
47
+ * edge. The knob is 20px tall, so its CENTER sits at `knobTopPx + 10` px
48
+ * from the top. For the knob center to ride the ring's centre radius the
49
+ * caller passes `(containerCenter − ringRadius) − 10`. Defaults to the
50
+ * thermostat gauge value (176px container, radius 76 → 88 − 76 − 10 = 2).
51
+ */
52
+ readonly knobTopPx?: number;
53
+ /**
54
+ * When provided, the knob becomes interactive: dragging it reports the new
55
+ * arc fraction (0..1) to the parent, which owns the value and re-renders the
56
+ * knob at the new `fraction`. When omitted the knob is a display-only
57
+ * indicator (no grab cursor, no pointer handlers).
58
+ */
59
+ readonly onDragFraction?: (fraction: number) => void;
60
+ }
61
+ /** Half the ArcKnob's 20px (h-5) height. Callers seat the knob centre on a
62
+ * ring by computing `(containerCentre − ringRadius) − KNOB_HALF_PX`. */
63
+ export declare const KNOB_HALF_PX = 10;
64
+ /**
65
+ * White knob riding a circular arc at `fraction` (0..1). For thermostat/light/fan.
66
+ *
67
+ * Display-only by default. Pass `onDragFraction` to make the knob draggable:
68
+ * pointer capture is taken on the knob, and pointer moves are mapped to a new
69
+ * arc fraction reported back to the parent (the parent owns the value).
70
+ */
71
+ export declare function ArcKnob({ fraction, color, knobTopPx, onDragFraction }: ArcKnobProps): ReactNode;
72
+ interface GripTrackProps {
73
+ readonly fraction: number;
74
+ readonly color?: string;
75
+ }
76
+ /**
77
+ * Horizontal track with a white grip thumb at `fraction` (0..1). For seek/volume/brightness.
78
+ * @remarks display-only indicator; interactive drag is deferred to a later task.
79
+ */
80
+ export declare function GripTrack({ fraction, color }: GripTrackProps): ReactNode;
81
+ export {};
@@ -0,0 +1,3 @@
1
+ import { ReactNode } from 'react';
2
+ import { ControlPanelProps } from './types';
3
+ export declare function BrightnessPanel({ trpc, deviceId, layout, optimistic }: ControlPanelProps): ReactNode;
@@ -0,0 +1,18 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceControlProps } from './control-registry';
3
+ /**
4
+ * ButtonControl (inline) — compact "Press" trigger for a device-list row.
5
+ *
6
+ * The `button` cap is fire-only (no status / runtimeState), so we bypass
7
+ * `useDeviceCapability` (which requires a slice) and resolve the cap
8
+ * handle directly from the device proxy. We guard on `cap === undefined`
9
+ * (device has no button cap) and render nothing in that case.
10
+ */
11
+ export declare function ButtonControl({ trpc, deviceId }: DeviceControlProps): ReactNode;
12
+ /**
13
+ * ButtonHeroCard — large press-actuator card for the device-detail hero area.
14
+ *
15
+ * Renders a centred "Press" button with real design-token classes. The cap is
16
+ * fire-only so no state display is needed — the affordance IS the control.
17
+ */
18
+ export declare function ButtonHeroCard({ trpc, deviceId }: DeviceControlProps): ReactNode;
@@ -0,0 +1,16 @@
1
+ import { ReactNode } from 'react';
2
+ import { ControlPanelProps } from './types.js';
3
+ import { HvacMode } from '@camstack/types';
4
+ /**
5
+ * Exhaustive colour map — one entry per HvacMode. Build will fail if a
6
+ * mode is added to the enum and not reflected here (Record<HvacMode, ...>
7
+ * + `satisfies` means TypeScript enforces full coverage).
8
+ */
9
+ export declare const MODE_COLOR: Record<HvacMode, string>;
10
+ /**
11
+ * Mode text-accent colour — applied to the compact (inline) temperature value
12
+ * as a subtle cue, replacing the old bordered/filled mode box so the control
13
+ * matches the borderless cover/lock inline visual language.
14
+ */
15
+ export declare const MODE_TEXT_COLOR: Record<HvacMode, string>;
16
+ export declare function ClimatePanel({ trpc, deviceId, features: _features, layout, optimistic }: ControlPanelProps): ReactNode;
@@ -0,0 +1,24 @@
1
+ import { ReactNode } from 'react';
2
+ import { UseDeviceProxyTrpc } from '../../hooks/use-device-proxy';
3
+ import { ContainerChild } from './container-primary';
4
+ /** A child row the hero can render + offer in the picker. */
5
+ export interface ContainerHeroChild extends ContainerChild {
6
+ readonly name: string;
7
+ readonly features?: readonly string[];
8
+ }
9
+ export interface ContainerPrimaryHeroProps {
10
+ readonly trpc: UseDeviceProxyTrpc;
11
+ /** The container's entity-children (from `deviceManager.getChildren`). */
12
+ readonly children: readonly ContainerHeroChild[];
13
+ /** Container's LEGACY persisted primary-child override (numeric child id) —
14
+ * consulted only when no durable `primaryChildEntityId` is set. */
15
+ readonly linkDeviceId: number | null | undefined;
16
+ /** Container's DURABLE primary-child override (chosen child's entityId) —
17
+ * null/undefined ⇒ no durable override (falls back to linkDeviceId, then
18
+ * priority default). */
19
+ readonly primaryChildEntityId?: string | null | undefined;
20
+ /** Persist the chosen primary child. Called with the picked child's
21
+ * re-sync/rename-stable `entityId`. */
22
+ readonly onSetPrimary: (childEntityId: string) => void;
23
+ }
24
+ export declare function ContainerPrimaryHero({ trpc, children, linkDeviceId, primaryChildEntityId, onSetPrimary, }: ContainerPrimaryHeroProps): ReactNode;
@@ -0,0 +1,46 @@
1
+ import { ChildRef } from './primary-child';
2
+ /**
3
+ * Minimal shape this module needs from a child device row. Structurally
4
+ * compatible with `DeviceItemDevice` / `DeviceInfo` — callers pass the raw
5
+ * `getChildren` rows through.
6
+ */
7
+ export interface ContainerChild {
8
+ readonly id: number;
9
+ readonly stableId: string;
10
+ readonly type: string;
11
+ readonly sourceInfo?: {
12
+ readonly id: string;
13
+ } | undefined;
14
+ }
15
+ /**
16
+ * Re-sync-stable identity of a child. Prefers the upstream-system dispatch key
17
+ * (`sourceInfo.id` — e.g. HA `entity_id`); falls back to `stableId` for
18
+ * providers that never populated SourceInfo. Both survive a re-sync that may
19
+ * reallocate the numeric `id`.
20
+ */
21
+ export declare function childEntityId(child: ContainerChild): string;
22
+ /** Project a raw child row onto the pure resolver's {@link ChildRef} shape. */
23
+ export declare function containerChildToRef(child: ContainerChild): ChildRef;
24
+ /**
25
+ * Resolve the entityId override from the container's persisted `linkDeviceId`.
26
+ * Returns the linked child's stable entityId, or `undefined` when there is no
27
+ * link or the link is stale (points to a numeric id no longer present) — in
28
+ * which case the resolver falls back to the priority default.
29
+ */
30
+ export declare function overrideEntityIdFromLink(children: readonly ContainerChild[], linkDeviceId: number | null | undefined): string | undefined;
31
+ /**
32
+ * Full container → primary-child resolution: project children to refs, resolve
33
+ * the override entityId, and delegate to the pure resolver. Returns `null` for a
34
+ * container with no children (caller degrades gracefully).
35
+ *
36
+ * Override precedence:
37
+ * 1. `primaryChildEntityId` — the DURABLE override (re-sync/rename-stable),
38
+ * passed straight to the resolver which matches on `entityId`. Wins when a
39
+ * non-empty string is supplied.
40
+ * 2. `linkDeviceId` — LEGACY numeric override, re-derived to an entityId from
41
+ * the live child set. Consulted only when there is no durable override.
42
+ *
43
+ * When neither resolves to a live child, the resolver falls back to the
44
+ * priority default.
45
+ */
46
+ export declare function resolveContainerPrimary(children: readonly ContainerChild[], linkDeviceId: number | null | undefined, primaryChildEntityId?: string | null | undefined): ChildRef | null;
@@ -0,0 +1,10 @@
1
+ import { ReactNode } from 'react';
2
+ import { ControlPanelProps } from './types.js';
3
+ /** Trim an HA date value to the `<input type=date>` shape ('YYYY-MM-DD'). */
4
+ export declare function toDateInput(value: string): string;
5
+ /** Trim an HA time value ('HH:MM:SS') to the `<input type=time>` shape ('HH:MM'). */
6
+ export declare function toTimeInput(value: string): string;
7
+ /** Trim an HA datetime value to the `<input type=datetime-local>` shape
8
+ * ('YYYY-MM-DDTHH:MM'). Accepts both 'T'- and space-separated forms. */
9
+ export declare function toDateTimeInput(value: string): string;
10
+ export declare function ControlPanel({ trpc, deviceId, features: _features, layout, optimistic }: ControlPanelProps): ReactNode;