@ceed/ads 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
@@ -2692,6 +2692,118 @@ function InfoSign(props) {
2692
2692
  var InfoSign_default = InfoSign;
2693
2693
 
2694
2694
  // src/components/DataTable/DataTable.tsx
2695
+ function extractFieldsFromGroupingModel(items) {
2696
+ const fields = /* @__PURE__ */ new Set();
2697
+ for (const item of items) {
2698
+ if ("groupId" in item) {
2699
+ const children = Array.isArray(item.children) ? item.children : [item.children];
2700
+ const childFields = extractFieldsFromGroupingModel(children);
2701
+ childFields.forEach((field) => fields.add(field));
2702
+ } else {
2703
+ fields.add(item.field);
2704
+ }
2705
+ }
2706
+ return fields;
2707
+ }
2708
+ function reorderColumnsByGroupingModel(columns, columnGroupingModel) {
2709
+ const fieldsInGroupingModel = extractFieldsFromGroupingModel(columnGroupingModel);
2710
+ const orderedFields = [];
2711
+ function collectFieldsInOrder(items) {
2712
+ for (const item of items) {
2713
+ if ("groupId" in item) {
2714
+ const children = Array.isArray(item.children) ? item.children : [item.children];
2715
+ collectFieldsInOrder(children);
2716
+ } else {
2717
+ if (!orderedFields.includes(item.field)) {
2718
+ orderedFields.push(item.field);
2719
+ }
2720
+ }
2721
+ }
2722
+ }
2723
+ collectFieldsInOrder(columnGroupingModel);
2724
+ const columnMap = new Map(columns.map((col) => [col.field, col]));
2725
+ const reorderedColumns = [];
2726
+ for (const field of orderedFields) {
2727
+ const column = columnMap.get(field);
2728
+ if (column) {
2729
+ reorderedColumns.push(column);
2730
+ }
2731
+ }
2732
+ for (const column of columns) {
2733
+ if (!fieldsInGroupingModel.has(column.field)) {
2734
+ reorderedColumns.push(column);
2735
+ }
2736
+ }
2737
+ return reorderedColumns;
2738
+ }
2739
+ function flattenColumnGroups(items, groupPath = [], columnIndex = { current: 0 }) {
2740
+ const result = [];
2741
+ for (const item of items) {
2742
+ if ("groupId" in item) {
2743
+ const newPath = [...groupPath, item.groupId];
2744
+ const children = Array.isArray(item.children) ? item.children : [item.children];
2745
+ result.push(...flattenColumnGroups(children, newPath, columnIndex));
2746
+ } else {
2747
+ result.push({
2748
+ ...item,
2749
+ groupPath,
2750
+ columnIndex: columnIndex.current++
2751
+ });
2752
+ }
2753
+ }
2754
+ return result;
2755
+ }
2756
+ function calculateColumnGroups(columnGroupingModel, columns) {
2757
+ const fieldsInGroupingModel = extractFieldsFromGroupingModel(columnGroupingModel);
2758
+ const flattenedColumns = flattenColumnGroups(columnGroupingModel);
2759
+ const columnIndexMap = new Map(flattenedColumns.map((col) => [col.field, col.columnIndex]));
2760
+ const processedGroups = /* @__PURE__ */ new Map();
2761
+ const groupsByLevel = [];
2762
+ let maxLevel = 0;
2763
+ function processGroup(items, level, parentGroupId) {
2764
+ let minIndex = Infinity;
2765
+ let maxIndex = -Infinity;
2766
+ for (const item of items) {
2767
+ if ("groupId" in item) {
2768
+ const groupKey = parentGroupId ? `${parentGroupId}.${item.groupId}` : item.groupId;
2769
+ if (!processedGroups.has(groupKey)) {
2770
+ const children = Array.isArray(item.children) ? item.children : [item.children];
2771
+ const { startIndex, colspan } = processGroup(children, level + 1, groupKey);
2772
+ const group = {
2773
+ groupId: item.groupId,
2774
+ headerName: item.headerName,
2775
+ colspan,
2776
+ level,
2777
+ startIndex
2778
+ };
2779
+ processedGroups.set(groupKey, group);
2780
+ if (!groupsByLevel[level]) {
2781
+ groupsByLevel[level] = [];
2782
+ }
2783
+ groupsByLevel[level].push(group);
2784
+ maxLevel = Math.max(maxLevel, level);
2785
+ minIndex = Math.min(minIndex, startIndex);
2786
+ maxIndex = Math.max(maxIndex, startIndex + colspan - 1);
2787
+ }
2788
+ } else {
2789
+ const columnIndex = columnIndexMap.get(item.field);
2790
+ if (columnIndex !== void 0) {
2791
+ minIndex = Math.min(minIndex, columnIndex);
2792
+ maxIndex = Math.max(maxIndex, columnIndex);
2793
+ }
2794
+ }
2795
+ }
2796
+ return {
2797
+ startIndex: minIndex === Infinity ? 0 : minIndex,
2798
+ colspan: maxIndex === -Infinity ? 0 : maxIndex - minIndex + 1
2799
+ };
2800
+ }
2801
+ processGroup(columnGroupingModel, 0);
2802
+ groupsByLevel.forEach((level) => {
2803
+ level.sort((a, b) => a.startIndex - b.startIndex);
2804
+ });
2805
+ return { groups: groupsByLevel, maxLevel, fieldsInGroupingModel };
2806
+ }
2695
2807
  function getTextAlign(props) {
2696
2808
  return !props.editMode && ["number", "date", "currency"].includes(props.type || "") ? "end" : "start";
2697
2809
  }
@@ -2762,7 +2874,7 @@ var OverlayWrapper = (0, import_joy33.styled)("tr", {
2762
2874
  }
2763
2875
  });
2764
2876
  var numberFormatter = (value) => "Intl" in window ? new Intl.NumberFormat().format(value) : value;
2765
- var Resizer = (ref) => /* @__PURE__ */ import_react25.default.createElement(
2877
+ var Resizer = (ref, targetRef = ref) => /* @__PURE__ */ import_react25.default.createElement(
2766
2878
  Box_default,
2767
2879
  {
2768
2880
  sx: {
@@ -2780,6 +2892,7 @@ var Resizer = (ref) => /* @__PURE__ */ import_react25.default.createElement(
2780
2892
  const onMouseMove = (e2) => {
2781
2893
  if (initialWidth && initialX) {
2782
2894
  ref.current.style.width = `${initialWidth + (e2.clientX - initialX)}px`;
2895
+ targetRef.current.style.width = `${initialWidth + (e2.clientX - initialX)}px`;
2783
2896
  }
2784
2897
  };
2785
2898
  const onMouseUp = () => {
@@ -2890,17 +3003,19 @@ var HeadCell = (props) => {
2890
3003
  isPinned,
2891
3004
  pinnedStartPosition,
2892
3005
  pinnedEndPosition,
2893
- headerRef
3006
+ headerRef,
3007
+ tableColRef
2894
3008
  } = props;
2895
3009
  const theme = (0, import_joy33.useTheme)();
2896
3010
  const ref = headerRef;
3011
+ const colRef = tableColRef;
2897
3012
  const [isHovered, setIsHovered] = (0, import_react25.useState)(false);
2898
3013
  const sortable = type === "actions" ? false : _sortable;
2899
3014
  const headId = (0, import_react25.useMemo)(
2900
3015
  () => `${tableId}_header_${headerName ?? field}`.trim(),
2901
3016
  [tableId, headerName, field]
2902
3017
  );
2903
- const resizer = (0, import_react25.useMemo)(() => resizable ?? true ? Resizer(ref) : null, [resizable, ref]);
3018
+ const resizer = (0, import_react25.useMemo)(() => resizable ?? true ? Resizer(ref, colRef) : null, [resizable, ref, colRef]);
2904
3019
  const style = (0, import_react25.useMemo)(
2905
3020
  () => ({
2906
3021
  width,
@@ -3235,8 +3350,15 @@ function useDataTableRenderer({
3235
3350
  editMode,
3236
3351
  getId: _getId,
3237
3352
  isTotalSelected: _isTotalSelected,
3238
- isRowSelectable
3353
+ isRowSelectable,
3354
+ columnGroupingModel
3355
+ // Add this prop
3239
3356
  }) {
3357
+ if (pinnedColumns && columnGroupingModel) {
3358
+ throw new Error(
3359
+ "You cannot use both `pinnedColumns` and `columnGroupingModel` at the same time. Please choose one of them."
3360
+ );
3361
+ }
3240
3362
  const onSelectionModelChangeRef = (0, import_react25.useRef)(onSelectionModelChange);
3241
3363
  onSelectionModelChangeRef.current = onSelectionModelChange;
3242
3364
  const [focusedRowId, setFocusedRowId] = (0, import_react25.useState)(null);
@@ -3245,18 +3367,23 @@ function useDataTableRenderer({
3245
3367
  initialState?.sorting?.sortModel ?? [],
3246
3368
  (0, import_react25.useCallback)((sortModel2) => onSortModelChange?.(sortModel2), [onSortModelChange])
3247
3369
  );
3370
+ const reorderedColumns = (0, import_react25.useMemo)(() => {
3371
+ if (!columnGroupingModel) return columnsProp;
3372
+ return reorderColumnsByGroupingModel(columnsProp, columnGroupingModel);
3373
+ }, [columnsProp, columnGroupingModel]);
3248
3374
  const columnsByField = (0, import_react25.useMemo)(
3249
- () => columnsProp.reduce(
3375
+ () => reorderedColumns.reduce(
3250
3376
  (acc, curr) => ({
3251
3377
  ...acc,
3252
3378
  [curr.field]: {
3253
3379
  ...curr,
3254
- headerRef: import_react25.default.createRef()
3380
+ headerRef: import_react25.default.createRef(),
3381
+ tableColRef: import_react25.default.createRef()
3255
3382
  }
3256
3383
  }),
3257
3384
  {}
3258
3385
  ),
3259
- [columnsProp]
3386
+ [reorderedColumns]
3260
3387
  );
3261
3388
  const sortComparator = (0, import_react25.useCallback)(
3262
3389
  (rowA, rowB) => {
@@ -3336,11 +3463,17 @@ function useDataTableRenderer({
3336
3463
  columnsByField[f].minWidth ?? 0,
3337
3464
  [columnWidths, columnsByField]
3338
3465
  );
3466
+ const processedColumnGroups = (0, import_react25.useMemo)(() => {
3467
+ if (!columnGroupingModel) return null;
3468
+ return calculateColumnGroups(columnGroupingModel, reorderedColumns);
3469
+ }, [columnGroupingModel, reorderedColumns]);
3339
3470
  const columns = (0, import_react25.useMemo)(() => {
3340
- const baseColumns = columnsProp || // fallback
3341
- Object.keys(rows[0] || {}).map((key) => ({
3342
- field: key
3343
- }));
3471
+ const baseColumns = reorderedColumns.length > 0 ? reorderedColumns : (
3472
+ // fallback
3473
+ Object.keys(rows[0] || {}).map((key) => ({
3474
+ field: key
3475
+ }))
3476
+ );
3344
3477
  return baseColumns.map((column) => {
3345
3478
  const isLeftPinned = pinnedColumns?.left?.includes(column.field);
3346
3479
  const isRightPinned = pinnedColumns?.right?.includes(column.field);
@@ -3348,6 +3481,7 @@ function useDataTableRenderer({
3348
3481
  return {
3349
3482
  ...column,
3350
3483
  headerRef: columnsByField[column.field].headerRef,
3484
+ tableColRef: columnsByField[column.field].tableColRef,
3351
3485
  isCellEditable: editMode && (typeof column.isCellEditable === "function" ? column.isCellEditable : column.isCellEditable ?? true),
3352
3486
  sort: sortModel.find((sort) => sort.field === column.field)?.sort,
3353
3487
  sortOrder: columnsByField[column.field]?.sortOrder || sortOrder,
@@ -3360,7 +3494,7 @@ function useDataTableRenderer({
3360
3494
  };
3361
3495
  });
3362
3496
  }, [
3363
- columnsProp,
3497
+ reorderedColumns,
3364
3498
  rows,
3365
3499
  pinnedColumns?.left,
3366
3500
  pinnedColumns?.right,
@@ -3456,6 +3590,7 @@ function useDataTableRenderer({
3456
3590
  [selectionModel, onSelectionModelChange, selectedModelSet]
3457
3591
  ),
3458
3592
  columns,
3593
+ processedColumnGroups,
3459
3594
  onTotalSelect: (0, import_react25.useCallback)(() => {
3460
3595
  const selectableRows = rows.filter((row, i) => {
3461
3596
  if (!isRowSelectable) return true;
@@ -3498,6 +3633,8 @@ function Component(props, apiRef) {
3498
3633
  getId: ____________,
3499
3634
  // getId is used in useDataTableRenderer
3500
3635
  loading,
3636
+ columnGroupingModel: _________________,
3637
+ // columnGroupingModel is used in useDataTableRenderer
3501
3638
  slots: {
3502
3639
  checkbox: RenderCheckbox = Checkbox_default,
3503
3640
  toolbar: Toolbar,
@@ -3514,6 +3651,7 @@ function Component(props, apiRef) {
3514
3651
  const tableBodyRef = (0, import_react25.useRef)(null);
3515
3652
  const {
3516
3653
  columns,
3654
+ processedColumnGroups,
3517
3655
  isAllSelected,
3518
3656
  isSelectedRow,
3519
3657
  isRowSelectable: isRowSelectableCheck,
@@ -3621,7 +3759,8 @@ function Component(props, apiRef) {
3621
3759
  boxShadow: "sm",
3622
3760
  borderRadius: "sm",
3623
3761
  "--DataTableSheet-width": parentRef.current?.clientWidth + "px",
3624
- backgroundColor: "white"
3762
+ backgroundColor: "white",
3763
+ "--TableCell-cornerRadius": "0px"
3625
3764
  },
3626
3765
  className: [
3627
3766
  ...(parentRef.current?.scrollLeft || 0) > 0 ? ["ScrollableRight"] : [],
@@ -3630,12 +3769,30 @@ function Component(props, apiRef) {
3630
3769
  ref: parentRef,
3631
3770
  ...backgroundProps
3632
3771
  },
3633
- /* @__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(
3772
+ /* @__PURE__ */ import_react25.default.createElement(Table, { ...innerProps }, /* @__PURE__ */ import_react25.default.createElement("colgroup", null, checkboxSelection && /* @__PURE__ */ import_react25.default.createElement(
3773
+ "col",
3774
+ {
3775
+ style: {
3776
+ width: "40px"
3777
+ }
3778
+ }
3779
+ ), columns.map((c) => /* @__PURE__ */ import_react25.default.createElement(
3780
+ "col",
3781
+ {
3782
+ ref: c.tableColRef,
3783
+ key: `${c.field.toString()}_${c.width}`,
3784
+ style: {
3785
+ width: c.width
3786
+ }
3787
+ }
3788
+ ))), /* @__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(
3634
3789
  "th",
3635
3790
  {
3791
+ rowSpan: processedColumnGroups.maxLevel + 2,
3636
3792
  style: {
3637
3793
  width: "40px",
3638
- textAlign: "center"
3794
+ textAlign: "center",
3795
+ verticalAlign: "middle"
3639
3796
  }
3640
3797
  },
3641
3798
  /* @__PURE__ */ import_react25.default.createElement(
@@ -3648,16 +3805,52 @@ function Component(props, apiRef) {
3648
3805
  ...checkboxProps
3649
3806
  }
3650
3807
  )
3651
- ), columns.map((c, i) => /* @__PURE__ */ import_react25.default.createElement(
3652
- HeadCell2,
3808
+ ), 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) => {
3809
+ const nextGroup = levelGroups[groupIndex + 1];
3810
+ const emptyCells = nextGroup ? nextGroup.startIndex - (group.startIndex + group.colspan) : columns.length - (group.startIndex + group.colspan);
3811
+ return /* @__PURE__ */ import_react25.default.createElement(import_react25.Fragment, { key: group.groupId }, /* @__PURE__ */ import_react25.default.createElement(
3812
+ "th",
3813
+ {
3814
+ colSpan: group.colspan,
3815
+ style: {
3816
+ textAlign: "center",
3817
+ fontWeight: "bold",
3818
+ verticalAlign: "middle"
3819
+ }
3820
+ },
3821
+ group.headerName ?? group.groupId
3822
+ ), emptyCells > 0 && Array.from({ length: emptyCells }).map((_2, i) => /* @__PURE__ */ import_react25.default.createElement("th", { key: `empty-between-${level}-${groupIndex}-${i}`, colSpan: 1 })));
3823
+ }))), /* @__PURE__ */ import_react25.default.createElement("tr", null, !processedColumnGroups && checkboxSelection && /* @__PURE__ */ import_react25.default.createElement(
3824
+ "th",
3653
3825
  {
3654
- tableId,
3655
- key: `${c.field.toString()}_${i}`,
3656
- stickyHeader: props.stickyHeader,
3657
- editMode: !!c.isCellEditable,
3658
- onSortChange: handleSortChange,
3659
- ...c
3660
- }
3826
+ style: {
3827
+ width: "40px",
3828
+ textAlign: "center"
3829
+ }
3830
+ },
3831
+ /* @__PURE__ */ import_react25.default.createElement(
3832
+ RenderCheckbox,
3833
+ {
3834
+ onChange: onAllCheckboxChange,
3835
+ checked: isAllSelected,
3836
+ indeterminate: (selectionModel || []).length > 0 && !isAllSelected,
3837
+ disabled: dataInPage.length > 0 && !selectableRowCount,
3838
+ ...checkboxProps
3839
+ }
3840
+ )
3841
+ ), columns.map((c, i) => (
3842
+ // @ts-ignore
3843
+ /* @__PURE__ */ import_react25.default.createElement(
3844
+ HeadCell2,
3845
+ {
3846
+ tableId,
3847
+ key: `${c.field.toString()}_${i}`,
3848
+ stickyHeader: props.stickyHeader,
3849
+ editMode: !!c.isCellEditable,
3850
+ onSortChange: handleSortChange,
3851
+ ...c
3852
+ }
3853
+ )
3661
3854
  )))), /* @__PURE__ */ import_react25.default.createElement(
3662
3855
  VirtualizedTableBody,
3663
3856
  {