@camstack/ui-library 0.1.57 → 0.2.0
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.
- package/dist/composites/addon-global-settings-form.d.ts +1 -1
- package/dist/composites/agent-step-editor.d.ts +1 -1
- package/dist/composites/app-shell/app-shell.d.ts +1 -1
- package/dist/composites/app-shell/sidebar-item.d.ts +1 -1
- package/dist/composites/app-shell/sidebar.d.ts +1 -1
- package/dist/composites/audio-classification-list.d.ts +1 -1
- package/dist/composites/audio-level-waveform.d.ts +1 -1
- package/dist/composites/audio-waveform.d.ts +1 -1
- package/dist/composites/battery-badge.d.ts +4 -1
- package/dist/composites/breadcrumb.d.ts +2 -1
- package/dist/composites/camera-stream-player.d.ts +94 -2
- package/dist/composites/cap-settings/AutotrackSection.d.ts +1 -1
- package/dist/composites/cap-settings/ConsumablesPanel.d.ts +2 -0
- package/dist/composites/cap-settings/CoverageTrack.d.ts +12 -0
- package/dist/composites/cap-settings/EventBucketStrip.d.ts +25 -0
- package/dist/composites/cap-settings/EventHeatmap.d.ts +10 -0
- package/dist/composites/cap-settings/MaskShapeCanvas.d.ts +1 -1
- package/dist/composites/cap-settings/MotionZonesSettings.d.ts +1 -1
- package/dist/composites/cap-settings/PrivacyMaskSettings.d.ts +1 -1
- package/dist/composites/cap-settings/PtzPanel.d.ts +1 -1
- package/dist/composites/cap-settings/RecordingPanel.d.ts +2 -0
- package/dist/composites/cap-settings/RecordingRulesEditor.d.ts +7 -0
- package/dist/composites/cap-settings/RecordingSettings.d.ts +8 -0
- package/dist/composites/cap-settings/RecordingTimeline.d.ts +5 -0
- package/dist/composites/cap-settings/ScheduleBandsEditor.d.ts +7 -0
- package/dist/composites/cap-settings/TimelineControls.d.ts +21 -0
- package/dist/composites/cap-settings/event-thumb.d.ts +11 -0
- package/dist/composites/cap-settings/index.d.ts +4 -0
- package/dist/composites/cap-settings/recording-config-form.d.ts +34 -0
- package/dist/composites/cap-settings/recording-spans.d.ts +22 -0
- package/dist/composites/cap-settings/recording-timeline-model.d.ts +84 -0
- package/dist/composites/code-block.d.ts +1 -1
- package/dist/composites/config-form-builder.d.ts +1 -1
- package/dist/composites/config-form-field.d.ts +3 -3
- package/dist/composites/confirm-action-button.d.ts +1 -1
- package/dist/composites/confirm-dialog.d.ts +1 -1
- package/dist/composites/data-table/data-table-header.d.ts +1 -1
- package/dist/composites/data-table/data-table-pagination.d.ts +1 -1
- package/dist/composites/data-table/data-table-row.d.ts +1 -1
- package/dist/composites/data-table/data-table.d.ts +1 -1
- package/dist/composites/data-table.d.ts +1 -1
- package/dist/composites/detection-canvas.d.ts +1 -1
- package/dist/composites/detection-overlay.d.ts +1 -1
- package/dist/composites/detection-result-tree.d.ts +1 -1
- package/dist/composites/dev-shell.d.ts +1 -1
- package/dist/composites/device-activity-panel.d.ts +5 -1
- package/dist/composites/device-card.d.ts +1 -1
- package/dist/composites/device-controls/alarm-hero-card.d.ts +24 -0
- package/dist/composites/device-controls/atoms.d.ts +81 -0
- package/dist/composites/device-controls/brightness-panel.d.ts +3 -0
- package/dist/composites/device-controls/button-control.d.ts +18 -0
- package/dist/composites/device-controls/climate-panel.d.ts +16 -0
- package/dist/composites/device-controls/container-primary-hero.d.ts +24 -0
- package/dist/composites/device-controls/container-primary.d.ts +46 -0
- package/dist/composites/device-controls/control-panel.d.ts +10 -0
- package/dist/composites/device-controls/control-registry.d.ts +74 -0
- package/dist/composites/device-controls/cover-hero-card.d.ts +27 -0
- package/dist/composites/device-controls/cover-inline.d.ts +27 -0
- package/dist/composites/device-controls/cover-panel.d.ts +3 -0
- package/dist/composites/device-controls/dummy-hero-card.d.ts +6 -0
- package/dist/composites/device-controls/fan-hero-card.d.ts +21 -0
- package/dist/composites/device-controls/fan-panel.d.ts +3 -0
- package/dist/composites/device-controls/humidifier-control.d.ts +37 -0
- package/dist/composites/device-controls/image-control.d.ts +24 -0
- package/dist/composites/device-controls/index.d.ts +33 -0
- package/dist/composites/device-controls/lawn-mower-control.d.ts +24 -0
- package/dist/composites/device-controls/light-hero-card.d.ts +16 -0
- package/dist/composites/device-controls/lock-hero-card.d.ts +15 -0
- package/dist/composites/device-controls/lock-panel.d.ts +3 -0
- package/dist/composites/device-controls/media-player-hero-card.d.ts +17 -0
- package/dist/composites/device-controls/media-player-panel.d.ts +9 -0
- package/dist/composites/device-controls/offline-badge.d.ts +11 -0
- package/dist/composites/device-controls/panel-controls.d.ts +17 -0
- package/dist/composites/device-controls/popover-row-action.d.ts +9 -0
- package/dist/composites/device-controls/primary-child.d.ts +18 -0
- package/dist/composites/device-controls/radial-gauge.d.ts +20 -0
- package/dist/composites/device-controls/sensor-hero-card.d.ts +10 -0
- package/dist/composites/device-controls/sensor-inline-control.d.ts +11 -0
- package/dist/composites/device-controls/sensor-value-atom.d.ts +106 -0
- package/dist/composites/device-controls/switch-hero-card.d.ts +13 -0
- package/dist/composites/device-controls/switch-panel.d.ts +3 -0
- package/dist/composites/device-controls/tap-toggle.d.ts +17 -0
- package/dist/composites/device-controls/thermostat-hero-card.d.ts +17 -0
- package/dist/composites/device-controls/types.d.ts +17 -0
- package/dist/composites/device-controls/update-control.d.ts +11 -0
- package/dist/composites/device-controls/vacuum-control.d.ts +36 -0
- package/dist/composites/device-controls/valve-control.d.ts +46 -0
- package/dist/composites/device-controls/water-heater-control.d.ts +41 -0
- package/dist/composites/device-controls/weather-control.d.ts +41 -0
- package/dist/composites/device-export-panel.d.ts +1 -1
- package/dist/composites/device-grid.d.ts +1 -1
- package/dist/composites/device-item/actions.d.ts +12 -1
- package/dist/composites/device-item/child-layout.d.ts +32 -0
- package/dist/composites/device-item/child-section-accordion.d.ts +9 -0
- package/dist/composites/device-item/children-accordion.d.ts +1 -1
- package/dist/composites/device-item/container-children-context.d.ts +15 -0
- package/dist/composites/device-item/device-delete-action.d.ts +8 -0
- package/dist/composites/device-item/header.d.ts +8 -1
- package/dist/composites/device-item/helpers.d.ts +108 -2
- package/dist/composites/device-item/index.d.ts +4 -0
- package/dist/composites/device-item/preview.d.ts +3 -3
- package/dist/composites/device-item/reboot-quick-action.d.ts +9 -0
- package/dist/composites/device-item/status-dot.d.ts +4 -1
- package/dist/composites/device-list/batch-actions-bar.d.ts +15 -0
- package/dist/composites/device-list/batch-toolbar.d.ts +15 -0
- package/dist/composites/device-list/cards-layout.d.ts +18 -0
- package/dist/composites/device-list/columns.d.ts +68 -0
- package/dist/composites/device-list/device-mode.d.ts +115 -0
- package/dist/composites/device-list/filter-chips.d.ts +15 -6
- package/dist/composites/device-list/fuzzy-match.d.ts +27 -0
- package/dist/composites/device-list/generic-mode.d.ts +81 -0
- package/dist/composites/device-list/group.d.ts +19 -0
- package/dist/composites/device-list/hardware-cell.d.ts +7 -0
- package/dist/composites/device-list/hardware.d.ts +26 -0
- package/dist/composites/device-list/icon-action.d.ts +17 -0
- package/dist/composites/device-list/index.d.ts +23 -39
- package/dist/composites/device-list/multi-select.d.ts +22 -0
- package/dist/composites/device-list/sort.d.ts +18 -0
- package/dist/composites/device-list/sortable-header.d.ts +10 -0
- package/dist/composites/device-list/table-layout.d.ts +25 -0
- package/dist/composites/device-list/type-filter.d.ts +19 -0
- package/dist/composites/device-list/url-state.d.ts +7 -2
- package/dist/composites/device-meta.d.ts +38 -0
- package/dist/composites/discovery-panel.d.ts +4 -4
- package/dist/composites/doorbell-recent-panel.d.ts +1 -1
- package/dist/composites/empty-state.d.ts +1 -1
- package/dist/composites/error-box.d.ts +1 -1
- package/dist/composites/event-stream.d.ts +2 -2
- package/dist/composites/filter-bar.d.ts +1 -1
- package/dist/composites/form-field.d.ts +1 -1
- package/dist/composites/hls-quality.d.ts +35 -0
- package/dist/composites/hls-video.d.ts +26 -0
- package/dist/composites/hover-zoom-image.d.ts +18 -0
- package/dist/composites/image-selector.d.ts +1 -1
- package/dist/composites/index.d.ts +15 -4
- package/dist/composites/inference-config-selector.d.ts +1 -1
- package/dist/composites/kebab-menu.d.ts +1 -1
- package/dist/composites/key-value-list.d.ts +1 -1
- package/dist/composites/log-stream-scroll.d.ts +32 -0
- package/dist/composites/log-stream.d.ts +9 -2
- package/dist/composites/login-form.d.ts +1 -1
- package/dist/composites/node-picker.d.ts +1 -1
- package/dist/composites/page-header.d.ts +1 -1
- package/dist/composites/phase-icon.d.ts +1 -1
- package/dist/composites/pipeline-builder.d.ts +1 -1
- package/dist/composites/pipeline-runtime-selector.d.ts +1 -1
- package/dist/composites/pipeline-step.d.ts +3 -3
- package/dist/composites/pipeline-tree-matrix.d.ts +1 -1
- package/dist/composites/provider-badge.d.ts +1 -1
- package/dist/composites/ptz-overlay.d.ts +1 -1
- package/dist/composites/qr-code.d.ts +1 -1
- package/dist/composites/response-log.d.ts +1 -1
- package/dist/composites/scope-picker.d.ts +1 -1
- package/dist/composites/slide-over-panel.d.ts +1 -1
- package/dist/composites/snapshot-button.d.ts +1 -1
- package/dist/composites/stat-card.d.ts +1 -1
- package/dist/composites/state-values-stream.d.ts +1 -1
- package/dist/composites/status-badge.d.ts +1 -1
- package/dist/composites/step-timings.d.ts +1 -1
- package/dist/composites/step-tree-master.d.ts +1 -1
- package/dist/composites/stream-panel.d.ts +45 -10
- package/dist/composites/timezone-selector.d.ts +18 -0
- package/dist/composites/version-badge.d.ts +2 -2
- package/dist/composites/widget-panel.d.ts +28 -0
- package/dist/contexts/custom-field-renderers.d.ts +1 -1
- package/dist/contexts/device-context.d.ts +1 -1
- package/dist/contexts/player-overlays.d.ts +1 -1
- package/dist/contexts/system-context.d.ts +1 -1
- package/dist/contexts/vod-playback.d.ts +17 -0
- package/dist/contexts/widget-registry.d.ts +1 -1
- package/dist/contexts/zone-editing.d.ts +1 -1
- package/dist/generated/system-hooks.d.ts +386 -56
- package/dist/hooks/index.d.ts +8 -2
- package/dist/hooks/use-device-cap-slice.d.ts +19 -0
- package/dist/hooks/use-device-capability.d.ts +12 -0
- package/dist/hooks/use-device-list-page-size.d.ts +14 -0
- package/dist/hooks/use-device-webrtc.d.ts +36 -4
- package/dist/hooks/use-optimistic-slice.d.ts +11 -0
- package/dist/index.cjs +54834 -11195
- package/dist/index.d.ts +1 -0
- package/dist/index.js +54483 -11136
- package/dist/lib/format-control-datetime.d.ts +18 -0
- package/dist/lib/format-last-seen.d.ts +12 -0
- package/dist/lib/format-numeric.d.ts +9 -0
- package/dist/lib/index.d.ts +3 -0
- package/dist/primitives/bottom-sheet.d.ts +1 -1
- package/dist/primitives/collapsible-card.d.ts +1 -1
- package/dist/primitives/dialog.d.ts +19 -6
- package/dist/primitives/dropdown.d.ts +3 -3
- package/dist/primitives/floating-panel.d.ts +1 -1
- package/dist/primitives/mobile-drawer.d.ts +1 -1
- package/dist/primitives/popover.d.ts +3 -3
- package/dist/primitives/tabs.d.ts +4 -4
- package/dist/primitives/tooltip.d.ts +11 -5
- package/dist/theme/index.cjs +0 -3
- package/dist/theme/index.js +0 -2
- package/dist/theme/theme-provider.d.ts +1 -1
- package/package.json +13 -5
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/theme/index.cjs.map +0 -1
- package/dist/theme/index.js.map +0 -1
|
@@ -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
|
|
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
|
|
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>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { UseDeviceProxyTrpc } from '../../hooks/use-device-proxy';
|
|
3
|
+
import { DeviceItemDevice, DeviceItemParent } from '../device-item/helpers';
|
|
4
|
+
import { DeviceListView } from './url-state';
|
|
5
|
+
import { TableContext } from './columns';
|
|
6
|
+
import { MultiSelectOption } from './multi-select';
|
|
7
|
+
export interface DeviceListAddonOption {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly icon?: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
export interface DeviceListDeviceProps {
|
|
13
|
+
/** Discriminant — omit (or 'devices') for the default registry-device list. */
|
|
14
|
+
readonly mode?: 'devices';
|
|
15
|
+
/**
|
|
16
|
+
* Where this table is rendered — selects the visible column subset (table
|
|
17
|
+
* view only). Defaults to `'devices'` (all columns) so every existing
|
|
18
|
+
* call site renders unchanged.
|
|
19
|
+
*/
|
|
20
|
+
readonly context?: TableContext;
|
|
21
|
+
readonly devices: readonly DeviceItemDevice[];
|
|
22
|
+
readonly trpc: UseDeviceProxyTrpc;
|
|
23
|
+
readonly defaultView?: DeviceListView;
|
|
24
|
+
/**
|
|
25
|
+
* Integration filter options + an optional TYPE-filter override.
|
|
26
|
+
*
|
|
27
|
+
* The type filter defaults to the EXHAUSTIVE device-type set
|
|
28
|
+
* (`allDeviceTypeFilterOptions`), so a newly added `DeviceType` appears
|
|
29
|
+
* automatically on every general list and nothing is ever missing. A
|
|
30
|
+
* surface that lists only a known subset (e.g. the export panel restricts
|
|
31
|
+
* to an exporter's supported kinds) may pass `typeOptions` to narrow the
|
|
32
|
+
* dropdown to that subset — built from the same shared meta so labels +
|
|
33
|
+
* icons stay identical.
|
|
34
|
+
*/
|
|
35
|
+
readonly filters?: {
|
|
36
|
+
readonly addons?: readonly DeviceListAddonOption[];
|
|
37
|
+
readonly typeOptions?: readonly MultiSelectOption[];
|
|
38
|
+
};
|
|
39
|
+
/** Pre-applied addon filter — hides the addon-chip group. */
|
|
40
|
+
readonly forceAddon?: string;
|
|
41
|
+
readonly resolveIntegrationIcon?: (addonId: string) => ReactNode | null;
|
|
42
|
+
readonly resolveParent?: (parentId: number) => DeviceItemParent | null;
|
|
43
|
+
readonly onNavigate?: (deviceId: number) => void;
|
|
44
|
+
readonly pageSize?: {
|
|
45
|
+
readonly cards: number;
|
|
46
|
+
readonly table: number;
|
|
47
|
+
};
|
|
48
|
+
readonly urlScope?: 'root' | 'nested';
|
|
49
|
+
/** LocalStorage key prefix. Default 'devices' for root scope, 'integrations:nested' for nested. */
|
|
50
|
+
readonly lsKey?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Optional trailing per-row control rendered beside each row's
|
|
53
|
+
* built-in actions (both card + table views, top-level rows only —
|
|
54
|
+
* accessory sub-rows are excluded). Threaded straight through to
|
|
55
|
+
* `<DeviceItem renderRowAction>`. Omitted ⇒ every current call site
|
|
56
|
+
* (Devices, IntegrationDetail, Integrations nested) renders unchanged.
|
|
57
|
+
* Used by `<DeviceExportPanel>` to surface a per-device Expose toggle.
|
|
58
|
+
*/
|
|
59
|
+
readonly renderRowAction?: (device: DeviceItemDevice) => ReactNode;
|
|
60
|
+
/**
|
|
61
|
+
* Built-in confirmed per-row DELETE — a first-class TABLE feature. When
|
|
62
|
+
* provided, every top-level table row renders a red trash button in the
|
|
63
|
+
* merged actions cell; clicking it opens the shared centered confirm
|
|
64
|
+
* dialog ("Delete device?") and calls `onDeleteDevice(device)` only on
|
|
65
|
+
* confirm. Accessory sub-rows are excluded (same gate as `renderRowAction`).
|
|
66
|
+
* Context gating is "consumer passes the handler or not" — omit it (default)
|
|
67
|
+
* and no trash renders, so existing call sites are unchanged. Cap-agnostic:
|
|
68
|
+
* delete is generic and never branches on the device's capabilities. The
|
|
69
|
+
* confirm is built in, so consumers must NOT add their own. Table-only for
|
|
70
|
+
* now (card delete is a follow-up).
|
|
71
|
+
*/
|
|
72
|
+
readonly onDeleteDevice?: (device: DeviceItemDevice) => void;
|
|
73
|
+
/**
|
|
74
|
+
* When `true`, renders a leading checkbox on each top-level row
|
|
75
|
+
* (accessory sub-rows are excluded — same gate as `renderRowAction`).
|
|
76
|
+
* Selection state may be controlled via `selectedIds`; when omitted
|
|
77
|
+
* the list maintains internal state.
|
|
78
|
+
*/
|
|
79
|
+
readonly selectable?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Controlled set of selected device ids. Provide together with
|
|
82
|
+
* `onSelectionChange` for a fully controlled selection surface.
|
|
83
|
+
* Omit to let `<DeviceList>` manage internal state.
|
|
84
|
+
*/
|
|
85
|
+
readonly selectedIds?: ReadonlySet<number>;
|
|
86
|
+
/**
|
|
87
|
+
* Called on every toggle with the NEW full selection set.
|
|
88
|
+
* Invoked for both controlled and uncontrolled usage.
|
|
89
|
+
*/
|
|
90
|
+
readonly onSelectionChange?: (ids: ReadonlySet<number>) => void;
|
|
91
|
+
/**
|
|
92
|
+
* Opt into self-managed BATCH MODE. When provided, the list renders an
|
|
93
|
+
* "Select" activation button; batch mode is OFF by default (no
|
|
94
|
+
* checkboxes, no bulk toolbar). Activating it reveals the leading
|
|
95
|
+
* row checkboxes plus a responsive bulk-actions bar wired to these
|
|
96
|
+
* callbacks (full toolbar at `sm+`, a compact menu at L1). Selection
|
|
97
|
+
* state is managed internally for the duration of the activated
|
|
98
|
+
* session and cleared on exit. Mutually independent from the
|
|
99
|
+
* lower-level `selectable`/`selectedIds` controlled API.
|
|
100
|
+
*/
|
|
101
|
+
readonly batchActions?: DeviceListBatchActions;
|
|
102
|
+
}
|
|
103
|
+
/** Self-managed batch-mode bulk-action callbacks (see `batchActions`). */
|
|
104
|
+
export interface DeviceListBatchActions {
|
|
105
|
+
readonly remove: (input: {
|
|
106
|
+
deviceId: number;
|
|
107
|
+
}) => void;
|
|
108
|
+
readonly disable: (input: {
|
|
109
|
+
deviceId: number;
|
|
110
|
+
}) => void;
|
|
111
|
+
readonly enable: (input: {
|
|
112
|
+
deviceId: number;
|
|
113
|
+
}) => void;
|
|
114
|
+
}
|
|
115
|
+
export declare function DeviceModeList(props: DeviceListDeviceProps): ReactNode;
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
import { DeviceListView } from './url-state';
|
|
3
|
+
import { MultiSelectOption } from './multi-select';
|
|
3
4
|
export interface FilterChipsAddonOption {
|
|
4
5
|
readonly id: string;
|
|
5
6
|
readonly name: string;
|
|
6
7
|
readonly icon?: ReactNode;
|
|
7
8
|
}
|
|
8
9
|
export interface FilterChipsProps {
|
|
9
|
-
readonly view
|
|
10
|
-
readonly onViewChange
|
|
11
|
-
|
|
12
|
-
readonly
|
|
13
|
-
|
|
10
|
+
readonly view?: DeviceListView;
|
|
11
|
+
readonly onViewChange?: (view: DeviceListView) => void;
|
|
12
|
+
/** Hide the cards/table view toggle (generic lists have a single view). */
|
|
13
|
+
readonly hideView?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Ready-made options for the shared multi-select type filter. Built once by
|
|
16
|
+
* the caller from the exhaustive device-type meta (`allDeviceTypeFilterOptions`)
|
|
17
|
+
* so the option set is complete + identical everywhere. Omit to hide the
|
|
18
|
+
* type filter entirely.
|
|
19
|
+
*/
|
|
20
|
+
readonly typeOptions?: readonly MultiSelectOption[];
|
|
21
|
+
readonly selectedTypes: readonly string[];
|
|
22
|
+
readonly onTypesChange: (types: readonly string[]) => void;
|
|
14
23
|
readonly addons?: readonly FilterChipsAddonOption[];
|
|
15
24
|
/** Currently-selected integration ids (multi-select). */
|
|
16
25
|
readonly selectedAddons: readonly string[];
|
|
@@ -18,4 +27,4 @@ export interface FilterChipsProps {
|
|
|
18
27
|
/** When true, the addon group is hidden (used by Integrations expand-row inner DeviceList). */
|
|
19
28
|
readonly hideAddons?: boolean;
|
|
20
29
|
}
|
|
21
|
-
export declare function FilterChips({ view, onViewChange,
|
|
30
|
+
export declare function FilterChips({ view, onViewChange, hideView, typeOptions, selectedTypes, onTypesChange, addons, selectedAddons, onAddonsChange, hideAddons, }: FilterChipsProps): ReactNode;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fuzzy-match — case and separator-insensitive text search helper.
|
|
3
|
+
*
|
|
4
|
+
* Normalization rule: lowercase + strip all runs of hyphens, underscores and
|
|
5
|
+
* whitespace entirely. This makes `Tapparella cucina` and `tapparella-cucina`
|
|
6
|
+
* both normalize to `tapparellacucina`, so a substring match succeeds in
|
|
7
|
+
* either direction.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Strips hyphens, underscores and whitespace runs from `s` and lowercases it.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* normalizeForSearch('Tapparella cucina') // 'tapparellacucina'
|
|
14
|
+
* normalizeForSearch('tapparella-cucina') // 'tapparellacucina'
|
|
15
|
+
* normalizeForSearch('tapparella_cucina') // 'tapparellacucina'
|
|
16
|
+
*/
|
|
17
|
+
export declare function normalizeForSearch(s: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Returns `true` when the normalized form of `target` contains the normalized
|
|
20
|
+
* form of `query`. An empty query always matches.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* fuzzyMatch('Tapparella cucina', 'tapparella-cucina') // true
|
|
24
|
+
* fuzzyMatch('tapparella cucina', 'Tapparella-Cucina') // true
|
|
25
|
+
* fuzzyMatch('xyz', 'tapparella-cucina') // false
|
|
26
|
+
*/
|
|
27
|
+
export declare function fuzzyMatch(query: string, target: string): boolean;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { LucideIcon } from 'lucide-react';
|
|
3
|
+
import { MultiSelectOption } from './multi-select';
|
|
4
|
+
/**
|
|
5
|
+
* Generic-mode props — a paginated, searchable, render-prop list that reuses
|
|
6
|
+
* the same chrome (search box + <Pagination>) as the device list but over an
|
|
7
|
+
* arbitrary item type. Used by the HA adoption modal for discovered-device
|
|
8
|
+
* candidates; selection + per-row controls live in the caller's `renderItem`.
|
|
9
|
+
*/
|
|
10
|
+
/** One column of the generic-mode TABLE rendering. */
|
|
11
|
+
export interface DeviceListColumn<T> {
|
|
12
|
+
readonly key: string;
|
|
13
|
+
readonly header: ReactNode;
|
|
14
|
+
readonly cell: (item: T) => ReactNode;
|
|
15
|
+
/** Extra classes on the `<th>` (width, responsive hiding, alignment). */
|
|
16
|
+
readonly headerClassName?: string;
|
|
17
|
+
/** Extra classes on the `<td>`. */
|
|
18
|
+
readonly cellClassName?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface DeviceListGenericProps<T> {
|
|
21
|
+
readonly mode: 'generic';
|
|
22
|
+
readonly items: readonly T[];
|
|
23
|
+
readonly getKey: (item: T) => string;
|
|
24
|
+
readonly getSearchText: (item: T) => string;
|
|
25
|
+
/**
|
|
26
|
+
* Row body. Provide EITHER `columns` (renders a table matching the device
|
|
27
|
+
* list's table styling) OR `renderItem` (a free-form row list). `columns`
|
|
28
|
+
* wins when both are set.
|
|
29
|
+
*/
|
|
30
|
+
readonly renderItem?: (item: T) => ReactNode;
|
|
31
|
+
/** Table columns — when set, the list renders as a bordered table. */
|
|
32
|
+
readonly columns?: readonly DeviceListColumn<T>[];
|
|
33
|
+
/** Full-width expansion row rendered under an item when `isExpanded` is true. */
|
|
34
|
+
readonly renderExpanded?: (item: T) => ReactNode;
|
|
35
|
+
readonly isExpanded?: (item: T) => boolean;
|
|
36
|
+
/** Per-row class (table `<tr>` or list wrapper) — e.g. selected/adopted state. */
|
|
37
|
+
readonly rowClassName?: (item: T) => string;
|
|
38
|
+
/** Click handler for a whole table row — when set, the `<tr>` becomes a
|
|
39
|
+
* clickable button (cursor + keyboard) that navigates/opens the item.
|
|
40
|
+
* Per-cell interactive controls must `stopPropagation` to avoid double-firing. */
|
|
41
|
+
readonly onRowClick?: (item: T) => void;
|
|
42
|
+
/**
|
|
43
|
+
* Optional type-filter projection. When provided, the list renders a compact
|
|
44
|
+
* MULTI-SELECT dropdown over the DISTINCT values present in `items` and keeps
|
|
45
|
+
* items matching ANY selected value (empty selection = no filter). Return
|
|
46
|
+
* `null` for items that carry no type. Omit to hide the filter entirely.
|
|
47
|
+
*/
|
|
48
|
+
readonly getFilterType?: (item: T) => string | null;
|
|
49
|
+
/**
|
|
50
|
+
* Enrich a raw filter value into a display `{ label, icon }` for the
|
|
51
|
+
* multi-select (e.g. via the device-type meta map). Defaults to the raw
|
|
52
|
+
* value as label, no icon. Ignored when `filterOptions` is supplied.
|
|
53
|
+
*/
|
|
54
|
+
readonly mapFilterOption?: (value: string) => {
|
|
55
|
+
readonly label: string;
|
|
56
|
+
readonly icon?: LucideIcon;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Ready-made, COMPLETE option set for the type filter. When supplied, it
|
|
60
|
+
* overrides the present-values-only set the list would otherwise derive from
|
|
61
|
+
* `items` — so e.g. the adoption modal shows every `DeviceType` (via
|
|
62
|
+
* `allDeviceTypeFilterOptions`), not just the types of the current
|
|
63
|
+
* candidates. `getFilterType` still selects each item's value for matching.
|
|
64
|
+
*/
|
|
65
|
+
readonly filterOptions?: readonly MultiSelectOption[];
|
|
66
|
+
/** Label for the type multi-select trigger. Default 'Type'. */
|
|
67
|
+
readonly filterLabel?: string;
|
|
68
|
+
readonly isLoading?: boolean;
|
|
69
|
+
readonly searchPlaceholder?: string;
|
|
70
|
+
readonly emptyLabel?: string;
|
|
71
|
+
readonly pageSize?: number;
|
|
72
|
+
/** Optional control rendered next to the search box (e.g. a Refresh button). */
|
|
73
|
+
readonly toolbar?: ReactNode;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Generic render-prop list: search + pagination chrome shared with the device
|
|
77
|
+
* list, over an arbitrary item type. Local (non-URL) state — intended for
|
|
78
|
+
* modal/ephemeral surfaces. Selection + per-row controls are the caller's, via
|
|
79
|
+
* `renderItem`.
|
|
80
|
+
*/
|
|
81
|
+
export declare function GenericModeList<T>(props: DeviceListGenericProps<T>): ReactNode;
|
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
import { DeviceItemDevice } from '../device-item/helpers';
|
|
2
|
+
/**
|
|
3
|
+
* countableDevices — the subset of `devices` that should be counted in
|
|
4
|
+
* the device-list footer "Showing N of **M** devices".
|
|
5
|
+
*
|
|
6
|
+
* A device is countable when:
|
|
7
|
+
* - it has no parent (`parentDeviceId === null`), OR
|
|
8
|
+
* - its parent's type is a hub-like owner (`hub` / `nvr`) — an NVR's
|
|
9
|
+
* cameras and a hub's children are real, individually-operable devices.
|
|
10
|
+
*
|
|
11
|
+
* Excluded:
|
|
12
|
+
* - devices whose parent has type `container` — a Container is a grouping
|
|
13
|
+
* shell for entity-children (e.g. a HA device's entities); the children
|
|
14
|
+
* are represented by the parent row and must NOT inflate the total.
|
|
15
|
+
* - orphan devices (parent id present but not in the input list) are
|
|
16
|
+
* treated as countable — better to over-count by one than hide them.
|
|
17
|
+
*
|
|
18
|
+
* Pure: builds a `Map<id, device>` once and then filters in a single pass.
|
|
19
|
+
*/
|
|
20
|
+
export declare function countableDevices(devices: readonly DeviceItemDevice[]): readonly DeviceItemDevice[];
|
|
2
21
|
export interface GroupedDevices {
|
|
3
22
|
readonly topLevel: readonly DeviceItemDevice[];
|
|
4
23
|
readonly accessoriesByParent: ReadonlyMap<number, readonly DeviceItemDevice[]>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { DeviceItemDevice } from '../device-item/helpers';
|
|
2
|
+
/** Coerce a single metadata value to a trimmed, non-empty display string.
|
|
3
|
+
* Strings pass through (trimmed); finite numbers + booleans coerce; every
|
|
4
|
+
* other shape (object, array, null, undefined, NaN) yields `null` so callers
|
|
5
|
+
* skip it rather than render a useless `[object Object]`. */
|
|
6
|
+
export declare function metadataValueString(value: unknown): string | null;
|
|
7
|
+
/** Read one metadata key as a safe display string (null when absent/empty). */
|
|
8
|
+
export declare function metadataString(metadata: Readonly<Record<string, unknown>> | null | undefined, key: string): string | null;
|
|
9
|
+
/** Compact cell text: `manufacturer · model`, or whichever is present, or
|
|
10
|
+
* `null` when neither is a usable string (caller renders the empty state). */
|
|
11
|
+
export declare function hardwareLabel(device: DeviceItemDevice): string | null;
|
|
12
|
+
/** A single key/value pair for the metadata tooltip dump. */
|
|
13
|
+
export interface MetadataEntry {
|
|
14
|
+
readonly key: string;
|
|
15
|
+
readonly value: string;
|
|
16
|
+
}
|
|
17
|
+
/** All string-coercible, non-empty metadata entries in insertion order — the
|
|
18
|
+
* full identity blob for the tooltip. Empty array when there's nothing to show. */
|
|
19
|
+
export declare function metadataEntries(metadata: Readonly<Record<string, unknown>> | null | undefined): readonly MetadataEntry[];
|
|
20
|
+
/** True when the device advertises a non-empty `manufacturer`. Drives the
|
|
21
|
+
* direction-independent "missing sorts last" rule in `sortRows`. */
|
|
22
|
+
export declare function hasManufacturer(device: DeviceItemDevice): boolean;
|
|
23
|
+
/** Ascending, case-insensitive comparator on `manufacturer` then `model`. The
|
|
24
|
+
* "missing manufacturer sorts last" rule is applied direction-independently by
|
|
25
|
+
* the caller (`sortRows`); this comparator only orders the values themselves. */
|
|
26
|
+
export declare function compareHardware(a: DeviceItemDevice, b: DeviceItemDevice): number;
|