@guiexpert/table 1.1.94 → 1.1.96

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 (72) hide show
  1. package/README.md +12 -0
  2. package/css/main.css +192 -0
  3. package/css/table-calc.css +7 -0
  4. package/css/table-color-vars.css +46 -0
  5. package/css/table-fill-batch.css +58 -0
  6. package/css/table-row-drag.css +81 -0
  7. package/index.cjs +39 -37
  8. package/index.d.ts +28 -0
  9. package/index.js +5501 -1934
  10. package/lib/table/action/action-id.type.d.ts +1 -1
  11. package/lib/table/aria/aria-texts.d.ts +11 -0
  12. package/lib/table/aria/aria-texts.if.d.ts +87 -0
  13. package/lib/table/batch-edit/batch-edit-options.d.ts +21 -0
  14. package/lib/table/batch-edit/batch-edit-options.if.d.ts +20 -0
  15. package/lib/table/batch-edit/batch-edit-service.d.ts +104 -0
  16. package/lib/table/batch-edit/cell-change.if.d.ts +39 -0
  17. package/lib/table/batch-edit/cell-value-change.if.d.ts +18 -0
  18. package/lib/table/batch-edit/pending-cell-changes.d.ts +104 -0
  19. package/lib/table/calc/column-values.d.ts +55 -0
  20. package/lib/table/calc/show-values-as-internals.d.ts +4 -0
  21. package/lib/table/calc/show-values-as.d.ts +87 -0
  22. package/lib/table/calc/show-values-as.type.d.ts +55 -0
  23. package/lib/table/calc/value-getter-context.if.d.ts +35 -0
  24. package/lib/table/calc/value-getter.type.d.ts +36 -0
  25. package/lib/table/data/common/event/ge-batch-edit-event.d.ts +46 -0
  26. package/lib/table/data/common/event/ge-model-change-event.d.ts +27 -1
  27. package/lib/table/data/common/event/ge-row-drag-event.d.ts +89 -0
  28. package/lib/table/data/options/table-options.d.ts +6 -0
  29. package/lib/table/data/options/table-options.if.d.ts +44 -0
  30. package/lib/table/data/tablemodel/areamodel/abstract-area-model.d.ts +66 -0
  31. package/lib/table/data/tablemodel/areamodel/area-model-aggregate-footer.d.ts +5 -0
  32. package/lib/table/data/tablemodel/areamodel/area-model-array-of-arrays.d.ts +24 -0
  33. package/lib/table/data/tablemodel/areamodel/area-model-object-array.d.ts +70 -0
  34. package/lib/table/data/tablemodel/areamodel/area-model-pivot.d.ts +31 -0
  35. package/lib/table/data/tablemodel/areamodel/area-model-row-groups.d.ts +23 -1
  36. package/lib/table/data/tablemodel/areamodel/area-model-tree.d.ts +83 -0
  37. package/lib/table/data/tablemodel/areamodel/area-model.if.d.ts +55 -0
  38. package/lib/table/data/tablemodel/column/column-def.d.ts +31 -1
  39. package/lib/table/data/tablemodel/column/column-def.if.d.ts +86 -0
  40. package/lib/table/event-adapter.d.ts +7 -0
  41. package/lib/table/event-listener.if.d.ts +32 -0
  42. package/lib/table/factory/table-factory.d.ts +11 -0
  43. package/lib/table/fill/compute-fill-changes.d.ts +67 -0
  44. package/lib/table/fill/fill-direction.type.d.ts +7 -0
  45. package/lib/table/fill/fill-handle-options.d.ts +29 -0
  46. package/lib/table/fill/fill-handle-options.if.d.ts +35 -0
  47. package/lib/table/fill/fill-handle-service.d.ts +55 -0
  48. package/lib/table/fill/fill-series.d.ts +60 -0
  49. package/lib/table/fill/fill-value-context.if.d.ts +36 -0
  50. package/lib/table/formula/compile-formula.d.ts +86 -0
  51. package/lib/table/formula/evaluator.d.ts +19 -0
  52. package/lib/table/formula/formula-ast.if.d.ts +42 -0
  53. package/lib/table/formula/formula-error.d.ts +59 -0
  54. package/lib/table/formula/formula-options.if.d.ts +41 -0
  55. package/lib/table/formula/formula-values.d.ts +13 -0
  56. package/lib/table/formula/functions.d.ts +18 -0
  57. package/lib/table/formula/parser.d.ts +10 -0
  58. package/lib/table/formula/tokenizer.d.ts +22 -0
  59. package/lib/table/pivot/pivot-value.type.d.ts +13 -0
  60. package/lib/table/render-scope.d.ts +81 -0
  61. package/lib/table/renderer/edit/input-cell-renderer.d.ts +1 -1
  62. package/lib/table/renderer/edit/select-cell-renderer.d.ts +1 -1
  63. package/lib/table/row-drag/move-rows.d.ts +157 -0
  64. package/lib/table/row-drag/row-drag-options.d.ts +28 -0
  65. package/lib/table/row-drag/row-drag-options.if.d.ts +36 -0
  66. package/lib/table/row-drag/row-drag-service.d.ts +130 -0
  67. package/lib/table/row-drag/row-drop-target.if.d.ts +29 -0
  68. package/lib/table/service/excel-service.d.ts +6 -0
  69. package/lib/table/service/excel-service.if.d.ts +35 -0
  70. package/lib/table/table-api.d.ts +277 -2
  71. package/lib/table/table-scope.d.ts +121 -0
  72. package/package.json +1 -1
@@ -6,6 +6,10 @@ import { FilterFunction } from '../../common/filter-function.js';
6
6
  import { SorterService } from '../../../service/sorter.service.js';
7
7
  import { SortItem } from '../../common/sort-item.js';
8
8
  import { TreeRowIf } from '../../common/tree-row-if.js';
9
+ import { CellRendererIf } from '../../../renderer/cell-render.if.js';
10
+ import { DerivedValuesChangeIf } from './derived-values-change.if.js';
11
+ import { ShowValuesAsIf } from '../../../calc/show-values-as.type.js';
12
+ import { RowDropTargetIf } from '../../../row-drag/row-drop-target.if.js';
9
13
 
10
14
  /**
11
15
  * Represents a tree-based table model.
@@ -57,6 +61,8 @@ export declare class AreaModelTree<S> extends AbstractAreaModel<TreeRow<S>> {
57
61
  private lastPredictFn?;
58
62
  /** `row.keep` was set for the current filter (it depends on the filter and the data, not on expand/collapse). */
59
63
  private keepMarked;
64
+ /** „Show values as“: shown value per row and property (cleared on filter, sorting and invalidation). */
65
+ private showValuesCache;
60
66
  constructor(areaIdent: AreaIdent, rows: TreeRowIf<S>[], defaultRowHeight: number, columnDefs?: ColumnDefIf[]);
61
67
  changeColumnOrder(sourceColumnIndex: number, targetColumnIndex: number): void;
62
68
  /**
@@ -136,7 +142,84 @@ export declare class AreaModelTree<S> extends AbstractAreaModel<TreeRow<S>> {
136
142
  * @since 1.1.92
137
143
  */
138
144
  getUnfilteredRowCount(): number;
145
+ /**
146
+ * Row drag: true if the rows can be moved to the target – no filter hides a displayed row, and the target is none of
147
+ * the dragged rows and no descendant of them (no cycle). `"before"` / `"after"` = sibling of the target row,
148
+ * `"inside"` = last child.
149
+ *
150
+ * @param {number[]} rowIndices - display indices of the dragged rows
151
+ * @param {RowDropTargetIf} target - the drop target
152
+ * @returns {boolean} true if {@link moveRows} would move them
153
+ *
154
+ * @example
155
+ * const treeModel = tableModel.getBodyModel() as AreaModelTree<OrderNode>;
156
+ * treeModel.canMoveRows([0], { rowIndex: 1, position: "inside" }); // false if row 1 is a child of row 0
157
+ *
158
+ * @since 1.1.95
159
+ */
160
+ canMoveRows(rowIndices: number[], target: RowDropTargetIf): boolean;
161
+ /**
162
+ * Row drag: moves tree rows (with their children) in front of / behind the target row (siblings) or into it
163
+ * (`"inside"`: last children, the target is expanded) – in place in {@link rows} and the `children` arrays;
164
+ * `parent` and `deep` follow. See {@link canMoveRows}.
165
+ *
166
+ * @param {number[]} rowIndices - display indices of the dragged rows
167
+ * @param {RowDropTargetIf} target - the drop target (display index before the move)
168
+ * @returns {boolean} true if moved
169
+ *
170
+ * @example
171
+ * const treeModel = tableModel.getBodyModel() as AreaModelTree<OrderNode>;
172
+ * treeModel.moveRows([3], { rowIndex: 0, position: "inside" });
173
+ * tableApi.repaintHard(); // or simply tableApi.moveRows([3], { rowIndex: 0, position: "inside" })
174
+ *
175
+ * @since 1.1.95
176
+ */
177
+ moveRows(rowIndices: number[], target: RowDropTargetIf): boolean;
178
+ /** The topmost tree rows of the display indices (children travel with their parent). */
179
+ private getDragRows;
139
180
  getValueAt(rowIndex: number, columnIndex: number): any;
181
+ /**
182
+ * The value of a tree row for a property – before „Show values as“: the computed value of a calculated column,
183
+ * otherwise the property (path) of `row.data`. Subclasses (row groups) return their aggregates for group rows.
184
+ *
185
+ * @param {TreeRowIf<S>} row - the tree row
186
+ * @param {string} prop - the property of the column
187
+ * @returns {any} the raw value
188
+ * @since 1.1.95
189
+ */
190
+ protected getRawValueOfRow(row: TreeRowIf<S>, prop: string): any;
191
+ /**
192
+ * The text of a cell: for a „Show values as“ column the formatted value (`"66.7%"`), for a formula error its code;
193
+ * otherwise like before (`'' + value`).
194
+ *
195
+ * @param {number} rowIndex - the row
196
+ * @param {number} columnIndex - the column
197
+ * @returns {string} the text
198
+ */
199
+ getTextValueAt(rowIndex: number, columnIndex: number): string;
200
+ /** A „Show values as“ column without an own body renderer shows the formatted text. */
201
+ getCellRenderer(rowIndex: number, columnIndex: number): CellRendererIf | undefined;
202
+ /** The message of a formula error as tooltip. */
203
+ getTooltipAt(rowIndex: number, columnIndex: number): any;
204
+ /**
205
+ * Drops derived values: cached aggregates of formulas and „Show values as“ (called by the table on filter,
206
+ * sorting, editing, `updateCells` and `api.repaint()`).
207
+ *
208
+ * @param {DerivedValuesChangeIf} [_change] - the changed rows (not used here)
209
+ * @since 1.1.95
210
+ */
211
+ invalidateDerivedValues(_change?: DerivedValuesChangeIf): void;
212
+ /**
213
+ * „Show values as“ of one tree row: percentages of the column total relate to the (filtered) leaf rows,
214
+ * `percentOfParentTotal`, `runningTotal`, `differenceFromPrevious` and `rank` to the displayed siblings.
215
+ */
216
+ protected applyShowValuesAs(row: TreeRowIf<S>, property: string, value: unknown, options: ShowValuesAsIf): unknown;
217
+ protected readPropertyPath(t: any, property: string): unknown;
218
+ protected getAggregationSourceForValues(): {
219
+ signal: unknown;
220
+ length: number;
221
+ rows: () => ArrayLike<unknown>;
222
+ };
140
223
  getRowHeight(_rowIndex: number): number;
141
224
  /**
142
225
  * All rows share `defaultRowHeight` – unless a subclass overrides `getRowHeight()`, then row heights may
@@ -1,3 +1,4 @@
1
+ import { RowDropTargetIf } from '../../../row-drag/row-drop-target.if.js';
1
2
  import { DerivedValuesChangeIf } from './derived-values-change.if.js';
2
3
  import { CellRendererIf } from '../../../renderer/cell-render.if.js';
3
4
  import { CheckedType } from '../../common/checked-type.js';
@@ -281,6 +282,40 @@ export interface AreaModelIf {
281
282
  * @since 1.1.92
282
283
  */
283
284
  getUnfilteredRowCount?(): number;
285
+ /**
286
+ * Optional (row drag, managed mode): true if the rows can be moved to the target – e.g. false for a node dropped
287
+ * into its own child, or while a filter hides rows. Without {@link moveRows} the table cannot move rows itself.
288
+ *
289
+ * @param {number[]} rowIndices - display indices of the dragged rows (ascending)
290
+ * @param {RowDropTargetIf} target - the drop target (display index before the move)
291
+ * @returns {boolean} true if {@link moveRows} would move them
292
+ *
293
+ * @example
294
+ * class TasksBodyModel extends AreaModel {
295
+ * tasks: string[] = ["Write", "Test", "Ship"];
296
+ * override getRowCount(): number { return this.tasks.length; }
297
+ * override getValueAt(rowIndex: number): any { return this.tasks[rowIndex]; }
298
+ * canMoveRows(rowIndices: number[], target: RowDropTargetIf): boolean { return target.position !== "inside"; }
299
+ * moveRows(rowIndices: number[], target: RowDropTargetIf): boolean {
300
+ * return moveArrayItems(this.tasks, rowIndices, getInsertBeforeIndex(target)) > -1;
301
+ * }
302
+ * }
303
+ *
304
+ * @since 1.1.95
305
+ */
306
+ canMoveRows?(rowIndices: number[], target: RowDropTargetIf): boolean;
307
+ /**
308
+ * Optional (row drag, managed mode): moves the rows to the target in the model – the table calls it on a drop and
309
+ * for `tableApi.moveRows()`, then repaints. Implemented by `AreaModelObjectArray`, `AreaModelArrayOfArrays` and
310
+ * `AreaModelTree` (in place in their row arrays). See {@link canMoveRows} for an example.
311
+ *
312
+ * @param {number[]} rowIndices - display indices of the dragged rows (ascending)
313
+ * @param {RowDropTargetIf} target - the drop target (display index before the move)
314
+ * @returns {boolean} true if the rows were moved
315
+ *
316
+ * @since 1.1.95
317
+ */
318
+ moveRows?(rowIndices: number[], target: RowDropTargetIf): boolean;
284
319
  /**
285
320
  * Optional: drops cached, derived values (e.g. the totals of an {@link AreaModelAggregateFooter}). The table calls
286
321
  * it for all three areas when data may have changed: filter, sorting, editing, `updateCells()` and the public
@@ -319,6 +354,26 @@ export interface AreaModelIf {
319
354
  * @since 1.1.94
320
355
  */
321
356
  isRowHeaderCell?(rowIndex: number, columnIndex: number): boolean;
357
+ /**
358
+ * Optional: true if the column with this property is calculated (`ColumnDefIf.valueGetter` / `formula`).
359
+ * Implemented by the object-array, tree, row-group and pivot models.
360
+ *
361
+ * @example
362
+ * tableModel.getBodyModel().isCalculatedProperty?.("total"); // true
363
+ *
364
+ * @since 1.1.95
365
+ */
366
+ isCalculatedProperty?(property: string): boolean;
367
+ /**
368
+ * Optional: the value of a column for a row object – computed for a calculated column, otherwise the property
369
+ * path ({@link TableApi.getRowValue}). Implemented by the object-array, tree, row-group and pivot models.
370
+ *
371
+ * @example
372
+ * tableModel.getBodyModel().getRowValue?.({ price: 25, quantity: 10 }, "total"); // 250
373
+ *
374
+ * @since 1.1.95
375
+ */
376
+ getRowValue?(row: any, property: string): unknown;
322
377
  /**
323
378
  * Returns the value of the cell at the specified row and column indices.
324
379
  * This is one of the most important methods in the area model, as it provides
@@ -1,5 +1,5 @@
1
1
  import { SizeIf } from '../../common/size.if.js';
2
- import { ColumnDefIf, ValueRowSortComparator } from './column-def.if.js';
2
+ import { CellValidator, ColumnDefIf, RowDragColumnOption, ValueRowSortComparator } from './column-def.if.js';
3
3
  import { CellRendererIf } from '../../../renderer/cell-render.if.js';
4
4
  import { AreaObjectMapType } from '../../common/area-map.type.js';
5
5
  import { AreaObjectMap } from '../../common/area-map.js';
@@ -7,6 +7,9 @@ import { BooleanFunction } from '../../common/boolean-function.js';
7
7
  import { SortState } from '../../common/sort-state.type.js';
8
8
  import { GetEditRenderer } from '../../../renderer/edit/edit-cell-renderer.type.js';
9
9
  import { editInputPipe } from '../../../renderer/edit/edit-input-pipe.if.js';
10
+ import { ValueGetter, ValueSetter } from '../../../calc/value-getter.type.js';
11
+ import { ShowValuesAsConfig } from '../../../calc/show-values-as.type.js';
12
+ import { FormulaOptionsIf } from '../../../formula/formula-options.if.js';
10
13
 
11
14
  /**
12
15
  * The column definition – see {@link ColumnDefIf} for all fields.
@@ -52,6 +55,17 @@ export declare class ColumnDef implements ColumnDefIf {
52
55
  editInputPipe?: editInputPipe | undefined;
53
56
  isVisible: BooleanFunction;
54
57
  visible: boolean;
58
+ /** A calculated column, see {@link ColumnDefIf.valueGetter}. */
59
+ valueGetter?: ValueGetter;
60
+ /** Writes an edited calculated value back, see {@link ColumnDefIf.valueSetter}. */
61
+ valueSetter?: ValueSetter;
62
+ /** „Show values as“, see {@link ColumnDefIf.showValuesAs}. */
63
+ showValuesAs?: ShowValuesAsConfig;
64
+ /** The formula of a formula column, see {@link ColumnDefIf.formula}. */
65
+ formula?: string;
66
+ rowDrag?: RowDragColumnOption;
67
+ /** Checks edited values, see {@link ColumnDefIf.validate} (since 1.1.96). */
68
+ validate?: CellValidator;
55
69
  constructor(property: string, headerLabel: string, width?: SizeIf, classes?: AreaObjectMapType<string[]>, rendererMap?: AreaObjectMapType<CellRendererIf>, minWidth?: SizeIf, maxWidth?: SizeIf, sortable?: BooleanFunction | undefined, sortComparator?: ValueRowSortComparator | undefined, sortState?: SortState | undefined, sortStatesOrder?: SortState[] | undefined, sortIconVisible?: BooleanFunction | undefined, editable?: BooleanFunction | undefined, getEditRenderer?: GetEditRenderer | undefined, editInputPipe?: editInputPipe | undefined, isVisible?: BooleanFunction);
56
70
  static bodyRenderer(bodyRenderer: CellRendererIf): AreaObjectMap<CellRendererIf>;
57
71
  /**
@@ -109,5 +123,21 @@ export declare class ColumnDef implements ColumnDefIf {
109
123
  getEditRenderer?: GetEditRenderer;
110
124
  /** Converts the editor text before it is stored, e.g. `editInputPipeForNumber`. */
111
125
  editInputPipe?: editInputPipe;
126
+ /** A calculated column: `(row, context) => value` (since 1.1.95). */
127
+ valueGetter?: ValueGetter;
128
+ /** Makes a calculated column editable (since 1.1.95). */
129
+ valueSetter?: ValueSetter;
130
+ /** „Show values as“: `"percentOfColumnTotal"`, `"rank"` … (since 1.1.95). */
131
+ showValuesAs?: ShowValuesAsConfig;
132
+ /**
133
+ * A formula column, e.g. `"[price] * [quantity] * 1.19"` – compiled once into the `valueGetter` (since 1.1.95,
134
+ * see {@link compileFormula}). Takes precedence over `valueGetter`.
135
+ */
136
+ formula?: string;
137
+ /** Own functions of the formula (since 1.1.95). */
138
+ formulaOptions?: FormulaOptionsIf;
139
+ rowDrag?: RowDragColumnOption;
140
+ /** Checks edited values: a message for an invalid value (since 1.1.96). */
141
+ validate?: CellValidator;
112
142
  }): ColumnDefIf;
113
143
  }
@@ -5,6 +5,8 @@ import { BooleanFunction } from '../../common/boolean-function.js';
5
5
  import { SortState } from '../../common/sort-state.type.js';
6
6
  import { GetEditRenderer } from '../../../renderer/edit/edit-cell-renderer.type.js';
7
7
  import { editInputPipe } from '../../../renderer/edit/edit-input-pipe.if.js';
8
+ import { ValueGetter, ValueSetter } from '../../../calc/value-getter.type.js';
9
+ import { ShowValuesAsConfig } from '../../../calc/show-values-as.type.js';
8
10
 
9
11
  /**
10
12
  * Represents a function that compares two values for sorting purposes.
@@ -116,4 +118,88 @@ export interface ColumnDefIf {
116
118
  getEditRenderer?: GetEditRenderer;
117
119
  /** Converts the text of the editor before it is written into the model, e.g. {@link editInputPipeForNumber}. */
118
120
  editInputPipe?: editInputPipe;
121
+ /**
122
+ * A calculated column: computes the value from the row instead of reading `property` (the data stays unchanged).
123
+ * Sorting, filter ({@link TableApi.getRowValue}), copy, Excel, totals (`footerAggregates`) and row groups use the
124
+ * computed value. `property` stays the id of the column (state, `footerAggregates`, other formulas).
125
+ * Without a {@link ColumnDefIf.valueSetter} the column cannot be edited.
126
+ *
127
+ * Supported by the models of `TableFactory.createTableModel({ rows })`, trees, row groups (not by array-of-arrays
128
+ * models; an own `AreaModel` calls it itself).
129
+ *
130
+ * @example
131
+ * ColumnDef.create({ property: "total", headerLabel: "Total", sortable: TrueFn,
132
+ * valueGetter: (o: Item) => o.price * o.quantity })
133
+ *
134
+ * @see Training: [Columns with ColumnDef](https://gui.expert/training/column-definitions/)
135
+ * @since 1.1.95
136
+ */
137
+ valueGetter?: ValueGetter;
138
+ /**
139
+ * Makes a calculated column editable: writes the edited value back into the row (return `false` to reject).
140
+ *
141
+ * @example
142
+ * valueSetter: (o: Item, v: unknown) => { o.price = Number(v) / o.quantity; }
143
+ *
144
+ * @since 1.1.95
145
+ */
146
+ valueSetter?: ValueSetter;
147
+ /**
148
+ * Shows the numbers of the column differently without changing the data: share of the column total, running
149
+ * total, rank … (see {@link ShowValuesAs}). Change it at runtime with {@link TableApi.setShowValuesAs}.
150
+ *
151
+ * @example
152
+ * ColumnDef.create({ property: "total", headerLabel: "Share", showValuesAs: "percentOfColumnTotal" })
153
+ *
154
+ * @since 1.1.95
155
+ */
156
+ showValuesAs?: ShowValuesAsConfig;
157
+ /**
158
+ * The formula of a formula column (`ColumnDef.create({ formula })` compiles it into the `valueGetter`), e.g.
159
+ * `"[price] * [quantity] * 1.19"`. Informational – change it with {@link TableApi.setColumnFormula}.
160
+ *
161
+ * @since 1.1.95
162
+ */
163
+ formula?: string;
164
+ /**
165
+ * Row drag: the body cells of this column get a drag handle (`true`, or a function that decides per row – e.g. no
166
+ * handle for locked rows). Dragging the handle moves the row (see {@link TableOptionsIf.rowDragOptions}); the keyboard
167
+ * alternative is Ctrl+Alt+↑/↓. `TableFactory.createTableModel({ rows, rowDrag: true })` sets it on the first data
168
+ * column.
169
+ *
170
+ * @example
171
+ * ColumnDef.create({ property: "title", headerLabel: "Task", rowDrag: (task: { locked: boolean }) => !task.locked })
172
+ *
173
+ * @since 1.1.95
174
+ */
175
+ rowDrag?: RowDragColumnOption;
176
+ /**
177
+ * Checks an edited, filled or pasted value (after `editInputPipe`): return a message for an invalid value,
178
+ * `undefined` for a valid one. Batch edit: invalid values are marked (`ge-table-cell-invalid`, the message as
179
+ * tooltip) and block {@link TableApi.commitBatchEdit}; otherwise an invalid value is not written.
180
+ *
181
+ * @example
182
+ * ColumnDef.create({
183
+ * property: "price", headerLabel: "Price", editable: TrueFn, editInputPipe: editInputPipeForNumber,
184
+ * validate: (value: unknown) => typeof value === "number" && value >= 0 ? undefined : "A price is a number ≥ 0",
185
+ * })
186
+ *
187
+ * @since 1.1.96
188
+ */
189
+ validate?: CellValidator;
119
190
  }
191
+ /**
192
+ * {@link ColumnDefIf.validate}: a message for an invalid value, `undefined` (or `null`, `""`) for a valid one.
193
+ *
194
+ * @category Fill handle & batch edit
195
+ * @since 1.1.96
196
+ */
197
+ export type CellValidator<T = any> = (value: unknown, row: T) => string | undefined | null | void;
198
+ /**
199
+ * {@link ColumnDefIf.rowDrag}: `true` = every row has a drag handle in this column, a function decides per row
200
+ * (`row` = the row object, a `TreeRow` in a tree; `rowIndex` = display index).
201
+ *
202
+ * @category Row drag
203
+ * @since 1.1.95
204
+ */
205
+ export type RowDragColumnOption = boolean | ((row: any, rowIndex: number) => boolean);
@@ -5,6 +5,8 @@ import { EventListenerIf } from './event-listener.if.js';
5
5
  import { SelectionModelIf } from './selection/selection-model.if.js';
6
6
  import { FocusModelIf } from './focus/focus-model.if.js';
7
7
  import { GeStateChangeEvent } from './data/common/event/ge-state-change-event.js';
8
+ import { GeBatchEditEvent } from './data/common/event/ge-batch-edit-event.js';
9
+ import { GeRowDragEvent } from './data/common/event/ge-row-drag-event.js';
8
10
 
9
11
  /**
10
12
  * Empty implementation of {@link EventListenerIf} – extend it and override only the events you need.
@@ -33,4 +35,9 @@ export declare class EventAdapter implements EventListenerIf {
33
35
  onFocusChanged(_model: FocusModelIf): void;
34
36
  /** @since 1.1.92 */
35
37
  onStateChanged(_evt: GeStateChangeEvent): void;
38
+ onRowDragStart(_evt: GeRowDragEvent): void;
39
+ onRowDragMove(_evt: GeRowDragEvent): void;
40
+ onRowDragEnd(_evt: GeRowDragEvent): void;
41
+ /** @since 1.1.96 */
42
+ onBatchEditChanged(_evt: GeBatchEditEvent): void;
36
43
  }
@@ -2,6 +2,8 @@ import { GeMouseEvent } from './data/common/event/ge-mouse-event.js';
2
2
  import { GeModelChangeEvent } from './data/common/event/ge-model-change-event.js';
3
3
  import { GeScrollEvent } from './data/common/event/ge-scroll-event.js';
4
4
  import { GeStateChangeEvent } from './data/common/event/ge-state-change-event.js';
5
+ import { GeRowDragEvent } from './data/common/event/ge-row-drag-event.js';
6
+ import { GeBatchEditEvent } from './data/common/event/ge-batch-edit-event.js';
5
7
  import { SelectionModelIf } from './selection/selection-model.if.js';
6
8
  import { FocusModelIf } from './focus/focus-model.if.js';
7
9
  import { EventFocusChangedListenerIf } from './focus/event-focus-changed-listener.if.js';
@@ -63,4 +65,34 @@ export interface EventListenerIf extends EventFocusChangedListenerIf, EventSelec
63
65
  * @since 1.1.92
64
66
  */
65
67
  onStateChanged?(evt: GeStateChangeEvent): void;
68
+ /**
69
+ * Optional: a row drag started (drag handle moved past the threshold, or Ctrl+Alt+↑/↓). `evt.preventDefault()`
70
+ * cancels it. See {@link GeRowDragEvent}.
71
+ *
72
+ * @since 1.1.95
73
+ */
74
+ onRowDragStart?(evt: GeRowDragEvent): void;
75
+ /**
76
+ * Optional: the drop target of a row drag changed (not on every pixel). `evt.preventDefault()` marks the target as
77
+ * invalid (no drop marker, no drop).
78
+ *
79
+ * @since 1.1.95
80
+ */
81
+ onRowDragMove?(evt: GeRowDragEvent): void;
82
+ /**
83
+ * Optional: a row drag ended – `evt.type` is `"end"` (dropped; `evt.moved` = the table moved the rows) or `"cancel"`.
84
+ * Managed: called after the move (`evt.toIndex` = the real position). Save the new order here; to veto a drop, call
85
+ * `evt.preventDefault()` in {@link onRowDragMove}.
86
+ *
87
+ * @since 1.1.95
88
+ */
89
+ onRowDragEnd?(evt: GeRowDragEvent): void;
90
+ /**
91
+ * The pending changes of a batch edit changed: values were marked, set back, written or discarded
92
+ * ({@link TableApi.startBatchEdit}, {@link TableOptionsIf.batchEdit}). Optional.
93
+ *
94
+ * @param {GeBatchEditEvent} evt - type, cells of this step, number of pending and invalid cells
95
+ * @since 1.1.96
96
+ */
97
+ onBatchEditChanged?(evt: GeBatchEditEvent): void;
66
98
  }
@@ -78,6 +78,17 @@ export interface CreateTableModelPara {
78
78
  * @since 1.1.93
79
79
  */
80
80
  rowNumbersVisible: boolean | Partial<RowNumberOptionsIf>;
81
+ /**
82
+ * Row drag: `true` puts a drag handle into the first data column (behind row numbers and checkboxes) –
83
+ * sets {@link ColumnDefIf.rowDrag} on that column definition. Options: {@link TableOptionsIf.rowDragOptions}.
84
+ * Default false.
85
+ *
86
+ * @example
87
+ * const tableModel = TableFactory.createTableModel({ rows: orders, rowDrag: true });
88
+ *
89
+ * @since 1.1.95
90
+ */
91
+ rowDrag: boolean;
81
92
  /** Row heights of header, body and footer in px. Default: `tableOptions.defaultRowHeights`, otherwise 34 / 34 / 34. */
82
93
  defaultRowHeights: DefaultRowHeightsIf;
83
94
  /** The columns: property, header label, width, classes, renderers, sorting, editing. See {@link ColumnDef}. */
@@ -0,0 +1,67 @@
1
+ import { CellRange } from '../data/common/cell-range.js';
2
+ import { CellValueChangeIf } from '../batch-edit/cell-value-change.if.js';
3
+ import { FillDirection } from './fill-direction.type.js';
4
+ import { FillValueContextIf } from './fill-value-context.if.js';
5
+ import { FillMode } from './fill-series.js';
6
+
7
+ /**
8
+ * Input of {@link computeFillChanges}.
9
+ *
10
+ * @category Fill handle & batch edit
11
+ * @since 1.1.96
12
+ */
13
+ export interface FillRequestIf {
14
+ /** The selected source range (body display indices, `r1 <= r2`, `c1 <= c2`). */
15
+ source: CellRange;
16
+ /** Where the range is extended to. */
17
+ direction: FillDirection;
18
+ /** The number of rows (down/up) or columns (right/left) to fill. */
19
+ count: number;
20
+ /** Series or copy, see {@link FillMode}. */
21
+ mode?: FillMode;
22
+ /** The shown value of a cell (batch edit: the pending value). */
23
+ getValue: (rowIndex: number, columnIndex: number) => unknown;
24
+ /** `false` skips a target cell (not editable, hidden column …); the series keeps counting. */
25
+ isFillable?: (rowIndex: number, columnIndex: number) => boolean;
26
+ /** Own fill function, see {@link FillHandleOptionsIf.fillValue}. */
27
+ fillValue?: (ctx: FillValueContextIf) => unknown;
28
+ /** Row object and property of a cell – for `fillValue`. */
29
+ getCellInfo?: (rowIndex: number, columnIndex: number) => {
30
+ row: unknown;
31
+ property: string;
32
+ };
33
+ }
34
+ /**
35
+ * The cells a fill covers: the source range extended by `count` rows or columns in `direction`.
36
+ *
37
+ * @param {CellRange} source - the selected range
38
+ * @param {FillDirection} direction - the direction of the fill
39
+ * @param {number} count - rows or columns added
40
+ * @returns {CellRange} source and filled cells together
41
+ *
42
+ * @example
43
+ * getFillRange(new CellRange(2, 1, 3, 1), "down", 3); // rows 2…6 of column 1
44
+ *
45
+ * @category Fill handle & batch edit
46
+ * @since 1.1.96
47
+ */
48
+ export declare function getFillRange(source: CellRange, direction: FillDirection, count: number): CellRange;
49
+ /**
50
+ * The values a fill writes: per line of the source range (each column when filling down/up, each row when filling
51
+ * right/left) the continuation of its values ({@link computeFillValues}) – DOM-free, the table applies the result with
52
+ * {@link TableApi.applyCellChanges}.
53
+ *
54
+ * @param {FillRequestIf} request - source range, direction, count, value access
55
+ * @returns {CellValueChangeIf[]} one change per fillable target cell (column = model index)
56
+ *
57
+ * @example
58
+ * const values = [[1], [2]]; // a 2 × 1 source range
59
+ * computeFillChanges({
60
+ * source: new CellRange(0, 0, 1, 0), direction: "down", count: 2,
61
+ * getValue: (r, c) => values[r]?.[c],
62
+ * }); // [{ rowIndex: 2, column: 0, value: 3 }, { rowIndex: 3, column: 0, value: 4 }]
63
+ *
64
+ * @category Fill handle & batch edit
65
+ * @since 1.1.96
66
+ */
67
+ export declare function computeFillChanges(request: FillRequestIf): CellValueChangeIf[];
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The direction of a fill: the fill handle was dragged down, up, right or left of the selected range.
3
+ *
4
+ * @category Fill handle & batch edit
5
+ * @since 1.1.96
6
+ */
7
+ export type FillDirection = "down" | "up" | "right" | "left";
@@ -0,0 +1,29 @@
1
+ import { FillHandleOptionsIf } from './fill-handle-options.if.js';
2
+ import { FillValueContextIf } from './fill-value-context.if.js';
3
+
4
+ /**
5
+ * The defaults of {@link FillHandleOptionsIf}.
6
+ *
7
+ * @example
8
+ * const options = FillHandleOptions.create(true);
9
+ * options?.direction; // "both"
10
+ *
11
+ * @category Fill handle & batch edit
12
+ * @since 1.1.96
13
+ */
14
+ export declare class FillHandleOptions implements FillHandleOptionsIf {
15
+ direction: "vertical" | "horizontal" | "both";
16
+ fillValue?: (ctx: FillValueContextIf) => unknown;
17
+ maxCells: number;
18
+ dragThresholdInPx: number;
19
+ autoScrollZoneInPx: number;
20
+ autoScrollMaxSpeedInPx: number;
21
+ /**
22
+ * The options of {@link TableOptionsIf.fillHandle}: `undefined`/`false` = no fill handle, `true` = the defaults, an
23
+ * object = the defaults overridden by its values (`undefined` keeps the default).
24
+ *
25
+ * @param {boolean | Partial<FillHandleOptionsIf>} [option] - `tableOptions.fillHandle`
26
+ * @returns {FillHandleOptionsIf | undefined} complete options, `undefined` if the fill handle is off
27
+ */
28
+ static create(option?: boolean | Partial<FillHandleOptionsIf>): FillHandleOptionsIf | undefined;
29
+ }
@@ -0,0 +1,35 @@
1
+ import { FillValueContextIf } from './fill-value-context.if.js';
2
+
3
+ /**
4
+ * Options of the fill handle ({@link TableOptionsIf.fillHandle}): the small square at the bottom right corner of the
5
+ * selected range. Dragging it fills the neighbouring cells – copies values or continues series (1, 2, 3 …; dates;
6
+ * `Item 1` → `Item 2`). Needs a {@link SelectionModel} of the type `"range"`.
7
+ *
8
+ * @example
9
+ * const selectionModel = new SelectionModel("range", "multi"); // one model – not a new one per call
10
+ * const tableOptions: TableOptionsIf = {
11
+ * ...new TableOptions(),
12
+ * getSelectionModel: () => selectionModel,
13
+ * fillHandle: { direction: "vertical" },
14
+ * };
15
+ *
16
+ * @category Fill handle & batch edit
17
+ * @since 1.1.96
18
+ */
19
+ export interface FillHandleOptionsIf {
20
+ /** Allowed directions: `"vertical"` (down/up), `"horizontal"` (right/left) or `"both"` (default). */
21
+ direction: "vertical" | "horizontal" | "both";
22
+ /**
23
+ * Own fill function, called for every filled cell. Return the value – or `undefined` for the default
24
+ * (`ctx.defaultValue`).
25
+ */
26
+ fillValue?: (ctx: FillValueContextIf) => unknown;
27
+ /** At most this many cells are filled at once (default 100,000); a bigger fill is refused and announced. */
28
+ maxCells: number;
29
+ /** Distance in px the pointer has to move before a fill starts (default 3). */
30
+ dragThresholdInPx: number;
31
+ /** Width in px of the zone at the edges of the body that scrolls automatically (default 40). */
32
+ autoScrollZoneInPx: number;
33
+ /** Maximum auto-scroll speed in px per animation frame (default 24). */
34
+ autoScrollMaxSpeedInPx: number;
35
+ }
@@ -0,0 +1,55 @@
1
+ import { TableScope } from '../table-scope.js';
2
+
3
+ /**
4
+ * The fill handle of one table (`tableScope.fillHandleService`): the square at the bottom right corner of the selected
5
+ * range, the pointer drag with preview and auto-scroll, Escape. The values come from {@link TableScope.fillRange}.
6
+ * Use {@link TableApi.fillRange}, {@link TableApi.isFilling} and {@link TableApi.cancelFill} instead.
7
+ *
8
+ * Pointer Events (mouse, touch, pen) only on the handle; its `pointerdown` is prevented, so the mouse handler of the
9
+ * table (range selection, column drag) never sees it.
10
+ *
11
+ * @category Internals
12
+ * @since 1.1.96
13
+ */
14
+ export declare class FillHandleService {
15
+ private readonly tableScope;
16
+ private handle?;
17
+ private preview?;
18
+ private pending?;
19
+ private session?;
20
+ private sessionAbortController?;
21
+ private autoScrollFrame;
22
+ private suppressClick;
23
+ private shortcutsSet;
24
+ constructor(tableScope: TableScope);
25
+ /** true while the handle is dragged. */
26
+ isFilling(): boolean;
27
+ /**
28
+ * Places the handle at the bottom right corner of the last selected range (called after every render pass) – or
29
+ * hides it: no fill handle option, no range, the corner scrolled out, an open editor, a blocking overlay.
30
+ */
31
+ update(): void;
32
+ /** The handle's position (the corner of the range) in host coordinates – `undefined` if it is not visible. */
33
+ private getCornerOf;
34
+ private ensureHandle;
35
+ private removeHandle;
36
+ /** Keyboard alternative on the grid: Ctrl/⌘+D, Ctrl/⌘+R (only directions the options allow). */
37
+ private updateHostShortcuts;
38
+ private isHandle;
39
+ private onPointerDown;
40
+ private listenToDocument;
41
+ private onPointerMove;
42
+ private onPointerUp;
43
+ private suppressNextClick;
44
+ private onClickCapture;
45
+ /** Target cell under the pointer → direction and count; preview; auto-scroll. */
46
+ private updateTarget;
47
+ private renderPreview;
48
+ private updateAutoScroll;
49
+ private autoScrollStep;
50
+ /** Cancels a running fill (Escape, `pointercancel`, {@link TableApi.cancelFill}): nothing is changed. */
51
+ cancel(): void;
52
+ private stopListening;
53
+ /** Releases everything (TableScope.destroy). */
54
+ destroy(): void;
55
+ }