11import Guid from '@js/core/guid' ;
2+ import $ from '@js/core/renderer' ;
23import { extend } from '@js/core/utils/extend' ;
34import { captionize } from '@js/core/utils/inflector' ;
45import { each } from '@js/core/utils/iterator' ;
5- import { isDefined } from '@js/core/utils/type' ;
6+ import { isBoolean , isDefined , isFunction } from '@js/core/utils/type' ;
7+ import type { dxDropDownEditorOptions } from '@js/ui/drop_down_editor/ui.drop_down_editor' ;
8+ import type { FormItemComponent } from '@js/ui/form' ;
9+ import type { dxOverlayOptions } from '@js/ui/overlay' ;
10+ import type dxTextBox from '@js/ui/text_box' ;
611
712import { SIMPLE_ITEM_TYPE } from './constants' ;
813
9- const EDITORS_WITH_ARRAY_VALUE = [ 'dxTagBox' , 'dxRangeSlider' , 'dxDateRangeBox' ] ;
10- const EDITORS_WITH_SPECIFIC_LABELS = [ 'dxRangeSlider' , 'dxSlider' ] ;
11- export const EDITORS_WITHOUT_LABELS = [ 'dxCalendar' , 'dxCheckBox' , 'dxHtmlEditor' , 'dxRadioGroup' , 'dxRangeSlider' , 'dxSlider' , 'dxSwitch' ] ;
14+ const EDITORS_WITH_ARRAY_VALUE : FormItemComponent [ ] = [
15+ 'dxTagBox' ,
16+ 'dxRangeSlider' ,
17+ 'dxDateRangeBox' ,
18+ ] ;
19+ const EDITORS_WITH_SPECIFIC_LABELS : FormItemComponent [ ] = [ 'dxRangeSlider' , 'dxSlider' ] ;
20+ export const EDITORS_WITHOUT_LABELS : FormItemComponent [ ] = [
21+ 'dxCalendar' ,
22+ 'dxCheckBox' ,
23+ 'dxHtmlEditor' ,
24+ 'dxRadioGroup' ,
25+ 'dxRangeSlider' ,
26+ 'dxSlider' ,
27+ 'dxSwitch' ,
28+ ] ;
29+ const DROP_DOWN_EDITORS : FormItemComponent [ ] = [
30+ 'dxSelectBox' ,
31+ 'dxDropDownBox' ,
32+ 'dxTagBox' ,
33+ 'dxLookup' ,
34+ 'dxAutocomplete' ,
35+ 'dxColorBox' ,
36+ 'dxDateBox' ,
37+ 'dxDateRangeBox' ,
38+ ] ;
39+
40+ type DropDownOptions = dxDropDownEditorOptions < dxTextBox > ;
1241
1342export function convertToRenderFieldItemOptions ( {
1443 $parent,
@@ -33,7 +62,9 @@ export function convertToRenderFieldItemOptions({
3362 labelMode,
3463 onLabelTemplateRendered,
3564} ) {
36- const isRequired = isDefined ( item . isRequired ) ? item . isRequired : ! ! _hasRequiredRuleInSet ( item . validationRules ) ;
65+ const isRequired = isDefined ( item . isRequired )
66+ ? item . isRequired
67+ : ! ! _hasRequiredRuleInSet ( item . validationRules ) ;
3768 const isSimpleItem = item . itemType === SIMPLE_ITEM_TYPE ;
3869 const helpID = item . helpText ? `dx-${ new Guid ( ) } ` : null ;
3970
@@ -49,11 +80,16 @@ export function convertToRenderFieldItemOptions({
4980 onLabelTemplateRendered,
5081 } ) ;
5182
52- const needRenderLabel = labelOptions . visible && ( labelOptions . text || ( labelOptions . labelTemplate && isSimpleItem ) ) ;
83+ const needRenderLabel = labelOptions . visible
84+ && ( labelOptions . text || ( labelOptions . labelTemplate && isSimpleItem ) ) ;
5385 const { location : labelLocation , labelID } = labelOptions ;
54- const labelNeedBaselineAlign = labelLocation !== 'top' && [ 'dxTextArea' , 'dxRadioGroup' , 'dxCalendar' , 'dxHtmlEditor' ] . includes ( item . editorType ) ;
86+ const labelNeedBaselineAlign = labelLocation !== 'top'
87+ && [ 'dxTextArea' , 'dxRadioGroup' , 'dxCalendar' , 'dxHtmlEditor' ] . includes (
88+ item . editorType ,
89+ ) ;
5590
5691 const editorOptions = _convertToEditorOptions ( {
92+ $parent,
5793 editorType : item . editorType ,
5894 editorValue,
5995 defaultEditorName : item . dataField ,
@@ -70,8 +106,9 @@ export function convertToRenderFieldItemOptions({
70106 } ) ;
71107
72108 const needRenderOptionalMarkAsHelpText = labelOptions . markOptions . showOptionalMark
73- && ! labelOptions . visible && editorOptions . labelMode !== 'hidden'
74- && ! isDefined ( item . helpText ) ;
109+ && ! labelOptions . visible
110+ && editorOptions . labelMode !== 'hidden'
111+ && ! isDefined ( item . helpText ) ;
75112
76113 const helpText = needRenderOptionalMarkAsHelpText
77114 ? labelOptions . markOptions . optionalMark
@@ -102,18 +139,26 @@ export function convertToRenderFieldItemOptions({
102139}
103140
104141export function getLabelMarkText ( {
105- showRequiredMark, requiredMark, showOptionalMark, optionalMark,
142+ showRequiredMark,
143+ requiredMark,
144+ showOptionalMark,
145+ optionalMark,
106146} ) {
107147 if ( ! showRequiredMark && ! showOptionalMark ) {
108148 return '' ;
109149 }
110150
111- return String . fromCharCode ( 160 ) + ( showRequiredMark ? requiredMark : optionalMark ) ;
151+ return (
152+ String . fromCharCode ( 160 ) + ( showRequiredMark ? requiredMark : optionalMark )
153+ ) ;
112154}
113155
114- export function convertToLabelMarkOptions ( {
115- showRequiredMark, requiredMark, showOptionalMark, optionalMark,
116- } , isRequired ?: boolean ) {
156+ export function convertToLabelMarkOptions (
157+ {
158+ showRequiredMark, requiredMark, showOptionalMark, optionalMark,
159+ } ,
160+ isRequired ?: boolean ,
161+ ) {
117162 return {
118163 showRequiredMark : showRequiredMark && isRequired ,
119164 requiredMark,
@@ -122,8 +167,55 @@ export function convertToLabelMarkOptions({
122167 } ;
123168}
124169
170+ // eslint-disable-next-line @typescript-eslint/naming-convention
171+ function _getDropDownEditorOptions (
172+ $parent ,
173+ editorType : FormItemComponent ,
174+ editorInputId : string ,
175+ onContentReadyExternal ?: DropDownOptions [ 'onContentReady' ] ,
176+ ) : DropDownOptions {
177+ const isDropDownEditor = DROP_DOWN_EDITORS . includes ( editorType ) ;
178+
179+ if ( ! isDropDownEditor ) {
180+ return { } ;
181+ }
182+
183+ return {
184+ onContentReady : ( e ) => {
185+ const { component } = e ;
186+ const openOnFieldClick = component . option ( 'openOnFieldClick' ) as DropDownOptions [ 'openOnFieldClick' ] ;
187+ const initialHideOnOutsideClick = component . option ( 'dropDownOptions.hideOnOutsideClick' ) as dxOverlayOptions < dxTextBox > [ 'hideOnOutsideClick' ] ;
188+
189+ if ( openOnFieldClick ) {
190+ component . option ( 'dropDownOptions' , {
191+ hideOnOutsideClick : ( e ) => {
192+ if ( isBoolean ( initialHideOnOutsideClick ) ) {
193+ return initialHideOnOutsideClick ;
194+ }
195+
196+ const $target = $ ( e . target ) ;
197+ const $label = $parent . find ( `label[for="${ editorInputId } "]` ) ;
198+ const isLabelClicked = ! ! $target . closest ( $label ) . length ;
199+
200+ if ( ! isFunction ( initialHideOnOutsideClick ) ) {
201+ return ! isLabelClicked ;
202+ }
203+
204+ return ! isLabelClicked && initialHideOnOutsideClick ( e ) ;
205+ } ,
206+ } ) ;
207+ }
208+
209+ if ( isFunction ( onContentReadyExternal ) ) {
210+ onContentReadyExternal ( e ) ;
211+ }
212+ } ,
213+ } ;
214+ }
215+
125216// eslint-disable-next-line @typescript-eslint/naming-convention
126217function _convertToEditorOptions ( {
218+ $parent,
127219 editorType,
128220 defaultEditorName,
129221 editorValue,
@@ -153,10 +245,13 @@ function _convertToEditorOptions({
153245 const stylingMode = externalEditorOptions ?. stylingMode || editorStylingMode ;
154246 const useSpecificLabelOptions = EDITORS_WITH_SPECIFIC_LABELS . includes ( editorType ) ;
155247
248+ const dropDownEditorOptions = _getDropDownEditorOptions ( $parent , editorType , editorInputId , externalEditorOptions ?. onContentReady ) ;
249+
156250 const result = extend (
157251 true ,
158252 editorOptionsWithValue ,
159253 externalEditorOptions ,
254+ dropDownEditorOptions ,
160255 {
161256 inputAttr : { id : editorInputId } ,
162257 validationBoundary : editorValidationBoundary ,
@@ -179,6 +274,7 @@ function _convertToEditorOptions({
179274 if ( defaultEditorName && ! result . name ) {
180275 result . name = defaultEditorName ;
181276 }
277+
182278 return result ;
183279}
184280
@@ -201,15 +297,27 @@ function _hasRequiredRuleInSet(rules) {
201297
202298// eslint-disable-next-line @typescript-eslint/naming-convention
203299function _convertToLabelOptions ( {
204- item, id, isRequired, managerMarkOptions, showColonAfterLabel, labelLocation, labelTemplate, formLabelMode, onLabelTemplateRendered,
300+ item,
301+ id,
302+ isRequired,
303+ managerMarkOptions,
304+ showColonAfterLabel,
305+ labelLocation,
306+ labelTemplate,
307+ formLabelMode,
308+ onLabelTemplateRendered,
205309} ) {
206- const isEditorWithoutLabels = EDITORS_WITHOUT_LABELS . includes ( item . editorType ) ;
310+ const isEditorWithoutLabels = EDITORS_WITHOUT_LABELS . includes (
311+ item . editorType ,
312+ ) ;
207313 const labelOptions = extend (
208314 {
209315 showColon : showColonAfterLabel ,
210316 location : labelLocation ,
211317 id,
212- visible : formLabelMode === 'outside' || ( isEditorWithoutLabels && formLabelMode !== 'hidden' ) ,
318+ visible :
319+ formLabelMode === 'outside'
320+ || ( isEditorWithoutLabels && formLabelMode !== 'hidden' ) ,
213321 isRequired,
214322 } ,
215323 item ? item . label : { } ,
@@ -220,7 +328,16 @@ function _convertToLabelOptions({
220328 } ,
221329 ) ;
222330
223- const editorsRequiringIdForLabel = [ 'dxRadioGroup' , 'dxCheckBox' , 'dxLookup' , 'dxSlider' , 'dxRangeSlider' , 'dxSwitch' , 'dxHtmlEditor' , 'dxDateRangeBox' ] ; // TODO: support "dxCalendar"
331+ const editorsRequiringIdForLabel : FormItemComponent [ ] = [
332+ 'dxRadioGroup' ,
333+ 'dxCheckBox' ,
334+ 'dxLookup' ,
335+ 'dxSlider' ,
336+ 'dxRangeSlider' ,
337+ 'dxSwitch' ,
338+ 'dxHtmlEditor' ,
339+ 'dxDateRangeBox' ,
340+ ] ; // TODO: support "dxCalendar"
224341 if ( editorsRequiringIdForLabel . includes ( item . editorType ) ) {
225342 labelOptions . labelID = `dx-label-${ new Guid ( ) } ` ;
226343 }
0 commit comments