@@ -2,11 +2,22 @@ import * as React from 'react';
2
2
import { BaseComponentContext } from '@microsoft/sp-component-base' ;
3
3
import { Guid } from '@microsoft/sp-core-library' ;
4
4
import { IIconProps } from 'office-ui-fabric-react/lib/components/Icon' ;
5
- import { PrimaryButton , DefaultButton , IconButton , IButtonStyles } from 'office-ui-fabric-react/lib/Button' ;
5
+ import { PrimaryButton ,
6
+ DefaultButton ,
7
+ IconButton ,
8
+ IButtonStyles
9
+ } from 'office-ui-fabric-react/lib/Button' ;
6
10
import { Label } from 'office-ui-fabric-react/lib/Label' ;
7
- import { Panel , PanelType } from 'office-ui-fabric-react/lib/Panel' ;
8
- import { IBasePickerStyleProps , IBasePickerStyles , ITag , TagPicker } from 'office-ui-fabric-react/lib/Pickers' ;
9
- import { IStackTokens , Stack } from 'office-ui-fabric-react/lib/Stack' ;
11
+ import { Panel ,
12
+ PanelType
13
+ } from 'office-ui-fabric-react/lib/Panel' ;
14
+ import { IBasePickerStyleProps ,
15
+ IBasePickerStyles ,
16
+ ISuggestionItemProps
17
+ } from 'office-ui-fabric-react/lib/Pickers' ;
18
+ import { IStackTokens ,
19
+ Stack
20
+ } from 'office-ui-fabric-react/lib/Stack' ;
10
21
import { IStyleFunctionOrObject } from 'office-ui-fabric-react/lib/Utilities' ;
11
22
import { sp } from '@pnp/sp' ;
12
23
import { SPTaxonomyService } from '../../services/SPTaxonomyService' ;
@@ -16,52 +27,74 @@ import * as strings from 'ControlStrings';
16
27
import { TooltipHost } from '@microsoft/office-ui-fabric-react-bundle' ;
17
28
import { useId } from '@uifabric/react-hooks' ;
18
29
import { ITooltipHostStyles } from 'office-ui-fabric-react' ;
19
- import { ITermInfo , ITermSetInfo , ITermStoreInfo } from '@pnp/sp/taxonomy' ;
30
+ import { ITermInfo ,
31
+ ITermSetInfo ,
32
+ ITermStoreInfo
33
+ } from '@pnp/sp/taxonomy' ;
34
+ import { TermItemSuggestion } from './termItem/TermItemSuggestion' ;
35
+ import { ModernTermPicker } from './modernTermPicker/ModernTermPicker' ;
36
+ import { ITermInfoExt ,
37
+ ITermItemProps
38
+ } from './modernTermPicker/ModernTermPicker.types' ;
39
+ import { TermItem } from './termItem/TermItem' ;
20
40
21
41
export interface IModernTaxonomyPickerProps {
22
- allowMultipleSelections : boolean ;
42
+ allowMultipleSelections ? : boolean ;
23
43
termSetId : string ;
24
44
anchorTermId ?: string ;
25
45
panelTitle : string ;
26
46
label : string ;
27
47
context : BaseComponentContext ;
28
- initialValues ?: ITag [ ] ;
48
+ initialValues ?: ITermInfo [ ] ;
29
49
disabled ?: boolean ;
30
50
required ?: boolean ;
31
- onChange ?: ( newValue ?: ITag [ ] ) => void ;
51
+ onChange ?: ( newValue ?: ITermInfo [ ] ) => void ;
52
+ onRenderItem ?: ( itemProps : ITermItemProps ) => JSX . Element ;
53
+ onRenderSuggestionsItem ?: ( term : ITermInfoExt , itemProps : ISuggestionItemProps < ITermInfoExt > ) => JSX . Element ;
32
54
placeHolder ?: string ;
55
+ customPanelWidth ?: number ;
33
56
}
34
57
35
58
export function ModernTaxonomyPicker ( props : IModernTaxonomyPickerProps ) {
36
59
const [ taxonomyService ] = React . useState ( ( ) => new SPTaxonomyService ( props . context ) ) ;
37
60
const [ panelIsOpen , setPanelIsOpen ] = React . useState ( false ) ;
38
- const [ selectedOptions , setSelectedOptions ] = React . useState < ITag [ ] > ( Object . prototype . toString . call ( props . initialValues ) === '[object Array]' ? props . initialValues : [ ] ) ;
39
- const [ selectedPanelOptions , setSelectedPanelOptions ] = React . useState < ITag [ ] > ( [ ] ) ;
40
- const [ termStoreInfo , setTermStoreInfo ] = React . useState < ITermStoreInfo > ( ) ;
41
- const [ termSetInfo , setTermSetInfo ] = React . useState < ITermSetInfo > ( ) ;
42
- const [ anchorTermInfo , setAnchorTermInfo ] = React . useState < ITermInfo > ( ) ;
61
+ const [ selectedOptions , setSelectedOptions ] = React . useState < ITermInfoExt [ ] > ( [ ] ) ;
62
+ const [ selectedPanelOptions , setSelectedPanelOptions ] = React . useState < ITermInfoExt [ ] > ( [ ] ) ;
63
+ const [ currentTermStoreInfo , setCurrentTermStoreInfo ] = React . useState < ITermStoreInfo > ( ) ;
64
+ const [ currentTermSetInfo , setCurrentTermSetInfo ] = React . useState < ITermSetInfo > ( ) ;
65
+ const [ currentAnchorTermInfo , setCurrentAnchorTermInfo ] = React . useState < ITermInfo > ( ) ;
66
+ const [ currentLanguageTag , setCurrentLanguageTag ] = React . useState < string > ( "" ) ;
43
67
44
68
React . useEffect ( ( ) => {
45
69
sp . setup ( props . context ) ;
46
70
taxonomyService . getTermStoreInfo ( )
47
- . then ( ( localTermStoreInfo ) => {
48
- setTermStoreInfo ( localTermStoreInfo ) ;
71
+ . then ( ( termStoreInfo ) => {
72
+ setCurrentTermStoreInfo ( termStoreInfo ) ;
73
+ setCurrentLanguageTag ( props . context . pageContext . cultureInfo . currentUICultureName !== '' ?
74
+ props . context . pageContext . cultureInfo . currentUICultureName :
75
+ currentTermStoreInfo . defaultLanguageTag ) ;
76
+ setSelectedOptions ( Object . prototype . toString . call ( props . initialValues ) === '[object Array]' ?
77
+ props . initialValues . map ( term => { return { ...term , languageTag : currentLanguageTag , termStoreInfo : currentTermStoreInfo } as ITermInfoExt ; } ) :
78
+ [ ] ) ;
49
79
} ) ;
50
80
taxonomyService . getTermSetInfo ( Guid . parse ( props . termSetId ) )
51
- . then ( ( localTermSetInfo ) => {
52
- setTermSetInfo ( localTermSetInfo ) ;
81
+ . then ( ( termSetInfo ) => {
82
+ setCurrentTermSetInfo ( termSetInfo ) ;
53
83
} ) ;
54
84
if ( props . anchorTermId && props . anchorTermId !== Guid . empty . toString ( ) ) {
55
85
taxonomyService . getTermById ( Guid . parse ( props . termSetId ) , props . anchorTermId ? Guid . parse ( props . anchorTermId ) : Guid . empty )
56
- . then ( ( localAnchorTermInfo ) => {
57
- setAnchorTermInfo ( localAnchorTermInfo ) ;
86
+ . then ( ( anchorTermInfo ) => {
87
+ setCurrentAnchorTermInfo ( anchorTermInfo ) ;
58
88
} ) ;
59
89
}
60
90
} , [ ] ) ;
61
91
62
92
React . useEffect ( ( ) => {
63
93
if ( props . onChange ) {
64
- props . onChange ( selectedOptions ) ;
94
+ props . onChange ( selectedOptions . map ( termInfoExt => {
95
+ const { languageTag, termStoreInfo, ...termInfo } = termInfoExt ;
96
+ return termInfo ;
97
+ } ) ) ;
65
98
}
66
99
} , [ selectedOptions ] ) ;
67
100
@@ -83,29 +116,69 @@ export function ModernTaxonomyPicker(props: IModernTaxonomyPickerProps) {
83
116
onClosePanel ( ) ;
84
117
}
85
118
86
- async function onResolveSuggestions ( filter : string , selectedItems ?: ITag [ ] ) : Promise < ITag [ ] > {
87
- const languageTag = props . context . pageContext . cultureInfo . currentUICultureName !== '' ? props . context . pageContext . cultureInfo . currentUICultureName : termStoreInfo . defaultLanguageTag ;
119
+ async function onResolveSuggestions ( filter : string , selectedItems ?: ITermInfoExt [ ] ) : Promise < ITermInfoExt [ ] > {
88
120
if ( filter === '' ) {
89
121
return [ ] ;
90
122
}
91
- const filteredTerms = await taxonomyService . searchTerm ( Guid . parse ( props . termSetId ) , filter , languageTag , props . anchorTermId ? Guid . parse ( props . anchorTermId ) : Guid . empty ) ;
123
+ const filteredTerms = await taxonomyService . searchTerm ( Guid . parse ( props . termSetId ) , filter , currentLanguageTag , props . anchorTermId ? Guid . parse ( props . anchorTermId ) : Guid . empty ) ;
92
124
const filteredTermsWithoutSelectedItems = filteredTerms . filter ( ( term ) => {
93
125
if ( ! selectedItems || selectedItems . length === 0 ) {
94
126
return true ;
95
127
}
96
- return selectedItems . every ( ( item ) => item . key !== term . id ) ;
128
+ return selectedItems . every ( ( item ) => item . id !== term . id ) ;
97
129
} ) ;
98
130
const filteredTermsAndAvailable = filteredTermsWithoutSelectedItems . filter ( ( term ) => term . isAvailableForTagging . filter ( ( t ) => t . setId === props . termSetId ) [ 0 ] . isAvailable ) ;
99
- const filteredTags = filteredTermsAndAvailable . map ( ( term ) => {
100
- const key = term . id ;
101
- let labelsWithMatchingLanguageTag = term . labels . filter ( ( termLabel ) => ( termLabel . languageTag === languageTag ) ) ;
131
+ const filteredTermsAndAvailableAsExt = filteredTermsAndAvailable . map ( term => { return { ...term , languageTag : currentLanguageTag , termStoreInfo : currentTermStoreInfo } as ITermInfoExt ; } ) ;
132
+ return filteredTermsAndAvailableAsExt ;
133
+ }
134
+
135
+ async function onLoadParentLabel ( termId : Guid ) : Promise < string > {
136
+ const termInfo = await taxonomyService . getTermById ( Guid . parse ( props . termSetId ) , termId ) ;
137
+ if ( termInfo . parent ) {
138
+ let labelsWithMatchingLanguageTag = termInfo . parent . labels . filter ( ( termLabel ) => ( termLabel . languageTag === currentLanguageTag ) ) ;
102
139
if ( labelsWithMatchingLanguageTag . length === 0 ) {
103
- labelsWithMatchingLanguageTag = term . labels . filter ( ( termLabel ) => ( termLabel . languageTag === termStoreInfo . defaultLanguageTag ) ) ;
140
+ labelsWithMatchingLanguageTag = termInfo . parent . labels . filter ( ( termLabel ) => ( termLabel . languageTag === currentTermStoreInfo . defaultLanguageTag ) ) ;
104
141
}
105
- const name = labelsWithMatchingLanguageTag . filter ( ( termLabel ) => termLabel . name . toLowerCase ( ) . indexOf ( filter . toLowerCase ( ) ) === 0 ) [ 0 ] ?. name ;
106
- return { key : key , name : name } ;
107
- } ) ;
108
- return filteredTags ;
142
+ return labelsWithMatchingLanguageTag [ 0 ] ?. name ;
143
+ }
144
+ else {
145
+ let termSetNames = currentTermSetInfo . localizedNames . filter ( ( name ) => name . languageTag === currentLanguageTag ) ;
146
+ if ( termSetNames . length === 0 ) {
147
+ termSetNames = currentTermSetInfo . localizedNames . filter ( ( name ) => name . languageTag === currentTermStoreInfo . defaultLanguageTag ) ;
148
+ }
149
+ return termSetNames [ 0 ] . name ;
150
+ }
151
+ }
152
+
153
+ function onRenderSuggestionsItem ( term : ITermInfoExt , itemProps : ISuggestionItemProps < ITermInfoExt > ) : JSX . Element {
154
+ return (
155
+ < TermItemSuggestion
156
+ onLoadParentLabel = { onLoadParentLabel }
157
+ term = { term }
158
+ termStoreInfo = { currentTermStoreInfo }
159
+ languageTag = { currentLanguageTag }
160
+ { ...itemProps }
161
+ />
162
+ ) ;
163
+ }
164
+
165
+ function onRenderItem ( itemProps : ITermItemProps ) : JSX . Element {
166
+ let labels = itemProps . item . labels . filter ( ( name ) => name . languageTag === currentLanguageTag && name . isDefault ) ;
167
+ if ( labels . length === 0 ) {
168
+ labels = itemProps . item . labels . filter ( ( name ) => name . languageTag === currentTermStoreInfo . defaultLanguageTag && name . isDefault ) ;
169
+ }
170
+
171
+ return labels . length > 0 ? (
172
+ < TermItem languageTag = { currentLanguageTag } termStoreInfo = { currentTermStoreInfo } { ...itemProps } > { labels [ 0 ] . name } </ TermItem >
173
+ ) : null ;
174
+ }
175
+
176
+ function getTextFromItem ( termInfo : ITermInfoExt ) : string {
177
+ let labelsWithMatchingLanguageTag = termInfo . labels . filter ( ( termLabel ) => ( termLabel . languageTag === currentLanguageTag ) ) ;
178
+ if ( labelsWithMatchingLanguageTag . length === 0 ) {
179
+ labelsWithMatchingLanguageTag = termInfo . labels . filter ( ( termLabel ) => ( termLabel . languageTag === currentTermStoreInfo . defaultLanguageTag ) ) ;
180
+ }
181
+ return labelsWithMatchingLanguageTag [ 0 ] ?. name ;
109
182
}
110
183
111
184
const calloutProps = { gapSpace : 0 } ;
@@ -119,22 +192,25 @@ export function ModernTaxonomyPicker(props: IModernTaxonomyPickerProps) {
119
192
{ props . label && < Label required = { props . required } > { props . label } </ Label > }
120
193
< div className = { styles . termField } >
121
194
< div className = { styles . termFieldInput } >
122
- < TagPicker
195
+ < ModernTermPicker
123
196
removeButtonAriaLabel = { strings . ModernTaxonomyPickerRemoveButtonText }
124
197
onResolveSuggestions = { onResolveSuggestions }
125
198
itemLimit = { props . allowMultipleSelections ? undefined : 1 }
126
199
selectedItems = { selectedOptions }
127
200
disabled = { props . disabled }
128
201
styles = { tagPickerStyles }
129
- onChange = { ( itms ?: ITag [ ] ) => {
202
+ onChange = { ( itms ?: ITermInfoExt [ ] ) => {
130
203
setSelectedOptions ( itms || [ ] ) ;
131
204
setSelectedPanelOptions ( itms || [ ] ) ;
132
205
} }
133
- getTextFromItem = { ( tag : ITag ) => tag . name }
206
+ getTextFromItem = { getTextFromItem }
207
+ pickerSuggestionsProps = { { noResultsFoundText : strings . ModernTaxonomyPickerNoResultsFound } }
134
208
inputProps = { {
135
209
'aria-label' : props . placeHolder || strings . ModernTaxonomyPickerDefaultPlaceHolder ,
136
210
placeholder : props . placeHolder || strings . ModernTaxonomyPickerDefaultPlaceHolder
137
211
} }
212
+ onRenderSuggestionsItem = { props . onRenderSuggestionsItem ?? onRenderSuggestionsItem }
213
+ onRenderItem = { props . onRenderItem ?? onRenderItem }
138
214
/>
139
215
</ div >
140
216
< div className = { styles . termFieldButton } >
@@ -155,7 +231,8 @@ export function ModernTaxonomyPicker(props: IModernTaxonomyPickerProps) {
155
231
closeButtonAriaLabel = { strings . ModernTaxonomyPickerPanelCloseButtonText }
156
232
onDismiss = { onClosePanel }
157
233
isLightDismiss = { true }
158
- type = { PanelType . medium }
234
+ type = { props . customPanelWidth ? PanelType . custom : PanelType . medium }
235
+ customWidth = { props . customPanelWidth ? `${ props . customPanelWidth } px` : undefined }
159
236
headerText = { props . panelTitle }
160
237
onRenderFooterContent = { ( ) => {
161
238
const horizontalGapStackTokens : IStackTokens = {
@@ -176,15 +253,19 @@ export function ModernTaxonomyPicker(props: IModernTaxonomyPickerProps) {
176
253
allowMultipleSelections = { props . allowMultipleSelections }
177
254
onResolveSuggestions = { onResolveSuggestions }
178
255
onLoadMoreData = { taxonomyService . getTerms }
179
- anchorTermInfo = { anchorTermInfo }
180
- termSetInfo = { termSetInfo }
181
- termStoreInfo = { termStoreInfo }
256
+ anchorTermInfo = { currentAnchorTermInfo }
257
+ termSetInfo = { currentTermSetInfo }
258
+ termStoreInfo = { currentTermStoreInfo }
182
259
context = { props . context }
183
260
termSetId = { Guid . parse ( props . termSetId ) }
184
261
pageSize = { 50 }
185
262
selectedPanelOptions = { selectedPanelOptions }
186
263
setSelectedPanelOptions = { setSelectedPanelOptions }
187
264
placeHolder = { props . placeHolder || strings . ModernTaxonomyPickerDefaultPlaceHolder }
265
+ onRenderSuggestionsItem = { props . onRenderSuggestionsItem ?? onRenderSuggestionsItem }
266
+ onRenderItem = { props . onRenderItem ?? onRenderItem }
267
+ getTextFromItem = { getTextFromItem }
268
+ languageTag = { currentLanguageTag }
188
269
/>
189
270
</ div >
190
271
)
0 commit comments