@mozaic-ds/angular 2.0.41 → 2.0.42
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.
|
@@ -4897,6 +4897,7 @@ class GridStateManager {
|
|
|
4897
4897
|
scrollLeft = signal(0, ...(ngDevMode ? [{ debugName: "scrollLeft" }] : /* istanbul ignore next */ []));
|
|
4898
4898
|
scrollTop = signal(0, ...(ngDevMode ? [{ debugName: "scrollTop" }] : /* istanbul ignore next */ []));
|
|
4899
4899
|
scrollViewportWidth = signal(0, ...(ngDevMode ? [{ debugName: "scrollViewportWidth" }] : /* istanbul ignore next */ []));
|
|
4900
|
+
scrollViewportHeight = signal(0, ...(ngDevMode ? [{ debugName: "scrollViewportHeight" }] : /* istanbul ignore next */ []));
|
|
4900
4901
|
scrollContentTotalWidth = signal(0, ...(ngDevMode ? [{ debugName: "scrollContentTotalWidth" }] : /* istanbul ignore next */ []));
|
|
4901
4902
|
// --- Horizontal virtual scroll ---
|
|
4902
4903
|
horizontalVirtualScrollEnabled = signal(false, ...(ngDevMode ? [{ debugName: "horizontalVirtualScrollEnabled" }] : /* istanbul ignore next */ []));
|
|
@@ -4919,6 +4920,8 @@ class GridStateManager {
|
|
|
4919
4920
|
isFilling = signal(false, ...(ngDevMode ? [{ debugName: "isFilling" }] : /* istanbul ignore next */ []));
|
|
4920
4921
|
fillAnchor = signal(null, ...(ngDevMode ? [{ debugName: "fillAnchor" }] : /* istanbul ignore next */ []));
|
|
4921
4922
|
fillTarget = signal(null, ...(ngDevMode ? [{ debugName: "fillTarget" }] : /* istanbul ignore next */ []));
|
|
4923
|
+
// --- Cut (Ctrl+X) source — drives the marching-ants outline in view ---
|
|
4924
|
+
cutSource = signal(null, ...(ngDevMode ? [{ debugName: "cutSource" }] : /* istanbul ignore next */ []));
|
|
4922
4925
|
// --- Expandable Rows ---
|
|
4923
4926
|
expandedRowIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedRowIds" }] : /* istanbul ignore next */ []));
|
|
4924
4927
|
rowIdField = signal('id', ...(ngDevMode ? [{ debugName: "rowIdField" }] : /* istanbul ignore next */ []));
|
|
@@ -5524,8 +5527,485 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
5524
5527
|
type: Injectable
|
|
5525
5528
|
}] });
|
|
5526
5529
|
|
|
5530
|
+
const PASTE_SKIP$1 = Symbol('PASTE_SKIP');
|
|
5531
|
+
/**
|
|
5532
|
+
* Applies a set of cell-level mutations to sourceData and returns the list of
|
|
5533
|
+
* actual changes that occurred, so the caller (usually the history engine) can
|
|
5534
|
+
* record them. `PASTE_SKIP` return values are filtered out transparently.
|
|
5535
|
+
*/
|
|
5536
|
+
class ClipboardEngine {
|
|
5537
|
+
state = inject(GridStateManager);
|
|
5538
|
+
/** Derived by components (marching-ants outline). */
|
|
5539
|
+
cutRange = computed(() => this.state.cutSource(), ...(ngDevMode ? [{ debugName: "cutRange" }] : /* istanbul ignore next */ []));
|
|
5540
|
+
markCut(range) {
|
|
5541
|
+
this.state.cutSource.set(range);
|
|
5542
|
+
}
|
|
5543
|
+
clearCut() {
|
|
5544
|
+
this.state.cutSource.set(null);
|
|
5545
|
+
}
|
|
5546
|
+
/** Vertical fill: row 0 of the range is the source, subsequent rows are targets. */
|
|
5547
|
+
fillDown(range) {
|
|
5548
|
+
if (range.start.row === range.end.row)
|
|
5549
|
+
return [];
|
|
5550
|
+
const cols = this.state.visibleColumns();
|
|
5551
|
+
const defMap = this.state.columnDefMap();
|
|
5552
|
+
const changes = [];
|
|
5553
|
+
this.state.sourceData.update((data) => {
|
|
5554
|
+
const updated = [...data];
|
|
5555
|
+
const sourceRow = updated[range.start.row];
|
|
5556
|
+
if (!sourceRow)
|
|
5557
|
+
return updated;
|
|
5558
|
+
for (let r = range.start.row + 1; r <= range.end.row; r++) {
|
|
5559
|
+
if (!updated[r])
|
|
5560
|
+
continue;
|
|
5561
|
+
const rowCopy = { ...updated[r] };
|
|
5562
|
+
let changed = false;
|
|
5563
|
+
for (let c = range.start.col; c <= range.end.col; c++) {
|
|
5564
|
+
const field = cols[c]?.field;
|
|
5565
|
+
if (!field)
|
|
5566
|
+
continue;
|
|
5567
|
+
const def = defMap.get(field);
|
|
5568
|
+
if (!def?.editable)
|
|
5569
|
+
continue;
|
|
5570
|
+
const sourceValue = def.valueGetter
|
|
5571
|
+
? def.valueGetter(sourceRow)
|
|
5572
|
+
: sourceRow[field];
|
|
5573
|
+
const coerced = this.coerceAndValidate(field, sourceValue, updated[r]);
|
|
5574
|
+
if (coerced === PASTE_SKIP$1)
|
|
5575
|
+
continue;
|
|
5576
|
+
const before = updated[r][field];
|
|
5577
|
+
if (before === coerced)
|
|
5578
|
+
continue;
|
|
5579
|
+
rowCopy[field] = coerced;
|
|
5580
|
+
changes.push({ rowIndex: r, field, before, after: coerced });
|
|
5581
|
+
changed = true;
|
|
5582
|
+
}
|
|
5583
|
+
if (changed)
|
|
5584
|
+
updated[r] = rowCopy;
|
|
5585
|
+
}
|
|
5586
|
+
return updated;
|
|
5587
|
+
});
|
|
5588
|
+
return changes;
|
|
5589
|
+
}
|
|
5590
|
+
/** Horizontal fill: col 0 of the range is the source, subsequent cols are targets. */
|
|
5591
|
+
fillRight(range) {
|
|
5592
|
+
if (range.start.col === range.end.col)
|
|
5593
|
+
return [];
|
|
5594
|
+
const cols = this.state.visibleColumns();
|
|
5595
|
+
const defMap = this.state.columnDefMap();
|
|
5596
|
+
const sourceField = cols[range.start.col]?.field;
|
|
5597
|
+
if (!sourceField)
|
|
5598
|
+
return [];
|
|
5599
|
+
const sourceDef = defMap.get(sourceField);
|
|
5600
|
+
if (!sourceDef)
|
|
5601
|
+
return [];
|
|
5602
|
+
const changes = [];
|
|
5603
|
+
this.state.sourceData.update((data) => {
|
|
5604
|
+
const updated = [...data];
|
|
5605
|
+
for (let r = range.start.row; r <= range.end.row; r++) {
|
|
5606
|
+
const row = updated[r];
|
|
5607
|
+
if (!row)
|
|
5608
|
+
continue;
|
|
5609
|
+
const sourceValue = sourceDef.valueGetter
|
|
5610
|
+
? sourceDef.valueGetter(row)
|
|
5611
|
+
: row[sourceField];
|
|
5612
|
+
const rowCopy = { ...row };
|
|
5613
|
+
let changed = false;
|
|
5614
|
+
for (let c = range.start.col + 1; c <= range.end.col; c++) {
|
|
5615
|
+
const field = cols[c]?.field;
|
|
5616
|
+
if (!field)
|
|
5617
|
+
continue;
|
|
5618
|
+
const def = defMap.get(field);
|
|
5619
|
+
if (!def?.editable)
|
|
5620
|
+
continue;
|
|
5621
|
+
const coerced = this.coerceAndValidate(field, sourceValue, row);
|
|
5622
|
+
if (coerced === PASTE_SKIP$1)
|
|
5623
|
+
continue;
|
|
5624
|
+
const before = row[field];
|
|
5625
|
+
if (before === coerced)
|
|
5626
|
+
continue;
|
|
5627
|
+
rowCopy[field] = coerced;
|
|
5628
|
+
changes.push({ rowIndex: r, field, before, after: coerced });
|
|
5629
|
+
changed = true;
|
|
5630
|
+
}
|
|
5631
|
+
if (changed)
|
|
5632
|
+
updated[r] = rowCopy;
|
|
5633
|
+
}
|
|
5634
|
+
return updated;
|
|
5635
|
+
});
|
|
5636
|
+
return changes;
|
|
5637
|
+
}
|
|
5638
|
+
/** Ctrl+Enter: write `value` into every editable cell of `range`. */
|
|
5639
|
+
fillSelection(range, value) {
|
|
5640
|
+
const cols = this.state.visibleColumns();
|
|
5641
|
+
const defMap = this.state.columnDefMap();
|
|
5642
|
+
const changes = [];
|
|
5643
|
+
this.state.sourceData.update((data) => {
|
|
5644
|
+
const updated = [...data];
|
|
5645
|
+
for (let r = range.start.row; r <= range.end.row; r++) {
|
|
5646
|
+
const row = updated[r];
|
|
5647
|
+
if (!row)
|
|
5648
|
+
continue;
|
|
5649
|
+
const rowCopy = { ...row };
|
|
5650
|
+
let changed = false;
|
|
5651
|
+
for (let c = range.start.col; c <= range.end.col; c++) {
|
|
5652
|
+
const field = cols[c]?.field;
|
|
5653
|
+
if (!field)
|
|
5654
|
+
continue;
|
|
5655
|
+
const def = defMap.get(field);
|
|
5656
|
+
if (!def?.editable)
|
|
5657
|
+
continue;
|
|
5658
|
+
const coerced = this.coerceAndValidate(field, value, row);
|
|
5659
|
+
if (coerced === PASTE_SKIP$1)
|
|
5660
|
+
continue;
|
|
5661
|
+
const before = row[field];
|
|
5662
|
+
if (before === coerced)
|
|
5663
|
+
continue;
|
|
5664
|
+
rowCopy[field] = coerced;
|
|
5665
|
+
changes.push({ rowIndex: r, field, before, after: coerced });
|
|
5666
|
+
changed = true;
|
|
5667
|
+
}
|
|
5668
|
+
if (changed)
|
|
5669
|
+
updated[r] = rowCopy;
|
|
5670
|
+
}
|
|
5671
|
+
return updated;
|
|
5672
|
+
});
|
|
5673
|
+
return changes;
|
|
5674
|
+
}
|
|
5675
|
+
/** Clears every editable cell in `range` and returns the undo payload. */
|
|
5676
|
+
clearRange(range) {
|
|
5677
|
+
const cols = this.state.visibleColumns();
|
|
5678
|
+
const defMap = this.state.columnDefMap();
|
|
5679
|
+
const changes = [];
|
|
5680
|
+
this.state.sourceData.update((data) => {
|
|
5681
|
+
const updated = [...data];
|
|
5682
|
+
for (let r = range.start.row; r <= range.end.row; r++) {
|
|
5683
|
+
const row = updated[r];
|
|
5684
|
+
if (!row)
|
|
5685
|
+
continue;
|
|
5686
|
+
const rowCopy = { ...row };
|
|
5687
|
+
let changed = false;
|
|
5688
|
+
for (let c = range.start.col; c <= range.end.col; c++) {
|
|
5689
|
+
const field = cols[c]?.field;
|
|
5690
|
+
if (!field)
|
|
5691
|
+
continue;
|
|
5692
|
+
const def = defMap.get(field);
|
|
5693
|
+
if (!def?.editable)
|
|
5694
|
+
continue;
|
|
5695
|
+
const coerced = this.coerceAndValidate(field, null, row);
|
|
5696
|
+
if (coerced === PASTE_SKIP$1)
|
|
5697
|
+
continue;
|
|
5698
|
+
const before = row[field];
|
|
5699
|
+
if (before === coerced)
|
|
5700
|
+
continue;
|
|
5701
|
+
rowCopy[field] = coerced;
|
|
5702
|
+
changes.push({ rowIndex: r, field, before, after: coerced });
|
|
5703
|
+
changed = true;
|
|
5704
|
+
}
|
|
5705
|
+
if (changed)
|
|
5706
|
+
updated[r] = rowCopy;
|
|
5707
|
+
}
|
|
5708
|
+
return updated;
|
|
5709
|
+
});
|
|
5710
|
+
return changes;
|
|
5711
|
+
}
|
|
5712
|
+
/** Applies TSV `pasteRows` starting at `range.start`, returning actual changes. */
|
|
5713
|
+
applyPaste(range, pasteRows) {
|
|
5714
|
+
const cols = this.state.visibleColumns();
|
|
5715
|
+
const changes = [];
|
|
5716
|
+
this.state.sourceData.update((data) => {
|
|
5717
|
+
const updated = [...data];
|
|
5718
|
+
for (let ri = 0; ri < pasteRows.length; ri++) {
|
|
5719
|
+
const targetRow = range.start.row + ri;
|
|
5720
|
+
if (targetRow >= updated.length)
|
|
5721
|
+
break;
|
|
5722
|
+
const row = updated[targetRow];
|
|
5723
|
+
if (!row)
|
|
5724
|
+
continue;
|
|
5725
|
+
const rowCopy = { ...row };
|
|
5726
|
+
let changed = false;
|
|
5727
|
+
for (let ci = 0; ci < pasteRows[ri].length; ci++) {
|
|
5728
|
+
const targetCol = range.start.col + ci;
|
|
5729
|
+
if (targetCol >= cols.length)
|
|
5730
|
+
break;
|
|
5731
|
+
const field = cols[targetCol]?.field;
|
|
5732
|
+
if (!field)
|
|
5733
|
+
continue;
|
|
5734
|
+
const coerced = this.coerceAndValidate(field, pasteRows[ri][ci], row);
|
|
5735
|
+
if (coerced === PASTE_SKIP$1)
|
|
5736
|
+
continue;
|
|
5737
|
+
const before = row[field];
|
|
5738
|
+
if (before === coerced)
|
|
5739
|
+
continue;
|
|
5740
|
+
rowCopy[field] = coerced;
|
|
5741
|
+
changes.push({ rowIndex: targetRow, field, before, after: coerced });
|
|
5742
|
+
changed = true;
|
|
5743
|
+
}
|
|
5744
|
+
if (changed)
|
|
5745
|
+
updated[targetRow] = rowCopy;
|
|
5746
|
+
}
|
|
5747
|
+
return updated;
|
|
5748
|
+
});
|
|
5749
|
+
return changes;
|
|
5750
|
+
}
|
|
5751
|
+
/**
|
|
5752
|
+
* Reverses a previously-recorded list of changes by writing `before` back into
|
|
5753
|
+
* the cells. Used by the history engine for both undo and redo.
|
|
5754
|
+
*/
|
|
5755
|
+
applyChanges(changes, direction) {
|
|
5756
|
+
if (changes.length === 0)
|
|
5757
|
+
return;
|
|
5758
|
+
this.state.sourceData.update((data) => {
|
|
5759
|
+
const updated = [...data];
|
|
5760
|
+
// Group changes by rowIndex so each row is cloned once.
|
|
5761
|
+
const byRow = new Map();
|
|
5762
|
+
for (const change of changes) {
|
|
5763
|
+
const list = byRow.get(change.rowIndex) ?? [];
|
|
5764
|
+
list.push(change);
|
|
5765
|
+
byRow.set(change.rowIndex, list);
|
|
5766
|
+
}
|
|
5767
|
+
for (const [rowIndex, rowChanges] of byRow) {
|
|
5768
|
+
const row = updated[rowIndex];
|
|
5769
|
+
if (!row)
|
|
5770
|
+
continue;
|
|
5771
|
+
const rowCopy = { ...row };
|
|
5772
|
+
for (const change of rowChanges) {
|
|
5773
|
+
rowCopy[change.field] = direction === 'before' ? change.before : change.after;
|
|
5774
|
+
}
|
|
5775
|
+
updated[rowIndex] = rowCopy;
|
|
5776
|
+
}
|
|
5777
|
+
return updated;
|
|
5778
|
+
});
|
|
5779
|
+
}
|
|
5780
|
+
/**
|
|
5781
|
+
* Coerces a raw value (string from TSV, unknown from fill, null for clears)
|
|
5782
|
+
* into the editor's expected type, running the field's validator when present.
|
|
5783
|
+
* Returns PASTE_SKIP when the column isn't editable or the value is rejected.
|
|
5784
|
+
*/
|
|
5785
|
+
coerceAndValidate(field, rawValue, row) {
|
|
5786
|
+
const def = this.state.columnDefMap().get(field);
|
|
5787
|
+
if (!def?.editable)
|
|
5788
|
+
return PASTE_SKIP$1;
|
|
5789
|
+
const editorType = def.cellEditor;
|
|
5790
|
+
if (rawValue === null) {
|
|
5791
|
+
let clearValue;
|
|
5792
|
+
switch (editorType) {
|
|
5793
|
+
case 'number':
|
|
5794
|
+
clearValue = null;
|
|
5795
|
+
break;
|
|
5796
|
+
case 'checkbox':
|
|
5797
|
+
clearValue = false;
|
|
5798
|
+
break;
|
|
5799
|
+
default:
|
|
5800
|
+
clearValue = '';
|
|
5801
|
+
}
|
|
5802
|
+
if (def.cellEditorValidator) {
|
|
5803
|
+
const result = def.cellEditorValidator(clearValue, row);
|
|
5804
|
+
if (result === false || typeof result === 'string')
|
|
5805
|
+
return PASTE_SKIP$1;
|
|
5806
|
+
}
|
|
5807
|
+
return clearValue;
|
|
5808
|
+
}
|
|
5809
|
+
let value = rawValue;
|
|
5810
|
+
if (editorType === 'number') {
|
|
5811
|
+
const num = Number(rawValue);
|
|
5812
|
+
if (isNaN(num))
|
|
5813
|
+
return PASTE_SKIP$1;
|
|
5814
|
+
value = num;
|
|
5815
|
+
}
|
|
5816
|
+
else if (editorType === 'checkbox') {
|
|
5817
|
+
if (rawValue === 'true' || rawValue === true) {
|
|
5818
|
+
value = true;
|
|
5819
|
+
}
|
|
5820
|
+
else if (rawValue === 'false' || rawValue === false) {
|
|
5821
|
+
value = false;
|
|
5822
|
+
}
|
|
5823
|
+
else {
|
|
5824
|
+
return PASTE_SKIP$1;
|
|
5825
|
+
}
|
|
5826
|
+
}
|
|
5827
|
+
else if (editorType === 'select' && def.cellEditorOptions?.length) {
|
|
5828
|
+
const allowed = def.cellEditorOptions.map((o) => String(o.value));
|
|
5829
|
+
if (!allowed.includes(String(rawValue)))
|
|
5830
|
+
return PASTE_SKIP$1;
|
|
5831
|
+
value = rawValue;
|
|
5832
|
+
}
|
|
5833
|
+
if (def.cellEditorValidator) {
|
|
5834
|
+
const result = def.cellEditorValidator(value, row);
|
|
5835
|
+
if (result === false || typeof result === 'string')
|
|
5836
|
+
return PASTE_SKIP$1;
|
|
5837
|
+
}
|
|
5838
|
+
return value;
|
|
5839
|
+
}
|
|
5840
|
+
/** TSV string for a range — used by copy / cut. */
|
|
5841
|
+
extractTsv(range) {
|
|
5842
|
+
const cols = this.state.visibleColumns();
|
|
5843
|
+
const data = this.state.sourceData();
|
|
5844
|
+
const defMap = this.state.columnDefMap();
|
|
5845
|
+
const values = [];
|
|
5846
|
+
for (let r = range.start.row; r <= range.end.row; r++) {
|
|
5847
|
+
const row = data[r];
|
|
5848
|
+
if (!row)
|
|
5849
|
+
continue;
|
|
5850
|
+
const rowValues = [];
|
|
5851
|
+
for (let c = range.start.col; c <= range.end.col; c++) {
|
|
5852
|
+
const field = cols[c]?.field;
|
|
5853
|
+
if (!field) {
|
|
5854
|
+
rowValues.push('');
|
|
5855
|
+
continue;
|
|
5856
|
+
}
|
|
5857
|
+
const def = defMap.get(field);
|
|
5858
|
+
const val = def?.valueGetter
|
|
5859
|
+
? def.valueGetter(row)
|
|
5860
|
+
: row[field];
|
|
5861
|
+
rowValues.push(val != null ? String(val) : '');
|
|
5862
|
+
}
|
|
5863
|
+
values.push(rowValues);
|
|
5864
|
+
}
|
|
5865
|
+
return values;
|
|
5866
|
+
}
|
|
5867
|
+
/**
|
|
5868
|
+
* Checks whether a cell sits on any edge of the current cut outline. Four
|
|
5869
|
+
* booleans rather than a single "isCut" so the view can paint only the edges
|
|
5870
|
+
* that face outward — which is what produces the Excel-like marching ants.
|
|
5871
|
+
*/
|
|
5872
|
+
cutEdges(row, col) {
|
|
5873
|
+
const cut = this.state.cutSource();
|
|
5874
|
+
if (!cut)
|
|
5875
|
+
return { top: false, right: false, bottom: false, left: false, any: false };
|
|
5876
|
+
const minRow = Math.min(cut.start.row, cut.end.row);
|
|
5877
|
+
const maxRow = Math.max(cut.start.row, cut.end.row);
|
|
5878
|
+
const minCol = Math.min(cut.start.col, cut.end.col);
|
|
5879
|
+
const maxCol = Math.max(cut.start.col, cut.end.col);
|
|
5880
|
+
if (row < minRow || row > maxRow || col < minCol || col > maxCol) {
|
|
5881
|
+
return { top: false, right: false, bottom: false, left: false, any: false };
|
|
5882
|
+
}
|
|
5883
|
+
return {
|
|
5884
|
+
top: row === minRow,
|
|
5885
|
+
right: col === maxCol,
|
|
5886
|
+
bottom: row === maxRow,
|
|
5887
|
+
left: col === minCol,
|
|
5888
|
+
any: true,
|
|
5889
|
+
};
|
|
5890
|
+
}
|
|
5891
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ClipboardEngine, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
5892
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ClipboardEngine });
|
|
5893
|
+
}
|
|
5894
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ClipboardEngine, decorators: [{
|
|
5895
|
+
type: Injectable
|
|
5896
|
+
}] });
|
|
5897
|
+
|
|
5898
|
+
const MAX_HISTORY = 50;
|
|
5899
|
+
const STORAGE_PREFIX = 'moz-grid-history:';
|
|
5900
|
+
class HistoryEngine {
|
|
5901
|
+
clipboard = inject(ClipboardEngine);
|
|
5902
|
+
past = signal([], ...(ngDevMode ? [{ debugName: "past" }] : /* istanbul ignore next */ []));
|
|
5903
|
+
future = signal([], ...(ngDevMode ? [{ debugName: "future" }] : /* istanbul ignore next */ []));
|
|
5904
|
+
storageKey = null;
|
|
5905
|
+
canUndo = computed(() => this.past().length > 0, ...(ngDevMode ? [{ debugName: "canUndo" }] : /* istanbul ignore next */ []));
|
|
5906
|
+
canRedo = computed(() => this.future().length > 0, ...(ngDevMode ? [{ debugName: "canRedo" }] : /* istanbul ignore next */ []));
|
|
5907
|
+
/**
|
|
5908
|
+
* Binds a persistence key: all record/undo/redo calls will mirror the stacks
|
|
5909
|
+
* to localStorage, and past state is restored on bind. Pass null to detach.
|
|
5910
|
+
*/
|
|
5911
|
+
attach(gridId) {
|
|
5912
|
+
this.storageKey = gridId ? `${STORAGE_PREFIX}${gridId}` : null;
|
|
5913
|
+
if (!this.storageKey) {
|
|
5914
|
+
this.past.set([]);
|
|
5915
|
+
this.future.set([]);
|
|
5916
|
+
return;
|
|
5917
|
+
}
|
|
5918
|
+
this.restore();
|
|
5919
|
+
}
|
|
5920
|
+
/** Records a new mutation. Clears the redo stack (standard undo semantics). */
|
|
5921
|
+
record(type, changes) {
|
|
5922
|
+
if (changes.length === 0)
|
|
5923
|
+
return;
|
|
5924
|
+
const op = { type, changes, timestamp: Date.now() };
|
|
5925
|
+
this.past.update((stack) => {
|
|
5926
|
+
const next = [...stack, op];
|
|
5927
|
+
return next.length > MAX_HISTORY ? next.slice(next.length - MAX_HISTORY) : next;
|
|
5928
|
+
});
|
|
5929
|
+
this.future.set([]);
|
|
5930
|
+
this.persist();
|
|
5931
|
+
}
|
|
5932
|
+
undo() {
|
|
5933
|
+
const stack = this.past();
|
|
5934
|
+
if (stack.length === 0)
|
|
5935
|
+
return null;
|
|
5936
|
+
const op = stack[stack.length - 1];
|
|
5937
|
+
this.clipboard.applyChanges(op.changes, 'before');
|
|
5938
|
+
this.past.set(stack.slice(0, -1));
|
|
5939
|
+
this.future.update((f) => [...f, op]);
|
|
5940
|
+
this.persist();
|
|
5941
|
+
return op;
|
|
5942
|
+
}
|
|
5943
|
+
redo() {
|
|
5944
|
+
const stack = this.future();
|
|
5945
|
+
if (stack.length === 0)
|
|
5946
|
+
return null;
|
|
5947
|
+
const op = stack[stack.length - 1];
|
|
5948
|
+
this.clipboard.applyChanges(op.changes, 'after');
|
|
5949
|
+
this.future.set(stack.slice(0, -1));
|
|
5950
|
+
this.past.update((p) => [...p, op]);
|
|
5951
|
+
this.persist();
|
|
5952
|
+
return op;
|
|
5953
|
+
}
|
|
5954
|
+
clear() {
|
|
5955
|
+
this.past.set([]);
|
|
5956
|
+
this.future.set([]);
|
|
5957
|
+
if (this.storageKey) {
|
|
5958
|
+
try {
|
|
5959
|
+
localStorage.removeItem(this.storageKey);
|
|
5960
|
+
}
|
|
5961
|
+
catch {
|
|
5962
|
+
// Storage unavailable (private mode, quota) — non-fatal.
|
|
5963
|
+
}
|
|
5964
|
+
}
|
|
5965
|
+
}
|
|
5966
|
+
persist() {
|
|
5967
|
+
if (!this.storageKey)
|
|
5968
|
+
return;
|
|
5969
|
+
try {
|
|
5970
|
+
const payload = JSON.stringify({
|
|
5971
|
+
past: this.past(),
|
|
5972
|
+
future: this.future(),
|
|
5973
|
+
});
|
|
5974
|
+
localStorage.setItem(this.storageKey, payload);
|
|
5975
|
+
}
|
|
5976
|
+
catch {
|
|
5977
|
+
// Quota exceeded or storage disabled — we silently drop persistence.
|
|
5978
|
+
}
|
|
5979
|
+
}
|
|
5980
|
+
restore() {
|
|
5981
|
+
if (!this.storageKey)
|
|
5982
|
+
return;
|
|
5983
|
+
try {
|
|
5984
|
+
const raw = localStorage.getItem(this.storageKey);
|
|
5985
|
+
if (!raw) {
|
|
5986
|
+
this.past.set([]);
|
|
5987
|
+
this.future.set([]);
|
|
5988
|
+
return;
|
|
5989
|
+
}
|
|
5990
|
+
const parsed = JSON.parse(raw);
|
|
5991
|
+
this.past.set(Array.isArray(parsed.past) ? parsed.past : []);
|
|
5992
|
+
this.future.set(Array.isArray(parsed.future) ? parsed.future : []);
|
|
5993
|
+
}
|
|
5994
|
+
catch {
|
|
5995
|
+
this.past.set([]);
|
|
5996
|
+
this.future.set([]);
|
|
5997
|
+
}
|
|
5998
|
+
}
|
|
5999
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: HistoryEngine, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
6000
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: HistoryEngine });
|
|
6001
|
+
}
|
|
6002
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: HistoryEngine, decorators: [{
|
|
6003
|
+
type: Injectable
|
|
6004
|
+
}] });
|
|
6005
|
+
|
|
5527
6006
|
class InlineEditEngine {
|
|
5528
6007
|
state = inject(GridStateManager);
|
|
6008
|
+
history = inject(HistoryEngine);
|
|
5529
6009
|
startEdit(rowIndex, field) {
|
|
5530
6010
|
const defMap = this.state.columnDefMap();
|
|
5531
6011
|
const def = defMap.get(field);
|
|
@@ -5545,6 +6025,54 @@ class InlineEditEngine {
|
|
|
5545
6025
|
validationError: null,
|
|
5546
6026
|
});
|
|
5547
6027
|
}
|
|
6028
|
+
/**
|
|
6029
|
+
* Excel-style "typing-to-edit": starts the editor with the cell value replaced
|
|
6030
|
+
* by the character the user just typed. For non-text editors (select / date /
|
|
6031
|
+
* checkbox / number) we coerce: the character is kept if it's compatible with
|
|
6032
|
+
* the editor, otherwise the editor opens on a cleared value.
|
|
6033
|
+
*/
|
|
6034
|
+
startEditWithChar(rowIndex, field, char) {
|
|
6035
|
+
const defMap = this.state.columnDefMap();
|
|
6036
|
+
const def = defMap.get(field);
|
|
6037
|
+
if (!def?.editable)
|
|
6038
|
+
return;
|
|
6039
|
+
const colIndex = this.state.visibleColumns().findIndex((c) => c.field === field);
|
|
6040
|
+
if (colIndex < 0)
|
|
6041
|
+
return;
|
|
6042
|
+
const row = this.state.sourceData()[rowIndex];
|
|
6043
|
+
if (!row)
|
|
6044
|
+
return;
|
|
6045
|
+
const currentValue = def.valueGetter
|
|
6046
|
+
? def.valueGetter(row)
|
|
6047
|
+
: row[field];
|
|
6048
|
+
const editorType = def.cellEditor ?? this.resolveEditorType(field, currentValue);
|
|
6049
|
+
let draftValue = char;
|
|
6050
|
+
switch (editorType) {
|
|
6051
|
+
case 'number': {
|
|
6052
|
+
const n = Number(char);
|
|
6053
|
+
draftValue = Number.isNaN(n) ? '' : n;
|
|
6054
|
+
break;
|
|
6055
|
+
}
|
|
6056
|
+
case 'checkbox':
|
|
6057
|
+
// A character press toggles the checkbox to true — closest Excel-equivalent
|
|
6058
|
+
// (Excel doesn't have checkbox cells but booleans flip on typing).
|
|
6059
|
+
draftValue = true;
|
|
6060
|
+
break;
|
|
6061
|
+
case 'select':
|
|
6062
|
+
case 'date':
|
|
6063
|
+
// These editors have picker UIs; typing just opens them with an empty draft.
|
|
6064
|
+
draftValue = '';
|
|
6065
|
+
break;
|
|
6066
|
+
default:
|
|
6067
|
+
draftValue = char;
|
|
6068
|
+
}
|
|
6069
|
+
this.state.cellEditState.set({
|
|
6070
|
+
editingCell: { row: rowIndex, col: colIndex },
|
|
6071
|
+
originalValue: currentValue,
|
|
6072
|
+
draftValue,
|
|
6073
|
+
validationError: null,
|
|
6074
|
+
});
|
|
6075
|
+
}
|
|
5548
6076
|
updateDraft(value) {
|
|
5549
6077
|
this.state.cellEditState.update((s) => ({ ...s, draftValue: value }));
|
|
5550
6078
|
}
|
|
@@ -5596,6 +6124,11 @@ class InlineEditEngine {
|
|
|
5596
6124
|
oldValue: editState.originalValue,
|
|
5597
6125
|
newValue: editState.draftValue,
|
|
5598
6126
|
};
|
|
6127
|
+
if (this.state.mode() === 'client' && event.oldValue !== event.newValue) {
|
|
6128
|
+
this.history.record('edit', [
|
|
6129
|
+
{ rowIndex, field, before: event.oldValue, after: event.newValue },
|
|
6130
|
+
]);
|
|
6131
|
+
}
|
|
5599
6132
|
this.state.cellEditState.set({
|
|
5600
6133
|
editingCell: null,
|
|
5601
6134
|
originalValue: undefined,
|
|
@@ -5880,24 +6413,290 @@ class CellSelectionEngine {
|
|
|
5880
6413
|
if (!this.state.isDragging())
|
|
5881
6414
|
return;
|
|
5882
6415
|
const range = this.state.cellRange();
|
|
5883
|
-
if (!range)
|
|
6416
|
+
if (!range)
|
|
6417
|
+
return;
|
|
6418
|
+
this.state.cellRange.set({ start: range.start, end: { row, col } });
|
|
6419
|
+
}
|
|
6420
|
+
endRangeSelection() {
|
|
6421
|
+
this.state.isDragging.set(false);
|
|
6422
|
+
}
|
|
6423
|
+
moveUp() {
|
|
6424
|
+
this.moveBy(-1, 0);
|
|
6425
|
+
}
|
|
6426
|
+
moveDown() {
|
|
6427
|
+
this.moveBy(1, 0);
|
|
6428
|
+
}
|
|
6429
|
+
moveLeft() {
|
|
6430
|
+
this.moveBy(0, -1);
|
|
6431
|
+
}
|
|
6432
|
+
moveRight() {
|
|
6433
|
+
this.moveBy(0, 1);
|
|
6434
|
+
}
|
|
6435
|
+
// --- Home / End / Grid bounds -------------------------------------------------
|
|
6436
|
+
moveToRowStart() {
|
|
6437
|
+
const focused = this.state.focusedCell();
|
|
6438
|
+
if (!focused)
|
|
6439
|
+
return;
|
|
6440
|
+
this.focusCell(focused.row, 0, 'keyboard');
|
|
6441
|
+
}
|
|
6442
|
+
moveToRowEnd() {
|
|
6443
|
+
const focused = this.state.focusedCell();
|
|
6444
|
+
if (!focused)
|
|
6445
|
+
return;
|
|
6446
|
+
const maxCol = this.state.visibleColumns().length - 1;
|
|
6447
|
+
if (maxCol < 0)
|
|
6448
|
+
return;
|
|
6449
|
+
this.focusCell(focused.row, this.findLastNonEmptyCol(focused.row, maxCol), 'keyboard');
|
|
6450
|
+
}
|
|
6451
|
+
moveToGridStart() {
|
|
6452
|
+
const pageStart = this.state.pageIndex() * this.state.pageSize();
|
|
6453
|
+
this.focusCell(pageStart, 0, 'keyboard');
|
|
6454
|
+
}
|
|
6455
|
+
moveToGridEnd() {
|
|
6456
|
+
const pageStart = this.state.pageIndex() * this.state.pageSize();
|
|
6457
|
+
const pageEnd = pageStart + Math.max(0, this.state.visibleRowCount() - 1);
|
|
6458
|
+
const maxCol = this.state.visibleColumns().length - 1;
|
|
6459
|
+
if (maxCol < 0)
|
|
6460
|
+
return;
|
|
6461
|
+
this.focusCell(pageEnd, this.findLastNonEmptyCol(pageEnd, maxCol), 'keyboard');
|
|
6462
|
+
}
|
|
6463
|
+
/**
|
|
6464
|
+
* Excel-style Ctrl+Arrow: jump to the edge of the current data block.
|
|
6465
|
+
* If on an empty cell, jumps to the next non-empty cell. If on a filled
|
|
6466
|
+
* cell, jumps to the last filled cell before the next empty transition
|
|
6467
|
+
* (or to the grid edge if no empty cell is encountered).
|
|
6468
|
+
*/
|
|
6469
|
+
jumpToEdge(direction) {
|
|
6470
|
+
const focused = this.state.focusedCell();
|
|
6471
|
+
if (!focused)
|
|
6472
|
+
return;
|
|
6473
|
+
const { dRow, dCol } = this.directionVector(direction);
|
|
6474
|
+
const bounds = this.pageBounds();
|
|
6475
|
+
const maxCol = this.state.visibleColumns().length - 1;
|
|
6476
|
+
if (maxCol < 0)
|
|
6477
|
+
return;
|
|
6478
|
+
const startFilled = this.isCellFilled(focused.row, focused.col);
|
|
6479
|
+
let row = focused.row;
|
|
6480
|
+
let col = focused.col;
|
|
6481
|
+
// Step once to start looking at the neighbour
|
|
6482
|
+
let nextRow = row + dRow;
|
|
6483
|
+
let nextCol = col + dCol;
|
|
6484
|
+
while (this.inBounds(nextRow, nextCol, bounds, maxCol)) {
|
|
6485
|
+
const filled = this.isCellFilled(nextRow, nextCol);
|
|
6486
|
+
if (startFilled) {
|
|
6487
|
+
// Moving through filled cells — stop right before the next empty gap.
|
|
6488
|
+
if (!filled)
|
|
6489
|
+
break;
|
|
6490
|
+
row = nextRow;
|
|
6491
|
+
col = nextCol;
|
|
6492
|
+
}
|
|
6493
|
+
else {
|
|
6494
|
+
// Moving through empty cells — stop on the first filled cell we meet.
|
|
6495
|
+
if (filled) {
|
|
6496
|
+
row = nextRow;
|
|
6497
|
+
col = nextCol;
|
|
6498
|
+
break;
|
|
6499
|
+
}
|
|
6500
|
+
row = nextRow;
|
|
6501
|
+
col = nextCol;
|
|
6502
|
+
}
|
|
6503
|
+
nextRow += dRow;
|
|
6504
|
+
nextCol += dCol;
|
|
6505
|
+
}
|
|
6506
|
+
this.focusCell(row, col, 'keyboard');
|
|
6507
|
+
}
|
|
6508
|
+
movePage(direction) {
|
|
6509
|
+
const focused = this.state.focusedCell();
|
|
6510
|
+
if (!focused)
|
|
6511
|
+
return;
|
|
6512
|
+
const step = this.pageRowStep() * (direction === 'down' ? 1 : -1);
|
|
6513
|
+
this.moveBy(step, 0);
|
|
6514
|
+
}
|
|
6515
|
+
// --- Shift + navigation : extend current range --------------------------------
|
|
6516
|
+
extendRangeBy(dRow, dCol) {
|
|
6517
|
+
const focused = this.state.focusedCell();
|
|
6518
|
+
if (!focused)
|
|
6519
|
+
return;
|
|
6520
|
+
const range = this.state.cellRange();
|
|
6521
|
+
const bounds = this.pageBounds();
|
|
6522
|
+
const maxCol = this.state.visibleColumns().length - 1;
|
|
6523
|
+
if (maxCol < 0)
|
|
6524
|
+
return;
|
|
6525
|
+
const currentEnd = range ? range.end : focused;
|
|
6526
|
+
const newEnd = {
|
|
6527
|
+
row: Math.max(bounds.start, Math.min(bounds.end, currentEnd.row + dRow)),
|
|
6528
|
+
col: Math.max(0, Math.min(maxCol, currentEnd.col + dCol)),
|
|
6529
|
+
};
|
|
6530
|
+
const start = range ? range.start : focused;
|
|
6531
|
+
this.state.cellRange.set({ start, end: newEnd });
|
|
6532
|
+
}
|
|
6533
|
+
extendRangeToRowStart() {
|
|
6534
|
+
const focused = this.state.focusedCell();
|
|
6535
|
+
if (!focused)
|
|
6536
|
+
return;
|
|
6537
|
+
const range = this.state.cellRange();
|
|
6538
|
+
const start = range?.start ?? focused;
|
|
6539
|
+
this.state.cellRange.set({ start, end: { row: (range?.end ?? focused).row, col: 0 } });
|
|
6540
|
+
}
|
|
6541
|
+
extendRangeToRowEnd() {
|
|
6542
|
+
const focused = this.state.focusedCell();
|
|
6543
|
+
if (!focused)
|
|
6544
|
+
return;
|
|
6545
|
+
const maxCol = this.state.visibleColumns().length - 1;
|
|
6546
|
+
if (maxCol < 0)
|
|
6547
|
+
return;
|
|
6548
|
+
const range = this.state.cellRange();
|
|
6549
|
+
const start = range?.start ?? focused;
|
|
6550
|
+
this.state.cellRange.set({ start, end: { row: (range?.end ?? focused).row, col: maxCol } });
|
|
6551
|
+
}
|
|
6552
|
+
extendRangeToGridStart() {
|
|
6553
|
+
const focused = this.state.focusedCell();
|
|
6554
|
+
if (!focused)
|
|
6555
|
+
return;
|
|
6556
|
+
const bounds = this.pageBounds();
|
|
6557
|
+
const range = this.state.cellRange();
|
|
6558
|
+
const start = range?.start ?? focused;
|
|
6559
|
+
this.state.cellRange.set({ start, end: { row: bounds.start, col: 0 } });
|
|
6560
|
+
}
|
|
6561
|
+
extendRangeToGridEnd() {
|
|
6562
|
+
const focused = this.state.focusedCell();
|
|
6563
|
+
if (!focused)
|
|
6564
|
+
return;
|
|
6565
|
+
const bounds = this.pageBounds();
|
|
6566
|
+
const maxCol = this.state.visibleColumns().length - 1;
|
|
6567
|
+
if (maxCol < 0)
|
|
6568
|
+
return;
|
|
6569
|
+
const range = this.state.cellRange();
|
|
6570
|
+
const start = range?.start ?? focused;
|
|
6571
|
+
this.state.cellRange.set({ start, end: { row: bounds.end, col: maxCol } });
|
|
6572
|
+
}
|
|
6573
|
+
extendRangeJumpToEdge(direction) {
|
|
6574
|
+
const focused = this.state.focusedCell();
|
|
6575
|
+
if (!focused)
|
|
6576
|
+
return;
|
|
6577
|
+
const range = this.state.cellRange();
|
|
6578
|
+
const anchor = range?.start ?? focused;
|
|
6579
|
+
const end = range?.end ?? focused;
|
|
6580
|
+
const target = this.edgeFromCell(end, direction);
|
|
6581
|
+
this.state.cellRange.set({ start: anchor, end: target });
|
|
6582
|
+
}
|
|
6583
|
+
extendRangeByPage(direction) {
|
|
6584
|
+
const step = this.pageRowStep() * (direction === 'down' ? 1 : -1);
|
|
6585
|
+
this.extendRangeBy(step, 0);
|
|
6586
|
+
}
|
|
6587
|
+
// --- Whole row / column / grid selection --------------------------------------
|
|
6588
|
+
selectRow(row) {
|
|
6589
|
+
const maxCol = this.state.visibleColumns().length - 1;
|
|
6590
|
+
if (maxCol < 0)
|
|
5884
6591
|
return;
|
|
5885
|
-
this.state.
|
|
6592
|
+
this.state.focusedCell.set({ row, col: 0 });
|
|
6593
|
+
this.state.focusSource.set('keyboard');
|
|
6594
|
+
this.state.cellRange.set({
|
|
6595
|
+
start: { row, col: 0 },
|
|
6596
|
+
end: { row, col: maxCol },
|
|
6597
|
+
});
|
|
5886
6598
|
}
|
|
5887
|
-
|
|
5888
|
-
this.
|
|
6599
|
+
selectColumn(col) {
|
|
6600
|
+
const bounds = this.pageBounds();
|
|
6601
|
+
this.state.focusedCell.set({ row: bounds.start, col });
|
|
6602
|
+
this.state.focusSource.set('keyboard');
|
|
6603
|
+
this.state.cellRange.set({
|
|
6604
|
+
start: { row: bounds.start, col },
|
|
6605
|
+
end: { row: bounds.end, col },
|
|
6606
|
+
});
|
|
5889
6607
|
}
|
|
5890
|
-
|
|
5891
|
-
this.
|
|
6608
|
+
selectAll() {
|
|
6609
|
+
const bounds = this.pageBounds();
|
|
6610
|
+
const maxCol = this.state.visibleColumns().length - 1;
|
|
6611
|
+
if (maxCol < 0)
|
|
6612
|
+
return;
|
|
6613
|
+
this.state.focusedCell.set({ row: bounds.start, col: 0 });
|
|
6614
|
+
this.state.focusSource.set('keyboard');
|
|
6615
|
+
this.state.cellRange.set({
|
|
6616
|
+
start: { row: bounds.start, col: 0 },
|
|
6617
|
+
end: { row: bounds.end, col: maxCol },
|
|
6618
|
+
});
|
|
5892
6619
|
}
|
|
5893
|
-
|
|
5894
|
-
|
|
6620
|
+
// --- Private helpers ----------------------------------------------------------
|
|
6621
|
+
pageBounds() {
|
|
6622
|
+
const start = this.state.pageIndex() * this.state.pageSize();
|
|
6623
|
+
const end = start + Math.max(0, this.state.visibleRowCount() - 1);
|
|
6624
|
+
return { start, end };
|
|
6625
|
+
}
|
|
6626
|
+
pageRowStep() {
|
|
6627
|
+
const rowHeight = this.state.rowHeight() || 48;
|
|
6628
|
+
const viewportHeight = this.state.scrollViewportHeight();
|
|
6629
|
+
if (viewportHeight > 0) {
|
|
6630
|
+
return Math.max(1, Math.floor(viewportHeight / rowHeight));
|
|
6631
|
+
}
|
|
6632
|
+
// Fallback: a sensible default when the viewport hasn't been measured yet.
|
|
6633
|
+
return Math.max(1, Math.floor(this.state.visibleRowCount() / 2) || 10);
|
|
6634
|
+
}
|
|
6635
|
+
directionVector(dir) {
|
|
6636
|
+
switch (dir) {
|
|
6637
|
+
case 'up':
|
|
6638
|
+
return { dRow: -1, dCol: 0 };
|
|
6639
|
+
case 'down':
|
|
6640
|
+
return { dRow: 1, dCol: 0 };
|
|
6641
|
+
case 'left':
|
|
6642
|
+
return { dRow: 0, dCol: -1 };
|
|
6643
|
+
case 'right':
|
|
6644
|
+
return { dRow: 0, dCol: 1 };
|
|
6645
|
+
}
|
|
6646
|
+
}
|
|
6647
|
+
inBounds(row, col, bounds, maxCol) {
|
|
6648
|
+
return row >= bounds.start && row <= bounds.end && col >= 0 && col <= maxCol;
|
|
6649
|
+
}
|
|
6650
|
+
isCellFilled(row, col) {
|
|
6651
|
+
const cols = this.state.visibleColumns();
|
|
6652
|
+
const field = cols[col]?.field;
|
|
6653
|
+
if (!field)
|
|
6654
|
+
return false;
|
|
6655
|
+
const data = this.state.sourceData();
|
|
6656
|
+
const rowData = data[row];
|
|
6657
|
+
if (!rowData)
|
|
6658
|
+
return false;
|
|
6659
|
+
const def = this.state.columnDefMap().get(field);
|
|
6660
|
+
const value = def?.valueGetter
|
|
6661
|
+
? def.valueGetter(rowData)
|
|
6662
|
+
: rowData[field];
|
|
6663
|
+
return value !== null && value !== undefined && value !== '';
|
|
5895
6664
|
}
|
|
5896
|
-
|
|
5897
|
-
|
|
6665
|
+
findLastNonEmptyCol(row, maxCol) {
|
|
6666
|
+
for (let c = maxCol; c >= 0; c--) {
|
|
6667
|
+
if (this.isCellFilled(row, c))
|
|
6668
|
+
return c;
|
|
6669
|
+
}
|
|
6670
|
+
return maxCol;
|
|
5898
6671
|
}
|
|
5899
|
-
|
|
5900
|
-
this.
|
|
6672
|
+
edgeFromCell(from, direction) {
|
|
6673
|
+
const { dRow, dCol } = this.directionVector(direction);
|
|
6674
|
+
const bounds = this.pageBounds();
|
|
6675
|
+
const maxCol = this.state.visibleColumns().length - 1;
|
|
6676
|
+
if (maxCol < 0)
|
|
6677
|
+
return from;
|
|
6678
|
+
const startFilled = this.isCellFilled(from.row, from.col);
|
|
6679
|
+
let row = from.row;
|
|
6680
|
+
let col = from.col;
|
|
6681
|
+
let nextRow = row + dRow;
|
|
6682
|
+
let nextCol = col + dCol;
|
|
6683
|
+
while (this.inBounds(nextRow, nextCol, bounds, maxCol)) {
|
|
6684
|
+
const filled = this.isCellFilled(nextRow, nextCol);
|
|
6685
|
+
if (startFilled) {
|
|
6686
|
+
if (!filled)
|
|
6687
|
+
break;
|
|
6688
|
+
}
|
|
6689
|
+
else if (filled) {
|
|
6690
|
+
row = nextRow;
|
|
6691
|
+
col = nextCol;
|
|
6692
|
+
break;
|
|
6693
|
+
}
|
|
6694
|
+
row = nextRow;
|
|
6695
|
+
col = nextCol;
|
|
6696
|
+
nextRow += dRow;
|
|
6697
|
+
nextCol += dCol;
|
|
6698
|
+
}
|
|
6699
|
+
return { row, col };
|
|
5901
6700
|
}
|
|
5902
6701
|
moveToNextEditableCell() {
|
|
5903
6702
|
const focused = this.state.focusedCell();
|
|
@@ -6162,113 +6961,203 @@ class KeyboardEngine {
|
|
|
6162
6961
|
cellSelection = inject(CellSelectionEngine);
|
|
6163
6962
|
inlineEdit = inject(InlineEditEngine);
|
|
6164
6963
|
state = inject(GridStateManager);
|
|
6964
|
+
actions = null;
|
|
6965
|
+
registerActions(actions) {
|
|
6966
|
+
this.actions = actions;
|
|
6967
|
+
}
|
|
6165
6968
|
handleKeydown(event) {
|
|
6166
|
-
// Edit
|
|
6167
|
-
|
|
6168
|
-
if (
|
|
6969
|
+
// Edit-mode keys are handled by MozGridComponent.handleEditModeKeydown;
|
|
6970
|
+
// this engine only owns navigation + shortcuts in non-editing state.
|
|
6971
|
+
if (this.state.cellEditState().editingCell !== null)
|
|
6169
6972
|
return;
|
|
6170
|
-
this.handleNavigationKey(event);
|
|
6171
|
-
}
|
|
6172
|
-
handleNavigationKey(event) {
|
|
6173
6973
|
const focused = this.state.focusedCell();
|
|
6174
6974
|
if (!focused)
|
|
6175
6975
|
return;
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
const col = this.state.visibleColumns()[focused.col];
|
|
6225
|
-
if (col) {
|
|
6226
|
-
const def = this.state.columnDefMap().get(col.field);
|
|
6227
|
-
if (def?.editable) {
|
|
6228
|
-
this.inlineEdit.startEdit(focused.row, col.field);
|
|
6229
|
-
}
|
|
6230
|
-
}
|
|
6231
|
-
break;
|
|
6976
|
+
if (this.dispatch(event, focused)) {
|
|
6977
|
+
event.preventDefault();
|
|
6978
|
+
}
|
|
6979
|
+
}
|
|
6980
|
+
/** Returns true when the event was handled (so the caller should preventDefault). */
|
|
6981
|
+
dispatch(event, focused) {
|
|
6982
|
+
const mod = event.ctrlKey || event.metaKey;
|
|
6983
|
+
const shift = event.shiftKey;
|
|
6984
|
+
const alt = event.altKey;
|
|
6985
|
+
const key = event.key;
|
|
6986
|
+
// ---- Clipboard / history shortcuts --------------------------------------
|
|
6987
|
+
if (mod && !shift && !alt) {
|
|
6988
|
+
switch (key.toLowerCase()) {
|
|
6989
|
+
case 'c':
|
|
6990
|
+
this.actions?.copy();
|
|
6991
|
+
return true;
|
|
6992
|
+
case 'v':
|
|
6993
|
+
this.actions?.paste();
|
|
6994
|
+
return true;
|
|
6995
|
+
case 'x':
|
|
6996
|
+
this.actions?.cut();
|
|
6997
|
+
return true;
|
|
6998
|
+
case 'z':
|
|
6999
|
+
this.actions?.undo();
|
|
7000
|
+
return true;
|
|
7001
|
+
case 'y':
|
|
7002
|
+
this.actions?.redo();
|
|
7003
|
+
return true;
|
|
7004
|
+
case 'a':
|
|
7005
|
+
this.cellSelection.selectAll();
|
|
7006
|
+
return true;
|
|
7007
|
+
case 'd':
|
|
7008
|
+
this.actions?.fillDown();
|
|
7009
|
+
return true;
|
|
7010
|
+
case 'r':
|
|
7011
|
+
this.actions?.fillRight();
|
|
7012
|
+
return true;
|
|
7013
|
+
}
|
|
7014
|
+
}
|
|
7015
|
+
if (mod && shift && !alt && key.toLowerCase() === 'z') {
|
|
7016
|
+
this.actions?.redo();
|
|
7017
|
+
return true;
|
|
7018
|
+
}
|
|
7019
|
+
// ---- Whole row / column selection ---------------------------------------
|
|
7020
|
+
if (key === ' ') {
|
|
7021
|
+
if (mod && shift) {
|
|
7022
|
+
this.cellSelection.selectAll();
|
|
7023
|
+
return true;
|
|
6232
7024
|
}
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
if (col2) {
|
|
6237
|
-
const def2 = this.state.columnDefMap().get(col2.field);
|
|
6238
|
-
if (def2?.editable) {
|
|
6239
|
-
this.inlineEdit.startEdit(focused.row, col2.field);
|
|
6240
|
-
}
|
|
6241
|
-
}
|
|
6242
|
-
break;
|
|
7025
|
+
if (mod) {
|
|
7026
|
+
this.cellSelection.selectColumn(focused.col);
|
|
7027
|
+
return true;
|
|
6243
7028
|
}
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
7029
|
+
if (shift) {
|
|
7030
|
+
this.cellSelection.selectRow(focused.row);
|
|
7031
|
+
return true;
|
|
7032
|
+
}
|
|
7033
|
+
}
|
|
7034
|
+
// ---- Delete / Backspace: clear cells ------------------------------------
|
|
7035
|
+
if (key === 'Delete' || key === 'Backspace') {
|
|
7036
|
+
this.actions?.deleteRange();
|
|
7037
|
+
return true;
|
|
7038
|
+
}
|
|
7039
|
+
// ---- Arrow keys ---------------------------------------------------------
|
|
7040
|
+
if (key === 'ArrowUp' || key === 'ArrowDown' || key === 'ArrowLeft' || key === 'ArrowRight') {
|
|
7041
|
+
return this.handleArrow(key, { mod, shift });
|
|
7042
|
+
}
|
|
7043
|
+
// ---- Home / End / PageUp / PageDown -------------------------------------
|
|
7044
|
+
if (key === 'Home') {
|
|
7045
|
+
if (mod && shift)
|
|
7046
|
+
this.cellSelection.extendRangeToGridStart();
|
|
7047
|
+
else if (mod)
|
|
7048
|
+
this.cellSelection.moveToGridStart();
|
|
7049
|
+
else if (shift)
|
|
7050
|
+
this.cellSelection.extendRangeToRowStart();
|
|
7051
|
+
else
|
|
7052
|
+
this.cellSelection.moveToRowStart();
|
|
7053
|
+
return true;
|
|
7054
|
+
}
|
|
7055
|
+
if (key === 'End') {
|
|
7056
|
+
if (mod && shift)
|
|
7057
|
+
this.cellSelection.extendRangeToGridEnd();
|
|
7058
|
+
else if (mod)
|
|
7059
|
+
this.cellSelection.moveToGridEnd();
|
|
7060
|
+
else if (shift)
|
|
7061
|
+
this.cellSelection.extendRangeToRowEnd();
|
|
7062
|
+
else
|
|
7063
|
+
this.cellSelection.moveToRowEnd();
|
|
7064
|
+
return true;
|
|
7065
|
+
}
|
|
7066
|
+
if (key === 'PageUp') {
|
|
7067
|
+
if (shift)
|
|
7068
|
+
this.cellSelection.extendRangeByPage('up');
|
|
7069
|
+
else
|
|
7070
|
+
this.cellSelection.movePage('up');
|
|
7071
|
+
return true;
|
|
7072
|
+
}
|
|
7073
|
+
if (key === 'PageDown') {
|
|
7074
|
+
if (shift)
|
|
7075
|
+
this.cellSelection.extendRangeByPage('down');
|
|
7076
|
+
else
|
|
7077
|
+
this.cellSelection.movePage('down');
|
|
7078
|
+
return true;
|
|
7079
|
+
}
|
|
7080
|
+
// ---- Tab / Enter / F2 / Escape ------------------------------------------
|
|
7081
|
+
if (key === 'Tab') {
|
|
7082
|
+
if (shift)
|
|
7083
|
+
this.cellSelection.moveLeft();
|
|
7084
|
+
else
|
|
7085
|
+
this.cellSelection.moveRight();
|
|
7086
|
+
return true;
|
|
7087
|
+
}
|
|
7088
|
+
if (key === 'Enter') {
|
|
7089
|
+
const col = this.state.visibleColumns()[focused.col];
|
|
7090
|
+
const def = col ? this.state.columnDefMap().get(col.field) : undefined;
|
|
7091
|
+
if (def?.editable) {
|
|
7092
|
+
this.actions?.startEdit(focused.row, focused.col);
|
|
7093
|
+
return true;
|
|
7094
|
+
}
|
|
7095
|
+
if (shift)
|
|
7096
|
+
this.cellSelection.moveUp();
|
|
7097
|
+
else
|
|
7098
|
+
this.cellSelection.moveDown();
|
|
7099
|
+
return true;
|
|
7100
|
+
}
|
|
7101
|
+
if (key === 'F2') {
|
|
7102
|
+
this.actions?.startEdit(focused.row, focused.col);
|
|
7103
|
+
return true;
|
|
7104
|
+
}
|
|
7105
|
+
if (key === 'Escape') {
|
|
7106
|
+
this.cellSelection.clearFocus();
|
|
7107
|
+
return true;
|
|
7108
|
+
}
|
|
7109
|
+
// ---- Typing-to-edit -----------------------------------------------------
|
|
7110
|
+
if (!mod && !alt && this.isPrintableKey(event)) {
|
|
7111
|
+
this.actions?.startEdit(focused.row, focused.col, key);
|
|
7112
|
+
return true;
|
|
6248
7113
|
}
|
|
7114
|
+
return false;
|
|
6249
7115
|
}
|
|
6250
|
-
|
|
6251
|
-
const
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
return;
|
|
6255
|
-
const pageStart = this.state.pageIndex() * this.state.pageSize();
|
|
6256
|
-
const pageEnd = pageStart + Math.max(0, this.state.visibleRowCount() - 1);
|
|
6257
|
-
const maxCol = this.state.visibleColumns().length - 1;
|
|
6258
|
-
if (range) {
|
|
6259
|
-
const newEnd = {
|
|
6260
|
-
row: Math.max(pageStart, Math.min(pageEnd, range.end.row + dRow)),
|
|
6261
|
-
col: Math.max(0, Math.min(maxCol, range.end.col + dCol)),
|
|
6262
|
-
};
|
|
6263
|
-
this.state.cellRange.set({ start: range.start, end: newEnd });
|
|
7116
|
+
handleArrow(key, mods) {
|
|
7117
|
+
const dir = key === 'ArrowUp' ? 'up' : key === 'ArrowDown' ? 'down' : key === 'ArrowLeft' ? 'left' : 'right';
|
|
7118
|
+
if (mods.mod && mods.shift) {
|
|
7119
|
+
this.cellSelection.extendRangeJumpToEdge(dir);
|
|
7120
|
+
return true;
|
|
6264
7121
|
}
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
7122
|
+
if (mods.mod) {
|
|
7123
|
+
this.cellSelection.jumpToEdge(dir);
|
|
7124
|
+
return true;
|
|
7125
|
+
}
|
|
7126
|
+
if (mods.shift) {
|
|
7127
|
+
const dRow = dir === 'up' ? -1 : dir === 'down' ? 1 : 0;
|
|
7128
|
+
const dCol = dir === 'left' ? -1 : dir === 'right' ? 1 : 0;
|
|
7129
|
+
this.cellSelection.extendRangeBy(dRow, dCol);
|
|
7130
|
+
return true;
|
|
7131
|
+
}
|
|
7132
|
+
switch (dir) {
|
|
7133
|
+
case 'up':
|
|
7134
|
+
this.cellSelection.moveUp();
|
|
7135
|
+
break;
|
|
7136
|
+
case 'down':
|
|
7137
|
+
this.cellSelection.moveDown();
|
|
7138
|
+
break;
|
|
7139
|
+
case 'left':
|
|
7140
|
+
this.cellSelection.moveLeft();
|
|
7141
|
+
break;
|
|
7142
|
+
case 'right':
|
|
7143
|
+
this.cellSelection.moveRight();
|
|
7144
|
+
break;
|
|
6271
7145
|
}
|
|
7146
|
+
return true;
|
|
7147
|
+
}
|
|
7148
|
+
/**
|
|
7149
|
+
* A key press is "printable" when it represents exactly one character that
|
|
7150
|
+
* the user intends as input — excluding named keys like F1, Home, Arrow*,
|
|
7151
|
+
* etc. Also excludes IME composition events (event.isComposing).
|
|
7152
|
+
*/
|
|
7153
|
+
isPrintableKey(event) {
|
|
7154
|
+
if (event.isComposing)
|
|
7155
|
+
return false;
|
|
7156
|
+
if (event.key.length !== 1)
|
|
7157
|
+
return false;
|
|
7158
|
+
// Guard against NUL / control characters slipping through on some layouts.
|
|
7159
|
+
const code = event.key.charCodeAt(0);
|
|
7160
|
+
return code >= 32 && code !== 127;
|
|
6272
7161
|
}
|
|
6273
7162
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: KeyboardEngine, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
6274
7163
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: KeyboardEngine });
|
|
@@ -7779,6 +8668,7 @@ class MozGridCellComponent {
|
|
|
7779
8668
|
inlineEdit = inject(InlineEditEngine);
|
|
7780
8669
|
cellSelectionEngine = inject(CellSelectionEngine);
|
|
7781
8670
|
validationEngine = inject(CellValidationEngine);
|
|
8671
|
+
clipboard = inject(ClipboardEngine);
|
|
7782
8672
|
elRef = inject((ElementRef));
|
|
7783
8673
|
preEditWidth = null;
|
|
7784
8674
|
constructor() {
|
|
@@ -7893,6 +8783,7 @@ class MozGridCellComponent {
|
|
|
7893
8783
|
isInFillRejectRange = computed(() => {
|
|
7894
8784
|
return this.cellSelectionEngine.isCellInFillRejectRange(this.rowIndex(), this.colIndex());
|
|
7895
8785
|
}, ...(ngDevMode ? [{ debugName: "isInFillRejectRange" }] : /* istanbul ignore next */ []));
|
|
8786
|
+
cutEdges = computed(() => this.clipboard.cutEdges(this.rowIndex(), this.colIndex()), ...(ngDevMode ? [{ debugName: "cutEdges" }] : /* istanbul ignore next */ []));
|
|
7896
8787
|
cellError = computed(() => this.validationEngine.getCellError(this.rowIndex(), this.def().field), ...(ngDevMode ? [{ debugName: "cellError" }] : /* istanbul ignore next */ []));
|
|
7897
8788
|
onCellClick(event) {
|
|
7898
8789
|
// Shift+click: extend range from current focused cell to this cell
|
|
@@ -7997,7 +8888,7 @@ class MozGridCellComponent {
|
|
|
7997
8888
|
});
|
|
7998
8889
|
}
|
|
7999
8890
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8000
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: MozGridCellComponent, isStandalone: true, selector: "moz-grid-cell", inputs: { row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: true, transformFunction: null }, rowIndex: { classPropertyName: "rowIndex", publicName: "rowIndex", isSignal: true, isRequired: true, transformFunction: null }, colIndex: { classPropertyName: "colIndex", publicName: "colIndex", isSignal: true, isRequired: true, transformFunction: null }, colState: { classPropertyName: "colState", publicName: "colState", isSignal: true, isRequired: true, transformFunction: null }, def: { classPropertyName: "def", publicName: "def", isSignal: true, isRequired: true, transformFunction: null }, isLast: { classPropertyName: "isLast", publicName: "isLast", isSignal: true, isRequired: false, transformFunction: null }, pinnedEnd: { classPropertyName: "pinnedEnd", publicName: "pinnedEnd", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { commitEdit: "commitEdit", cancelEdit: "cancelEdit" }, host: { properties: { "style.flex": "isLast() ? \"1 0 auto\" : \"0 0 auto\"", "style.width.px": "isLast() ? undefined : colState().currentWidth", "style.min-width.px": "isLast() ? colState().currentWidth : resolvedMinWidth()", "style.overflow": "isFocused() && !isEditing() ? \"visible\" : \"hidden\"" } }, ngImport: i0, template: "<div\n class=\"grid-cell\"\n [class.grid-cell--focused]=\"isFocused()\"\n [class.grid-cell--in-range]=\"isInRange()\"\n [class.grid-cell--in-fill-range]=\"isInFillRange()\"\n [class.grid-cell--in-fill-reject-range]=\"isInFillRejectRange()\"\n [class.grid-cell--last]=\"isLast()\"\n [class.grid-cell--pinned-end]=\"pinnedEnd()\"\n [class.grid-cell--readonly]=\"!def().editable\"\n [class.grid-cell--error]=\"cellError()\"\n [attr.aria-invalid]=\"cellError() ? 'true' : null\"\n (click)=\"onCellClick($event)\"\n (dblclick)=\"onDoubleClick()\"\n (mousedown)=\"onMouseDown($event)\"\n (mouseenter)=\"onMouseEnter()\"\n>\n @if (isEditing()) {\n <div class=\"grid-cell__editor\" (focusout)=\"onEditorBlur($event)\">\n @if (editTemplate()) {\n <div class=\"grid-cell__editor-custom\">\n <ng-container\n [ngTemplateOutlet]=\"editTemplate()!\"\n [ngTemplateOutletContext]=\"{\n $implicit: value(),\n row: row(),\n field: def().field,\n draft: editState().draftValue,\n updateDraft: updateDraftFn,\n commitEdit: commitEditFn\n }\"\n />\n </div>\n } @else { @switch (editorType()) { @case ('text') {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"text\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } @case ('number') {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"number\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } @case ('select') {\n <moz-select\n name=\"cell-editor\"\n [options]=\"def().cellEditorOptions ?? []\"\n [ngModel]=\"editState().draftValue\"\n (change)=\"onSelectChange($event)\"\n [size]=\"'s'\"\n />\n } @case ('checkbox') {\n <moz-checkbox\n [id]=\"'grid-cell-editor-' + rowIndex() + '-' + colIndex()\"\n [ngModel]=\"!!editState().draftValue\"\n (change)=\"onCheckboxChange($event)\"\n />\n } @case ('date') {\n <moz-datepicker\n [id]=\"'grid-cell-editor-' + rowIndex() + '-' + colIndex()\"\n size=\"s\"\n [ngModel]=\"editState().draftValue\"\n (ngModelChange)=\"onDateChange($event)\"\n />\n } @default {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"text\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } } }\n </div>\n } @else { @if (cellTemplate()) {\n <div class=\"grid-cell__custom\">\n <ng-container\n [ngTemplateOutlet]=\"cellTemplate()!\"\n [ngTemplateOutletContext]=\"{ $implicit: value(), row: row(), field: def().field }\"\n />\n </div>\n } @else {\n <span class=\"grid-cell__value\">{{ displayValue() }}</span>\n } } @if (isFocused() && !isEditing() && def().editable) {\n <div class=\"grid-cell__fill-handle\" (mousedown)=\"onFillHandleMouseDown($event)\"></div>\n } @if (cellError(); as error) {\n <div\n class=\"grid-cell__error-icon\"\n [mozTooltip]=\"error.message\"\n tooltipPosition=\"top\"\n aria-label=\"Erreur de validation\"\n >\n <ErrorFilled24 />\n </div>\n }\n</div>\n", styles: [":host{display:block;height:100%;min-width:0}.grid-cell{display:flex;align-items:center;position:relative;padding:0 var(--spacing-s, 8px);height:100%;border-right:1px solid var(--color-border-primary);overflow:hidden;box-sizing:border-box;min-width:0;background:inherit;cursor:pointer}.grid-cell--last{border-right:none}.grid-cell--pinned-end{border-right:none;border-left:1px solid var(--color-border-primary)}:host(:first-child) .grid-cell--pinned-end{border-left:none}.grid-cell__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--font-size-s, 14px);color:var(--color-text-primary);position:relative;z-index:1}.grid-cell__editor{width:100%;min-width:0;max-width:100%;height:100%;display:flex;align-items:center;overflow:hidden;box-sizing:border-box;position:relative;z-index:1}.grid-cell__editor-custom{width:100%;min-width:0;max-width:100%;overflow:hidden;box-sizing:border-box;display:flex;align-items:center;flex-wrap:wrap;gap:4px}.grid-cell__editor input,.grid-cell__editor moz-select{width:100%;min-width:0;box-sizing:border-box}.grid-cell__input--plain{border:none;outline:none;background:transparent;font-family:inherit;font-size:var(--font-size-s, 14px);color:var(--color-text-primary);padding:0;height:100%;width:100%;min-width:0;box-sizing:border-box}.grid-cell__input--plain:focus{outline:none}.grid-cell__input--plain[type=number]::-webkit-inner-spin-button,.grid-cell__input--plain[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.grid-cell__input--plain[type=number]{-moz-appearance:textfield}.grid-cell__editor ::ng-deep .text-input{width:100%;min-width:0;box-sizing:border-box}.grid-cell__editor ::ng-deep .select,.grid-cell__editor ::ng-deep .select__trigger{min-width:0;width:100%}.grid-cell__editor ::ng-deep *{max-width:100%}.grid-cell__editor moz-datepicker{width:100%;min-width:0;box-sizing:border-box}.grid-cell__editor ::ng-deep .mc-datepicker,.grid-cell__editor ::ng-deep .mc-text-input{width:100%;min-width:0;box-sizing:border-box;height:28px;font-size:var(--font-size-xs, 12px)}.grid-cell__editor ::ng-deep moz-datepicker label{display:none}.grid-cell>*:not(.grid-cell__fill-handle){position:relative;z-index:1}.grid-cell:before{content:\"\";position:absolute;inset:3px;border-radius:4px;pointer-events:none;z-index:0}.grid-cell:hover:before{background:#f1f3f4}.grid-cell--focused:hover:before,.grid-cell--in-range:hover:before,.grid-cell--in-fill-range:hover:before,.grid-cell--in-fill-reject-range:hover:before{background:transparent}.grid-cell--focused{z-index:2;overflow:visible}.grid-cell--focused:after{content:\"\";position:absolute;inset:0;border:2px solid var(--color-background-accent-inverse);border-radius:4px;pointer-events:none}.grid-cell--in-range{background:var(--color-background-accent)}.grid-cell--readonly .grid-cell__value{color:var(--color-text-secondary, #666)}.grid-cell--in-fill-range{background:#34a85314}.grid-cell--in-fill-range:after{content:\"\";position:absolute;inset:0;border:1px dashed var(--color-success, #34a853);border-radius:4px;pointer-events:none}.grid-cell--in-fill-reject-range{background:#ea302d1f;cursor:not-allowed;z-index:2}.grid-cell--in-fill-reject-range:after{content:\"\";position:absolute;inset:0;border:2px dashed var(--Status-Standalone-element-Error, #ea302d);border-radius:4px;pointer-events:none;z-index:3}.grid-cell__fill-handle{position:absolute;right:-4px;bottom:-4px;width:8px;height:8px;background:var(--color-background-primary, #fff);border:1px solid var(--color-background-accent-inverse);border-radius:2px;cursor:crosshair;z-index:4}.grid-cell--error{background:var(--Background-Primary, #FFF);outline:2px solid var(--Status-Border-Error, #EF5F5C);outline-offset:-2px;z-index:1}.grid-cell--error:hover:before{background:transparent}.grid-cell__error-icon{display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-left:auto;cursor:help;position:relative;z-index:2}.grid-cell__error-icon ::ng-deep svg{fill:var(--Status-Standalone-element-Error, #EA302D)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: MozSelectComponent, selector: "moz-select", inputs: ["id", "name", "options", "placeholder", "isInvalid", "disabled", "readonly", "size"] }, { kind: "component", type: MozCheckboxComponent, selector: "moz-checkbox", inputs: ["id", "name", "label", "indeterminate", "isInvalid", "disabled", "indented"] }, { kind: "component", type: MozDatepickerComponent, selector: "moz-datepicker", inputs: ["id", "disabled", "readonly", "invalid", "error", "clearable", "size", "label"] }, { kind: "directive", type: MozTooltipDirective, selector: "[mozTooltip]", inputs: ["mozTooltip", "tooltipPosition", "tooltipNoPointer"] }, { kind: "component", type: ErrorFilled24, selector: "ErrorFilled24", inputs: ["hostClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8891
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: MozGridCellComponent, isStandalone: true, selector: "moz-grid-cell", inputs: { row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: true, transformFunction: null }, rowIndex: { classPropertyName: "rowIndex", publicName: "rowIndex", isSignal: true, isRequired: true, transformFunction: null }, colIndex: { classPropertyName: "colIndex", publicName: "colIndex", isSignal: true, isRequired: true, transformFunction: null }, colState: { classPropertyName: "colState", publicName: "colState", isSignal: true, isRequired: true, transformFunction: null }, def: { classPropertyName: "def", publicName: "def", isSignal: true, isRequired: true, transformFunction: null }, isLast: { classPropertyName: "isLast", publicName: "isLast", isSignal: true, isRequired: false, transformFunction: null }, pinnedEnd: { classPropertyName: "pinnedEnd", publicName: "pinnedEnd", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { commitEdit: "commitEdit", cancelEdit: "cancelEdit" }, host: { properties: { "style.flex": "isLast() ? \"1 0 auto\" : \"0 0 auto\"", "style.width.px": "isLast() ? undefined : colState().currentWidth", "style.min-width.px": "isLast() ? colState().currentWidth : resolvedMinWidth()", "style.overflow": "isFocused() && !isEditing() ? \"visible\" : \"hidden\"" } }, ngImport: i0, template: "<div\n class=\"grid-cell\"\n [class.grid-cell--focused]=\"isFocused()\"\n [class.grid-cell--in-range]=\"isInRange()\"\n [class.grid-cell--in-fill-range]=\"isInFillRange()\"\n [class.grid-cell--in-fill-reject-range]=\"isInFillRejectRange()\"\n [class.grid-cell--cut]=\"cutEdges().any\"\n [class.grid-cell--last]=\"isLast()\"\n [class.grid-cell--pinned-end]=\"pinnedEnd()\"\n [class.grid-cell--readonly]=\"!def().editable\"\n [class.grid-cell--error]=\"cellError()\"\n [attr.aria-invalid]=\"cellError() ? 'true' : null\"\n (click)=\"onCellClick($event)\"\n (dblclick)=\"onDoubleClick()\"\n (mousedown)=\"onMouseDown($event)\"\n (mouseenter)=\"onMouseEnter()\"\n>\n @if (isEditing()) {\n <div class=\"grid-cell__editor\" (focusout)=\"onEditorBlur($event)\">\n @if (editTemplate()) {\n <div class=\"grid-cell__editor-custom\">\n <ng-container\n [ngTemplateOutlet]=\"editTemplate()!\"\n [ngTemplateOutletContext]=\"{\n $implicit: value(),\n row: row(),\n field: def().field,\n draft: editState().draftValue,\n updateDraft: updateDraftFn,\n commitEdit: commitEditFn\n }\"\n />\n </div>\n } @else { @switch (editorType()) { @case ('text') {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"text\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } @case ('number') {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"number\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } @case ('select') {\n <moz-select\n name=\"cell-editor\"\n [options]=\"def().cellEditorOptions ?? []\"\n [ngModel]=\"editState().draftValue\"\n (change)=\"onSelectChange($event)\"\n [size]=\"'s'\"\n />\n } @case ('checkbox') {\n <moz-checkbox\n [id]=\"'grid-cell-editor-' + rowIndex() + '-' + colIndex()\"\n [ngModel]=\"!!editState().draftValue\"\n (change)=\"onCheckboxChange($event)\"\n />\n } @case ('date') {\n <moz-datepicker\n [id]=\"'grid-cell-editor-' + rowIndex() + '-' + colIndex()\"\n size=\"s\"\n [ngModel]=\"editState().draftValue\"\n (ngModelChange)=\"onDateChange($event)\"\n />\n } @default {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"text\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } } }\n </div>\n } @else { @if (cellTemplate()) {\n <div class=\"grid-cell__custom\">\n <ng-container\n [ngTemplateOutlet]=\"cellTemplate()!\"\n [ngTemplateOutletContext]=\"{ $implicit: value(), row: row(), field: def().field }\"\n />\n </div>\n } @else {\n <span class=\"grid-cell__value\">{{ displayValue() }}</span>\n } } @if (cutEdges(); as edges) { @if (edges.top) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--top\"></div>\n } @if (edges.bottom) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--bottom\"></div>\n } @if (edges.left) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--left\"></div>\n } @if (edges.right) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--right\"></div>\n } } @if (isFocused() && !isEditing() && def().editable) {\n <div class=\"grid-cell__fill-handle\" (mousedown)=\"onFillHandleMouseDown($event)\"></div>\n } @if (cellError(); as error) {\n <div\n class=\"grid-cell__error-icon\"\n [mozTooltip]=\"error.message\"\n tooltipPosition=\"top\"\n aria-label=\"Erreur de validation\"\n >\n <ErrorFilled24 />\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;height:100%;min-width:0}.grid-cell{display:flex;align-items:center;position:relative;padding:0 var(--spacing-s, 8px);height:100%;border-right:1px solid var(--color-border-primary);overflow:hidden;box-sizing:border-box;min-width:0;background:inherit;cursor:pointer}.grid-cell--last{border-right:none}.grid-cell--pinned-end{border-right:none;border-left:1px solid var(--color-border-primary)}:host(:first-child) .grid-cell--pinned-end{border-left:none}.grid-cell__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--font-size-s, 14px);color:var(--color-text-primary);position:relative;z-index:1}.grid-cell__editor{width:100%;min-width:0;max-width:100%;height:100%;display:flex;align-items:center;overflow:hidden;box-sizing:border-box;position:relative;z-index:1}.grid-cell__editor-custom{width:100%;min-width:0;max-width:100%;overflow:hidden;box-sizing:border-box;display:flex;align-items:center;flex-wrap:wrap;gap:4px}.grid-cell__editor input,.grid-cell__editor moz-select{width:100%;min-width:0;box-sizing:border-box}.grid-cell__input--plain{border:none;outline:none;background:transparent;font-family:inherit;font-size:var(--font-size-s, 14px);color:var(--color-text-primary);padding:0;height:100%;width:100%;min-width:0;box-sizing:border-box}.grid-cell__input--plain:focus{outline:none}.grid-cell__input--plain[type=number]::-webkit-inner-spin-button,.grid-cell__input--plain[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.grid-cell__input--plain[type=number]{-moz-appearance:textfield}.grid-cell__editor ::ng-deep .text-input{width:100%;min-width:0;box-sizing:border-box}.grid-cell__editor ::ng-deep .select,.grid-cell__editor ::ng-deep .select__trigger{min-width:0;width:100%}.grid-cell__editor ::ng-deep *{max-width:100%}.grid-cell__editor moz-datepicker{width:100%;min-width:0;box-sizing:border-box}.grid-cell__editor ::ng-deep .mc-datepicker,.grid-cell__editor ::ng-deep .mc-text-input{width:100%;min-width:0;box-sizing:border-box;height:28px;font-size:var(--font-size-xs, 12px)}.grid-cell__editor ::ng-deep moz-datepicker label{display:none}.grid-cell>*:not(.grid-cell__fill-handle){position:relative;z-index:1}.grid-cell:before{content:\"\";position:absolute;inset:3px;border-radius:4px;pointer-events:none;z-index:0}.grid-cell:hover:before{background:#f1f3f4}.grid-cell--focused:hover:before,.grid-cell--in-range:hover:before,.grid-cell--in-fill-range:hover:before,.grid-cell--in-fill-reject-range:hover:before{background:transparent}.grid-cell--focused{z-index:2;overflow:visible}.grid-cell--focused:after{content:\"\";position:absolute;inset:0;border:2px solid var(--color-background-accent-inverse);border-radius:4px;pointer-events:none}.grid-cell--in-range{background:var(--color-background-accent)}.grid-cell--readonly .grid-cell__value{color:var(--color-text-secondary, #666)}.grid-cell--in-fill-range{background:#34a85314}.grid-cell--in-fill-range:after{content:\"\";position:absolute;inset:0;border:1px dashed var(--color-success, #34a853);border-radius:4px;pointer-events:none}.grid-cell--in-fill-reject-range{background:#ea302d1f;cursor:not-allowed;z-index:2}.grid-cell--in-fill-reject-range:after{content:\"\";position:absolute;inset:0;border:2px dashed var(--Status-Standalone-element-Error, #ea302d);border-radius:4px;pointer-events:none;z-index:3}.grid-cell--cut{background:#1a73e80f}.grid-cell__cut-mark{position:absolute;pointer-events:none;z-index:5;--cut-color: var(--color-background-accent-inverse, #1a73e8)}.grid-cell__cut-mark--top,.grid-cell__cut-mark--bottom{left:0;right:0;height:2px;background-image:linear-gradient(90deg,var(--cut-color) 50%,transparent 50%);background-size:8px 2px;background-repeat:repeat-x;animation:moz-grid-marching-ants-x .5s linear infinite}.grid-cell__cut-mark--top{top:0}.grid-cell__cut-mark--bottom{bottom:0}.grid-cell__cut-mark--left,.grid-cell__cut-mark--right{top:0;bottom:0;width:2px;background-image:linear-gradient(180deg,var(--cut-color) 50%,transparent 50%);background-size:2px 8px;background-repeat:repeat-y;animation:moz-grid-marching-ants-y .5s linear infinite}.grid-cell__cut-mark--left{left:0}.grid-cell__cut-mark--right{right:0}@keyframes moz-grid-marching-ants-x{0%{background-position-x:0}to{background-position-x:8px}}@keyframes moz-grid-marching-ants-y{0%{background-position-y:0}to{background-position-y:8px}}.grid-cell__fill-handle{position:absolute;right:-4px;bottom:-4px;width:8px;height:8px;background:var(--color-background-primary, #fff);border:1px solid var(--color-background-accent-inverse);border-radius:2px;cursor:crosshair;z-index:4}.grid-cell--error{background:var(--Background-Primary, #FFF);outline:2px solid var(--Status-Border-Error, #EF5F5C);outline-offset:-2px;z-index:1}.grid-cell--error:hover:before{background:transparent}.grid-cell__error-icon{display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-left:auto;cursor:help;position:relative;z-index:2}.grid-cell__error-icon ::ng-deep svg{fill:var(--Status-Standalone-element-Error, #EA302D)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: MozSelectComponent, selector: "moz-select", inputs: ["id", "name", "options", "placeholder", "isInvalid", "disabled", "readonly", "size"] }, { kind: "component", type: MozCheckboxComponent, selector: "moz-checkbox", inputs: ["id", "name", "label", "indeterminate", "isInvalid", "disabled", "indented"] }, { kind: "component", type: MozDatepickerComponent, selector: "moz-datepicker", inputs: ["id", "disabled", "readonly", "invalid", "error", "clearable", "size", "label"] }, { kind: "directive", type: MozTooltipDirective, selector: "[mozTooltip]", inputs: ["mozTooltip", "tooltipPosition", "tooltipNoPointer"] }, { kind: "component", type: ErrorFilled24, selector: "ErrorFilled24", inputs: ["hostClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8001
8892
|
}
|
|
8002
8893
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MozGridCellComponent, decorators: [{
|
|
8003
8894
|
type: Component,
|
|
@@ -8014,7 +8905,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
8014
8905
|
'[style.width.px]': 'isLast() ? undefined : colState().currentWidth',
|
|
8015
8906
|
'[style.min-width.px]': 'isLast() ? colState().currentWidth : resolvedMinWidth()',
|
|
8016
8907
|
'[style.overflow]': 'isFocused() && !isEditing() ? "visible" : "hidden"',
|
|
8017
|
-
}, template: "<div\n class=\"grid-cell\"\n [class.grid-cell--focused]=\"isFocused()\"\n [class.grid-cell--in-range]=\"isInRange()\"\n [class.grid-cell--in-fill-range]=\"isInFillRange()\"\n [class.grid-cell--in-fill-reject-range]=\"isInFillRejectRange()\"\n [class.grid-cell--last]=\"isLast()\"\n [class.grid-cell--pinned-end]=\"pinnedEnd()\"\n [class.grid-cell--readonly]=\"!def().editable\"\n [class.grid-cell--error]=\"cellError()\"\n [attr.aria-invalid]=\"cellError() ? 'true' : null\"\n (click)=\"onCellClick($event)\"\n (dblclick)=\"onDoubleClick()\"\n (mousedown)=\"onMouseDown($event)\"\n (mouseenter)=\"onMouseEnter()\"\n>\n @if (isEditing()) {\n <div class=\"grid-cell__editor\" (focusout)=\"onEditorBlur($event)\">\n @if (editTemplate()) {\n <div class=\"grid-cell__editor-custom\">\n <ng-container\n [ngTemplateOutlet]=\"editTemplate()!\"\n [ngTemplateOutletContext]=\"{\n $implicit: value(),\n row: row(),\n field: def().field,\n draft: editState().draftValue,\n updateDraft: updateDraftFn,\n commitEdit: commitEditFn\n }\"\n />\n </div>\n } @else { @switch (editorType()) { @case ('text') {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"text\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } @case ('number') {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"number\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } @case ('select') {\n <moz-select\n name=\"cell-editor\"\n [options]=\"def().cellEditorOptions ?? []\"\n [ngModel]=\"editState().draftValue\"\n (change)=\"onSelectChange($event)\"\n [size]=\"'s'\"\n />\n } @case ('checkbox') {\n <moz-checkbox\n [id]=\"'grid-cell-editor-' + rowIndex() + '-' + colIndex()\"\n [ngModel]=\"!!editState().draftValue\"\n (change)=\"onCheckboxChange($event)\"\n />\n } @case ('date') {\n <moz-datepicker\n [id]=\"'grid-cell-editor-' + rowIndex() + '-' + colIndex()\"\n size=\"s\"\n [ngModel]=\"editState().draftValue\"\n (ngModelChange)=\"onDateChange($event)\"\n />\n } @default {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"text\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } } }\n </div>\n } @else { @if (cellTemplate()) {\n <div class=\"grid-cell__custom\">\n <ng-container\n [ngTemplateOutlet]=\"cellTemplate()!\"\n [ngTemplateOutletContext]=\"{ $implicit: value(), row: row(), field: def().field }\"\n />\n </div>\n } @else {\n <span class=\"grid-cell__value\">{{ displayValue() }}</span>\n } } @if (isFocused() && !isEditing() && def().editable) {\n <div class=\"grid-cell__fill-handle\" (mousedown)=\"onFillHandleMouseDown($event)\"></div>\n } @if (cellError(); as error) {\n <div\n class=\"grid-cell__error-icon\"\n [mozTooltip]=\"error.message\"\n tooltipPosition=\"top\"\n aria-label=\"Erreur de validation\"\n >\n <ErrorFilled24 />\n </div>\n }\n</div>\n", styles: ["
|
|
8908
|
+
}, template: "<div\n class=\"grid-cell\"\n [class.grid-cell--focused]=\"isFocused()\"\n [class.grid-cell--in-range]=\"isInRange()\"\n [class.grid-cell--in-fill-range]=\"isInFillRange()\"\n [class.grid-cell--in-fill-reject-range]=\"isInFillRejectRange()\"\n [class.grid-cell--cut]=\"cutEdges().any\"\n [class.grid-cell--last]=\"isLast()\"\n [class.grid-cell--pinned-end]=\"pinnedEnd()\"\n [class.grid-cell--readonly]=\"!def().editable\"\n [class.grid-cell--error]=\"cellError()\"\n [attr.aria-invalid]=\"cellError() ? 'true' : null\"\n (click)=\"onCellClick($event)\"\n (dblclick)=\"onDoubleClick()\"\n (mousedown)=\"onMouseDown($event)\"\n (mouseenter)=\"onMouseEnter()\"\n>\n @if (isEditing()) {\n <div class=\"grid-cell__editor\" (focusout)=\"onEditorBlur($event)\">\n @if (editTemplate()) {\n <div class=\"grid-cell__editor-custom\">\n <ng-container\n [ngTemplateOutlet]=\"editTemplate()!\"\n [ngTemplateOutletContext]=\"{\n $implicit: value(),\n row: row(),\n field: def().field,\n draft: editState().draftValue,\n updateDraft: updateDraftFn,\n commitEdit: commitEditFn\n }\"\n />\n </div>\n } @else { @switch (editorType()) { @case ('text') {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"text\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } @case ('number') {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"number\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } @case ('select') {\n <moz-select\n name=\"cell-editor\"\n [options]=\"def().cellEditorOptions ?? []\"\n [ngModel]=\"editState().draftValue\"\n (change)=\"onSelectChange($event)\"\n [size]=\"'s'\"\n />\n } @case ('checkbox') {\n <moz-checkbox\n [id]=\"'grid-cell-editor-' + rowIndex() + '-' + colIndex()\"\n [ngModel]=\"!!editState().draftValue\"\n (change)=\"onCheckboxChange($event)\"\n />\n } @case ('date') {\n <moz-datepicker\n [id]=\"'grid-cell-editor-' + rowIndex() + '-' + colIndex()\"\n size=\"s\"\n [ngModel]=\"editState().draftValue\"\n (ngModelChange)=\"onDateChange($event)\"\n />\n } @default {\n <input\n class=\"grid-cell__input grid-cell__input--plain\"\n type=\"text\"\n [value]=\"editState().draftValue\"\n (input)=\"onEditorInput($event)\"\n />\n } } }\n </div>\n } @else { @if (cellTemplate()) {\n <div class=\"grid-cell__custom\">\n <ng-container\n [ngTemplateOutlet]=\"cellTemplate()!\"\n [ngTemplateOutletContext]=\"{ $implicit: value(), row: row(), field: def().field }\"\n />\n </div>\n } @else {\n <span class=\"grid-cell__value\">{{ displayValue() }}</span>\n } } @if (cutEdges(); as edges) { @if (edges.top) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--top\"></div>\n } @if (edges.bottom) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--bottom\"></div>\n } @if (edges.left) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--left\"></div>\n } @if (edges.right) {\n <div class=\"grid-cell__cut-mark grid-cell__cut-mark--right\"></div>\n } } @if (isFocused() && !isEditing() && def().editable) {\n <div class=\"grid-cell__fill-handle\" (mousedown)=\"onFillHandleMouseDown($event)\"></div>\n } @if (cellError(); as error) {\n <div\n class=\"grid-cell__error-icon\"\n [mozTooltip]=\"error.message\"\n tooltipPosition=\"top\"\n aria-label=\"Erreur de validation\"\n >\n <ErrorFilled24 />\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;height:100%;min-width:0}.grid-cell{display:flex;align-items:center;position:relative;padding:0 var(--spacing-s, 8px);height:100%;border-right:1px solid var(--color-border-primary);overflow:hidden;box-sizing:border-box;min-width:0;background:inherit;cursor:pointer}.grid-cell--last{border-right:none}.grid-cell--pinned-end{border-right:none;border-left:1px solid var(--color-border-primary)}:host(:first-child) .grid-cell--pinned-end{border-left:none}.grid-cell__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--font-size-s, 14px);color:var(--color-text-primary);position:relative;z-index:1}.grid-cell__editor{width:100%;min-width:0;max-width:100%;height:100%;display:flex;align-items:center;overflow:hidden;box-sizing:border-box;position:relative;z-index:1}.grid-cell__editor-custom{width:100%;min-width:0;max-width:100%;overflow:hidden;box-sizing:border-box;display:flex;align-items:center;flex-wrap:wrap;gap:4px}.grid-cell__editor input,.grid-cell__editor moz-select{width:100%;min-width:0;box-sizing:border-box}.grid-cell__input--plain{border:none;outline:none;background:transparent;font-family:inherit;font-size:var(--font-size-s, 14px);color:var(--color-text-primary);padding:0;height:100%;width:100%;min-width:0;box-sizing:border-box}.grid-cell__input--plain:focus{outline:none}.grid-cell__input--plain[type=number]::-webkit-inner-spin-button,.grid-cell__input--plain[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.grid-cell__input--plain[type=number]{-moz-appearance:textfield}.grid-cell__editor ::ng-deep .text-input{width:100%;min-width:0;box-sizing:border-box}.grid-cell__editor ::ng-deep .select,.grid-cell__editor ::ng-deep .select__trigger{min-width:0;width:100%}.grid-cell__editor ::ng-deep *{max-width:100%}.grid-cell__editor moz-datepicker{width:100%;min-width:0;box-sizing:border-box}.grid-cell__editor ::ng-deep .mc-datepicker,.grid-cell__editor ::ng-deep .mc-text-input{width:100%;min-width:0;box-sizing:border-box;height:28px;font-size:var(--font-size-xs, 12px)}.grid-cell__editor ::ng-deep moz-datepicker label{display:none}.grid-cell>*:not(.grid-cell__fill-handle){position:relative;z-index:1}.grid-cell:before{content:\"\";position:absolute;inset:3px;border-radius:4px;pointer-events:none;z-index:0}.grid-cell:hover:before{background:#f1f3f4}.grid-cell--focused:hover:before,.grid-cell--in-range:hover:before,.grid-cell--in-fill-range:hover:before,.grid-cell--in-fill-reject-range:hover:before{background:transparent}.grid-cell--focused{z-index:2;overflow:visible}.grid-cell--focused:after{content:\"\";position:absolute;inset:0;border:2px solid var(--color-background-accent-inverse);border-radius:4px;pointer-events:none}.grid-cell--in-range{background:var(--color-background-accent)}.grid-cell--readonly .grid-cell__value{color:var(--color-text-secondary, #666)}.grid-cell--in-fill-range{background:#34a85314}.grid-cell--in-fill-range:after{content:\"\";position:absolute;inset:0;border:1px dashed var(--color-success, #34a853);border-radius:4px;pointer-events:none}.grid-cell--in-fill-reject-range{background:#ea302d1f;cursor:not-allowed;z-index:2}.grid-cell--in-fill-reject-range:after{content:\"\";position:absolute;inset:0;border:2px dashed var(--Status-Standalone-element-Error, #ea302d);border-radius:4px;pointer-events:none;z-index:3}.grid-cell--cut{background:#1a73e80f}.grid-cell__cut-mark{position:absolute;pointer-events:none;z-index:5;--cut-color: var(--color-background-accent-inverse, #1a73e8)}.grid-cell__cut-mark--top,.grid-cell__cut-mark--bottom{left:0;right:0;height:2px;background-image:linear-gradient(90deg,var(--cut-color) 50%,transparent 50%);background-size:8px 2px;background-repeat:repeat-x;animation:moz-grid-marching-ants-x .5s linear infinite}.grid-cell__cut-mark--top{top:0}.grid-cell__cut-mark--bottom{bottom:0}.grid-cell__cut-mark--left,.grid-cell__cut-mark--right{top:0;bottom:0;width:2px;background-image:linear-gradient(180deg,var(--cut-color) 50%,transparent 50%);background-size:2px 8px;background-repeat:repeat-y;animation:moz-grid-marching-ants-y .5s linear infinite}.grid-cell__cut-mark--left{left:0}.grid-cell__cut-mark--right{right:0}@keyframes moz-grid-marching-ants-x{0%{background-position-x:0}to{background-position-x:8px}}@keyframes moz-grid-marching-ants-y{0%{background-position-y:0}to{background-position-y:8px}}.grid-cell__fill-handle{position:absolute;right:-4px;bottom:-4px;width:8px;height:8px;background:var(--color-background-primary, #fff);border:1px solid var(--color-background-accent-inverse);border-radius:2px;cursor:crosshair;z-index:4}.grid-cell--error{background:var(--Background-Primary, #FFF);outline:2px solid var(--Status-Border-Error, #EF5F5C);outline-offset:-2px;z-index:1}.grid-cell--error:hover:before{background:transparent}.grid-cell__error-icon{display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-left:auto;cursor:help;position:relative;z-index:2}.grid-cell__error-icon ::ng-deep svg{fill:var(--Status-Standalone-element-Error, #EA302D)}\n"] }]
|
|
8018
8909
|
}], ctorParameters: () => [], propDecorators: { row: [{ type: i0.Input, args: [{ isSignal: true, alias: "row", required: true }] }], rowIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowIndex", required: true }] }], colIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "colIndex", required: true }] }], colState: [{ type: i0.Input, args: [{ isSignal: true, alias: "colState", required: true }] }], def: [{ type: i0.Input, args: [{ isSignal: true, alias: "def", required: true }] }], isLast: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLast", required: false }] }], pinnedEnd: [{ type: i0.Input, args: [{ isSignal: true, alias: "pinnedEnd", required: false }] }], commitEdit: [{ type: i0.Output, args: ["commitEdit"] }], cancelEdit: [{ type: i0.Output, args: ["cancelEdit"] }] } });
|
|
8019
8910
|
|
|
8020
8911
|
class MozGridRowComponent {
|
|
@@ -8519,6 +9410,8 @@ class MozGridComponent {
|
|
|
8519
9410
|
rowSelectionEngine = inject(RowSelectionEngine);
|
|
8520
9411
|
cellSelectionEngine = inject(CellSelectionEngine);
|
|
8521
9412
|
keyboardEngine = inject(KeyboardEngine);
|
|
9413
|
+
clipboardEngine = inject(ClipboardEngine);
|
|
9414
|
+
historyEngine = inject(HistoryEngine);
|
|
8522
9415
|
groupEngine = inject(GroupEngine);
|
|
8523
9416
|
filterEngine = inject(FilterEngine);
|
|
8524
9417
|
persistenceEngine = inject(StatePersistenceEngine);
|
|
@@ -8596,6 +9489,22 @@ class MozGridComponent {
|
|
|
8596
9489
|
stateRestored = false;
|
|
8597
9490
|
documentMouseUpHandler = null;
|
|
8598
9491
|
constructor() {
|
|
9492
|
+
this.keyboardEngine.registerActions({
|
|
9493
|
+
copy: () => this.onBulkCopy(),
|
|
9494
|
+
paste: () => { void this.onBulkPaste(); },
|
|
9495
|
+
cut: () => this.onCutShortcut(),
|
|
9496
|
+
deleteRange: () => this.onBulkDelete(),
|
|
9497
|
+
undo: () => this.onUndo(),
|
|
9498
|
+
redo: () => this.onRedo(),
|
|
9499
|
+
fillDown: () => this.onFillDownShortcut(),
|
|
9500
|
+
fillRight: () => this.onFillRightShortcut(),
|
|
9501
|
+
startEdit: (row, col, char) => this.onStartEditShortcut(row, col, char),
|
|
9502
|
+
});
|
|
9503
|
+
// Bind history persistence to the grid's stateKey (same key we use for
|
|
9504
|
+
// column/sort persistence — one localStorage namespace per grid).
|
|
9505
|
+
effect(() => {
|
|
9506
|
+
this.historyEngine.attach(this.stateKey());
|
|
9507
|
+
});
|
|
8599
9508
|
// Global mouseup listener for fill handle — if the user drags the fill
|
|
8600
9509
|
// handle outside the grid and releases, we still need to end the fill.
|
|
8601
9510
|
afterNextRender(() => {
|
|
@@ -8631,6 +9540,7 @@ class MozGridComponent {
|
|
|
8631
9540
|
const pushScroll = () => {
|
|
8632
9541
|
this.ngZone.run(() => {
|
|
8633
9542
|
this.horizontalVirtualScrollEngine.onScroll(scrollEl.scrollLeft, scrollEl.clientWidth);
|
|
9543
|
+
this.state.scrollViewportHeight.set(scrollEl.clientHeight);
|
|
8634
9544
|
});
|
|
8635
9545
|
};
|
|
8636
9546
|
// Prime the engine with the initial viewport width so the range is
|
|
@@ -8907,64 +9817,152 @@ class MozGridComponent {
|
|
|
8907
9817
|
if (tag === 'input' || tag === 'select' || tag === 'textarea') {
|
|
8908
9818
|
return;
|
|
8909
9819
|
}
|
|
8910
|
-
//
|
|
9820
|
+
// Row-selection mode forwards a small set of shortcuts without touching
|
|
9821
|
+
// cell focus. Cell-mode (and anywhere else) goes through the keyboard engine.
|
|
8911
9822
|
const selMode = this.state.activeSelectionMode();
|
|
8912
|
-
if (selMode === 'rows'
|
|
8913
|
-
if ((event.ctrlKey || event.metaKey) && event.key === 'c') {
|
|
9823
|
+
if (selMode === 'rows') {
|
|
9824
|
+
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'c') {
|
|
8914
9825
|
event.preventDefault();
|
|
8915
9826
|
this.onBulkCopy();
|
|
8916
9827
|
return;
|
|
8917
9828
|
}
|
|
8918
|
-
if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
|
|
9829
|
+
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'v') {
|
|
8919
9830
|
event.preventDefault();
|
|
8920
|
-
this.onBulkPaste();
|
|
9831
|
+
void this.onBulkPaste();
|
|
8921
9832
|
return;
|
|
8922
9833
|
}
|
|
8923
9834
|
if (event.key === 'Delete' || event.key === 'Backspace') {
|
|
8924
|
-
|
|
8925
|
-
|
|
8926
|
-
|
|
8927
|
-
return;
|
|
8928
|
-
}
|
|
9835
|
+
event.preventDefault();
|
|
9836
|
+
this.onBulkDelete();
|
|
9837
|
+
return;
|
|
8929
9838
|
}
|
|
8930
9839
|
}
|
|
8931
9840
|
this.keyboardEngine.handleKeydown(event);
|
|
8932
9841
|
}
|
|
8933
9842
|
handleEditModeKeydown(event) {
|
|
8934
|
-
|
|
8935
|
-
|
|
8936
|
-
|
|
8937
|
-
|
|
8938
|
-
|
|
8939
|
-
this.cellEditCancel.emit(cancelEvt);
|
|
8940
|
-
}
|
|
8941
|
-
break;
|
|
9843
|
+
if (event.key === 'Escape') {
|
|
9844
|
+
event.preventDefault();
|
|
9845
|
+
const cancelEvt = this.inlineEditEngine.cancelEdit();
|
|
9846
|
+
if (cancelEvt) {
|
|
9847
|
+
this.cellEditCancel.emit(cancelEvt);
|
|
8942
9848
|
}
|
|
8943
|
-
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
8947
|
-
|
|
9849
|
+
return;
|
|
9850
|
+
}
|
|
9851
|
+
if (event.key === 'Enter') {
|
|
9852
|
+
// Alt+Enter: insert a newline in the draft for text editors (Excel-style).
|
|
9853
|
+
if (event.altKey) {
|
|
9854
|
+
const editState = this.state.cellEditState();
|
|
9855
|
+
const def = this.state.columnDefMap().get(this.state.visibleColumns()[editState.editingCell?.col ?? -1]?.field ?? '');
|
|
9856
|
+
const editorType = def?.cellEditor ?? 'text';
|
|
9857
|
+
if (editorType === 'text') {
|
|
9858
|
+
event.preventDefault();
|
|
9859
|
+
const draft = editState.draftValue;
|
|
9860
|
+
const next = (typeof draft === 'string' ? draft : String(draft ?? '')) + '\n';
|
|
9861
|
+
this.inlineEditEngine.updateDraft(next);
|
|
8948
9862
|
}
|
|
8949
|
-
|
|
8950
|
-
this.refocusGrid();
|
|
8951
|
-
break;
|
|
9863
|
+
return;
|
|
8952
9864
|
}
|
|
8953
|
-
|
|
9865
|
+
// Ctrl+Enter: commit current draft and broadcast it to the active range.
|
|
9866
|
+
if (event.ctrlKey || event.metaKey) {
|
|
8954
9867
|
event.preventDefault();
|
|
8955
|
-
const
|
|
8956
|
-
|
|
8957
|
-
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
this.
|
|
9868
|
+
const editState = this.state.cellEditState();
|
|
9869
|
+
const value = editState.draftValue;
|
|
9870
|
+
const cancel = this.inlineEditEngine.cancelEdit();
|
|
9871
|
+
if (cancel)
|
|
9872
|
+
this.cellEditCancel.emit(cancel);
|
|
9873
|
+
const range = this.cellSelectionEngine.getNormalizedRange();
|
|
9874
|
+
if (range) {
|
|
9875
|
+
const changes = this.clipboardEngine.fillSelection(range, value);
|
|
9876
|
+
this.historyEngine.record('fill-selection', changes);
|
|
8964
9877
|
}
|
|
8965
9878
|
this.refocusGrid();
|
|
8966
|
-
|
|
9879
|
+
return;
|
|
8967
9880
|
}
|
|
9881
|
+
event.preventDefault();
|
|
9882
|
+
this.commitFromEditMode();
|
|
9883
|
+
if (event.shiftKey)
|
|
9884
|
+
this.cellSelectionEngine.moveUp();
|
|
9885
|
+
else
|
|
9886
|
+
this.cellSelectionEngine.moveDown();
|
|
9887
|
+
this.refocusGrid();
|
|
9888
|
+
return;
|
|
9889
|
+
}
|
|
9890
|
+
if (event.key === 'Tab') {
|
|
9891
|
+
event.preventDefault();
|
|
9892
|
+
this.commitFromEditMode();
|
|
9893
|
+
if (event.shiftKey)
|
|
9894
|
+
this.cellSelectionEngine.moveLeft();
|
|
9895
|
+
else
|
|
9896
|
+
this.cellSelectionEngine.moveRight();
|
|
9897
|
+
this.refocusGrid();
|
|
9898
|
+
}
|
|
9899
|
+
}
|
|
9900
|
+
commitFromEditMode() {
|
|
9901
|
+
const evt = this.inlineEditEngine.commitEdit();
|
|
9902
|
+
if (!evt)
|
|
9903
|
+
return;
|
|
9904
|
+
this.cellEdit.emit(evt);
|
|
9905
|
+
this.clipboardEngine.clearCut();
|
|
9906
|
+
}
|
|
9907
|
+
onCutShortcut() {
|
|
9908
|
+
const range = this.cellSelectionEngine.getNormalizedRange();
|
|
9909
|
+
if (!range)
|
|
9910
|
+
return;
|
|
9911
|
+
const values = this.clipboardEngine.extractTsv(range);
|
|
9912
|
+
const tsv = values.map((row) => row.join('\t')).join('\n');
|
|
9913
|
+
navigator.clipboard.writeText(tsv);
|
|
9914
|
+
this.clipboardEngine.markCut(range);
|
|
9915
|
+
this.bulkCopy.emit({
|
|
9916
|
+
range,
|
|
9917
|
+
data: values,
|
|
9918
|
+
rowIds: this.getRangeRowIds(range),
|
|
9919
|
+
fields: this.getRangeFields(range),
|
|
9920
|
+
});
|
|
9921
|
+
}
|
|
9922
|
+
onUndo() {
|
|
9923
|
+
this.historyEngine.undo();
|
|
9924
|
+
this.clipboardEngine.clearCut();
|
|
9925
|
+
}
|
|
9926
|
+
onRedo() {
|
|
9927
|
+
this.historyEngine.redo();
|
|
9928
|
+
this.clipboardEngine.clearCut();
|
|
9929
|
+
}
|
|
9930
|
+
onFillDownShortcut() {
|
|
9931
|
+
const range = this.cellSelectionEngine.getNormalizedRange();
|
|
9932
|
+
if (!range)
|
|
9933
|
+
return;
|
|
9934
|
+
if (this.state.mode() !== 'client')
|
|
9935
|
+
return;
|
|
9936
|
+
const changes = this.clipboardEngine.fillDown(range);
|
|
9937
|
+
if (changes.length === 0)
|
|
9938
|
+
return;
|
|
9939
|
+
this.historyEngine.record('fill-down', changes);
|
|
9940
|
+
this.clipboardEngine.clearCut();
|
|
9941
|
+
}
|
|
9942
|
+
onFillRightShortcut() {
|
|
9943
|
+
const range = this.cellSelectionEngine.getNormalizedRange();
|
|
9944
|
+
if (!range)
|
|
9945
|
+
return;
|
|
9946
|
+
if (this.state.mode() !== 'client')
|
|
9947
|
+
return;
|
|
9948
|
+
const changes = this.clipboardEngine.fillRight(range);
|
|
9949
|
+
if (changes.length === 0)
|
|
9950
|
+
return;
|
|
9951
|
+
this.historyEngine.record('fill-right', changes);
|
|
9952
|
+
this.clipboardEngine.clearCut();
|
|
9953
|
+
}
|
|
9954
|
+
onStartEditShortcut(row, col, initialChar) {
|
|
9955
|
+
const colDef = this.state.visibleColumns()[col];
|
|
9956
|
+
if (!colDef)
|
|
9957
|
+
return;
|
|
9958
|
+
const def = this.state.columnDefMap().get(colDef.field);
|
|
9959
|
+
if (!def?.editable)
|
|
9960
|
+
return;
|
|
9961
|
+
if (initialChar !== undefined) {
|
|
9962
|
+
this.inlineEditEngine.startEditWithChar(row, colDef.field, initialChar);
|
|
9963
|
+
}
|
|
9964
|
+
else {
|
|
9965
|
+
this.inlineEditEngine.startEdit(row, colDef.field);
|
|
8968
9966
|
}
|
|
8969
9967
|
}
|
|
8970
9968
|
resetInfiniteScrollIfActive() {
|
|
@@ -9051,17 +10049,26 @@ class MozGridComponent {
|
|
|
9051
10049
|
if (srcIdx >= 0)
|
|
9052
10050
|
indexMap.set(r, srcIdx);
|
|
9053
10051
|
}
|
|
10052
|
+
const changes = [];
|
|
9054
10053
|
this.state.sourceData.update((d) => {
|
|
9055
10054
|
const updated = [...d];
|
|
9056
10055
|
for (const [, srcIdx] of indexMap) {
|
|
9057
10056
|
if (!updated[srcIdx])
|
|
9058
10057
|
continue;
|
|
10058
|
+
const before = updated[srcIdx][field];
|
|
10059
|
+
if (before === sourceValue)
|
|
10060
|
+
continue;
|
|
9059
10061
|
const rowCopy = { ...updated[srcIdx] };
|
|
9060
10062
|
rowCopy[field] = sourceValue;
|
|
9061
10063
|
updated[srcIdx] = rowCopy;
|
|
10064
|
+
changes.push({ rowIndex: srcIdx, field, before, after: sourceValue });
|
|
9062
10065
|
}
|
|
9063
10066
|
return updated;
|
|
9064
10067
|
});
|
|
10068
|
+
if (changes.length > 0) {
|
|
10069
|
+
this.historyEngine.record('fill', changes);
|
|
10070
|
+
}
|
|
10071
|
+
this.clipboardEngine.clearCut();
|
|
9065
10072
|
}
|
|
9066
10073
|
this.fillDown.emit({
|
|
9067
10074
|
sourceCell: anchor,
|
|
@@ -9102,6 +10109,7 @@ class MozGridComponent {
|
|
|
9102
10109
|
if (targetFields.length === 0)
|
|
9103
10110
|
return;
|
|
9104
10111
|
if (this.state.mode() === 'client') {
|
|
10112
|
+
const changes = [];
|
|
9105
10113
|
this.state.sourceData.update((d) => {
|
|
9106
10114
|
const updated = [...d];
|
|
9107
10115
|
const src = updated[anchorSourceIdx];
|
|
@@ -9109,11 +10117,19 @@ class MozGridComponent {
|
|
|
9109
10117
|
return updated;
|
|
9110
10118
|
const rowCopy = { ...src };
|
|
9111
10119
|
for (const f of targetFields) {
|
|
10120
|
+
const before = src[f];
|
|
10121
|
+
if (before === sourceValue)
|
|
10122
|
+
continue;
|
|
9112
10123
|
rowCopy[f] = sourceValue;
|
|
10124
|
+
changes.push({ rowIndex: anchorSourceIdx, field: f, before, after: sourceValue });
|
|
9113
10125
|
}
|
|
9114
10126
|
updated[anchorSourceIdx] = rowCopy;
|
|
9115
10127
|
return updated;
|
|
9116
10128
|
});
|
|
10129
|
+
if (changes.length > 0) {
|
|
10130
|
+
this.historyEngine.record('fill', changes);
|
|
10131
|
+
}
|
|
10132
|
+
this.clipboardEngine.clearCut();
|
|
9117
10133
|
}
|
|
9118
10134
|
this.fillDown.emit({
|
|
9119
10135
|
sourceCell: anchor,
|
|
@@ -9320,6 +10336,8 @@ class MozGridComponent {
|
|
|
9320
10336
|
});
|
|
9321
10337
|
}
|
|
9322
10338
|
onBulkCopy() {
|
|
10339
|
+
// A fresh copy always cancels any pending cut (Excel parity).
|
|
10340
|
+
this.clipboardEngine.clearCut();
|
|
9323
10341
|
if (this.state.activeSelectionMode() === 'rows') {
|
|
9324
10342
|
const event = this.rowSelectionEngine.getSelectionEvent();
|
|
9325
10343
|
const rowData = this.extractRowSelectionData(event.selectedRows);
|
|
@@ -9387,7 +10405,16 @@ class MozGridComponent {
|
|
|
9387
10405
|
},
|
|
9388
10406
|
};
|
|
9389
10407
|
if (this.state.mode() === 'client') {
|
|
9390
|
-
|
|
10408
|
+
// If a cut is pending, first wipe the source cells so cut+paste == move,
|
|
10409
|
+
// and fold both halves into a single undoable history op.
|
|
10410
|
+
const cutSource = this.state.cutSource();
|
|
10411
|
+
const clearChanges = cutSource ? this.clipboardEngine.clearRange(cutSource) : [];
|
|
10412
|
+
const pasteChanges = this.clipboardEngine.applyPaste(pasteRange, rows);
|
|
10413
|
+
const allChanges = [...clearChanges, ...pasteChanges];
|
|
10414
|
+
if (allChanges.length > 0) {
|
|
10415
|
+
this.historyEngine.record(cutSource ? 'cut' : 'paste', allChanges);
|
|
10416
|
+
}
|
|
10417
|
+
this.clipboardEngine.clearCut();
|
|
9391
10418
|
}
|
|
9392
10419
|
this.bulkPaste.emit({
|
|
9393
10420
|
range: pasteRange,
|
|
@@ -9449,33 +10476,14 @@ class MozGridComponent {
|
|
|
9449
10476
|
const pageEnd = pageStart + this.gridEngine.paginatedData().length - 1;
|
|
9450
10477
|
if (range.start.row < pageStart || range.end.row > pageEnd)
|
|
9451
10478
|
return;
|
|
9452
|
-
const cols = this.state.visibleColumns();
|
|
9453
10479
|
const rows = range.end.row - range.start.row + 1;
|
|
9454
10480
|
const colCount = range.end.col - range.start.col + 1;
|
|
9455
10481
|
if (this.state.mode() === 'client') {
|
|
9456
|
-
this.
|
|
9457
|
-
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
const rowCopy = { ...updated[r] };
|
|
9462
|
-
let changed = false;
|
|
9463
|
-
for (let c = range.start.col; c <= range.end.col; c++) {
|
|
9464
|
-
const field = cols[c]?.field;
|
|
9465
|
-
if (!field)
|
|
9466
|
-
continue;
|
|
9467
|
-
const coerced = this.coerceAndValidate(field, null, updated[r]);
|
|
9468
|
-
if (coerced !== PASTE_SKIP) {
|
|
9469
|
-
rowCopy[field] = coerced;
|
|
9470
|
-
changed = true;
|
|
9471
|
-
}
|
|
9472
|
-
}
|
|
9473
|
-
if (changed) {
|
|
9474
|
-
updated[r] = rowCopy;
|
|
9475
|
-
}
|
|
9476
|
-
}
|
|
9477
|
-
return updated;
|
|
9478
|
-
});
|
|
10482
|
+
const changes = this.clipboardEngine.clearRange(range);
|
|
10483
|
+
if (changes.length > 0) {
|
|
10484
|
+
this.historyEngine.record('delete', changes);
|
|
10485
|
+
}
|
|
10486
|
+
this.clipboardEngine.clearCut();
|
|
9479
10487
|
}
|
|
9480
10488
|
this.bulkDelete.emit({
|
|
9481
10489
|
range,
|
|
@@ -9513,36 +10521,6 @@ class MozGridComponent {
|
|
|
9513
10521
|
}
|
|
9514
10522
|
return { range, values };
|
|
9515
10523
|
}
|
|
9516
|
-
applyPasteData(range, pasteRows) {
|
|
9517
|
-
const cols = this.state.visibleColumns();
|
|
9518
|
-
this.state.sourceData.update((data) => {
|
|
9519
|
-
const updated = [...data];
|
|
9520
|
-
for (let ri = 0; ri < pasteRows.length; ri++) {
|
|
9521
|
-
const targetRow = range.start.row + ri;
|
|
9522
|
-
if (targetRow >= updated.length)
|
|
9523
|
-
break;
|
|
9524
|
-
const rowCopy = { ...updated[targetRow] };
|
|
9525
|
-
let changed = false;
|
|
9526
|
-
for (let ci = 0; ci < pasteRows[ri].length; ci++) {
|
|
9527
|
-
const targetCol = range.start.col + ci;
|
|
9528
|
-
if (targetCol >= cols.length)
|
|
9529
|
-
break;
|
|
9530
|
-
const field = cols[targetCol]?.field;
|
|
9531
|
-
if (!field)
|
|
9532
|
-
continue;
|
|
9533
|
-
const coerced = this.coerceAndValidate(field, pasteRows[ri][ci], updated[targetRow]);
|
|
9534
|
-
if (coerced !== PASTE_SKIP) {
|
|
9535
|
-
rowCopy[field] = coerced;
|
|
9536
|
-
changed = true;
|
|
9537
|
-
}
|
|
9538
|
-
}
|
|
9539
|
-
if (changed) {
|
|
9540
|
-
updated[targetRow] = rowCopy;
|
|
9541
|
-
}
|
|
9542
|
-
}
|
|
9543
|
-
return updated;
|
|
9544
|
-
});
|
|
9545
|
-
}
|
|
9546
10524
|
extractRowSelectionData(selectedRows) {
|
|
9547
10525
|
const cols = this.state.visibleColumns();
|
|
9548
10526
|
const defMap = this.state.columnDefMap();
|
|
@@ -9679,6 +10657,8 @@ class MozGridComponent {
|
|
|
9679
10657
|
RowSelectionEngine,
|
|
9680
10658
|
CellSelectionEngine,
|
|
9681
10659
|
KeyboardEngine,
|
|
10660
|
+
ClipboardEngine,
|
|
10661
|
+
HistoryEngine,
|
|
9682
10662
|
GroupEngine,
|
|
9683
10663
|
FilterEngine,
|
|
9684
10664
|
ColumnReorderEngine,
|
|
@@ -9925,6 +10905,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
9925
10905
|
RowSelectionEngine,
|
|
9926
10906
|
CellSelectionEngine,
|
|
9927
10907
|
KeyboardEngine,
|
|
10908
|
+
ClipboardEngine,
|
|
10909
|
+
HistoryEngine,
|
|
9928
10910
|
GroupEngine,
|
|
9929
10911
|
FilterEngine,
|
|
9930
10912
|
ColumnReorderEngine,
|