diff --git a/lib/ui/dropdown.d.ts b/lib/ui/dropdown.d.ts
index ef22018..5e93352 100644
--- a/lib/ui/dropdown.d.ts
+++ b/lib/ui/dropdown.d.ts
@@ -32,7 +32,7 @@ export interface DropdownOptions {
     search?: boolean;
     /** 搜索的关键字数组 */
     searchKeys?: Array<string>;
-    /** 搜索提示文本,默认值取语言资源 <code>searchHolder</code> "Search..." */
+    /** 搜索提示文本,默认值取语言资源 `searchHolder` "Search..." */
     searchPlaceholder?: string;
     /** 焦点索引 */
     tabIndex?: Number;
@@ -47,7 +47,7 @@ export interface DropdownOptions {
 /** 下拉框类 */
 export class Dropdown {
     /**
-     * 把父元素下的所有 <code>select</code> 元素修改为统一下拉框组件
+     * 把父元素下的所有 `select` 元素修改为统一下拉框组件
      * @param dom 父元素
      * @returns 返回该父元素
      */
@@ -64,16 +64,24 @@ export class Dropdown {
     /**
      * 选中时触发
      * @param item 选中的条目
+     * @eventProperty
      */
     onSelected: (item: DropdownItem) => void;
     /**
      * 选中多个时触发
      * @param list 选中的条目数组
+     * @eventProperty
      */
     onSelectedList: (list: Array<DropdownItem>) => void;
-    /** 下拉框展开时触发 */
+    /**
+     * 下拉框展开时触发
+     * @eventProperty
+     */
     onExpanded: () => void;
-    /** 下拉框收缩时触发 */
+    /**
+     * 下拉框收缩时触发
+     * @eventProperty
+     */
     onCollapsed: () => void;
 
     /** 获取下拉框是否禁用 */
diff --git a/lib/ui/grid/column.d.ts b/lib/ui/grid/column.d.ts
index a125b7a..6da6c40 100644
--- a/lib/ui/grid/column.d.ts
+++ b/lib/ui/grid/column.d.ts
@@ -35,13 +35,18 @@ export interface GridColumnDefinition {
     align?: "left" | "center" | "right";
     /**
      * 列是否可用(可编辑),允许以下类型<br/><br/>
-     * <code>boolean</code> 则直接使用该值<br/><br/>
-     * <code>string</code> 则以该值为关键字从行数据中取值作为判断条件<br/><br/>
-     * <code>(item: GridItem) => boolean</code> 则调用该函数(上下文为列定义对象),以返回值作为判断条件<br/><br/>
+     * `boolean` 则直接使用该值<br/><br/>
+     * `string` 则以该值为关键字从行数据中取值作为判断条件<br/><br/>
+     * `(item: GridItem) => boolean` 则调用该函数(上下文为列定义对象),以返回值作为判断条件<br/><br/>
      */
     enabled?: boolean | string | ((item: GridItem) => boolean);
-    /** 单元格取值采用该方法返回的值 */
-    filter?: (item: GridItem) => any;
+    /**
+     * 单元格取值采用该方法返回的值
+     * @param item 行数据对象
+     * @param editing 是否处于编辑状态
+     * @param body Grid 控件的 `&lt;tbody&gt;` 部分
+     */
+    filter?: (item: GridItem, editing: boolean, body?: HTMLElement) => any;
     /** 单元格以该值填充内容,忽略filter与关键字属性 */
     text?: string;
     /** 列是否可见 */
@@ -76,8 +81,8 @@ export interface GridColumnDefinition {
     source?: Array<GridSourceItem> | ((item: GridItem) => Array<GridSourceItem> | Promise<Array<GridSourceItem>>);
     /** 下拉列表数据源是否缓存结果(即行数据未发生变化时仅从source属性获取一次值) */
     sourceCache?: boolean;
-    /** 列为图标类型时以该值设置图标样式(函数上下文为列定义对象),允许值为 <code>fa-light</code>、<code>fa-regular</code>、<code>fa-solid</code> */
-    iconType?: string;
+    /** 列为图标类型时以该值设置图标样式(函数上下文为列定义对象),默认值 `fa-light` */
+    iconType?: "fa-light" | "fa-regular" | "fa-solid";
     /** 列为图标类型时以该值作为单元格元素的额外样式类型(函数上下文为列定义对象) */
     iconClassName?: string | ((item: GridItem) => string);
     /** 列为日期类型时以该值作为最小可选日期值 */
@@ -92,6 +97,7 @@ export interface GridColumnDefinition {
      * @param this 上下文为 Grid 对象
      * @param col 列定义对象
      * @param flag 是否选中
+     * @eventProperty
      */
     onAllChecked?: (this: Grid, col: GridColumnDefinition, flag: boolean) => void;
     /**
@@ -101,6 +107,7 @@ export interface GridColumnDefinition {
      * @param value 修改后的值
      * @param oldValue 修改前的值
      * @param e 列修改事件传递过来的任意对象
+     * @eventProperty
      */
     onChanged?: (this: Grid, item: GridItem, value: boolean | string | Number, oldValue: boolean | string | Number, e?: any) => void;
     /**
@@ -108,6 +115,7 @@ export interface GridColumnDefinition {
      * @param this 上下文为 Grid 对象
      * @param item 数据行对象
      * @param value 修改后的文本框值
+     * @eventProperty
      */
     onInputEnded?: (this: Grid, item: GridItem, value: string) => void;
     /**
@@ -115,12 +123,14 @@ export interface GridColumnDefinition {
      * @param this 上下文为 Grid 对象
      * @param col 列定义对象
      * @param selected 选中的过滤项
+     * @eventProperty
      */
     onFilterOk?: (this: Grid, col: GridColumnDefinition, selected: Array<GridItem>) => void;
     /**
      * 列过滤后触发的事件
      * @param this 上下文为 Grid 对象
      * @param col 列定义对象
+     * @eventProperty
      */
     onFiltered?: (this: Grid, col: GridColumnDefinition) => void;
     /**
@@ -128,6 +138,7 @@ export interface GridColumnDefinition {
      * @param this 上下文为列定义对象
      * @param item 数据行对象
      * @param drop 下拉框对象
+     * @eventProperty
      */
     onDropExpanded?: (this: GridColumnDefinition, item: GridItem, drop: Dropdown) => void;
 }
@@ -138,19 +149,28 @@ export class GridColumn {
      * 创建显示单元格时调用的方法
      * @param col 列定义对象
      * @returns 返回创建的单元格元素
+     * @virtual
      */
     static create(col: GridColumnDefinition): HTMLElement;
     /**
      * 创建编辑单元格时调用的方法<br/><br/>
-     * 元素修改后设置行包装对象的 <code>__editing</code> 后,支持在离开编辑状态时及时触发 {@linkcode leaveEdit} 方法<br/>
+     * 元素修改后设置行包装对象的 `__editing` 后,支持在离开编辑状态时及时触发 {@linkcode leaveEdit} 方法<br/>
      * 更多例子参考代码中 {@linkcode GridDropdownColumn} 的实现。
      * @param trigger 编辑事件回调函数,e 参数会传递给 {@linkcode getValue} 方法
      * @param col 列定义对象
      * @param container 父容器元素
-     * @param vals 行包装对象,其 <code>values</code> 属性为行数据对象
+     * @param vals 行包装对象,其 `values` 属性为行数据对象
      * @returns 返回创建的编辑状态的单元格元素
+     * @virtual
      */
     static createEdit(trigger: (e: any) => void, col: GridColumnDefinition, container: HTMLElement, vals: GridItemWrapper): HTMLElement;
+    /**
+     * 创建列头时调用的方法
+     * @param col 列定义对象
+     * @returns 返回创建的列头元素
+     * @virtual
+     */
+    static createCaption?(col: GridColumnDefinition): HTMLElement;
     /**
      * 设置单元格值时调用的方法
      * @param element 单元格元素
@@ -158,59 +178,178 @@ export class GridColumn {
      * @param vals 行包装对象
      * @param col 列定义对象
      * @param grid {@linkcode Grid} 对象
+     * @virtual
      */
     static setValue(element: HTMLElement, val: string | boolean | Number, vals: GridItemWrapper, col: GridColumnDefinition, grid: Grid): void;
     /**
      * 获取编辑状态单元格值时调用的方法
-     * @param e 由 {@linkcode createEdit} 方法中 <code>trigger</code> 函数传递来的对象
+     * @param e 由 {@linkcode createEdit} 方法中 `trigger` 函数传递来的对象
      * @param col 列定义对象
      * @returns 返回单元格的值
+     * @virtual
      */
     static getValue(e: any, col: GridColumnDefinition): string | boolean | Number;
     /**
      * 设置单元格样式时调用的方法
      * @param element 单元格元素
      * @param style 样式对象
+     * @virtual
      */
     static setStyle(element: HTMLElement, style: { [key: string]: string }): void;
     /**
      * 设置单元格可用性时调用的方法
      * @param element 单元格元素
      * @param enabled 启用值,为false时代表禁用
+     * @virtual
      */
     static setEnabled(element: HTMLElement, enabled?: boolean): void;
     /**
-     * 单元格离开编辑元素时触发,需要由行包装对象的 <code>__editing</code> 来确定是否触发。
+     * 单元格离开编辑元素时触发,需要由行包装对象的 `__editing` 来确定是否触发。
      * @param element 单元格元素
      * @param container 父容器元素
+     * @virtual
      */
-    static leaveEdit(element: HTMLElement, container: HTMLElement): void;
+    static leaveEdit?(element: HTMLElement, container: HTMLElement): void;
 }
 
 /** 单行文本列 */
 export class GridInputColumn extends GridColumn {
     /**
      * 设置该类型是否支持触发 {@linkcode GridColumnDefinition.onInputEnded} 方法<br/>
-     * 该属性返回 <code>true</code> 后,在任意事件中修改行包装对象的 <code>__editing</code> 值,则会在行列元素变动时及时触发 {@linkcode GridColumnDefinition.onInputEnded} 方法,避免例如文本框还未触发 <code>onchange</code> 事件就被移除元素而导致的问题<br/>
+     * 该属性返回 `true` 后,在任意事件中修改行包装对象的 `__editing` 值,则会在行列元素变动时及时触发 {@linkcode GridColumnDefinition.onInputEnded} 方法,避免例如文本框还未触发 `onchange` 事件就被移除元素而导致的问题<br/>
      * 更多例子参考代码中 {@linkcode GridInputColumn} 的实现
      */
     static get editing(): boolean;
+    /**
+     * @inheritdoc GridColumn.createEdit
+     * @override
+     */
+    static createEdit(trigger: (e: any) => void, col: GridColumnDefinition, container: HTMLElement, vals: GridItemWrapper): HTMLElement;
+    /**
+     * @inheritdoc GridColumn.setValue
+     * @override
+     */
+    static setValue(element: HTMLElement, val: string, vals: GridItemWrapper, col: GridColumnDefinition, grid: Grid): void;
+    /**
+     * @inheritdoc GridColumn.getValue
+     * @override
+     */
+    static getValue(e: any): string;
+    /**
+     * @inheritdoc GridColumn.setEnabled
+     * @override
+     */
+    static setEnabled(element: HTMLElement, enabled?: boolean): void;
 }
 
 /** 多行文本列 */
-export class GridTextColumn extends GridInputColumn { }
+export class GridTextColumn extends GridInputColumn {
+    /**
+     * @inheritdoc GridInputColumn.createEdit
+     * @override
+     */
+    static createEdit(trigger: (e: any) => void, col: GridColumnDefinition, container: HTMLElement, vals: GridItemWrapper): HTMLElement;
+    /**
+     * @inheritdoc GridInputColumn.setValue
+     * @override
+     */
+    static setValue(element: HTMLElement, val: string, vals: GridItemWrapper, col: GridColumnDefinition, grid: Grid): void;
+}
 
 /** 下拉选择列 */
-export class GridDropdownColumn extends GridColumn { }
+export class GridDropdownColumn extends GridColumn {
+    /**
+     * @inheritdoc GridColumn.createEdit
+     * @override
+     */
+    static createEdit(trigger: (e: any) => void, col: GridColumnDefinition, container: HTMLElement, vals: GridItemWrapper): HTMLElement;
+    /**
+     * @inheritdoc GridColumn.setValue
+     * @override
+     */
+    static setValue(element: HTMLElement, val: string, vals: GridItemWrapper, col: GridColumnDefinition): void;
+    /**
+     * @inheritdoc GridColumn.getValue
+     * @override
+     */
+    static getValue(e: any, col: GridColumnDefinition): string;
+    /**
+     * @inheritdoc GridColumn.setEnabled
+     * @override
+     */
+    static setEnabled(element: HTMLElement, enabled?: boolean): void;
+    /**
+     * @inheritdoc GridColumn.leaveEdit
+     * @override
+     */
+    static leaveEdit?(element: HTMLElement, container: HTMLElement): void;
+}
 
 /** 复选框列 */
-export class GridCheckboxColumn extends GridColumn { }
+export class GridCheckboxColumn extends GridColumn {
+    /**
+     * @inheritdoc GridColumn.createEdit
+     * @override
+     */
+    static createEdit(trigger: (e: any) => void): HTMLElement;
+    /**
+     * @inheritdoc GridColumn.setValue
+     * @override
+     */
+    static setValue(element: HTMLElement, val: boolean): void;
+    /**
+     * @inheritdoc GridColumn.getValue
+     * @override
+     */
+    static getValue(e: any): boolean;
+    /**
+     * @inheritdoc GridColumn.setEnabled
+     * @override
+     */
+    static setEnabled(element: HTMLElement, enabled?: boolean): void;
+}
 
 /** 图标列 */
-export class GridIconColumn extends GridColumn { }
+export class GridIconColumn extends GridColumn {
+    /**
+     * @inheritdoc GridColumn.create
+     * @override
+     */
+    static create(): HTMLElement;
+    /**
+     * @inheritdoc GridColumn.setValue
+     * @override
+     */
+    static setValue(element: HTMLElement, val: string, vals: GridItemWrapper, col: GridColumnDefinition): void;
+    /**
+     * @inheritdoc GridColumn.setEnabled
+     * @override
+     */
+    static setEnabled(element: HTMLElement, enabled?: boolean): void;
+}
 
 /** 日期选择列 */
 export class GridDateColumn extends GridColumn {
+    /**
+     * @inheritdoc GridColumn.createEdit
+     * @override
+     */
+    static createEdit(trigger: (e: any) => void, col: GridColumnDefinition, container: HTMLElement, vals: GridItemWrapper): HTMLElement;
+    /**
+     * @inheritdoc GridColumn.setValue
+     * @override
+     */
+    static setValue(element: HTMLElement, val: string | Number): void;
+    /**
+     * @inheritdoc GridColumn.getValue
+     * @override
+     */
+    static getValue(e: any): string | Number;
+    /**
+     * @inheritdoc GridColumn.setEnabled
+     * @override
+     */
+    static setEnabled(element: HTMLElement, enabled?: boolean): void;
     /**
      * 格式化日期对象为 M/d/yyyy 格式的字符串
      * @param date 日期对象
diff --git a/lib/ui/grid/column.js b/lib/ui/grid/column.js
index ad8dbc9..df07cf5 100644
--- a/lib/ui/grid/column.js
+++ b/lib/ui/grid/column.js
@@ -250,7 +250,7 @@ export class GridCheckboxColumn extends GridColumn {
 export class GridIconColumn extends GridColumn {
     static create() { return createElement('span', 'col-icon') }
 
-    static setValue(element, val, item, col, _grid) {
+    static setValue(element, val, item, col) {
         let className = col.iconClassName;
         if (typeof className === 'function') {
             className = className.call(col, item.values);
@@ -264,7 +264,7 @@ export class GridIconColumn extends GridColumn {
         if (typeof type === 'function') {
             type = type.call(col, item.values);
         }
-        type ??= 'fa-regular';
+        type ??= 'fa-light';
         if (element.dataset.type !== type || element.dataset.icon !== val) {
             const icon = createIcon(type, val);
             // const layer = element.children[0];
diff --git a/lib/ui/grid/grid.d.ts b/lib/ui/grid/grid.d.ts
index 599e1fb..b44ee7e 100644
--- a/lib/ui/grid/grid.d.ts
+++ b/lib/ui/grid/grid.d.ts
@@ -24,6 +24,22 @@ interface GridSourceItem {
     text: string;
 }
 
+/** Grid 语言资源接口 */
+interface GridLanguages {
+    /**
+     * “所有”文本
+     * 
+     * @default `( All )`
+     */
+    all: string;
+    /** “确定”文本,默认值 OK */
+    ok: string;
+    /** “重置”文本,默认值 Reset */
+    reset: string;
+    /** “空”文本,默认值 ( Null ) */
+    null: string
+}
+
 /** 列排序枚举 */
 declare enum GridColumnDirection {
     /** 倒序 */
@@ -70,7 +86,7 @@ export class Grid {
     /** 列定义的数组 */
     columns: Array<GridColumnDefinition>;
     /** 多语言资源对象 */
-    langs?: { all: string, ok: string, reset: string };
+    langs?: GridLanguages;
     /** 行数大于等于该值则启用虚模式,默认值 100 */
     virtualCount?: Number;
     /** 表格行高,默认值 36 */
@@ -112,29 +128,34 @@ export class Grid {
      * 即将选中行时触发,返回 false、null、undefined、0 等则取消选中动作
      * @param index 即将选中的行索引
      * @param colIndex 即将选中的列索引
+     * @eventProperty
      */
     willSelect?: (index: Number, colIndex: Number) => boolean;
     /**
      * 单元格单击时触发,colIndex 为 -1 则表示点击的是行的空白处,返回 false 则取消事件冒泡
      * @param index 点击的行索引
      * @param colIndex 点击的列索引
+     * @eventProperty
      */
     cellClicked?: (index: Number, colIndex: Number) => boolean;
 
     /**
      * 选中行发生变化时触发的事件
      * @param index 选中的行索引
+     * @eventProperty
      */
     onSelectedRowChanged?: (index?: Number) => void;
     /**
      * 单元格双击时触发的事件,colIndex 为 -1 则表示点击的是行的空白处
      * @param index 双击的行索引
      * @param colIndex 双击的列索引
+     * @eventProperty
      */
     onCellDblClicked?: (index: Number, colIndex: Number) => void;
     /**
      * 行双击时触发的事件
      * @param index 双击的行索引
+     * @eventProperty
      */
     onRowDblClicked?: (index: Number) => void;
     /**
@@ -145,11 +166,13 @@ export class Grid {
      * "sort" 为发生列排序事件,此时 value 为 1(升序)或 -1(倒序)
      * @param colIndex 发生变化事件的列索引
      * @param value 变化的值
+     * @eventProperty
      */
     onColumnChanged?: (type: GridColumnColumnEvent, colIndex: Number, value: Number | GridColumnDirection) => void;
     /**
      * 列滚动时触发的事件
      * @param e 滚动事件对象
+     * @eventProperty
      */
     onBodyScrolled?: (e: Event) => void;
 
diff --git a/lib/ui/grid/grid.js b/lib/ui/grid/grid.js
index 43b44e3..02472cb 100644
--- a/lib/ui/grid/grid.js
+++ b/lib/ui/grid/grid.js
@@ -17,7 +17,7 @@ const ColumnChangedType = {
 const RefreshInterval = isMobile() ? 32 : 0;
 const HoverInternal = 200;
 const RedumCount = 4;
-const MiniDragOffset = 4;
+const MiniDragOffset = 10;
 const MiniColumnWidth = 50;
 const FilterPanelWidth = 200;
 
@@ -29,15 +29,6 @@ function getClientX(e) {
     return cx ?? e.clientX;
 }
 
-function getOffsetLeftFromWindow(element) {
-    let left = 0;
-    while (element != null) {
-        left += element.offsetLeft;
-        element = element.offsetParent;
-    }
-    return left;
-}
-
 function indexOfParent(target) {
     // return [...target.parentElement.children].indexOf(target);
     return Array.prototype.indexOf.call(target.parentElement.children, target);
@@ -127,7 +118,8 @@ export class Grid {
         this.langs = {
             all: r('allItem', '( All )'),
             ok: r('ok', 'OK'),
-            reset: r('reset', 'Reset')
+            reset: r('reset', 'Reset'),
+            null: r('null', '( Null )')
         };
     }
 
@@ -190,7 +182,7 @@ export class Grid {
             this._var.currentSource = list.filter(it => {
                 for (let col of this.columns) {
                     if (Array.isArray(col.filterValues)) {
-                        const v = this._getItemValue(it.values, col.key, col.filter);
+                        const v = this._getItemProp(it.values, false, col);
                         if (col.filterValues.indexOf(v) < 0) {
                             return false;
                         }
@@ -485,8 +477,8 @@ export class Grid {
                 direction = 1;
             }
             comparer = (a, b) => {
-                a = this._getItemValue(a.values, col.key, col.filter);
-                b = this._getItemValue(b.values, col.key, col.filter);
+                a = this._getItemProp(a.values, true, col);
+                b = this._getItemProp(b.values, true, col);
                 if (a == null && typeof b === 'number') {
                     a = 0;
                 } else if (typeof a === 'number' && b == null) {
@@ -533,7 +525,7 @@ export class Grid {
         let left = 0;
         for (let col of this.columns) {
             if (col.visible === false) {
-                const hidden = createElement('th');
+                const hidden = createElement('th', 'column');
                 hidden.style.display = 'none';
                 if (col.sortable !== false) {
                     hidden.dataset.key = col.key;
@@ -544,7 +536,17 @@ export class Grid {
             }
             // style
             const isCheckbox = Grid.ColumnTypes.isCheckbox(col.type);
-            if (col.width > 0) {
+            let type = this._var.colTypes[col.key];
+            if (type == null) {
+                if (isNaN(col.type)) {
+                    type = col.type;
+                } else {
+                    type = ColumnTypes[col.type];
+                }
+                type ??= GridColumn;
+                this._var.colTypes[col.key] = type;
+            }
+            if (col.width > 0 || typeof type.createCaption === 'function') {
                 // col.autoResize = false;
             } else {
                 this._set(col.key, 'autoResize', true);
@@ -600,12 +602,19 @@ export class Grid {
                 });
                 wrapper.appendChild(check);
             }
-            const caption = createElement('span');
-            if (col.captionStyle != null) {
-                caption.style.cssText = convertCssStyle(col.captionStyle);
+            let caption;
+            if (typeof type.createCaption === 'function') {
+                caption = type.createCaption(col);
+            } else {
+                caption = createElement('span');
+                caption.innerText = col.caption ?? '';
+            }
+            if (caption instanceof HTMLElement) {
+                if (col.captionStyle != null) {
+                    caption.style.cssText = convertCssStyle(col.captionStyle);
+                }
+                wrapper.appendChild(caption);
             }
-            caption.innerText = col.caption ?? '';
-            wrapper.appendChild(caption);
             // order arrow
             if (col.sortable) {
                 th.appendChild(createElement('layer', 'arrow'));
@@ -906,13 +915,15 @@ export class Grid {
         }
     }
 
-    _changingColumnOrder(index, offset, x, offsetLeft, scrollLeft) {
+    _changingColumnOrder(index, offset, mouse, draggerCellLeft) {
         const children = this._var.refs.header.children;
         let element = children[index];
-        this._var.refs.dragger.style.cssText = `left: ${element.offsetLeft - offsetLeft + offset}px; width: ${element.style.width}; display: block`;
-        offset = x + scrollLeft - element.offsetLeft; // getOffsetLeftFromWindow(element);
+        this._var.refs.dragger.style.cssText = `left: ${element.offsetLeft - draggerCellLeft + offset}px; width: ${element.style.width}; display: block`;
+        // offset = x + gridScrollLeft - element.offsetLeft; // getOffsetLeftFromWindow(element);
+        offset += mouse;
         let idx;
-        if (offset < 0) {
+        const toLeft = offset < 0;
+        if (toLeft) {
             offset = -offset;
             for (let i = index - 1; i >= 0 && offset >= 0; i -= 1) {
                 element = children[i];
@@ -943,13 +954,27 @@ export class Grid {
             }
             idx ??= count - 1;
         }
-        if (idx !== this._var.colAttrs.__orderIndex) {
-            this._var.colAttrs.__orderIndex = idx;
+        if (idx !== this._var.colAttrs.__orderIndex || this._var.refs.draggerCursor.style.display !== 'block') {
             element = children[idx];
             if (element == null) {
                 return;
             }
-            this._var.refs.draggerCursor.style.cssText = `left: ${element.offsetLeft - offsetLeft}px; display: block`;
+            this._var.colAttrs.__orderIndex = idx;
+            // avoid `offsetLeft` of hidden header to be 0
+            let left;
+            if (element.style.display === 'none') {
+                left = 0;
+                while (left === 0 && (element = children[++idx]) != null) {
+                    left = element.offsetLeft;
+                }
+                if (!toLeft && left === 0) {
+                    left = draggerCellLeft;
+                }
+            } else {
+                left = element.offsetLeft;
+            }
+            // set position of dragger cursor
+            this._var.refs.draggerCursor.style.cssText = `left: ${left - draggerCellLeft}px; display: block`;
         }
     }
 
@@ -1057,14 +1082,21 @@ export class Grid {
         }
     }
 
-    _getItemValue(item, key, filter) {
+    _getItemProp(item, editing, col) {
         let value;
-        if (typeof filter === 'function') {
-            value = filter(item, false, this._var.refs.body);
+        if (typeof col?.filter === 'function') {
+            value = col.filter(item, editing, this._var.refs.body);
         } else {
-            value = item[key];
+            value = item[col.key];
         }
-        return value?.Value ?? value;
+        if (value == null) {
+            return value;
+        }
+        const prop = editing ? 'Value' : 'DisplayValue';
+        if (Object.prototype.hasOwnProperty.call(value, prop)) {
+            return value[prop];
+        }
+        return value;
     }
 
     _getRowTarget(target) {
@@ -1174,19 +1206,35 @@ export class Grid {
         } else {
             const dict = Object.create(null);
             for (let item of this._var.source) {
-                const val = this._getItemValue(item.values, col.key, col.filter);
-                if (!Object.hasOwnProperty.call(dict, val)) {
-                    const v = item.values[col.key];
-                    dict[val] = {
+                let displayValue = this._getItemProp(item.values, false, col);
+                if (displayValue == null) {
+                    displayValue = this.langs.null;
+                }
+                if (!Object.hasOwnProperty.call(dict, displayValue)) {
+                    const val = this._getItemProp(item.values, true, col);
+                    dict[displayValue] = {
                         Value: val,
-                        DisplayValue: typeof col.filter === 'function' ? col.filter(item.values) : v?.DisplayValue ?? v
+                        DisplayValue: displayValue
                     };
                 }
             }
             array = Object.values(dict)
                 .sort((a, b) => {
-                    a = a?.Value ?? a;
-                    b = b?.Value ?? b;
+                    if (a == null && b == null) {
+                        return 0;
+                    }
+                    if (a == null && b != null) {
+                        return -1;
+                    }
+                    if (a != null && b == null) {
+                        return 1;
+                    }
+                    if (Object.prototype.hasOwnProperty.call(a, 'Value')) {
+                        a = a.Value;
+                    }
+                    if (Object.prototype.hasOwnProperty.call(b, 'Value')) {
+                        b = b.Value;
+                    }
                     return a > b ? 1 : a < b ? -1 : 0;
                 });
         }
@@ -1197,7 +1245,7 @@ export class Grid {
             }
             return {
                 Value: i,
-                DisplayValue: i == null ? '' : i
+                DisplayValue: i == null ? this.langs.null : i
             };
         });
         this._fillFilterList(col, itemlist, array, itemall);
@@ -1207,8 +1255,16 @@ export class Grid {
             searchbox.addEventListener('input', e => {
                 const key = e.currentTarget.value.toLowerCase();
                 const items = key.length === 0 ? array : array.filter(i => {
-                    const displayValue = i?.DisplayValue ?? i;
-                    return String(displayValue ?? '').toLowerCase().includes(key);
+                    let displayValue;
+                    if (i != null && Object.prototype.hasOwnProperty.call(i, 'DisplayValue')) {
+                        displayValue = i.DisplayValue;
+                    } else {
+                        displayValue = i;
+                    }
+                    if (displayValue == null) {
+                        displayValue = this.langs.null;
+                    }
+                    return String(displayValue).toLowerCase().includes(key);
                 });
                 this._fillFilterList(col, itemlist, items, itemall);
             });
@@ -1223,7 +1279,7 @@ export class Grid {
                     if (typeof col.onFilterOk === 'function') {
                         col.onFilterOk.call(this, col, array);
                     } else {
-                        col.filterValues = array.map(a => a.Value);
+                        col.filterValues = array.map(a => a.DisplayValue);
                     }
                     this._var.colAttrs.__filtered = true;
                     this._refreshSource();
@@ -1268,7 +1324,8 @@ export class Grid {
         content.style.top = `${rowHeight}px`;
         this._set(col.key, 'filterSource', array);
         for (let item of array) {
-            item.__checked = !Array.isArray(col.filterValues) || col.filterValues.includes(item.Value ?? item);
+            const v = Object.prototype.hasOwnProperty.call(item, 'DisplayValue') ? item.DisplayValue : item;
+            item.__checked = !Array.isArray(col.filterValues) || col.filterValues.includes(v);
         }
         if (array.length > 12) {
             array = array.slice(0, 12);
@@ -1282,7 +1339,7 @@ export class Grid {
             const div = createElement('div', 'filter-item');
             div.appendChild(createCheckbox({
                 checked: item.__checked,
-                label: item?.DisplayValue ?? item,
+                label: Object.prototype.hasOwnProperty.call(item, 'DisplayValue') ? item.DisplayValue : item,
                 onchange: e => {
                     item.__checked = e.target.checked;
                     all.querySelector('input').checked = ![...content.querySelectorAll('input')].some(i => !i.checked);
@@ -1333,7 +1390,7 @@ export class Grid {
         const cx = getClientX(e);
         const clearEvents = attr => {
             for (let event of ['mousemove', 'mouseup']) {
-                if (attr.hasOwnProperty(event)) {
+                if (Object.prototype.hasOwnProperty.call(attr, event)) {
                     window.removeEventListener(event, attr[event]);
                     delete attr[event];
                 }
@@ -1346,20 +1403,28 @@ export class Grid {
             clearEvents(attr);
         }
         attr.dragging = true;
-        const offsetLeft = this._var.refs.header.querySelector('th:last-child').offsetLeft;
-        const scrollLeft = this._var.el.scrollLeft;
+        const draggerCellLeft = this._var.refs.header.querySelector('th:last-child').offsetLeft;
+        // const gridScrollLeft = this._var.el.scrollLeft;
+        let p = this._var.el;
+        let gridLeftFromWindow = p.offsetLeft;
+        while ((p = p.offsetParent) != null) {
+            gridLeftFromWindow += p.offsetLeft + p.clientLeft;
+        }
+        const mouse = cx - e.currentTarget.offsetLeft + this._var.el.scrollLeft - gridLeftFromWindow;
         const dragmove = e => {
             const cx2 = getClientX(e);
             const offset = cx2 - cx;
             let pos = attr.offset;
             let dragging;
-            if (pos == null && (offset > MiniDragOffset || offset < -MiniDragOffset)) {
-                dragging = true;
+            if (pos == null) {
+                if (offset > MiniDragOffset || offset < -MiniDragOffset) {
+                    dragging = true;
+                }
             } else if (pos !== offset) {
                 dragging = true;
             }
             if (dragging) {
-                this._changingColumnOrder(index, offset, cx2, offsetLeft, scrollLeft);
+                this._changingColumnOrder(index, offset, mouse, draggerCellLeft);
                 attr.offset = offset;
             }
         };
@@ -1386,7 +1451,7 @@ export class Grid {
         const window = this.window ?? global;
         const clearEvents = attr => {
             for (let event of ['mousemove', 'mouseup']) {
-                if (attr.hasOwnProperty(event)) {
+                if (Object.prototype.hasOwnProperty.call(attr, event)) {
                     window.removeEventListener(event, attr[event]);
                     delete attr[event];
                 }
@@ -1648,7 +1713,7 @@ export class Grid {
         if (enabled !== false) {
             const val = item[col.key];
             let oldValue;
-            if (val?.Value != null) {
+            if (val != null && Object.prototype.hasOwnProperty.call(val, 'Value') != null) {
                 oldValue = val.Value;
                 val.Value = value;
             } else {