@camstack/ui-library 1.2.290 → 1.2.291
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.
|
@@ -70,5 +70,13 @@ export interface DevicePickerProps<Id extends PickerId = number> {
|
|
|
70
70
|
* surface whose whole job is the list — the rule's Devices tab (R15).
|
|
71
71
|
*/
|
|
72
72
|
readonly showAll?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Devices that are ALREADY part of the thing being edited (linked to the
|
|
75
|
+
* camera, assigned to the room): drawn checked and disabled with
|
|
76
|
+
* `lockedNote`, never part of `value`, never emitted by `onChange`.
|
|
77
|
+
*/
|
|
78
|
+
readonly lockedIds?: readonly Id[];
|
|
79
|
+
/** The line under a locked device. Default "Already added". */
|
|
80
|
+
readonly lockedNote?: string;
|
|
73
81
|
}
|
|
74
|
-
export declare function DevicePicker<Id extends PickerId = number>({ devices: devicesIn, listState, fleet, value, onChange, emptyMeansAll, savedHadDevices, noun, groupTitle, emptyLabel, annotate, disabled, testId, showAll }: DevicePickerProps<Id>): JSX.Element;
|
|
82
|
+
export declare function DevicePicker<Id extends PickerId = number>({ devices: devicesIn, listState, fleet, value, onChange, emptyMeansAll, savedHadDevices, noun, groupTitle, emptyLabel, annotate, disabled, testId, showAll, lockedIds, lockedNote }: DevicePickerProps<Id>): JSX.Element;
|
|
@@ -17,6 +17,12 @@ type OptionSelectSharedProps = {
|
|
|
17
17
|
};
|
|
18
18
|
export type OptionSelectSingleProps = OptionSelectSharedProps & {
|
|
19
19
|
readonly mode: "single";
|
|
20
|
+
/**
|
|
21
|
+
* `chip` (default): the choice is a removable chip above the field.
|
|
22
|
+
* `field`: the field itself shows the current choice (placeholder-style, so
|
|
23
|
+
* typing searches), no chip and no clear — for a choice that always exists.
|
|
24
|
+
*/
|
|
25
|
+
readonly singleDisplay?: "chip" | "field";
|
|
20
26
|
readonly value: string;
|
|
21
27
|
readonly onChange: (next: string) => void;
|
|
22
28
|
};
|
package/dist/index.cjs
CHANGED
|
@@ -13614,6 +13614,7 @@ function OptionSelect(props) {
|
|
|
13614
13614
|
}, opt.value);
|
|
13615
13615
|
}) });
|
|
13616
13616
|
const footerAddNewIndex = dropdownRows.length;
|
|
13617
|
+
const fieldShowsChoice = mode === "single" && props.singleDisplay === "field" && selectedOptions.length > 0;
|
|
13617
13618
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
13618
13619
|
className: "min-w-0 space-y-1",
|
|
13619
13620
|
"data-testid": testId,
|
|
@@ -13632,7 +13633,7 @@ function OptionSelect(props) {
|
|
|
13632
13633
|
...optionTestIdPrefix !== void 0 ? { testId: `${optionTestIdPrefix}-chip-${opt.value}` } : {}
|
|
13633
13634
|
}, opt.value))
|
|
13634
13635
|
}),
|
|
13635
|
-
mode === "single" && selectedOptions.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
13636
|
+
mode === "single" && props.singleDisplay !== "field" && selectedOptions.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
13636
13637
|
className: "flex flex-wrap gap-1 mb-1.5",
|
|
13637
13638
|
children: selectedOptions.map((opt) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectedChip, {
|
|
13638
13639
|
label: opt.label,
|
|
@@ -13655,8 +13656,8 @@ function OptionSelect(props) {
|
|
|
13655
13656
|
"aria-expanded": open,
|
|
13656
13657
|
"aria-controls": listboxId,
|
|
13657
13658
|
className: "min-w-0 flex-1 bg-transparent text-xs text-foreground outline-none placeholder:text-foreground-disabled",
|
|
13658
|
-
value: query,
|
|
13659
|
-
placeholder: triggerPlaceholder,
|
|
13659
|
+
value: fieldShowsChoice && !open && query === "" ? selectedOptions[0]?.label ?? "" : query,
|
|
13660
|
+
placeholder: fieldShowsChoice ? selectedOptions[0]?.label ?? "" : triggerPlaceholder,
|
|
13660
13661
|
disabled: isDisabled,
|
|
13661
13662
|
onChange: (e) => {
|
|
13662
13663
|
setQuery(e.target.value);
|
|
@@ -60755,12 +60756,14 @@ function noItemsSentence(noun, emptyMeansAll) {
|
|
|
60755
60756
|
if (noun === "camera" || noun === "device") return "No cameras available — the rule applies to all devices.";
|
|
60756
60757
|
return `No ${plural} available — the rule applies to every ${noun}.`;
|
|
60757
60758
|
}
|
|
60758
|
-
function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, emptyMeansAll = false, savedHadDevices = false, noun = "camera", groupTitle = deviceGroupTitle, emptyLabel, annotate, disabled = false, testId, showAll = false }) {
|
|
60759
|
+
function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, emptyMeansAll = false, savedHadDevices = false, noun = "camera", groupTitle = deviceGroupTitle, emptyLabel, annotate, disabled = false, testId, showAll = false, lockedIds, lockedNote = "Already added" }) {
|
|
60759
60760
|
const devices = (0, react$1.useMemo)(() => withResolvedParentDetail(devicesIn), [devicesIn]);
|
|
60760
60761
|
const judge = fleet ?? {
|
|
60761
60762
|
state: listState,
|
|
60762
60763
|
devices: listState === "answered" ? devices : void 0
|
|
60763
60764
|
};
|
|
60765
|
+
const locked = (0, react$1.useMemo)(() => new Set(lockedIds ?? []), [lockedIds]);
|
|
60766
|
+
const emit = (next) => onChange(locked.size === 0 ? next : next.filter((id) => !locked.has(id)));
|
|
60764
60767
|
const [open, setOpen] = (0, react$1.useState)(false);
|
|
60765
60768
|
const [chipsExpanded, setChipsExpanded] = (0, react$1.useState)(false);
|
|
60766
60769
|
const [hadDevices, setHadDevices] = (0, react$1.useState)(savedHadDevices || value.length > 0);
|
|
@@ -60824,7 +60827,9 @@ function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, e
|
|
|
60824
60827
|
listOpen && listState === "answered" && (isDeviceNoun(noun) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupedPickerPanel, {
|
|
60825
60828
|
devices,
|
|
60826
60829
|
value,
|
|
60827
|
-
onChange,
|
|
60830
|
+
onChange: emit,
|
|
60831
|
+
locked,
|
|
60832
|
+
lockedNote,
|
|
60828
60833
|
annotate,
|
|
60829
60834
|
groupTitle,
|
|
60830
60835
|
noun,
|
|
@@ -60833,7 +60838,9 @@ function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, e
|
|
|
60833
60838
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PickerPanel, {
|
|
60834
60839
|
devices,
|
|
60835
60840
|
value,
|
|
60836
|
-
onChange,
|
|
60841
|
+
onChange: emit,
|
|
60842
|
+
locked,
|
|
60843
|
+
lockedNote,
|
|
60837
60844
|
annotate,
|
|
60838
60845
|
groupTitle,
|
|
60839
60846
|
noun,
|
|
@@ -60873,7 +60880,7 @@ var NO_ZONES = {
|
|
|
60873
60880
|
zones: []
|
|
60874
60881
|
};
|
|
60875
60882
|
/** The searchable, grouped list — rendered one page at a time. */
|
|
60876
|
-
function PickerPanel({ devices, value, onChange, annotate, groupTitle, noun, disabled, showAll, grouping }) {
|
|
60883
|
+
function PickerPanel({ devices, value, onChange, annotate, groupTitle, noun, disabled, locked, lockedNote, showAll, grouping }) {
|
|
60877
60884
|
const [filters, setFilters] = (0, react$1.useState)(PICKER_NO_FILTERS);
|
|
60878
60885
|
const [toggles, setToggles] = (0, react$1.useState)(/* @__PURE__ */ new Map());
|
|
60879
60886
|
const [shown, setShown] = (0, react$1.useState)(60);
|
|
@@ -60970,7 +60977,9 @@ function PickerPanel({ devices, value, onChange, annotate, groupTitle, noun, dis
|
|
|
60970
60977
|
annotate,
|
|
60971
60978
|
groupTitle,
|
|
60972
60979
|
groupBy: shownBy,
|
|
60973
|
-
disabled
|
|
60980
|
+
disabled,
|
|
60981
|
+
locked,
|
|
60982
|
+
lockedNote
|
|
60974
60983
|
}, row.key)), page.remaining > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
60975
60984
|
type: "button",
|
|
60976
60985
|
className: "mt-1 w-full rounded border border-border py-1 text-[11px] text-foreground-subtle hover:bg-surface-hover",
|
|
@@ -61003,7 +61012,7 @@ function secondLine(device, groupBy) {
|
|
|
61003
61012
|
].filter((part) => part !== null);
|
|
61004
61013
|
return parts.length > 0 ? parts.join(" · ") : void 0;
|
|
61005
61014
|
}
|
|
61006
|
-
function PickerRowView({ row, value, onChange, onToggleGroup, annotate, groupTitle, groupBy, disabled }) {
|
|
61015
|
+
function PickerRowView({ row, value, onChange, onToggleGroup, annotate, groupTitle, groupBy, disabled, locked, lockedNote }) {
|
|
61007
61016
|
if (row.kind === "header") {
|
|
61008
61017
|
const { group, expanded } = row;
|
|
61009
61018
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -61050,15 +61059,16 @@ function PickerRowView({ row, value, onChange, onToggleGroup, annotate, groupTit
|
|
|
61050
61059
|
]
|
|
61051
61060
|
});
|
|
61052
61061
|
}
|
|
61053
|
-
const
|
|
61062
|
+
const isLocked = locked.has(row.device.id);
|
|
61063
|
+
const note = isLocked ? lockedNote : annotate?.(row.device.id);
|
|
61054
61064
|
const second = secondLine(row.device, groupBy);
|
|
61055
61065
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
61056
61066
|
className: "flex cursor-pointer items-center gap-2.5 px-3 py-1 text-xs text-foreground hover:bg-surface-hover",
|
|
61057
61067
|
"data-testid": "device-picker-row",
|
|
61058
61068
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
61059
61069
|
type: "checkbox",
|
|
61060
|
-
checked: row.selected,
|
|
61061
|
-
disabled,
|
|
61070
|
+
checked: row.selected || isLocked,
|
|
61071
|
+
disabled: disabled || isLocked,
|
|
61062
61072
|
onChange: () => onChange(togglePickerDevice(value, row.device.id)),
|
|
61063
61073
|
className: "accent-primary",
|
|
61064
61074
|
"data-device-id": String(row.device.id)
|
|
@@ -61074,7 +61084,7 @@ function PickerRowView({ row, value, onChange, onToggleGroup, annotate, groupTit
|
|
|
61074
61084
|
children: second
|
|
61075
61085
|
}),
|
|
61076
61086
|
note !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
61077
|
-
className: "text-[10px] text-warning",
|
|
61087
|
+
className: isLocked ? "text-[10px] text-foreground-subtle" : "text-[10px] text-warning",
|
|
61078
61088
|
children: note
|
|
61079
61089
|
})
|
|
61080
61090
|
]
|
package/dist/index.js
CHANGED
|
@@ -13590,6 +13590,7 @@ function OptionSelect(props) {
|
|
|
13590
13590
|
}, opt.value);
|
|
13591
13591
|
}) });
|
|
13592
13592
|
const footerAddNewIndex = dropdownRows.length;
|
|
13593
|
+
const fieldShowsChoice = mode === "single" && props.singleDisplay === "field" && selectedOptions.length > 0;
|
|
13593
13594
|
return /* @__PURE__ */ jsxs("div", {
|
|
13594
13595
|
className: "min-w-0 space-y-1",
|
|
13595
13596
|
"data-testid": testId,
|
|
@@ -13608,7 +13609,7 @@ function OptionSelect(props) {
|
|
|
13608
13609
|
...optionTestIdPrefix !== void 0 ? { testId: `${optionTestIdPrefix}-chip-${opt.value}` } : {}
|
|
13609
13610
|
}, opt.value))
|
|
13610
13611
|
}),
|
|
13611
|
-
mode === "single" && selectedOptions.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
13612
|
+
mode === "single" && props.singleDisplay !== "field" && selectedOptions.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
13612
13613
|
className: "flex flex-wrap gap-1 mb-1.5",
|
|
13613
13614
|
children: selectedOptions.map((opt) => /* @__PURE__ */ jsx(SelectedChip, {
|
|
13614
13615
|
label: opt.label,
|
|
@@ -13631,8 +13632,8 @@ function OptionSelect(props) {
|
|
|
13631
13632
|
"aria-expanded": open,
|
|
13632
13633
|
"aria-controls": listboxId,
|
|
13633
13634
|
className: "min-w-0 flex-1 bg-transparent text-xs text-foreground outline-none placeholder:text-foreground-disabled",
|
|
13634
|
-
value: query,
|
|
13635
|
-
placeholder: triggerPlaceholder,
|
|
13635
|
+
value: fieldShowsChoice && !open && query === "" ? selectedOptions[0]?.label ?? "" : query,
|
|
13636
|
+
placeholder: fieldShowsChoice ? selectedOptions[0]?.label ?? "" : triggerPlaceholder,
|
|
13636
13637
|
disabled: isDisabled,
|
|
13637
13638
|
onChange: (e) => {
|
|
13638
13639
|
setQuery(e.target.value);
|
|
@@ -60731,12 +60732,14 @@ function noItemsSentence(noun, emptyMeansAll) {
|
|
|
60731
60732
|
if (noun === "camera" || noun === "device") return "No cameras available — the rule applies to all devices.";
|
|
60732
60733
|
return `No ${plural} available — the rule applies to every ${noun}.`;
|
|
60733
60734
|
}
|
|
60734
|
-
function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, emptyMeansAll = false, savedHadDevices = false, noun = "camera", groupTitle = deviceGroupTitle, emptyLabel, annotate, disabled = false, testId, showAll = false }) {
|
|
60735
|
+
function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, emptyMeansAll = false, savedHadDevices = false, noun = "camera", groupTitle = deviceGroupTitle, emptyLabel, annotate, disabled = false, testId, showAll = false, lockedIds, lockedNote = "Already added" }) {
|
|
60735
60736
|
const devices = useMemo(() => withResolvedParentDetail(devicesIn), [devicesIn]);
|
|
60736
60737
|
const judge = fleet ?? {
|
|
60737
60738
|
state: listState,
|
|
60738
60739
|
devices: listState === "answered" ? devices : void 0
|
|
60739
60740
|
};
|
|
60741
|
+
const locked = useMemo(() => new Set(lockedIds ?? []), [lockedIds]);
|
|
60742
|
+
const emit = (next) => onChange(locked.size === 0 ? next : next.filter((id) => !locked.has(id)));
|
|
60740
60743
|
const [open, setOpen] = useState(false);
|
|
60741
60744
|
const [chipsExpanded, setChipsExpanded] = useState(false);
|
|
60742
60745
|
const [hadDevices, setHadDevices] = useState(savedHadDevices || value.length > 0);
|
|
@@ -60800,7 +60803,9 @@ function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, e
|
|
|
60800
60803
|
listOpen && listState === "answered" && (isDeviceNoun(noun) ? /* @__PURE__ */ jsx(GroupedPickerPanel, {
|
|
60801
60804
|
devices,
|
|
60802
60805
|
value,
|
|
60803
|
-
onChange,
|
|
60806
|
+
onChange: emit,
|
|
60807
|
+
locked,
|
|
60808
|
+
lockedNote,
|
|
60804
60809
|
annotate,
|
|
60805
60810
|
groupTitle,
|
|
60806
60811
|
noun,
|
|
@@ -60809,7 +60814,9 @@ function DevicePicker({ devices: devicesIn, listState, fleet, value, onChange, e
|
|
|
60809
60814
|
}) : /* @__PURE__ */ jsx(PickerPanel, {
|
|
60810
60815
|
devices,
|
|
60811
60816
|
value,
|
|
60812
|
-
onChange,
|
|
60817
|
+
onChange: emit,
|
|
60818
|
+
locked,
|
|
60819
|
+
lockedNote,
|
|
60813
60820
|
annotate,
|
|
60814
60821
|
groupTitle,
|
|
60815
60822
|
noun,
|
|
@@ -60849,7 +60856,7 @@ var NO_ZONES = {
|
|
|
60849
60856
|
zones: []
|
|
60850
60857
|
};
|
|
60851
60858
|
/** The searchable, grouped list — rendered one page at a time. */
|
|
60852
|
-
function PickerPanel({ devices, value, onChange, annotate, groupTitle, noun, disabled, showAll, grouping }) {
|
|
60859
|
+
function PickerPanel({ devices, value, onChange, annotate, groupTitle, noun, disabled, locked, lockedNote, showAll, grouping }) {
|
|
60853
60860
|
const [filters, setFilters] = useState(PICKER_NO_FILTERS);
|
|
60854
60861
|
const [toggles, setToggles] = useState(/* @__PURE__ */ new Map());
|
|
60855
60862
|
const [shown, setShown] = useState(60);
|
|
@@ -60946,7 +60953,9 @@ function PickerPanel({ devices, value, onChange, annotate, groupTitle, noun, dis
|
|
|
60946
60953
|
annotate,
|
|
60947
60954
|
groupTitle,
|
|
60948
60955
|
groupBy: shownBy,
|
|
60949
|
-
disabled
|
|
60956
|
+
disabled,
|
|
60957
|
+
locked,
|
|
60958
|
+
lockedNote
|
|
60950
60959
|
}, row.key)), page.remaining > 0 && /* @__PURE__ */ jsxs("button", {
|
|
60951
60960
|
type: "button",
|
|
60952
60961
|
className: "mt-1 w-full rounded border border-border py-1 text-[11px] text-foreground-subtle hover:bg-surface-hover",
|
|
@@ -60979,7 +60988,7 @@ function secondLine(device, groupBy) {
|
|
|
60979
60988
|
].filter((part) => part !== null);
|
|
60980
60989
|
return parts.length > 0 ? parts.join(" · ") : void 0;
|
|
60981
60990
|
}
|
|
60982
|
-
function PickerRowView({ row, value, onChange, onToggleGroup, annotate, groupTitle, groupBy, disabled }) {
|
|
60991
|
+
function PickerRowView({ row, value, onChange, onToggleGroup, annotate, groupTitle, groupBy, disabled, locked, lockedNote }) {
|
|
60983
60992
|
if (row.kind === "header") {
|
|
60984
60993
|
const { group, expanded } = row;
|
|
60985
60994
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -61026,15 +61035,16 @@ function PickerRowView({ row, value, onChange, onToggleGroup, annotate, groupTit
|
|
|
61026
61035
|
]
|
|
61027
61036
|
});
|
|
61028
61037
|
}
|
|
61029
|
-
const
|
|
61038
|
+
const isLocked = locked.has(row.device.id);
|
|
61039
|
+
const note = isLocked ? lockedNote : annotate?.(row.device.id);
|
|
61030
61040
|
const second = secondLine(row.device, groupBy);
|
|
61031
61041
|
return /* @__PURE__ */ jsxs("label", {
|
|
61032
61042
|
className: "flex cursor-pointer items-center gap-2.5 px-3 py-1 text-xs text-foreground hover:bg-surface-hover",
|
|
61033
61043
|
"data-testid": "device-picker-row",
|
|
61034
61044
|
children: [/* @__PURE__ */ jsx("input", {
|
|
61035
61045
|
type: "checkbox",
|
|
61036
|
-
checked: row.selected,
|
|
61037
|
-
disabled,
|
|
61046
|
+
checked: row.selected || isLocked,
|
|
61047
|
+
disabled: disabled || isLocked,
|
|
61038
61048
|
onChange: () => onChange(togglePickerDevice(value, row.device.id)),
|
|
61039
61049
|
className: "accent-primary",
|
|
61040
61050
|
"data-device-id": String(row.device.id)
|
|
@@ -61050,7 +61060,7 @@ function PickerRowView({ row, value, onChange, onToggleGroup, annotate, groupTit
|
|
|
61050
61060
|
children: second
|
|
61051
61061
|
}),
|
|
61052
61062
|
note !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
61053
|
-
className: "text-[10px] text-warning",
|
|
61063
|
+
className: isLocked ? "text-[10px] text-foreground-subtle" : "text-[10px] text-warning",
|
|
61054
61064
|
children: note
|
|
61055
61065
|
})
|
|
61056
61066
|
]
|