@camstack/ui-library 0.1.56 → 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 (126) hide show
  1. package/dist/composites/battery-badge.d.ts +3 -0
  2. package/dist/composites/camera-stream-player.d.ts +101 -1
  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/MaskShapeCanvas.d.ts +48 -0
  8. package/dist/composites/cap-settings/MotionZonesSettings.d.ts +1 -1
  9. package/dist/composites/cap-settings/PrivacyMaskSettings.d.ts +2 -0
  10. package/dist/composites/cap-settings/RecordingPanel.d.ts +2 -0
  11. package/dist/composites/cap-settings/RecordingRulesEditor.d.ts +7 -0
  12. package/dist/composites/cap-settings/RecordingSettings.d.ts +8 -0
  13. package/dist/composites/cap-settings/RecordingTimeline.d.ts +5 -0
  14. package/dist/composites/cap-settings/TimelineControls.d.ts +21 -0
  15. package/dist/composites/cap-settings/event-thumb.d.ts +11 -0
  16. package/dist/composites/cap-settings/index.d.ts +7 -0
  17. package/dist/composites/cap-settings/recording-config-form.d.ts +5 -0
  18. package/dist/composites/cap-settings/recording-spans.d.ts +22 -0
  19. package/dist/composites/cap-settings/recording-timeline-model.d.ts +84 -0
  20. package/dist/composites/device-activity-panel.d.ts +5 -1
  21. package/dist/composites/device-controls/alarm-hero-card.d.ts +24 -0
  22. package/dist/composites/device-controls/atoms.d.ts +81 -0
  23. package/dist/composites/device-controls/brightness-panel.d.ts +3 -0
  24. package/dist/composites/device-controls/button-control.d.ts +18 -0
  25. package/dist/composites/device-controls/climate-panel.d.ts +16 -0
  26. package/dist/composites/device-controls/container-primary-hero.d.ts +24 -0
  27. package/dist/composites/device-controls/container-primary.d.ts +46 -0
  28. package/dist/composites/device-controls/control-panel.d.ts +10 -0
  29. package/dist/composites/device-controls/control-registry.d.ts +74 -0
  30. package/dist/composites/device-controls/cover-hero-card.d.ts +27 -0
  31. package/dist/composites/device-controls/cover-inline.d.ts +27 -0
  32. package/dist/composites/device-controls/cover-panel.d.ts +3 -0
  33. package/dist/composites/device-controls/dummy-hero-card.d.ts +6 -0
  34. package/dist/composites/device-controls/fan-hero-card.d.ts +21 -0
  35. package/dist/composites/device-controls/fan-panel.d.ts +3 -0
  36. package/dist/composites/device-controls/humidifier-control.d.ts +37 -0
  37. package/dist/composites/device-controls/image-control.d.ts +24 -0
  38. package/dist/composites/device-controls/index.d.ts +33 -0
  39. package/dist/composites/device-controls/lawn-mower-control.d.ts +24 -0
  40. package/dist/composites/device-controls/light-hero-card.d.ts +16 -0
  41. package/dist/composites/device-controls/lock-hero-card.d.ts +15 -0
  42. package/dist/composites/device-controls/lock-panel.d.ts +3 -0
  43. package/dist/composites/device-controls/media-player-hero-card.d.ts +17 -0
  44. package/dist/composites/device-controls/media-player-panel.d.ts +9 -0
  45. package/dist/composites/device-controls/offline-badge.d.ts +11 -0
  46. package/dist/composites/device-controls/panel-controls.d.ts +17 -0
  47. package/dist/composites/device-controls/popover-row-action.d.ts +9 -0
  48. package/dist/composites/device-controls/primary-child.d.ts +18 -0
  49. package/dist/composites/device-controls/radial-gauge.d.ts +20 -0
  50. package/dist/composites/device-controls/sensor-hero-card.d.ts +10 -0
  51. package/dist/composites/device-controls/sensor-inline-control.d.ts +11 -0
  52. package/dist/composites/device-controls/sensor-value-atom.d.ts +106 -0
  53. package/dist/composites/device-controls/switch-hero-card.d.ts +13 -0
  54. package/dist/composites/device-controls/switch-panel.d.ts +3 -0
  55. package/dist/composites/device-controls/tap-toggle.d.ts +17 -0
  56. package/dist/composites/device-controls/thermostat-hero-card.d.ts +17 -0
  57. package/dist/composites/device-controls/types.d.ts +17 -0
  58. package/dist/composites/device-controls/update-control.d.ts +11 -0
  59. package/dist/composites/device-controls/vacuum-control.d.ts +36 -0
  60. package/dist/composites/device-controls/valve-control.d.ts +46 -0
  61. package/dist/composites/device-controls/water-heater-control.d.ts +41 -0
  62. package/dist/composites/device-controls/weather-control.d.ts +41 -0
  63. package/dist/composites/device-export-panel.d.ts +8 -1
  64. package/dist/composites/device-item/actions.d.ts +12 -1
  65. package/dist/composites/device-item/child-layout.d.ts +32 -0
  66. package/dist/composites/device-item/child-section-accordion.d.ts +9 -0
  67. package/dist/composites/device-item/container-children-context.d.ts +15 -0
  68. package/dist/composites/device-item/device-delete-action.d.ts +8 -0
  69. package/dist/composites/device-item/header.d.ts +8 -1
  70. package/dist/composites/device-item/helpers.d.ts +117 -2
  71. package/dist/composites/device-item/index.d.ts +4 -0
  72. package/dist/composites/device-item/preview.d.ts +3 -3
  73. package/dist/composites/device-item/reboot-quick-action.d.ts +9 -0
  74. package/dist/composites/device-item/status-dot.d.ts +4 -1
  75. package/dist/composites/device-list/batch-actions-bar.d.ts +15 -0
  76. package/dist/composites/device-list/batch-toolbar.d.ts +15 -0
  77. package/dist/composites/device-list/cards-layout.d.ts +18 -0
  78. package/dist/composites/device-list/columns.d.ts +68 -0
  79. package/dist/composites/device-list/device-mode.d.ts +115 -0
  80. package/dist/composites/device-list/filter-chips.d.ts +24 -13
  81. package/dist/composites/device-list/fuzzy-match.d.ts +27 -0
  82. package/dist/composites/device-list/generic-mode.d.ts +81 -0
  83. package/dist/composites/device-list/group.d.ts +19 -0
  84. package/dist/composites/device-list/hardware-cell.d.ts +7 -0
  85. package/dist/composites/device-list/hardware.d.ts +26 -0
  86. package/dist/composites/device-list/icon-action.d.ts +17 -0
  87. package/dist/composites/device-list/index.d.ts +23 -30
  88. package/dist/composites/device-list/multi-select.d.ts +22 -0
  89. package/dist/composites/device-list/sort.d.ts +18 -0
  90. package/dist/composites/device-list/sortable-header.d.ts +10 -0
  91. package/dist/composites/device-list/table-layout.d.ts +25 -0
  92. package/dist/composites/device-list/type-filter.d.ts +19 -0
  93. package/dist/composites/device-list/url-state.d.ts +14 -4
  94. package/dist/composites/device-meta.d.ts +38 -0
  95. package/dist/composites/discovery-panel.d.ts +3 -3
  96. package/dist/composites/hls-quality.d.ts +35 -0
  97. package/dist/composites/hls-video.d.ts +26 -0
  98. package/dist/composites/hover-zoom-image.d.ts +18 -0
  99. package/dist/composites/index.d.ts +13 -2
  100. package/dist/composites/log-stream-scroll.d.ts +32 -0
  101. package/dist/composites/log-stream.d.ts +8 -1
  102. package/dist/composites/stream-panel.d.ts +60 -9
  103. package/dist/composites/timezone-selector.d.ts +18 -0
  104. package/dist/composites/widget-panel.d.ts +28 -0
  105. package/dist/contexts/vod-playback.d.ts +17 -0
  106. package/dist/generated/system-hooks.d.ts +358 -56
  107. package/dist/hooks/index.d.ts +6 -0
  108. package/dist/hooks/use-device-cap-slice.d.ts +19 -0
  109. package/dist/hooks/use-device-capability.d.ts +12 -0
  110. package/dist/hooks/use-device-list-page-size.d.ts +14 -0
  111. package/dist/hooks/use-device-webrtc.d.ts +101 -4
  112. package/dist/hooks/use-optimistic-slice.d.ts +11 -0
  113. package/dist/index.cjs +53123 -9819
  114. package/dist/index.cjs.map +1 -1
  115. package/dist/index.d.ts +1 -0
  116. package/dist/index.js +52791 -9767
  117. package/dist/index.js.map +1 -1
  118. package/dist/lib/cap-error.d.ts +41 -0
  119. package/dist/lib/format-control-datetime.d.ts +18 -0
  120. package/dist/lib/format-last-seen.d.ts +12 -0
  121. package/dist/lib/format-numeric.d.ts +9 -0
  122. package/dist/lib/index.d.ts +4 -0
  123. package/dist/primitives/dialog.d.ts +13 -0
  124. package/dist/primitives/tooltip.d.ts +9 -3
  125. package/package.json +3 -1
  126. package/dist/composites/cap-settings/MotionGridCanvas.d.ts +0 -9
@@ -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;
@@ -0,0 +1,17 @@
1
+ import { UseDeviceProxyTrpc } from '../../hooks/use-device-proxy';
2
+ /** Shared props for every control panel. `layout` selects the compact (row
3
+ * popover) vs full (detail tab) presentation. `features` gates sub-controls
4
+ * (e.g. cover position/tilt, fan speed/preset). */
5
+ export interface ControlPanelProps {
6
+ readonly trpc: UseDeviceProxyTrpc;
7
+ readonly deviceId: number;
8
+ readonly features: readonly string[] | undefined;
9
+ readonly layout: 'compact' | 'full';
10
+ /** Enable optimistic UI mode (default: true). Set to false to disable. */
11
+ readonly optimistic?: boolean;
12
+ }
13
+ /** Cap names whose presence (bound on a device) means it has a control surface.
14
+ * Single source of truth shared by the row registry and the detail Controls tab
15
+ * so the two never drift. */
16
+ export declare const CONTROL_CAP_NAMES: readonly ["cover", "lock-control", "switch", "brightness", "fan-control", "climate-control", "media-player", "control"];
17
+ export type ControlCapName = (typeof CONTROL_CAP_NAMES)[number];
@@ -0,0 +1,11 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceControlProps } from './control-registry';
3
+ /**
4
+ * UpdateInlineControl — compact firmware state for a device-list row.
5
+ */
6
+ export declare function UpdateInlineControl({ trpc, deviceId }: DeviceControlProps): ReactNode;
7
+ /**
8
+ * UpdateHeroCard — version transition + Install action for the device-detail
9
+ * hero area.
10
+ */
11
+ export declare function UpdateHeroCard({ trpc, deviceId }: DeviceControlProps): ReactNode;
@@ -0,0 +1,36 @@
1
+ import { ReactNode } from 'react';
2
+ import { VacuumState, TankStatus } from '@camstack/types';
3
+ import { DeviceControlProps } from './control-registry';
4
+ export interface VacuumStateMeta {
5
+ readonly label: string;
6
+ /** Tailwind text-colour token for the state label. */
7
+ readonly tone: string;
8
+ }
9
+ /**
10
+ * Pure helper — maps a VacuumState (or undefined when no slice yet) to a
11
+ * human label + colour tone. Exhaustive switch: a new enum member fails
12
+ * the build.
13
+ */
14
+ export declare function vacuumStateMeta(state: VacuumState | undefined): VacuumStateMeta;
15
+ /** The four intrinsic tanks a vacuum cap can expose. */
16
+ export type TankKind = 'cleanWater' | 'dirtyWater' | 'detergent' | 'dustBin';
17
+ export type TankAlert = 'ok' | 'warning';
18
+ /**
19
+ * Pure helper — classifies a tank as `ok` or `warning`.
20
+ *
21
+ * Clean-water + detergent are "supply" tanks: low is bad. Dirty-water +
22
+ * dust-bin are "waste" tanks: full is bad. Prefers the numeric `level`
23
+ * (thresholds 15 / 85), falling back to the discrete `status` flag. Both
24
+ * unknown → `ok`. Exhaustive over TankKind.
25
+ */
26
+ export declare function tankAlert(kind: TankKind, tank: TankStatus): TankAlert;
27
+ /**
28
+ * VacuumInlineControl — compact, state-aware vacuum controls for a
29
+ * device-list row. Start / Pause / Return-to-base + battery badge.
30
+ */
31
+ export declare function VacuumInlineControl({ trpc, deviceId }: DeviceControlProps): ReactNode;
32
+ /**
33
+ * VacuumHeroCard — full control surface for the device-detail hero area.
34
+ * State label + action buttons + fan-speed selector + battery readout.
35
+ */
36
+ export declare function VacuumHeroCard({ trpc, deviceId }: DeviceControlProps): ReactNode;
@@ -0,0 +1,46 @@
1
+ import { ReactNode } from 'react';
2
+ import { ValveState } from '@camstack/types';
3
+ import { DeviceControlProps } from './control-registry';
4
+ export interface ValveStateMeta {
5
+ readonly label: string;
6
+ /** Tailwind text-colour token for the state label. */
7
+ readonly tone: string;
8
+ }
9
+ /**
10
+ * Pure helper — maps a ValveState (or undefined when no slice yet) to a
11
+ * human label + colour tone. Exhaustive switch: a new enum member fails
12
+ * the build.
13
+ */
14
+ export declare function valveStateMeta(state: ValveState | undefined): ValveStateMeta;
15
+ /**
16
+ * ValveInlineControl — compact, state-aware valve controls for a
17
+ * device-list row. Open ▲ Stop ■ Close ▼ + optional position % badge.
18
+ * Mirrors `CoverInlineControl` exactly. Must NOT blow out the row width.
19
+ */
20
+ export declare function ValveInlineControl({ trpc, deviceId, optimistic }: DeviceControlProps): ReactNode;
21
+ /** Per-state visual tint tokens for the framed valve graphic. */
22
+ export interface ValveGraphicTint {
23
+ /** Tailwind class for the flow-glow wash over the pipe. */
24
+ readonly glow: string;
25
+ /** Tailwind text token driving the valve body via currentColor. */
26
+ readonly accent: string;
27
+ /** Whether to render the animated flow dashes (open / opening). */
28
+ readonly flowing: boolean;
29
+ /** Whether to render the transient-motion activity pulse. */
30
+ readonly active: boolean;
31
+ }
32
+ export declare function valveGraphicTint(state: ValveState | undefined): ValveGraphicTint;
33
+ /**
34
+ * ValveHeroCard — full control surface for the device-detail hero area.
35
+ *
36
+ * Bespoke pipe + gate-valve graphic (drag surface when positionable) +
37
+ * a large position % (or capitalized state when position is null) +
38
+ * Open / Stop / Close hero buttons + a position slider gated on
39
+ * `ValvePositionable` (additive to drag), and a read-only range when a
40
+ * position is reported but not settable.
41
+ *
42
+ * Position support is detected via `ValveStatus.position !== null`; the
43
+ * interactive setPosition surface is gated on `DeviceFeature.ValvePositionable`.
44
+ * Only hides when the device truly has no valve cap.
45
+ */
46
+ export declare function ValveHeroCard({ trpc, deviceId, features, optimistic }: DeviceControlProps): ReactNode;
@@ -0,0 +1,41 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceControlProps } from './control-registry';
3
+ /** Heating phase derived from the slice — drives the tank tint + element glow. */
4
+ export type WaterHeaterPhase = 'heating' | 'idle' | 'off';
5
+ export interface WaterHeaterTint {
6
+ /** Tailwind class for the wash behind the tank. */
7
+ readonly glow: string;
8
+ /** Tailwind text token driving the tank via currentColor. */
9
+ readonly accent: string;
10
+ /** Whether to render the heating-element glow. */
11
+ readonly heating: boolean;
12
+ }
13
+ /**
14
+ * Pure helper — maps a phase to a tint. Exhaustive over WaterHeaterPhase
15
+ * (a new member fails the build).
16
+ *
17
+ * heating → warning (element glow)
18
+ * idle → primary (warm but settled)
19
+ * off → muted
20
+ */
21
+ export declare function waterHeaterTint(phase: WaterHeaterPhase): WaterHeaterTint;
22
+ /**
23
+ * Heuristic heating phase: off when the operation mode is 'off' (or null);
24
+ * heating when current < target (actively warming); otherwise idle.
25
+ */
26
+ export declare function waterHeaterPhase(operationMode: string | null, currentTemp: number | null, targetTemp: number | null): WaterHeaterPhase;
27
+ /**
28
+ * WaterHeaterInlineControl — compact current→target readout + mode hint
29
+ * for a device-list row. Must NOT blow out the row width.
30
+ */
31
+ export declare function WaterHeaterInlineControl({ trpc, deviceId, optimistic }: DeviceControlProps): ReactNode;
32
+ /**
33
+ * WaterHeaterHeroCard — full control surface for the device-detail hero.
34
+ *
35
+ * Bespoke tank graphic + a large current-temp readout + a target-temp
36
+ * stepper (clamped to min/maxTemp) + an operation-mode `<select>` + an
37
+ * "Away" toggle (only when `away !== null`) + the current-temp readout.
38
+ * Gated on `available`. Only hides when the device truly has no
39
+ * water-heater cap.
40
+ */
41
+ export declare function WaterHeaterHeroCard({ trpc, deviceId, optimistic }: DeviceControlProps): ReactNode;
@@ -0,0 +1,41 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceControlProps } from './control-registry';
3
+ /** Which sky glyph a condition maps to. Drives both the inline icon and
4
+ * the hero scene's graphic branch. */
5
+ export type WeatherGlyph = 'sun' | 'clear-night' | 'partly-cloudy' | 'cloudy' | 'rain' | 'snow' | 'lightning' | 'fog' | 'wind' | 'hail';
6
+ export interface WeatherConditionMeta {
7
+ /** Human-readable label for the condition. */
8
+ readonly label: string;
9
+ /** Which glyph to render. */
10
+ readonly glyph: WeatherGlyph;
11
+ }
12
+ export interface WeatherTint {
13
+ /** Tailwind class for the wash behind the scene. */
14
+ readonly glow: string;
15
+ /** Tailwind text token driving the scene glyph via currentColor. */
16
+ readonly accent: string;
17
+ }
18
+ /**
19
+ * Pure helper — maps a verbatim HA condition string to a label + glyph.
20
+ * Covers the known HA `weather.*` condition set; an unknown / null
21
+ * condition falls back to a neutral cloud.
22
+ */
23
+ export declare function weatherConditionMeta(condition: string | null): WeatherConditionMeta;
24
+ /**
25
+ * Pure helper — maps a verbatim HA condition string to a tint. Exhaustive
26
+ * over the known condition set with a neutral default.
27
+ */
28
+ export declare function weatherTint(condition: string | null): WeatherTint;
29
+ /**
30
+ * WeatherInlineControl — compact condition icon + temperature readout
31
+ * for a device-list row. Read-only; must NOT blow out the row width.
32
+ */
33
+ export declare function WeatherInlineControl({ trpc, deviceId }: DeviceControlProps): ReactNode;
34
+ /**
35
+ * WeatherHeroCard — read-only weather panel for the device-detail hero.
36
+ *
37
+ * Bespoke state-tinted sky scene + a large temperature readout +
38
+ * humidity / wind-speed (with a bearing arrow) / pressure readouts.
39
+ * Pure — no controls (the cap is read-only).
40
+ */
41
+ export declare function WeatherHeroCard({ trpc, deviceId }: DeviceControlProps): ReactNode;
@@ -1,4 +1,5 @@
1
1
  import { ReactElement } from 'react';
2
+ import { UseDeviceProxyTrpc } from '../hooks/use-device-proxy';
2
3
  export interface DeviceExportPanelProps {
3
4
  /**
4
5
  * Addon id of the export addon whose surface is rendered. Used for
@@ -6,6 +7,12 @@ export interface DeviceExportPanelProps {
6
7
  * router resolves the (hub-resident) provider by `nodeId`.
7
8
  */
8
9
  readonly addonId: string;
10
+ /**
11
+ * tRPC client passed straight to the shared `<DeviceList>` so its
12
+ * per-row capability atoms (snapshot, switch, …) work. Supply
13
+ * `system.trpcClient` from `useSystem()` at the mount site.
14
+ */
15
+ readonly trpc: UseDeviceProxyTrpc;
9
16
  /** Optional callback to open a device-details page. */
10
17
  readonly onOpenDevice?: (deviceId: string) => void;
11
18
  }
@@ -13,4 +20,4 @@ export interface DeviceExportPanelProps {
13
20
  * Generic device-export panel. Addon-agnostic — only the `device-export`
14
21
  * cap drives it.
15
22
  */
16
- export declare function DeviceExportPanel({ addonId, onOpenDevice }: DeviceExportPanelProps): ReactElement;
23
+ export declare function DeviceExportPanel({ addonId, trpc, onOpenDevice }: DeviceExportPanelProps): ReactElement;
@@ -1,9 +1,20 @@
1
1
  import { ReactNode } from 'react';
2
2
  import { UseDeviceProxyTrpc } from '../../hooks/use-device-proxy';
3
+ import { ControlPlacement } from '../device-controls/control-registry';
3
4
  import { DeviceItemDevice, VariantDefaults } from './helpers';
4
5
  export interface DeviceItemActionsProps {
5
6
  readonly trpc: UseDeviceProxyTrpc;
6
7
  readonly device: DeviceItemDevice;
7
8
  readonly flags: VariantDefaults;
9
+ /**
10
+ * Where the control sits and whether the column carries the fixed `w-[140px]`
11
+ * box. `'right'` (default) → flush right in the fixed column, for card /
12
+ * minimal views. `'center'` → centered in the fixed column. `'table'` → the
13
+ * column sizes to its content (no fixed width) so a wide control lays out in
14
+ * sequence with the trailing trash + status dot without overflowing/colliding
15
+ * — used by the main devices table. FILL controls stretch to the fixed column
16
+ * width under `'right'`/`'center'`; under `'table'` they take natural width.
17
+ */
18
+ readonly controlPlacement?: ControlPlacement;
8
19
  }
9
- export declare function DeviceItemActions({ trpc, device, flags }: DeviceItemActionsProps): ReactNode;
20
+ export declare function DeviceItemActions({ trpc, device, controlPlacement }: DeviceItemActionsProps): ReactNode;
@@ -0,0 +1,32 @@
1
+ import { ChildLayout } from '@camstack/types';
2
+ import { DeviceItemDevice } from './helpers';
3
+ export interface ChildLayoutSection {
4
+ readonly title: string;
5
+ readonly children: readonly DeviceItemDevice[];
6
+ /** Whether the section renders open by default. `false` when any matched
7
+ * layout entry for this section declared `collapsed: true`. */
8
+ readonly defaultOpen: boolean;
9
+ /** When `true`, the section's children render visually de-emphasized
10
+ * (muted). Set only on the synthetic "Disabled" section. */
11
+ readonly muted?: boolean;
12
+ }
13
+ export interface GroupedChildren {
14
+ /** Sections ordered by first appearance in `childLayout`. */
15
+ readonly sections: readonly ChildLayoutSection[];
16
+ /** Children not referenced by any layout entry — render plainly, as today. */
17
+ readonly ungrouped: readonly DeviceItemDevice[];
18
+ /** Trailing synthetic section gathering every child whose CamStack-side
19
+ * `disabled` flag is set. Kept SEPARATE from `sections`/`ungrouped` so
20
+ * consumers render it LAST (after provider sections + ungrouped) and
21
+ * muted. Undefined when no child is disabled. Generic — driven purely by
22
+ * `child.disabled`, no provider/HA-specific input. */
23
+ readonly disabledSection?: ChildLayoutSection;
24
+ }
25
+ /**
26
+ * Partition a parent's children into provider-declared accordion sections +
27
+ * the remaining (ungrouped) children. A child matches an entry when
28
+ * `child.stableId === ${parentStableId}-${entry.childKey}`. Within a section,
29
+ * children sort by `order ?? Number.MAX_SAFE_INTEGER` then by name. Sections
30
+ * appear in first-seen order across `childLayout`. Pure — no I/O.
31
+ */
32
+ export declare function groupChildrenByLayout(parentStableId: string, children: readonly DeviceItemDevice[], childLayout: ChildLayout | undefined): GroupedChildren;
@@ -0,0 +1,9 @@
1
+ import { ReactNode } from 'react';
2
+ interface ChildSectionAccordionProps {
3
+ readonly title: string;
4
+ readonly count: number;
5
+ readonly children: ReactNode;
6
+ readonly defaultOpen?: boolean;
7
+ }
8
+ export declare function ChildSectionAccordion({ title, count, children, defaultOpen, }: ChildSectionAccordionProps): ReactNode;
9
+ export {};
@@ -0,0 +1,15 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceItemDevice } from './helpers';
3
+ export interface ContainerChildrenProviderProps {
4
+ /** `parentDeviceId → that parent's children`. Built once by the list. */
5
+ readonly value: ReadonlyMap<number, readonly DeviceItemDevice[]>;
6
+ readonly children: ReactNode;
7
+ }
8
+ export declare function ContainerChildrenProvider({ value, children, }: ContainerChildrenProviderProps): ReactNode;
9
+ /**
10
+ * The children of `deviceId` as published by the nearest
11
+ * `ContainerChildrenProvider`. Returns a stable empty array when there is no
12
+ * provider or the device has no children — so a non-container row, or a row
13
+ * rendered outside a list, degrades exactly like the old optional prop.
14
+ */
15
+ export declare function useContainerChildren(deviceId: number): readonly DeviceItemDevice[];
@@ -0,0 +1,8 @@
1
+ import { ReactNode } from 'react';
2
+ import { DeviceItemDevice } from './helpers';
3
+ export interface DeviceDeleteActionProps {
4
+ readonly device: DeviceItemDevice;
5
+ /** Called only after the operator confirms the destructive delete. */
6
+ readonly onDelete: (device: DeviceItemDevice) => void;
7
+ }
8
+ export declare function DeviceDeleteAction({ device, onDelete }: DeviceDeleteActionProps): ReactNode;
@@ -10,5 +10,12 @@ export interface DeviceItemHeaderProps {
10
10
  readonly expanded: boolean;
11
11
  readonly onToggleExpand: () => void;
12
12
  readonly onNavigateToParent: (id: number) => void;
13
+ /**
14
+ * Vertical-card layout. When true, the name is rendered as the card's
15
+ * primary anchor: bigger font, heavier weight, line-clamp-2 instead of
16
+ * single-line truncation, and the inline integrationIcon flips above
17
+ * the name as a small lead-line so the name's full width is name-only.
18
+ */
19
+ readonly stacked?: boolean;
13
20
  }
14
- export declare function DeviceItemHeader({ device, variant, flags, parent, integrationIcon, hasChildren, expanded, onToggleExpand, onNavigateToParent, }: DeviceItemHeaderProps): ReactNode;
21
+ export declare function DeviceItemHeader({ device, variant, flags, parent, integrationIcon, hasChildren, expanded, onToggleExpand, onNavigateToParent, stacked, }: DeviceItemHeaderProps): ReactNode;
@@ -1,5 +1,6 @@
1
1
  import { ReactNode } from 'react';
2
2
  import { LucideIcon } from 'lucide-react';
3
+ import { SourceInfo, ChildLayout } from '@camstack/types';
3
4
  import { UseDeviceProxyTrpc } from '../../hooks/use-device-proxy';
4
5
  /**
5
6
  * Mirror of the canonical `DeviceInfo` wire shape (see
@@ -26,6 +27,40 @@ export interface DeviceItemDevice {
26
27
  readonly features?: readonly string[];
27
28
  /** True for ICameraDevice instances — gates snapshot popover. */
28
29
  readonly isCamera?: boolean;
30
+ /** Upstream-system identity (dispatch key + system tag + optional uniqueId).
31
+ * Flows from `DeviceInfo.sourceInfo` via structural cast / parse;
32
+ * optional so rows from providers that pre-date SourceInfo still work.
33
+ * Does NOT carry rendering hints (unit, precision) — those live in the
34
+ * cap STATUS SLICE for generic numeric-sensor, or are canonical constants
35
+ * in ROLE_DESCRIPTOR for typed sensor roles. */
36
+ readonly sourceInfo?: SourceInfo;
37
+ /** Soft-link to another device id. For a CONTAINER device this is the
38
+ * LEGACY picker-chosen primary child (numeric id); the row/hero resolve the
39
+ * primary child via `resolveContainerPrimary(children, linkDeviceId)`.
40
+ * Nullable: an explicit null means "no override → priority default".
41
+ * Mirrors `DeviceInfo.linkDeviceId` so the wire row passes through. */
42
+ readonly linkDeviceId?: number | null;
43
+ /** Durable primary-child override for a CONTAINER device, keyed on the
44
+ * chosen child's re-sync/rename-stable `entityId`. Preferred over the
45
+ * numeric `linkDeviceId` in `resolveContainerPrimary` (survives a re-sync
46
+ * that reallocates the child's numeric id). Mirrors
47
+ * `DeviceInfo.primaryChildEntityId`. */
48
+ readonly primaryChildEntityId?: string | null;
49
+ /** Container-level layout hint: assigns specific accessory children to named
50
+ * accordion sections (with optional intra-section order). Each entry's
51
+ * `childKey` is the child's re-sync-stable accessory `stableIdSuffix`; a child
52
+ * matches when `child.stableId === ${this.stableId}-${childKey}`. Mirrors
53
+ * `DeviceInfo.childLayout` so the wire row passes through. Absent ⇒ no layout
54
+ * declared (all children render in the plain list). Consumed by
55
+ * `groupChildrenByLayout` for the device-detail list + the table row
56
+ * expansion. */
57
+ readonly childLayout?: ChildLayout;
58
+ /** Hardware + identity blob (manufacturer / model / firmware / sn / uid /
59
+ * mac / …). Driver-populated; keys are free-form and values arbitrary, so
60
+ * consumers MUST treat them defensively (string-coerce for display, skip
61
+ * non-string/empty). Mirrors `DeviceInfo.metadata` so the wire row passes
62
+ * through unchanged. Absent/null ⇒ no identity info advertised. */
63
+ readonly metadata?: Readonly<Record<string, unknown>> | null;
29
64
  }
30
65
  export interface DeviceItemParent {
31
66
  readonly id: number;
@@ -96,6 +131,56 @@ export interface DeviceItemProps {
96
131
  readonly children?: readonly DeviceItemDevice[];
97
132
  readonly onNavigate?: (deviceId: number) => void;
98
133
  readonly className?: string;
134
+ /**
135
+ * Optional trailing per-row control rendered alongside the built-in
136
+ * `<DeviceItemActions>` (both card + table views). Lets a caller
137
+ * inject a context-specific affordance — e.g. the device-export
138
+ * panel's per-row Expose/Exposed toggle — without the composite
139
+ * knowing anything about that domain. Omitted ⇒ no extra control,
140
+ * so every existing call site renders unchanged.
141
+ */
142
+ readonly renderRowAction?: (device: DeviceItemDevice) => ReactNode;
143
+ /**
144
+ * Built-in confirmed delete (TABLE feature). When provided, the merged
145
+ * previewActions cell renders a red trash button that opens the shared
146
+ * centered confirm dialog and calls `onDelete(device)` only on confirm.
147
+ * Cap/feature-agnostic — delete is generic. Context gating is "consumer
148
+ * passes the handler or not": omitted ⇒ no trash, zero layout change.
149
+ * No effect on `view='card'` (card delete is a deliberate follow-up).
150
+ */
151
+ readonly onDelete?: (device: DeviceItemDevice) => void;
152
+ /**
153
+ * When provided, renders a leading checkbox for row selection.
154
+ * Only passed to top-level rows (accessory sub-rows omit it).
155
+ * Omitted ⇒ no checkbox, zero layout change for existing callers.
156
+ */
157
+ readonly selection?: {
158
+ readonly selected: boolean;
159
+ readonly onToggle: () => void;
160
+ };
161
+ /**
162
+ * When `true` (and `selection` is provided), a click anywhere on the
163
+ * row's primary body toggles selection instead of navigating — the
164
+ * batch-mode interaction (spec §6). The row also gains an accessible
165
+ * pressed affordance (`role="button"` + `aria-pressed`). When omitted
166
+ * or `false`, the row navigates on click as usual; the leading
167
+ * checkbox remains the only selection surface. The checkbox always
168
+ * toggles regardless of this flag (and never bubbles to navigate).
169
+ */
170
+ readonly rowClickSelects?: boolean;
171
+ /**
172
+ * Table-view column gates (context-configured). Default `true` so every
173
+ * existing call site renders all cells. When `false`, the corresponding
174
+ * `<td>` is omitted entirely (the matching `<th>` is gated in the parent
175
+ * `TableLayout`, so header + cells stay in lock-step). No effect on
176
+ * `view='card'`.
177
+ */
178
+ readonly showTypeCell?: boolean;
179
+ /** Render the Hardware (manufacturer/model) `<td>` (context-gated). Default
180
+ * `true` so existing call sites render it; the parent `TableLayout` gates the
181
+ * matching `<th>` so header + cell stay in lock-step. No effect on `'card'`. */
182
+ readonly showManufacturerCell?: boolean;
183
+ readonly showFeaturesCell?: boolean;
99
184
  }
100
185
  export interface VariantDefaults {
101
186
  readonly showIntegrationIcon: boolean;
@@ -108,8 +193,6 @@ export interface VariantDefaults {
108
193
  readonly showChildrenAccordion: boolean;
109
194
  }
110
195
  export declare function resolveFlags(props: DeviceItemProps): VariantDefaults;
111
- export declare const ROLE_ICONS: Readonly<Record<string, LucideIcon>>;
112
- export declare const TYPE_ICONS: Readonly<Record<string, LucideIcon>>;
113
196
  export declare function deviceLeadIcon(device: DeviceItemDevice): LucideIcon;
114
197
  /**
115
198
  * Icon for each `DeviceFeature` advertised on a device row. Used by
@@ -144,4 +227,36 @@ export declare const FEATURE_TO_CAP_NAME: Readonly<Record<string, string>>;
144
227
  export declare function maySupportSwitch(device: DeviceItemDevice): boolean;
145
228
  export declare function isAccessoryChild(device: DeviceItemDevice): boolean;
146
229
  export declare function hasMotionTriggerFeature(device: DeviceItemDevice): boolean;
230
+ /**
231
+ * Effective online state for a device row's status dot.
232
+ *
233
+ * A CONTAINER device renders AS its PRIMARY child (same row control via
234
+ * `ContainerPrimaryInline`, same hero via `ContainerPrimaryHero`). Its
235
+ * reachability dot should therefore follow that primary child's `online`,
236
+ * NOT "any child online" — so the dot and the control gate agree (the
237
+ * control is bound to the same primary child via `resolveContainerPrimary`).
238
+ *
239
+ * Resolution reuses the SAME `resolveContainerPrimary(children, linkDeviceId)`
240
+ * helper the row control uses, then reads the resolved child row's `online`.
241
+ * Degrades gracefully: a non-container, a container with no children, or a
242
+ * container with no resolvable primary child falls back to `device.online`
243
+ * (never crashes, never forces offline).
244
+ */
245
+ export declare function effectiveOnline(device: DeviceItemDevice, containerChildren: readonly DeviceItemDevice[] | undefined): boolean;
246
+ /**
247
+ * The type a CONTAINER should DISPLAY as (and be FILTERED by): its primary
248
+ * child's type, since a container renders as that child (a vacuum container
249
+ * should read + filter as `vacuum`, not the structural `container`). Mirrors
250
+ * {@link effectiveOnline} — same primary-child resolution. Non-containers, and
251
+ * containers with no resolvable primary child, fall back to `device.type`.
252
+ */
253
+ export declare function effectiveType(device: DeviceItemDevice, containerChildren: readonly DeviceItemDevice[] | undefined): string;
254
+ /**
255
+ * The device whose lead ICON a row should display: a Container shows its
256
+ * primary CHILD's icon (the child's type + role drive the glyph, e.g. a vacuum
257
+ * rather than the generic container box). Mirrors {@link effectiveType}, but
258
+ * returns the resolved device so {@link deviceLeadIcon} also picks up the
259
+ * child's role. Falls back to the device itself for non-containers / no primary.
260
+ */
261
+ export declare function effectiveIconDevice(device: DeviceItemDevice, containerChildren: readonly DeviceItemDevice[] | undefined): DeviceItemDevice;
147
262
  export declare function shortStableId(stableId: string): string;
@@ -2,4 +2,8 @@ import { ReactNode } from 'react';
2
2
  import { DeviceItemProps } from './helpers';
3
3
  export type { DeviceItemDevice, DeviceItemParent, DeviceItemKind, DeviceItemVariant, DeviceItemView, DeviceItemProps, } from './helpers';
4
4
  export { deriveDeviceKind } from './helpers';
5
+ export { ChildSectionAccordion } from './child-section-accordion';
6
+ export { ContainerChildrenProvider, useContainerChildren } from './container-children-context';
7
+ export { groupChildrenByLayout } from './child-layout';
8
+ export type { GroupedChildren, ChildLayoutSection } from './child-layout';
5
9
  export declare function DeviceItem(props: DeviceItemProps): ReactNode;
@@ -6,7 +6,7 @@ export interface DeviceItemPreviewProps {
6
6
  readonly device: DeviceItemDevice;
7
7
  readonly status: 'online' | 'offline' | 'disabled';
8
8
  /**
9
- * Render gate for the snapshot eye icon (typically
9
+ * Render gate for the snapshot thumbnail (typically
10
10
  * `flags.showSnapshot && isCamera` from the orchestrator). Even when
11
11
  * `false`, the component still renders the status-pills row when
12
12
  * `showStatusPills` is on.
@@ -14,8 +14,8 @@ export interface DeviceItemPreviewProps {
14
14
  readonly enabled: boolean;
15
15
  /**
16
16
  * When true, render the battery / charging / sleeping pills next to
17
- * the snapshot eye. Driven by table view (where the Preview cell is
18
- * the natural home for status indicators); off in card view to keep
17
+ * the snapshot thumbnail. Driven by table view (where the Preview cell
18
+ * is the natural home for status indicators); off in card view to keep
19
19
  * the row narrow.
20
20
  */
21
21
  readonly showStatusPills?: boolean;
@@ -0,0 +1,9 @@
1
+ import { ReactNode } from 'react';
2
+ import { UseDeviceProxyTrpc } from '../../hooks/use-device-proxy';
3
+ export interface RebootQuickActionProps {
4
+ readonly trpc: UseDeviceProxyTrpc;
5
+ readonly deviceId: number;
6
+ /** Device display name — woven into the confirm-modal body. */
7
+ readonly deviceName: string;
8
+ }
9
+ export declare function RebootQuickAction({ trpc, deviceId, deviceName }: RebootQuickActionProps): ReactNode;
@@ -1,5 +1,8 @@
1
1
  import { ReactNode } from 'react';
2
2
  export interface StatusDotProps {
3
3
  readonly status: 'online' | 'offline' | 'disabled';
4
+ /** Ms epoch of the last online→offline transition. Only used to enrich
5
+ * the tooltip when `status === 'offline'`. */
6
+ readonly lastChangedAt?: number | null;
4
7
  }
5
- export declare function StatusDot({ status }: StatusDotProps): ReactNode;
8
+ export declare function StatusDot({ status, lastChangedAt }: StatusDotProps): ReactNode;
@@ -0,0 +1,15 @@
1
+ import { ReactNode } from 'react';
2
+ export interface DeviceBatchActionsBarProps {
3
+ readonly selectedIds: ReadonlySet<number>;
4
+ readonly onClear: () => void;
5
+ readonly remove: (input: {
6
+ deviceId: number;
7
+ }) => void;
8
+ readonly disable: (input: {
9
+ deviceId: number;
10
+ }) => void;
11
+ readonly enable: (input: {
12
+ deviceId: number;
13
+ }) => void;
14
+ }
15
+ export declare function DeviceBatchActionsBar(props: DeviceBatchActionsBarProps): ReactNode;
@@ -0,0 +1,15 @@
1
+ import { ReactNode } from 'react';
2
+ export interface DeviceBatchToolbarProps {
3
+ readonly selectedIds: ReadonlySet<number>;
4
+ readonly onClear: () => void;
5
+ readonly remove: (input: {
6
+ deviceId: number;
7
+ }) => void;
8
+ readonly disable: (input: {
9
+ deviceId: number;
10
+ }) => void;
11
+ readonly enable: (input: {
12
+ deviceId: number;
13
+ }) => void;
14
+ }
15
+ export declare function DeviceBatchToolbar({ selectedIds, onClear, remove, disable, enable, }: DeviceBatchToolbarProps): ReactNode;
@@ -0,0 +1,18 @@
1
+ import { ReactNode } from 'react';
2
+ import { UseDeviceProxyTrpc } from '../../hooks/use-device-proxy';
3
+ import { DeviceItemDevice, DeviceItemParent } from '../device-item/helpers';
4
+ export interface CardsLayoutProps {
5
+ readonly rows: readonly DeviceItemDevice[];
6
+ readonly accessoriesByParent: ReadonlyMap<number, readonly DeviceItemDevice[]>;
7
+ readonly trpc: UseDeviceProxyTrpc;
8
+ readonly resolveIntegrationIcon: ((addonId: string) => ReactNode | null) | null;
9
+ readonly resolveParent: ((parentId: number) => DeviceItemParent | null) | null;
10
+ readonly onNavigate: ((deviceId: number) => void) | null;
11
+ readonly renderRowAction: ((device: DeviceItemDevice) => ReactNode) | null;
12
+ readonly selectable: boolean;
13
+ /** Batch mode active — a row click toggles selection instead of navigating. */
14
+ readonly rowClickSelects: boolean;
15
+ readonly activeSelected: ReadonlySet<number>;
16
+ readonly onToggleSelection: (id: number) => void;
17
+ }
18
+ export declare function CardsLayout({ rows, accessoriesByParent, trpc, resolveIntegrationIcon, resolveParent, onNavigate, renderRowAction, selectable, rowClickSelects, activeSelected, onToggleSelection, }: CardsLayoutProps): ReactNode;