@revolist/revogrid 4.25.2 → 4.26.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.
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * Built by Revolist OU ❤️
3
3
  */
4
- import { J as reduce, g as getRange, K as baseEach, C as getColumnType, c as columnTypes, L as toInteger, u as isGrouping, t as getGroupingName, r as rowTypes, B as getCellDataParsed, A as getCellRaw, j as GROUP_COLUMN_PROP, I as getColumnByProp, h as GROUP_EXPANDED, x as getParsedGroup, y as isSameGroup, G as GROUP_DEPTH, e as PSEUDO_GROUP_ITEM_VALUE, d as PSEUDO_GROUP_ITEM_ID, o as GROUPING_ROW_TYPE, p as getSource, f as PSEUDO_GROUP_COLUMN, s as gatherGrouping, m as GROUP_EXPAND_EVENT, v as isGroupingColumn, q as getExpanded, E as isColGrouping } from './column.service-Cfw3L0PN.js';
4
+ import { J as reduce, g as getRange, K as baseEach, C as getColumnType, c as columnTypes, L as toInteger, u as isGrouping, t as getGroupingName, r as rowTypes, A as getCellRaw, j as GROUP_COLUMN_PROP, I as getColumnByProp, h as GROUP_EXPANDED, x as getParsedGroup, y as isSameGroup, G as GROUP_DEPTH, e as PSEUDO_GROUP_ITEM_VALUE, d as PSEUDO_GROUP_ITEM_ID, o as GROUPING_ROW_TYPE, p as getSource, f as PSEUDO_GROUP_COLUMN, s as gatherGrouping, m as GROUP_EXPAND_EVENT, v as isGroupingColumn, q as getExpanded, E as isColGrouping } from './column.service-Cfw3L0PN.js';
5
5
  import { K as createStore, l as setStore, i as calculateDimensionData, L as identity, N as isArray, b as getSourceItem, g as getPhysical, e as setItems, j as getItemByPosition } from './dimension.helpers-fl4HSSK9.js';
6
6
  import { j as calculateRowHeaderSize } from './viewport.store-BSXmsiZc.js';
7
7
  import { g as getScrollbarSize, t as timeout } from './index-D24ku9ej.js';
@@ -1223,8 +1223,84 @@ const lsEq = function (value, extra) {
1223
1223
  };
1224
1224
  lsEq.extra = 'input';
1225
1225
 
1226
- const set = (value) => !(value === '' || value === null || value === void 0);
1227
- const notSet = (value) => !set(value);
1226
+ const DEFAULT_BLANK_SEMANTICS = Object.freeze({
1227
+ null: true,
1228
+ undefined: true,
1229
+ emptyString: true,
1230
+ whitespaceOnlyString: false,
1231
+ emptyArray: false,
1232
+ missingProperty: true,
1233
+ });
1234
+ const BOOLEAN_FIELDS = [
1235
+ 'null',
1236
+ 'undefined',
1237
+ 'emptyString',
1238
+ 'whitespaceOnlyString',
1239
+ 'emptyArray',
1240
+ 'missingProperty',
1241
+ ];
1242
+ /** Merge a grid policy and a partial column policy over Core defaults. */
1243
+ function resolveBlankSemantics(gridPolicy, columnPolicy) {
1244
+ const resolved = Object.assign({}, DEFAULT_BLANK_SEMANTICS);
1245
+ for (const policy of [gridPolicy, columnPolicy]) {
1246
+ if (!policy) {
1247
+ continue;
1248
+ }
1249
+ for (const field of BOOLEAN_FIELDS) {
1250
+ if (policy[field] !== undefined) {
1251
+ resolved[field] = policy[field];
1252
+ }
1253
+ }
1254
+ if (policy.isBlank !== undefined) {
1255
+ resolved.isBlank = policy.isBlank;
1256
+ }
1257
+ }
1258
+ return resolved;
1259
+ }
1260
+ /** Evaluate blankness from the unparsed source value and property presence. */
1261
+ function isBlankValue(value, context) {
1262
+ const policy = context.blankSemantics;
1263
+ let fallbackResult;
1264
+ if (!context.hasOwnProperty) {
1265
+ fallbackResult = policy.missingProperty === true;
1266
+ }
1267
+ else if (value === null) {
1268
+ fallbackResult = policy.null === true;
1269
+ }
1270
+ else if (value === undefined) {
1271
+ fallbackResult = policy.undefined === true;
1272
+ }
1273
+ else if (value === '') {
1274
+ fallbackResult = policy.emptyString === true;
1275
+ }
1276
+ else if (typeof value === 'string' &&
1277
+ value.length > 0 &&
1278
+ value.trim() === '') {
1279
+ fallbackResult = policy.whitespaceOnlyString === true;
1280
+ }
1281
+ else if (Array.isArray(value) && value.length === 0) {
1282
+ fallbackResult = policy.emptyArray === true;
1283
+ }
1284
+ else {
1285
+ fallbackResult = false;
1286
+ }
1287
+ return policy.isBlank
1288
+ ? policy.isBlank(value, context, fallbackResult)
1289
+ : fallbackResult;
1290
+ }
1291
+
1292
+ function blankContext(value, context) {
1293
+ return context !== null && context !== void 0 ? context : {
1294
+ model: {},
1295
+ property: '',
1296
+ sourceValue: value,
1297
+ parsedValue: value,
1298
+ hasOwnProperty: true,
1299
+ blankSemantics: DEFAULT_BLANK_SEMANTICS,
1300
+ };
1301
+ }
1302
+ const notSet = (value, _extra, context) => isBlankValue(context ? context.sourceValue : value, blankContext(value, context));
1303
+ const set = (value, _extra, context) => !notSet(value, _extra, context);
1228
1304
 
1229
1305
  const beginsWith = (value, extra) => {
1230
1306
  if (!value) {
@@ -1284,11 +1360,13 @@ const filterCoreFunctionsIndexedByType = {
1284
1360
  const filterTypes = {
1285
1361
  string: ['notEmpty', 'empty', 'eq', 'notEq', 'begins', 'contains', 'notContains'],
1286
1362
  number: ['notEmpty', 'empty', 'eqN', 'neqN', 'gt', 'gte', 'lt', 'lte'],
1363
+ boolean: ['notEmpty', 'empty'],
1364
+ array: ['notEmpty', 'empty'],
1287
1365
  };
1288
1366
  const filterNames = {
1289
1367
  none: 'None',
1290
- empty: 'Not set',
1291
- notEmpty: 'Set',
1368
+ empty: 'Is blank',
1369
+ notEmpty: 'Is not blank',
1292
1370
  eq: 'Equal',
1293
1371
  notEq: 'Not equal',
1294
1372
  begins: 'Begins with',
@@ -1411,6 +1489,7 @@ class FilterPlugin extends BasePlugin {
1411
1489
  }
1412
1490
  initConfig(config) {
1413
1491
  var _a;
1492
+ this.config = config;
1414
1493
  this.allowDuplicateOperators = (_a = config.allowDuplicateOperators) !== null && _a !== void 0 ? _a : true;
1415
1494
  if (this.pop) {
1416
1495
  this.pop.allowDuplicateOperators = this.allowDuplicateOperators;
@@ -1627,38 +1706,56 @@ class FilterPlugin extends BasePlugin {
1627
1706
  * Get trimmed rows based on filter
1628
1707
  */
1629
1708
  getRowFilter(rows, filterItems, columnByProp) {
1709
+ var _a, _b;
1630
1710
  const propKeys = Object.keys(filterItems);
1711
+ const blankSemanticsByProp = {};
1712
+ for (const prop of propKeys) {
1713
+ blankSemanticsByProp[prop] = resolveBlankSemantics((_a = this.config) === null || _a === void 0 ? void 0 : _a.blankSemantics, (_b = columnByProp[prop]) === null || _b === void 0 ? void 0 : _b.blankSemantics);
1714
+ }
1631
1715
  const trimmed = {};
1632
1716
  // each rows
1633
1717
  for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
1634
1718
  // check filter by column properties
1635
1719
  for (const prop of propKeys) {
1636
1720
  // add to the list of removed/trimmed rows of filter condition is satisfied
1637
- if (this.shouldTrimRow(filterItems[prop], prop, columnByProp[prop], rows[rowIndex])) {
1721
+ if (this.shouldTrimRow(filterItems[prop], prop, blankSemanticsByProp[prop], columnByProp[prop], rows[rowIndex])) {
1638
1722
  trimmed[rowIndex] = true;
1639
1723
  }
1640
1724
  } // end of for-of propKeys
1641
1725
  }
1642
1726
  return trimmed;
1643
1727
  }
1644
- shouldTrimRow(propFilters, prop, column, model = {}) {
1728
+ shouldTrimRow(propFilters, prop, blankSemantics, column, model = {}) {
1645
1729
  // reset the count of satisfied filters
1646
1730
  let propFilterSatisfiedCount = 0;
1647
1731
  // reset the array of last filter results
1648
1732
  let lastFilterResults = [];
1733
+ // THE MAGIC OF FILTERING IS HERE
1734
+ // If there is no column but user wants to filter by a property
1735
+ const hasOwnProperty = Object.hasOwn(model, prop);
1736
+ const sourceValue = model[prop];
1737
+ const parsedValue = (column === null || column === void 0 ? void 0 : column.cellParser)
1738
+ ? column.cellParser(model, column)
1739
+ : sourceValue;
1740
+ const context = {
1741
+ model,
1742
+ column,
1743
+ property: prop,
1744
+ sourceValue,
1745
+ parsedValue,
1746
+ hasOwnProperty,
1747
+ blankSemantics,
1748
+ };
1649
1749
  // testing each filter for a prop
1650
1750
  for (const [filterIndex, filterData] of propFilters.entries()) {
1651
1751
  // the filter LogicFunction based on the type
1652
1752
  const filterFunc = this.filterFunctionsIndexedByType[filterData.type];
1653
- // THE MAGIC OF FILTERING IS HERE
1654
- // If there is no column but user wants to filter by a property
1655
- const value = column ? getCellDataParsed(model, column) : model[prop];
1656
1753
  // OR relation
1657
1754
  if (filterData.relation === 'or') {
1658
1755
  // reset the array of last filter results
1659
1756
  lastFilterResults = [];
1660
1757
  // if the filter is satisfied, continue to the next filter
1661
- if (filterFunc(value, filterData.value)) {
1758
+ if (filterFunc(parsedValue, filterData.value, context)) {
1662
1759
  continue;
1663
1760
  }
1664
1761
  // if the filter is not satisfied, count it
@@ -1668,7 +1765,7 @@ class FilterPlugin extends BasePlugin {
1668
1765
  else {
1669
1766
  // 'and' relation will need to know the next filter
1670
1767
  // so we save this current filter to include it in the next filter
1671
- lastFilterResults.push(!filterFunc(value, filterData.value));
1768
+ lastFilterResults.push(!filterFunc(parsedValue, filterData.value, context));
1672
1769
  if (isFinalAndFilter(filterIndex, propFilters)) {
1673
1770
  // let's just continue since for sure propFilterSatisfiedCount cannot be satisfied
1674
1771
  if (allAndConditionsSatisfied(lastFilterResults)) {
@@ -2951,4 +3048,4 @@ function getColumnDragPosition(targetItem, startItem, renderOffset, scrollOffset
2951
3048
  return insertionEdge - renderOffset + scrollOffset;
2952
3049
  }
2953
3050
 
2954
- export { AutoSizeColumnPlugin as A, BasePlugin as B, ColumnAutoSizeMode as C, DimensionStore as D, ExportFilePlugin as E, FILTER_TRIMMED_TYPE as F, GroupingRowPlugin as G, getNextOrder as H, getComparer as I, SelectionStore as S, StretchColumn as a, ExportCsv as b, FILTER_CONFIG_CHANGED_EVENT as c, defineTheme as d, FILTE_PANEL as e, FilterPlugin as f, filterCoreFunctionsIndexedByType as g, filterTypes as h, isStretchPlugin as i, filterNames as j, doCollapse as k, doExpand as l, COLUMN_DRAG_MOVE_EVENT as m, COLUMN_DRAG_END_EVENT as n, BEFORE_COLUMN_DRAG_END_EVENT as o, COLUMN_DRAG_START_EVENT as p, ColumnMovePlugin as q, getLeftRelative as r, getColumnDragPosition as s, themeTokenCssVariables as t, SortingPlugin as u, hasActiveSorting as v, getSortingIndex as w, sortIndexByItems as x, defaultCellCompare as y, descCellCompare as z };
3051
+ export { AutoSizeColumnPlugin as A, BasePlugin as B, ColumnAutoSizeMode as C, DimensionStore as D, ExportFilePlugin as E, FILTER_TRIMMED_TYPE as F, GroupingRowPlugin as G, sortIndexByItems as H, defaultCellCompare as I, descCellCompare as J, getNextOrder as K, getComparer as L, SelectionStore as S, StretchColumn as a, ExportCsv as b, FILTER_CONFIG_CHANGED_EVENT as c, defineTheme as d, FILTE_PANEL as e, FilterPlugin as f, filterCoreFunctionsIndexedByType as g, filterTypes as h, isStretchPlugin as i, filterNames as j, DEFAULT_BLANK_SEMANTICS as k, isBlankValue as l, doCollapse as m, doExpand as n, COLUMN_DRAG_MOVE_EVENT as o, COLUMN_DRAG_END_EVENT as p, BEFORE_COLUMN_DRAG_END_EVENT as q, resolveBlankSemantics as r, COLUMN_DRAG_START_EVENT as s, themeTokenCssVariables as t, ColumnMovePlugin as u, getLeftRelative as v, getColumnDragPosition as w, SortingPlugin as x, hasActiveSorting as y, getSortingIndex as z };
@@ -1,8 +1,8 @@
1
1
  /*!
2
2
  * Built by Revolist OU ❤️
3
3
  */
4
- import { d as defineTheme, B as BasePlugin } from './column.drag.plugin-DKgC_0I1.js';
5
- export { A as AutoSizeColumnPlugin, o as BEFORE_COLUMN_DRAG_END_EVENT, n as COLUMN_DRAG_END_EVENT, m as COLUMN_DRAG_MOVE_EVENT, p as COLUMN_DRAG_START_EVENT, C as ColumnAutoSizeMode, q as ColumnMovePlugin, D as DimensionStore, b as ExportCsv, E as ExportFilePlugin, c as FILTER_CONFIG_CHANGED_EVENT, F as FILTER_TRIMMED_TYPE, e as FILTE_PANEL, f as FilterPlugin, G as GroupingRowPlugin, S as SelectionStore, u as SortingPlugin, a as StretchColumn, y as defaultCellCompare, z as descCellCompare, k as doCollapse, l as doExpand, g as filterCoreFunctionsIndexedByType, j as filterNames, h as filterTypes, s as getColumnDragPosition, I as getComparer, r as getLeftRelative, H as getNextOrder, w as getSortingIndex, v as hasActiveSorting, i as isStretchPlugin, x as sortIndexByItems, t as themeTokenCssVariables } from './column.drag.plugin-DKgC_0I1.js';
4
+ import { d as defineTheme, B as BasePlugin } from './column.drag.plugin-DDBKeTGI.js';
5
+ export { A as AutoSizeColumnPlugin, q as BEFORE_COLUMN_DRAG_END_EVENT, p as COLUMN_DRAG_END_EVENT, o as COLUMN_DRAG_MOVE_EVENT, s as COLUMN_DRAG_START_EVENT, C as ColumnAutoSizeMode, u as ColumnMovePlugin, k as DEFAULT_BLANK_SEMANTICS, D as DimensionStore, b as ExportCsv, E as ExportFilePlugin, c as FILTER_CONFIG_CHANGED_EVENT, F as FILTER_TRIMMED_TYPE, e as FILTE_PANEL, f as FilterPlugin, G as GroupingRowPlugin, S as SelectionStore, x as SortingPlugin, a as StretchColumn, I as defaultCellCompare, J as descCellCompare, m as doCollapse, n as doExpand, g as filterCoreFunctionsIndexedByType, j as filterNames, h as filterTypes, w as getColumnDragPosition, L as getComparer, v as getLeftRelative, K as getNextOrder, z as getSortingIndex, y as hasActiveSorting, l as isBlankValue, i as isStretchPlugin, r as resolveBlankSemantics, H as sortIndexByItems, t as themeTokenCssVariables } from './column.drag.plugin-DDBKeTGI.js';
6
6
  export { o as GROUPING_ROW_TYPE, j as GROUP_COLUMN_PROP, G as GROUP_DEPTH, h as GROUP_EXPANDED, l as GROUP_EXPAND_BTN, m as GROUP_EXPAND_EVENT, k as GROUP_ORIGINAL_INDEX, f as PSEUDO_GROUP_COLUMN, P as PSEUDO_GROUP_ITEM, d as PSEUDO_GROUP_ITEM_ID, e as PSEUDO_GROUP_ITEM_VALUE, c as columnTypes, a as cropCellToMax, H as gatherGroup, s as gatherGrouping, z as getCellData, B as getCellDataParsed, A as getCellRaw, I as getColumnByProp, D as getColumnSizes, C as getColumnType, F as getColumns, q as getExpanded, t as getGroupingName, x as getParsedGroup, g as getRange, p as getSource, E as isColGrouping, u as isGrouping, v as isGroupingColumn, b as isRangeSingleCell, i as isRowType, y as isSameGroup, w as measureEqualDepth, n as nextCell, r as rowTypes } from './column.service-Cfw3L0PN.js';
7
7
  export { d as dispatch, a as dispatchByEvent } from './header-cell-renderer-Cqtg1ETB.js';
8
8
  export { C as CellRenderer, G as GroupingRowRenderer, S as SortingSign, e as expandEvent, a as expandSvgIconVNode, r as renderGroupCells } from './cell-renderer-11cf-lu3.js';
@@ -6,7 +6,7 @@ import { c as columnTypes, J as reduce, C as getColumnType, r as rowTypes, i as
6
6
  import { D as DataStore, b as getSourceItem, f as getSourceItemVirtualIndexByProp, d as setSourceByPhysicalIndex, s as setSourceByVirtualIndex, a as getVisibleSourceItem, h as gatherTrimmedItems, k as getItemByIndex, R as RESIZE_INTERVAL } from './dimension.helpers-fl4HSSK9.js';
7
7
  import { d as debounce } from './debounce-PCRWZliA.js';
8
8
  import { g as getScrollDimension, v as viewportDataPartition, F as FOOTER_SLOT, C as CONTENT_SLOT, H as HEADER_SLOT, D as DATA_SLOT } from './viewport.helpers-CoCAvmZs.js';
9
- import { D as DimensionStore, t as themeTokenCssVariables, S as SelectionStore, B as BasePlugin, G as GroupingRowPlugin, a as StretchColumn, i as isStretchPlugin, A as AutoSizeColumnPlugin, f as FilterPlugin, E as ExportFilePlugin, u as SortingPlugin, q as ColumnMovePlugin } from './column.drag.plugin-DKgC_0I1.js';
9
+ import { D as DimensionStore, t as themeTokenCssVariables, S as SelectionStore, B as BasePlugin, G as GroupingRowPlugin, a as StretchColumn, i as isStretchPlugin, A as AutoSizeColumnPlugin, f as FilterPlugin, E as ExportFilePlugin, x as SortingPlugin, u as ColumnMovePlugin } from './column.drag.plugin-DDBKeTGI.js';
10
10
  import { V as ViewportStore } from './viewport.store-BSXmsiZc.js';
11
11
  import { t as timeout } from './index-D24ku9ej.js';
12
12
  import { g as getPropertyFromEvent } from './events-BvSmBueA.js';
@@ -1,4 +1,4 @@
1
1
  import { LogicFunction } from '../filter.types';
2
- declare const set: LogicFunction;
3
2
  export declare const notSet: LogicFunction;
3
+ declare const set: LogicFunction;
4
4
  export default set;
@@ -0,0 +1,6 @@
1
+ import type { BlankSemantics, FilterEvaluationContext } from './filter.types';
2
+ export declare const DEFAULT_BLANK_SEMANTICS: Readonly<BlankSemantics>;
3
+ /** Merge a grid policy and a partial column policy over Core defaults. */
4
+ export declare function resolveBlankSemantics(gridPolicy?: BlankSemantics, columnPolicy?: BlankSemantics): BlankSemantics;
5
+ /** Evaluate blankness from the unparsed source value and property presence. */
6
+ export declare function isBlankValue(value: any, context: FilterEvaluationContext): boolean;
@@ -5,6 +5,7 @@ import type { ColumnFilterConfig, FilterCollectionItem, LogicFunction, MultiFilt
5
5
  import { TrimmedEntity } from "../../store/index";
6
6
  export * from './filter.types';
7
7
  export * from './filter.indexed';
8
+ export * from './filter.blank';
8
9
  export * from './filter.button';
9
10
  export declare const FILTER_TRIMMED_TYPE = "filter";
10
11
  export declare const FILTER_CONFIG_CHANGED_EVENT = "filterconfigchanged";
@@ -1,4 +1,4 @@
1
- import type { ColumnProp, ColumnRegular, HyperFunc } from "../../types/index";
1
+ import type { ColumnProp, ColumnRegular, DataType, HyperFunc } from "../../types/index";
2
2
  import type { FilterType } from './filter.indexed';
3
3
  import type { VNode } from '../../stencil-public-runtime';
4
4
  export type DateEnum = 'today' | 'yesterday' | 'tomorrow' | 'thisweek' | 'lastweek' | 'nextweek' | 'thismonth' | 'lastmonth' | 'nextmonth' | 'thisyear' | 'lastyear' | 'nextyear';
@@ -13,8 +13,36 @@ export type ExtraField = 'input' | 'datepicker' | ((h: HyperFunc<VNode>, config:
13
13
  }) => VNode | VNode[]);
14
14
  export type LogicFunctionParam = any;
15
15
  export type LogicFunctionExtraParam = 'select' | 'input' | 'multi' | 'datepicker' | number | Date | DateEnum | null | undefined | string | string[] | number[];
16
+ /** Source-aware context supplied while a filter predicate is evaluated. */
17
+ export interface FilterEvaluationContext<TModel extends DataType = DataType, TColumn extends ColumnRegular = ColumnRegular> {
18
+ /** Row model being evaluated. */
19
+ model: TModel;
20
+ /** Column associated with the filter, when one is available. */
21
+ column?: TColumn;
22
+ /** Property the filter is evaluating. */
23
+ property: ColumnProp;
24
+ /** Unparsed value read from the row model. */
25
+ sourceValue: any;
26
+ /** Value after the column cell parser, when configured. */
27
+ parsedValue: any;
28
+ /** Whether the property exists directly on the row model. */
29
+ hasOwnProperty: boolean;
30
+ /** Effective blank policy after grid and column settings are merged. */
31
+ blankSemantics: BlankSemantics;
32
+ }
33
+ /** Configures which source values are treated as blank by blank operators. */
34
+ export interface BlankSemantics {
35
+ null?: boolean;
36
+ undefined?: boolean;
37
+ emptyString?: boolean;
38
+ whitespaceOnlyString?: boolean;
39
+ emptyArray?: boolean;
40
+ missingProperty?: boolean;
41
+ /** Final application override, evaluated after the configured fallback. */
42
+ isBlank?: (value: any, context: FilterEvaluationContext, fallbackResult: boolean) => boolean;
43
+ }
16
44
  export interface LogicFunction<T1 = LogicFunctionParam, T2 = LogicFunctionExtraParam> {
17
- (value: T1, extra?: T2): boolean;
45
+ (value: T1, extra?: T2, context?: FilterEvaluationContext): boolean;
18
46
  extra?: ExtraField;
19
47
  }
20
48
  export interface CustomFilter<T1 = LogicFunctionParam, T2 = LogicFunctionExtraParam> {
@@ -53,6 +81,8 @@ export interface FilterLocalization {
53
81
  * Filter configuration for a column. This is the type of the `filter` property on a column.
54
82
  */
55
83
  export interface ColumnFilterConfig {
84
+ /** Grid-level blank policy. Individual columns can override fields. */
85
+ blankSemantics?: BlankSemantics;
56
86
  /**
57
87
  * The collection of filters to be applied to the column.
58
88
  */
@@ -3,6 +3,7 @@ import type { DimensionCols, DimensionRows, DimensionColPin, DimensionType, Mult
3
3
  import type { Cell, EditorCtr, FocusedCells, OldNewRangeMapping, RangeArea, SelectionStoreState } from './selection';
4
4
  import type { Observable } from '../utils';
5
5
  import type { JSXBase } from '../stencil-public-runtime';
6
+ import type { BlankSemantics } from '../plugins/filter/filter.types';
6
7
  export type Nullable<T> = {
7
8
  [P in keyof T]: T[P] | null;
8
9
  };
@@ -194,6 +195,8 @@ export interface ColumnRegular<P extends ColumnProp = ColumnProp, TModel extends
194
195
  * Filter. Require filter plugin to be installed and activated through grid config filter.
195
196
  */
196
197
  filter?: boolean | string | string[];
198
+ /** Partial column override for the grid-level blank filtering policy. */
199
+ blankSemantics?: BlankSemantics;
197
200
  /**
198
201
  * Is column can be sorted, check cellCompare function for custom sorting.
199
202
  */
package/hydrate/index.js CHANGED
@@ -16158,8 +16158,84 @@ const lsEq = function (value, extra) {
16158
16158
  };
16159
16159
  lsEq.extra = 'input';
16160
16160
 
16161
- const set = (value) => !(value === '' || value === null || value === void 0);
16162
- const notSet = (value) => !set(value);
16161
+ const DEFAULT_BLANK_SEMANTICS = Object.freeze({
16162
+ null: true,
16163
+ undefined: true,
16164
+ emptyString: true,
16165
+ whitespaceOnlyString: false,
16166
+ emptyArray: false,
16167
+ missingProperty: true,
16168
+ });
16169
+ const BOOLEAN_FIELDS = [
16170
+ 'null',
16171
+ 'undefined',
16172
+ 'emptyString',
16173
+ 'whitespaceOnlyString',
16174
+ 'emptyArray',
16175
+ 'missingProperty',
16176
+ ];
16177
+ /** Merge a grid policy and a partial column policy over Core defaults. */
16178
+ function resolveBlankSemantics(gridPolicy, columnPolicy) {
16179
+ const resolved = Object.assign({}, DEFAULT_BLANK_SEMANTICS);
16180
+ for (const policy of [gridPolicy, columnPolicy]) {
16181
+ if (!policy) {
16182
+ continue;
16183
+ }
16184
+ for (const field of BOOLEAN_FIELDS) {
16185
+ if (policy[field] !== undefined) {
16186
+ resolved[field] = policy[field];
16187
+ }
16188
+ }
16189
+ if (policy.isBlank !== undefined) {
16190
+ resolved.isBlank = policy.isBlank;
16191
+ }
16192
+ }
16193
+ return resolved;
16194
+ }
16195
+ /** Evaluate blankness from the unparsed source value and property presence. */
16196
+ function isBlankValue(value, context) {
16197
+ const policy = context.blankSemantics;
16198
+ let fallbackResult;
16199
+ if (!context.hasOwnProperty) {
16200
+ fallbackResult = policy.missingProperty === true;
16201
+ }
16202
+ else if (value === null) {
16203
+ fallbackResult = policy.null === true;
16204
+ }
16205
+ else if (value === undefined) {
16206
+ fallbackResult = policy.undefined === true;
16207
+ }
16208
+ else if (value === '') {
16209
+ fallbackResult = policy.emptyString === true;
16210
+ }
16211
+ else if (typeof value === 'string' &&
16212
+ value.length > 0 &&
16213
+ value.trim() === '') {
16214
+ fallbackResult = policy.whitespaceOnlyString === true;
16215
+ }
16216
+ else if (Array.isArray(value) && value.length === 0) {
16217
+ fallbackResult = policy.emptyArray === true;
16218
+ }
16219
+ else {
16220
+ fallbackResult = false;
16221
+ }
16222
+ return policy.isBlank
16223
+ ? policy.isBlank(value, context, fallbackResult)
16224
+ : fallbackResult;
16225
+ }
16226
+
16227
+ function blankContext(value, context) {
16228
+ return context !== null && context !== void 0 ? context : {
16229
+ model: {},
16230
+ property: '',
16231
+ sourceValue: value,
16232
+ parsedValue: value,
16233
+ hasOwnProperty: true,
16234
+ blankSemantics: DEFAULT_BLANK_SEMANTICS,
16235
+ };
16236
+ }
16237
+ const notSet = (value, _extra, context) => isBlankValue(context ? context.sourceValue : value, blankContext(value, context));
16238
+ const set = (value, _extra, context) => !notSet(value, _extra, context);
16163
16239
 
16164
16240
  const beginsWith = (value, extra) => {
16165
16241
  if (!value) {
@@ -16219,11 +16295,13 @@ const filterCoreFunctionsIndexedByType = {
16219
16295
  const filterTypes = {
16220
16296
  string: ['notEmpty', 'empty', 'eq', 'notEq', 'begins', 'contains', 'notContains'],
16221
16297
  number: ['notEmpty', 'empty', 'eqN', 'neqN', 'gt', 'gte', 'lt', 'lte'],
16298
+ boolean: ['notEmpty', 'empty'],
16299
+ array: ['notEmpty', 'empty'],
16222
16300
  };
16223
16301
  const filterNames = {
16224
16302
  none: 'None',
16225
- empty: 'Not set',
16226
- notEmpty: 'Set',
16303
+ empty: 'Is blank',
16304
+ notEmpty: 'Is not blank',
16227
16305
  eq: 'Equal',
16228
16306
  notEq: 'Not equal',
16229
16307
  begins: 'Begins with',
@@ -16346,6 +16424,7 @@ class FilterPlugin extends BasePlugin {
16346
16424
  }
16347
16425
  initConfig(config) {
16348
16426
  var _a;
16427
+ this.config = config;
16349
16428
  this.allowDuplicateOperators = (_a = config.allowDuplicateOperators) !== null && _a !== void 0 ? _a : true;
16350
16429
  if (this.pop) {
16351
16430
  this.pop.allowDuplicateOperators = this.allowDuplicateOperators;
@@ -16562,38 +16641,56 @@ class FilterPlugin extends BasePlugin {
16562
16641
  * Get trimmed rows based on filter
16563
16642
  */
16564
16643
  getRowFilter(rows, filterItems, columnByProp) {
16644
+ var _a, _b;
16565
16645
  const propKeys = Object.keys(filterItems);
16646
+ const blankSemanticsByProp = {};
16647
+ for (const prop of propKeys) {
16648
+ blankSemanticsByProp[prop] = resolveBlankSemantics((_a = this.config) === null || _a === void 0 ? void 0 : _a.blankSemantics, (_b = columnByProp[prop]) === null || _b === void 0 ? void 0 : _b.blankSemantics);
16649
+ }
16566
16650
  const trimmed = {};
16567
16651
  // each rows
16568
16652
  for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
16569
16653
  // check filter by column properties
16570
16654
  for (const prop of propKeys) {
16571
16655
  // add to the list of removed/trimmed rows of filter condition is satisfied
16572
- if (this.shouldTrimRow(filterItems[prop], prop, columnByProp[prop], rows[rowIndex])) {
16656
+ if (this.shouldTrimRow(filterItems[prop], prop, blankSemanticsByProp[prop], columnByProp[prop], rows[rowIndex])) {
16573
16657
  trimmed[rowIndex] = true;
16574
16658
  }
16575
16659
  } // end of for-of propKeys
16576
16660
  }
16577
16661
  return trimmed;
16578
16662
  }
16579
- shouldTrimRow(propFilters, prop, column, model = {}) {
16663
+ shouldTrimRow(propFilters, prop, blankSemantics, column, model = {}) {
16580
16664
  // reset the count of satisfied filters
16581
16665
  let propFilterSatisfiedCount = 0;
16582
16666
  // reset the array of last filter results
16583
16667
  let lastFilterResults = [];
16668
+ // THE MAGIC OF FILTERING IS HERE
16669
+ // If there is no column but user wants to filter by a property
16670
+ const hasOwnProperty = Object.hasOwn(model, prop);
16671
+ const sourceValue = model[prop];
16672
+ const parsedValue = (column === null || column === void 0 ? void 0 : column.cellParser)
16673
+ ? column.cellParser(model, column)
16674
+ : sourceValue;
16675
+ const context = {
16676
+ model,
16677
+ column,
16678
+ property: prop,
16679
+ sourceValue,
16680
+ parsedValue,
16681
+ hasOwnProperty,
16682
+ blankSemantics,
16683
+ };
16584
16684
  // testing each filter for a prop
16585
16685
  for (const [filterIndex, filterData] of propFilters.entries()) {
16586
16686
  // the filter LogicFunction based on the type
16587
16687
  const filterFunc = this.filterFunctionsIndexedByType[filterData.type];
16588
- // THE MAGIC OF FILTERING IS HERE
16589
- // If there is no column but user wants to filter by a property
16590
- const value = column ? getCellDataParsed(model, column) : model[prop];
16591
16688
  // OR relation
16592
16689
  if (filterData.relation === 'or') {
16593
16690
  // reset the array of last filter results
16594
16691
  lastFilterResults = [];
16595
16692
  // if the filter is satisfied, continue to the next filter
16596
- if (filterFunc(value, filterData.value)) {
16693
+ if (filterFunc(parsedValue, filterData.value, context)) {
16597
16694
  continue;
16598
16695
  }
16599
16696
  // if the filter is not satisfied, count it
@@ -16603,7 +16700,7 @@ class FilterPlugin extends BasePlugin {
16603
16700
  else {
16604
16701
  // 'and' relation will need to know the next filter
16605
16702
  // so we save this current filter to include it in the next filter
16606
- lastFilterResults.push(!filterFunc(value, filterData.value));
16703
+ lastFilterResults.push(!filterFunc(parsedValue, filterData.value, context));
16607
16704
  if (isFinalAndFilter(filterIndex, propFilters)) {
16608
16705
  // let's just continue since for sure propFilterSatisfiedCount cannot be satisfied
16609
16706
  if (allAndConditionsSatisfied(lastFilterResults)) {