@shadng/sng-ui 1.0.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.
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/cli/sng-ui.js +331 -0
- package/ng-package.json +29 -0
- package/package.json +64 -0
- package/registry.json +72 -0
- package/src/lib/accordion/cn.ts +6 -0
- package/src/lib/accordion/index.ts +18 -0
- package/src/lib/accordion/sng-accordion-content.ts +131 -0
- package/src/lib/accordion/sng-accordion-item.ts +299 -0
- package/src/lib/accordion/sng-accordion-trigger.ts +137 -0
- package/src/lib/accordion/sng-accordion.ts +118 -0
- package/src/lib/accordion/sng-accordion.types.ts +82 -0
- package/src/lib/alert/cn.ts +6 -0
- package/src/lib/alert/index.ts +3 -0
- package/src/lib/alert/sng-alert-description.ts +49 -0
- package/src/lib/alert/sng-alert-title.ts +46 -0
- package/src/lib/alert/sng-alert.ts +48 -0
- package/src/lib/avatar/cn.ts +6 -0
- package/src/lib/avatar/index.ts +3 -0
- package/src/lib/avatar/sng-avatar-fallback.ts +50 -0
- package/src/lib/avatar/sng-avatar-image.ts +73 -0
- package/src/lib/avatar/sng-avatar.ts +60 -0
- package/src/lib/badge/cn.ts +6 -0
- package/src/lib/badge/index.ts +1 -0
- package/src/lib/badge/sng-badge.ts +36 -0
- package/src/lib/breadcrumb/cn.ts +6 -0
- package/src/lib/breadcrumb/index.ts +7 -0
- package/src/lib/breadcrumb/sng-breadcrumb-ellipsis.ts +61 -0
- package/src/lib/breadcrumb/sng-breadcrumb-item.ts +47 -0
- package/src/lib/breadcrumb/sng-breadcrumb-link.ts +43 -0
- package/src/lib/breadcrumb/sng-breadcrumb-list.ts +42 -0
- package/src/lib/breadcrumb/sng-breadcrumb-page.ts +44 -0
- package/src/lib/breadcrumb/sng-breadcrumb-separator.ts +60 -0
- package/src/lib/breadcrumb/sng-breadcrumb.ts +52 -0
- package/src/lib/button/cn.ts +6 -0
- package/src/lib/button/index.ts +2 -0
- package/src/lib/button/sng-button.ts +264 -0
- package/src/lib/calendar/cn.ts +6 -0
- package/src/lib/calendar/index.ts +2 -0
- package/src/lib/calendar/sng-calendar.ts +753 -0
- package/src/lib/card/cn.ts +6 -0
- package/src/lib/card/index.ts +6 -0
- package/src/lib/card/sng-card-content.ts +36 -0
- package/src/lib/card/sng-card-description.ts +38 -0
- package/src/lib/card/sng-card-footer.ts +34 -0
- package/src/lib/card/sng-card-header.ts +34 -0
- package/src/lib/card/sng-card-title.ts +48 -0
- package/src/lib/card/sng-card.ts +43 -0
- package/src/lib/carousel/cn.ts +6 -0
- package/src/lib/carousel/index.ts +18 -0
- package/src/lib/carousel/sng-carousel.ts +526 -0
- package/src/lib/checkbox/cn.ts +6 -0
- package/src/lib/checkbox/index.ts +1 -0
- package/src/lib/checkbox/sng-checkbox.ts +154 -0
- package/src/lib/code-block/cn.ts +6 -0
- package/src/lib/code-block/index.ts +1 -0
- package/src/lib/code-block/sng-code-block.ts +296 -0
- package/src/lib/dialog/cn.ts +6 -0
- package/src/lib/dialog/index.ts +37 -0
- package/src/lib/dialog/sng-dialog-close.ts +76 -0
- package/src/lib/dialog/sng-dialog-content.ts +132 -0
- package/src/lib/dialog/sng-dialog-description.ts +36 -0
- package/src/lib/dialog/sng-dialog-footer.ts +39 -0
- package/src/lib/dialog/sng-dialog-header.ts +39 -0
- package/src/lib/dialog/sng-dialog-title.ts +52 -0
- package/src/lib/dialog/sng-dialog.service.ts +222 -0
- package/src/lib/dialog/sng-dialog.ts +224 -0
- package/src/lib/drawer/cn.ts +6 -0
- package/src/lib/drawer/index.ts +36 -0
- package/src/lib/drawer/sng-drawer-close.ts +28 -0
- package/src/lib/drawer/sng-drawer-content.ts +135 -0
- package/src/lib/drawer/sng-drawer-description.ts +29 -0
- package/src/lib/drawer/sng-drawer-footer.ts +34 -0
- package/src/lib/drawer/sng-drawer-handle.ts +30 -0
- package/src/lib/drawer/sng-drawer-header.ts +30 -0
- package/src/lib/drawer/sng-drawer-title.ts +27 -0
- package/src/lib/drawer/sng-drawer-trigger.ts +21 -0
- package/src/lib/drawer/sng-drawer-wrapper.ts +27 -0
- package/src/lib/drawer/sng-drawer.ts +166 -0
- package/src/lib/file-input/cn.ts +6 -0
- package/src/lib/file-input/index.ts +1 -0
- package/src/lib/file-input/sng-file-input.ts +288 -0
- package/src/lib/hover-card/cn.ts +6 -0
- package/src/lib/hover-card/index.ts +3 -0
- package/src/lib/hover-card/sng-hover-card-content.ts +100 -0
- package/src/lib/hover-card/sng-hover-card-trigger.ts +43 -0
- package/src/lib/hover-card/sng-hover-card.ts +246 -0
- package/src/lib/input/cn.ts +6 -0
- package/src/lib/input/index.ts +1 -0
- package/src/lib/input/sng-input.ts +160 -0
- package/src/lib/layout/cn.ts +6 -0
- package/src/lib/layout/index.ts +98 -0
- package/src/lib/layout/sng-layout-footer.ts +37 -0
- package/src/lib/layout/sng-layout-header.ts +38 -0
- package/src/lib/layout/sng-layout-sidebar-content.ts +149 -0
- package/src/lib/layout/sng-layout-sidebar-footer.ts +54 -0
- package/src/lib/layout/sng-layout-sidebar-group-action.ts +67 -0
- package/src/lib/layout/sng-layout-sidebar-group-content.ts +41 -0
- package/src/lib/layout/sng-layout-sidebar-group-label.ts +53 -0
- package/src/lib/layout/sng-layout-sidebar-group.ts +41 -0
- package/src/lib/layout/sng-layout-sidebar-header.ts +54 -0
- package/src/lib/layout/sng-layout-sidebar-input.ts +112 -0
- package/src/lib/layout/sng-layout-sidebar-inset.ts +45 -0
- package/src/lib/layout/sng-layout-sidebar-menu-action.ts +84 -0
- package/src/lib/layout/sng-layout-sidebar-menu-badge.ts +47 -0
- package/src/lib/layout/sng-layout-sidebar-menu-button.ts +160 -0
- package/src/lib/layout/sng-layout-sidebar-menu-item.ts +40 -0
- package/src/lib/layout/sng-layout-sidebar-menu-skeleton.ts +71 -0
- package/src/lib/layout/sng-layout-sidebar-menu-sub-button.ts +142 -0
- package/src/lib/layout/sng-layout-sidebar-menu-sub-item.ts +38 -0
- package/src/lib/layout/sng-layout-sidebar-menu-sub.ts +48 -0
- package/src/lib/layout/sng-layout-sidebar-menu.ts +41 -0
- package/src/lib/layout/sng-layout-sidebar-provider.ts +189 -0
- package/src/lib/layout/sng-layout-sidebar-rail.ts +60 -0
- package/src/lib/layout/sng-layout-sidebar-separator.ts +38 -0
- package/src/lib/layout/sng-layout-sidebar-trigger.ts +97 -0
- package/src/lib/layout/sng-layout-sidebar.ts +254 -0
- package/src/lib/menu/cn.ts +6 -0
- package/src/lib/menu/index.ts +21 -0
- package/src/lib/menu/sng-context-trigger.ts +128 -0
- package/src/lib/menu/sng-menu-checkbox-item.ts +91 -0
- package/src/lib/menu/sng-menu-item.ts +80 -0
- package/src/lib/menu/sng-menu-label.ts +47 -0
- package/src/lib/menu/sng-menu-radio-group.ts +38 -0
- package/src/lib/menu/sng-menu-radio-item.ts +94 -0
- package/src/lib/menu/sng-menu-separator.ts +27 -0
- package/src/lib/menu/sng-menu-shortcut.ts +25 -0
- package/src/lib/menu/sng-menu-sub-content.ts +267 -0
- package/src/lib/menu/sng-menu-sub-trigger.ts +68 -0
- package/src/lib/menu/sng-menu-sub.ts +124 -0
- package/src/lib/menu/sng-menu-tokens.ts +52 -0
- package/src/lib/menu/sng-menu-trigger.ts +266 -0
- package/src/lib/menu/sng-menu.ts +100 -0
- package/src/lib/nav-menu/cn.ts +6 -0
- package/src/lib/nav-menu/index.ts +6 -0
- package/src/lib/nav-menu/sng-nav-menu-content.ts +72 -0
- package/src/lib/nav-menu/sng-nav-menu-item.ts +109 -0
- package/src/lib/nav-menu/sng-nav-menu-link.ts +54 -0
- package/src/lib/nav-menu/sng-nav-menu-list.ts +43 -0
- package/src/lib/nav-menu/sng-nav-menu-trigger.ts +98 -0
- package/src/lib/nav-menu/sng-nav-menu.ts +99 -0
- package/src/lib/otp-input/cn.ts +6 -0
- package/src/lib/otp-input/index.ts +14 -0
- package/src/lib/otp-input/sng-otp-input-group.ts +38 -0
- package/src/lib/otp-input/sng-otp-input-separator.ts +43 -0
- package/src/lib/otp-input/sng-otp-input-slot.ts +128 -0
- package/src/lib/otp-input/sng-otp-input-tokens.ts +20 -0
- package/src/lib/otp-input/sng-otp-input.ts +301 -0
- package/src/lib/popover/cn.ts +6 -0
- package/src/lib/popover/index.ts +3 -0
- package/src/lib/popover/sng-popover-content.ts +66 -0
- package/src/lib/popover/sng-popover-trigger.ts +44 -0
- package/src/lib/popover/sng-popover.ts +218 -0
- package/src/lib/preview-box/cn.ts +6 -0
- package/src/lib/preview-box/index.ts +5 -0
- package/src/lib/preview-box/sng-code-block.ts +80 -0
- package/src/lib/preview-box/sng-html-block.ts +79 -0
- package/src/lib/preview-box/sng-preview-block.ts +47 -0
- package/src/lib/preview-box/sng-preview-box.ts +369 -0
- package/src/lib/preview-box/sng-style-block.ts +80 -0
- package/src/lib/progress/cn.ts +6 -0
- package/src/lib/progress/index.ts +1 -0
- package/src/lib/progress/sng-progress.ts +65 -0
- package/src/lib/radio/cn.ts +6 -0
- package/src/lib/radio/index.ts +5 -0
- package/src/lib/radio/sng-radio-item.ts +100 -0
- package/src/lib/radio/sng-radio.ts +54 -0
- package/src/lib/resizable/cn.ts +6 -0
- package/src/lib/resizable/index.ts +3 -0
- package/src/lib/resizable/sng-resizable-group.ts +188 -0
- package/src/lib/resizable/sng-resizable-handle.ts +236 -0
- package/src/lib/resizable/sng-resizable-panel.ts +71 -0
- package/src/lib/search-input/cn.ts +6 -0
- package/src/lib/search-input/index.ts +16 -0
- package/src/lib/search-input/sng-search-input-context.ts +24 -0
- package/src/lib/search-input/sng-search-input-empty.ts +42 -0
- package/src/lib/search-input/sng-search-input-group.ts +69 -0
- package/src/lib/search-input/sng-search-input-item.ts +164 -0
- package/src/lib/search-input/sng-search-input-list.ts +34 -0
- package/src/lib/search-input/sng-search-input-separator.ts +32 -0
- package/src/lib/search-input/sng-search-input-shortcut.ts +29 -0
- package/src/lib/search-input/sng-search-input.ts +368 -0
- package/src/lib/select/cn.ts +6 -0
- package/src/lib/select/index.ts +7 -0
- package/src/lib/select/sng-select-content.ts +27 -0
- package/src/lib/select/sng-select-empty.ts +48 -0
- package/src/lib/select/sng-select-group.ts +29 -0
- package/src/lib/select/sng-select-item.ts +140 -0
- package/src/lib/select/sng-select-label.ts +29 -0
- package/src/lib/select/sng-select-separator.ts +29 -0
- package/src/lib/select/sng-select.ts +326 -0
- package/src/lib/separator/cn.ts +6 -0
- package/src/lib/separator/index.ts +1 -0
- package/src/lib/separator/sng-separator.ts +40 -0
- package/src/lib/skeleton/cn.ts +6 -0
- package/src/lib/skeleton/index.ts +1 -0
- package/src/lib/skeleton/sng-skeleton.ts +49 -0
- package/src/lib/slider/cn.ts +6 -0
- package/src/lib/slider/index.ts +2 -0
- package/src/lib/slider/sng-slider.ts +137 -0
- package/src/lib/sng-table/cn.ts +6 -0
- package/src/lib/sng-table/flex-render.ts +222 -0
- package/src/lib/sng-table/index.ts +85 -0
- package/src/lib/sng-table/sng-table-body.ts +59 -0
- package/src/lib/sng-table/sng-table-caption.ts +49 -0
- package/src/lib/sng-table/sng-table-cell.ts +62 -0
- package/src/lib/sng-table/sng-table-footer.ts +60 -0
- package/src/lib/sng-table/sng-table-head.ts +66 -0
- package/src/lib/sng-table/sng-table-header.ts +48 -0
- package/src/lib/sng-table/sng-table-pagination.ts +265 -0
- package/src/lib/sng-table/sng-table-row.ts +65 -0
- package/src/lib/sng-table/sng-table.ts +67 -0
- package/src/lib/sng-table-core/core/create-cell.ts +117 -0
- package/src/lib/sng-table-core/core/create-column.ts +266 -0
- package/src/lib/sng-table-core/core/create-header.ts +271 -0
- package/src/lib/sng-table-core/core/create-row.ts +293 -0
- package/src/lib/sng-table-core/core/create-table.ts +534 -0
- package/src/lib/sng-table-core/core/types.ts +1197 -0
- package/src/lib/sng-table-core/core/utils.ts +307 -0
- package/src/lib/sng-table-core/features/column-filtering.ts +376 -0
- package/src/lib/sng-table-core/features/column-ordering.ts +159 -0
- package/src/lib/sng-table-core/features/column-pinning.ts +219 -0
- package/src/lib/sng-table-core/features/column-sizing.ts +268 -0
- package/src/lib/sng-table-core/features/column-visibility.ts +128 -0
- package/src/lib/sng-table-core/features/faceting.ts +279 -0
- package/src/lib/sng-table-core/features/fuzzy-filtering.ts +188 -0
- package/src/lib/sng-table-core/features/global-filtering.ts +128 -0
- package/src/lib/sng-table-core/features/pagination.ts +179 -0
- package/src/lib/sng-table-core/features/row-expanding.ts +181 -0
- package/src/lib/sng-table-core/features/row-grouping.ts +235 -0
- package/src/lib/sng-table-core/features/row-pinning.ts +196 -0
- package/src/lib/sng-table-core/features/row-selection.ts +298 -0
- package/src/lib/sng-table-core/features/sorting.ts +425 -0
- package/src/lib/sng-table-core/features/virtualization.ts +298 -0
- package/src/lib/sng-table-core/index.ts +235 -0
- package/src/lib/sng-table-core/row-models/core-row-model.ts +256 -0
- package/src/lib/sng-table-core/row-models/expanded-row-model.ts +175 -0
- package/src/lib/sng-table-core/row-models/filtered-row-model.ts +307 -0
- package/src/lib/sng-table-core/row-models/grouped-row-model.ts +290 -0
- package/src/lib/sng-table-core/row-models/paginated-row-model.ts +135 -0
- package/src/lib/sng-table-core/row-models/sorted-row-model.ts +197 -0
- package/src/lib/styles/sng-themes.css +164 -0
- package/src/lib/switch/cn.ts +6 -0
- package/src/lib/switch/index.ts +1 -0
- package/src/lib/switch/sng-switch.ts +137 -0
- package/src/lib/tabs/cn.ts +6 -0
- package/src/lib/tabs/index.ts +4 -0
- package/src/lib/tabs/sng-tabs-content.ts +66 -0
- package/src/lib/tabs/sng-tabs-list.ts +55 -0
- package/src/lib/tabs/sng-tabs-trigger.ts +86 -0
- package/src/lib/tabs/sng-tabs.ts +83 -0
- package/src/lib/toast/cn.ts +6 -0
- package/src/lib/toast/index.ts +3 -0
- package/src/lib/toast/sng-toast.service.ts +258 -0
- package/src/lib/toast/sng-toast.ts +101 -0
- package/src/lib/toast/sng-toaster.ts +67 -0
- package/src/lib/toggle/cn.ts +6 -0
- package/src/lib/toggle/index.ts +6 -0
- package/src/lib/toggle/sng-toggle-group-item.ts +89 -0
- package/src/lib/toggle/sng-toggle-group.ts +85 -0
- package/src/lib/toggle/sng-toggle.ts +78 -0
- package/src/lib/toggle-group/index.ts +6 -0
- package/src/lib/tooltip/cn.ts +6 -0
- package/src/lib/tooltip/index.ts +5 -0
- package/src/lib/tooltip/sng-tooltip-content.ts +64 -0
- package/src/lib/tooltip/sng-tooltip.ts +216 -0
- package/src/public-api.ts +207 -0
- package/tsconfig.json +24 -0
- package/tsconfig.lib.json +17 -0
- package/tsconfig.lib.prod.json +11 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Column creation and management for sng-table-core
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Column,
|
|
7
|
+
ColumnDef,
|
|
8
|
+
Table,
|
|
9
|
+
AccessorFn,
|
|
10
|
+
ColumnPinningPosition,
|
|
11
|
+
} from './types';
|
|
12
|
+
import { flattenBy, getValueAtPath } from './utils';
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// COLUMN CREATION
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create a column instance from a column definition
|
|
20
|
+
*/
|
|
21
|
+
export function createColumn<TData, TValue = unknown>(
|
|
22
|
+
table: Table<TData>,
|
|
23
|
+
columnDef: ColumnDef<TData, TValue>,
|
|
24
|
+
depth: number,
|
|
25
|
+
parent?: Column<TData, unknown>,
|
|
26
|
+
path: number[] = []
|
|
27
|
+
): Column<TData, TValue> {
|
|
28
|
+
// Resolve column ID
|
|
29
|
+
const id = resolveColumnId(columnDef, path);
|
|
30
|
+
|
|
31
|
+
// Merge with default column options
|
|
32
|
+
const resolvedDef = {
|
|
33
|
+
...table.options.defaultColumn,
|
|
34
|
+
...columnDef,
|
|
35
|
+
} as ColumnDef<TData, TValue>;
|
|
36
|
+
|
|
37
|
+
// Create the column instance
|
|
38
|
+
const column: Column<TData, TValue> = {
|
|
39
|
+
id,
|
|
40
|
+
columnDef: resolvedDef,
|
|
41
|
+
depth,
|
|
42
|
+
parent,
|
|
43
|
+
columns: [], // Will be populated for group columns
|
|
44
|
+
|
|
45
|
+
getLeafColumns: () => getLeafColumns(column as Column<TData, unknown>),
|
|
46
|
+
getFlatColumns: () => getFlatColumns(column as Column<TData, unknown>),
|
|
47
|
+
getAccessorFn: () => getAccessorFn(column),
|
|
48
|
+
getIndex: (position?: ColumnPinningPosition) =>
|
|
49
|
+
getColumnIndex(table, column as Column<TData, unknown>, position),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Recursively create child columns if this is a group column
|
|
53
|
+
if (resolvedDef.columns?.length) {
|
|
54
|
+
column.columns = resolvedDef.columns.map((childDef, childIndex) =>
|
|
55
|
+
createColumn(table, childDef, depth + 1, column as Column<TData, unknown>, [...path, childIndex])
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return column;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Resolve column ID from column definition
|
|
64
|
+
*/
|
|
65
|
+
function resolveColumnId<TData, TValue>(
|
|
66
|
+
columnDef: ColumnDef<TData, TValue>,
|
|
67
|
+
path: number[]
|
|
68
|
+
): string {
|
|
69
|
+
// Explicit ID takes precedence
|
|
70
|
+
if (columnDef.id) {
|
|
71
|
+
return columnDef.id;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Use accessor key as ID
|
|
75
|
+
if (columnDef.accessorKey) {
|
|
76
|
+
return columnDef.accessorKey;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Use accessor function name if available
|
|
80
|
+
if (columnDef.accessorFn) {
|
|
81
|
+
if (columnDef.accessorFn.name) {
|
|
82
|
+
return columnDef.accessorFn.name;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// No ID can be determined - this is an error for non-group columns
|
|
87
|
+
if (!columnDef.columns?.length) {
|
|
88
|
+
console.warn(
|
|
89
|
+
'[sng-table] Column definition requires either id, accessorKey, or accessorFn with a name:',
|
|
90
|
+
columnDef
|
|
91
|
+
);
|
|
92
|
+
return `column_${path.join('_') || '0'}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Group columns without explicit ID get a deterministic one.
|
|
96
|
+
return `group_${path.join('_') || '0'}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get accessor function for a column
|
|
101
|
+
*/
|
|
102
|
+
function getAccessorFn<TData, TValue>(
|
|
103
|
+
column: Column<TData, TValue>
|
|
104
|
+
): AccessorFn<TData, TValue> | undefined {
|
|
105
|
+
const { accessorFn, accessorKey } = column.columnDef;
|
|
106
|
+
|
|
107
|
+
// Direct accessor function
|
|
108
|
+
if (accessorFn) {
|
|
109
|
+
return accessorFn;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Create accessor from key
|
|
113
|
+
if (accessorKey) {
|
|
114
|
+
// Handle nested keys (e.g., 'user.profile.name')
|
|
115
|
+
if (accessorKey.includes('.')) {
|
|
116
|
+
return (row: TData) =>
|
|
117
|
+
getValueAtPath(row as Record<string, unknown>, accessorKey) as TValue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Simple key access
|
|
121
|
+
return (row: TData) => (row as Record<string, unknown>)[accessorKey] as TValue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// No accessor - this is a display/group column
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ============================================================================
|
|
129
|
+
// COLUMN TRAVERSAL
|
|
130
|
+
// ============================================================================
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get all leaf columns (columns without children) from this column's subtree
|
|
134
|
+
*/
|
|
135
|
+
function getLeafColumns<TData>(
|
|
136
|
+
column: Column<TData, unknown>
|
|
137
|
+
): Column<TData, unknown>[] {
|
|
138
|
+
if (!column.columns.length) {
|
|
139
|
+
return [column];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return column.columns.flatMap((child) => getLeafColumns(child));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get all columns (including groups) flattened from this column's subtree
|
|
147
|
+
*/
|
|
148
|
+
function getFlatColumns<TData>(
|
|
149
|
+
column: Column<TData, unknown>
|
|
150
|
+
): Column<TData, unknown>[] {
|
|
151
|
+
return [column, ...column.columns.flatMap((child) => getFlatColumns(child))];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get column index among siblings (considering pinning position if specified)
|
|
156
|
+
*/
|
|
157
|
+
function getColumnIndex<TData>(
|
|
158
|
+
table: Table<TData>,
|
|
159
|
+
column: Column<TData, unknown>,
|
|
160
|
+
position?: ColumnPinningPosition
|
|
161
|
+
): number {
|
|
162
|
+
// Get the appropriate column list based on position
|
|
163
|
+
let columns: Column<TData, unknown>[];
|
|
164
|
+
|
|
165
|
+
if (position === 'left') {
|
|
166
|
+
columns = table.getLeftVisibleLeafColumns?.() ?? [];
|
|
167
|
+
} else if (position === 'right') {
|
|
168
|
+
columns = table.getRightVisibleLeafColumns?.() ?? [];
|
|
169
|
+
} else if (position === false) {
|
|
170
|
+
columns = table.getCenterVisibleLeafColumns?.() ?? [];
|
|
171
|
+
} else {
|
|
172
|
+
// Default: use all visible leaf columns
|
|
173
|
+
columns = table.getVisibleLeafColumns?.() ?? table.getAllLeafColumns();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return columns.findIndex((c) => c.id === column.id);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ============================================================================
|
|
180
|
+
// TABLE COLUMN HELPERS
|
|
181
|
+
// ============================================================================
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Build all columns from column definitions
|
|
185
|
+
*/
|
|
186
|
+
export function buildColumns<TData>(
|
|
187
|
+
table: Table<TData>,
|
|
188
|
+
columnDefs: ColumnDef<TData, unknown>[]
|
|
189
|
+
): Column<TData, unknown>[] {
|
|
190
|
+
return columnDefs.map((columnDef, columnIndex) =>
|
|
191
|
+
createColumn(table, columnDef, 0, undefined, [columnIndex])
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Get all columns from column tree
|
|
197
|
+
*/
|
|
198
|
+
export function getAllColumns<TData>(
|
|
199
|
+
columns: Column<TData, unknown>[]
|
|
200
|
+
): Column<TData, unknown>[] {
|
|
201
|
+
return columns;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get all columns flattened (including nested)
|
|
206
|
+
*/
|
|
207
|
+
export function getAllFlatColumns<TData>(
|
|
208
|
+
columns: Column<TData, unknown>[]
|
|
209
|
+
): Column<TData, unknown>[] {
|
|
210
|
+
return flattenBy(columns, (col) =>
|
|
211
|
+
col.columns.length ? col.columns : undefined
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get all leaf columns (no children)
|
|
217
|
+
*/
|
|
218
|
+
export function getAllLeafColumns<TData>(
|
|
219
|
+
columns: Column<TData, unknown>[]
|
|
220
|
+
): Column<TData, unknown>[] {
|
|
221
|
+
return getAllFlatColumns(columns).filter((col) => !col.columns.length);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get column by ID from flat columns
|
|
226
|
+
*/
|
|
227
|
+
export function getColumnById<TData>(
|
|
228
|
+
flatColumns: Column<TData, unknown>[],
|
|
229
|
+
columnId: string
|
|
230
|
+
): Column<TData, unknown> | undefined {
|
|
231
|
+
return flatColumns.find((col) => col.id === columnId);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Order columns by column order state
|
|
236
|
+
*/
|
|
237
|
+
export function orderColumns<TData>(
|
|
238
|
+
columns: Column<TData, unknown>[],
|
|
239
|
+
columnOrder: string[]
|
|
240
|
+
): Column<TData, unknown>[] {
|
|
241
|
+
if (!columnOrder.length) {
|
|
242
|
+
return columns;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Create a map for fast lookup
|
|
246
|
+
const columnMap = new Map(columns.map((col) => [col.id, col]));
|
|
247
|
+
|
|
248
|
+
// Build ordered array
|
|
249
|
+
const ordered: Column<TData, unknown>[] = [];
|
|
250
|
+
|
|
251
|
+
// First, add columns in specified order
|
|
252
|
+
for (const id of columnOrder) {
|
|
253
|
+
const col = columnMap.get(id);
|
|
254
|
+
if (col) {
|
|
255
|
+
ordered.push(col);
|
|
256
|
+
columnMap.delete(id);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Then, add any remaining columns not in order
|
|
261
|
+
for (const col of columnMap.values()) {
|
|
262
|
+
ordered.push(col);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return ordered;
|
|
266
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Header creation and management for sng-table-core
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Header,
|
|
7
|
+
HeaderContext,
|
|
8
|
+
HeaderGroup,
|
|
9
|
+
Column,
|
|
10
|
+
Table,
|
|
11
|
+
ColumnPinningPosition,
|
|
12
|
+
} from './types';
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// HEADER CREATION
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create a header instance for a column
|
|
20
|
+
*/
|
|
21
|
+
export function createHeader<TData, TValue = unknown>(
|
|
22
|
+
table: Table<TData>,
|
|
23
|
+
column: Column<TData, TValue>,
|
|
24
|
+
options: {
|
|
25
|
+
id?: string;
|
|
26
|
+
isPlaceholder?: boolean;
|
|
27
|
+
depth: number;
|
|
28
|
+
index: number;
|
|
29
|
+
}
|
|
30
|
+
): Header<TData, TValue> {
|
|
31
|
+
const id = options.id ?? `${column.id}_${options.depth}`;
|
|
32
|
+
|
|
33
|
+
// Create the header instance
|
|
34
|
+
const header: Header<TData, TValue> = {
|
|
35
|
+
id,
|
|
36
|
+
column,
|
|
37
|
+
depth: options.depth,
|
|
38
|
+
index: options.index,
|
|
39
|
+
isPlaceholder: options.isPlaceholder ?? false,
|
|
40
|
+
colSpan: 1, // Will be calculated
|
|
41
|
+
rowSpan: 1, // Will be calculated
|
|
42
|
+
subHeaders: [], // Will be populated for group columns
|
|
43
|
+
|
|
44
|
+
getContext: () => getHeaderContext(table, column, header),
|
|
45
|
+
|
|
46
|
+
getLeafHeaders: () => getLeafHeaders(header),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return header;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// HEADER CONTEXT
|
|
54
|
+
// ============================================================================
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create the header context for rendering
|
|
58
|
+
*/
|
|
59
|
+
function getHeaderContext<TData, TValue>(
|
|
60
|
+
table: Table<TData>,
|
|
61
|
+
column: Column<TData, TValue>,
|
|
62
|
+
header: Header<TData, TValue>
|
|
63
|
+
): HeaderContext<TData, TValue> {
|
|
64
|
+
return {
|
|
65
|
+
table,
|
|
66
|
+
column,
|
|
67
|
+
header,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// HEADER TRAVERSAL
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get all leaf headers from a header's subtree
|
|
77
|
+
*/
|
|
78
|
+
function getLeafHeaders<TData>(
|
|
79
|
+
header: Header<TData, unknown>
|
|
80
|
+
): Header<TData, unknown>[] {
|
|
81
|
+
if (!header.subHeaders.length) {
|
|
82
|
+
return [header];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return header.subHeaders.flatMap((subHeader) => getLeafHeaders(subHeader));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ============================================================================
|
|
89
|
+
// HEADER GROUPS
|
|
90
|
+
// ============================================================================
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Build header groups from columns
|
|
94
|
+
*
|
|
95
|
+
* Header groups represent rows of headers in the table head.
|
|
96
|
+
* For nested columns, this creates multiple rows with appropriate colSpan/rowSpan.
|
|
97
|
+
*/
|
|
98
|
+
export function buildHeaderGroups<TData>(
|
|
99
|
+
table: Table<TData>,
|
|
100
|
+
columns: Column<TData, unknown>[],
|
|
101
|
+
_position?: ColumnPinningPosition
|
|
102
|
+
): HeaderGroup<TData>[] {
|
|
103
|
+
// Find the maximum depth of nested columns
|
|
104
|
+
const maxDepth = getMaxColumnDepth(columns);
|
|
105
|
+
|
|
106
|
+
// Build header groups from bottom to top
|
|
107
|
+
const headerGroups: HeaderGroup<TData>[] = [];
|
|
108
|
+
|
|
109
|
+
// Build each depth level
|
|
110
|
+
for (let depth = 0; depth <= maxDepth; depth++) {
|
|
111
|
+
const headers: Header<TData, unknown>[] = [];
|
|
112
|
+
|
|
113
|
+
// Get columns that should appear at this depth
|
|
114
|
+
const columnsAtDepth = getColumnsForDepth(columns, depth, maxDepth);
|
|
115
|
+
|
|
116
|
+
columnsAtDepth.forEach((col, index) => {
|
|
117
|
+
const isLeaf = !col.columns.length;
|
|
118
|
+
const actualDepth = col.depth;
|
|
119
|
+
|
|
120
|
+
// Create header for this column
|
|
121
|
+
const header = createHeader(table, col, {
|
|
122
|
+
depth,
|
|
123
|
+
index,
|
|
124
|
+
isPlaceholder: depth < actualDepth, // Placeholder if we're above the column's actual depth
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Calculate spans
|
|
128
|
+
if (isLeaf) {
|
|
129
|
+
// Leaf columns span all remaining rows
|
|
130
|
+
header.rowSpan = maxDepth - actualDepth + 1;
|
|
131
|
+
header.colSpan = 1;
|
|
132
|
+
} else {
|
|
133
|
+
// Group columns span their children
|
|
134
|
+
header.rowSpan = 1;
|
|
135
|
+
header.colSpan = col.getLeafColumns().length;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Build sub-headers for group columns
|
|
139
|
+
if (col.columns.length) {
|
|
140
|
+
header.subHeaders = col.columns.map((childCol, childIndex) =>
|
|
141
|
+
createHeader(table, childCol, {
|
|
142
|
+
depth: depth + 1,
|
|
143
|
+
index: childIndex,
|
|
144
|
+
isPlaceholder: false,
|
|
145
|
+
})
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
headers.push(header);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
headerGroups.push({
|
|
153
|
+
id: `header_group_${depth}`,
|
|
154
|
+
depth,
|
|
155
|
+
headers,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return headerGroups;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get the maximum depth of nested columns
|
|
164
|
+
*/
|
|
165
|
+
function getMaxColumnDepth<TData>(columns: Column<TData, unknown>[]): number {
|
|
166
|
+
let maxDepth = 0;
|
|
167
|
+
|
|
168
|
+
function traverse(cols: Column<TData, unknown>[], currentDepth: number) {
|
|
169
|
+
for (const col of cols) {
|
|
170
|
+
maxDepth = Math.max(maxDepth, currentDepth);
|
|
171
|
+
if (col.columns.length) {
|
|
172
|
+
traverse(col.columns, currentDepth + 1);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
traverse(columns, 0);
|
|
178
|
+
return maxDepth;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Group columns by their depth
|
|
183
|
+
* @internal Reserved for future use
|
|
184
|
+
*/
|
|
185
|
+
function _groupColumnsByDepth<TData>(
|
|
186
|
+
columns: Column<TData, unknown>[]
|
|
187
|
+
): Map<number, Column<TData, unknown>[]> {
|
|
188
|
+
const result = new Map<number, Column<TData, unknown>[]>();
|
|
189
|
+
|
|
190
|
+
function traverse(cols: Column<TData, unknown>[]) {
|
|
191
|
+
for (const col of cols) {
|
|
192
|
+
const depth = col.depth;
|
|
193
|
+
if (!result.has(depth)) {
|
|
194
|
+
result.set(depth, []);
|
|
195
|
+
}
|
|
196
|
+
result.get(depth)!.push(col);
|
|
197
|
+
|
|
198
|
+
if (col.columns.length) {
|
|
199
|
+
traverse(col.columns);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
traverse(columns);
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get columns that should appear at a specific depth level
|
|
210
|
+
* Includes placeholders for columns that appear at deeper levels
|
|
211
|
+
*/
|
|
212
|
+
function getColumnsForDepth<TData>(
|
|
213
|
+
columns: Column<TData, unknown>[],
|
|
214
|
+
targetDepth: number,
|
|
215
|
+
_maxDepth: number
|
|
216
|
+
): Column<TData, unknown>[] {
|
|
217
|
+
const result: Column<TData, unknown>[] = [];
|
|
218
|
+
|
|
219
|
+
function traverse(cols: Column<TData, unknown>[], currentDepth: number) {
|
|
220
|
+
for (const col of cols) {
|
|
221
|
+
if (currentDepth === targetDepth) {
|
|
222
|
+
// This column should appear at this depth
|
|
223
|
+
result.push(col);
|
|
224
|
+
} else if (currentDepth < targetDepth && col.columns.length) {
|
|
225
|
+
// Recurse into children
|
|
226
|
+
traverse(col.columns, currentDepth + 1);
|
|
227
|
+
} else if (currentDepth < targetDepth && !col.columns.length) {
|
|
228
|
+
// Leaf column that starts earlier - need placeholder at this depth
|
|
229
|
+
// But we only show it once at its actual depth with rowSpan
|
|
230
|
+
// So skip here
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
traverse(columns, 0);
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Build flattened header list from header groups
|
|
241
|
+
*/
|
|
242
|
+
export function getFlatHeaders<TData>(
|
|
243
|
+
headerGroups: HeaderGroup<TData>[]
|
|
244
|
+
): Header<TData, unknown>[] {
|
|
245
|
+
return headerGroups.flatMap((group) => group.headers);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get leaf headers (headers without sub-headers)
|
|
250
|
+
*/
|
|
251
|
+
export function getLeafHeadersFromGroups<TData>(
|
|
252
|
+
headerGroups: HeaderGroup<TData>[]
|
|
253
|
+
): Header<TData, unknown>[] {
|
|
254
|
+
const lastGroup = headerGroups[headerGroups.length - 1];
|
|
255
|
+
return lastGroup?.headers ?? [];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Build footer groups (same structure as header groups, just from bottom of table)
|
|
260
|
+
*/
|
|
261
|
+
export function buildFooterGroups<TData>(
|
|
262
|
+
table: Table<TData>,
|
|
263
|
+
columns: Column<TData, unknown>[]
|
|
264
|
+
): HeaderGroup<TData>[] {
|
|
265
|
+
// Footers are built the same way as headers
|
|
266
|
+
// The difference is in how they're rendered (at bottom of table)
|
|
267
|
+
return buildHeaderGroups(table, columns).map((group) => ({
|
|
268
|
+
...group,
|
|
269
|
+
id: `footer_group_${group.depth}`,
|
|
270
|
+
}));
|
|
271
|
+
}
|