@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,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,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;
@@ -105,6 +140,47 @@ export interface DeviceItemProps {
105
140
  * so every existing call site renders unchanged.
106
141
  */
107
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;
108
184
  }
109
185
  export interface VariantDefaults {
110
186
  readonly showIntegrationIcon: boolean;
@@ -117,8 +193,6 @@ export interface VariantDefaults {
117
193
  readonly showChildrenAccordion: boolean;
118
194
  }
119
195
  export declare function resolveFlags(props: DeviceItemProps): VariantDefaults;
120
- export declare const ROLE_ICONS: Readonly<Record<string, LucideIcon>>;
121
- export declare const TYPE_ICONS: Readonly<Record<string, LucideIcon>>;
122
196
  export declare function deviceLeadIcon(device: DeviceItemDevice): LucideIcon;
123
197
  /**
124
198
  * Icon for each `DeviceFeature` advertised on a device row. Used by
@@ -153,4 +227,36 @@ export declare const FEATURE_TO_CAP_NAME: Readonly<Record<string, string>>;
153
227
  export declare function maySupportSwitch(device: DeviceItemDevice): boolean;
154
228
  export declare function isAccessoryChild(device: DeviceItemDevice): boolean;
155
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;
156
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;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Device-mode column model: a fixed catalog, a per-context subset matrix,
3
+ * a responsive priority, and a priority-derived breakpoint mapping. Each
4
+ * consumer declares its `TableContext`; the table renders only the columns
5
+ * that context enables, in catalog order, hiding the optional ones as the
6
+ * viewport narrows.
7
+ *
8
+ * The catalog is intentionally device-mode only. Generic-mode (integration
9
+ * rows) keeps its own `DeviceListColumn[]` defs — reconciled in a later phase.
10
+ *
11
+ * ── Responsive convention (single source of truth) ────────────────────────
12
+ * LOWER priority number = MORE important = hidden at a NARROWER width (kept
13
+ * longest). So as the viewport shrinks the HIGHEST-priority-number column
14
+ * drops first.
15
+ *
16
+ * name (0) — never hidden; sticky left, always visible
17
+ * previewActions (1) — never hidden; carries the live control + status dot
18
+ * icon (2) — integration badge; rendered INSIDE the name cell,
19
+ * not a standalone column, so it has no breakpoint
20
+ * features (3) — optional; hidden below `md` (kept longer)
21
+ * type (4) — optional; hidden below `lg`
22
+ * manufacturer (5) — optional; hidden below `xl` (drops FIRST,
23
+ * wide-screen-only hardware/identity column)
24
+ *
25
+ * `COLUMN_BREAKPOINT_CLASS` derives ONE Tailwind visibility class per id and
26
+ * is the single source used by BOTH the `<th>` and the `<td>`, so a hidden
27
+ * column folds header + cell in lock-step. The drop order is therefore:
28
+ * `manufacturer` first (below `xl`), then `type` (below `lg`), then
29
+ * `features` (below `md`) — matching the priority numbers above.
30
+ */
31
+ /** Fixed device-mode column catalog, in render order. `name` is always first.
32
+ * `manufacturer` sits after `type` — a wide-screen-only hardware/identity
33
+ * column (manufacturer · model, full metadata in a tooltip). */
34
+ export declare const DEVICE_COLUMNS: readonly ["name", "icon", "type", "manufacturer", "features", "previewActions"];
35
+ export type DeviceColumnId = (typeof DEVICE_COLUMNS)[number];
36
+ /**
37
+ * The places a device table is rendered; each declares its column subset.
38
+ *
39
+ * Device-detail CHILDREN are deliberately NOT a table context: they render as
40
+ * a stack of `<DeviceItem variant="minimal">` cards (see `ChildrenList`), not
41
+ * through this table, so no `device-children` context exists.
42
+ */
43
+ export type TableContext = 'devices' | 'integration-detail';
44
+ export declare function columnsForContext(ctx: TableContext): readonly DeviceColumnId[];
45
+ /**
46
+ * Lower number = higher priority (kept longest as width shrinks). `name` = 0
47
+ * (sticky, never dropped); `previewActions` = 1 (always visible). The optional
48
+ * columns drop in REVERSE priority order as width shrinks — highest number
49
+ * (`type`) hides first, then `features`. `icon` is a name-cell badge, not a
50
+ * standalone column. See `COLUMN_BREAKPOINT_CLASS` for the derived classes.
51
+ */
52
+ export declare const COLUMN_PRIORITY: Record<DeviceColumnId, number>;
53
+ /**
54
+ * Priority-derived Tailwind visibility class per column. Used by BOTH the
55
+ * `<th>` and the `<td>` (single source → header + cell stay in lock-step).
56
+ *
57
+ * - `name` / `previewActions`: empty string — never hidden (always visible).
58
+ * - `icon`: empty string — rendered inside the name cell, no standalone cell.
59
+ * - `features`: `hidden md:table-cell` — visible from `md` up (kept longer).
60
+ * - `type`: `hidden lg:table-cell` — visible only from `lg` up.
61
+ * - `manufacturer`: `hidden xl:table-cell` — visible only from `xl` up (drops
62
+ * first).
63
+ *
64
+ * Drop order as the viewport narrows therefore follows the priority numbers:
65
+ * `manufacturer` (5) disappears below `xl`, then `type` (4) below `lg`, then
66
+ * `features` (3) below `md`.
67
+ */
68
+ export declare const COLUMN_BREAKPOINT_CLASS: Record<DeviceColumnId, string>;