@ai-table/state 0.1.41-0 → 0.1.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.
@@ -1,9 +1,10 @@
1
1
  import * as Y from 'yjs';
2
2
  import { UndoManager } from 'yjs';
3
- import { getShareTypeNumberPath, ActionName, translateArrayEvent, getSharedRecordIndex, getSharedMapValueIndex, toMapSyncElement, getIdBySystemFieldValuesType, setRecordUpdatedInfo, getPositionsByRecordSyncElement, setRecordPositions as setRecordPositions$1, toRecordSyncElement, AI_TABLE_CONTENT_FIELD_NAME, getValuesByCustomFieldValues, getPositionsBySystemFieldValues, getTrackableEntityBySystemFieldValues, getShortIdBySystemFieldValues, getIdBySystemFieldValues, sortByViewPosition, AITableFilterLogical, AITableFilterOperation, AITableFieldType, isEmpty, AI_TABLE_DEFAULT_MIN_UNFROZEN_WIDTH, AI_TABLE_MIN_FROZEN_COLUMN_COUNT, idCreator } from '@ai-table/utils';
3
+ import { getShareTypeNumberPath, ActionName, translateArrayEvent, getSharedRecordIndex, getSharedMapValueIndex, toMapSyncElement, getIdBySystemFieldValuesType, setRecordUpdatedInfo, getPositionsByRecordSyncElement, setRecordPositions as setRecordPositions$1, toRecordSyncElement, AI_TABLE_CONTENT_FIELD_NAME, getValuesByCustomFieldValues, getPositionsBySystemFieldValues, getTrackableEntityBySystemFieldValues, getShortIdBySystemFieldValues, getIdBySystemFieldValues, SortDirection, AITableFilterLogical, AITableFilterOperation, AITableFieldType, isEmpty, sortByViewPosition, AI_TABLE_DEFAULT_MIN_UNFROZEN_WIDTH, AI_TABLE_MIN_FROZEN_COLUMN_COUNT, idCreator } from '@ai-table/utils';
4
4
  import * as i0 from '@angular/core';
5
5
  import { signal, Injectable } from '@angular/core';
6
- import { FieldModelMap, AITableQueries, isSystemField, AITable, getColumnIndicesSizeMap, shortIdCreator, generateNewName, getDefaultFieldValue, idsCreator, shortIdsCreator, setSelection, AI_TABLE_GRID_FIELD_SERVICE_MAP, clearSelection, buildClipboardData, writeToClipboard, getI18nTextByKey, AITableGridI18nKey, isMac, writeToAITable } from '@ai-table/grid';
6
+ import { FieldModelMap, AITableQueries, isSystemField, AITableRowType, AITable, getColumnIndicesSizeMap, shortIdCreator, generateNewName, getDefaultFieldValue, AI_TABLE_GROUP_MAX_LEVEL, idsCreator, shortIdsCreator, closeExpendCell, setSelection, AI_TABLE_GRID_FIELD_SERVICE_MAP, clearSelection, buildClipboardData, writeToClipboard, getI18nTextByKey, AITableGridI18nKey, isMac, writeToAITable } from '@ai-table/grid';
7
+ import { nanoid } from 'nanoid';
7
8
  import * as _ from 'lodash';
8
9
  import ___default from 'lodash';
9
10
  import { createDraft, finishDraft } from 'immer';
@@ -409,14 +410,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.10", ngImpo
409
410
  }] });
410
411
 
411
412
  function getSortRecords(aiTable, records, activeView, sortKeysMap) {
412
- if (!activeView?.settings || !activeView.settings.sorts?.length) {
413
- return sortByViewPosition(records, activeView);
414
- }
415
- const { is_keep_sort, sorts } = activeView.settings;
416
- if (is_keep_sort && sorts?.length) {
417
- return sortRecordsBySortInfo(aiTable, records, activeView, sortKeysMap);
418
- }
419
- return sortByViewPosition(records, activeView);
413
+ const shallowRecords = [...records];
414
+ const groups = activeView.settings?.groups;
415
+ return shallowRecords.sort((record1, record2) => {
416
+ // 分组排序(高优先级)
417
+ if (groups && groups.length > 0) {
418
+ const groupCompareResult = compareByGroups(aiTable, record1, record2, groups);
419
+ if (groupCompareResult !== 0) {
420
+ return groupCompareResult;
421
+ }
422
+ }
423
+ // sorts排序(中优先级)
424
+ let shouldSkipPositionSort = false;
425
+ if (activeView.settings?.sorts?.length) {
426
+ const { is_keep_sort, sorts } = activeView.settings;
427
+ if (sorts?.length) {
428
+ const sortsCompareResult = compareBySorts(record1, record2, sorts, aiTable, sortKeysMap);
429
+ if (sortsCompareResult !== 0) {
430
+ return sortsCompareResult;
431
+ }
432
+ shouldSkipPositionSort = Boolean(is_keep_sort);
433
+ }
434
+ }
435
+ // position排序(低优先级)
436
+ if (shouldSkipPositionSort) {
437
+ // is_keep_sort为true,禁用position排序
438
+ return 0;
439
+ }
440
+ return compareByPosition(record1, record2, activeView);
441
+ });
420
442
  }
421
443
  function sortRecordsBySortInfo(aiTable, records, activeView, sortKeysMap) {
422
444
  const shallowRows = [...records];
@@ -443,6 +465,59 @@ function sortRecordsBySortInfo(aiTable, records, activeView, sortKeysMap) {
443
465
  }
444
466
  return shallowRows;
445
467
  }
468
+ function compareByGroups(aiTable, record1, record2, groups) {
469
+ return groups.reduce((result, groupField) => {
470
+ if (result !== 0)
471
+ return result;
472
+ const field = aiTable.fieldsMap()[groupField.field_id];
473
+ if (!field)
474
+ return 0;
475
+ const value1 = AITableQueries.getFieldValue(aiTable, [record1._id, field._id]);
476
+ const value2 = AITableQueries.getFieldValue(aiTable, [record2._id, field._id]);
477
+ const fieldModel = FieldModelMap[field.type];
478
+ if (!fieldModel)
479
+ return 0;
480
+ const compareResult = fieldModel.compare(value1, value2, aiTable.context.references(), undefined, {
481
+ aiTable,
482
+ field
483
+ });
484
+ return compareResult * (groupField.direction === SortDirection.ascending ? 1 : -1);
485
+ }, 0);
486
+ }
487
+ function compareBySorts(record1, record2, sorts, aiTable, sortKeysMap) {
488
+ return sorts.reduce((acc, rule) => {
489
+ const field = aiTable.fieldsMap()[rule.sort_by];
490
+ if (!field || acc !== 0) {
491
+ return acc;
492
+ }
493
+ const fieldMethod = FieldModelMap[field.type];
494
+ const sortKey = sortKeysMap?.[field.type];
495
+ const cellValue1 = AITableQueries.getFieldValue(aiTable, [record1._id, field._id]);
496
+ const cellValue2 = AITableQueries.getFieldValue(aiTable, [record2._id, field._id]);
497
+ const references = aiTable.context.references();
498
+ const res = fieldMethod.compare(cellValue1, cellValue2, references, sortKey, {
499
+ aiTable,
500
+ field
501
+ });
502
+ return res * rule.direction;
503
+ }, 0);
504
+ }
505
+ function compareByPosition(record1, record2, activeView) {
506
+ const hasPosition1 = record1.positions && record1.positions[activeView._id] !== undefined;
507
+ const hasPosition2 = record2.positions && record2.positions[activeView._id] !== undefined;
508
+ if (hasPosition1 && hasPosition2) {
509
+ return record1.positions[activeView._id] - record2.positions[activeView._id];
510
+ }
511
+ // 如果只有一个有位置信息,有位置的排在前面
512
+ if (hasPosition1 && !hasPosition2) {
513
+ return -1;
514
+ }
515
+ if (!hasPosition1 && hasPosition2) {
516
+ return 1;
517
+ }
518
+ // 如果都没有位置信息,保持原有顺序
519
+ return 0;
520
+ }
446
521
 
447
522
  function getFilteredRecords(aiTable, records, fields, activeView) {
448
523
  const { conditions, condition_logical } = activeView.settings || {};
@@ -591,6 +666,283 @@ function buildFieldStatType(data, activeView) {
591
666
  });
592
667
  }
593
668
 
669
+ class GroupCalculator {
670
+ constructor(aiTable, groups, collapseState) {
671
+ this.aiTable = aiTable;
672
+ this.groups = groups;
673
+ this.groupBreakpoints = new Map();
674
+ this.groupCollapseState = new Set(collapseState || []);
675
+ this.fieldsMap = this.aiTable.fieldsMap();
676
+ }
677
+ calculateLinearRows(records) {
678
+ this.detectGroupBreakpoints(records);
679
+ return this.generateLinearRows(records);
680
+ }
681
+ // 检测断点
682
+ detectGroupBreakpoints(records) {
683
+ this.groupBreakpoints.clear();
684
+ if (records.length === 0)
685
+ return;
686
+ let previousRecord = null;
687
+ records.forEach((record, index) => {
688
+ if (previousRecord === null) {
689
+ // 第一条记录,所有分组字段都是断点
690
+ this.groups.forEach((groupField) => {
691
+ this.addBreakpoint(groupField.field_id, index);
692
+ });
693
+ }
694
+ else {
695
+ // 检查每个分组字段是否发生变化
696
+ this.groups.forEach((groupField, groupIndex) => {
697
+ const field = this.fieldsMap[groupField.field_id];
698
+ if (!field)
699
+ return;
700
+ const prevValue = AITableQueries.getFieldValue(this.aiTable, [previousRecord._id, field._id]);
701
+ const currValue = AITableQueries.getFieldValue(this.aiTable, [record._id, field._id]);
702
+ const fieldModel = FieldModelMap[field.type];
703
+ if (!fieldModel)
704
+ return;
705
+ const compareResult = fieldModel.compare(prevValue, currValue, this.aiTable.context.references(), undefined, {
706
+ aiTable: this.aiTable,
707
+ field
708
+ });
709
+ if (compareResult !== 0) {
710
+ // 值发生变化,从当前层级开始的所有层级都是断点
711
+ for (let i = groupIndex; i < this.groups.length; i++) {
712
+ this.addBreakpoint(this.groups[i].field_id, index);
713
+ }
714
+ return;
715
+ }
716
+ });
717
+ }
718
+ previousRecord = record;
719
+ });
720
+ }
721
+ // 添加断点
722
+ addBreakpoint(fieldId, recordIndex) {
723
+ if (!this.groupBreakpoints.has(fieldId)) {
724
+ this.groupBreakpoints.set(fieldId, []);
725
+ }
726
+ const breakpoints = this.groupBreakpoints.get(fieldId);
727
+ if (!breakpoints.includes(recordIndex)) {
728
+ breakpoints.push(recordIndex);
729
+ }
730
+ }
731
+ // 生成GroupLinearRows
732
+ generateLinearRows(records) {
733
+ const linearRows = [];
734
+ let lastGroupDepth = -1;
735
+ let currentGroupRecords = [];
736
+ let currentGroupIds = [];
737
+ let currentGroupRecordIndices = []; // 记录当前分组中每个记录的原始索引
738
+ // 开始添加一个空白行
739
+ linearRows.push({
740
+ type: AITableRowType.blank,
741
+ _id: nanoid(),
742
+ depth: 0
743
+ });
744
+ records.forEach((record, index) => {
745
+ // 生成分组标签
746
+ const groupTabRows = this.generateGroupTabRows(record, index, records.length);
747
+ if (groupTabRows.length > 0) {
748
+ // 如果有新的分组标签,先处理上一个分组的结束
749
+ if (currentGroupRecords.length > 0) {
750
+ this.handleGroupEnd(currentGroupRecords, linearRows, currentGroupIds, currentGroupRecordIndices);
751
+ currentGroupRecords = [];
752
+ currentGroupRecordIndices = [];
753
+ }
754
+ currentGroupIds = groupTabRows.map((row) => row.groupId);
755
+ const depths = groupTabRows.filter((d) => d.depth !== undefined).map((d) => d.depth);
756
+ const minDepth = depths.length > 0 ? Math.min(...depths) : 0;
757
+ // 如果当前分组的最小深度小于等于上一个分组的深度,说明是同级或上级分组,需添加空白行
758
+ if (lastGroupDepth >= 0 && minDepth <= lastGroupDepth) {
759
+ linearRows.push({
760
+ type: AITableRowType.blank,
761
+ _id: nanoid(),
762
+ depth: minDepth
763
+ });
764
+ }
765
+ // 只添加未被父级折叠的分组
766
+ const visibleGroupTabRows = this.filterVisibleGroupTabs(groupTabRows);
767
+ linearRows.push(...visibleGroupTabRows);
768
+ lastGroupDepth = depths.length > 0 ? Math.max(...depths) : 0;
769
+ }
770
+ // 将记录添加到当前分组
771
+ currentGroupRecords.push(record);
772
+ currentGroupRecordIndices.push(index);
773
+ });
774
+ // 处理最后一个分组
775
+ if (currentGroupRecords.length > 0) {
776
+ this.handleGroupEnd(currentGroupRecords, linearRows, currentGroupIds, currentGroupRecordIndices);
777
+ }
778
+ // 添加分组结束的空白行
779
+ if (lastGroupDepth >= 0) {
780
+ linearRows.push({
781
+ type: AITableRowType.blank,
782
+ _id: nanoid(),
783
+ depth: 0
784
+ });
785
+ }
786
+ return linearRows;
787
+ }
788
+ handleGroupEnd(currentGroupRecords, linearRows, currentGroupIds, currentGroupRecordIndices) {
789
+ // 分组结束时添加该分组的记录和add行
790
+ let groupDisplayRowIndex = 0;
791
+ currentGroupRecords.forEach((record, i) => {
792
+ const recordIndex = currentGroupRecordIndices?.[i] ?? 0;
793
+ if (this.shouldShowRecord(recordIndex)) {
794
+ groupDisplayRowIndex++;
795
+ linearRows.push({
796
+ type: AITableRowType.record,
797
+ _id: record._id,
798
+ displayIndex: groupDisplayRowIndex,
799
+ depth: this.groups.length
800
+ });
801
+ }
802
+ });
803
+ // 分组未折叠,为每个分组添加add新增行
804
+ if (currentGroupRecords.length > 0 && this.shouldShowAddRow(currentGroupIds)) {
805
+ let startRecordIndex = 0;
806
+ let endRecordIndex = 0;
807
+ if (currentGroupRecordIndices) {
808
+ // 当前添加按钮对于的记录范围
809
+ startRecordIndex = Math.min(...currentGroupRecordIndices);
810
+ endRecordIndex = Math.max(...currentGroupRecordIndices);
811
+ }
812
+ linearRows.push({
813
+ type: AITableRowType.add,
814
+ _id: nanoid(),
815
+ depth: this.groups.length,
816
+ range: [startRecordIndex, endRecordIndex]
817
+ });
818
+ }
819
+ }
820
+ // 生成分组标签
821
+ generateGroupTabRows(record, recordIndex, totalRecords) {
822
+ const groupTabRows = [];
823
+ this.groups.forEach((groupField, depth) => {
824
+ const breakpoints = this.groupBreakpoints.get(groupField.field_id) || [];
825
+ if (breakpoints.includes(recordIndex)) {
826
+ const field = this.fieldsMap[groupField.field_id];
827
+ if (!field)
828
+ return;
829
+ const groupValue = AITableQueries.getFieldValue(this.aiTable, [record._id, field._id]);
830
+ const breakpointIndex = breakpoints.indexOf(recordIndex);
831
+ const groupId = this.generateGroupId(groupField.field_id, depth, breakpointIndex);
832
+ const recordRange = this.calculateGroupRecordRange(groupField.field_id, breakpointIndex, totalRecords);
833
+ groupTabRows.push({
834
+ type: AITableRowType.group,
835
+ _id: nanoid(),
836
+ depth,
837
+ fieldId: groupField.field_id,
838
+ groupValue,
839
+ isCollapsed: this.groupCollapseState.has(groupId),
840
+ range: recordRange,
841
+ groupId
842
+ });
843
+ }
844
+ });
845
+ return groupTabRows;
846
+ }
847
+ calculateGroupRecordRange(fieldId, breakpointIndex, totalRecords) {
848
+ const breakpoints = this.groupBreakpoints.get(fieldId) || [];
849
+ const startIndex = breakpoints[breakpointIndex] || 0;
850
+ let endIndex;
851
+ if (breakpointIndex + 1 < breakpoints.length) {
852
+ // 如果不是最后一个分组,结束位置是下一个断点的前一个位置
853
+ endIndex = breakpoints[breakpointIndex + 1] - 1;
854
+ }
855
+ else {
856
+ // 如果是最后一个分组,结束位置是最后一条记录
857
+ endIndex = totalRecords - 1;
858
+ }
859
+ return [startIndex, endIndex];
860
+ }
861
+ // 生成分组ID
862
+ generateGroupId(fieldId, depth, breakpointIndex) {
863
+ // 通过字段ID、深度和断点索引确保唯一
864
+ return `${fieldId}_${depth}_${breakpointIndex}`;
865
+ }
866
+ shouldShowRecord(recordIndex) {
867
+ for (let depth = 0; depth < this.groups.length; depth++) {
868
+ const groupField = this.groups[depth];
869
+ const breakpoints = this.groupBreakpoints.get(groupField.field_id) || [];
870
+ // 找到当前记录所属的分组断点
871
+ let belongsToBreakpointIndex = -1;
872
+ for (let i = breakpoints.length - 1; i >= 0; i--) {
873
+ if (breakpoints[i] <= recordIndex) {
874
+ belongsToBreakpointIndex = i;
875
+ break;
876
+ }
877
+ }
878
+ if (belongsToBreakpointIndex >= 0) {
879
+ const groupId = this.generateGroupId(groupField.field_id, depth, belongsToBreakpointIndex);
880
+ if (this.groupCollapseState.has(groupId)) {
881
+ return false; // 父级分组被折叠,不显示记录
882
+ }
883
+ }
884
+ }
885
+ return true;
886
+ }
887
+ // 检查当前分组是否应该显示添加行
888
+ shouldShowAddRow(currentGroupIds) {
889
+ if (!currentGroupIds || currentGroupIds.length === 0) {
890
+ return true; // 默认显示
891
+ }
892
+ // 检查当前组的所有分组层级是否都展开
893
+ for (const groupId of currentGroupIds) {
894
+ if (this.groupCollapseState.has(groupId)) {
895
+ return false; // 有层级被折叠,不显示添加行
896
+ }
897
+ }
898
+ return true;
899
+ }
900
+ // 过滤可见的分组标签
901
+ filterVisibleGroupTabs(groupTabRows) {
902
+ const visibleRows = [];
903
+ for (let i = 0; i < groupTabRows.length; i++) {
904
+ const currentRow = groupTabRows[i];
905
+ let show = true;
906
+ // 检查当前分组标签的所有父级是否都展开
907
+ const currentDepth = currentRow.depth ?? 0;
908
+ for (let parentDepth = 0; parentDepth < currentDepth; parentDepth++) {
909
+ // 找到同一记录索引下的父级分组ID
910
+ const parentRow = groupTabRows.find((row) => row.depth === parentDepth);
911
+ if (parentRow && this.groupCollapseState.has(parentRow.groupId)) {
912
+ show = false;
913
+ break;
914
+ }
915
+ }
916
+ if (show) {
917
+ visibleRows.push(currentRow);
918
+ }
919
+ }
920
+ return visibleRows;
921
+ }
922
+ }
923
+
924
+ function getParentLinearRowGroups(aiTable, targetId) {
925
+ const linearRows = aiTable.context.linearRows();
926
+ const targetIndex = aiTable.context.visibleRowsIndexMap().get(targetId);
927
+ if (targetIndex === -1) {
928
+ return [];
929
+ }
930
+ const targetRow = linearRows[targetIndex];
931
+ const parentGroups = [];
932
+ let parentDepthPointer = targetRow.depth - 1;
933
+ for (let i = targetIndex - 1; i >= 0 && parentDepthPointer >= 0; i--) {
934
+ const row = linearRows[i];
935
+ if (row.type === AITableRowType.group) {
936
+ const rowDepth = row.depth || 0;
937
+ if (rowDepth <= parentDepthPointer) {
938
+ parentGroups.push(row);
939
+ parentDepthPointer--;
940
+ }
941
+ }
942
+ }
943
+ return parentGroups;
944
+ }
945
+
594
946
  function buildRecordsByView(aiTable, records, fields, activeView, sortKeysMap) {
595
947
  const filteredRecords = getFilteredRecords(aiTable, records, fields, activeView);
596
948
  return getSortRecords(aiTable, filteredRecords, activeView, sortKeysMap);
@@ -599,6 +951,20 @@ function buildFieldsByView(aiTable, fields, activeView) {
599
951
  const sortFields = getSortFields(aiTable, fields, activeView);
600
952
  return buildFieldStatType(sortFields, activeView);
601
953
  }
954
+ function buildGroupLinearRows(aiTable, activeView, records) {
955
+ if (aiTable && activeView?.settings?.groups?.length) {
956
+ try {
957
+ const groups = activeView.settings?.groups;
958
+ const collapsedGroupIds = activeView.settings?.collapsed_group_ids;
959
+ const calculator = new GroupCalculator(aiTable, groups, collapsedGroupIds);
960
+ return calculator.calculateLinearRows(records);
961
+ }
962
+ catch (error) {
963
+ console.warn('Grouped build failed, using the default build method:', error);
964
+ }
965
+ }
966
+ return null;
967
+ }
602
968
 
603
969
  function setView(aiTable, value, path) {
604
970
  const view = aiTable.views().find((item) => item._id === path[0]);
@@ -1374,12 +1740,7 @@ function addRecord(aiTable, record) {
1374
1740
  }
1375
1741
  function addRecords$1(aiTable, records, options) {
1376
1742
  const invalidFieldValues = [];
1377
- const sortRecords = options?.sortRecords ||
1378
- getSortRecords(aiTable, aiTable.records(), aiTable.views().find((item) => item._id === aiTable.activeViewId()));
1379
- const targetIndex = options?.targetId
1380
- ? sortRecords.findIndex((item) => item._id === options.targetId)
1381
- : options?.targetIndex || sortRecords.length - 1;
1382
- const positions = createMultiplePositions(aiTable.views(), aiTable.activeViewId(), sortRecords, targetIndex, records.length, options?.isInsertBefore);
1743
+ const positions = getNewRecordsPosition(aiTable, options);
1383
1744
  records.forEach((record, index) => {
1384
1745
  Object.entries(record.values).every(([fieldId, value]) => {
1385
1746
  const field = AITableQueries.getField(aiTable, [fieldId]);
@@ -1419,12 +1780,123 @@ const RecordActions = {
1419
1780
  updateSystemFieldValues
1420
1781
  };
1421
1782
 
1783
+ function setViewGroup(aiTable, groups) {
1784
+ const viewId = aiTable.activeViewId();
1785
+ const view = aiTable.views().find((v) => v._id === viewId);
1786
+ if (!view)
1787
+ return;
1788
+ const currentSettings = view.settings || {};
1789
+ const newSettings = {
1790
+ ...currentSettings,
1791
+ groups: groups || [],
1792
+ collapsed_group_ids: [] // 重置折叠
1793
+ };
1794
+ const operation = {
1795
+ type: ActionName.SetView,
1796
+ properties: { settings: currentSettings },
1797
+ newProperties: { settings: newSettings },
1798
+ path: [viewId]
1799
+ };
1800
+ aiTable.apply(operation);
1801
+ }
1802
+ function setCollapsedGroup(aiTable, collapsedGroupIds) {
1803
+ const viewId = aiTable.activeViewId();
1804
+ const view = aiTable.views().find((v) => v._id === viewId);
1805
+ if (!view)
1806
+ return;
1807
+ const currentSettings = view.settings || {};
1808
+ const newSettings = {
1809
+ ...currentSettings,
1810
+ collapsed_group_ids: collapsedGroupIds
1811
+ };
1812
+ const operation = {
1813
+ type: ActionName.SetView,
1814
+ properties: { settings: currentSettings },
1815
+ newProperties: { settings: newSettings },
1816
+ path: [viewId]
1817
+ };
1818
+ aiTable.apply(operation);
1819
+ }
1820
+ // 折叠
1821
+ function toggleGroupCollapse(aiTable, groupId) {
1822
+ const activeView = aiTable.viewsMap()[aiTable.activeViewId()];
1823
+ if (!activeView)
1824
+ return;
1825
+ const currentCollapse = activeView.settings?.collapsed_group_ids || [];
1826
+ const newCollapse = currentCollapse.includes(groupId) ? currentCollapse.filter((id) => id !== groupId) : [...currentCollapse, groupId];
1827
+ setCollapsedGroup(aiTable, newCollapse);
1828
+ }
1829
+ // 添加分组
1830
+ function addGroupField(aiTable, fieldId, direction = SortDirection.ascending) {
1831
+ const activeView = aiTable.viewsMap()[aiTable.activeViewId()];
1832
+ if (!activeView)
1833
+ return;
1834
+ const currentGroups = activeView.settings?.groups || [];
1835
+ // 是否已存在
1836
+ if (currentGroups.some((group) => group.field_id === fieldId)) {
1837
+ throw new Error('The field has been used for grouping.');
1838
+ }
1839
+ // 层级限制
1840
+ if (currentGroups.length >= AI_TABLE_GROUP_MAX_LEVEL) {
1841
+ throw new Error(`The maximum number of groups is ${AI_TABLE_GROUP_MAX_LEVEL}.`);
1842
+ }
1843
+ const newGroups = [...currentGroups, { field_id: fieldId, direction }];
1844
+ setViewGroup(aiTable, newGroups);
1845
+ }
1846
+ // 删除分组
1847
+ function removeGroupField(aiTable, fieldId) {
1848
+ const view = aiTable.views().find((v) => v._id === aiTable.activeViewId());
1849
+ if (!view)
1850
+ return;
1851
+ const currentGroups = view.settings?.groups || [];
1852
+ const newGroups = currentGroups.filter((group) => group.field_id !== fieldId);
1853
+ setViewGroup(aiTable, newGroups.length > 0 ? newGroups : null);
1854
+ }
1855
+ // 更新排序方向
1856
+ function updateGroupFieldDirection(aiTable, fieldId, direction) {
1857
+ const view = aiTable.views().find((v) => v._id === aiTable.activeViewId());
1858
+ if (!view)
1859
+ return;
1860
+ const currentGroups = view.settings?.groups || [];
1861
+ const newGroups = currentGroups.map((group) => (group.field_id === fieldId ? { ...group, direction } : group));
1862
+ setViewGroup(aiTable, newGroups);
1863
+ }
1864
+ // 重新排序,拖拽顺序
1865
+ function reorderGroupFields(aiTable, fromIndex, toIndex) {
1866
+ const view = aiTable.views().find((v) => v._id === aiTable.activeViewId());
1867
+ if (!view)
1868
+ return;
1869
+ const currentGroups = view.settings?.groups || [];
1870
+ if (fromIndex < 0 || fromIndex >= currentGroups.length || toIndex < 0 || toIndex >= currentGroups.length) {
1871
+ return;
1872
+ }
1873
+ const newGroups = [...currentGroups];
1874
+ const [movedItem] = newGroups.splice(fromIndex, 1);
1875
+ newGroups.splice(toIndex, 0, movedItem);
1876
+ setViewGroup(aiTable, newGroups);
1877
+ }
1878
+ // 清空分组
1879
+ function clearAllGroups(aiTable) {
1880
+ setViewGroup(aiTable, null);
1881
+ }
1882
+ const GroupActions = {
1883
+ setViewGroup,
1884
+ setCollapsedGroup,
1885
+ toggleGroupCollapse,
1886
+ addGroupField,
1887
+ removeGroupField,
1888
+ updateGroupFieldDirection,
1889
+ reorderGroupFields,
1890
+ clearAllGroups
1891
+ };
1892
+
1422
1893
  const Actions = {
1423
1894
  ...GeneralActions,
1424
1895
  ...RecordActions,
1425
1896
  ...FieldActions,
1426
1897
  ...ViewActions,
1427
- ...PositionsActions
1898
+ ...PositionsActions,
1899
+ ...GroupActions
1428
1900
  };
1429
1901
 
1430
1902
  function moveFields(aiTable, options) {
@@ -1432,35 +1904,131 @@ function moveFields(aiTable, options) {
1432
1904
  Actions.moveField(aiTable, path, newPath);
1433
1905
  }
1434
1906
 
1435
- function moveRecords(aiTable, options, updatedInfo) {
1436
- const gridRecords = aiTable.gridData().records;
1907
+ function findNextRecordForTargetInOriginalRecords(aiTable, targetRecordId) {
1908
+ const viewId = aiTable.activeViewId();
1909
+ const records = aiTable.records();
1910
+ const recordsMap = aiTable.recordsMap();
1911
+ const targetRecord = recordsMap[targetRecordId];
1912
+ const targetPosition = targetRecord.positions[viewId] || 0;
1913
+ let nextRecord = null;
1914
+ for (const record of records) {
1915
+ const pos = record.positions[viewId] || 0;
1916
+ // 找到所有position大于目标position的记录中最小的
1917
+ if (pos > targetPosition && (nextRecord === null || pos < nextRecord.positions[viewId])) {
1918
+ nextRecord = record;
1919
+ }
1920
+ }
1921
+ return nextRecord;
1922
+ }
1923
+ function findPrevRecordForTargetInOriginalRecords(aiTable, targetRecordId) {
1924
+ const viewId = aiTable.activeViewId();
1925
+ const records = aiTable.records();
1926
+ const recordsMap = aiTable.recordsMap();
1927
+ const targetRecord = recordsMap[targetRecordId];
1928
+ const targetPosition = targetRecord.positions[viewId] || 0;
1929
+ let prevRecord = null;
1930
+ for (const record of records) {
1931
+ const pos = record.positions[viewId] || 0;
1932
+ // 找到所有position小于目标记录position的记录中最大的
1933
+ if (pos < targetPosition && (prevRecord === null || pos > prevRecord.positions[viewId])) {
1934
+ prevRecord = record;
1935
+ }
1936
+ }
1937
+ return prevRecord;
1938
+ }
1939
+ function getPositionByAfterOrBeforeRecordId(aiTable, options) {
1940
+ const recordsMap = aiTable.recordsMap();
1437
1941
  const activeViewId = aiTable.activeViewId();
1438
- const activeView = aiTable.views().find((view) => view._id === activeViewId);
1439
- const { recordIds, newPath } = options;
1942
+ const originalRecords = aiTable.records();
1943
+ const { afterRecordId, beforeRecordId } = options;
1440
1944
  let targetPosition = 0;
1441
1945
  let prevPosition = 0;
1442
- if (newPath[0] === 0) {
1443
- targetPosition = gridRecords[0].positions[activeViewId];
1444
- prevPosition = targetPosition - 1;
1946
+ if (afterRecordId) {
1947
+ // 移动到指定记录之后
1948
+ const targetRecord = recordsMap[afterRecordId];
1949
+ if (!targetRecord) {
1950
+ throw new Error(`Target record with id ${afterRecordId} not found`);
1951
+ }
1952
+ prevPosition = targetRecord.positions[activeViewId] || 0;
1953
+ const nextPosition = findNextRecordForTargetInOriginalRecords(aiTable, afterRecordId);
1954
+ if (nextPosition !== null) {
1955
+ targetPosition = nextPosition.positions[activeViewId] || 0;
1956
+ }
1957
+ else {
1958
+ // 最后一个
1959
+ targetPosition = getMaxPosition(originalRecords, activeViewId) + 1;
1960
+ }
1445
1961
  }
1446
- else if (newPath[0] >= gridRecords.length) {
1447
- targetPosition = getMaxPosition(gridRecords, activeViewId) + 1;
1448
- prevPosition = gridRecords[gridRecords.length - 1].positions[activeViewId];
1962
+ else if (beforeRecordId) {
1963
+ // 移动到指定记录之前
1964
+ const targetRecord = recordsMap[beforeRecordId];
1965
+ if (!targetRecord) {
1966
+ throw new Error(`Target record with id ${beforeRecordId} not found`);
1967
+ }
1968
+ targetPosition = targetRecord.positions[activeViewId] || 0;
1969
+ const previousPosition = findPrevRecordForTargetInOriginalRecords(aiTable, beforeRecordId);
1970
+ if (previousPosition !== null) {
1971
+ prevPosition = previousPosition.positions[activeViewId] || 0;
1972
+ }
1973
+ else {
1974
+ // 第一个
1975
+ prevPosition = targetPosition - 1;
1976
+ }
1449
1977
  }
1450
1978
  else {
1451
- targetPosition = gridRecords[newPath[0]].positions[activeViewId];
1452
- prevPosition = gridRecords[newPath[0] - 1].positions[activeViewId];
1979
+ throw new Error('Either afterRecordId or beforeRecordId must be provided');
1453
1980
  }
1454
- const records = aiTable.records();
1455
- const recordsIndexMap = new Map(records.map((row, index) => [row._id, index]));
1981
+ return {
1982
+ targetPosition,
1983
+ prevPosition
1984
+ };
1985
+ }
1986
+ function getNewRecordsPosition(aiTable, options) {
1987
+ options = options || {};
1988
+ if (!options.afterRecordId && !options.beforeRecordId) {
1989
+ options.afterRecordId = aiTable.gridData().records[aiTable.gridData().records.length - 1]._id;
1990
+ }
1991
+ options.count = options.count || 1;
1992
+ const { targetPosition, prevPosition } = getPositionByAfterOrBeforeRecordId(aiTable, options);
1993
+ const interval = (targetPosition - prevPosition) / ((options.count || 1) + 1);
1994
+ const positionsOfItems = ___default.range(prevPosition + interval, targetPosition, interval);
1995
+ const views = aiTable.views();
1996
+ const activeViewId = aiTable.activeViewId();
1997
+ const viewsMaxPosition = {};
1998
+ views.forEach((view) => {
1999
+ viewsMaxPosition[view._id] = getMaxPosition(aiTable.records(), view._id);
2000
+ });
2001
+ const viewPositions = positionsOfItems.map((itemPositions) => {
2002
+ const viewPositions = {};
2003
+ views.forEach((view) => {
2004
+ if (view._id === activeViewId) {
2005
+ viewPositions[view._id] = itemPositions;
2006
+ }
2007
+ else {
2008
+ viewsMaxPosition[view._id] += 1;
2009
+ viewPositions[view._id] = viewsMaxPosition[view._id];
2010
+ }
2011
+ });
2012
+ return viewPositions;
2013
+ });
2014
+ return viewPositions;
2015
+ }
2016
+
2017
+ function moveRecords(aiTable, options, updatedInfo) {
2018
+ const activeViewId = aiTable.activeViewId();
2019
+ const activeView = aiTable.views().find((view) => view._id === activeViewId);
2020
+ const { recordIds, afterRecordId, beforeRecordId } = options;
2021
+ const originalRecords = aiTable.records();
2022
+ const recordsIndexMap = new Map(originalRecords.map((row, index) => [row._id, index]));
1456
2023
  const sourceRecords = [];
1457
- recordIds.forEach((idPath) => {
1458
- const index = recordsIndexMap.get(idPath[0]);
2024
+ recordIds.forEach((id) => {
2025
+ const index = recordsIndexMap.get(id);
1459
2026
  if (index === undefined) {
1460
- throw new Error(`Record with id ${idPath[0]} not found`);
2027
+ throw new Error(`Record with id ${id} not found`);
1461
2028
  }
1462
- sourceRecords.push(records[index]);
2029
+ sourceRecords.push(originalRecords[index]);
1463
2030
  });
2031
+ let { targetPosition, prevPosition } = getPositionByAfterOrBeforeRecordId(aiTable, { afterRecordId, beforeRecordId });
1464
2032
  // 勾选多行顺序可能不一致,需要排序
1465
2033
  const sortedSourceRecords = sortByViewPosition(sourceRecords, activeView);
1466
2034
  let nextPosition = (prevPosition + targetPosition) / 2;
@@ -1510,6 +2078,10 @@ const buildRemoveFieldItem = (aiTable, getUpdatedInfo) => {
1510
2078
 
1511
2079
  function addRecords(aiTable, trackableEntity, options) {
1512
2080
  options = options || {};
2081
+ const newRecords = [];
2082
+ const activeViewId = aiTable.activeViewId();
2083
+ const activeView = aiTable.viewsMap()[activeViewId];
2084
+ const groups = activeView.settings?.groups;
1513
2085
  let { originId, isDuplicate, count = 1 } = options;
1514
2086
  const recordCount = aiTable.records().length;
1515
2087
  const maxRecordCount = aiTable.context?.maxRecords();
@@ -1517,14 +2089,35 @@ function addRecords(aiTable, trackableEntity, options) {
1517
2089
  count = maxRecordCount - recordCount;
1518
2090
  options.count = count;
1519
2091
  }
1520
- const activeView = aiTable.viewsMap()[aiTable.activeViewId()];
1521
2092
  const newRecordIds = idsCreator(count);
1522
2093
  const newRecordShortIds = shortIdsCreator(count);
1523
2094
  const newRecordValues = getDefaultRecordValues(aiTable, isDuplicate, originId);
1524
- const newRecords = [];
1525
2095
  const hiddenRecordIds = [];
2096
+ let needCopyGroupValuesMap = null;
2097
+ if (groups?.length && options.forGroupId) {
2098
+ const parentGroups = getParentLinearRowGroups(aiTable, options.forGroupId);
2099
+ needCopyGroupValuesMap = parentGroups.reduce((pre, cur) => {
2100
+ pre[cur.fieldId] = cur.groupValue;
2101
+ return pre;
2102
+ }, {});
2103
+ }
2104
+ const records = aiTable.gridData().records;
1526
2105
  newRecordIds.forEach((id, index) => {
1527
- const record = { _id: id, short_id: newRecordShortIds[index], values: newRecordValues, ...trackableEntity };
2106
+ const record = {
2107
+ _id: id,
2108
+ short_id: newRecordShortIds[index],
2109
+ values: newRecordValues,
2110
+ ...trackableEntity,
2111
+ positions: {
2112
+ [activeViewId]: getMaxPosition(records, activeViewId) + 1
2113
+ }
2114
+ };
2115
+ if (needCopyGroupValuesMap) {
2116
+ groups?.forEach((group) => {
2117
+ // 复制分组字段值
2118
+ record.values[group.field_id] = needCopyGroupValuesMap[group.field_id];
2119
+ });
2120
+ }
1528
2121
  const checkResult = checkConditions(aiTable, aiTable.fields(), record);
1529
2122
  if (!checkResult) {
1530
2123
  hiddenRecordIds.push(id);
@@ -1537,9 +2130,10 @@ function addRecords(aiTable, trackableEntity, options) {
1537
2130
  });
1538
2131
  }
1539
2132
  Actions.addRecords(aiTable, newRecords, options);
1540
- const recentAddRecord = options.isInsertBefore ? newRecords[newRecords.length - 1] : newRecords[0];
2133
+ const recentAddRecord = options.beforeRecordId ? newRecords[newRecords.length - 1] : newRecords[0];
1541
2134
  const activeRecordId = recentAddRecord._id;
1542
2135
  const activeFieldId = aiTable.gridData().fields[0]._id;
2136
+ closeExpendCell(aiTable);
1543
2137
  setSelection(aiTable, {
1544
2138
  selectedRecords: new Set([]),
1545
2139
  selectedFields: new Set([]),
@@ -1761,11 +2355,16 @@ const InsertUpwardRecords = (aiTable, actions) => {
1761
2355
  count: 1,
1762
2356
  exec: (aiTable, targetName, position, notifyService, count) => {
1763
2357
  let selectedRecordIds = AITable.getActiveRecordIds(aiTable);
1764
- actions.addRecord({
1765
- targetId: selectedRecordIds[0],
1766
- count,
1767
- isInsertBefore: true
1768
- });
2358
+ const aiViewTable = aiTable;
2359
+ const activeView = aiViewTable.viewsMap()[aiViewTable.activeViewId()];
2360
+ const addRecordOptions = {
2361
+ beforeRecordId: selectedRecordIds[0],
2362
+ count
2363
+ };
2364
+ if (activeView?.settings?.groups?.length) {
2365
+ addRecordOptions.forGroupId = selectedRecordIds[0];
2366
+ }
2367
+ actions.addRecord(addRecordOptions);
1769
2368
  }
1770
2369
  };
1771
2370
  };
@@ -1779,10 +2378,16 @@ const InsertDownwardRecords = (aiTable, actions) => {
1779
2378
  isInputNumber: true,
1780
2379
  exec: (aiTable, targetName, position, notifyService, count) => {
1781
2380
  let selectedRecordIds = AITable.getActiveRecordIds(aiTable);
1782
- actions.addRecord({
1783
- targetId: selectedRecordIds[0],
2381
+ const aiViewTable = aiTable;
2382
+ const activeView = aiViewTable.viewsMap()[aiViewTable.activeViewId()];
2383
+ const addRecordOptions = {
2384
+ afterRecordId: selectedRecordIds[0],
1784
2385
  count
1785
- });
2386
+ };
2387
+ if (activeView?.settings?.groups?.length) {
2388
+ addRecordOptions.forGroupId = selectedRecordIds[0];
2389
+ }
2390
+ actions.addRecord(addRecordOptions);
1786
2391
  }
1787
2392
  };
1788
2393
  };
@@ -1833,5 +2438,5 @@ const VIEW_ACTIONS = [ActionName.SetView, ActionName.AddView, ActionName.RemoveV
1833
2438
  * Generated bundle index. Do not edit.
1834
2439
  */
1835
2440
 
1836
- export { AITableStateI18nKey, AITableStateI18nText, Actions, CopyCellsItem, CopyFieldPropertyItem, DividerMenuItem, EditFieldPropertyItem, FLUSHING, InsertDownwardRecords, InsertUpwardRecords, PasteCellsItem, RemoveRecordsItem, UndoManagerService, VIEW_ACTIONS, YjsAITable, actionMappers, addFields, addRecords, addView, applyActionOps, applyActions, applyEvents, applyYjsEvents, buildFieldsByView, buildRecordsByView, buildRemoveFieldItem, buildSetFieldAction, buildSetRecordPositionsActon, calculateAdaptiveFrozenColumnCount, checkConditions, createMultiplePositions, createPositions, createSharedType, doFilter, freezeToThisColumn, generateCopyName, getDataBySharedType, getDefaultI18nTextByKey, getDefaultRecordDataByFilter, getDefaultRecordValues, getFieldPositionInView, getFieldsSizeMap, getFilteredRecords, getFrozenFieldId, getMaxPosition, getPosition, getPositions, getRecordsBySharedJson, getSharedTypeByData, getSortFields, getSortRecords, getStateI18nTextByKey, isPathEqual, moveFields, moveRecords, removeView, restoreDefaultFrozenColumn, sortRecordsBySortInfo, sortViews, toSharedType, translateYjsEvent, updateFieldAndValues, updateFieldValues, withState };
2441
+ export { AITableStateI18nKey, AITableStateI18nText, Actions, CopyCellsItem, CopyFieldPropertyItem, DividerMenuItem, EditFieldPropertyItem, FLUSHING, GroupCalculator, InsertDownwardRecords, InsertUpwardRecords, PasteCellsItem, RemoveRecordsItem, UndoManagerService, VIEW_ACTIONS, YjsAITable, actionMappers, addFields, addRecords, addView, applyActionOps, applyActions, applyEvents, applyYjsEvents, buildFieldsByView, buildGroupLinearRows, buildRecordsByView, buildRemoveFieldItem, buildSetFieldAction, buildSetRecordPositionsActon, calculateAdaptiveFrozenColumnCount, checkConditions, createMultiplePositions, createPositions, createSharedType, doFilter, findNextRecordForTargetInOriginalRecords, findPrevRecordForTargetInOriginalRecords, freezeToThisColumn, generateCopyName, getDataBySharedType, getDefaultI18nTextByKey, getDefaultRecordDataByFilter, getDefaultRecordValues, getFieldPositionInView, getFieldsSizeMap, getFilteredRecords, getFrozenFieldId, getMaxPosition, getNewRecordsPosition, getParentLinearRowGroups, getPosition, getPositionByAfterOrBeforeRecordId, getPositions, getRecordsBySharedJson, getSharedTypeByData, getSortFields, getSortRecords, getStateI18nTextByKey, isPathEqual, moveFields, moveRecords, removeView, restoreDefaultFrozenColumn, sortRecordsBySortInfo, sortViews, toSharedType, translateYjsEvent, updateFieldAndValues, updateFieldValues, withState };
1837
2442
  //# sourceMappingURL=ai-table-state.mjs.map