From 88c838aac8563720bad7070460fca527565230de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:57:13 +0300 Subject: [PATCH] Mobil Grid ve Tree Popup EditForm problemi --- ui/src/views/form/FormDevExpress.tsx | 214 ++++++++++++++------------- ui/src/views/form/useFormData.tsx | 81 +++++++++- ui/src/views/list/Grid.tsx | 182 ++++++++++++++++------- ui/src/views/list/Tree.tsx | 176 +++++++++++++++------- 4 files changed, 437 insertions(+), 216 deletions(-) diff --git a/ui/src/views/form/FormDevExpress.tsx b/ui/src/views/form/FormDevExpress.tsx index bd8ae48..fbe12b4 100644 --- a/ui/src/views/form/FormDevExpress.tsx +++ b/ui/src/views/form/FormDevExpress.tsx @@ -140,7 +140,9 @@ const getValueByField = (data: Record = {}, field?: string) => { } const shouldRunEditorScriptOnContentReady = (script?: string) => - Boolean(script && (script.includes('setEditorReadOnly') || script.includes('runtimeSetEditorReadOnly'))) + Boolean( + script && (script.includes('setEditorReadOnly') || script.includes('runtimeSetEditorReadOnly')), + ) const FormDevExpress = (props: { listFormCode: string @@ -158,9 +160,15 @@ const FormDevExpress = (props: { const formItemsRef = useRef(formItems) const formInstanceRef = useRef() const lastContentReadyScriptKeyRef = useRef() + const didAutoFocusRef = useRef(false) const [runtimeReadOnlyFields, setRuntimeReadOnlyFields] = useState>({}) const runtimeReadOnlyFieldsRef = useRef>({}) + const isTouchLikeDevice = () => + typeof window !== 'undefined' && + (window.matchMedia?.('(pointer: coarse)').matches || + window.matchMedia?.('(hover: none)').matches) + useEffect(() => { formDataRef.current = formData }, [formData]) @@ -173,6 +181,10 @@ const FormDevExpress = (props: { runtimeReadOnlyFieldsRef.current = runtimeReadOnlyFields }, [runtimeReadOnlyFields]) + useEffect(() => { + didAutoFocusRef.current = false + }, [listFormCode, mode]) + const setRuntimeEditorReadOnly = (field: string, readOnly: boolean) => { const resolvedField = findFormFieldKey(formItemsRef.current, field) const key = String(resolvedField || field || '').toLowerCase() @@ -202,11 +214,7 @@ const FormDevExpress = (props: { setTimeout(() => setFormEditorReadOnly(formInstanceRef.current ?? form, field, readOnly), 0) } - const runEditorScript = ( - formItem: SimpleItemWithColData, - eventValue: any, - component?: any, - ) => { + const runEditorScript = (formItem: SimpleItemWithColData, eventValue: any, component?: any) => { if (!formItem?.editorScript) { return } @@ -250,7 +258,9 @@ const FormDevExpress = (props: { const prevOnValueChanged = formItem.editorOptions?.onValueChanged return { - ...(index !== undefined && mode !== 'view' ? { autoFocus: index === 1 } : {}), + ...(index !== undefined && mode !== 'view' && !isTouchLikeDevice() + ? { autoFocus: index === 1 } + : {}), ...(formItem.editorType === 'dxDateBox' ? { useMaskBehavior: true, @@ -466,7 +476,6 @@ const FormDevExpress = (props: { setTimeout(() => { updateCascadeDisabledStates() }, 0) - }} onContentReady={(e) => { formInstanceRef.current = e.component @@ -478,7 +487,8 @@ const FormDevExpress = (props: { const groupItems = e.component.option('items') as any[] const firstItem = groupItems?.[0]?.items?.[0] - if (firstItem?.dataField) { + if (!didAutoFocusRef.current && firstItem?.dataField && !isTouchLikeDevice()) { + didAutoFocusRef.current = true const editor = e.component.getEditor(firstItem.dataField) mode !== 'view' && editor?.focus() } @@ -492,98 +502,100 @@ const FormDevExpress = (props: { colSpan={formGroupItem.colSpan} caption={formGroupItem.caption} > - {(formGroupItem.items as SimpleItemWithColData[])?.filter((formItem) => { - if (mode === 'edit') return formItem.allowEditing !== false - if (mode === 'new') return formItem.allowAdding !== false - return true - }).map((formItem, i) => { - return formItem.editorType2 === PlatformEditorTypes.dxTagBox ? ( - ( - { - const newData = { ...formDataRef.current, [formItem.dataField!]: e } - formDataRef.current = newData - setFormData(newData) - runEditorScript(formItem, e, formInstanceRef.current) - }} - editorOptions={getEditorOptions(formItem)} - > - )} - label={{ - text: translate('::' + formItem.colData?.captionName), - className: 'font-semibold', - }} - > - ) : formItem.editorType2 === PlatformEditorTypes.dxGridBox ? ( - ( - { - const newData = { ...formDataRef.current, [formItem.dataField!]: e } - formDataRef.current = newData - setFormData(newData) - runEditorScript(formItem, e, formInstanceRef.current) - }} - editorOptions={getEditorOptions(formItem)} - > - )} - label={{ - text: translate('::' + formItem.colData?.captionName), - className: 'font-semibold', - }} - > - ) : formItem.editorType2 === PlatformEditorTypes.dxImageUpload ? ( - ( - { - const newData = { ...formDataRef.current, [formItem.dataField!]: val } - formDataRef.current = newData - setFormData(newData) - runEditorScript(formItem, val, formInstanceRef.current) - }} - editorOptions={getEditorOptions(formItem)} - /> - )} - label={{ - text: translate('::' + formItem.colData?.captionName), - className: 'font-semibold', - }} - > - ) : ( - - ) - })} + {(formGroupItem.items as SimpleItemWithColData[]) + ?.filter((formItem) => { + if (mode === 'edit') return formItem.allowEditing !== false + if (mode === 'new') return formItem.allowAdding !== false + return true + }) + .map((formItem, i) => { + return formItem.editorType2 === PlatformEditorTypes.dxTagBox ? ( + ( + { + const newData = { ...formDataRef.current, [formItem.dataField!]: e } + formDataRef.current = newData + setFormData(newData) + runEditorScript(formItem, e, formInstanceRef.current) + }} + editorOptions={getEditorOptions(formItem)} + > + )} + label={{ + text: translate('::' + formItem.colData?.captionName), + className: 'font-semibold', + }} + > + ) : formItem.editorType2 === PlatformEditorTypes.dxGridBox ? ( + ( + { + const newData = { ...formDataRef.current, [formItem.dataField!]: e } + formDataRef.current = newData + setFormData(newData) + runEditorScript(formItem, e, formInstanceRef.current) + }} + editorOptions={getEditorOptions(formItem)} + > + )} + label={{ + text: translate('::' + formItem.colData?.captionName), + className: 'font-semibold', + }} + > + ) : formItem.editorType2 === PlatformEditorTypes.dxImageUpload ? ( + ( + { + const newData = { ...formDataRef.current, [formItem.dataField!]: val } + formDataRef.current = newData + setFormData(newData) + runEditorScript(formItem, val, formInstanceRef.current) + }} + editorOptions={getEditorOptions(formItem)} + /> + )} + label={{ + text: translate('::' + formItem.colData?.captionName), + className: 'font-semibold', + }} + > + ) : ( + + ) + })} ) })} diff --git a/ui/src/views/form/useFormData.tsx b/ui/src/views/form/useFormData.tsx index abae5e1..c187cb4 100644 --- a/ui/src/views/form/useFormData.tsx +++ b/ui/src/views/form/useFormData.tsx @@ -19,6 +19,13 @@ import { layoutTypes } from '../admin/listForm/edit/types' import { useListFormCustomDataSource } from '../list/useListFormCustomDataSource' import { useListFormColumns } from '../list/useListFormColumns' +const flattenFormItems = (items: any[] = []): SimpleItemWithColData[] => + items.flatMap((item) => [ + ...(item?.dataField ? [item] : []), + ...flattenFormItems(item?.items || []), + ...(item?.tabs || []).flatMap((tab: any) => flattenFormItems(tab?.items || [])), + ]) + const useGridData = (props: { mode: RowMode listFormCode: string @@ -41,6 +48,7 @@ const useGridData = (props: { const [permissionResults, setPermissionResults] = useState() const refForm = useRef(null) + const previousFormDataRef = useRef() const [searchParams] = useSearchParams() const navigate = useNavigate() const { translate } = useLocalization() @@ -306,18 +314,36 @@ const useGridData = (props: { setGridReady(true) }, [gridDto]) - // formData değiştiğinde sadece lookup datasource'ları güncelle + // formData değiştiğinde sadece etkilenen cascading lookup datasource'ları güncelle useEffect(() => { if (!gridDto || !formItems.length) { + previousFormDataRef.current = formData return } - // View mode'da formData olsa da olmasa da cascading alanlar için dataSource oluşturulmalı - const updatedItems = formItems.map((groupItem) => ({ - ...groupItem, - items: (groupItem.items as SimpleItemWithColData[])?.map((item) => { + const previousFormData = previousFormDataRef.current + const changedFields = previousFormData + ? Object.keys({ ...(previousFormData || {}), ...(formData || {}) }).filter( + (field) => !Object.is(previousFormData?.[field], formData?.[field]), + ) + : [] + + const shouldRefreshLookup = (item: SimpleItemWithColData) => { + const cascadeParentFields = item.colData?.lookupDto?.cascadeParentFields + ?.split(',') + .map((field: string) => field.trim()) + .filter(Boolean) + + return ( + !previousFormData || + cascadeParentFields?.some((field: string) => changedFields.includes(field)) + ) + } + + const updateItems = (items: any[] = []) => + items.map((item) => { const colData = gridDto.columnFormats.find((x) => x.fieldName === item.dataField) - if (colData?.lookupDto?.dataSourceType) { + if (colData?.lookupDto?.dataSourceType && shouldRefreshLookup(item)) { const currentDataSource = item.editorOptions?.dataSource const keepCustomDataSource = currentDataSource !== undefined && typeof currentDataSource?.load !== 'function' @@ -330,16 +356,55 @@ const useGridData = (props: { dataSource: keepCustomDataSource ? currentDataSource : getLookupDataSource(colData?.editorOptions, colData, formData || null), - valueExpr: item.editorOptions?.valueExpr ?? colData?.lookupDto?.valueExpr?.toLowerCase(), + valueExpr: + item.editorOptions?.valueExpr ?? colData?.lookupDto?.valueExpr?.toLowerCase(), displayExpr: item.editorOptions?.displayExpr ?? colData?.lookupDto?.displayExpr?.toLowerCase(), }, } } + + if (item?.items?.length) { + return { + ...item, + items: updateItems(item.items), + } + } + + if (item?.tabs?.length) { + return { + ...item, + tabs: item.tabs.map((tab: any) => ({ + ...tab, + items: updateItems(tab.items), + })), + } + } + return item - }), + }) + + const hasAffectedLookup = + !previousFormData || + formItems + .flatMap((group) => flattenFormItems([group])) + .some((item) => item.colData?.lookupDto?.dataSourceType && shouldRefreshLookup(item)) + + if (!hasAffectedLookup) { + previousFormDataRef.current = formData + return + } + + const updatedItems = formItems.map((groupItem) => ({ + ...groupItem, + items: updateItems(groupItem.items as any[]), + tabs: (groupItem as any).tabs?.map((tab: any) => ({ + ...tab, + items: updateItems(tab.items), + })), })) + previousFormDataRef.current = formData setFormItems(updatedItems) }, [formData, gridDto]) diff --git a/ui/src/views/list/Grid.tsx b/ui/src/views/list/Grid.tsx index 614d38a..2e9dd53 100644 --- a/ui/src/views/list/Grid.tsx +++ b/ui/src/views/list/Grid.tsx @@ -15,7 +15,6 @@ import { postListFormCustomization, } from '@/services/list-form-customization.service' import { useLocalization } from '@/utils/hooks/useLocalization' -import useResponsive from '@/utils/hooks/useResponsive' import { executeEditorScript } from '@/utils/editorScriptRuntime' import { Template } from 'devextreme-react/core/template' import DataGrid, { @@ -239,19 +238,29 @@ const getValueByField = (data: Record = {}, field?: string) => { } const shouldRunEditorScriptOnContentReady = (script?: string) => - Boolean(script && (script.includes('setEditorReadOnly') || script.includes('runtimeSetEditorReadOnly'))) + Boolean( + script && (script.includes('setEditorReadOnly') || script.includes('runtimeSetEditorReadOnly')), + ) + +const isTouchLikeDevice = () => + typeof window !== 'undefined' && + (window.matchMedia?.('(pointer: coarse)').matches || window.matchMedia?.('(hover: none)').matches) + +const isMobileViewport = () => + typeof window !== 'undefined' && window.matchMedia?.('(max-width: 767px)').matches const Grid = (props: GridProps) => { const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props const { translate } = useLocalization() - const { smaller } = useResponsive() const currentUser = useStoreState((state) => state.auth.user) + const useMobileEditPopup = useRef(isMobileViewport() || isTouchLikeDevice()).current const gridRef = useRef() const refListFormCode = useRef('') const widgetGroupRef = useRef(null) const editingFormDataRef = useRef>({}) const editingFormInstanceRef = useRef() + const lastEditingContentReadyScriptKeyRef = useRef() // Edit popup state kaydetmeyi engellemek için flag const isEditingRef = useRef(false) @@ -344,6 +353,29 @@ const Grid = (props: GridProps) => { const { createSelectDataSource } = useListFormCustomDataSource({ gridRef }) + const applyMobileEditorFocusGuard = useCallback( + (editorOptions: EditorOptionsWithButtons, dataField?: string) => { + if (!useMobileEditPopup || !dataField) { + return editorOptions + } + + const previousOnFocusIn = editorOptions.onFocusIn + + return { + ...editorOptions, + autoFocus: false, + focusStateEnabled: false, + selectTextOnFocus: false, + onFocusIn: (e: any) => { + if (typeof previousOnFocusIn === 'function') { + previousOnFocusIn(e) + } + }, + } + }, + [useMobileEditPopup], + ) + const openNotePanel = useCallback( (rowData: Record) => { const keyFieldName = gridDto?.gridOptions.keyFieldName @@ -728,6 +760,10 @@ const Grid = (props: GridProps) => { const onEditorPreparing = useCallback( (editor: DataGridTypes.EditorPreparingEvent) => { if (editor.parentType === 'dataRow' && editor.dataField && gridDto) { + if (isTouchLikeDevice()) { + editor.editorOptions = applyMobileEditorFocusGuard(editor.editorOptions, editor.dataField) + } + const formItem = gridDto.gridOptions.editingFormDto .flatMap((group) => flattenEditingFormItems([group])) .find((i) => i.dataField === editor.dataField) @@ -888,7 +924,13 @@ const Grid = (props: GridProps) => { } } }, - [gridDto, cascadeFieldsMap], + [ + gridDto, + cascadeFieldsMap, + parentToChildrenMap, + mode, + applyMobileEditorFocusGuard, + ], ) const customLoadState = useCallback(() => { @@ -1181,20 +1223,20 @@ const Grid = (props: GridProps) => { if (listFormField?.sourceDbType === DbTypeEnum.Date) { Object.assign(defaultEditorOptions, { - type: 'date', - dateSerializationFormat: 'yyyy-MM-dd', - displayFormat: 'shortDate', - }) + type: 'date', + dateSerializationFormat: 'yyyy-MM-dd', + displayFormat: 'shortDate', + }) } else if ( listFormField?.sourceDbType === DbTypeEnum.DateTime || listFormField?.sourceDbType === DbTypeEnum.DateTime2 || listFormField?.sourceDbType === DbTypeEnum.DateTimeOffset ) { Object.assign(defaultEditorOptions, { - type: 'datetime', - dateSerializationFormat: 'yyyy-MM-ddTHH:mm:ss', - displayFormat: 'shortDateShortTime', - }) + type: 'datetime', + dateSerializationFormat: 'yyyy-MM-ddTHH:mm:ss', + displayFormat: 'shortDateShortTime', + }) } // Her item'a placeholder olarak captionName ekle @@ -1217,6 +1259,8 @@ const Grid = (props: GridProps) => { ...forcedEditorOptions, } + editorOptions = applyMobileEditorFocusGuard(editorOptions, i.dataField) + if (editorOptions?.buttons) { editorOptions.buttons = (editorOptions?.buttons || []).map((btn: any) => { if (btn?.options?.onClick && typeof btn.options.onClick === 'string') { @@ -1261,7 +1305,7 @@ const Grid = (props: GridProps) => { return item }, - [gridDto, mode, searchParams, extraFilters], + [gridDto, mode, searchParams, extraFilters, applyMobileEditorFocusGuard], ) // WidgetGroup yüksekliğini hesapla @@ -1514,7 +1558,7 @@ const Grid = (props: GridProps) => { /> { showTitle: gridDto.gridOptions.editingOptionDto?.popup?.showTitle, hideOnOutsideClick: gridDto.gridOptions.editingOptionDto?.popup?.hideOnOutsideClick, - width: gridDto.gridOptions.editingOptionDto?.popup?.width, - height: gridDto.gridOptions.editingOptionDto?.popup?.height, - resizeEnabled: gridDto.gridOptions.editingOptionDto?.popup?.resizeEnabled, - fullScreen: isPopupFullScreen, + width: useMobileEditPopup + ? '100%' + : gridDto.gridOptions.editingOptionDto?.popup?.width, + height: useMobileEditPopup + ? '100dvh' + : gridDto.gridOptions.editingOptionDto?.popup?.height, + resizeEnabled: + !useMobileEditPopup && + gridDto.gridOptions.editingOptionDto?.popup?.resizeEnabled, + fullScreen: useMobileEditPopup || isPopupFullScreen, + dragEnabled: !useMobileEditPopup, + focusStateEnabled: !useMobileEditPopup, + restorePosition: !useMobileEditPopup, toolbarItems: [ { widget: 'dxButton', @@ -1581,6 +1634,7 @@ const Grid = (props: GridProps) => { }} form={{ colCount: 1, + focusStateEnabled: !useMobileEditPopup, onContentReady: (e) => { editingFormInstanceRef.current = e.component @@ -1601,15 +1655,27 @@ const Grid = (props: GridProps) => { } const runReadOnlyScripts = () => { - const editorValues = gridDto.gridOptions.editingFormDto - .flatMap((group) => flattenEditingFormItems([group])) - .reduce>((values, formItem) => { + const formItems = gridDto.gridOptions.editingFormDto.flatMap((group) => + flattenEditingFormItems([group]), + ) + const scriptItems = formItems.filter((formItem) => + shouldRunEditorScriptOnContentReady(formItem.editorScript), + ) + + if (!scriptItems.length) { + return + } + + const editorValues = formItems.reduce>( + (values, formItem) => { const editorInstance = form?.getEditor?.(formItem.dataField) if (editorInstance?.option) { values[formItem.dataField] = editorInstance.option('value') } return values - }, {}) + }, + {}, + ) const formData = { ...editingFormDataRef.current, ...(form?.option?.('formData') || {}), @@ -1617,38 +1683,47 @@ const Grid = (props: GridProps) => { } editingFormDataRef.current = { ...formData } - gridDto.gridOptions.editingFormDto - .flatMap((group) => flattenEditingFormItems([group])) - .filter((formItem) => - shouldRunEditorScriptOnContentReady(formItem.editorScript), - ) - .forEach((formItem) => { - try { - const editorInstance = form?.getEditor?.(formItem.dataField) - const editorValue = - editorInstance?.option?.('value') ?? - getValueByField(formData, formItem.dataField) - const editor = { - dataField: formItem.dataField, - component: grid, - } - const e = { - component: form, - dataField: formItem.dataField, - value: editorValue, - } + const scriptKey = `${mode}|${String(rowKey)}|${scriptItems + .map((formItem) => formItem.dataField) + .join('|')}|${JSON.stringify(formData)}` - executeEditorScript(formItem.editorScript!, { - formData, - e, - editor, - runtimeSetEditorReadOnly, - setFormData, - }) - } catch (err) { - console.error('Script exec error on contentReady', formItem.dataField, err) + if (lastEditingContentReadyScriptKeyRef.current === scriptKey) { + return + } + + lastEditingContentReadyScriptKeyRef.current = scriptKey + + scriptItems.forEach((formItem) => { + try { + const editorInstance = form?.getEditor?.(formItem.dataField) + const editorValue = + editorInstance?.option?.('value') ?? + getValueByField(formData, formItem.dataField) + const editor = { + dataField: formItem.dataField, + component: grid, } - }) + const e = { + component: form, + dataField: formItem.dataField, + value: editorValue, + } + + executeEditorScript(formItem.editorScript!, { + formData, + e, + editor, + runtimeSetEditorReadOnly, + setFormData, + }) + } catch (err) { + console.error( + 'Script exec error on contentReady', + formItem.dataField, + err, + ) + } + }) } runReadOnlyScripts() @@ -1704,7 +1779,6 @@ const Grid = (props: GridProps) => { console.error('Script exec error', err) } } - } }, items: @@ -1896,7 +1970,7 @@ const Grid = (props: GridProps) => { )} filterData.setIsImportModalOpen(false)} onRequestClose={() => filterData.setIsImportModalOpen(false)} diff --git a/ui/src/views/list/Tree.tsx b/ui/src/views/list/Tree.tsx index ef4ffa8..1b6779b 100644 --- a/ui/src/views/list/Tree.tsx +++ b/ui/src/views/list/Tree.tsx @@ -15,7 +15,6 @@ import { postListFormCustomization, } from '@/services/list-form-customization.service' import { useLocalization } from '@/utils/hooks/useLocalization' -import useResponsive from '@/utils/hooks/useResponsive' import { captionize } from 'devextreme/core/utils/inflector' import { Template } from 'devextreme-react/core/template' import TreeListDx, { @@ -227,19 +226,29 @@ const getValueByField = (data: Record = {}, field?: string) => { } const shouldRunEditorScriptOnContentReady = (script?: string) => - Boolean(script && (script.includes('setEditorReadOnly') || script.includes('runtimeSetEditorReadOnly'))) + Boolean( + script && (script.includes('setEditorReadOnly') || script.includes('runtimeSetEditorReadOnly')), + ) + +const isTouchLikeDevice = () => + typeof window !== 'undefined' && + (window.matchMedia?.('(pointer: coarse)').matches || window.matchMedia?.('(hover: none)').matches) + +const isMobileViewport = () => + typeof window !== 'undefined' && window.matchMedia?.('(max-width: 767px)').matches const Tree = (props: TreeProps) => { const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props const { translate } = useLocalization() - const { smaller } = useResponsive() const currentUser = useStoreState((state) => state.auth.user) + const useMobileEditPopup = useRef(isMobileViewport() || isTouchLikeDevice()).current const gridRef = useRef() const refListFormCode = useRef('') const widgetGroupRef = useRef(null) const editingFormDataRef = useRef>({}) const editingFormInstanceRef = useRef() + const lastEditingContentReadyScriptKeyRef = useRef() // Edit popup state kaydetmeyi engellemek için flag const isEditingRef = useRef(false) @@ -349,6 +358,29 @@ const Tree = (props: TreeProps) => { const { createSelectDataSource } = useListFormCustomDataSource({ gridRef }) + const applyMobileEditorFocusGuard = useCallback( + (editorOptions: EditorOptionsWithButtons, dataField?: string) => { + if (!useMobileEditPopup || !dataField) { + return editorOptions + } + + const previousOnFocusIn = editorOptions.onFocusIn + + return { + ...editorOptions, + autoFocus: false, + focusStateEnabled: false, + selectTextOnFocus: false, + onFocusIn: (e: any) => { + if (typeof previousOnFocusIn === 'function') { + previousOnFocusIn(e) + } + }, + } + }, + [useMobileEditPopup], + ) + const openNotePanel = useCallback( (rowData: Record) => { const keyFieldName = gridDto?.gridOptions.keyFieldName @@ -677,6 +709,10 @@ const Tree = (props: TreeProps) => { function onEditorPreparing(editor: TreeListTypes.EditorPreparingEvent) { if (editor.parentType === 'dataRow' && editor.dataField && gridDto) { + if (isTouchLikeDevice()) { + editor.editorOptions = applyMobileEditorFocusGuard(editor.editorOptions, editor.dataField) + } + const formItem = gridDto.gridOptions.editingFormDto .flatMap((group) => flattenEditingFormItems([group])) .find((i) => i.dataField === editor.dataField) @@ -1180,7 +1216,7 @@ const Tree = (props: TreeProps) => { { showTitle: gridDto.gridOptions.editingOptionDto?.popup?.showTitle, hideOnOutsideClick: gridDto.gridOptions.editingOptionDto?.popup?.hideOnOutsideClick, - width: gridDto.gridOptions.editingOptionDto?.popup?.width, - height: gridDto.gridOptions.editingOptionDto?.popup?.height, - resizeEnabled: gridDto.gridOptions.editingOptionDto?.popup?.resizeEnabled, - fullScreen: isPopupFullScreen, + width: useMobileEditPopup + ? '100%' + : gridDto.gridOptions.editingOptionDto?.popup?.width, + height: useMobileEditPopup + ? '100dvh' + : gridDto.gridOptions.editingOptionDto?.popup?.height, + resizeEnabled: + !useMobileEditPopup && + gridDto.gridOptions.editingOptionDto?.popup?.resizeEnabled, + fullScreen: useMobileEditPopup || isPopupFullScreen, + dragEnabled: !useMobileEditPopup, + focusStateEnabled: !useMobileEditPopup, + restorePosition: !useMobileEditPopup, toolbarItems: [ { widget: 'dxButton', @@ -1243,6 +1288,7 @@ const Tree = (props: TreeProps) => { }} form={{ colCount: 1, + focusStateEnabled: !useMobileEditPopup, onContentReady: (e) => { editingFormInstanceRef.current = e.component @@ -1263,15 +1309,27 @@ const Tree = (props: TreeProps) => { } const runReadOnlyScripts = () => { - const editorValues = gridDto.gridOptions.editingFormDto - .flatMap((group) => flattenEditingFormItems([group])) - .reduce>((values, formItem) => { + const formItems = gridDto.gridOptions.editingFormDto.flatMap((group) => + flattenEditingFormItems([group]), + ) + const scriptItems = formItems.filter((formItem) => + shouldRunEditorScriptOnContentReady(formItem.editorScript), + ) + + if (!scriptItems.length) { + return + } + + const editorValues = formItems.reduce>( + (values, formItem) => { const editorInstance = form?.getEditor?.(formItem.dataField) if (editorInstance?.option) { values[formItem.dataField] = editorInstance.option('value') } return values - }, {}) + }, + {}, + ) const formData = { ...editingFormDataRef.current, ...(form?.option?.('formData') || {}), @@ -1279,38 +1337,47 @@ const Tree = (props: TreeProps) => { } editingFormDataRef.current = { ...formData } - gridDto.gridOptions.editingFormDto - .flatMap((group) => flattenEditingFormItems([group])) - .filter((formItem) => - shouldRunEditorScriptOnContentReady(formItem.editorScript), - ) - .forEach((formItem) => { - try { - const editorInstance = form?.getEditor?.(formItem.dataField) - const editorValue = - editorInstance?.option?.('value') ?? - getValueByField(formData, formItem.dataField) - const editor = { - dataField: formItem.dataField, - component: grid, - } - const e = { - component: form, - dataField: formItem.dataField, - value: editorValue, - } + const scriptKey = `${mode}|${String(rowKey)}|${scriptItems + .map((formItem) => formItem.dataField) + .join('|')}|${JSON.stringify(formData)}` - executeEditorScript(formItem.editorScript!, { - formData, - e, - editor, - runtimeSetEditorReadOnly, - setFormData, - }) - } catch (err) { - console.error('Script exec error on contentReady', formItem.dataField, err) + if (lastEditingContentReadyScriptKeyRef.current === scriptKey) { + return + } + + lastEditingContentReadyScriptKeyRef.current = scriptKey + + scriptItems.forEach((formItem) => { + try { + const editorInstance = form?.getEditor?.(formItem.dataField) + const editorValue = + editorInstance?.option?.('value') ?? + getValueByField(formData, formItem.dataField) + const editor = { + dataField: formItem.dataField, + component: grid, } - }) + const e = { + component: form, + dataField: formItem.dataField, + value: editorValue, + } + + executeEditorScript(formItem.editorScript!, { + formData, + e, + editor, + runtimeSetEditorReadOnly, + setFormData, + }) + } catch (err) { + console.error( + 'Script exec error on contentReady', + formItem.dataField, + err, + ) + } + }) } runReadOnlyScripts() @@ -1366,7 +1433,6 @@ const Tree = (props: TreeProps) => { console.error('Script exec error', err) } } - } }, items: @@ -1388,7 +1454,9 @@ const Tree = (props: TreeProps) => { let parsedEditorOptions: EditorOptionsWithButtons = {} const forcedEditorOptions: EditorOptionsWithButtons = {} try { - parsedEditorOptions = i.editorOptions ? JSON.parse(i.editorOptions) : {} + parsedEditorOptions = i.editorOptions + ? JSON.parse(i.editorOptions) + : {} const rawFilter = searchParams?.get('filter') if (rawFilter) { @@ -1420,20 +1488,20 @@ const Tree = (props: TreeProps) => { if (listFormField?.sourceDbType === DbTypeEnum.Date) { Object.assign(defaultEditorOptions, { - type: 'date', - dateSerializationFormat: 'yyyy-MM-dd', - displayFormat: 'shortDate', - }) + type: 'date', + dateSerializationFormat: 'yyyy-MM-dd', + displayFormat: 'shortDate', + }) } else if ( listFormField?.sourceDbType === DbTypeEnum.DateTime || listFormField?.sourceDbType === DbTypeEnum.DateTime2 || listFormField?.sourceDbType === DbTypeEnum.DateTimeOffset ) { Object.assign(defaultEditorOptions, { - type: 'datetime', - dateSerializationFormat: 'yyyy-MM-ddTHH:mm:ss', - displayFormat: 'shortDateShortTime', - }) + type: 'datetime', + dateSerializationFormat: 'yyyy-MM-ddTHH:mm:ss', + displayFormat: 'shortDateShortTime', + }) } editorOptions = { @@ -1442,6 +1510,8 @@ const Tree = (props: TreeProps) => { ...forcedEditorOptions, } + editorOptions = applyMobileEditorFocusGuard(editorOptions, i.dataField) + if (editorOptions?.buttons) { editorOptions.buttons = (editorOptions?.buttons || []).map( (btn: any) => { @@ -1636,7 +1706,7 @@ const Tree = (props: TreeProps) => { )} filterData.setIsImportModalOpen(false)} onRequestClose={() => filterData.setIsImportModalOpen(false)}