@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
@@ -0,0 +1,74 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceType } from '@camstack/types';
3
+ import { UseDeviceProxyTrpc } from '../../hooks/use-device-proxy';
4
+ import { ControlAlign } from './atoms';
5
+ export { coverHighlight } from './cover-inline';
6
+ export interface DeviceControlProps {
7
+ readonly trpc: UseDeviceProxyTrpc;
8
+ readonly deviceId: number;
9
+ readonly features: readonly string[] | undefined;
10
+ /** Enable optimistic UI mode (default: true). Set to false to disable. */
11
+ readonly optimistic?: boolean;
12
+ }
13
+ export interface DeviceTypeControl {
14
+ /** Compact, state-aware controls rendered directly in a device-list row. */
15
+ readonly InlineControl: (p: DeviceControlProps) => ReactNode;
16
+ /** Full control surface rendered as a card in the device-detail hero area. */
17
+ readonly HeroCard: (p: DeviceControlProps) => ReactNode;
18
+ }
19
+ /**
20
+ * Exhaustive record over DeviceType. Omitting a member causes a
21
+ * TypeScript build error (same pattern as DEVICE_TYPE_ACTIONS).
22
+ */
23
+ export declare const DEVICE_TYPE_CONTROL: Record<DeviceType, DeviceTypeControl>;
24
+ /**
25
+ * Per-type FILL flag.
26
+ *
27
+ * true — range / choice / button controls (cover ▲■▼ triad, thermostat
28
+ * setpoint stepper, media/control/button action buttons, alarm
29
+ * segmented control). The child STRETCHES to the full column width.
30
+ * false — readouts, tap-toggles, icon controls (lock), and dummy/placeholder
31
+ * types. The child keeps its NATURAL width; where it sits in the
32
+ * column (flush right vs centered) is decided by the caller via a
33
+ * `ControlPlacement` — see `resolveControlAlign`.
34
+ *
35
+ * Dummy types (Camera, Hub, Generic, Weather, Vacuum, Container, and placeholder
36
+ * types Notifier/Script/Automation whose InlineControl renders "—") are `false` —
37
+ * the dash placeholder must not be stretched to the full column width. (AlarmPanel
38
+ * has a real segmented inline control and is `true`.) When a later task gives a
39
+ * remaining placeholder type a real stretchy inline control, flip its flag to true.
40
+ *
41
+ * Exhaustive over DeviceType — a missing member is a TypeScript build error.
42
+ */
43
+ export declare const CONTROL_FILLS: Record<DeviceType, boolean>;
44
+ /**
45
+ * Where a control sits, and whether the column carries the fixed `w-[140px]` box.
46
+ * 'right' — natural-width content flush to the fixed column's right edge (card / minimal views).
47
+ * 'center' — natural-width content centered in the fixed column.
48
+ * 'table' — the column DROPS its fixed width and sizes to its content (table view). Every type
49
+ * resolves to FIT so the control takes its natural width and lays out in sequence with
50
+ * the trailing trash + status dot, never overflowing a fixed box and colliding.
51
+ * FILL controls ignore 'right'/'center' (they always stretch to the fixed width); under 'table'
52
+ * a FILL control simply takes its natural width in the content-sized wrapper.
53
+ */
54
+ export type ControlPlacement = 'right' | 'center' | 'table';
55
+ /**
56
+ * Runtime-string-safe lookup: a device row's `type` crosses the wire
57
+ * as a plain string, so an unrecognised value resolves to null instead
58
+ * of indexing the registry with an unverified key.
59
+ */
60
+ export declare function resolveDeviceControl(type: string): DeviceTypeControl | null;
61
+ /**
62
+ * Runtime-string-safe alignment lookup for the fixed-width control column.
63
+ *
64
+ * A device's `type` crosses the wire as a plain string;
65
+ * `DEVICE_TYPE_VALUES.has(type)` narrows it to `DeviceType` before indexing
66
+ * `CONTROL_FILLS`, making the cast sound. The `'table'` placement is
67
+ * content-width for EVERY type (returns `FIT`), so a wide control never
68
+ * overflows a fixed box — the FILL flag is irrelevant there. For the fixed-box
69
+ * placements a FILL type always stretches (`FILL`) and a natural-width type is
70
+ * placed by the caller's `placement` — `'center'` → `CENTER`, `'right'` →
71
+ * `RIGHT` (card / minimal view). An unrecognised type falls back to
72
+ * natural-width placement.
73
+ */
74
+ export declare function resolveControlAlign(type: string, placement: ControlPlacement): ControlAlign;
@@ -0,0 +1,27 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceControlProps } from './control-registry';
3
+ /**
4
+ * CoverHeroCard — full control surface for the device-detail hero area.
5
+ *
6
+ * TWO VARIANTS based on whether the cover reports a position:
7
+ *
8
+ * With position (position !== null):
9
+ * - Window graphic with slats descending from top to (100-position)%
10
+ * - Visual (display-only) lever at the slat boundary with % label
11
+ * - Open / Stop / Close buttons with state-aware highlight
12
+ * - Interactive position range input (gated on CoverPositionable)
13
+ * - Tilt range input (gated on CoverTilt)
14
+ *
15
+ * Without position (open/close-only, position === null):
16
+ * - Window graphic showing state via clear pane (open) or full slats (closed)
17
+ * - NO lever, NO % label; the value slot shows the cover STATE text (no %)
18
+ * - Open / Stop / Close buttons with state-aware highlight
19
+ *
20
+ * Position support is detected via `CoverStatus.position !== null`.
21
+ * The interactive setPosition slider is gated on `DeviceFeature.CoverPositionable`.
22
+ *
23
+ * Follows the same `useDeviceCapability` pattern as CoverPanel / CoverInlineControl.
24
+ * Only hides when the device truly has no cover cap — a transient undefined slice
25
+ * renders a neutral "unknown" state with all buttons still mounted.
26
+ */
27
+ export declare function CoverHeroCard({ trpc, deviceId, features, optimistic }: DeviceControlProps): ReactNode;
@@ -0,0 +1,27 @@
1
+ import { ReactNode } from 'react';
2
+ import { CoverState } from '@camstack/types';
3
+ import { DeviceControlProps } from './control-registry';
4
+ /** Button highlight variant for cover state-aware colouring. */
5
+ export type CoverHighlightVariant = 'accent' | 'filled' | 'neutral';
6
+ export interface CoverButtonHighlight {
7
+ readonly up: CoverHighlightVariant;
8
+ readonly stop: CoverHighlightVariant;
9
+ readonly down: CoverHighlightVariant;
10
+ }
11
+ /**
12
+ * Pure helper — maps a live CoverState (or undefined when no slice yet)
13
+ * to per-button highlight variants.
14
+ *
15
+ * opening → up:accent (transient/active upward motion)
16
+ * closing → down:accent (transient/active downward motion)
17
+ * open → up:filled (stable fully-open state)
18
+ * closed → down:filled (stable fully-closed state)
19
+ * stopped / undefined → neutral (no active state)
20
+ */
21
+ export declare function coverHighlight(state: CoverState | undefined): CoverButtonHighlight;
22
+ /**
23
+ * CoverInlineControl — compact, state-aware cover controls for a
24
+ * device-list row. Renders ↑ ■ ↓ buttons + optional % open badge.
25
+ * Reuses `useDeviceCapability` exactly as `CoverPanel` does.
26
+ */
27
+ export declare function CoverInlineControl({ trpc, deviceId, optimistic }: DeviceControlProps): ReactNode;
@@ -0,0 +1,3 @@
1
+ import { ReactNode } from 'react';
2
+ import { ControlPanelProps } from './types';
3
+ export declare function CoverPanel({ trpc, deviceId, features, layout }: ControlPanelProps): ReactNode;
@@ -0,0 +1,6 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceControlProps } from './control-registry';
3
+ /** Inline placeholder: renders nothing (no-op row atom for types with no inline control). */
4
+ export declare function DummyInline(_p: DeviceControlProps): ReactNode;
5
+ /** Hero card placeholder: shows a muted "Controls coming soon" card for unimplemented types. */
6
+ export declare function DummyHeroCard(_p: DeviceControlProps): ReactNode;
@@ -0,0 +1,21 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceControlProps } from './control-registry';
3
+ /**
4
+ * FanHeroCard — radial speed-dial hero for Fan devices.
5
+ *
6
+ * Renders:
7
+ * - RadialGauge: draggable speed ArcKnob riding a 270° ring at the speed
8
+ * fraction (drag snaps to the device's percentageStep), centered fan glyph +
9
+ * percentage readout. The gauge is the sole speed control — no slider.
10
+ * - Preset selector (centered) — ONLY when the device advertises
11
+ * DeviceFeature.FanPreset AND the slice exposes a non-empty
12
+ * `availablePresets`.
13
+ * - TAP TOGGLE for on/off (Spec §3 — NOT a SlideToggle; slide is safety-only).
14
+ * - Oscillate toggle button — ONLY when the device advertises
15
+ * DeviceFeature.FanOscillating AND the slice exposes a non-null
16
+ * `oscillating` value.
17
+ *
18
+ * Composes two caps: `switch` (on/off) + `fan-control` (speed / oscillate).
19
+ * Cap-gated on `switch` (always present on a Fan) — returns null without it.
20
+ */
21
+ export declare function FanHeroCard({ trpc, deviceId, features, optimistic }: DeviceControlProps): ReactNode;
@@ -0,0 +1,3 @@
1
+ import { ReactNode } from 'react';
2
+ import { ControlPanelProps } from './types';
3
+ export declare function FanPanel({ trpc, deviceId, features, layout, optimistic }: ControlPanelProps): ReactNode;
@@ -0,0 +1,37 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceControlProps } from './control-registry';
3
+ /** The `action` HA attribute drives the droplet / mist tint. */
4
+ export type HumidifierAction = 'humidifying' | 'drying' | 'idle' | 'off';
5
+ export interface HumidifierTint {
6
+ /** Tailwind class for the wash behind the droplet. */
7
+ readonly glow: string;
8
+ /** Tailwind text token driving the droplet via currentColor. */
9
+ readonly accent: string;
10
+ /** Whether to render the upward mist dashes (actively humidifying). */
11
+ readonly misting: boolean;
12
+ }
13
+ /**
14
+ * Pure helper — maps the device's on-state + HA `action` to a tint.
15
+ * Exhaustive over the known action strings; an unknown / null action
16
+ * falls back to on/off heuristics.
17
+ *
18
+ * humidifying → primary (blue)
19
+ * drying → warning
20
+ * idle / off → muted
21
+ */
22
+ export declare function humidifierTint(on: boolean, action: string | null): HumidifierTint;
23
+ /**
24
+ * HumidifierInlineControl — compact on/off toggle + current→target readout
25
+ * for a device-list row. Must NOT blow out the row width.
26
+ */
27
+ export declare function HumidifierInlineControl({ trpc, deviceId, optimistic }: DeviceControlProps): ReactNode;
28
+ /**
29
+ * HumidifierHeroCard — full control surface for the device-detail hero.
30
+ *
31
+ * Bespoke droplet + mist graphic + a large target-humidity readout +
32
+ * an on/off toggle + a target-humidity range slider (bounds from
33
+ * min/maxHumidity ?? 0..100) + a current-humidity readout + a mode
34
+ * `<select>` (only when modes are available). Gated on `available`.
35
+ * Only hides when the device truly has no humidifier cap.
36
+ */
37
+ export declare function HumidifierHeroCard({ trpc, deviceId, optimistic }: DeviceControlProps): ReactNode;
@@ -0,0 +1,24 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceControlProps } from './control-registry';
3
+ /**
4
+ * ImageInlineControl — compact thumbnail for a device-list row that expands
5
+ * into a floating preview on hover/focus (shared `HoverZoomImage`). Shows a
6
+ * muted ImageOff placeholder box when no url is available. Row-narrow.
7
+ *
8
+ * `DeviceControlProps` carries no device name, so the alt/caption is the
9
+ * generic 'Image'; the framed hero (ImageHeroCard) carries the real name.
10
+ */
11
+ export declare function ImageInlineControl({ trpc, deviceId }: DeviceControlProps): ReactNode;
12
+ export interface ImageHeroCardProps extends DeviceControlProps {
13
+ /** Optional device name for the image alt text. */
14
+ readonly deviceName?: string;
15
+ }
16
+ /**
17
+ * ImageHeroCard — read-only framed image for the device-detail hero.
18
+ *
19
+ * A still image in the cover-style framed box (rounded border,
20
+ * object-contain) when a url is present; a centered ImageOff + "No image"
21
+ * placeholder when null. Keeps the framed-box chrome for visual
22
+ * consistency with the other heroes.
23
+ */
24
+ export declare function ImageHeroCard({ trpc, deviceId, deviceName }: ImageHeroCardProps): ReactNode;
@@ -0,0 +1,33 @@
1
+ export { ControlColumn, ValueReadout, FILL, RIGHT, CENTER, type ControlAlign, Stepper, SlideToggle, ArcKnob, GripTrack } from './atoms';
2
+ export { RadialGauge, type RadialGaugeProps } from './radial-gauge';
3
+ export { TapToggle, type TapToggleProps } from './tap-toggle';
4
+ export { ButtonControl, ButtonHeroCard } from './button-control';
5
+ export { CoverPanel } from './cover-panel';
6
+ export { LockPanel } from './lock-panel';
7
+ export { SwitchPanel } from './switch-panel';
8
+ export { BrightnessPanel } from './brightness-panel';
9
+ export { FanPanel } from './fan-panel';
10
+ export { ClimatePanel, MODE_COLOR } from './climate-panel';
11
+ export { MediaPlayerPanel, STATE_COLOR } from './media-player-panel';
12
+ export { ControlPanel } from './control-panel';
13
+ export { CONTROL_CAP_NAMES, type ControlCapName, type ControlPanelProps } from './types';
14
+ export { PopoverRowAction } from './popover-row-action';
15
+ export { SensorValueAtom, ROLE_DESCRIPTOR, type SensorDescriptor, type SensorValueAtomProps } from './sensor-value-atom';
16
+ export { DEVICE_TYPE_CONTROL, CONTROL_FILLS, resolveDeviceControl, resolveControlAlign, coverHighlight, type DeviceTypeControl, type DeviceControlProps, type ControlPlacement, } from './control-registry';
17
+ export { OfflineBadge, type OfflineBadgeProps } from './offline-badge';
18
+ export { CoverInlineControl, type CoverHighlightVariant, type CoverButtonHighlight } from './cover-inline';
19
+ export { CoverHeroCard } from './cover-hero-card';
20
+ export { VacuumInlineControl, VacuumHeroCard, vacuumStateMeta, tankAlert, type VacuumStateMeta, type TankKind, type TankAlert, } from './vacuum-control';
21
+ export { LawnMowerInlineControl, LawnMowerHeroCard, lawnMowerActivityMeta, type LawnMowerActivityMeta } from './lawn-mower-control';
22
+ export { ValveInlineControl, ValveHeroCard, valveStateMeta, type ValveStateMeta } from './valve-control';
23
+ export { HumidifierInlineControl, HumidifierHeroCard, humidifierTint, type HumidifierTint, type HumidifierAction, } from './humidifier-control';
24
+ export { WaterHeaterInlineControl, WaterHeaterHeroCard, waterHeaterTint, waterHeaterPhase, type WaterHeaterTint, type WaterHeaterPhase, } from './water-heater-control';
25
+ export { WeatherInlineControl, WeatherHeroCard, weatherTint, weatherConditionMeta, type WeatherTint, type WeatherConditionMeta, type WeatherGlyph, } from './weather-control';
26
+ export { ImageInlineControl, ImageHeroCard, type ImageHeroCardProps, } from './image-control';
27
+ export { DummyInline, DummyHeroCard } from './dummy-hero-card';
28
+ export { SensorInlineControl } from './sensor-inline-control';
29
+ export { SensorHeroCard } from './sensor-hero-card';
30
+ export { SwitchInlineControl, SwitchHeroCard, LockInlineControl, LockHeroCard, LightInlineControl, LightHeroCard, FanInlineControl, FanHeroCard, ThermostatInlineControl, ThermostatHeroCard, MediaPlayerInlineControl, MediaPlayerHeroCard, AlarmPanelInlineControl, AlarmHeroCard, ControlInlineControl, ControlHeroCard, } from './panel-controls';
31
+ export { resolvePrimaryChild, PRIORITY, type ChildRef } from './primary-child';
32
+ export { resolveContainerPrimary, overrideEntityIdFromLink, containerChildToRef, childEntityId, type ContainerChild, } from './container-primary';
33
+ export { ContainerPrimaryHero, type ContainerPrimaryHeroProps, type ContainerHeroChild, } from './container-primary-hero';
@@ -0,0 +1,24 @@
1
+ import { ReactNode } from 'react';
2
+ import { LawnMowerActivity } from '@camstack/types';
3
+ import { DeviceControlProps } from './control-registry';
4
+ export interface LawnMowerActivityMeta {
5
+ readonly label: string;
6
+ /** Tailwind text-colour token for the activity label. */
7
+ readonly tone: string;
8
+ }
9
+ /**
10
+ * Pure helper — maps a LawnMowerActivity (or undefined when no slice
11
+ * yet) to a human label + colour tone. Exhaustive switch: a new enum
12
+ * member fails the build.
13
+ */
14
+ export declare function lawnMowerActivityMeta(activity: LawnMowerActivity | undefined): LawnMowerActivityMeta;
15
+ /**
16
+ * LawnMowerInlineControl — compact, activity-aware mower controls for a
17
+ * device-list row. Mow / Pause / Dock + battery badge.
18
+ */
19
+ export declare function LawnMowerInlineControl({ trpc, deviceId }: DeviceControlProps): ReactNode;
20
+ /**
21
+ * LawnMowerHeroCard — full control surface for the device-detail hero
22
+ * area. Activity label + action buttons + battery readout.
23
+ */
24
+ export declare function LawnMowerHeroCard({ trpc, deviceId }: DeviceControlProps): ReactNode;
@@ -0,0 +1,16 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceControlProps } from './control-registry';
3
+ /**
4
+ * LightHeroCard — radial dimmer hero for Light devices.
5
+ *
6
+ * Renders:
7
+ * - RadialGauge: brightness ArcKnob riding a 270° ring at the level fraction,
8
+ * centered bulb glyph + percentage readout.
9
+ * - TAP TOGGLE for on/off (Spec §3 — NOT a SlideToggle; slide is safety-only).
10
+ * - Colour-temperature GripTrack strip — ONLY when the device advertises
11
+ * DeviceFeature.LightColorMired AND the `color` cap is bound.
12
+ *
13
+ * Composes three caps: `switch` (on/off), `brightness` (level), `color` (CT).
14
+ * Cap-gated on `switch` (always present on a Light) — returns null without it.
15
+ */
16
+ export declare function LightHeroCard({ trpc, deviceId, features, optimistic }: DeviceControlProps): ReactNode;
@@ -0,0 +1,15 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceControlProps } from './control-registry';
3
+ /**
4
+ * LockHeroCard — padlock hero for lock devices.
5
+ *
6
+ * Renders:
7
+ * - Padlock glyph: state-colored (locked=green, unlocked=amber, jammed=red)
8
+ * - SlideToggle (safety gesture): thumb must be dragged past threshold to
9
+ * confirm lock/unlock — a plain tap never triggers the action (Spec §3).
10
+ * - Open (unlatch) button: rendered ONLY when device.features includes
11
+ * DeviceFeature.LockOpen ('lock-open'). Absent otherwise.
12
+ *
13
+ * Cap-gated: returns null when the device has no lockControl cap.
14
+ */
15
+ export declare function LockHeroCard({ trpc, deviceId, features, optimistic }: DeviceControlProps): ReactNode;
@@ -0,0 +1,3 @@
1
+ import { ReactNode } from 'react';
2
+ import { ControlPanelProps } from './types';
3
+ export declare function LockPanel({ trpc, deviceId, layout, optimistic, features }: ControlPanelProps): ReactNode;
@@ -0,0 +1,17 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceControlProps } from './control-registry';
3
+ /**
4
+ * MediaPlayerHeroCard — now-playing hero for MediaPlayer devices.
5
+ *
6
+ * Renders (Spec §3):
7
+ * - Now-playing block: cover glyph + title + artist/album.
8
+ * - Seek GripTrack with a native range overlay (gated on DeviceFeature
9
+ * MediaPlayerSeek + a known duration).
10
+ * - Transport row: SkipBack / Play-Pause / SkipForward (lucide icons). Skip
11
+ * buttons are gated on MediaPlayerPrevious / MediaPlayerNext.
12
+ * - Volume GripTrack with a native range overlay + mute toggle (gated on a
13
+ * non-null volume surface + DeviceFeature.MediaPlayerVolume).
14
+ *
15
+ * Cap-gated on `media-player` — returns null when the device lacks the cap.
16
+ */
17
+ export declare function MediaPlayerHeroCard({ trpc, deviceId, features, optimistic }: DeviceControlProps): ReactNode;
@@ -0,0 +1,9 @@
1
+ import { ReactNode } from 'react';
2
+ import { ControlPanelProps } from './types.js';
3
+ import { MediaPlayerState } from '@camstack/types';
4
+ /**
5
+ * Exhaustive colour map — one entry per MediaPlayerState.
6
+ * Build fails if a state is added and not reflected here.
7
+ */
8
+ export declare const STATE_COLOR: Record<MediaPlayerState, string>;
9
+ export declare function MediaPlayerPanel({ trpc, deviceId, features: _features, layout, optimistic }: ControlPanelProps): ReactNode;
@@ -0,0 +1,11 @@
1
+ import { ReactElement } from 'react';
2
+ export interface OfflineBadgeProps {
3
+ /**
4
+ * Ms epoch of the last online→offline transition (device-status
5
+ * `lastChangedAt`). `null`/`undefined`/non-positive → no "last seen"
6
+ * suffix is shown.
7
+ */
8
+ readonly lastChangedAt?: number | null;
9
+ readonly className?: string;
10
+ }
11
+ export declare function OfflineBadge({ lastChangedAt, className }: OfflineBadgeProps): ReactElement;
@@ -0,0 +1,17 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceControlProps } from './control-registry';
3
+ export { ThermostatHeroCard } from './thermostat-hero-card';
4
+ export { LockHeroCard } from './lock-hero-card';
5
+ export { LightHeroCard } from './light-hero-card';
6
+ export { FanHeroCard } from './fan-hero-card';
7
+ export { SwitchHeroCard } from './switch-hero-card';
8
+ export { MediaPlayerHeroCard } from './media-player-hero-card';
9
+ export { AlarmHeroCard, AlarmInlineControl as AlarmPanelInlineControl } from './alarm-hero-card';
10
+ export declare function SwitchInlineControl({ trpc, deviceId, features, optimistic }: DeviceControlProps): ReactNode;
11
+ export declare function LockInlineControl({ trpc, deviceId, features, optimistic }: DeviceControlProps): ReactNode;
12
+ export declare function LightInlineControl({ trpc, deviceId, features, optimistic }: DeviceControlProps): ReactNode;
13
+ export declare function FanInlineControl({ trpc, deviceId, features, optimistic }: DeviceControlProps): ReactNode;
14
+ export declare function ThermostatInlineControl({ trpc, deviceId, features, optimistic }: DeviceControlProps): ReactNode;
15
+ export declare function MediaPlayerInlineControl({ trpc, deviceId, features, optimistic }: DeviceControlProps): ReactNode;
16
+ export declare function ControlInlineControl({ trpc, deviceId, features, optimistic }: DeviceControlProps): ReactNode;
17
+ export declare function ControlHeroCard({ trpc, deviceId, features, optimistic }: DeviceControlProps): ReactNode;
@@ -0,0 +1,9 @@
1
+ import { ReactNode } from 'react';
2
+ import { LucideIcon } from 'lucide-react';
3
+ interface PopoverRowActionProps {
4
+ readonly icon: LucideIcon;
5
+ readonly title: string;
6
+ readonly children: ReactNode;
7
+ }
8
+ export declare function PopoverRowAction({ icon: Icon, title, children }: PopoverRowActionProps): ReactNode;
9
+ export {};
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Pure resolver: given a container's entity-children and an optional picker
3
+ * override (stored as an entityId string, re-sync-stable), returns the child
4
+ * that should be rendered as the container's PRIMARY child.
5
+ *
6
+ * Design note: ChildRef.type is kept as `string` (not `DeviceType`) to decouple
7
+ * this pure resolver from the types enum. The resolver is tolerant of unknown
8
+ * type names — they sort after all known PRIORITY entries. This avoids creating
9
+ * a hard import dependency between the resolver and the enum, and handles future
10
+ * entity types gracefully.
11
+ */
12
+ export declare const PRIORITY: readonly string[];
13
+ export interface ChildRef {
14
+ readonly id: number;
15
+ readonly entityId: string;
16
+ readonly type: string;
17
+ }
18
+ export declare function resolvePrimaryChild(children: readonly ChildRef[], overrideEntityId: string | undefined): ChildRef | null;
@@ -0,0 +1,20 @@
1
+ import { ReactNode } from 'react';
2
+ export interface RadialGaugeProps {
3
+ /** Fill fraction 0..1 (brightness or speed level). */
4
+ readonly fraction: number;
5
+ /** Hex/CSS colour for the fill arc + knob border. */
6
+ readonly color: string;
7
+ /** Centered content (icon + value readout). */
8
+ readonly children: ReactNode;
9
+ /**
10
+ * When provided, the ArcKnob becomes draggable: dragging it reports the new
11
+ * arc fraction (0..1) to the parent, which owns the value and re-renders the
12
+ * gauge at the new `fraction`. When omitted the knob is display-only.
13
+ */
14
+ readonly onDragFraction?: (fraction: number) => void;
15
+ }
16
+ /**
17
+ * Circular gauge with an ArcKnob at `fraction` and centered children.
18
+ * The knob wrapper carries `data-testid="arc-knob"` for test assertions.
19
+ */
20
+ export declare function RadialGauge({ fraction, color, children, onDragFraction }: RadialGaugeProps): ReactNode;
@@ -0,0 +1,10 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceControlProps } from './control-registry';
3
+ /**
4
+ * SensorHeroCard — full hero card for the device-detail area.
5
+ *
6
+ * Fetches the device row (name, role, sourceInfo) via React Query
7
+ * (`deviceManager.getDevice`), then delegates rendering to the inner
8
+ * component that reads the live state slice via the proxy.
9
+ */
10
+ export declare function SensorHeroCard({ trpc, deviceId }: DeviceControlProps): ReactNode;
@@ -0,0 +1,11 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceControlProps } from './control-registry';
3
+ /**
4
+ * Registry InlineControl for Sensor and Presence device types.
5
+ *
6
+ * Fetches the device row once (React Query cache — no waterfall for
7
+ * repeated renders), maps to DeviceItemDevice, and delegates rendering
8
+ * to the existing SensorValueAtom so both the row special-case path and
9
+ * the registry path stay in sync.
10
+ */
11
+ export declare function SensorInlineControl({ trpc, deviceId }: DeviceControlProps): ReactNode;
@@ -0,0 +1,106 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceProxy, SliceHandle } from '@camstack/types';
3
+ import { UseDeviceProxyTrpc } from '../../hooks/use-device-proxy';
4
+ import { ControlDateTimeFormat } from '../../lib/format-control-datetime';
5
+ import { DeviceItemDevice } from '../device-item/helpers';
6
+ interface NumericDescriptor {
7
+ readonly kind: 'numeric';
8
+ /** Field in the slice that holds the numeric value. */
9
+ readonly field: string;
10
+ /** Canonical display unit for TYPED roles (temperature → '°C', humidity → '%',
11
+ * ambient-light → 'lx', pressure → 'hPa'). When present the UI renders this
12
+ * directly WITHOUT consulting the slice or sourceInfo.
13
+ * For the generic `numeric-sensor` role this is `undefined` — the unit is
14
+ * read live from the slice (`slice.unit`). */
15
+ readonly canonicalUnit?: string;
16
+ /** Accessor into DeviceProxy['state'] — returns a typed slice handle. */
17
+ readonly getSliceHandle: (state: DeviceProxy['state']) => SliceHandle<unknown>;
18
+ }
19
+ interface BinaryDescriptor {
20
+ readonly kind: 'binary';
21
+ /** Field in the slice that holds the boolean trigger. */
22
+ readonly field: string;
23
+ /** Label shown when the boolean field is truthy. */
24
+ readonly onLabel: string;
25
+ /** Label shown when the boolean field is falsy. */
26
+ readonly offLabel: string;
27
+ readonly getSliceHandle: (state: DeviceProxy['state']) => SliceHandle<unknown>;
28
+ }
29
+ interface PresenceDescriptor {
30
+ readonly kind: 'presence';
31
+ readonly getSliceHandle: (state: DeviceProxy['state']) => SliceHandle<unknown>;
32
+ }
33
+ /**
34
+ * String-valued sensor whose slice holds `{ value: string, lastFetchedAt }`.
35
+ * Used for HA free-text sensors (IP address, SSID, firmware version, …) that
36
+ * have no numeric device_class and whose state is a plain string.
37
+ */
38
+ interface StringDescriptor {
39
+ readonly kind: 'string';
40
+ readonly getSliceHandle: (state: DeviceProxy['state']) => SliceHandle<unknown>;
41
+ }
42
+ /**
43
+ * Date / timestamp sensor whose slice holds `{ value: string, format? }`
44
+ * carrying a raw ISO string (e.g. '2025-12-02T09:36:12+00:00'). Rendered
45
+ * locale-formatted via `formatControlDateTime`. The slice's own `format` (seeded
46
+ * by the HA provider from the entity's `device_class` — `timestamp` → 'datetime',
47
+ * `date` → 'date', time-only → 'time') selects the shape; `descriptor.format` is
48
+ * the fallback for slices adopted before the field was threaded ('datetime').
49
+ */
50
+ interface DateTimeDescriptor {
51
+ readonly kind: 'datetime';
52
+ readonly format: ControlDateTimeFormat;
53
+ readonly getSliceHandle: (state: DeviceProxy['state']) => SliceHandle<unknown>;
54
+ }
55
+ export type SensorDescriptor = NumericDescriptor | BinaryDescriptor | PresenceDescriptor | StringDescriptor | DateTimeDescriptor;
56
+ /**
57
+ * Binary fallback for the `battery-sensor` role.
58
+ *
59
+ * HA assigns role `battery-sensor` to BOTH numeric battery entities
60
+ * (`sensor.*` device_class=battery → cap `numeric-sensor`) AND binary battery
61
+ * entities (`binary_sensor.*` device_class=battery → cap `binary`, e.g. a
62
+ * HomeMatic `LOW_BAT` flag). When the numeric slice is absent the binary slice
63
+ * is rendered instead, with battery-specific polarity: `on:true` means LOW
64
+ * (bad → warning style + "Low"), `on:false` means Normal (neutral).
65
+ */
66
+ export declare const BINARY_BATTERY_DESCRIPTOR: BinaryDescriptor;
67
+ /** True for the role that can resolve to either a numeric or a binary slice. */
68
+ export declare function isBatterySensorRole(role: string | null | undefined): boolean;
69
+ /**
70
+ * A numeric slice counts as "present" only when it carries a real numeric value
71
+ * at the descriptor field — a cold-start slice (`{ value: 0, lastFetchedAt: 0 }`)
72
+ * or a binary slice (no numeric field) does NOT, so the binary fallback wins.
73
+ */
74
+ export declare function hasBatteryNumericValue(slice: unknown, field: string): boolean;
75
+ /**
76
+ * Keyed by DeviceRole string value. Every entry maps a role to the
77
+ * exact proxy state accessor + rendering instructions. The accessor
78
+ * avoids dynamic string indexing — each one is a concrete property
79
+ * access on the typed `DeviceProxy['state']` shape.
80
+ *
81
+ * Canonical unit rules:
82
+ * - Typed roles (temperature/humidity/ambient-light/pressure/power/energy)
83
+ * carry a `canonicalUnit` constant. The slice is normalised to one unit
84
+ * so no per-device lookup is needed.
85
+ * - Generic `numeric-sensor` has no `canonicalUnit`. The unit is read live
86
+ * from the slice (`slice.unit`) as populated by the provider from the
87
+ * upstream source on every state push.
88
+ */
89
+ export declare const ROLE_DESCRIPTOR: Record<string, SensorDescriptor>;
90
+ export interface StringSliceValue {
91
+ readonly value: string;
92
+ }
93
+ export declare function isStringSliceValue(v: unknown): v is StringSliceValue;
94
+ /**
95
+ * The enum-sensor slice carries an optional `format` ('date' | 'time' |
96
+ * 'datetime') for `DateTimeSensor`-role sensors. Read it off the live slice so
97
+ * each sensor renders its true shape; returns `undefined` when absent (older
98
+ * adoptions) so the caller falls back to the descriptor default.
99
+ */
100
+ export declare function readSliceDateTimeFormat(slice: unknown): ControlDateTimeFormat | undefined;
101
+ export interface SensorValueAtomProps {
102
+ readonly trpc: UseDeviceProxyTrpc;
103
+ readonly device: DeviceItemDevice;
104
+ }
105
+ export declare function SensorValueAtom({ trpc, device }: SensorValueAtomProps): ReactNode;
106
+ export {};
@@ -0,0 +1,13 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceControlProps } from './control-registry';
3
+ /**
4
+ * SwitchHeroCard — large tap-toggle hero for Switch / Siren devices.
5
+ *
6
+ * Renders (Spec §3):
7
+ * - A power glyph tinted by on/off state (success when on, muted when off).
8
+ * - A large TAP TOGGLE for on/off — a single tap flips state. This is NOT a
9
+ * SlideToggle; slide-to-confirm is reserved for safety-gated locks ONLY.
10
+ *
11
+ * Cap-gated on `switch` — returns null when the device has no switch cap.
12
+ */
13
+ export declare function SwitchHeroCard({ trpc, deviceId, optimistic }: DeviceControlProps): ReactNode;
@@ -0,0 +1,3 @@
1
+ import { ReactNode } from 'react';
2
+ import { ControlPanelProps } from './types';
3
+ export declare function SwitchPanel({ trpc, deviceId, optimistic }: ControlPanelProps): ReactNode;
@@ -0,0 +1,17 @@
1
+ import { ReactNode } from 'react';
2
+ /**
3
+ * TapToggle — plain tap-to-toggle on/off control.
4
+ *
5
+ * Spec §3: everyday on/off (switch, light, fan) uses a plain TAP TOGGLE.
6
+ * slide-to-confirm is reserved for safety-gated actions (lock) ONLY. This
7
+ * component must NEVER be built on `SlideToggle` — a single tap flips state.
8
+ *
9
+ * Presentational: the caller owns the cap mutation + optimistic update via
10
+ * `onToggle`. Mirrors the `SwitchPanel` button treatment at hero scale.
11
+ */
12
+ export interface TapToggleProps {
13
+ readonly on: boolean;
14
+ readonly busy: boolean;
15
+ readonly onToggle: () => void;
16
+ }
17
+ export declare function TapToggle({ on, busy, onToggle }: TapToggleProps): ReactNode;
@@ -0,0 +1,17 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceControlProps } from './control-registry';
3
+ /**
4
+ * ThermostatHeroCard — circular gauge hero for thermostat / HVAC devices.
5
+ *
6
+ * Renders:
7
+ * - SVG ring gauge: track + fill arc representing the setpoint fraction
8
+ * - Centered current temperature (large, prominent)
9
+ * - ArcKnob(s) at the setpoint fraction(s) riding the ring
10
+ * - Single knob for heat / cool / auto / dry modes
11
+ * - Two knobs (low + high) with a band fill for heat_cool mode
12
+ * - Stepper (single) or "Low / High" display (range) for the setpoint
13
+ * - Mode select
14
+ *
15
+ * Cap-gated: returns null when the device has no climateControl cap.
16
+ */
17
+ export declare function ThermostatHeroCard({ trpc, deviceId, features: _features, optimistic }: DeviceControlProps): ReactNode;