apex-grid 3.4.0 → 3.6.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.
Files changed (217) hide show
  1. package/CHANGELOG.md +439 -0
  2. package/LICENSE.md +125 -0
  3. package/README.md +107 -14
  4. package/components/cell.d.ts +30 -4
  5. package/components/cell.js +199 -53
  6. package/components/cell.js.map +1 -1
  7. package/components/filter-row.d.ts +2 -0
  8. package/components/filter-row.js +82 -28
  9. package/components/filter-row.js.map +1 -1
  10. package/components/grid.d.ts +257 -20
  11. package/components/grid.js +463 -99
  12. package/components/grid.js.map +1 -1
  13. package/components/group-header-row.js +28 -25
  14. package/components/group-header-row.js.map +1 -1
  15. package/components/header-row.d.ts +7 -5
  16. package/components/header-row.js +19 -9
  17. package/components/header-row.js.map +1 -1
  18. package/components/header.js +36 -22
  19. package/components/header.js.map +1 -1
  20. package/components/row.d.ts +19 -3
  21. package/components/row.js +96 -22
  22. package/components/row.js.map +1 -1
  23. package/components/toolbar.js +2 -1
  24. package/components/toolbar.js.map +1 -1
  25. package/components/virtualizer.d.ts +45 -1
  26. package/components/virtualizer.js +184 -3
  27. package/components/virtualizer.js.map +1 -1
  28. package/controllers/dom.d.ts +4 -4
  29. package/controllers/dom.js +40 -22
  30. package/controllers/dom.js.map +1 -1
  31. package/controllers/editing.d.ts +104 -37
  32. package/controllers/editing.js +230 -78
  33. package/controllers/editing.js.map +1 -1
  34. package/controllers/expansion.d.ts +14 -5
  35. package/controllers/expansion.js +24 -1
  36. package/controllers/expansion.js.map +1 -1
  37. package/controllers/filter.d.ts +10 -2
  38. package/controllers/filter.js +62 -17
  39. package/controllers/filter.js.map +1 -1
  40. package/controllers/history.d.ts +45 -8
  41. package/controllers/history.js +58 -20
  42. package/controllers/history.js.map +1 -1
  43. package/controllers/navigation.d.ts +68 -13
  44. package/controllers/navigation.js +196 -51
  45. package/controllers/navigation.js.map +1 -1
  46. package/controllers/pagination.d.ts +6 -0
  47. package/controllers/pagination.js +10 -2
  48. package/controllers/pagination.js.map +1 -1
  49. package/controllers/reorder.js +42 -24
  50. package/controllers/reorder.js.map +1 -1
  51. package/controllers/resize.d.ts +17 -2
  52. package/controllers/resize.js +16 -6
  53. package/controllers/resize.js.map +1 -1
  54. package/controllers/row-pin.d.ts +20 -2
  55. package/controllers/row-pin.js +16 -9
  56. package/controllers/row-pin.js.map +1 -1
  57. package/controllers/row-reorder.d.ts +31 -11
  58. package/controllers/row-reorder.js +117 -37
  59. package/controllers/row-reorder.js.map +1 -1
  60. package/controllers/selection.d.ts +11 -4
  61. package/controllers/selection.js +26 -11
  62. package/controllers/selection.js.map +1 -1
  63. package/controllers/sort.d.ts +22 -2
  64. package/controllers/sort.js +29 -23
  65. package/controllers/sort.js.map +1 -1
  66. package/controllers/state.d.ts +65 -10
  67. package/controllers/state.js +86 -6
  68. package/controllers/state.js.map +1 -1
  69. package/controllers/tree.d.ts +15 -10
  70. package/controllers/tree.js +0 -0
  71. package/controllers/tree.js.map +1 -1
  72. package/custom-elements.json +3589 -452
  73. package/docs/assets/hierarchy.js +1 -1
  74. package/docs/assets/highlight.css +15 -8
  75. package/docs/assets/navigation.js +1 -1
  76. package/docs/assets/search.js +1 -1
  77. package/docs/classes/ApexGrid.html +660 -0
  78. package/docs/classes/ApexGridPaginator.html +36 -0
  79. package/docs/classes/ApexGridToolbar.html +67 -0
  80. package/docs/functions/localize.html +2 -2
  81. package/docs/functions/setup.html +1 -2
  82. package/docs/hierarchy.html +1 -1
  83. package/docs/index.html +105 -15
  84. package/docs/interfaces/ApexCellValidationFailedEvent.html +3 -2
  85. package/docs/interfaces/ApexCellValueChangedEvent.html +4 -2
  86. package/docs/interfaces/ApexCellValueChangingEvent.html +3 -2
  87. package/docs/interfaces/ApexColumnMovedEvent.html +2 -2
  88. package/docs/interfaces/ApexColumnMovingEvent.html +1 -1
  89. package/docs/interfaces/ApexDetailContext.html +2 -2
  90. package/docs/interfaces/ApexGridEventMap.html +9 -6
  91. package/docs/interfaces/ApexRowEditEndedEvent.html +2 -1
  92. package/docs/interfaces/ApexRowEditStartedEvent.html +2 -1
  93. package/docs/interfaces/ApexRowMovingEvent.html +9 -3
  94. package/docs/interfaces/ApexRowPinningEvent.html +1 -1
  95. package/docs/interfaces/ApexTreeRowExpandedEvent.html +4 -1
  96. package/docs/interfaces/ApexTreeRowExpandingEvent.html +4 -1
  97. package/docs/interfaces/BaseApexCellContext.html +1 -1
  98. package/docs/interfaces/BaseApexEditorContext.html +1 -1
  99. package/docs/interfaces/BaseColumnConfiguration.html +3 -3
  100. package/docs/interfaces/BaseFilterExpression.html +2 -2
  101. package/docs/interfaces/CSVExportOptions.html +13 -5
  102. package/docs/interfaces/DataPipelineConfiguration.html +2 -2
  103. package/docs/interfaces/EditingHistoryConfiguration.html +1 -1
  104. package/docs/interfaces/ExportFormat.html +2 -2
  105. package/docs/interfaces/ExportOptions.html +5 -3
  106. package/docs/interfaces/GridExpansionConfiguration.html +6 -2
  107. package/docs/interfaces/GridRowPinningConfiguration.html +1 -1
  108. package/docs/interfaces/GridRowReorderingConfiguration.html +2 -2
  109. package/docs/interfaces/PaginationConfiguration.html +1 -1
  110. package/docs/interfaces/PaginationState.html +1 -1
  111. package/docs/interfaces/SetStateResult.html +3 -2
  112. package/docs/interfaces/ValidatorContext.html +7 -2
  113. package/docs/media/_token-values.scss +118 -0
  114. package/docs/media/package.json +5 -5
  115. package/docs/media/state-persistence.html +0 -10
  116. package/docs/modules.html +1 -1
  117. package/docs/types/DataPipelineParams.html +2 -2
  118. package/docs/types/EditMode.html +2 -2
  119. package/docs/types/ExportSource.html +6 -4
  120. package/docs/types/GridLocaleText.html +1 -1
  121. package/docs/types/OperandKeys.html +9 -0
  122. package/docs/types/PaginationMode.html +1 -1
  123. package/docs/variables/EN_LOCALE.html +3 -3
  124. package/docs/variables/esLocale.html +1 -1
  125. package/i18n/en.d.ts +19 -0
  126. package/i18n/en.js +19 -0
  127. package/i18n/en.js.map +1 -1
  128. package/i18n/es.js +19 -0
  129. package/i18n/es.js.map +1 -1
  130. package/index.d.ts +2 -0
  131. package/index.js +2 -0
  132. package/index.js.map +1 -1
  133. package/internal/column-types.d.ts +27 -1
  134. package/internal/column-types.js +75 -49
  135. package/internal/column-types.js.map +1 -1
  136. package/internal/constants.d.ts +1 -2
  137. package/internal/constants.js +0 -4
  138. package/internal/constants.js.map +1 -1
  139. package/internal/document-styles.d.ts +30 -0
  140. package/internal/document-styles.js +48 -0
  141. package/internal/document-styles.js.map +1 -0
  142. package/internal/export.d.ts +39 -7
  143. package/internal/export.js +35 -34
  144. package/internal/export.js.map +1 -1
  145. package/internal/feature-module.d.ts +82 -3
  146. package/internal/feature-module.js +11 -0
  147. package/internal/feature-module.js.map +1 -1
  148. package/internal/index.d.ts +6 -4
  149. package/internal/index.js +5 -3
  150. package/internal/index.js.map +1 -1
  151. package/internal/normalize-case.d.ts +8 -1
  152. package/internal/normalize-case.js +3 -2
  153. package/internal/normalize-case.js.map +1 -1
  154. package/internal/state-snapshot.d.ts +60 -8
  155. package/internal/state-snapshot.js +87 -19
  156. package/internal/state-snapshot.js.map +1 -1
  157. package/internal/types.d.ts +17 -2
  158. package/internal/types.js.map +1 -1
  159. package/internal/utils.d.ts +48 -6
  160. package/internal/utils.js +35 -21
  161. package/internal/utils.js.map +1 -1
  162. package/internal/validators.js +1 -0
  163. package/internal/validators.js.map +1 -1
  164. package/operations/filter/operands/number.js +9 -6
  165. package/operations/filter/operands/number.js.map +1 -1
  166. package/operations/filter/operands/string.js +2 -3
  167. package/operations/filter/operands/string.js.map +1 -1
  168. package/operations/filter/types.d.ts +13 -1
  169. package/operations/filter/types.js.map +1 -1
  170. package/operations/filter.d.ts +6 -0
  171. package/operations/filter.js +23 -7
  172. package/operations/filter.js.map +1 -1
  173. package/operations/quick-filter.js +2 -5
  174. package/operations/quick-filter.js.map +1 -1
  175. package/operations/sort.d.ts +21 -2
  176. package/operations/sort.js +41 -14
  177. package/operations/sort.js.map +1 -1
  178. package/package.json +8 -4
  179. package/setup.d.ts +23 -11
  180. package/setup.js +19 -21
  181. package/setup.js.map +1 -1
  182. package/styles/_common.css.js +1 -1
  183. package/styles/_common.css.js.map +1 -1
  184. package/styles/_token-values.css.d.ts +1 -0
  185. package/styles/_token-values.css.js +3 -0
  186. package/styles/_token-values.css.js.map +1 -0
  187. package/styles/_tokens.css.js +1 -1
  188. package/styles/_tokens.css.js.map +1 -1
  189. package/styles/body-cell/body-cell.css.js +1 -1
  190. package/styles/body-cell/body-cell.css.js.map +1 -1
  191. package/styles/body-cell/cell-renderers.css.js +1 -1
  192. package/styles/body-cell/cell-renderers.css.js.map +1 -1
  193. package/styles/body-row/body-row.css.js +1 -1
  194. package/styles/body-row/body-row.css.js.map +1 -1
  195. package/styles/document-tokens.css.d.ts +1 -0
  196. package/styles/document-tokens.css.js +3 -0
  197. package/styles/document-tokens.css.js.map +1 -0
  198. package/styles/filter-row/filter-row.css.js +1 -1
  199. package/styles/filter-row/filter-row.css.js.map +1 -1
  200. package/styles/grid/_grid.base.css.js +1 -1
  201. package/styles/grid/_grid.base.css.js.map +1 -1
  202. package/styles/grid/grid.css.js +1 -1
  203. package/styles/grid/grid.css.js.map +1 -1
  204. package/styles/group-header-row/group-header-row.css.js +1 -1
  205. package/styles/group-header-row/group-header-row.css.js.map +1 -1
  206. package/styles/header-cell/header-cell.css.js +1 -1
  207. package/styles/header-cell/header-cell.css.js.map +1 -1
  208. package/styles/header-row/header-row.base.css.js +1 -1
  209. package/styles/header-row/header-row.base.css.js.map +1 -1
  210. package/styles/paginator/paginator.css.js +1 -1
  211. package/styles/paginator/paginator.css.js.map +1 -1
  212. package/styles/toolbar/toolbar.css.js +1 -1
  213. package/styles/toolbar/toolbar.css.js.map +1 -1
  214. package/tokens.css +78 -0
  215. package/LICENSE +0 -80
  216. package/docs/media/LICENSE +0 -80
  217. package/docs/media/_tokens.scss +0 -193
@@ -8,12 +8,32 @@ export declare const EXPANSION_COLUMN_WIDTH = 40;
8
8
  export declare const REORDER_HANDLE_COLUMN_WIDTH = 36;
9
9
  /** Width of the transient spreadsheet row-number gutter in CSS pixels. */
10
10
  export declare const ROW_NUMBER_COLUMN_WIDTH = 48;
11
- export declare function applyColumnWidths<T extends object>(columns: Array<ColumnConfiguration<T>>, options?: {
12
- showSelectionColumn?: boolean;
13
- showExpansionColumn?: boolean;
14
- showReorderHandle?: boolean;
15
- showRowNumbers?: boolean;
16
- }): StyleInfo;
11
+ /** A leading chrome track the grid renders ahead of the data columns. */
12
+ export type ChromeTrack = 'row-number' | 'reorder' | 'selection' | 'expansion';
13
+ /** The state that decides which chrome tracks are shown (the grid's StateController). */
14
+ interface ChromeSource {
15
+ selection: {
16
+ showCheckboxColumn: boolean;
17
+ };
18
+ expansion: {
19
+ showToggleColumn: boolean;
20
+ };
21
+ rowReorder: {
22
+ showHandleColumn: boolean;
23
+ };
24
+ host: {
25
+ coordinateHints?: boolean;
26
+ };
27
+ }
28
+ /**
29
+ * The leading chrome tracks in visual order, each with its width in CSS px:
30
+ * the spreadsheet row-number gutter first (matches A1), then the grip handle,
31
+ * the selection checkbox and the expansion chevron. The one list the grid
32
+ * template, the group header row's grid lines and the chrome cells' sticky
33
+ * offsets are all derived from, so they cannot drift apart.
34
+ */
35
+ export declare function leadingChromeTracks(source?: ChromeSource): Array<[ChromeTrack, number]>;
36
+ export declare function applyColumnWidths<T extends object>(columns: Array<ColumnConfiguration<T>>, chrome?: Array<[ChromeTrack, number]>): StyleInfo;
17
37
  /**
18
38
  * The spreadsheet column letter for a zero-based column index (`0 → A`,
19
39
  * `25 → Z`, `26 → AA`). Used for the transient A1 coordinate hints shown while a
@@ -38,4 +58,26 @@ export declare function getDisplayColumns<T extends object>(columns: Array<Colum
38
58
  export declare function getPinEdge<T extends object>(displayColumns: Array<ColumnConfiguration<T>>, index: number): 'start' | 'end' | null;
39
59
  export declare function autoGenerateColumns<T extends object>(grid: GridHost<T>): void;
40
60
  export declare function asArray<T>(value: T | T[]): T[];
61
+ /**
62
+ * Whether a column holds numbers: `'number'` and the numeric presentation
63
+ * types `'currency'`, `'rating'` and `'progress'`, which filter as numbers.
64
+ * The one numeric-type rule; read it through
65
+ * {@link StateController.isNumericColumn} outside this package.
66
+ */
67
+ export declare function isNumericColumn(column: Pick<ColumnConfiguration<any>, 'type'>): boolean;
41
68
  export declare function getFilterOperandsFor<T extends object>(column: ColumnConfiguration<T>): Readonly<import("../operations/filter/types.js").FilterOperands<number, import("../operations/filter/operands/number.js").NumberKeys>> | Readonly<import("../operations/filter/types.js").FilterOperands<string, import("../operations/filter/operands/string.js").StringKeys>> | Readonly<import("../operations/filter/types.js").FilterOperands<boolean, import("../operations/filter/operands/boolean.js").BooleanKeys>>;
69
+ /**
70
+ * Whether an element renders right-to-left.
71
+ *
72
+ * The grid's stylesheets are written entirely in logical properties
73
+ * (`inline-size`, `inset-inline-start`, `border-inline-end`, …), so layout
74
+ * mirrors under `dir="rtl"` with no extra CSS. What does *not* mirror by itself
75
+ * is code that reasons in physical pixels or physical key names: resizing from
76
+ * a `clientX` delta, deciding a drop side from a midpoint, and mapping
77
+ * ArrowLeft/ArrowRight onto previous/next column. Those read this.
78
+ *
79
+ * Resolved from the computed style rather than the `dir` attribute so an
80
+ * inherited direction (the common case: `dir` on `<html>`) is honoured.
81
+ */
82
+ export declare function isRTL(element: Element): boolean;
83
+ export {};
package/internal/utils.js CHANGED
@@ -5,22 +5,25 @@ export const SELECTION_COLUMN_WIDTH = 44;
5
5
  export const EXPANSION_COLUMN_WIDTH = 40;
6
6
  export const REORDER_HANDLE_COLUMN_WIDTH = 36;
7
7
  export const ROW_NUMBER_COLUMN_WIDTH = 48;
8
- export function applyColumnWidths(columns, options = {}) {
9
- const tracks = columns
10
- .filter((each) => !each.hidden)
11
- .map(({ width }) => width ?? 'minmax(136px, 1fr)');
12
- if (options.showExpansionColumn) {
13
- tracks.unshift(`${EXPANSION_COLUMN_WIDTH}px`);
14
- }
15
- if (options.showSelectionColumn) {
16
- tracks.unshift(`${SELECTION_COLUMN_WIDTH}px`);
17
- }
18
- if (options.showReorderHandle) {
19
- tracks.unshift(`${REORDER_HANDLE_COLUMN_WIDTH}px`);
20
- }
21
- if (options.showRowNumbers) {
22
- tracks.unshift(`${ROW_NUMBER_COLUMN_WIDTH}px`);
23
- }
8
+ export function leadingChromeTracks(source) {
9
+ const tracks = [];
10
+ if (!source)
11
+ return tracks;
12
+ if (source.host.coordinateHints)
13
+ tracks.push(['row-number', ROW_NUMBER_COLUMN_WIDTH]);
14
+ if (source.rowReorder.showHandleColumn)
15
+ tracks.push(['reorder', REORDER_HANDLE_COLUMN_WIDTH]);
16
+ if (source.selection.showCheckboxColumn)
17
+ tracks.push(['selection', SELECTION_COLUMN_WIDTH]);
18
+ if (source.expansion.showToggleColumn)
19
+ tracks.push(['expansion', EXPANSION_COLUMN_WIDTH]);
20
+ return tracks;
21
+ }
22
+ export function applyColumnWidths(columns, chrome = []) {
23
+ const tracks = [
24
+ ...chrome.map(([, width]) => `${width}px`),
25
+ ...columns.filter((each) => !each.hidden).map(({ width }) => width ?? 'minmax(136px, 1fr)'),
26
+ ];
24
27
  return { 'grid-template-columns': tracks.join(' ') };
25
28
  }
26
29
  export function columnLetter(index) {
@@ -79,14 +82,25 @@ export function autoGenerateColumns(grid) {
79
82
  export function asArray(value) {
80
83
  return Array.isArray(value) ? value : [value];
81
84
  }
82
- export function getFilterOperandsFor(column) {
85
+ export function isNumericColumn(column) {
83
86
  switch (column.type) {
84
- case 'boolean':
85
- return BooleanOperands;
86
87
  case 'number':
87
- return NumberOperands;
88
+ case 'currency':
89
+ case 'rating':
90
+ case 'progress':
91
+ return true;
88
92
  default:
89
- return StringOperands;
93
+ return false;
90
94
  }
91
95
  }
96
+ export function getFilterOperandsFor(column) {
97
+ if (column.type === 'boolean')
98
+ return BooleanOperands;
99
+ return isNumericColumn(column) ? NumberOperands : StringOperands;
100
+ }
101
+ export function isRTL(element) {
102
+ if (typeof getComputedStyle !== 'function')
103
+ return false;
104
+ return getComputedStyle(element).direction === 'rtl';
105
+ }
92
106
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/internal/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StyleInfo } from 'lit/directives/style-map.js';\nimport { BooleanOperands } from '../operations/filter/operands/boolean.js';\nimport { NumberOperands } from '../operations/filter/operands/number.js';\nimport { StringOperands } from '../operations/filter/operands/string.js';\nimport type { ColumnConfiguration, DataType, GridHost } from './types.js';\n\n/** Width of the auto-rendered selection (checkbox) column in CSS pixels. */\nexport const SELECTION_COLUMN_WIDTH = 44;\n/** Width of the auto-rendered expansion (chevron) column in CSS pixels. */\nexport const EXPANSION_COLUMN_WIDTH = 40;\n/** Width of the auto-rendered row-reorder (grip handle) column in CSS pixels. */\nexport const REORDER_HANDLE_COLUMN_WIDTH = 36;\n/** Width of the transient spreadsheet row-number gutter in CSS pixels. */\nexport const ROW_NUMBER_COLUMN_WIDTH = 48;\n\nexport function applyColumnWidths<T extends object>(\n columns: Array<ColumnConfiguration<T>>,\n options: {\n showSelectionColumn?: boolean;\n showExpansionColumn?: boolean;\n showReorderHandle?: boolean;\n showRowNumbers?: boolean;\n } = {}\n): StyleInfo {\n const tracks = columns\n .filter((each) => !each.hidden)\n .map(({ width }) => width ?? 'minmax(136px, 1fr)');\n if (options.showExpansionColumn) {\n tracks.unshift(`${EXPANSION_COLUMN_WIDTH}px`);\n }\n if (options.showSelectionColumn) {\n tracks.unshift(`${SELECTION_COLUMN_WIDTH}px`);\n }\n // Unshifted last so the grip handle is the leftmost leading column, ahead of\n // any selection / expansion chrome.\n if (options.showReorderHandle) {\n tracks.unshift(`${REORDER_HANDLE_COLUMN_WIDTH}px`);\n }\n // The spreadsheet row-number gutter is the very first column (matches A1).\n if (options.showRowNumbers) {\n tracks.unshift(`${ROW_NUMBER_COLUMN_WIDTH}px`);\n }\n return { 'grid-template-columns': tracks.join(' ') };\n}\n\n/**\n * The spreadsheet column letter for a zero-based column index (`0 → A`,\n * `25 → Z`, `26 → AA`). Used for the transient A1 coordinate hints shown while a\n * formula cell is being edited.\n */\nexport function columnLetter(index: number): string {\n let n = index;\n let letters = '';\n do {\n letters = String.fromCharCode(65 + (n % 26)) + letters;\n n = Math.floor(n / 26) - 1;\n } while (n >= 0);\n return letters;\n}\n\n/**\n * Returns the visual render order of the columns: start-pinned columns first,\n * then unpinned columns, then end-pinned columns. Within each group the\n * original `columns` array order is preserved.\n *\n * @remarks\n * The grid uses this order for rendering, navigation, and CSS grid track widths.\n * The user-supplied `columns` array is never mutated; this returns a new array.\n */\nexport function getDisplayColumns<T extends object>(\n columns: Array<ColumnConfiguration<T>>\n): Array<ColumnConfiguration<T>> {\n const start: Array<ColumnConfiguration<T>> = [];\n const middle: Array<ColumnConfiguration<T>> = [];\n const end: Array<ColumnConfiguration<T>> = [];\n for (const column of columns) {\n if (column.pinned === 'start') start.push(column);\n else if (column.pinned === 'end') end.push(column);\n else middle.push(column);\n }\n return [...start, ...middle, ...end];\n}\n\n/**\n * Returns `'start'` for the last visible start-pinned column, `'end'` for the\n * first visible end-pinned column, or `null` otherwise. The edge column gets a\n * subtle shadow / border to separate the pinned region from the scrolling one.\n */\nexport function getPinEdge<T extends object>(\n displayColumns: Array<ColumnConfiguration<T>>,\n index: number\n): 'start' | 'end' | null {\n const column = displayColumns[index];\n if (!column?.pinned) return null;\n\n if (column.pinned === 'start') {\n for (let i = index + 1; i < displayColumns.length; i++) {\n const next = displayColumns[i];\n if (next.hidden) continue;\n return next.pinned === 'start' ? null : 'start';\n }\n return 'start';\n }\n\n for (let i = index - 1; i >= 0; i--) {\n const prev = displayColumns[i];\n if (prev.hidden) continue;\n return prev.pinned === 'end' ? null : 'end';\n }\n return 'end';\n}\n\nexport function autoGenerateColumns<T extends object>(grid: GridHost<T>) {\n if (grid.autoGenerate && grid.columns.length < 1) {\n const record = grid.data[0] ?? {};\n\n grid.columns = Object.entries(record).map(([key, value]) => {\n const type: DataType =\n typeof value === 'boolean' ? 'boolean' : typeof value === 'number' ? 'number' : 'string';\n return { key, type } as ColumnConfiguration<T>;\n });\n }\n}\n\nexport function asArray<T>(value: T | T[]) {\n return Array.isArray(value) ? value : [value];\n}\n\nexport function getFilterOperandsFor<T extends object>(column: ColumnConfiguration<T>) {\n // Check for custom class in the filter config\n switch (column.type) {\n case 'boolean':\n return BooleanOperands;\n case 'number':\n return NumberOperands;\n default:\n return StringOperands;\n }\n}\n"]}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/internal/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StyleInfo } from 'lit/directives/style-map.js';\nimport { BooleanOperands } from '../operations/filter/operands/boolean.js';\nimport { NumberOperands } from '../operations/filter/operands/number.js';\nimport { StringOperands } from '../operations/filter/operands/string.js';\nimport type { ColumnConfiguration, DataType, GridHost } from './types.js';\n\n/** Width of the auto-rendered selection (checkbox) column in CSS pixels. */\nexport const SELECTION_COLUMN_WIDTH = 44;\n/** Width of the auto-rendered expansion (chevron) column in CSS pixels. */\nexport const EXPANSION_COLUMN_WIDTH = 40;\n/** Width of the auto-rendered row-reorder (grip handle) column in CSS pixels. */\nexport const REORDER_HANDLE_COLUMN_WIDTH = 36;\n/** Width of the transient spreadsheet row-number gutter in CSS pixels. */\nexport const ROW_NUMBER_COLUMN_WIDTH = 48;\n\n/** A leading chrome track the grid renders ahead of the data columns. */\nexport type ChromeTrack = 'row-number' | 'reorder' | 'selection' | 'expansion';\n\n/** The state that decides which chrome tracks are shown (the grid's StateController). */\ninterface ChromeSource {\n selection: { showCheckboxColumn: boolean };\n expansion: { showToggleColumn: boolean };\n rowReorder: { showHandleColumn: boolean };\n host: { coordinateHints?: boolean };\n}\n\n/**\n * The leading chrome tracks in visual order, each with its width in CSS px:\n * the spreadsheet row-number gutter first (matches A1), then the grip handle,\n * the selection checkbox and the expansion chevron. The one list the grid\n * template, the group header row's grid lines and the chrome cells' sticky\n * offsets are all derived from, so they cannot drift apart.\n */\nexport function leadingChromeTracks(source?: ChromeSource): Array<[ChromeTrack, number]> {\n const tracks: Array<[ChromeTrack, number]> = [];\n if (!source) return tracks;\n if (source.host.coordinateHints) tracks.push(['row-number', ROW_NUMBER_COLUMN_WIDTH]);\n if (source.rowReorder.showHandleColumn) tracks.push(['reorder', REORDER_HANDLE_COLUMN_WIDTH]);\n if (source.selection.showCheckboxColumn) tracks.push(['selection', SELECTION_COLUMN_WIDTH]);\n if (source.expansion.showToggleColumn) tracks.push(['expansion', EXPANSION_COLUMN_WIDTH]);\n return tracks;\n}\n\nexport function applyColumnWidths<T extends object>(\n columns: Array<ColumnConfiguration<T>>,\n chrome: Array<[ChromeTrack, number]> = []\n): StyleInfo {\n const tracks = [\n ...chrome.map(([, width]) => `${width}px`),\n ...columns.filter((each) => !each.hidden).map(({ width }) => width ?? 'minmax(136px, 1fr)'),\n ];\n return { 'grid-template-columns': tracks.join(' ') };\n}\n\n/**\n * The spreadsheet column letter for a zero-based column index (`0 → A`,\n * `25 → Z`, `26 → AA`). Used for the transient A1 coordinate hints shown while a\n * formula cell is being edited.\n */\nexport function columnLetter(index: number): string {\n let n = index;\n let letters = '';\n do {\n letters = String.fromCharCode(65 + (n % 26)) + letters;\n n = Math.floor(n / 26) - 1;\n } while (n >= 0);\n return letters;\n}\n\n/**\n * Returns the visual render order of the columns: start-pinned columns first,\n * then unpinned columns, then end-pinned columns. Within each group the\n * original `columns` array order is preserved.\n *\n * @remarks\n * The grid uses this order for rendering, navigation, and CSS grid track widths.\n * The user-supplied `columns` array is never mutated; this returns a new array.\n */\nexport function getDisplayColumns<T extends object>(\n columns: Array<ColumnConfiguration<T>>\n): Array<ColumnConfiguration<T>> {\n const start: Array<ColumnConfiguration<T>> = [];\n const middle: Array<ColumnConfiguration<T>> = [];\n const end: Array<ColumnConfiguration<T>> = [];\n for (const column of columns) {\n if (column.pinned === 'start') start.push(column);\n else if (column.pinned === 'end') end.push(column);\n else middle.push(column);\n }\n return [...start, ...middle, ...end];\n}\n\n/**\n * Returns `'start'` for the last visible start-pinned column, `'end'` for the\n * first visible end-pinned column, or `null` otherwise. The edge column gets a\n * subtle shadow / border to separate the pinned region from the scrolling one.\n */\nexport function getPinEdge<T extends object>(\n displayColumns: Array<ColumnConfiguration<T>>,\n index: number\n): 'start' | 'end' | null {\n const column = displayColumns[index];\n if (!column?.pinned) return null;\n\n if (column.pinned === 'start') {\n for (let i = index + 1; i < displayColumns.length; i++) {\n const next = displayColumns[i];\n if (next.hidden) continue;\n return next.pinned === 'start' ? null : 'start';\n }\n return 'start';\n }\n\n for (let i = index - 1; i >= 0; i--) {\n const prev = displayColumns[i];\n if (prev.hidden) continue;\n return prev.pinned === 'end' ? null : 'end';\n }\n return 'end';\n}\n\nexport function autoGenerateColumns<T extends object>(grid: GridHost<T>) {\n if (grid.autoGenerate && grid.columns.length < 1) {\n const record = grid.data[0] ?? {};\n\n grid.columns = Object.entries(record).map(([key, value]) => {\n const type: DataType =\n typeof value === 'boolean' ? 'boolean' : typeof value === 'number' ? 'number' : 'string';\n return { key, type } as ColumnConfiguration<T>;\n });\n }\n}\n\nexport function asArray<T>(value: T | T[]) {\n return Array.isArray(value) ? value : [value];\n}\n\n/**\n * Whether a column holds numbers: `'number'` and the numeric presentation\n * types `'currency'`, `'rating'` and `'progress'`, which filter as numbers.\n * The one numeric-type rule; read it through\n * {@link StateController.isNumericColumn} outside this package.\n */\nexport function isNumericColumn(column: Pick<ColumnConfiguration<any>, 'type'>): boolean {\n switch (column.type) {\n case 'number':\n case 'currency':\n case 'rating':\n case 'progress':\n return true;\n default:\n return false;\n }\n}\n\nexport function getFilterOperandsFor<T extends object>(column: ColumnConfiguration<T>) {\n if (column.type === 'boolean') return BooleanOperands;\n return isNumericColumn(column) ? NumberOperands : StringOperands;\n}\n\n/**\n * Whether an element renders right-to-left.\n *\n * The grid's stylesheets are written entirely in logical properties\n * (`inline-size`, `inset-inline-start`, `border-inline-end`, …), so layout\n * mirrors under `dir=\"rtl\"` with no extra CSS. What does *not* mirror by itself\n * is code that reasons in physical pixels or physical key names: resizing from\n * a `clientX` delta, deciding a drop side from a midpoint, and mapping\n * ArrowLeft/ArrowRight onto previous/next column. Those read this.\n *\n * Resolved from the computed style rather than the `dir` attribute so an\n * inherited direction (the common case: `dir` on `<html>`) is honoured.\n */\nexport function isRTL(element: Element): boolean {\n if (typeof getComputedStyle !== 'function') return false;\n return getComputedStyle(element).direction === 'rtl';\n}\n"]}
@@ -24,6 +24,7 @@ export function pattern(regex, message = 'Invalid format') {
24
24
  return (value) => {
25
25
  if (isBlank(value))
26
26
  return null;
27
+ regex.lastIndex = 0;
27
28
  return regex.test(String(value)) ? null : message;
28
29
  };
29
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"validators.js","sourceRoot":"","sources":["../../src/internal/validators.ts"],"names":[],"mappings":"AAgBA,SAAS,OAAO,CAAC,KAAc;IAC7B,OAAO,KAAK,IAAI,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAC7E,CAAC;AAMD,MAAM,UAAU,QAAQ,CACtB,OAAO,GAAG,wBAAwB;IAElC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACtD,CAAC;AAMD,MAAM,UAAU,GAAG,CACjB,KAAa,EACb,OAAO,GAAG,oBAAoB,KAAK,EAAE;IAErC,OAAO,CAAC,KAAK,EAAE,EAAE;QACf,IAAI,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAChC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1B,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;IAC5D,CAAC,CAAC;AACJ,CAAC;AAMD,MAAM,UAAU,GAAG,CACjB,KAAa,EACb,OAAO,GAAG,mBAAmB,KAAK,EAAE;IAEpC,OAAO,CAAC,KAAK,EAAE,EAAE;QACf,IAAI,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAChC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1B,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;IAC5D,CAAC,CAAC;AACJ,CAAC;AAMD,MAAM,UAAU,OAAO,CACrB,KAAa,EACb,OAAO,GAAG,gBAAgB;IAE1B,OAAO,CAAC,KAAK,EAAE,EAAE;QACf,IAAI,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAChC,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;IACpD,CAAC,CAAC;AACJ,CAAC;AAMD,MAAM,UAAU,MAAM,CACpB,EAAmB;IAEnB,OAAO,EAAE,CAAC;AACZ,CAAC;AAMD,MAAM,UAAU,aAAa,CAC3B,UAAyC,EACzC,KAAc,EACd,OAA+B;IAE/B,IAAI,CAAC,UAAU,EAAE,MAAM;QAAE,OAAO,EAAE,CAAC;IACnC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACzC,IAAI,OAAO;YAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import type { Keys, Validator, ValidatorContext } from './types.js';\n\n/**\n * Built-in declarative validators for {@link BaseColumnConfiguration.validators}\n * plus the {@link runValidators} runner that executes them.\n *\n * @remarks\n * Each factory returns a {@link Validator} — a function `(value, context) =>\n * string | null` that yields an error message when the value is invalid or\n * `null` when it passes. Bounds validators (`min` / `max`) and `pattern` treat\n * an empty value (`null` / `undefined` / `''`) as out of scope so they compose\n * cleanly with {@link required}; pair them with `required` when a value is\n * mandatory.\n */\n\n/** `true` for `null`, `undefined`, or an empty / whitespace-only string. */\nfunction isBlank(value: unknown): boolean {\n return value == null || (typeof value === 'string' && value.trim() === '');\n}\n\n/**\n * Fails when the value is `null`, `undefined`, or an empty / whitespace-only\n * string.\n */\nexport function required<T extends object, K extends Keys<T> = Keys<T>>(\n message = 'This field is required'\n): Validator<T, K> {\n return (value) => (isBlank(value) ? message : null);\n}\n\n/**\n * Fails when the numeric value is below `limit`. Non-numeric and empty values\n * pass (compose with {@link required}).\n */\nexport function min<T extends object, K extends Keys<T> = Keys<T>>(\n limit: number,\n message = `Must be at least ${limit}`\n): Validator<T, K> {\n return (value) => {\n if (isBlank(value)) return null;\n const num = Number(value);\n return Number.isNaN(num) || num >= limit ? null : message;\n };\n}\n\n/**\n * Fails when the numeric value is above `limit`. Non-numeric and empty values\n * pass (compose with {@link required}).\n */\nexport function max<T extends object, K extends Keys<T> = Keys<T>>(\n limit: number,\n message = `Must be at most ${limit}`\n): Validator<T, K> {\n return (value) => {\n if (isBlank(value)) return null;\n const num = Number(value);\n return Number.isNaN(num) || num <= limit ? null : message;\n };\n}\n\n/**\n * Fails when the string form of the value does not match `regex`. Empty values\n * pass (compose with {@link required}).\n */\nexport function pattern<T extends object, K extends Keys<T> = Keys<T>>(\n regex: RegExp,\n message = 'Invalid format'\n): Validator<T, K> {\n return (value) => {\n if (isBlank(value)) return null;\n return regex.test(String(value)) ? null : message;\n };\n}\n\n/**\n * Wraps an arbitrary predicate as a {@link Validator}. Provided for symmetry and\n * type inference; an inline arrow function works identically.\n */\nexport function custom<T extends object, K extends Keys<T> = Keys<T>>(\n fn: Validator<T, K>\n): Validator<T, K> {\n return fn;\n}\n\n/**\n * Runs every validator in order and collects all error messages. Returns an\n * empty array when the value passes.\n */\nexport function runValidators<T extends object, K extends Keys<T> = Keys<T>>(\n validators: Validator<T, K>[] | undefined,\n value: unknown,\n context: ValidatorContext<T, K>\n): string[] {\n if (!validators?.length) return [];\n const errors: string[] = [];\n for (const validate of validators) {\n const message = validate(value, context);\n if (message) errors.push(message);\n }\n return errors;\n}\n"]}
1
+ {"version":3,"file":"validators.js","sourceRoot":"","sources":["../../src/internal/validators.ts"],"names":[],"mappings":"AAgBA,SAAS,OAAO,CAAC,KAAc;IAC7B,OAAO,KAAK,IAAI,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAC7E,CAAC;AAMD,MAAM,UAAU,QAAQ,CACtB,OAAO,GAAG,wBAAwB;IAElC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACtD,CAAC;AAMD,MAAM,UAAU,GAAG,CACjB,KAAa,EACb,OAAO,GAAG,oBAAoB,KAAK,EAAE;IAErC,OAAO,CAAC,KAAK,EAAE,EAAE;QACf,IAAI,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAChC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1B,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;IAC5D,CAAC,CAAC;AACJ,CAAC;AAMD,MAAM,UAAU,GAAG,CACjB,KAAa,EACb,OAAO,GAAG,mBAAmB,KAAK,EAAE;IAEpC,OAAO,CAAC,KAAK,EAAE,EAAE;QACf,IAAI,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAChC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1B,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;IAC5D,CAAC,CAAC;AACJ,CAAC;AAMD,MAAM,UAAU,OAAO,CACrB,KAAa,EACb,OAAO,GAAG,gBAAgB;IAE1B,OAAO,CAAC,KAAK,EAAE,EAAE;QACf,IAAI,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAGhC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;QACpB,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;IACpD,CAAC,CAAC;AACJ,CAAC;AAMD,MAAM,UAAU,MAAM,CACpB,EAAmB;IAEnB,OAAO,EAAE,CAAC;AACZ,CAAC;AAMD,MAAM,UAAU,aAAa,CAC3B,UAAyC,EACzC,KAAc,EACd,OAA+B;IAE/B,IAAI,CAAC,UAAU,EAAE,MAAM;QAAE,OAAO,EAAE,CAAC;IACnC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACzC,IAAI,OAAO;YAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import type { Keys, Validator, ValidatorContext } from './types.js';\n\n/**\n * Built-in declarative validators for {@link BaseColumnConfiguration.validators}\n * plus the {@link runValidators} runner that executes them.\n *\n * @remarks\n * Each factory returns a {@link Validator} — a function `(value, context) =>\n * string | null` that yields an error message when the value is invalid or\n * `null` when it passes. Bounds validators (`min` / `max`) and `pattern` treat\n * an empty value (`null` / `undefined` / `''`) as out of scope so they compose\n * cleanly with {@link required}; pair them with `required` when a value is\n * mandatory.\n */\n\n/** `true` for `null`, `undefined`, or an empty / whitespace-only string. */\nfunction isBlank(value: unknown): boolean {\n return value == null || (typeof value === 'string' && value.trim() === '');\n}\n\n/**\n * Fails when the value is `null`, `undefined`, or an empty / whitespace-only\n * string.\n */\nexport function required<T extends object, K extends Keys<T> = Keys<T>>(\n message = 'This field is required'\n): Validator<T, K> {\n return (value) => (isBlank(value) ? message : null);\n}\n\n/**\n * Fails when the numeric value is below `limit`. Non-numeric and empty values\n * pass (compose with {@link required}).\n */\nexport function min<T extends object, K extends Keys<T> = Keys<T>>(\n limit: number,\n message = `Must be at least ${limit}`\n): Validator<T, K> {\n return (value) => {\n if (isBlank(value)) return null;\n const num = Number(value);\n return Number.isNaN(num) || num >= limit ? null : message;\n };\n}\n\n/**\n * Fails when the numeric value is above `limit`. Non-numeric and empty values\n * pass (compose with {@link required}).\n */\nexport function max<T extends object, K extends Keys<T> = Keys<T>>(\n limit: number,\n message = `Must be at most ${limit}`\n): Validator<T, K> {\n return (value) => {\n if (isBlank(value)) return null;\n const num = Number(value);\n return Number.isNaN(num) || num <= limit ? null : message;\n };\n}\n\n/**\n * Fails when the string form of the value does not match `regex`. Empty values\n * pass (compose with {@link required}).\n */\nexport function pattern<T extends object, K extends Keys<T> = Keys<T>>(\n regex: RegExp,\n message = 'Invalid format'\n): Validator<T, K> {\n return (value) => {\n if (isBlank(value)) return null;\n // A global or sticky RegExp resumes from `lastIndex`, so start every\n // validation from the beginning of the value.\n regex.lastIndex = 0;\n return regex.test(String(value)) ? null : message;\n };\n}\n\n/**\n * Wraps an arbitrary predicate as a {@link Validator}. Provided for symmetry and\n * type inference; an inline arrow function works identically.\n */\nexport function custom<T extends object, K extends Keys<T> = Keys<T>>(\n fn: Validator<T, K>\n): Validator<T, K> {\n return fn;\n}\n\n/**\n * Runs every validator in order and collects all error messages. Returns an\n * empty array when the value passes.\n */\nexport function runValidators<T extends object, K extends Keys<T> = Keys<T>>(\n validators: Validator<T, K>[] | undefined,\n value: unknown,\n context: ValidatorContext<T, K>\n): string[] {\n if (!validators?.length) return [];\n const errors: string[] = [];\n for (const validate of validators) {\n const message = validate(value, context);\n if (message) errors.push(message);\n }\n return errors;\n}\n"]}
@@ -1,4 +1,7 @@
1
1
  import { isDefined } from '../../../internal/is-defined.js';
2
+ const isEmptyNumber = (target) => !isDefined(target) ||
3
+ Number.isNaN(target) ||
4
+ (typeof target === 'string' && target.trim() === '');
2
5
  export const NumberOperands = Object.freeze({
3
6
  equals: {
4
7
  name: 'equals',
@@ -16,37 +19,37 @@ export const NumberOperands = Object.freeze({
16
19
  name: 'greaterThan',
17
20
  label: 'Greater than',
18
21
  unary: false,
19
- logic: (target, term) => target > term,
22
+ logic: (target, term) => !isEmptyNumber(target) && target > term,
20
23
  },
21
24
  lessThan: {
22
25
  name: 'lessThan',
23
26
  label: 'Less than',
24
27
  unary: false,
25
- logic: (target, term) => target < term,
28
+ logic: (target, term) => !isEmptyNumber(target) && target < term,
26
29
  },
27
30
  greaterThanOrEqual: {
28
31
  name: 'greaterThanOrEqual',
29
32
  label: 'Greater than or equal',
30
33
  unary: false,
31
- logic: (target, term) => target >= term,
34
+ logic: (target, term) => !isEmptyNumber(target) && target >= term,
32
35
  },
33
36
  lessThanOrEqual: {
34
37
  name: 'lessThanOrEqual',
35
38
  label: 'Less than or equal',
36
39
  unary: false,
37
- logic: (target, term) => target <= term,
40
+ logic: (target, term) => !isEmptyNumber(target) && target <= term,
38
41
  },
39
42
  empty: {
40
43
  name: 'empty',
41
44
  label: 'Empty',
42
45
  unary: true,
43
- logic: (target) => !isDefined(target) || Number.isNaN(target),
46
+ logic: (target) => isEmptyNumber(target),
44
47
  },
45
48
  notEmpty: {
46
49
  name: 'notEmpty',
47
50
  label: 'Not empty',
48
51
  unary: true,
49
- logic: (target) => isDefined(target) && !Number.isNaN(target),
52
+ logic: (target) => !isEmptyNumber(target),
50
53
  },
51
54
  });
52
55
  //# sourceMappingURL=number.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"number.js","sourceRoot":"","sources":["../../../../src/operations/filter/operands/number.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAa5D,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAqC;IAC9E,MAAM,EAAE;QACN,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,QAAQ;QACf,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,KAAK,IAAI;KACzC;IACD,YAAY,EAAE;QACZ,IAAI,EAAE,cAAc;QACpB,KAAK,EAAE,gBAAgB;QACvB,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,KAAK,IAAI;KACzC;IACD,WAAW,EAAE;QACX,IAAI,EAAE,aAAa;QACnB,KAAK,EAAE,cAAc;QACrB,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,GAAG,IAAI;KACvC;IACD,QAAQ,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,WAAW;QAClB,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,GAAG,IAAI;KACvC;IACD,kBAAkB,EAAE;QAClB,IAAI,EAAE,oBAAoB;QAC1B,KAAK,EAAE,uBAAuB;QAC9B,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,IAAI,IAAI;KACxC;IACD,eAAe,EAAE;QACf,IAAI,EAAE,iBAAiB;QACvB,KAAK,EAAE,oBAAoB;QAC3B,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,IAAI,IAAI;KACxC;IACD,KAAK,EAAE;QACL,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,OAAO;QACd,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;KAC9D;IACD,QAAQ,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,WAAW;QAClB,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;KAC9D;CACF,CAAC,CAAC","sourcesContent":["import { isDefined } from '../../../internal/is-defined.js';\nimport type { FilterOperands } from '../types.js';\n\nexport type NumberKeys =\n | 'equals'\n | 'doesNotEqual'\n | 'greaterThan'\n | 'lessThan'\n | 'greaterThanOrEqual'\n | 'lessThanOrEqual'\n | 'empty'\n | 'notEmpty';\n\nexport const NumberOperands = Object.freeze<FilterOperands<number, NumberKeys>>({\n equals: {\n name: 'equals',\n label: 'Equals',\n unary: false,\n logic: (target, term) => target === term,\n },\n doesNotEqual: {\n name: 'doesNotEqual',\n label: 'Does not equal',\n unary: false,\n logic: (target, term) => target !== term,\n },\n greaterThan: {\n name: 'greaterThan',\n label: 'Greater than',\n unary: false,\n logic: (target, term) => target > term,\n },\n lessThan: {\n name: 'lessThan',\n label: 'Less than',\n unary: false,\n logic: (target, term) => target < term,\n },\n greaterThanOrEqual: {\n name: 'greaterThanOrEqual',\n label: 'Greater than or equal',\n unary: false,\n logic: (target, term) => target >= term,\n },\n lessThanOrEqual: {\n name: 'lessThanOrEqual',\n label: 'Less than or equal',\n unary: false,\n logic: (target, term) => target <= term,\n },\n empty: {\n name: 'empty',\n label: 'Empty',\n unary: true,\n logic: (target) => !isDefined(target) || Number.isNaN(target),\n },\n notEmpty: {\n name: 'notEmpty',\n label: 'Not empty',\n unary: true,\n logic: (target) => isDefined(target) && !Number.isNaN(target),\n },\n});\n"]}
1
+ {"version":3,"file":"number.js","sourceRoot":"","sources":["../../../../src/operations/filter/operands/number.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAkB5D,MAAM,aAAa,GAAG,CAAC,MAAe,EAAE,EAAE,CACxC,CAAC,SAAS,CAAC,MAAM,CAAC;IAClB,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;IACpB,CAAC,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAEvD,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAqC;IAC9E,MAAM,EAAE;QACN,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,QAAQ;QACf,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,KAAK,IAAI;KACzC;IACD,YAAY,EAAE;QACZ,IAAI,EAAE,cAAc;QACpB,KAAK,EAAE,gBAAgB;QACvB,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,KAAK,IAAI;KACzC;IACD,WAAW,EAAE;QACX,IAAI,EAAE,aAAa;QACnB,KAAK,EAAE,cAAc;QACrB,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,IAAI;KACjE;IACD,QAAQ,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,WAAW;QAClB,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,IAAI;KACjE;IACD,kBAAkB,EAAE;QAClB,IAAI,EAAE,oBAAoB;QAC1B,KAAK,EAAE,uBAAuB;QAC9B,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,IAAI;KAClE;IACD,eAAe,EAAE;QACf,IAAI,EAAE,iBAAiB;QACvB,KAAK,EAAE,oBAAoB;QAC3B,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,IAAI;KAClE;IACD,KAAK,EAAE;QACL,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,OAAO;QACd,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC;KACzC;IACD,QAAQ,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,WAAW;QAClB,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC;KAC1C;CACF,CAAC,CAAC","sourcesContent":["import { isDefined } from '../../../internal/is-defined.js';\nimport type { FilterOperands } from '../types.js';\n\nexport type NumberKeys =\n | 'equals'\n | 'doesNotEqual'\n | 'greaterThan'\n | 'lessThan'\n | 'greaterThanOrEqual'\n | 'lessThanOrEqual'\n | 'empty'\n | 'notEmpty';\n\n/**\n * A missing number: `null`, `undefined`, `NaN` or blank text (a missing value\n * in CSV or JSON data). It is what Empty matches, and it never matches a range\n * condition (JS would otherwise read `null` and `''` as 0).\n */\nconst isEmptyNumber = (target: unknown) =>\n !isDefined(target) ||\n Number.isNaN(target) ||\n (typeof target === 'string' && target.trim() === '');\n\nexport const NumberOperands = Object.freeze<FilterOperands<number, NumberKeys>>({\n equals: {\n name: 'equals',\n label: 'Equals',\n unary: false,\n logic: (target, term) => target === term,\n },\n doesNotEqual: {\n name: 'doesNotEqual',\n label: 'Does not equal',\n unary: false,\n logic: (target, term) => target !== term,\n },\n greaterThan: {\n name: 'greaterThan',\n label: 'Greater than',\n unary: false,\n logic: (target, term) => !isEmptyNumber(target) && target > term,\n },\n lessThan: {\n name: 'lessThan',\n label: 'Less than',\n unary: false,\n logic: (target, term) => !isEmptyNumber(target) && target < term,\n },\n greaterThanOrEqual: {\n name: 'greaterThanOrEqual',\n label: 'Greater than or equal',\n unary: false,\n logic: (target, term) => !isEmptyNumber(target) && target >= term,\n },\n lessThanOrEqual: {\n name: 'lessThanOrEqual',\n label: 'Less than or equal',\n unary: false,\n logic: (target, term) => !isEmptyNumber(target) && target <= term,\n },\n empty: {\n name: 'empty',\n label: 'Empty',\n unary: true,\n logic: (target) => isEmptyNumber(target),\n },\n notEmpty: {\n name: 'notEmpty',\n label: 'Not empty',\n unary: true,\n logic: (target) => !isEmptyNumber(target),\n },\n});\n"]}
@@ -1,4 +1,3 @@
1
- import { isDefined } from '../../../internal/is-defined.js';
2
1
  import { normalizeCase } from '../../../internal/normalize-case.js';
3
2
  export const StringOperands = Object.freeze({
4
3
  contains: {
@@ -41,13 +40,13 @@ export const StringOperands = Object.freeze({
41
40
  name: 'empty',
42
41
  label: 'Empty',
43
42
  unary: true,
44
- logic: (target) => !isDefined(target) || target.length < 1,
43
+ logic: (target) => normalizeCase(target, true).length < 1,
45
44
  },
46
45
  notEmpty: {
47
46
  name: 'notEmpty',
48
47
  label: 'Not empty',
49
48
  unary: true,
50
- logic: (target) => isDefined(target) && target.length > 0,
49
+ logic: (target) => normalizeCase(target, true).length > 0,
51
50
  },
52
51
  });
53
52
  //# sourceMappingURL=string.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"string.js","sourceRoot":"","sources":["../../../../src/operations/filter/operands/string.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AAapE,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAqC;IAC9E,QAAQ,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,UAAU;QACjB,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,aAAc,EAAE,EAAE,CACtC,aAAa,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;KACpF;IACD,cAAc,EAAE;QACd,IAAI,EAAE,gBAAgB;QACtB,KAAK,EAAE,kBAAkB;QACzB,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,aAAc,EAAE,EAAE,CACtC,CAAC,aAAa,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;KACrF;IACD,UAAU,EAAE;QACV,IAAI,EAAE,YAAY;QAClB,KAAK,EAAE,aAAa;QACpB,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,aAAc,EAAE,EAAE,CAC5C,aAAa,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;KAC5F;IACD,QAAQ,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,WAAW;QAClB,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,aAAc,EAAE,EAAE,CAC5C,aAAa,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;KAC1F;IACD,MAAM,EAAE;QACN,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,QAAQ;QACf,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,EAAE,CAC3C,aAAa,CAAC,MAAM,EAAE,aAAa,CAAC,KAAK,aAAa,CAAC,UAAU,EAAE,aAAa,CAAC;KACpF;IAED,YAAY,EAAE;QACZ,IAAI,EAAE,cAAc;QACpB,KAAK,EAAE,gBAAgB;QACvB,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,EAAE,CAC3C,aAAa,CAAC,MAAM,EAAE,aAAa,CAAC,KAAK,aAAa,CAAC,UAAU,EAAE,aAAa,CAAC;KACpF;IAED,KAAK,EAAE;QACL,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,OAAO;QACd,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;KAC3D;IAED,QAAQ,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,WAAW;QAClB,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;KAC1D;CACF,CAAC,CAAC","sourcesContent":["import { isDefined } from '../../../internal/is-defined.js';\nimport { normalizeCase } from '../../../internal/normalize-case.js';\nimport type { FilterOperands } from '../types.js';\n\nexport type StringKeys =\n | 'contains'\n | 'doesNotContain'\n | 'startsWith'\n | 'endsWith'\n | 'equals'\n | 'doesNotEqual'\n | 'empty'\n | 'notEmpty';\n\nexport const StringOperands = Object.freeze<FilterOperands<string, StringKeys>>({\n contains: {\n name: 'contains',\n label: 'Contains',\n unary: false,\n logic: (target, term, caseSensitive?) =>\n normalizeCase(target, caseSensitive).includes(normalizeCase(term, caseSensitive)),\n },\n doesNotContain: {\n name: 'doesNotContain',\n label: 'Does not contain',\n unary: false,\n logic: (target, term, caseSensitive?) =>\n !normalizeCase(target, caseSensitive).includes(normalizeCase(term, caseSensitive)),\n },\n startsWith: {\n name: 'startsWith',\n label: 'Starts with',\n unary: false,\n logic: (target, searchTerm, caseSensitive?) =>\n normalizeCase(target, caseSensitive).startsWith(normalizeCase(searchTerm, caseSensitive)),\n },\n endsWith: {\n name: 'endsWith',\n label: 'Ends with',\n unary: false,\n logic: (target, searchTerm, caseSensitive?) =>\n normalizeCase(target, caseSensitive).endsWith(normalizeCase(searchTerm, caseSensitive)),\n },\n equals: {\n name: 'equals',\n label: 'Equals',\n unary: false,\n logic: (target, searchTerm, caseSensitive) =>\n normalizeCase(target, caseSensitive) === normalizeCase(searchTerm, caseSensitive),\n },\n\n doesNotEqual: {\n name: 'doesNotEqual',\n label: 'Does not equal',\n unary: false,\n logic: (target, searchTerm, caseSensitive) =>\n normalizeCase(target, caseSensitive) !== normalizeCase(searchTerm, caseSensitive),\n },\n\n empty: {\n name: 'empty',\n label: 'Empty',\n unary: true,\n logic: (target) => !isDefined(target) || target.length < 1,\n },\n\n notEmpty: {\n name: 'notEmpty',\n label: 'Not empty',\n unary: true,\n logic: (target) => isDefined(target) && target.length > 0,\n },\n});\n"]}
1
+ {"version":3,"file":"string.js","sourceRoot":"","sources":["../../../../src/operations/filter/operands/string.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AAapE,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAqC;IAC9E,QAAQ,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,UAAU;QACjB,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,aAAc,EAAE,EAAE,CACtC,aAAa,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;KACpF;IACD,cAAc,EAAE;QACd,IAAI,EAAE,gBAAgB;QACtB,KAAK,EAAE,kBAAkB;QACzB,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,aAAc,EAAE,EAAE,CACtC,CAAC,aAAa,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;KACrF;IACD,UAAU,EAAE;QACV,IAAI,EAAE,YAAY;QAClB,KAAK,EAAE,aAAa;QACpB,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,aAAc,EAAE,EAAE,CAC5C,aAAa,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;KAC5F;IACD,QAAQ,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,WAAW;QAClB,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,aAAc,EAAE,EAAE,CAC5C,aAAa,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;KAC1F;IACD,MAAM,EAAE;QACN,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,QAAQ;QACf,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,EAAE,CAC3C,aAAa,CAAC,MAAM,EAAE,aAAa,CAAC,KAAK,aAAa,CAAC,UAAU,EAAE,aAAa,CAAC;KACpF;IAED,YAAY,EAAE;QACZ,IAAI,EAAE,cAAc;QACpB,KAAK,EAAE,gBAAgB;QACvB,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,EAAE,CAC3C,aAAa,CAAC,MAAM,EAAE,aAAa,CAAC,KAAK,aAAa,CAAC,UAAU,EAAE,aAAa,CAAC;KACpF;IAED,KAAK,EAAE;QACL,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,OAAO;QACd,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;KAC1D;IAED,QAAQ,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,WAAW;QAClB,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;KAC1D;CACF,CAAC,CAAC","sourcesContent":["import { normalizeCase } from '../../../internal/normalize-case.js';\nimport type { FilterOperands } from '../types.js';\n\nexport type StringKeys =\n | 'contains'\n | 'doesNotContain'\n | 'startsWith'\n | 'endsWith'\n | 'equals'\n | 'doesNotEqual'\n | 'empty'\n | 'notEmpty';\n\nexport const StringOperands = Object.freeze<FilterOperands<string, StringKeys>>({\n contains: {\n name: 'contains',\n label: 'Contains',\n unary: false,\n logic: (target, term, caseSensitive?) =>\n normalizeCase(target, caseSensitive).includes(normalizeCase(term, caseSensitive)),\n },\n doesNotContain: {\n name: 'doesNotContain',\n label: 'Does not contain',\n unary: false,\n logic: (target, term, caseSensitive?) =>\n !normalizeCase(target, caseSensitive).includes(normalizeCase(term, caseSensitive)),\n },\n startsWith: {\n name: 'startsWith',\n label: 'Starts with',\n unary: false,\n logic: (target, searchTerm, caseSensitive?) =>\n normalizeCase(target, caseSensitive).startsWith(normalizeCase(searchTerm, caseSensitive)),\n },\n endsWith: {\n name: 'endsWith',\n label: 'Ends with',\n unary: false,\n logic: (target, searchTerm, caseSensitive?) =>\n normalizeCase(target, caseSensitive).endsWith(normalizeCase(searchTerm, caseSensitive)),\n },\n equals: {\n name: 'equals',\n label: 'Equals',\n unary: false,\n logic: (target, searchTerm, caseSensitive) =>\n normalizeCase(target, caseSensitive) === normalizeCase(searchTerm, caseSensitive),\n },\n\n doesNotEqual: {\n name: 'doesNotEqual',\n label: 'Does not equal',\n unary: false,\n logic: (target, searchTerm, caseSensitive) =>\n normalizeCase(target, caseSensitive) !== normalizeCase(searchTerm, caseSensitive),\n },\n\n empty: {\n name: 'empty',\n label: 'Empty',\n unary: true,\n logic: (target) => normalizeCase(target, true).length < 1,\n },\n\n notEmpty: {\n name: 'notEmpty',\n label: 'Not empty',\n unary: true,\n logic: (target) => normalizeCase(target, true).length > 0,\n },\n});\n"]}
@@ -54,4 +54,16 @@ export interface BaseFilterExpression<T, K extends Keys<T> = Keys<T>> {
54
54
  * See {@link BaseFilterExpression} for the full documentation.
55
55
  */
56
56
  export type FilterExpression<T, K extends Keys<T> = Keys<T>> = K extends Keys<T> ? BaseFilterExpression<T, K> : never;
57
- export type OperandKeys<Type> = Type extends number ? keyof typeof NumberOperands : Type extends string ? keyof typeof StringOperands : Type extends boolean ? keyof typeof BooleanOperands : Type;
57
+ /**
58
+ * The operand names a filter `condition` may use for a value of `Type`.
59
+ *
60
+ * @remarks
61
+ * Any other value type (a `Date`, an array, an object) offers the string
62
+ * operand names, the set the grid applies to a column with no numeric or
63
+ * boolean type. `null` and `undefined` add no names, so a `number | null` or
64
+ * optional number field keeps the number operand names only. A field typed
65
+ * `unknown` (or `any`), such as every field of a `Record<string, unknown>`
66
+ * row, keeps every operand name: the grid picks the operands from the
67
+ * column type at runtime, which the value type does not tell.
68
+ */
69
+ export type OperandKeys<Type> = unknown extends Type ? keyof typeof NumberOperands | keyof typeof StringOperands | keyof typeof BooleanOperands : Type extends null | undefined ? never : Type extends number ? keyof typeof NumberOperands : Type extends string ? keyof typeof StringOperands : Type extends boolean ? keyof typeof BooleanOperands : keyof typeof StringOperands;
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/operations/filter/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Keys } from '../../internal/types.js';\nimport type { BooleanOperands } from './operands/boolean.js';\nimport type { NumberOperands } from './operands/number.js';\nimport type { StringOperands } from './operands/string.js';\n\n/**\n * Controls how a data record should resolve in a filter operation:\n * - `'and'` - the record must pass all the conditions.\n * - `'or'` - the record must pass at least one condition.\n */\nexport type FilterCriteria = 'and' | 'or';\n\nexport type FilterOperationLogic<T> = (\n target: T,\n searchTerm: T,\n caseSensitive?: boolean\n) => boolean;\n\nexport interface FilterOperation<T> {\n name: string;\n label?: string;\n unary: boolean;\n logic: FilterOperationLogic<T>;\n}\n\nexport type FilterOperands<DataType, Operands extends string> = {\n [key in Operands]: FilterOperation<DataType>;\n};\n\n/**\n * Represents a filter operation for a given column.\n */\nexport interface BaseFilterExpression<T, K extends Keys<T> = Keys<T>> {\n /**\n * The target column for the filter operation.\n */\n key: K;\n /**\n * The filter function which will be executed against the data records.\n */\n condition: FilterOperation<T[K]> | OperandKeys<T[K]>;\n\n /**\n * The filtering value used in the filter condition function.\n *\n * @remarks\n * Optional for unary conditions.\n */\n searchTerm?: T[K];\n /**\n * Dictates how this expression should resolve in the filter operation in relation to\n * other expressions.\n */\n criteria?: FilterCriteria;\n /**\n * Whether the sort operation should be case sensitive.\n *\n * @remarks\n * If not provided, the value is resolved based on the column filter configuration (if any).\n */\n caseSensitive?: boolean;\n}\n\n/**\n * See {@link BaseFilterExpression} for the full documentation.\n */\nexport type FilterExpression<T, K extends Keys<T> = Keys<T>> =\n K extends Keys<T> ? BaseFilterExpression<T, K> : never;\n\nexport type OperandKeys<Type> = Type extends number\n ? keyof typeof NumberOperands\n : Type extends string\n ? keyof typeof StringOperands\n : Type extends boolean\n ? keyof typeof BooleanOperands\n : Type;\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/operations/filter/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Keys } from '../../internal/types.js';\nimport type { BooleanOperands } from './operands/boolean.js';\nimport type { NumberOperands } from './operands/number.js';\nimport type { StringOperands } from './operands/string.js';\n\n/**\n * Controls how a data record should resolve in a filter operation:\n * - `'and'` - the record must pass all the conditions.\n * - `'or'` - the record must pass at least one condition.\n */\nexport type FilterCriteria = 'and' | 'or';\n\nexport type FilterOperationLogic<T> = (\n target: T,\n searchTerm: T,\n caseSensitive?: boolean\n) => boolean;\n\nexport interface FilterOperation<T> {\n name: string;\n label?: string;\n unary: boolean;\n logic: FilterOperationLogic<T>;\n}\n\nexport type FilterOperands<DataType, Operands extends string> = {\n [key in Operands]: FilterOperation<DataType>;\n};\n\n/**\n * Represents a filter operation for a given column.\n */\nexport interface BaseFilterExpression<T, K extends Keys<T> = Keys<T>> {\n /**\n * The target column for the filter operation.\n */\n key: K;\n /**\n * The filter function which will be executed against the data records.\n */\n condition: FilterOperation<T[K]> | OperandKeys<T[K]>;\n\n /**\n * The filtering value used in the filter condition function.\n *\n * @remarks\n * Optional for unary conditions.\n */\n searchTerm?: T[K];\n /**\n * Dictates how this expression should resolve in the filter operation in relation to\n * other expressions.\n */\n criteria?: FilterCriteria;\n /**\n * Whether the sort operation should be case sensitive.\n *\n * @remarks\n * If not provided, the value is resolved based on the column filter configuration (if any).\n */\n caseSensitive?: boolean;\n}\n\n/**\n * See {@link BaseFilterExpression} for the full documentation.\n */\nexport type FilterExpression<T, K extends Keys<T> = Keys<T>> =\n K extends Keys<T> ? BaseFilterExpression<T, K> : never;\n\n/**\n * The operand names a filter `condition` may use for a value of `Type`.\n *\n * @remarks\n * Any other value type (a `Date`, an array, an object) offers the string\n * operand names, the set the grid applies to a column with no numeric or\n * boolean type. `null` and `undefined` add no names, so a `number | null` or\n * optional number field keeps the number operand names only. A field typed\n * `unknown` (or `any`), such as every field of a `Record<string, unknown>`\n * row, keeps every operand name: the grid picks the operands from the\n * column type at runtime, which the value type does not tell.\n */\nexport type OperandKeys<Type> = unknown extends Type\n ? keyof typeof NumberOperands | keyof typeof StringOperands | keyof typeof BooleanOperands\n : Type extends null | undefined\n ? never\n : Type extends number\n ? keyof typeof NumberOperands\n : Type extends string\n ? keyof typeof StringOperands\n : Type extends boolean\n ? keyof typeof BooleanOperands\n : keyof typeof StringOperands;\n"]}
@@ -3,6 +3,12 @@ import type { FilterState } from './filter/state.js';
3
3
  import type { FilterExpression } from './filter/types.js';
4
4
  export default class FilterDataOperation<T extends object> extends DataOperation<T> {
5
5
  protected resolveFilter(record: T, expr: FilterExpression<T>): boolean;
6
+ /**
7
+ * Whether `record` passes one column's expressions: all of its `and`
8
+ * expressions (when it has any) or any one of its `or` expressions. Criteria
9
+ * never reach across columns, so each column is matched on its own.
10
+ */
6
11
  protected match(record: T, ands: FilterExpression<T>[], ors: FilterExpression<T>[]): boolean;
12
+ /** Keeps the records that pass every filtered column. */
7
13
  apply(data: T[], state: FilterState<T>): T[];
8
14
  }
@@ -5,18 +5,34 @@ export default class FilterDataOperation extends DataOperation {
5
5
  return condition.logic(this.resolveValue(record, expr.key), expr.searchTerm, expr.caseSensitive);
6
6
  }
7
7
  match(record, ands, ors) {
8
- for (const or of ors) {
9
- if (this.resolveFilter(record, or)) {
10
- return this.match(record, ands.filter((f) => f.key !== or.key), []);
11
- }
8
+ if (ands.length === 0 && ors.length === 0)
9
+ return true;
10
+ if (ands.length > 0) {
11
+ let all = true;
12
+ for (let i = 0; i < ands.length && all; i++)
13
+ all = this.resolveFilter(record, ands[i]);
14
+ if (all)
15
+ return true;
16
+ }
17
+ for (let i = 0; i < ors.length; i++) {
18
+ if (this.resolveFilter(record, ors[i]))
19
+ return true;
12
20
  }
13
- return ands.every((f) => this.resolveFilter(record, f));
21
+ return false;
14
22
  }
15
23
  apply(data, state) {
16
24
  if (state.empty)
17
25
  return data;
18
- const { ands, ors } = state;
19
- return data.filter((record) => this.match(record, ands, ors));
26
+ const trees = state.values;
27
+ const ands = trees.map((tree) => tree.ands);
28
+ const ors = trees.map((tree) => tree.ors);
29
+ return data.filter((record) => {
30
+ for (let i = 0; i < trees.length; i++) {
31
+ if (!this.match(record, ands[i], ors[i]))
32
+ return false;
33
+ }
34
+ return true;
35
+ });
20
36
  }
21
37
  }
22
38
  //# sourceMappingURL=filter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"filter.js","sourceRoot":"","sources":["../../src/operations/filter.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,WAAW,CAAC;AAItC,MAAM,CAAC,OAAO,OAAO,mBAAsC,SAAQ,aAAgB;IACvE,aAAa,CAAC,MAAS,EAAE,IAAyB;QAC1D,MAAM,SAAS,GAAG,IAAI,CAAC,SAA+B,CAAC;QACvD,OAAO,SAAS,CAAC,KAAK,CAEpB,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAM,EACxC,IAAI,CAAC,UAAe,EACpB,IAAI,CAAC,aAAa,CACnB,CAAC;IACJ,CAAC;IAES,KAAK,CAAC,MAAS,EAAE,IAA2B,EAAE,GAA0B;QAChF,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;YACrB,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;gBACnC,OAAO,IAAI,CAAC,KAAK,CACf,MAAM,EACN,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,CAAC,EACpC,EAAE,CACH,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC;IAEM,KAAK,CAAC,IAAS,EAAE,KAAqB;QAC3C,IAAI,KAAK,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAE7B,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC;QAC5B,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;IAChE,CAAC;CACF","sourcesContent":["import DataOperation from './base.js';\nimport type { FilterState } from './filter/state.js';\nimport type { FilterExpression, FilterOperation } from './filter/types.js';\n\nexport default class FilterDataOperation<T extends object> extends DataOperation<T> {\n protected resolveFilter(record: T, expr: FilterExpression<T>) {\n const condition = expr.condition as FilterOperation<T>;\n return condition.logic(\n // XXX: Types\n this.resolveValue(record, expr.key) as T,\n expr.searchTerm as T,\n expr.caseSensitive\n );\n }\n\n protected match(record: T, ands: FilterExpression<T>[], ors: FilterExpression<T>[]): boolean {\n for (const or of ors) {\n if (this.resolveFilter(record, or)) {\n return this.match(\n record,\n ands.filter((f) => f.key !== or.key),\n []\n );\n }\n }\n return ands.every((f) => this.resolveFilter(record, f));\n }\n\n public apply(data: T[], state: FilterState<T>): T[] {\n if (state.empty) return data;\n\n const { ands, ors } = state;\n return data.filter((record) => this.match(record, ands, ors));\n }\n}\n"]}
1
+ {"version":3,"file":"filter.js","sourceRoot":"","sources":["../../src/operations/filter.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,WAAW,CAAC;AAItC,MAAM,CAAC,OAAO,OAAO,mBAAsC,SAAQ,aAAgB;IACvE,aAAa,CAAC,MAAS,EAAE,IAAyB;QAC1D,MAAM,SAAS,GAAG,IAAI,CAAC,SAA+B,CAAC;QACvD,OAAO,SAAS,CAAC,KAAK,CAEpB,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAM,EACxC,IAAI,CAAC,UAAe,EACpB,IAAI,CAAC,aAAa,CACnB,CAAC;IACJ,CAAC;IAOS,KAAK,CAAC,MAAS,EAAE,IAA2B,EAAE,GAA0B;QAChF,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACvD,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,IAAI,GAAG,GAAG,IAAI,CAAC;YACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC,EAAE;gBAAE,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YACvF,IAAI,GAAG;gBAAE,OAAO,IAAI,CAAC;QACvB,CAAC;QACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACpC,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;gBAAE,OAAO,IAAI,CAAC;QACtD,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAGM,KAAK,CAAC,IAAS,EAAE,KAAqB;QAC3C,IAAI,KAAK,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAG7B,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC;QAC3B,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5C,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE;YAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACtC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;oBAAE,OAAO,KAAK,CAAC;YACzD,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC;CACF","sourcesContent":["import DataOperation from './base.js';\nimport type { FilterState } from './filter/state.js';\nimport type { FilterExpression, FilterOperation } from './filter/types.js';\n\nexport default class FilterDataOperation<T extends object> extends DataOperation<T> {\n protected resolveFilter(record: T, expr: FilterExpression<T>) {\n const condition = expr.condition as FilterOperation<T>;\n return condition.logic(\n // XXX: Types\n this.resolveValue(record, expr.key) as T,\n expr.searchTerm as T,\n expr.caseSensitive\n );\n }\n\n /**\n * Whether `record` passes one column's expressions: all of its `and`\n * expressions (when it has any) or any one of its `or` expressions. Criteria\n * never reach across columns, so each column is matched on its own.\n */\n protected match(record: T, ands: FilterExpression<T>[], ors: FilterExpression<T>[]): boolean {\n if (ands.length === 0 && ors.length === 0) return true;\n if (ands.length > 0) {\n let all = true;\n for (let i = 0; i < ands.length && all; i++) all = this.resolveFilter(record, ands[i]);\n if (all) return true;\n }\n for (let i = 0; i < ors.length; i++) {\n if (this.resolveFilter(record, ors[i])) return true;\n }\n return false;\n }\n\n /** Keeps the records that pass every filtered column. */\n public apply(data: T[], state: FilterState<T>): T[] {\n if (state.empty) return data;\n\n // Split each column into its and / or lists once per apply, not per record.\n const trees = state.values;\n const ands = trees.map((tree) => tree.ands);\n const ors = trees.map((tree) => tree.ors);\n return data.filter((record) => {\n for (let i = 0; i < trees.length; i++) {\n if (!this.match(record, ands[i], ors[i])) return false;\n }\n return true;\n });\n }\n}\n"]}
@@ -1,3 +1,4 @@
1
+ import { normalizeCase } from '../internal/normalize-case.js';
1
2
  import DataOperation from './base.js';
2
3
  export default class QuickFilterDataOperation extends DataOperation {
3
4
  apply(data, searchTerm, columns) {
@@ -11,11 +12,7 @@ export default class QuickFilterDataOperation extends DataOperation {
11
12
  }
12
13
  return data.filter((record) => {
13
14
  for (const key of keys) {
14
- const value = record[key];
15
- if (value === null || value === undefined) {
16
- continue;
17
- }
18
- if (String(value).toLowerCase().includes(term)) {
15
+ if (normalizeCase(record[key]).includes(term)) {
19
16
  return true;
20
17
  }
21
18
  }
@@ -1 +1 @@
1
- {"version":3,"file":"quick-filter.js","sourceRoot":"","sources":["../../src/operations/quick-filter.ts"],"names":[],"mappings":"AACA,OAAO,aAAa,MAAM,WAAW,CAAC;AAWtC,MAAM,CAAC,OAAO,OAAO,wBAA2C,SAAQ,aAAgB;IAQ/E,KAAK,CAAC,IAAS,EAAE,UAAkB,EAAE,OAAsC;QAChF,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC7C,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,GAAc,CAAC,CAAC;QAE/F,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE;YAC5B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;gBACvB,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC1B,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;oBAC1C,SAAS;gBACX,CAAC;gBACD,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC/C,OAAO,IAAI,CAAC;gBACd,CAAC;YACH,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC,CAAC,CAAC;IACL,CAAC;CACF","sourcesContent":["import type { ColumnConfiguration, Keys } from '../internal/types.js';\nimport DataOperation from './base.js';\n\n/**\n * Built-in quick-filter (global search) data operation.\n *\n * @remarks\n * Performs a case-insensitive substring match of the search term across every visible\n * column's stringified value. Returns the original dataset unchanged when the search\n * term is empty / whitespace-only. Used by the grid when `quickFilter` is set; the\n * search can be customised through {@link DataPipelineConfiguration.quickFilter}.\n */\nexport default class QuickFilterDataOperation<T extends object> extends DataOperation<T> {\n /**\n * Returns the records whose visible column values contain `searchTerm` (case-insensitive).\n *\n * @param data - The dataset to filter.\n * @param searchTerm - The trimmed search term (already normalised to lower-case is fine).\n * @param columns - The current column configuration; hidden columns are skipped.\n */\n public apply(data: T[], searchTerm: string, columns: Array<ColumnConfiguration<T>>): T[] {\n const term = searchTerm.trim().toLowerCase();\n if (!term) {\n return data;\n }\n\n const keys = columns.filter((column) => !column.hidden).map((column) => column.key as Keys<T>);\n\n if (keys.length === 0) {\n return data;\n }\n\n return data.filter((record) => {\n for (const key of keys) {\n const value = record[key];\n if (value === null || value === undefined) {\n continue;\n }\n if (String(value).toLowerCase().includes(term)) {\n return true;\n }\n }\n return false;\n });\n }\n}\n"]}
1
+ {"version":3,"file":"quick-filter.js","sourceRoot":"","sources":["../../src/operations/quick-filter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAE9D,OAAO,aAAa,MAAM,WAAW,CAAC;AAWtC,MAAM,CAAC,OAAO,OAAO,wBAA2C,SAAQ,aAAgB;IAQ/E,KAAK,CAAC,IAAS,EAAE,UAAkB,EAAE,OAAsC;QAChF,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC7C,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,GAAc,CAAC,CAAC;QAE/F,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE;YAC5B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;gBACvB,IAAI,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC9C,OAAO,IAAI,CAAC;gBACd,CAAC;YACH,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC,CAAC,CAAC;IACL,CAAC;CACF","sourcesContent":["import { normalizeCase } from '../internal/normalize-case.js';\nimport type { ColumnConfiguration, Keys } from '../internal/types.js';\nimport DataOperation from './base.js';\n\n/**\n * Built-in quick-filter (global search) data operation.\n *\n * @remarks\n * Performs a case-insensitive substring match of the search term across every visible\n * column's stringified value. Returns the original dataset unchanged when the search\n * term is empty / whitespace-only. Used by the grid when `quickFilter` is set; the\n * search can be customised through {@link DataPipelineConfiguration.quickFilter}.\n */\nexport default class QuickFilterDataOperation<T extends object> extends DataOperation<T> {\n /**\n * Returns the records whose visible column values contain `searchTerm` (case-insensitive).\n *\n * @param data - The dataset to filter.\n * @param searchTerm - The trimmed search term (already normalised to lower-case is fine).\n * @param columns - The current column configuration; hidden columns are skipped.\n */\n public apply(data: T[], searchTerm: string, columns: Array<ColumnConfiguration<T>>): T[] {\n const term = searchTerm.trim().toLowerCase();\n if (!term) {\n return data;\n }\n\n const keys = columns.filter((column) => !column.hidden).map((column) => column.key as Keys<T>);\n\n if (keys.length === 0) {\n return data;\n }\n\n return data.filter((record) => {\n for (const key of keys) {\n if (normalizeCase(record[key]).includes(term)) {\n return true;\n }\n }\n return false;\n });\n }\n}\n"]}
@@ -1,8 +1,27 @@
1
1
  import DataOperation from './base.js';
2
- import type { SortExpression, SortState } from './sort/types.js';
2
+ import type { SortState } from './sort/types.js';
3
+ /**
4
+ * Sorts rows by one or more sort expressions, in priority order.
5
+ *
6
+ * @remarks
7
+ * The built-in comparison (an expression without a `comparer`):
8
+ * - Empty cells, meaning `null`, `undefined`, `NaN` and invalid Dates, sort after every
9
+ * real value in both directions, and empties count as equal to each other.
10
+ * - Strings compare with `localeCompare`, case folded first unless `caseSensitive` is true.
11
+ * Other values compare with `<` and `>`.
12
+ *
13
+ * A custom `comparer` receives the raw cell values, empty ones and original casing
14
+ * included, and owns their order; a descending sort reverses its result.
15
+ *
16
+ * Rows that tie on every expression keep their incoming order, so the result is a stable
17
+ * total order. Each row's key is resolved once per expression before sorting, so case
18
+ * folding costs one call per row rather than one per comparison.
19
+ */
3
20
  export default class SortDataOperation<T> extends DataOperation<T> {
21
+ #private;
4
22
  protected orderBy: Map<string, number>;
5
23
  protected compareValues<U>(first: U, second: U): number;
6
- protected compareObjects(first: T, second: T, expression: SortExpression<T>): number;
24
+ /** The key the built-in comparison sorts by: case folded, Dates as time, empties as EMPTY. */
25
+ protected sortKey(value: unknown, caseSensitive?: boolean): {};
7
26
  apply(data: T[], state: SortState<T>): T[];
8
27
  }
@@ -1,4 +1,5 @@
1
1
  import DataOperation from './base.js';
2
+ const EMPTY = Symbol('empty');
2
3
  export default class SortDataOperation extends DataOperation {
3
4
  constructor() {
4
5
  super(...arguments);
@@ -13,24 +14,50 @@ export default class SortDataOperation extends DataOperation {
13
14
  }
14
15
  return first > second ? 1 : first < second ? -1 : 0;
15
16
  }
16
- compareObjects(first, second, expression) {
17
- const { direction, key, caseSensitive, comparer } = expression;
18
- const a = this.resolveCase(this.resolveValue(first, key), caseSensitive);
19
- const b = this.resolveCase(this.resolveValue(second, key), caseSensitive);
20
- return (this.orderBy.get(direction) * (comparer?.(a, b) ?? this.compareValues(a, b)));
17
+ sortKey(value, caseSensitive) {
18
+ const key = value instanceof Date ? value.getTime() : this.resolveCase(value, caseSensitive);
19
+ return key == null || Number.isNaN(key) ? EMPTY : key;
20
+ }
21
+ #rowCompare(data, expression, dir) {
22
+ const { key, caseSensitive, comparer } = expression;
23
+ const keys = data.map((row) => comparer
24
+ ? this.resolveValue(row, key)
25
+ : this.sortKey(this.resolveValue(row, key), caseSensitive));
26
+ if (comparer) {
27
+ return (x, y) => dir * comparer(keys[x], keys[y]);
28
+ }
29
+ return (x, y) => {
30
+ const a = keys[x];
31
+ const b = keys[y];
32
+ if (a === EMPTY || b === EMPTY) {
33
+ return a === b ? 0 : a === EMPTY ? 1 : -1;
34
+ }
35
+ return dir * this.compareValues(a, b);
36
+ };
21
37
  }
22
38
  apply(data, state) {
23
- const expressions = Array.from(state.values());
24
- const length = expressions.length;
25
- data.sort((a, b) => {
26
- let i = 0;
27
- let result = 0;
28
- while (i < length && !result) {
29
- result = this.compareObjects(a, b, expressions[i]);
30
- i++;
39
+ const compares = [];
40
+ for (const expression of state.values()) {
41
+ const dir = this.orderBy.get(expression.direction);
42
+ if (dir)
43
+ compares.push(this.#rowCompare(data, expression, dir));
44
+ }
45
+ if (!compares.length)
46
+ return data;
47
+ const length = compares.length;
48
+ const order = data.map((_, i) => i);
49
+ order.sort((x, y) => {
50
+ for (let i = 0; i < length; i++) {
51
+ const result = compares[i](x, y);
52
+ if (result)
53
+ return result;
31
54
  }
32
- return result;
55
+ return x - y;
33
56
  });
57
+ const rows = data.slice();
58
+ for (let i = 0; i < order.length; i++) {
59
+ data[i] = rows[order[i]];
60
+ }
34
61
  return data;
35
62
  }
36
63
  }