@ceed/cds 1.8.7 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -45,11 +45,28 @@ export type GridCellClassNamePropType<R, V> = (params: {
45
45
  field: string;
46
46
  id: string;
47
47
  }) => string | undefined;
48
+ export type ColumnGroupingModel<T extends Record<PropertyKey, any>, ID = unknown> = {
49
+ groupId: string;
50
+ headerName?: string;
51
+ children: ColumnGroupingModel<T, ID> | ColumnDef<T, ID>[];
52
+ }[];
53
+ export type ProcessedColumnGroup = {
54
+ groupId: string;
55
+ headerName?: string;
56
+ colspan: number;
57
+ level: number;
58
+ startIndex: number;
59
+ };
60
+ export type FlattenedColumn<T extends Record<PropertyKey, any>, ID> = ColumnDef<T, ID> & {
61
+ groupPath: string[];
62
+ columnIndex: number;
63
+ };
48
64
  export type BaseColumnDef<T extends Record<PropertyKey, V>, V, ID> = {
49
65
  [K in keyof T]: {
50
66
  field: K;
51
67
  headerName?: string;
52
68
  headerRef?: React.RefObject<HTMLTableCellElement>;
69
+ tableColRef?: React.RefObject<HTMLTableColElement>;
53
70
  width?: string;
54
71
  minWidth?: string;
55
72
  maxWidth?: string;
@@ -136,6 +153,7 @@ export type DataTableProps<T extends Record<PropertyKey, any>, GetId extends ((r
136
153
  checkboxSelection?: boolean;
137
154
  columns: ColumnDef<T, InferredIdType<T, GetId>>[];
138
155
  pinnedColumns?: PinnedColumns;
156
+ columnGroupingModel?: ColumnGroupingModel<T, InferredIdType<T, GetId>>;
139
157
  editMode?: boolean;
140
158
  /**
141
159
  * 체크박스가 있는 경우, 체크박스를 클릭했을 때 선택된 row의 index를 지정한다.
package/dist/index.cjs CHANGED
@@ -2694,6 +2694,118 @@ function InfoSign(props) {
2694
2694
  var InfoSign_default = InfoSign;
2695
2695
 
2696
2696
  // src/components/DataTable/DataTable.tsx
2697
+ function extractFieldsFromGroupingModel(items) {
2698
+ const fields = /* @__PURE__ */ new Set();
2699
+ for (const item of items) {
2700
+ if ("groupId" in item) {
2701
+ const children = Array.isArray(item.children) ? item.children : [item.children];
2702
+ const childFields = extractFieldsFromGroupingModel(children);
2703
+ childFields.forEach((field) => fields.add(field));
2704
+ } else {
2705
+ fields.add(item.field);
2706
+ }
2707
+ }
2708
+ return fields;
2709
+ }
2710
+ function reorderColumnsByGroupingModel(columns, columnGroupingModel) {
2711
+ const fieldsInGroupingModel = extractFieldsFromGroupingModel(columnGroupingModel);
2712
+ const orderedFields = [];
2713
+ function collectFieldsInOrder(items) {
2714
+ for (const item of items) {
2715
+ if ("groupId" in item) {
2716
+ const children = Array.isArray(item.children) ? item.children : [item.children];
2717
+ collectFieldsInOrder(children);
2718
+ } else {
2719
+ if (!orderedFields.includes(item.field)) {
2720
+ orderedFields.push(item.field);
2721
+ }
2722
+ }
2723
+ }
2724
+ }
2725
+ collectFieldsInOrder(columnGroupingModel);
2726
+ const columnMap = new Map(columns.map((col) => [col.field, col]));
2727
+ const reorderedColumns = [];
2728
+ for (const field of orderedFields) {
2729
+ const column = columnMap.get(field);
2730
+ if (column) {
2731
+ reorderedColumns.push(column);
2732
+ }
2733
+ }
2734
+ for (const column of columns) {
2735
+ if (!fieldsInGroupingModel.has(column.field)) {
2736
+ reorderedColumns.push(column);
2737
+ }
2738
+ }
2739
+ return reorderedColumns;
2740
+ }
2741
+ function flattenColumnGroups(items, groupPath = [], columnIndex = { current: 0 }) {
2742
+ const result = [];
2743
+ for (const item of items) {
2744
+ if ("groupId" in item) {
2745
+ const newPath = [...groupPath, item.groupId];
2746
+ const children = Array.isArray(item.children) ? item.children : [item.children];
2747
+ result.push(...flattenColumnGroups(children, newPath, columnIndex));
2748
+ } else {
2749
+ result.push({
2750
+ ...item,
2751
+ groupPath,
2752
+ columnIndex: columnIndex.current++
2753
+ });
2754
+ }
2755
+ }
2756
+ return result;
2757
+ }
2758
+ function calculateColumnGroups(columnGroupingModel, columns) {
2759
+ const fieldsInGroupingModel = extractFieldsFromGroupingModel(columnGroupingModel);
2760
+ const flattenedColumns = flattenColumnGroups(columnGroupingModel);
2761
+ const columnIndexMap = new Map(flattenedColumns.map((col) => [col.field, col.columnIndex]));
2762
+ const processedGroups = /* @__PURE__ */ new Map();
2763
+ const groupsByLevel = [];
2764
+ let maxLevel = 0;
2765
+ function processGroup(items, level, parentGroupId) {
2766
+ let minIndex = Infinity;
2767
+ let maxIndex = -Infinity;
2768
+ for (const item of items) {
2769
+ if ("groupId" in item) {
2770
+ const groupKey = parentGroupId ? `${parentGroupId}.${item.groupId}` : item.groupId;
2771
+ if (!processedGroups.has(groupKey)) {
2772
+ const children = Array.isArray(item.children) ? item.children : [item.children];
2773
+ const { startIndex, colspan } = processGroup(children, level + 1, groupKey);
2774
+ const group = {
2775
+ groupId: item.groupId,
2776
+ headerName: item.headerName,
2777
+ colspan,
2778
+ level,
2779
+ startIndex
2780
+ };
2781
+ processedGroups.set(groupKey, group);
2782
+ if (!groupsByLevel[level]) {
2783
+ groupsByLevel[level] = [];
2784
+ }
2785
+ groupsByLevel[level].push(group);
2786
+ maxLevel = Math.max(maxLevel, level);
2787
+ minIndex = Math.min(minIndex, startIndex);
2788
+ maxIndex = Math.max(maxIndex, startIndex + colspan - 1);
2789
+ }
2790
+ } else {
2791
+ const columnIndex = columnIndexMap.get(item.field);
2792
+ if (columnIndex !== void 0) {
2793
+ minIndex = Math.min(minIndex, columnIndex);
2794
+ maxIndex = Math.max(maxIndex, columnIndex);
2795
+ }
2796
+ }
2797
+ }
2798
+ return {
2799
+ startIndex: minIndex === Infinity ? 0 : minIndex,
2800
+ colspan: maxIndex === -Infinity ? 0 : maxIndex - minIndex + 1
2801
+ };
2802
+ }
2803
+ processGroup(columnGroupingModel, 0);
2804
+ groupsByLevel.forEach((level) => {
2805
+ level.sort((a, b) => a.startIndex - b.startIndex);
2806
+ });
2807
+ return { groups: groupsByLevel, maxLevel, fieldsInGroupingModel };
2808
+ }
2697
2809
  function getTextAlign(props) {
2698
2810
  return !props.editMode && ["number", "date", "currency"].includes(props.type || "") ? "end" : "start";
2699
2811
  }
@@ -2764,7 +2876,7 @@ var OverlayWrapper = (0, import_joy33.styled)("tr", {
2764
2876
  }
2765
2877
  });
2766
2878
  var numberFormatter = (value) => "Intl" in window ? new Intl.NumberFormat().format(value) : value;
2767
- var Resizer = (ref) => /* @__PURE__ */ import_react25.default.createElement(
2879
+ var Resizer = (ref, targetRef = ref) => /* @__PURE__ */ import_react25.default.createElement(
2768
2880
  Box_default,
2769
2881
  {
2770
2882
  sx: {
@@ -2782,6 +2894,7 @@ var Resizer = (ref) => /* @__PURE__ */ import_react25.default.createElement(
2782
2894
  const onMouseMove = (e2) => {
2783
2895
  if (initialWidth && initialX) {
2784
2896
  ref.current.style.width = `${initialWidth + (e2.clientX - initialX)}px`;
2897
+ targetRef.current.style.width = `${initialWidth + (e2.clientX - initialX)}px`;
2785
2898
  }
2786
2899
  };
2787
2900
  const onMouseUp = () => {
@@ -2892,17 +3005,19 @@ var HeadCell = (props) => {
2892
3005
  isPinned,
2893
3006
  pinnedStartPosition,
2894
3007
  pinnedEndPosition,
2895
- headerRef
3008
+ headerRef,
3009
+ tableColRef
2896
3010
  } = props;
2897
3011
  const theme = (0, import_joy33.useTheme)();
2898
3012
  const ref = headerRef;
3013
+ const colRef = tableColRef;
2899
3014
  const [isHovered, setIsHovered] = (0, import_react25.useState)(false);
2900
3015
  const sortable = type === "actions" ? false : _sortable;
2901
3016
  const headId = (0, import_react25.useMemo)(
2902
3017
  () => `${tableId}_header_${headerName ?? field}`.trim(),
2903
3018
  [tableId, headerName, field]
2904
3019
  );
2905
- const resizer = (0, import_react25.useMemo)(() => resizable ?? true ? Resizer(ref) : null, [resizable, ref]);
3020
+ const resizer = (0, import_react25.useMemo)(() => resizable ?? true ? Resizer(ref, colRef) : null, [resizable, ref, colRef]);
2906
3021
  const style = (0, import_react25.useMemo)(
2907
3022
  () => ({
2908
3023
  width,
@@ -3237,8 +3352,15 @@ function useDataTableRenderer({
3237
3352
  editMode,
3238
3353
  getId: _getId,
3239
3354
  isTotalSelected: _isTotalSelected,
3240
- isRowSelectable
3355
+ isRowSelectable,
3356
+ columnGroupingModel
3357
+ // Add this prop
3241
3358
  }) {
3359
+ if (pinnedColumns && columnGroupingModel) {
3360
+ throw new Error(
3361
+ "You cannot use both `pinnedColumns` and `columnGroupingModel` at the same time. Please choose one of them."
3362
+ );
3363
+ }
3242
3364
  const onSelectionModelChangeRef = (0, import_react25.useRef)(onSelectionModelChange);
3243
3365
  onSelectionModelChangeRef.current = onSelectionModelChange;
3244
3366
  const [focusedRowId, setFocusedRowId] = (0, import_react25.useState)(null);
@@ -3247,18 +3369,23 @@ function useDataTableRenderer({
3247
3369
  initialState?.sorting?.sortModel ?? [],
3248
3370
  (0, import_react25.useCallback)((sortModel2) => onSortModelChange?.(sortModel2), [onSortModelChange])
3249
3371
  );
3372
+ const reorderedColumns = (0, import_react25.useMemo)(() => {
3373
+ if (!columnGroupingModel) return columnsProp;
3374
+ return reorderColumnsByGroupingModel(columnsProp, columnGroupingModel);
3375
+ }, [columnsProp, columnGroupingModel]);
3250
3376
  const columnsByField = (0, import_react25.useMemo)(
3251
- () => columnsProp.reduce(
3377
+ () => reorderedColumns.reduce(
3252
3378
  (acc, curr) => ({
3253
3379
  ...acc,
3254
3380
  [curr.field]: {
3255
3381
  ...curr,
3256
- headerRef: import_react25.default.createRef()
3382
+ headerRef: import_react25.default.createRef(),
3383
+ tableColRef: import_react25.default.createRef()
3257
3384
  }
3258
3385
  }),
3259
3386
  {}
3260
3387
  ),
3261
- [columnsProp]
3388
+ [reorderedColumns]
3262
3389
  );
3263
3390
  const sortComparator = (0, import_react25.useCallback)(
3264
3391
  (rowA, rowB) => {
@@ -3338,11 +3465,17 @@ function useDataTableRenderer({
3338
3465
  columnsByField[f].minWidth ?? 0,
3339
3466
  [columnWidths, columnsByField]
3340
3467
  );
3468
+ const processedColumnGroups = (0, import_react25.useMemo)(() => {
3469
+ if (!columnGroupingModel) return null;
3470
+ return calculateColumnGroups(columnGroupingModel, reorderedColumns);
3471
+ }, [columnGroupingModel, reorderedColumns]);
3341
3472
  const columns = (0, import_react25.useMemo)(() => {
3342
- const baseColumns = columnsProp || // fallback
3343
- Object.keys(rows[0] || {}).map((key) => ({
3344
- field: key
3345
- }));
3473
+ const baseColumns = reorderedColumns.length > 0 ? reorderedColumns : (
3474
+ // fallback
3475
+ Object.keys(rows[0] || {}).map((key) => ({
3476
+ field: key
3477
+ }))
3478
+ );
3346
3479
  return baseColumns.map((column) => {
3347
3480
  const isLeftPinned = pinnedColumns?.left?.includes(column.field);
3348
3481
  const isRightPinned = pinnedColumns?.right?.includes(column.field);
@@ -3350,6 +3483,7 @@ function useDataTableRenderer({
3350
3483
  return {
3351
3484
  ...column,
3352
3485
  headerRef: columnsByField[column.field].headerRef,
3486
+ tableColRef: columnsByField[column.field].tableColRef,
3353
3487
  isCellEditable: editMode && (typeof column.isCellEditable === "function" ? column.isCellEditable : column.isCellEditable ?? true),
3354
3488
  sort: sortModel.find((sort) => sort.field === column.field)?.sort,
3355
3489
  sortOrder: columnsByField[column.field]?.sortOrder || sortOrder,
@@ -3362,7 +3496,7 @@ function useDataTableRenderer({
3362
3496
  };
3363
3497
  });
3364
3498
  }, [
3365
- columnsProp,
3499
+ reorderedColumns,
3366
3500
  rows,
3367
3501
  pinnedColumns?.left,
3368
3502
  pinnedColumns?.right,
@@ -3458,6 +3592,7 @@ function useDataTableRenderer({
3458
3592
  [selectionModel, onSelectionModelChange, selectedModelSet]
3459
3593
  ),
3460
3594
  columns,
3595
+ processedColumnGroups,
3461
3596
  onTotalSelect: (0, import_react25.useCallback)(() => {
3462
3597
  const selectableRows = rows.filter((row, i) => {
3463
3598
  if (!isRowSelectable) return true;
@@ -3500,6 +3635,8 @@ function Component(props, apiRef) {
3500
3635
  getId: ____________,
3501
3636
  // getId is used in useDataTableRenderer
3502
3637
  loading,
3638
+ columnGroupingModel: _________________,
3639
+ // columnGroupingModel is used in useDataTableRenderer
3503
3640
  slots: {
3504
3641
  checkbox: RenderCheckbox = Checkbox_default,
3505
3642
  toolbar: Toolbar,
@@ -3516,6 +3653,7 @@ function Component(props, apiRef) {
3516
3653
  const tableBodyRef = (0, import_react25.useRef)(null);
3517
3654
  const {
3518
3655
  columns,
3656
+ processedColumnGroups,
3519
3657
  isAllSelected,
3520
3658
  isSelectedRow,
3521
3659
  isRowSelectable: isRowSelectableCheck,
@@ -3623,7 +3761,8 @@ function Component(props, apiRef) {
3623
3761
  boxShadow: "sm",
3624
3762
  borderRadius: "sm",
3625
3763
  "--DataTableSheet-width": parentRef.current?.clientWidth + "px",
3626
- backgroundColor: "white"
3764
+ backgroundColor: "white",
3765
+ "--TableCell-cornerRadius": "0px"
3627
3766
  },
3628
3767
  className: [
3629
3768
  ...(parentRef.current?.scrollLeft || 0) > 0 ? ["ScrollableRight"] : [],
@@ -3632,12 +3771,30 @@ function Component(props, apiRef) {
3632
3771
  ref: parentRef,
3633
3772
  ...backgroundProps
3634
3773
  },
3635
- /* @__PURE__ */ import_react25.default.createElement(Table, { ...innerProps }, /* @__PURE__ */ import_react25.default.createElement("thead", null, /* @__PURE__ */ import_react25.default.createElement("tr", null, checkboxSelection && /* @__PURE__ */ import_react25.default.createElement(
3774
+ /* @__PURE__ */ import_react25.default.createElement(Table, { ...innerProps }, /* @__PURE__ */ import_react25.default.createElement("colgroup", null, checkboxSelection && /* @__PURE__ */ import_react25.default.createElement(
3775
+ "col",
3776
+ {
3777
+ style: {
3778
+ width: "40px"
3779
+ }
3780
+ }
3781
+ ), columns.map((c) => /* @__PURE__ */ import_react25.default.createElement(
3782
+ "col",
3783
+ {
3784
+ ref: c.tableColRef,
3785
+ key: `${c.field.toString()}_${c.width}`,
3786
+ style: {
3787
+ width: c.width
3788
+ }
3789
+ }
3790
+ ))), /* @__PURE__ */ import_react25.default.createElement("thead", null, processedColumnGroups && processedColumnGroups.groups.map((levelGroups, level) => /* @__PURE__ */ import_react25.default.createElement("tr", { key: `group-level-${level}` }, checkboxSelection && level === 0 && /* @__PURE__ */ import_react25.default.createElement(
3636
3791
  "th",
3637
3792
  {
3793
+ rowSpan: processedColumnGroups.maxLevel + 2,
3638
3794
  style: {
3639
3795
  width: "40px",
3640
- textAlign: "center"
3796
+ textAlign: "center",
3797
+ verticalAlign: "middle"
3641
3798
  }
3642
3799
  },
3643
3800
  /* @__PURE__ */ import_react25.default.createElement(
@@ -3650,16 +3807,52 @@ function Component(props, apiRef) {
3650
3807
  ...checkboxProps
3651
3808
  }
3652
3809
  )
3653
- ), columns.map((c, i) => /* @__PURE__ */ import_react25.default.createElement(
3654
- HeadCell2,
3810
+ ), level > 0 && Array.from({ length: levelGroups[0]?.startIndex || 0 }).map((_2, i) => /* @__PURE__ */ import_react25.default.createElement("th", { key: `empty-${level}-${i}` })), levelGroups.map((group, groupIndex) => {
3811
+ const nextGroup = levelGroups[groupIndex + 1];
3812
+ const emptyCells = nextGroup ? nextGroup.startIndex - (group.startIndex + group.colspan) : columns.length - (group.startIndex + group.colspan);
3813
+ return /* @__PURE__ */ import_react25.default.createElement(import_react25.Fragment, { key: group.groupId }, /* @__PURE__ */ import_react25.default.createElement(
3814
+ "th",
3815
+ {
3816
+ colSpan: group.colspan,
3817
+ style: {
3818
+ textAlign: "center",
3819
+ fontWeight: "bold",
3820
+ verticalAlign: "middle"
3821
+ }
3822
+ },
3823
+ group.headerName ?? group.groupId
3824
+ ), emptyCells > 0 && Array.from({ length: emptyCells }).map((_2, i) => /* @__PURE__ */ import_react25.default.createElement("th", { key: `empty-between-${level}-${groupIndex}-${i}`, colSpan: 1 })));
3825
+ }))), /* @__PURE__ */ import_react25.default.createElement("tr", null, !processedColumnGroups && checkboxSelection && /* @__PURE__ */ import_react25.default.createElement(
3826
+ "th",
3655
3827
  {
3656
- tableId,
3657
- key: `${c.field.toString()}_${i}`,
3658
- stickyHeader: props.stickyHeader,
3659
- editMode: !!c.isCellEditable,
3660
- onSortChange: handleSortChange,
3661
- ...c
3662
- }
3828
+ style: {
3829
+ width: "40px",
3830
+ textAlign: "center"
3831
+ }
3832
+ },
3833
+ /* @__PURE__ */ import_react25.default.createElement(
3834
+ RenderCheckbox,
3835
+ {
3836
+ onChange: onAllCheckboxChange,
3837
+ checked: isAllSelected,
3838
+ indeterminate: (selectionModel || []).length > 0 && !isAllSelected,
3839
+ disabled: dataInPage.length > 0 && !selectableRowCount,
3840
+ ...checkboxProps
3841
+ }
3842
+ )
3843
+ ), columns.map((c, i) => (
3844
+ // @ts-ignore
3845
+ /* @__PURE__ */ import_react25.default.createElement(
3846
+ HeadCell2,
3847
+ {
3848
+ tableId,
3849
+ key: `${c.field.toString()}_${i}`,
3850
+ stickyHeader: props.stickyHeader,
3851
+ editMode: !!c.isCellEditable,
3852
+ onSortChange: handleSortChange,
3853
+ ...c
3854
+ }
3855
+ )
3663
3856
  )))), /* @__PURE__ */ import_react25.default.createElement(
3664
3857
  VirtualizedTableBody,
3665
3858
  {