@sproutsocial/seeds-react-tree 0.3.1 → 0.4.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/.turbo/turbo-build.log +11 -10
- package/CHANGELOG.md +36 -0
- package/dist/esm/index.js +389 -32
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +86 -1
- package/dist/index.d.ts +86 -1
- package/dist/index.js +391 -32
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/Common/TreeSearchableSelectBase.tsx +383 -0
- package/src/Common/flattenTree.ts +22 -0
- package/src/MultiTreeSearchableSelect.stories.tsx +256 -0
- package/src/MultiTreeSearchableSelect.tsx +92 -0
- package/src/SingleTreeSearchableSelect.stories.tsx +217 -0
- package/src/SingleTreeSearchableSelect.tsx +95 -0
- package/src/TreeCombobox.tsx +91 -52
- package/src/TreeStyles.tsx +3 -2
- package/src/__tests__/MultiTreeSearchableSelect.test.tsx +194 -0
- package/src/__tests__/SingleTreeSearchableSelect.test.tsx +210 -0
- package/src/index.ts +8 -0
- package/src/storyData.tsx +339 -6
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
yarn run v1.22.22
|
|
2
|
+
warning package.json: "dependencies" has dependency "@sproutsocial/seeds-react-popout" with range "^2.5.7" that collides with a dependency in "devDependencies" of the same name with version "^2.5.9"
|
|
2
3
|
$ tsup --dts
|
|
3
4
|
[34mCLI[39m Building entry: src/index.ts
|
|
4
5
|
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
@@ -8,14 +9,14 @@ $ tsup --dts
|
|
|
8
9
|
[34mCLI[39m Cleaning output folder
|
|
9
10
|
[34mCJS[39m Build start
|
|
10
11
|
[34mESM[39m Build start
|
|
11
|
-
[32mCJS[39m [1mdist/index.js [22m[
|
|
12
|
-
[32mCJS[39m [1mdist/index.js.map [22m[
|
|
13
|
-
[32mCJS[39m ⚡️ Build success in
|
|
14
|
-
[32mESM[39m [1mdist/esm/index.js [22m[
|
|
15
|
-
[32mESM[39m [1mdist/esm/index.js.map [22m[
|
|
16
|
-
[32mESM[39m ⚡️ Build success in
|
|
12
|
+
[32mCJS[39m [1mdist/index.js [22m[32m41.77 KB[39m
|
|
13
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m86.20 KB[39m
|
|
14
|
+
[32mCJS[39m ⚡️ Build success in 77ms
|
|
15
|
+
[32mESM[39m [1mdist/esm/index.js [22m[32m38.14 KB[39m
|
|
16
|
+
[32mESM[39m [1mdist/esm/index.js.map [22m[32m85.67 KB[39m
|
|
17
|
+
[32mESM[39m ⚡️ Build success in 78ms
|
|
17
18
|
[34mDTS[39m Build start
|
|
18
|
-
[32mDTS[39m ⚡️ Build success in
|
|
19
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
20
|
-
[32mDTS[39m [1mdist/index.d.mts [22m[
|
|
21
|
-
Done in
|
|
19
|
+
[32mDTS[39m ⚡️ Build success in 2451ms
|
|
20
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m7.37 KB[39m
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m7.37 KB[39m
|
|
22
|
+
Done in 3.32s.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# @sproutsocial/seeds-react-tree
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 7e1e820: Add `SingleTreeSearchableSelect` and `MultiTreeSearchableSelect` — wrap
|
|
8
|
+
`TreeCombobox` in a popout-style disclosure that matches the existing
|
|
9
|
+
`SingleSearchableSelect` / `MultiSearchableSelect` API (`selectedItemId(s)`,
|
|
10
|
+
`onSelectedItemId(s)Change`, `defaultSelectedItemId(s)`).
|
|
11
|
+
|
|
12
|
+
The trigger renders the placeholder when nothing is selected, a comma-
|
|
13
|
+
separated list of labels for up to 3 selections, and `"N selected"` beyond
|
|
14
|
+
that. Pass `renderTrigger` to fully override the trigger label content. Pass
|
|
15
|
+
`contentWidth` to pin the popout width; by default it tracks the trigger via
|
|
16
|
+
`var(--radix-popover-trigger-width)` with a viewport cap, and expands
|
|
17
|
+
horizontally for very deep trees so labels never get clipped. The popout
|
|
18
|
+
height caps at `min(var(--radix-popper-available-height), 400px)` and the
|
|
19
|
+
tree section scrolls vertically beyond that.
|
|
20
|
+
|
|
21
|
+
`SingleTreeSearchableSelect` closes the popout on selection;
|
|
22
|
+
`MultiTreeSearchableSelect` stays open so users can accumulate picks. Both
|
|
23
|
+
close on Escape (when the search query is empty), outside click, or trigger
|
|
24
|
+
toggle.
|
|
25
|
+
|
|
26
|
+
`TreeCombobox` internals were restructured to a v3-style search header
|
|
27
|
+
(padded, with a border-bottom separator) + scrollable list section so the
|
|
28
|
+
inline appearance lines up with the new popout components.
|
|
29
|
+
|
|
30
|
+
`Tree` rows and `TreeCombobox`'s search input dropped from `typography[300]`
|
|
31
|
+
to `typography[200]` to match the rest of the searchable-select family.
|
|
32
|
+
`TreeItem` per-level indentation reduced from `space[500]` (20px) to
|
|
33
|
+
`space[400]` (16px) so deeply nested trees stay readable inside a narrow
|
|
34
|
+
popout.
|
|
35
|
+
|
|
36
|
+
`@sproutsocial/seeds-react-popout` is promoted from a dev dependency to a
|
|
37
|
+
runtime dependency.
|
|
38
|
+
|
|
3
39
|
## 0.3.1
|
|
4
40
|
|
|
5
41
|
### Patch Changes
|
package/dist/esm/index.js
CHANGED
|
@@ -105,7 +105,8 @@ var TreeRoot = styled.ul`
|
|
|
105
105
|
margin: 0;
|
|
106
106
|
padding: 0;
|
|
107
107
|
font-family: ${({ theme }) => theme.fontFamily};
|
|
108
|
-
${({ theme }) => theme.typography[
|
|
108
|
+
font-size: ${({ theme }) => theme.typography[200].fontSize};
|
|
109
|
+
line-height: ${({ theme }) => theme.typography[200].lineHeight};
|
|
109
110
|
color: ${({ theme }) => theme.colors.text.body};
|
|
110
111
|
`;
|
|
111
112
|
var TreeItemRow = styled.div`
|
|
@@ -113,7 +114,7 @@ var TreeItemRow = styled.div`
|
|
|
113
114
|
align-items: center;
|
|
114
115
|
gap: ${({ theme }) => theme.space[300]};
|
|
115
116
|
padding: ${({ theme }) => theme.space[300]};
|
|
116
|
-
padding-left: ${({ theme, $level }) => `calc(${theme.space[300]} + ${$level - 1} * ${theme.space[
|
|
117
|
+
padding-left: ${({ theme, $level }) => `calc(${theme.space[300]} + ${$level - 1} * ${theme.space[400]})`};
|
|
117
118
|
border-radius: ${({ theme }) => theme.radii[400]};
|
|
118
119
|
cursor: pointer;
|
|
119
120
|
user-select: none;
|
|
@@ -683,7 +684,13 @@ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
|
683
684
|
var Root = styled2.div`
|
|
684
685
|
display: flex;
|
|
685
686
|
flex-direction: column;
|
|
686
|
-
|
|
687
|
+
flex: 1;
|
|
688
|
+
min-height: 0;
|
|
689
|
+
`;
|
|
690
|
+
var SearchSection = styled2.div`
|
|
691
|
+
padding: ${({ theme }) => theme.space[300]};
|
|
692
|
+
border-bottom: 1px solid ${({ theme }) => theme.colors.container.border.base};
|
|
693
|
+
flex-shrink: 0;
|
|
687
694
|
`;
|
|
688
695
|
var InputGroup = styled2.div`
|
|
689
696
|
display: flex;
|
|
@@ -691,8 +698,8 @@ var InputGroup = styled2.div`
|
|
|
691
698
|
gap: ${({ theme }) => theme.space[200]};
|
|
692
699
|
padding: ${({ theme }) => theme.space[200]} ${({ theme }) => theme.space[300]};
|
|
693
700
|
border-radius: ${({ theme }) => theme.radii[500]};
|
|
694
|
-
border: 1px solid ${({ theme }) => theme.colors.
|
|
695
|
-
background: ${({ theme }) => theme.colors.
|
|
701
|
+
border: 1px solid ${({ theme }) => theme.colors.container.border.base};
|
|
702
|
+
background: ${({ theme }) => theme.colors.container.background.base};
|
|
696
703
|
color: ${({ theme }) => theme.colors.icon.base};
|
|
697
704
|
|
|
698
705
|
&:focus-within {
|
|
@@ -705,18 +712,26 @@ var ComboboxInput = styled2.input`
|
|
|
705
712
|
background: transparent;
|
|
706
713
|
outline: none;
|
|
707
714
|
font-family: ${({ theme }) => theme.fontFamily};
|
|
708
|
-
${({ theme }) => theme.typography[
|
|
715
|
+
font-size: ${({ theme }) => theme.typography[200].fontSize};
|
|
716
|
+
line-height: ${({ theme }) => theme.typography[200].lineHeight};
|
|
709
717
|
color: ${({ theme }) => theme.colors.text.body};
|
|
710
718
|
|
|
711
719
|
&::placeholder {
|
|
712
720
|
color: ${({ theme }) => theme.colors.text.subtext};
|
|
713
721
|
}
|
|
714
722
|
`;
|
|
723
|
+
var ListSection = styled2.div`
|
|
724
|
+
padding: ${({ theme }) => theme.space[200]};
|
|
725
|
+
overflow: auto;
|
|
726
|
+
flex: 1;
|
|
727
|
+
min-height: 0;
|
|
728
|
+
`;
|
|
715
729
|
var EmptyState = styled2.div`
|
|
716
|
-
padding: ${({ theme }) => theme.space[
|
|
730
|
+
padding: ${({ theme }) => `${theme.space[300]} ${theme.space[350]}`};
|
|
717
731
|
text-align: center;
|
|
718
732
|
color: ${({ theme }) => theme.colors.text.subtext};
|
|
719
|
-
${({ theme }) => theme.typography[
|
|
733
|
+
font-size: ${({ theme }) => theme.typography[200].fontSize};
|
|
734
|
+
line-height: ${({ theme }) => theme.typography[200].lineHeight};
|
|
720
735
|
`;
|
|
721
736
|
var NAV_KEYS = /* @__PURE__ */ new Set([
|
|
722
737
|
"ArrowDown",
|
|
@@ -819,9 +834,17 @@ function TreeCombobox(props) {
|
|
|
819
834
|
setFocusedId(result.nextFocusedId);
|
|
820
835
|
}
|
|
821
836
|
};
|
|
837
|
+
React3.useLayoutEffect(() => {
|
|
838
|
+
if (!focusedId) return;
|
|
839
|
+
const itemEl = findTreeItemEl(focusedId);
|
|
840
|
+
const row = itemEl?.querySelector("[data-treeitem-row]") ?? itemEl;
|
|
841
|
+
if (row && typeof row.scrollIntoView === "function") {
|
|
842
|
+
row.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
843
|
+
}
|
|
844
|
+
}, [focusedId]);
|
|
822
845
|
const showEmpty = visibleItems.length === 0;
|
|
823
846
|
return /* @__PURE__ */ jsxs2(Root, { className, children: [
|
|
824
|
-
/* @__PURE__ */ jsxs2(InputGroup, { children: [
|
|
847
|
+
/* @__PURE__ */ jsx3(SearchSection, { children: /* @__PURE__ */ jsxs2(InputGroup, { children: [
|
|
825
848
|
/* @__PURE__ */ jsx3(Icon2, { name: "magnifying-glass-outline", size: "small", "aria-hidden": true }),
|
|
826
849
|
/* @__PURE__ */ jsx3(
|
|
827
850
|
ComboboxInput,
|
|
@@ -842,29 +865,31 @@ function TreeCombobox(props) {
|
|
|
842
865
|
onKeyDown: handleInputKeyDown
|
|
843
866
|
}
|
|
844
867
|
)
|
|
845
|
-
] }),
|
|
846
|
-
/* @__PURE__ */
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
+
] }) }),
|
|
869
|
+
/* @__PURE__ */ jsxs2(ListSection, { children: [
|
|
870
|
+
/* @__PURE__ */ jsx3(
|
|
871
|
+
Tree,
|
|
872
|
+
{
|
|
873
|
+
ref: treeRef,
|
|
874
|
+
"aria-label": ariaLabel,
|
|
875
|
+
"aria-labelledby": ariaLabelledBy,
|
|
876
|
+
id: treeDomId,
|
|
877
|
+
manageDomFocus: false,
|
|
878
|
+
selectionMode,
|
|
879
|
+
selectableNodes,
|
|
880
|
+
expanded: effectiveExpanded,
|
|
881
|
+
onExpandedChange: handleExpandedChange,
|
|
882
|
+
selected: selectedProp,
|
|
883
|
+
defaultSelected,
|
|
884
|
+
onSelectionChange,
|
|
885
|
+
renderSelectionIndicator,
|
|
886
|
+
focusedId,
|
|
887
|
+
onFocusedIdChange: setFocusedId,
|
|
888
|
+
children: visibleItems.map((item) => /* @__PURE__ */ jsx3(TreeNode, { item }, item.id))
|
|
889
|
+
}
|
|
890
|
+
),
|
|
891
|
+
showEmpty ? /* @__PURE__ */ jsx3(EmptyState, { role: "status", "aria-live": "polite", children: emptyText }) : null
|
|
892
|
+
] })
|
|
868
893
|
] });
|
|
869
894
|
}
|
|
870
895
|
function TreeNode({ item }) {
|
|
@@ -879,7 +904,339 @@ function TreeNode({ item }) {
|
|
|
879
904
|
}
|
|
880
905
|
);
|
|
881
906
|
}
|
|
907
|
+
|
|
908
|
+
// src/SingleTreeSearchableSelect.tsx
|
|
909
|
+
import * as React5 from "react";
|
|
910
|
+
|
|
911
|
+
// src/Common/TreeSearchableSelectBase.tsx
|
|
912
|
+
import * as React4 from "react";
|
|
913
|
+
import styled3, { css as css2 } from "styled-components";
|
|
914
|
+
import { Icon as Icon3 } from "@sproutsocial/seeds-react-icon";
|
|
915
|
+
import { focusRing as focusRing2 } from "@sproutsocial/seeds-react-mixins";
|
|
916
|
+
import { Popout } from "@sproutsocial/seeds-react-popout/v2";
|
|
917
|
+
|
|
918
|
+
// src/Common/flattenTree.ts
|
|
919
|
+
function flattenTreeItems(items) {
|
|
920
|
+
const out = [];
|
|
921
|
+
const walk = (nodes) => {
|
|
922
|
+
for (const node of nodes) {
|
|
923
|
+
out.push(node);
|
|
924
|
+
if (node.children) walk(node.children);
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
walk(items);
|
|
928
|
+
return out;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// src/Common/TreeSearchableSelectBase.tsx
|
|
932
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
933
|
+
var invalidStyles = css2`
|
|
934
|
+
border-color: ${({ theme }) => theme.colors.form.border.error};
|
|
935
|
+
`;
|
|
936
|
+
var Trigger = styled3.button`
|
|
937
|
+
display: flex;
|
|
938
|
+
align-items: center;
|
|
939
|
+
justify-content: space-between;
|
|
940
|
+
gap: ${({ theme }) => theme.space[300]};
|
|
941
|
+
padding: ${({ theme }) => theme.space[200]} ${({ theme }) => theme.space[350]};
|
|
942
|
+
min-width: 160px;
|
|
943
|
+
width: ${({ $fullWidth }) => $fullWidth ? "100%" : "auto"};
|
|
944
|
+
border-radius: ${({ theme }) => theme.radii[500]};
|
|
945
|
+
border: 1px solid ${({ theme }) => theme.colors.form.border.base};
|
|
946
|
+
background: ${({ theme }) => theme.colors.form.background.base};
|
|
947
|
+
font-family: ${({ theme }) => theme.fontFamily};
|
|
948
|
+
font-size: ${({ theme }) => theme.typography[200].fontSize};
|
|
949
|
+
line-height: ${({ theme }) => theme.typography[200].lineHeight};
|
|
950
|
+
color: ${({ theme }) => theme.colors.text.body};
|
|
951
|
+
text-align: left;
|
|
952
|
+
cursor: default;
|
|
953
|
+
outline: none;
|
|
954
|
+
user-select: none;
|
|
955
|
+
transition: border-color ${({ theme }) => theme.duration.fast}
|
|
956
|
+
${({ theme }) => theme.easing.ease_in},
|
|
957
|
+
box-shadow ${({ theme }) => theme.duration.fast}
|
|
958
|
+
${({ theme }) => theme.easing.ease_in};
|
|
959
|
+
|
|
960
|
+
&:focus-visible {
|
|
961
|
+
${focusRing2}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
&[aria-disabled="true"],
|
|
965
|
+
&:disabled {
|
|
966
|
+
opacity: 0.4;
|
|
967
|
+
cursor: not-allowed;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
${({ $isInvalid }) => $isInvalid && invalidStyles}
|
|
971
|
+
`;
|
|
972
|
+
var TriggerLabel = styled3.span`
|
|
973
|
+
flex: 1;
|
|
974
|
+
min-width: 0;
|
|
975
|
+
overflow: hidden;
|
|
976
|
+
text-overflow: ellipsis;
|
|
977
|
+
white-space: nowrap;
|
|
978
|
+
`;
|
|
979
|
+
var PlaceholderText = styled3.span`
|
|
980
|
+
color: ${({ theme }) => theme.colors.text.subtext};
|
|
981
|
+
`;
|
|
982
|
+
var Chevron = styled3.span`
|
|
983
|
+
display: inline-flex;
|
|
984
|
+
align-items: center;
|
|
985
|
+
color: ${({ theme }) => theme.colors.icon.base};
|
|
986
|
+
transition: transform ${({ theme }) => theme.duration.fast}
|
|
987
|
+
${({ theme }) => theme.easing.ease_in};
|
|
988
|
+
${({ $open }) => $open && css2`
|
|
989
|
+
transform: rotate(-180deg);
|
|
990
|
+
`}
|
|
991
|
+
`;
|
|
992
|
+
var HiddenInput = styled3.input`
|
|
993
|
+
clip-path: inset(50%);
|
|
994
|
+
overflow: hidden;
|
|
995
|
+
white-space: nowrap;
|
|
996
|
+
border: 0;
|
|
997
|
+
padding: 0;
|
|
998
|
+
width: 1px;
|
|
999
|
+
height: 1px;
|
|
1000
|
+
margin: -1px;
|
|
1001
|
+
position: fixed;
|
|
1002
|
+
top: 0;
|
|
1003
|
+
left: 0;
|
|
1004
|
+
`;
|
|
1005
|
+
var PopoutContent = styled3.div`
|
|
1006
|
+
display: flex;
|
|
1007
|
+
flex-direction: column;
|
|
1008
|
+
/* Width sizes to content within bounds: at least the trigger width, never */
|
|
1009
|
+
/* wider than the available viewport space. Shallow popups stay flush with */
|
|
1010
|
+
/* the trigger; deeply nested trees expand horizontally as needed. */
|
|
1011
|
+
min-width: var(--radix-popover-trigger-width);
|
|
1012
|
+
max-width: var(--radix-popper-available-width);
|
|
1013
|
+
/* Height caps at 400px (or available viewport) so a popup with many */
|
|
1014
|
+
/* expanded branches scrolls vertically instead of growing off-screen. */
|
|
1015
|
+
max-height: min(var(--radix-popper-available-height), 400px);
|
|
1016
|
+
width: ${({ $width }) => $width === void 0 ? "max-content" : typeof $width === "number" ? `${$width}px` : $width};
|
|
1017
|
+
`;
|
|
1018
|
+
var MAX_INLINE_LABELS = 3;
|
|
1019
|
+
function defaultRenderTrigger(selected, placeholder) {
|
|
1020
|
+
if (selected.length === 0) {
|
|
1021
|
+
return /* @__PURE__ */ jsx4(PlaceholderText, { children: placeholder });
|
|
1022
|
+
}
|
|
1023
|
+
if (selected.length <= MAX_INLINE_LABELS) {
|
|
1024
|
+
return selected.map((s) => s.label).join(", ");
|
|
1025
|
+
}
|
|
1026
|
+
return `${selected.length} selected`;
|
|
1027
|
+
}
|
|
1028
|
+
function TreeSearchableSelectBase(props) {
|
|
1029
|
+
const {
|
|
1030
|
+
items,
|
|
1031
|
+
selectionMode,
|
|
1032
|
+
selectableNodes,
|
|
1033
|
+
selectedIds,
|
|
1034
|
+
onSelectedIdsChange,
|
|
1035
|
+
placeholder = "Select...",
|
|
1036
|
+
searchPlaceholder = "Search...",
|
|
1037
|
+
emptyText = "No results found.",
|
|
1038
|
+
renderTrigger,
|
|
1039
|
+
open: openProp,
|
|
1040
|
+
defaultOpen = false,
|
|
1041
|
+
onOpenChange,
|
|
1042
|
+
closeOnSelect,
|
|
1043
|
+
disabled,
|
|
1044
|
+
isInvalid,
|
|
1045
|
+
required,
|
|
1046
|
+
readOnly,
|
|
1047
|
+
name,
|
|
1048
|
+
fullWidth = true,
|
|
1049
|
+
contentWidth,
|
|
1050
|
+
renderSelectionIndicator,
|
|
1051
|
+
defaultExpanded,
|
|
1052
|
+
expanded,
|
|
1053
|
+
onExpandedChange,
|
|
1054
|
+
id,
|
|
1055
|
+
className
|
|
1056
|
+
} = props;
|
|
1057
|
+
const ariaLabel = props["aria-label"];
|
|
1058
|
+
const ariaLabelledBy = props["aria-labelledby"];
|
|
1059
|
+
const ariaDescribedBy = props["aria-describedby"];
|
|
1060
|
+
const isOpenControlled = openProp !== void 0;
|
|
1061
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React4.useState(defaultOpen);
|
|
1062
|
+
const open = isOpenControlled ? !!openProp : uncontrolledOpen;
|
|
1063
|
+
const setOpen = (next) => {
|
|
1064
|
+
if (!isOpenControlled) setUncontrolledOpen(next);
|
|
1065
|
+
onOpenChange?.(next);
|
|
1066
|
+
};
|
|
1067
|
+
const [query, setQuery] = React4.useState("");
|
|
1068
|
+
const reactId = React4.useId();
|
|
1069
|
+
const popupId = `tree-select-popup-${reactId.replace(/:/g, "")}`;
|
|
1070
|
+
const flat = React4.useMemo(() => flattenTreeItems(items), [items]);
|
|
1071
|
+
const selectedItems = React4.useMemo(() => {
|
|
1072
|
+
const order = new Map(flat.map((it, i) => [it.id, i]));
|
|
1073
|
+
return selectedIds.map((id2) => flat.find((it) => it.id === id2)).filter((it) => it != null).sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0));
|
|
1074
|
+
}, [flat, selectedIds]);
|
|
1075
|
+
const handleSelectionChange = (next) => {
|
|
1076
|
+
onSelectedIdsChange(next);
|
|
1077
|
+
if (closeOnSelect) {
|
|
1078
|
+
setOpen(false);
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
const triggerContent = renderTrigger ? renderTrigger(selectedItems) : defaultRenderTrigger(selectedItems, placeholder);
|
|
1082
|
+
const isMulti = selectionMode === "multiple";
|
|
1083
|
+
const primaryInputName = isMulti ? void 0 : name;
|
|
1084
|
+
const primaryInputId = id && primaryInputName == null ? `${id}-hidden-input` : void 0;
|
|
1085
|
+
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
1086
|
+
/* @__PURE__ */ jsx4(
|
|
1087
|
+
HiddenInput,
|
|
1088
|
+
{
|
|
1089
|
+
type: "text",
|
|
1090
|
+
id: primaryInputId,
|
|
1091
|
+
name: primaryInputName,
|
|
1092
|
+
value: isMulti ? "" : selectedIds[0] ?? "",
|
|
1093
|
+
onChange: () => {
|
|
1094
|
+
},
|
|
1095
|
+
disabled,
|
|
1096
|
+
required: required && !isMulti,
|
|
1097
|
+
readOnly,
|
|
1098
|
+
tabIndex: -1,
|
|
1099
|
+
"aria-hidden": "true"
|
|
1100
|
+
}
|
|
1101
|
+
),
|
|
1102
|
+
isMulti && name ? selectedIds.map((selectedId) => /* @__PURE__ */ jsx4(
|
|
1103
|
+
"input",
|
|
1104
|
+
{
|
|
1105
|
+
type: "hidden",
|
|
1106
|
+
name,
|
|
1107
|
+
value: selectedId
|
|
1108
|
+
},
|
|
1109
|
+
selectedId
|
|
1110
|
+
)) : null,
|
|
1111
|
+
/* @__PURE__ */ jsx4(
|
|
1112
|
+
Popout,
|
|
1113
|
+
{
|
|
1114
|
+
open,
|
|
1115
|
+
onOpenChange: setOpen,
|
|
1116
|
+
side: "bottom",
|
|
1117
|
+
align: "start",
|
|
1118
|
+
style: { padding: 0 },
|
|
1119
|
+
onEscapeKeyDown: (event) => {
|
|
1120
|
+
if (query.length > 0) {
|
|
1121
|
+
event.preventDefault();
|
|
1122
|
+
}
|
|
1123
|
+
},
|
|
1124
|
+
content: /* @__PURE__ */ jsx4(PopoutContent, { id: popupId, $width: contentWidth, children: /* @__PURE__ */ jsx4(
|
|
1125
|
+
TreeCombobox,
|
|
1126
|
+
{
|
|
1127
|
+
"aria-label": "Search",
|
|
1128
|
+
items,
|
|
1129
|
+
placeholder: searchPlaceholder,
|
|
1130
|
+
emptyText,
|
|
1131
|
+
query,
|
|
1132
|
+
onQueryChange: setQuery,
|
|
1133
|
+
selectionMode,
|
|
1134
|
+
selectableNodes,
|
|
1135
|
+
selected: selectedIds,
|
|
1136
|
+
onSelectionChange: handleSelectionChange,
|
|
1137
|
+
renderSelectionIndicator,
|
|
1138
|
+
defaultExpanded,
|
|
1139
|
+
expanded,
|
|
1140
|
+
onExpandedChange
|
|
1141
|
+
}
|
|
1142
|
+
) }),
|
|
1143
|
+
children: /* @__PURE__ */ jsxs3(
|
|
1144
|
+
Trigger,
|
|
1145
|
+
{
|
|
1146
|
+
type: "button",
|
|
1147
|
+
id,
|
|
1148
|
+
className,
|
|
1149
|
+
$isInvalid: isInvalid,
|
|
1150
|
+
$fullWidth: fullWidth,
|
|
1151
|
+
disabled,
|
|
1152
|
+
role: "combobox",
|
|
1153
|
+
"aria-haspopup": "dialog",
|
|
1154
|
+
"aria-expanded": open,
|
|
1155
|
+
"aria-controls": open ? popupId : void 0,
|
|
1156
|
+
"aria-label": ariaLabel,
|
|
1157
|
+
"aria-labelledby": ariaLabelledBy,
|
|
1158
|
+
"aria-describedby": ariaDescribedBy,
|
|
1159
|
+
"aria-invalid": isInvalid || void 0,
|
|
1160
|
+
"aria-required": required || void 0,
|
|
1161
|
+
children: [
|
|
1162
|
+
/* @__PURE__ */ jsx4(TriggerLabel, { children: triggerContent }),
|
|
1163
|
+
/* @__PURE__ */ jsx4(Chevron, { $open: open, "aria-hidden": true, children: /* @__PURE__ */ jsx4(Icon3, { name: "chevron-down-outline", size: "small" }) })
|
|
1164
|
+
]
|
|
1165
|
+
}
|
|
1166
|
+
)
|
|
1167
|
+
}
|
|
1168
|
+
)
|
|
1169
|
+
] });
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// src/SingleTreeSearchableSelect.tsx
|
|
1173
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
1174
|
+
function SingleTreeSearchableSelect(props) {
|
|
1175
|
+
const {
|
|
1176
|
+
selectedItemId: selectedItemIdProp,
|
|
1177
|
+
defaultSelectedItemId = null,
|
|
1178
|
+
onSelectedItemIdChange,
|
|
1179
|
+
...rest
|
|
1180
|
+
} = props;
|
|
1181
|
+
const isControlled = selectedItemIdProp !== void 0;
|
|
1182
|
+
const [internalId, setInternalId] = React5.useState(
|
|
1183
|
+
defaultSelectedItemId
|
|
1184
|
+
);
|
|
1185
|
+
const selectedItemId = isControlled ? selectedItemIdProp ?? null : internalId;
|
|
1186
|
+
const selectedIds = React5.useMemo(
|
|
1187
|
+
() => selectedItemId ? [selectedItemId] : [],
|
|
1188
|
+
[selectedItemId]
|
|
1189
|
+
);
|
|
1190
|
+
const handleSelectedIdsChange = (ids) => {
|
|
1191
|
+
const next = ids[0] ?? null;
|
|
1192
|
+
if (!isControlled) setInternalId(next);
|
|
1193
|
+
onSelectedItemIdChange?.(next);
|
|
1194
|
+
};
|
|
1195
|
+
return /* @__PURE__ */ jsx5(
|
|
1196
|
+
TreeSearchableSelectBase,
|
|
1197
|
+
{
|
|
1198
|
+
...rest,
|
|
1199
|
+
selectionMode: "single",
|
|
1200
|
+
selectedIds,
|
|
1201
|
+
onSelectedIdsChange: handleSelectedIdsChange,
|
|
1202
|
+
closeOnSelect: true
|
|
1203
|
+
}
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// src/MultiTreeSearchableSelect.tsx
|
|
1208
|
+
import * as React6 from "react";
|
|
1209
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
1210
|
+
function MultiTreeSearchableSelect(props) {
|
|
1211
|
+
const {
|
|
1212
|
+
selectedItemIds: selectedItemIdsProp,
|
|
1213
|
+
defaultSelectedItemIds,
|
|
1214
|
+
onSelectedItemIdsChange,
|
|
1215
|
+
...rest
|
|
1216
|
+
} = props;
|
|
1217
|
+
const isControlled = selectedItemIdsProp !== void 0;
|
|
1218
|
+
const [internalIds, setInternalIds] = React6.useState(() => [
|
|
1219
|
+
...defaultSelectedItemIds ?? []
|
|
1220
|
+
]);
|
|
1221
|
+
const selectedIds = isControlled ? selectedItemIdsProp ?? [] : internalIds;
|
|
1222
|
+
const handleSelectedIdsChange = (ids) => {
|
|
1223
|
+
if (!isControlled) setInternalIds(ids);
|
|
1224
|
+
onSelectedItemIdsChange?.(ids);
|
|
1225
|
+
};
|
|
1226
|
+
return /* @__PURE__ */ jsx6(
|
|
1227
|
+
TreeSearchableSelectBase,
|
|
1228
|
+
{
|
|
1229
|
+
...rest,
|
|
1230
|
+
selectionMode: "multiple",
|
|
1231
|
+
selectedIds,
|
|
1232
|
+
onSelectedIdsChange: handleSelectedIdsChange,
|
|
1233
|
+
closeOnSelect: false
|
|
1234
|
+
}
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
882
1237
|
export {
|
|
1238
|
+
MultiTreeSearchableSelect,
|
|
1239
|
+
SingleTreeSearchableSelect,
|
|
883
1240
|
Tree,
|
|
884
1241
|
TreeCombobox,
|
|
885
1242
|
TreeItem
|