@proyecto-viviana/solidaria-components 0.2.9 → 0.3.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/README.md +39 -272
- package/dist/ActionBar.d.ts +21 -13
- package/dist/ActionBar.d.ts.map +1 -1
- package/dist/ActionGroup.d.ts +8 -8
- package/dist/ActionGroup.d.ts.map +1 -1
- package/dist/Alert.d.ts +5 -5
- package/dist/Alert.d.ts.map +1 -1
- package/dist/Autocomplete.d.ts +5 -5
- package/dist/Autocomplete.d.ts.map +1 -1
- package/dist/Breadcrumbs.d.ts +18 -7
- package/dist/Breadcrumbs.d.ts.map +1 -1
- package/dist/Button.d.ts +24 -5
- package/dist/Button.d.ts.map +1 -1
- package/dist/Calendar.d.ts +38 -7
- package/dist/Calendar.d.ts.map +1 -1
- package/dist/Checkbox.d.ts +32 -7
- package/dist/Checkbox.d.ts.map +1 -1
- package/dist/Collection.d.ts +19 -14
- package/dist/Collection.d.ts.map +1 -1
- package/dist/Color.d.ts +103 -14
- package/dist/Color.d.ts.map +1 -1
- package/dist/ColorEditor.d.ts +6 -6
- package/dist/ColorEditor.d.ts.map +1 -1
- package/dist/ComboBox.d.ts +85 -19
- package/dist/ComboBox.d.ts.map +1 -1
- package/dist/ContextualHelpTrigger.d.ts +2 -2
- package/dist/ContextualHelpTrigger.d.ts.map +1 -1
- package/dist/DateField.d.ts +8 -6
- package/dist/DateField.d.ts.map +1 -1
- package/dist/DatePicker.d.ts +53 -22
- package/dist/DatePicker.d.ts.map +1 -1
- package/dist/DateRangePickerContext.d.ts +30 -0
- package/dist/DateRangePickerContext.d.ts.map +1 -0
- package/dist/Dialog.d.ts +5 -5
- package/dist/Dialog.d.ts.map +1 -1
- package/dist/Disclosure.d.ts +23 -5
- package/dist/Disclosure.d.ts.map +1 -1
- package/dist/DragAndDrop.d.ts +6 -6
- package/dist/DragAndDrop.d.ts.map +1 -1
- package/dist/DragPreview.d.ts +2 -2
- package/dist/DragPreview.d.ts.map +1 -1
- package/dist/DropZone.d.ts +4 -4
- package/dist/DropZone.d.ts.map +1 -1
- package/dist/FieldError.d.ts +9 -5
- package/dist/FieldError.d.ts.map +1 -1
- package/dist/FileTrigger.d.ts +3 -3
- package/dist/FileTrigger.d.ts.map +1 -1
- package/dist/Focusable.d.ts +2 -2
- package/dist/Focusable.d.ts.map +1 -1
- package/dist/Form.d.ts +18 -4
- package/dist/Form.d.ts.map +1 -1
- package/dist/GridList.d.ts +32 -12
- package/dist/GridList.d.ts.map +1 -1
- package/dist/HiddenDateInput.d.ts +26 -0
- package/dist/HiddenDateInput.d.ts.map +1 -0
- package/dist/HiddenTimeInput.d.ts +25 -0
- package/dist/HiddenTimeInput.d.ts.map +1 -0
- package/dist/Icon.d.ts +5 -5
- package/dist/Icon.d.ts.map +1 -1
- package/dist/Keyboard.d.ts +1 -1
- package/dist/Landmark.d.ts +3 -3
- package/dist/Landmark.d.ts.map +1 -1
- package/dist/Link.d.ts +10 -4
- package/dist/Link.d.ts.map +1 -1
- package/dist/ListBox.d.ts +32 -12
- package/dist/ListBox.d.ts.map +1 -1
- package/dist/ListDropTargetDelegate.d.ts +6 -6
- package/dist/ListDropTargetDelegate.d.ts.map +1 -1
- package/dist/Menu.d.ts +65 -14
- package/dist/Menu.d.ts.map +1 -1
- package/dist/Meter.d.ts +3 -3
- package/dist/Meter.d.ts.map +1 -1
- package/dist/Modal.d.ts +5 -5
- package/dist/Modal.d.ts.map +1 -1
- package/dist/NumberField.d.ts +8 -12
- package/dist/NumberField.d.ts.map +1 -1
- package/dist/Popover.d.ts +28 -5
- package/dist/Popover.d.ts.map +1 -1
- package/dist/Pressable.d.ts +2 -2
- package/dist/Pressable.d.ts.map +1 -1
- package/dist/ProgressBar.d.ts +5 -3
- package/dist/ProgressBar.d.ts.map +1 -1
- package/dist/RadioGroup.d.ts +43 -9
- package/dist/RadioGroup.d.ts.map +1 -1
- package/dist/RangeCalendar.d.ts +34 -7
- package/dist/RangeCalendar.d.ts.map +1 -1
- package/dist/RouterProvider.d.ts +2 -2
- package/dist/RouterProvider.d.ts.map +1 -1
- package/dist/SearchField.d.ts +23 -20
- package/dist/SearchField.d.ts.map +1 -1
- package/dist/Select.d.ts +41 -11
- package/dist/Select.d.ts.map +1 -1
- package/dist/SelectionIndicator.d.ts +3 -3
- package/dist/SelectionIndicator.d.ts.map +1 -1
- package/dist/Separator.d.ts +9 -3
- package/dist/Separator.d.ts.map +1 -1
- package/dist/SharedElementTransition.d.ts +6 -4
- package/dist/SharedElementTransition.d.ts.map +1 -1
- package/dist/Slider.d.ts +12 -8
- package/dist/Slider.d.ts.map +1 -1
- package/dist/StepList.d.ts +90 -0
- package/dist/StepList.d.ts.map +1 -0
- package/dist/Switch.d.ts +11 -5
- package/dist/Switch.d.ts.map +1 -1
- package/dist/Table.d.ts +187 -23
- package/dist/Table.d.ts.map +1 -1
- package/dist/Tabs.d.ts +45 -9
- package/dist/Tabs.d.ts.map +1 -1
- package/dist/TagGroup.d.ts +12 -10
- package/dist/TagGroup.d.ts.map +1 -1
- package/dist/Text.d.ts +2 -2
- package/dist/TextField.d.ts +15 -11
- package/dist/TextField.d.ts.map +1 -1
- package/dist/TimeField.d.ts +6 -6
- package/dist/TimeField.d.ts.map +1 -1
- package/dist/Toast.d.ts +29 -14
- package/dist/Toast.d.ts.map +1 -1
- package/dist/ToggleButton.d.ts +11 -5
- package/dist/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButtonGroup.d.ts +7 -7
- package/dist/ToggleButtonGroup.d.ts.map +1 -1
- package/dist/Toolbar.d.ts +7 -3
- package/dist/Toolbar.d.ts.map +1 -1
- package/dist/Tooltip.d.ts +50 -8
- package/dist/Tooltip.d.ts.map +1 -1
- package/dist/Tree.d.ts +66 -17
- package/dist/Tree.d.ts.map +1 -1
- package/dist/Virtualizer.d.ts +12 -12
- package/dist/Virtualizer.d.ts.map +1 -1
- package/dist/VirtualizerLayouts.d.ts +2 -2
- package/dist/VirtualizerLayouts.d.ts.map +1 -1
- package/dist/VisuallyHidden.d.ts +1 -1
- package/dist/VisuallyHidden.d.ts.map +1 -1
- package/dist/contexts.d.ts +5 -1
- package/dist/contexts.d.ts.map +1 -1
- package/dist/index.d.ts +73 -71
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23247 -18564
- package/dist/index.js.map +1 -1
- package/dist/index.jsx +18110 -0
- package/dist/index.jsx.map +1 -0
- package/dist/useDragAndDrop.d.ts +13 -13
- package/dist/useDragAndDrop.d.ts.map +1 -1
- package/dist/utils.d.ts +2 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/virtualizer/Layout.d.ts +1 -1
- package/dist/virtualizer/Layout.d.ts.map +1 -1
- package/package.json +31 -32
- package/src/ActionBar.tsx +75 -72
- package/src/ActionGroup.tsx +53 -61
- package/src/Alert.tsx +17 -42
- package/src/Autocomplete.tsx +39 -44
- package/src/Breadcrumbs.tsx +149 -80
- package/src/Button.tsx +267 -70
- package/src/Calendar.tsx +218 -138
- package/src/Checkbox.tsx +413 -121
- package/src/Collection.tsx +67 -58
- package/src/Color.tsx +803 -380
- package/src/ColorEditor.tsx +131 -149
- package/src/ComboBox.tsx +414 -249
- package/src/ContextualHelpTrigger.tsx +86 -74
- package/src/DateField.tsx +185 -91
- package/src/DatePicker.tsx +524 -213
- package/src/DateRangePickerContext.tsx +44 -0
- package/src/Dialog.tsx +156 -118
- package/src/Disclosure.tsx +127 -80
- package/src/DragAndDrop.tsx +60 -54
- package/src/DragPreview.tsx +13 -11
- package/src/DropZone.tsx +42 -22
- package/src/FieldError.tsx +45 -23
- package/src/FileTrigger.tsx +19 -19
- package/src/Focusable.tsx +21 -24
- package/src/Form.tsx +71 -16
- package/src/GridList.tsx +273 -197
- package/src/HiddenDateInput.tsx +153 -0
- package/src/HiddenTimeInput.tsx +133 -0
- package/src/Icon.tsx +22 -43
- package/src/Keyboard.tsx +3 -3
- package/src/Landmark.tsx +37 -63
- package/src/Link.tsx +125 -75
- package/src/ListBox.tsx +332 -233
- package/src/ListDropTargetDelegate.ts +81 -80
- package/src/Menu.tsx +1023 -274
- package/src/Meter.tsx +38 -56
- package/src/Modal.tsx +243 -175
- package/src/NumberField.tsx +139 -143
- package/src/Popover.tsx +386 -233
- package/src/Pressable.tsx +21 -21
- package/src/ProgressBar.tsx +48 -57
- package/src/RadioGroup.tsx +524 -122
- package/src/RangeCalendar.tsx +157 -90
- package/src/RouterProvider.tsx +30 -47
- package/src/SearchField.tsx +362 -143
- package/src/Select.tsx +656 -233
- package/src/SelectionIndicator.tsx +18 -15
- package/src/Separator.tsx +47 -49
- package/src/SharedElementTransition.tsx +103 -97
- package/src/Slider.tsx +138 -98
- package/src/StepList.tsx +272 -0
- package/src/Switch.tsx +93 -46
- package/src/Table.tsx +1308 -342
- package/src/Tabs.tsx +324 -103
- package/src/TagGroup.tsx +139 -126
- package/src/Text.tsx +3 -3
- package/src/TextField.tsx +389 -79
- package/src/TimeField.tsx +136 -76
- package/src/Toast.tsx +209 -157
- package/src/ToggleButton.tsx +47 -37
- package/src/ToggleButtonGroup.tsx +39 -34
- package/src/Toolbar.tsx +54 -69
- package/src/Tooltip.tsx +387 -119
- package/src/Tree.tsx +651 -368
- package/src/Virtualizer.tsx +208 -180
- package/src/VirtualizerLayouts.ts +45 -30
- package/src/VisuallyHidden.tsx +19 -19
- package/src/contexts.ts +29 -37
- package/src/index.ts +110 -195
- package/src/useDragAndDrop.ts +87 -71
- package/src/utils.tsx +40 -55
- package/src/virtualizer/Layout.ts +14 -22
- package/dist/index.ssr.js +0 -16996
- package/dist/index.ssr.js.map +0 -1
package/src/Table.tsx
CHANGED
|
@@ -10,13 +10,14 @@ import {
|
|
|
10
10
|
createContext,
|
|
11
11
|
createEffect,
|
|
12
12
|
createMemo,
|
|
13
|
+
createUniqueId,
|
|
13
14
|
createSignal,
|
|
14
15
|
onCleanup,
|
|
15
16
|
splitProps,
|
|
16
17
|
useContext,
|
|
17
18
|
For,
|
|
18
19
|
Show,
|
|
19
|
-
} from
|
|
20
|
+
} from "solid-js";
|
|
20
21
|
import {
|
|
21
22
|
createTable,
|
|
22
23
|
createTableColumnHeader,
|
|
@@ -26,43 +27,58 @@ import {
|
|
|
26
27
|
createTableSelectionCheckbox,
|
|
27
28
|
createTableSelectAllCheckbox,
|
|
28
29
|
createFocusRing,
|
|
30
|
+
isFocusVisible as isGlobalFocusVisible,
|
|
31
|
+
getTableData,
|
|
29
32
|
createHover,
|
|
30
33
|
mergeProps,
|
|
31
34
|
type AriaTableProps,
|
|
32
|
-
|
|
35
|
+
createTableColumnResize,
|
|
36
|
+
} from "@proyecto-viviana/solidaria";
|
|
33
37
|
import {
|
|
34
38
|
createTableState,
|
|
35
39
|
createTableCollection,
|
|
40
|
+
createTableColumnResizeState,
|
|
36
41
|
type TableState,
|
|
37
42
|
type TableCollection,
|
|
43
|
+
type TableColumnResizeState,
|
|
38
44
|
type Key,
|
|
39
45
|
type SortDescriptor,
|
|
40
46
|
type ColumnDefinition,
|
|
47
|
+
type ColumnSize,
|
|
41
48
|
type GridNode,
|
|
42
49
|
type DropTarget,
|
|
43
|
-
} from
|
|
50
|
+
} from "@proyecto-viviana/solid-stately";
|
|
44
51
|
import {
|
|
45
52
|
type RenderChildren,
|
|
46
53
|
type ClassNameOrFunction,
|
|
47
54
|
type StyleOrFunction,
|
|
48
55
|
type SlotProps,
|
|
56
|
+
dataAttr,
|
|
49
57
|
useRenderProps,
|
|
50
58
|
filterDOMProps,
|
|
51
|
-
} from
|
|
52
|
-
import { SharedElementTransition } from
|
|
53
|
-
import { type DragAndDropHooks } from
|
|
54
|
-
import {
|
|
55
|
-
import {
|
|
59
|
+
} from "./utils";
|
|
60
|
+
import { SharedElementTransition } from "./SharedElementTransition";
|
|
61
|
+
import { type DragAndDropHooks } from "./useDragAndDrop";
|
|
62
|
+
import { ButtonContext, type ButtonProps } from "./Button";
|
|
63
|
+
import {
|
|
64
|
+
CollectionRendererContext,
|
|
65
|
+
type CollectionRendererContextValue,
|
|
66
|
+
useCollectionRenderer,
|
|
67
|
+
} from "./Collection";
|
|
68
|
+
import { useVirtualizerContext } from "./Virtualizer";
|
|
69
|
+
import {
|
|
70
|
+
type LinkDOMProps,
|
|
71
|
+
type RouterOptions,
|
|
72
|
+
useRouter,
|
|
73
|
+
useLinkProps,
|
|
74
|
+
type RouterClickModifiers,
|
|
75
|
+
} from "./RouterProvider";
|
|
56
76
|
import {
|
|
57
77
|
getNormalizedDropTargetKey,
|
|
58
78
|
mergePersistedKeysIntoVirtualRange,
|
|
59
79
|
useDndPersistedKeys,
|
|
60
80
|
useRenderDropIndicator,
|
|
61
|
-
} from
|
|
62
|
-
|
|
63
|
-
// ============================================
|
|
64
|
-
// TYPES
|
|
65
|
-
// ============================================
|
|
81
|
+
} from "./DragAndDrop";
|
|
66
82
|
|
|
67
83
|
export interface TableRenderProps {
|
|
68
84
|
/** Whether the table has focus. */
|
|
@@ -75,25 +91,84 @@ export interface TableRenderProps {
|
|
|
75
91
|
isEmpty: boolean;
|
|
76
92
|
}
|
|
77
93
|
|
|
78
|
-
|
|
94
|
+
type RefLike<T> = ((el: T) => void) | { current?: T | null } | undefined;
|
|
95
|
+
export type TableColumnDefinition<T = unknown> = Omit<ColumnDefinition<T>, "key" | "children"> & {
|
|
96
|
+
/** React Spectrum-style alias for the column key. */
|
|
97
|
+
id?: Key;
|
|
98
|
+
key?: Key;
|
|
99
|
+
children?: TableColumnDefinition<T>[];
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
function assignRef<T>(ref: RefLike<T>, el: T): void {
|
|
103
|
+
if (!ref) return;
|
|
104
|
+
if (typeof ref === "function") ref(el);
|
|
105
|
+
else ref.current = el;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function normalizeColumnDefinitions<T>(
|
|
109
|
+
columns: TableColumnDefinition<T>[],
|
|
110
|
+
parentKey?: Key,
|
|
111
|
+
): (ColumnDefinition<T> & { id?: Key })[] {
|
|
112
|
+
return columns.map((column, index) => {
|
|
113
|
+
const key = column.key ?? column.id ?? (parentKey == null ? index : `${parentKey}-${index}`);
|
|
114
|
+
const id = column.id ?? column.key;
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
...column,
|
|
118
|
+
key,
|
|
119
|
+
id,
|
|
120
|
+
children: column.children ? normalizeColumnDefinitions(column.children, key) : undefined,
|
|
121
|
+
} as ColumnDefinition<T> & { id?: Key };
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function getRowHeaderColumnKeys<T>(columns: (ColumnDefinition<T> & { id?: Key })[]): Set<Key> {
|
|
126
|
+
const keys = new Set<Key>();
|
|
127
|
+
|
|
128
|
+
for (const column of columns) {
|
|
129
|
+
if (column.isRowHeader) {
|
|
130
|
+
keys.add(column.key);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (column.children) {
|
|
134
|
+
for (const key of getRowHeaderColumnKeys(
|
|
135
|
+
column.children as (ColumnDefinition<T> & {
|
|
136
|
+
id?: Key;
|
|
137
|
+
})[],
|
|
138
|
+
)) {
|
|
139
|
+
keys.add(key);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return keys;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface TableProps<T extends object> extends Omit<AriaTableProps, "children">, SlotProps {
|
|
79
148
|
/** The data items to render in the table. */
|
|
80
149
|
items: T[];
|
|
81
150
|
/** The column definitions. */
|
|
82
|
-
columns:
|
|
151
|
+
columns: TableColumnDefinition<T>[];
|
|
83
152
|
/** Function to get the key from an item. */
|
|
84
153
|
getKey?: (item: T) => Key;
|
|
85
154
|
/** Function to get the text value from an item for a column. */
|
|
86
|
-
getTextValue?: (item: T, column:
|
|
155
|
+
getTextValue?: (item: T, column: TableColumnDefinition<T>) => string;
|
|
87
156
|
/** The selection mode. */
|
|
88
|
-
selectionMode?:
|
|
157
|
+
selectionMode?: "none" | "single" | "multiple";
|
|
158
|
+
/** The selection behavior (toggle vs replace). */
|
|
159
|
+
selectionBehavior?: "toggle" | "replace";
|
|
160
|
+
/** Whether disabled rows remain focusable. */
|
|
161
|
+
disabledBehavior?: "selection" | "all";
|
|
162
|
+
/** Whether Escape clears selection. */
|
|
163
|
+
escapeKeyBehavior?: "clearSelection" | "none";
|
|
89
164
|
/** Keys of disabled items. */
|
|
90
165
|
disabledKeys?: Iterable<Key>;
|
|
91
166
|
/** Currently selected keys (controlled). */
|
|
92
|
-
selectedKeys?:
|
|
167
|
+
selectedKeys?: "all" | Iterable<Key>;
|
|
93
168
|
/** Default selected keys (uncontrolled). */
|
|
94
|
-
defaultSelectedKeys?:
|
|
169
|
+
defaultSelectedKeys?: "all" | Iterable<Key>;
|
|
95
170
|
/** Handler called when selection changes. */
|
|
96
|
-
onSelectionChange?: (keys:
|
|
171
|
+
onSelectionChange?: (keys: "all" | Set<Key>) => void;
|
|
97
172
|
/** The current sort descriptor. */
|
|
98
173
|
sortDescriptor?: SortDescriptor;
|
|
99
174
|
/** Handler called when sort changes. */
|
|
@@ -108,6 +183,13 @@ export interface TableProps<T extends object> extends Omit<AriaTableProps, 'chil
|
|
|
108
183
|
style?: StyleOrFunction<TableRenderProps>;
|
|
109
184
|
/** A function to render when the table is empty. */
|
|
110
185
|
renderEmptyState?: () => JSX.Element;
|
|
186
|
+
/** Ref for the table element. */
|
|
187
|
+
ref?: RefLike<HTMLTableElement>;
|
|
188
|
+
/** Custom renderer for the table element. */
|
|
189
|
+
render?: (
|
|
190
|
+
props: JSX.HTMLAttributes<HTMLTableElement>,
|
|
191
|
+
renderProps: TableRenderProps,
|
|
192
|
+
) => JSX.Element;
|
|
111
193
|
/** Drag and drop hooks from `useDragAndDrop`. */
|
|
112
194
|
dragAndDropHooks?: DragAndDropHooks<T>;
|
|
113
195
|
}
|
|
@@ -115,6 +197,8 @@ export interface TableProps<T extends object> extends Omit<AriaTableProps, 'chil
|
|
|
115
197
|
export interface TableHeaderRenderProps {
|
|
116
198
|
/** Whether the header has focus. */
|
|
117
199
|
isFocused: boolean;
|
|
200
|
+
/** Whether the header is being hovered. */
|
|
201
|
+
isHovered: boolean;
|
|
118
202
|
}
|
|
119
203
|
|
|
120
204
|
export interface TableHeaderProps extends SlotProps {
|
|
@@ -124,6 +208,13 @@ export interface TableHeaderProps extends SlotProps {
|
|
|
124
208
|
class?: ClassNameOrFunction<TableHeaderRenderProps>;
|
|
125
209
|
/** The inline style for the element. */
|
|
126
210
|
style?: StyleOrFunction<TableHeaderRenderProps>;
|
|
211
|
+
/** Ref for the table header element. */
|
|
212
|
+
ref?: RefLike<HTMLTableSectionElement>;
|
|
213
|
+
/** Custom renderer for the table header element. */
|
|
214
|
+
render?: (
|
|
215
|
+
props: JSX.HTMLAttributes<HTMLTableSectionElement>,
|
|
216
|
+
renderProps: TableHeaderRenderProps,
|
|
217
|
+
) => JSX.Element;
|
|
127
218
|
}
|
|
128
219
|
|
|
129
220
|
export interface TableColumnRenderProps {
|
|
@@ -134,9 +225,13 @@ export interface TableColumnRenderProps {
|
|
|
134
225
|
/** Whether the column is sortable. */
|
|
135
226
|
isSortable: boolean;
|
|
136
227
|
/** The current sort direction ('ascending', 'descending', or undefined). */
|
|
137
|
-
sortDirection:
|
|
228
|
+
sortDirection: "ascending" | "descending" | undefined;
|
|
138
229
|
/** Whether the column is being hovered. */
|
|
139
230
|
isHovered: boolean;
|
|
231
|
+
/** Whether the column allows resizing. */
|
|
232
|
+
allowsResizing: boolean;
|
|
233
|
+
/** Whether the column is currently being resized. */
|
|
234
|
+
isResizing: boolean;
|
|
140
235
|
}
|
|
141
236
|
|
|
142
237
|
export interface TableColumnProps extends SlotProps {
|
|
@@ -144,12 +239,29 @@ export interface TableColumnProps extends SlotProps {
|
|
|
144
239
|
id: Key;
|
|
145
240
|
/** Whether the column allows sorting. */
|
|
146
241
|
allowsSorting?: boolean;
|
|
242
|
+
/** Whether the column allows resizing. */
|
|
243
|
+
allowsResizing?: boolean;
|
|
244
|
+
/** Column width (number for px, string for 'Xfr', 'X%', 'Xpx'). */
|
|
245
|
+
width?: ColumnSize;
|
|
246
|
+
/** Default width for uncontrolled mode. */
|
|
247
|
+
defaultWidth?: ColumnSize;
|
|
248
|
+
/** Minimum column width in px. */
|
|
249
|
+
minWidth?: number;
|
|
250
|
+
/** Maximum column width in px. */
|
|
251
|
+
maxWidth?: number;
|
|
147
252
|
/** The children of the column. */
|
|
148
253
|
children?: RenderChildren<TableColumnRenderProps>;
|
|
149
254
|
/** The CSS className for the element. */
|
|
150
255
|
class?: ClassNameOrFunction<TableColumnRenderProps>;
|
|
151
256
|
/** The inline style for the element. */
|
|
152
257
|
style?: StyleOrFunction<TableColumnRenderProps>;
|
|
258
|
+
/** Ref for the column header element. */
|
|
259
|
+
ref?: RefLike<HTMLTableCellElement>;
|
|
260
|
+
/** Custom renderer for the column header element. */
|
|
261
|
+
render?: (
|
|
262
|
+
props: JSX.ThHTMLAttributes<HTMLTableCellElement>,
|
|
263
|
+
renderProps: TableColumnRenderProps,
|
|
264
|
+
) => JSX.Element;
|
|
153
265
|
}
|
|
154
266
|
|
|
155
267
|
export interface TableBodyRenderProps {
|
|
@@ -174,6 +286,29 @@ export interface TableBodyProps<T> extends SlotProps {
|
|
|
174
286
|
isLoading?: boolean;
|
|
175
287
|
/** Called when the load more sentinel becomes visible. */
|
|
176
288
|
onLoadMore?: () => void | Promise<void>;
|
|
289
|
+
/** Ref for the table body element. */
|
|
290
|
+
ref?: RefLike<HTMLTableSectionElement>;
|
|
291
|
+
/** Custom renderer for the table body element. */
|
|
292
|
+
render?: (
|
|
293
|
+
props: JSX.HTMLAttributes<HTMLTableSectionElement>,
|
|
294
|
+
renderProps: TableBodyRenderProps,
|
|
295
|
+
) => JSX.Element;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export interface TableFooterRenderProps {
|
|
299
|
+
/** Whether the footer has no items. */
|
|
300
|
+
isEmpty: boolean;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export interface TableFooterProps<T> extends SlotProps {
|
|
304
|
+
/** The footer items to render. */
|
|
305
|
+
items?: T[];
|
|
306
|
+
/** The children, or a render function when `items` is provided. */
|
|
307
|
+
children?: JSX.Element | ((item: T) => JSX.Element);
|
|
308
|
+
/** The CSS className for the element. */
|
|
309
|
+
class?: ClassNameOrFunction<TableFooterRenderProps>;
|
|
310
|
+
/** The inline style for the element. */
|
|
311
|
+
style?: StyleOrFunction<TableFooterRenderProps>;
|
|
177
312
|
}
|
|
178
313
|
|
|
179
314
|
export interface TableRowRenderProps {
|
|
@@ -193,17 +328,45 @@ export interface TableRowRenderProps {
|
|
|
193
328
|
|
|
194
329
|
export interface TableRowProps<T> extends SlotProps {
|
|
195
330
|
/** The unique key for the row. */
|
|
196
|
-
id
|
|
331
|
+
id?: Key;
|
|
197
332
|
/** The item value. */
|
|
198
333
|
item?: T;
|
|
334
|
+
/** Columns to render when children is a column render function. */
|
|
335
|
+
columns?: TableColumnDefinition<T>[];
|
|
336
|
+
/** Whether the row is disabled. */
|
|
337
|
+
isDisabled?: boolean;
|
|
199
338
|
/** The children of the row (usually TableCell components). */
|
|
200
|
-
children?:
|
|
339
|
+
children?:
|
|
340
|
+
| JSX.Element
|
|
341
|
+
| RenderChildren<TableRowRenderProps>
|
|
342
|
+
| ((column: TableColumnDefinition<T>) => JSX.Element);
|
|
201
343
|
/** The CSS className for the element. */
|
|
202
344
|
class?: ClassNameOrFunction<TableRowRenderProps>;
|
|
203
345
|
/** The inline style for the element. */
|
|
204
346
|
style?: StyleOrFunction<TableRowRenderProps>;
|
|
205
347
|
/** Handler called when the row is activated (double-click or Enter). */
|
|
206
348
|
onAction?: () => void;
|
|
349
|
+
/** The URL this row links to. */
|
|
350
|
+
href?: string;
|
|
351
|
+
/** Link target for linked rows. */
|
|
352
|
+
target?: LinkDOMProps["target"];
|
|
353
|
+
/** Link relationship for linked rows. */
|
|
354
|
+
rel?: LinkDOMProps["rel"];
|
|
355
|
+
/** Download attribute for linked rows. */
|
|
356
|
+
download?: LinkDOMProps["download"];
|
|
357
|
+
/** Ping attribute for linked rows. */
|
|
358
|
+
ping?: LinkDOMProps["ping"];
|
|
359
|
+
/** Referrer policy for linked rows. */
|
|
360
|
+
referrerPolicy?: LinkDOMProps["referrerPolicy"];
|
|
361
|
+
/** Router options for linked rows. */
|
|
362
|
+
routerOptions?: RouterOptions;
|
|
363
|
+
/** Ref for the table row element. */
|
|
364
|
+
ref?: RefLike<HTMLTableRowElement>;
|
|
365
|
+
/** Custom renderer for the table row element. */
|
|
366
|
+
render?: (
|
|
367
|
+
props: JSX.HTMLAttributes<HTMLTableRowElement>,
|
|
368
|
+
renderProps: TableRowRenderProps,
|
|
369
|
+
) => JSX.Element;
|
|
207
370
|
}
|
|
208
371
|
|
|
209
372
|
export interface TableCellRenderProps {
|
|
@@ -211,6 +374,8 @@ export interface TableCellRenderProps {
|
|
|
211
374
|
isFocused: boolean;
|
|
212
375
|
/** Whether the cell has keyboard focus. */
|
|
213
376
|
isFocusVisible: boolean;
|
|
377
|
+
/** The zero-based column index for the cell. */
|
|
378
|
+
columnIndex: number;
|
|
214
379
|
/** Whether the cell is pressed. */
|
|
215
380
|
isPressed: boolean;
|
|
216
381
|
/** Whether the cell is hovered. */
|
|
@@ -220,55 +385,69 @@ export interface TableCellRenderProps {
|
|
|
220
385
|
export interface TableCellProps extends SlotProps {
|
|
221
386
|
/** The unique key for the cell. */
|
|
222
387
|
id?: Key;
|
|
388
|
+
/** Number of columns spanned by the cell. */
|
|
389
|
+
colSpan?: number;
|
|
223
390
|
/** The children of the cell. */
|
|
224
391
|
children?: RenderChildren<TableCellRenderProps>;
|
|
225
392
|
/** The CSS className for the element. */
|
|
226
393
|
class?: ClassNameOrFunction<TableCellRenderProps>;
|
|
227
394
|
/** The inline style for the element. */
|
|
228
395
|
style?: StyleOrFunction<TableCellRenderProps>;
|
|
396
|
+
/** Ref for the table cell element. */
|
|
397
|
+
ref?: RefLike<HTMLTableCellElement>;
|
|
398
|
+
/** Custom renderer for the table cell element. */
|
|
399
|
+
render?: (
|
|
400
|
+
props: JSX.TdHTMLAttributes<HTMLTableCellElement>,
|
|
401
|
+
renderProps: TableCellRenderProps,
|
|
402
|
+
) => JSX.Element;
|
|
229
403
|
}
|
|
230
404
|
|
|
231
405
|
export interface TableLoadMoreItemProps extends SlotProps {
|
|
232
406
|
onLoadMore: () => void | Promise<void>;
|
|
233
407
|
isLoading?: boolean;
|
|
408
|
+
/** Scroll offset multiplier for early loading trigger (default: 1 = 100% of viewport height). */
|
|
409
|
+
scrollOffset?: number;
|
|
234
410
|
colSpan?: number;
|
|
235
411
|
children?: JSX.Element;
|
|
236
412
|
class?: ClassNameOrFunction<{ isLoading: boolean }>;
|
|
237
413
|
style?: StyleOrFunction<{ isLoading: boolean }>;
|
|
238
414
|
}
|
|
239
415
|
|
|
240
|
-
// ============================================
|
|
241
|
-
// CONTEXT
|
|
242
|
-
// ============================================
|
|
243
|
-
|
|
244
416
|
interface TableContextValue<T extends object> {
|
|
245
417
|
state: TableState<T, TableCollection<T>>;
|
|
246
418
|
collection: TableCollection<T>;
|
|
247
419
|
items: T[];
|
|
248
|
-
columns: ColumnDefinition<T>[];
|
|
420
|
+
columns: (ColumnDefinition<T> & { id?: Key })[];
|
|
249
421
|
isDisabled: boolean;
|
|
250
422
|
showSelectionCheckboxes: boolean;
|
|
251
423
|
dragAndDropHooks?: DragAndDropHooks<T>;
|
|
252
424
|
dragState?: unknown;
|
|
253
425
|
dropState?: unknown;
|
|
426
|
+
isVirtualized: boolean;
|
|
254
427
|
}
|
|
255
428
|
|
|
256
429
|
export const TableContext = createContext<TableContextValue<object> | null>(null);
|
|
257
|
-
export const TableStateContext = createContext<TableState<object, TableCollection<object>> | null>(
|
|
258
|
-
|
|
430
|
+
export const TableStateContext = createContext<TableState<object, TableCollection<object>> | null>(
|
|
431
|
+
null,
|
|
432
|
+
);
|
|
433
|
+
/** The resize context carries a getter for the resize state. The getter may return null before columns register. */
|
|
434
|
+
export const TableColumnResizeStateContext = createContext<{
|
|
435
|
+
getState: () => TableColumnResizeState | null;
|
|
436
|
+
getCallbacks?: () => {
|
|
437
|
+
onResizeStart?: (widths: Map<Key, number>) => void;
|
|
438
|
+
onResize?: (widths: Map<Key, number>) => void;
|
|
439
|
+
onResizeEnd?: (widths: Map<Key, number>) => void;
|
|
440
|
+
};
|
|
441
|
+
} | null>(null);
|
|
259
442
|
|
|
260
|
-
// Row-level context for cells
|
|
261
443
|
interface TableRowContextValue {
|
|
262
444
|
rowKey: Key;
|
|
263
445
|
rowNode: GridNode<unknown>;
|
|
446
|
+
getCellColumnKey(cellId: string, explicitId?: Key): Key | undefined;
|
|
264
447
|
}
|
|
265
448
|
|
|
266
449
|
export const TableRowContext = createContext<TableRowContextValue | null>(null);
|
|
267
450
|
|
|
268
|
-
// ============================================
|
|
269
|
-
// COMPONENTS
|
|
270
|
-
// ============================================
|
|
271
|
-
|
|
272
451
|
/**
|
|
273
452
|
* A table displays data in rows and columns and enables a user to navigate its contents via directional navigation keys,
|
|
274
453
|
* and optionally supports row selection and sorting.
|
|
@@ -276,42 +455,48 @@ export const TableRowContext = createContext<TableRowContextValue | null>(null);
|
|
|
276
455
|
export function Table<T extends object>(props: TableProps<T>): JSX.Element {
|
|
277
456
|
const [local, stateProps, ariaProps] = splitProps(
|
|
278
457
|
props,
|
|
279
|
-
[
|
|
458
|
+
["class", "style", "render", "slot", "renderEmptyState", "dragAndDropHooks", "ref"],
|
|
280
459
|
[
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
460
|
+
"items",
|
|
461
|
+
"columns",
|
|
462
|
+
"getKey",
|
|
463
|
+
"getTextValue",
|
|
464
|
+
"disabledKeys",
|
|
465
|
+
"disabledBehavior",
|
|
466
|
+
"escapeKeyBehavior",
|
|
467
|
+
"selectionMode",
|
|
468
|
+
"selectionBehavior",
|
|
469
|
+
"selectedKeys",
|
|
470
|
+
"defaultSelectedKeys",
|
|
471
|
+
"onSelectionChange",
|
|
472
|
+
"sortDescriptor",
|
|
473
|
+
"onSortChange",
|
|
474
|
+
"showSelectionCheckboxes",
|
|
475
|
+
],
|
|
294
476
|
);
|
|
295
477
|
|
|
296
|
-
// Create ref signal
|
|
297
478
|
const [ref, setRef] = createSignal<HTMLTableElement | null>(null);
|
|
479
|
+
const normalizedColumns = createMemo(() => normalizeColumnDefinitions(stateProps.columns));
|
|
480
|
+
const rowHeaderColumnKeys = createMemo(() => getRowHeaderColumnKeys(normalizedColumns()));
|
|
298
481
|
|
|
299
|
-
// Create collection
|
|
300
482
|
const collection = createMemo(() =>
|
|
301
483
|
createTableCollection<T>({
|
|
302
|
-
columns:
|
|
484
|
+
columns: normalizedColumns(),
|
|
303
485
|
rows: stateProps.items,
|
|
304
486
|
getKey: stateProps.getKey,
|
|
305
|
-
getTextValue: stateProps.getTextValue
|
|
487
|
+
getTextValue: stateProps.getTextValue as
|
|
488
|
+
| ((item: T, column: ColumnDefinition<T>) => string)
|
|
489
|
+
| undefined,
|
|
306
490
|
showSelectionCheckboxes: stateProps.showSelectionCheckboxes ?? false,
|
|
307
|
-
|
|
491
|
+
rowHeaderColumnKeys: rowHeaderColumnKeys().size > 0 ? rowHeaderColumnKeys() : undefined,
|
|
492
|
+
}),
|
|
308
493
|
);
|
|
309
494
|
|
|
310
|
-
// Create table state
|
|
311
495
|
const state = createTableState<T, TableCollection<T>>(() => ({
|
|
312
496
|
collection: collection(),
|
|
313
497
|
disabledKeys: stateProps.disabledKeys,
|
|
314
498
|
selectionMode: stateProps.selectionMode,
|
|
499
|
+
selectionBehavior: stateProps.selectionBehavior,
|
|
315
500
|
selectedKeys: stateProps.selectedKeys,
|
|
316
501
|
defaultSelectedKeys: stateProps.defaultSelectedKeys,
|
|
317
502
|
onSelectionChange: stateProps.onSelectionChange,
|
|
@@ -319,27 +504,27 @@ export function Table<T extends object>(props: TableProps<T>): JSX.Element {
|
|
|
319
504
|
onSortChange: stateProps.onSortChange,
|
|
320
505
|
showSelectionCheckboxes: stateProps.showSelectionCheckboxes,
|
|
321
506
|
}));
|
|
507
|
+
const parentCollectionRenderer = useCollectionRenderer<T>();
|
|
322
508
|
|
|
323
|
-
// Create table aria props
|
|
324
509
|
const { gridProps } = createTable<T>(
|
|
325
510
|
() => ({
|
|
326
511
|
id: ariaProps.id,
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
isVirtualized: ariaProps.isVirtualized,
|
|
512
|
+
"aria-label": ariaProps["aria-label"],
|
|
513
|
+
"aria-labelledby": ariaProps["aria-labelledby"],
|
|
514
|
+
"aria-describedby": ariaProps["aria-describedby"],
|
|
515
|
+
isVirtualized: ariaProps.isVirtualized ?? parentCollectionRenderer?.isVirtualized,
|
|
331
516
|
onRowAction: ariaProps.onRowAction,
|
|
332
517
|
onCellAction: ariaProps.onCellAction,
|
|
518
|
+
shouldSelectOnPressUp: ariaProps.shouldSelectOnPressUp,
|
|
333
519
|
focusMode: ariaProps.focusMode,
|
|
520
|
+
escapeKeyBehavior: stateProps.escapeKeyBehavior,
|
|
334
521
|
}),
|
|
335
522
|
() => state,
|
|
336
|
-
ref
|
|
523
|
+
ref,
|
|
337
524
|
);
|
|
338
525
|
|
|
339
|
-
// Create focus ring
|
|
340
526
|
const { isFocused, isFocusVisible, focusProps } = createFocusRing();
|
|
341
527
|
|
|
342
|
-
// Render props values
|
|
343
528
|
const renderValues = createMemo<TableRenderProps>(() => ({
|
|
344
529
|
isFocused: state.isFocused || isFocused(),
|
|
345
530
|
isFocusVisible: isFocusVisible(),
|
|
@@ -353,18 +538,16 @@ export function Table<T extends object>(props: TableProps<T>): JSX.Element {
|
|
|
353
538
|
{
|
|
354
539
|
class: local.class,
|
|
355
540
|
style: local.style,
|
|
356
|
-
defaultClassName:
|
|
541
|
+
defaultClassName: "solidaria-Table",
|
|
357
542
|
},
|
|
358
|
-
renderValues
|
|
543
|
+
renderValues,
|
|
359
544
|
);
|
|
360
545
|
|
|
361
|
-
// Filter DOM props
|
|
362
546
|
const domProps = createMemo(() => {
|
|
363
547
|
const filtered = filterDOMProps(ariaProps as Record<string, unknown>, { global: true });
|
|
364
548
|
return filtered;
|
|
365
549
|
});
|
|
366
550
|
|
|
367
|
-
// Remove ref from spread props
|
|
368
551
|
const cleanGridProps = () => {
|
|
369
552
|
const { ref: _ref1, ...rest } = gridProps as Record<string, unknown>;
|
|
370
553
|
return rest;
|
|
@@ -373,19 +556,25 @@ export function Table<T extends object>(props: TableProps<T>): JSX.Element {
|
|
|
373
556
|
const { ref: _ref2, ...rest } = focusProps as Record<string, unknown>;
|
|
374
557
|
return rest;
|
|
375
558
|
};
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
559
|
+
const getItemNodes = createMemo(() =>
|
|
560
|
+
Array.from(state.collection).filter((node) => node.type === "item"),
|
|
561
|
+
);
|
|
562
|
+
const getDropTargetByIndex = (
|
|
563
|
+
index: number,
|
|
564
|
+
position: "before" | "after" | "on",
|
|
565
|
+
): DropTarget | null => {
|
|
379
566
|
const node = getItemNodes()[index];
|
|
380
567
|
if (!node) return null;
|
|
381
|
-
return { type:
|
|
568
|
+
return { type: "item", key: node.key, dropPosition: position };
|
|
382
569
|
};
|
|
383
570
|
const hasDroppableDnd = createMemo(() => {
|
|
384
571
|
const hooks = local.dragAndDropHooks;
|
|
385
572
|
return Boolean(
|
|
386
573
|
hooks?.useDroppableCollectionState &&
|
|
387
574
|
hooks.useDroppableCollection &&
|
|
388
|
-
(hooks.dropTargetDelegate ||
|
|
575
|
+
(hooks.dropTargetDelegate ||
|
|
576
|
+
parentCollectionRenderer?.dropTargetDelegate ||
|
|
577
|
+
hooks.ListDropTargetDelegate),
|
|
389
578
|
);
|
|
390
579
|
});
|
|
391
580
|
const hasDraggableDnd = createMemo(() => {
|
|
@@ -414,22 +603,23 @@ export function Table<T extends object>(props: TableProps<T>): JSX.Element {
|
|
|
414
603
|
const hooks = local.dragAndDropHooks;
|
|
415
604
|
const activeDropState = dropState();
|
|
416
605
|
if (!hooks?.useDroppableCollection || !activeDropState) return undefined;
|
|
417
|
-
const resolveDirection = ():
|
|
606
|
+
const resolveDirection = (): "ltr" | "rtl" => {
|
|
418
607
|
const el = ref();
|
|
419
|
-
if (el && typeof window !==
|
|
608
|
+
if (el && typeof window !== "undefined" && typeof window.getComputedStyle === "function") {
|
|
420
609
|
const dir = window.getComputedStyle(el).direction;
|
|
421
|
-
if (dir ===
|
|
610
|
+
if (dir === "rtl") return "rtl";
|
|
422
611
|
}
|
|
423
|
-
return typeof document !==
|
|
612
|
+
return typeof document !== "undefined" && document.dir === "rtl" ? "rtl" : "ltr";
|
|
424
613
|
};
|
|
425
|
-
const dropTargetDelegate =
|
|
426
|
-
??
|
|
427
|
-
??
|
|
614
|
+
const dropTargetDelegate =
|
|
615
|
+
hooks.dropTargetDelegate ??
|
|
616
|
+
parentCollectionRenderer?.dropTargetDelegate ??
|
|
617
|
+
(hooks.ListDropTargetDelegate
|
|
428
618
|
? new hooks.ListDropTargetDelegate(
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
619
|
+
() => state.collection,
|
|
620
|
+
() => ref(),
|
|
621
|
+
{ layout: "grid", orientation: "vertical", direction: resolveDirection() },
|
|
622
|
+
)
|
|
433
623
|
: undefined);
|
|
434
624
|
if (!dropTargetDelegate) return undefined;
|
|
435
625
|
return hooks.useDroppableCollection(
|
|
@@ -441,72 +631,113 @@ export function Table<T extends object>(props: TableProps<T>): JSX.Element {
|
|
|
441
631
|
getKeyBelow: (key) => state.collection.getKeyAfter?.(key) ?? null,
|
|
442
632
|
getKeyAbove: (key) => state.collection.getKeyBefore?.(key) ?? null,
|
|
443
633
|
getKeyLeftOf: (key) =>
|
|
444
|
-
resolveDirection() ===
|
|
445
|
-
? state.collection.getKeyAfter?.(key) ?? null
|
|
446
|
-
: state.collection.getKeyBefore?.(key) ?? null,
|
|
634
|
+
resolveDirection() === "rtl"
|
|
635
|
+
? (state.collection.getKeyAfter?.(key) ?? null)
|
|
636
|
+
: (state.collection.getKeyBefore?.(key) ?? null),
|
|
447
637
|
getKeyRightOf: (key) =>
|
|
448
|
-
resolveDirection() ===
|
|
449
|
-
? state.collection.getKeyBefore?.(key) ?? null
|
|
450
|
-
: state.collection.getKeyAfter?.(key) ?? null,
|
|
638
|
+
resolveDirection() === "rtl"
|
|
639
|
+
? (state.collection.getKeyBefore?.(key) ?? null)
|
|
640
|
+
: (state.collection.getKeyAfter?.(key) ?? null),
|
|
451
641
|
getKeyPageBelow: (key) => state.collection.getKeyAfter?.(key) ?? null,
|
|
452
642
|
getKeyPageAbove: (key) => state.collection.getKeyBefore?.(key) ?? null,
|
|
453
643
|
},
|
|
644
|
+
get collection() {
|
|
645
|
+
return state.collection;
|
|
646
|
+
},
|
|
647
|
+
get selectedKeys() {
|
|
648
|
+
return state.selectedKeys;
|
|
649
|
+
},
|
|
650
|
+
setSelectedKeys: (keys: Set<Key>) => {
|
|
651
|
+
if (state.selectionMode === "none") return;
|
|
652
|
+
state.clearSelection();
|
|
653
|
+
for (const key of keys) {
|
|
654
|
+
state.toggleSelection(key);
|
|
655
|
+
}
|
|
656
|
+
},
|
|
657
|
+
setFocusedKey: (key) => state.setFocusedKey(key),
|
|
454
658
|
},
|
|
455
659
|
activeDropState,
|
|
456
|
-
() => ref()
|
|
660
|
+
() => ref(),
|
|
457
661
|
);
|
|
458
662
|
});
|
|
459
663
|
const isRootDropTarget = createMemo(() => {
|
|
460
|
-
return Boolean(dropState()?.target?.type ===
|
|
664
|
+
return Boolean(dropState()?.target?.type === "root");
|
|
461
665
|
});
|
|
462
|
-
const dndRenderDropIndicator = createMemo(() =>
|
|
463
|
-
|
|
666
|
+
const dndRenderDropIndicator = createMemo(() =>
|
|
667
|
+
useRenderDropIndicator(local.dragAndDropHooks, dropState()),
|
|
668
|
+
);
|
|
669
|
+
const dndDropIndicator = (index: number, position: "before" | "after" | "on") => {
|
|
464
670
|
const target = getDropTargetByIndex(index, position);
|
|
465
|
-
if (!target || target.type !==
|
|
671
|
+
if (!target || target.type !== "item") return undefined;
|
|
466
672
|
return dndRenderDropIndicator()?.(target);
|
|
467
673
|
};
|
|
468
674
|
|
|
469
|
-
const contextValue
|
|
675
|
+
const contextValue: TableContextValue<T> = {
|
|
470
676
|
state,
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
677
|
+
get collection() {
|
|
678
|
+
return collection();
|
|
679
|
+
},
|
|
680
|
+
get items() {
|
|
681
|
+
return stateProps.items;
|
|
682
|
+
},
|
|
683
|
+
get columns() {
|
|
684
|
+
return normalizedColumns();
|
|
685
|
+
},
|
|
474
686
|
isDisabled: false,
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
687
|
+
get showSelectionCheckboxes() {
|
|
688
|
+
return stateProps.showSelectionCheckboxes ?? false;
|
|
689
|
+
},
|
|
690
|
+
get dragAndDropHooks() {
|
|
691
|
+
return local.dragAndDropHooks;
|
|
692
|
+
},
|
|
693
|
+
get dragState() {
|
|
694
|
+
return dragState();
|
|
695
|
+
},
|
|
696
|
+
get dropState() {
|
|
697
|
+
return dropState();
|
|
698
|
+
},
|
|
699
|
+
get isVirtualized() {
|
|
700
|
+
return ariaProps.isVirtualized ?? parentCollectionRenderer?.isVirtualized ?? false;
|
|
701
|
+
},
|
|
702
|
+
};
|
|
480
703
|
const collectionRenderer = createMemo<CollectionRendererContextValue<unknown>>(() => ({
|
|
481
704
|
...parentCollectionRenderer,
|
|
482
705
|
renderItem: (item) => item as JSX.Element,
|
|
483
|
-
renderDropIndicator: (index: number, position:
|
|
484
|
-
dndDropIndicator(index, position) ??
|
|
706
|
+
renderDropIndicator: (index: number, position: "before" | "after" | "on") =>
|
|
707
|
+
dndDropIndicator(index, position) ??
|
|
708
|
+
parentCollectionRenderer?.renderDropIndicator?.(index, position),
|
|
485
709
|
}));
|
|
710
|
+
const tableChildren = () =>
|
|
711
|
+
typeof props.children === "function" ? props.children(renderValues()) : props.children;
|
|
712
|
+
const tableProps = () =>
|
|
713
|
+
({
|
|
714
|
+
ref: (el: HTMLTableElement) => {
|
|
715
|
+
setRef(el);
|
|
716
|
+
assignRef(local.ref, el);
|
|
717
|
+
},
|
|
718
|
+
...mergeProps(
|
|
719
|
+
domProps(),
|
|
720
|
+
cleanGridProps(),
|
|
721
|
+
cleanFocusProps(),
|
|
722
|
+
(droppableCollection()?.collectionProps as Record<string, unknown> | undefined) ?? {},
|
|
723
|
+
),
|
|
724
|
+
class: renderProps.class(),
|
|
725
|
+
style: renderProps.style(),
|
|
726
|
+
"data-focused": state.isFocused || undefined,
|
|
727
|
+
"data-focus-visible": isFocusVisible() || undefined,
|
|
728
|
+
"data-empty": stateProps.items.length === 0 || undefined,
|
|
729
|
+
"data-drop-target": isRootDropTarget() || undefined,
|
|
730
|
+
slot: local.slot,
|
|
731
|
+
children: tableChildren(),
|
|
732
|
+
}) as JSX.HTMLAttributes<HTMLTableElement>;
|
|
486
733
|
|
|
487
734
|
return (
|
|
488
|
-
<TableContext.Provider value={contextValue
|
|
489
|
-
<TableStateContext.Provider
|
|
735
|
+
<TableContext.Provider value={contextValue as unknown as TableContextValue<object>}>
|
|
736
|
+
<TableStateContext.Provider
|
|
737
|
+
value={state as unknown as TableState<object, TableCollection<object>>}
|
|
738
|
+
>
|
|
490
739
|
<CollectionRendererContext.Provider value={collectionRenderer()}>
|
|
491
|
-
<table
|
|
492
|
-
ref={setRef}
|
|
493
|
-
{...mergeProps(
|
|
494
|
-
domProps(),
|
|
495
|
-
cleanGridProps(),
|
|
496
|
-
cleanFocusProps(),
|
|
497
|
-
(droppableCollection()?.collectionProps as Record<string, unknown> | undefined) ?? {}
|
|
498
|
-
)}
|
|
499
|
-
class={renderProps.class()}
|
|
500
|
-
style={renderProps.style()}
|
|
501
|
-
data-focused={state.isFocused || undefined}
|
|
502
|
-
data-focus-visible={isFocusVisible() || undefined}
|
|
503
|
-
data-empty={stateProps.items.length === 0 || undefined}
|
|
504
|
-
data-drop-target={isRootDropTarget() || undefined}
|
|
505
|
-
>
|
|
506
|
-
{typeof props.children === 'function'
|
|
507
|
-
? props.children(renderValues())
|
|
508
|
-
: props.children}
|
|
509
|
-
</table>
|
|
740
|
+
{local.render ? local.render(tableProps(), renderValues()) : <table {...tableProps()} />}
|
|
510
741
|
</CollectionRendererContext.Provider>
|
|
511
742
|
</TableStateContext.Provider>
|
|
512
743
|
</TableContext.Provider>
|
|
@@ -517,38 +748,82 @@ export function Table<T extends object>(props: TableProps<T>): JSX.Element {
|
|
|
517
748
|
* A header row in a table containing column headers.
|
|
518
749
|
*/
|
|
519
750
|
export function TableHeader(props: TableHeaderProps): JSX.Element {
|
|
520
|
-
const [local, domProps] = splitProps(props, [
|
|
751
|
+
const [local, domProps] = splitProps(props, [
|
|
752
|
+
"class",
|
|
753
|
+
"style",
|
|
754
|
+
"render",
|
|
755
|
+
"slot",
|
|
756
|
+
"children",
|
|
757
|
+
"ref",
|
|
758
|
+
]);
|
|
521
759
|
|
|
522
|
-
// Get context
|
|
523
760
|
const context = useContext(TableContext);
|
|
524
761
|
if (!context) {
|
|
525
|
-
throw new Error(
|
|
762
|
+
throw new Error("TableHeader must be used within a Table");
|
|
526
763
|
}
|
|
527
764
|
|
|
528
|
-
const { rowGroupProps } = createTableRowGroup(() => ({ type:
|
|
765
|
+
const { rowGroupProps } = createTableRowGroup(() => ({ type: "thead" }));
|
|
766
|
+
|
|
767
|
+
const { isHovered, hoverProps } = createHover({
|
|
768
|
+
isDisabled: false,
|
|
769
|
+
onHoverStart(e) {
|
|
770
|
+
(domProps as Record<string, (e: unknown) => void>).onHoverStart?.(e);
|
|
771
|
+
},
|
|
772
|
+
onHoverEnd(e) {
|
|
773
|
+
(domProps as Record<string, (e: unknown) => void>).onHoverEnd?.(e);
|
|
774
|
+
},
|
|
775
|
+
onHoverChange(isHovering) {
|
|
776
|
+
(domProps as Record<string, (isHovering: boolean) => void>).onHoverChange?.(isHovering);
|
|
777
|
+
},
|
|
778
|
+
});
|
|
529
779
|
|
|
530
|
-
// Render props values
|
|
531
780
|
const renderValues = createMemo<TableHeaderRenderProps>(() => ({
|
|
532
781
|
isFocused: false,
|
|
782
|
+
isHovered: isHovered(),
|
|
533
783
|
}));
|
|
534
784
|
|
|
535
|
-
// Resolve render props
|
|
536
785
|
const renderProps = useRenderProps(
|
|
537
786
|
{
|
|
538
787
|
class: local.class,
|
|
539
788
|
style: local.style,
|
|
540
|
-
defaultClassName:
|
|
789
|
+
defaultClassName: "solidaria-Table-header",
|
|
541
790
|
},
|
|
542
|
-
renderValues
|
|
791
|
+
renderValues,
|
|
543
792
|
);
|
|
544
793
|
|
|
545
794
|
const cleanRowGroupProps = () => {
|
|
546
795
|
const { ref: _ref, ...rest } = rowGroupProps as Record<string, unknown>;
|
|
547
796
|
return rest;
|
|
548
797
|
};
|
|
798
|
+
const cleanHoverProps = () => {
|
|
799
|
+
const { ref: _ref, ...rest } = hoverProps as Record<string, unknown>;
|
|
800
|
+
return rest;
|
|
801
|
+
};
|
|
549
802
|
|
|
550
|
-
|
|
551
|
-
|
|
803
|
+
const headerProps = () =>
|
|
804
|
+
({
|
|
805
|
+
ref: (el: HTMLTableSectionElement) => assignRef(local.ref, el),
|
|
806
|
+
...domProps,
|
|
807
|
+
...cleanRowGroupProps(),
|
|
808
|
+
...cleanHoverProps(),
|
|
809
|
+
class: renderProps.class(),
|
|
810
|
+
style: renderProps.style(),
|
|
811
|
+
"data-hovered": isHovered() || undefined,
|
|
812
|
+
children: <tr role="row">{local.children}</tr>,
|
|
813
|
+
}) as JSX.HTMLAttributes<HTMLTableSectionElement>;
|
|
814
|
+
|
|
815
|
+
return local.render ? (
|
|
816
|
+
local.render(headerProps(), renderValues())
|
|
817
|
+
) : (
|
|
818
|
+
<thead
|
|
819
|
+
ref={(el) => assignRef(local.ref, el)}
|
|
820
|
+
{...domProps}
|
|
821
|
+
{...cleanRowGroupProps()}
|
|
822
|
+
{...cleanHoverProps()}
|
|
823
|
+
class={renderProps.class()}
|
|
824
|
+
style={renderProps.style()}
|
|
825
|
+
data-hovered={isHovered() || undefined}
|
|
826
|
+
>
|
|
552
827
|
<tr role="row">{local.children}</tr>
|
|
553
828
|
</thead>
|
|
554
829
|
);
|
|
@@ -558,25 +833,35 @@ export function TableHeader(props: TableHeaderProps): JSX.Element {
|
|
|
558
833
|
* A column header in a table.
|
|
559
834
|
*/
|
|
560
835
|
export function TableColumn(props: TableColumnProps): JSX.Element {
|
|
561
|
-
const [local, domProps] = splitProps(props, [
|
|
836
|
+
const [local, domProps] = splitProps(props, [
|
|
837
|
+
"class",
|
|
838
|
+
"style",
|
|
839
|
+
"render",
|
|
840
|
+
"slot",
|
|
841
|
+
"id",
|
|
842
|
+
"allowsSorting",
|
|
843
|
+
"allowsResizing",
|
|
844
|
+
"width",
|
|
845
|
+
"defaultWidth",
|
|
846
|
+
"minWidth",
|
|
847
|
+
"maxWidth",
|
|
848
|
+
"children",
|
|
849
|
+
"ref",
|
|
850
|
+
]);
|
|
562
851
|
|
|
563
|
-
// Get context
|
|
564
852
|
const context = useContext(TableContext);
|
|
565
853
|
if (!context) {
|
|
566
|
-
throw new Error(
|
|
854
|
+
throw new Error("TableColumn must be used within a Table");
|
|
567
855
|
}
|
|
568
856
|
const { state, collection } = context;
|
|
569
857
|
|
|
570
|
-
// Create ref signal
|
|
571
858
|
const [ref, setRef] = createSignal<HTMLTableCellElement | null>(null);
|
|
572
859
|
|
|
573
|
-
// Find the column node
|
|
574
860
|
const columnNode = createMemo(() => {
|
|
575
861
|
const node = collection.getItem(local.id);
|
|
576
862
|
if (!node) {
|
|
577
|
-
// Create a simple node for the column
|
|
578
863
|
return {
|
|
579
|
-
type:
|
|
864
|
+
type: "column" as const,
|
|
580
865
|
key: local.id,
|
|
581
866
|
value: null,
|
|
582
867
|
textValue: String(local.id),
|
|
@@ -589,25 +874,32 @@ export function TableColumn(props: TableColumnProps): JSX.Element {
|
|
|
589
874
|
return node;
|
|
590
875
|
});
|
|
591
876
|
|
|
592
|
-
// Create column header aria props
|
|
593
877
|
const columnHeaderAria = createTableColumnHeader<object>(
|
|
594
878
|
() => ({
|
|
595
879
|
node: columnNode(),
|
|
596
880
|
allowsSorting: local.allowsSorting,
|
|
597
881
|
}),
|
|
598
882
|
() => state as TableState<object, TableCollection<object>>,
|
|
599
|
-
ref
|
|
883
|
+
ref,
|
|
600
884
|
);
|
|
601
885
|
|
|
602
|
-
// Create hover
|
|
603
886
|
const { isHovered, hoverProps } = createHover({
|
|
604
|
-
isDisabled
|
|
887
|
+
get isDisabled() {
|
|
888
|
+
return !local.allowsSorting;
|
|
889
|
+
},
|
|
890
|
+
onHoverStart(e) {
|
|
891
|
+
(domProps as Record<string, (e: unknown) => void>).onHoverStart?.(e);
|
|
892
|
+
},
|
|
893
|
+
onHoverEnd(e) {
|
|
894
|
+
(domProps as Record<string, (e: unknown) => void>).onHoverEnd?.(e);
|
|
895
|
+
},
|
|
896
|
+
onHoverChange(isHovering) {
|
|
897
|
+
(domProps as Record<string, (isHovering: boolean) => void>).onHoverChange?.(isHovering);
|
|
898
|
+
},
|
|
605
899
|
});
|
|
606
900
|
|
|
607
|
-
// Create focus ring
|
|
608
901
|
const { isFocusVisible, focusProps } = createFocusRing();
|
|
609
902
|
|
|
610
|
-
// Get sort direction
|
|
611
903
|
const sortDirection = createMemo(() => {
|
|
612
904
|
const sortDescriptor = state.sortDescriptor;
|
|
613
905
|
if (sortDescriptor?.column === local.id) {
|
|
@@ -616,13 +908,29 @@ export function TableColumn(props: TableColumnProps): JSX.Element {
|
|
|
616
908
|
return undefined;
|
|
617
909
|
});
|
|
618
910
|
|
|
619
|
-
|
|
911
|
+
const resizeCtx = useContext(TableColumnResizeStateContext);
|
|
912
|
+
|
|
913
|
+
const isResizing = createMemo(() => {
|
|
914
|
+
const rs = resizeCtx?.getState();
|
|
915
|
+
if (!rs) return false;
|
|
916
|
+
return rs.resizingColumn() === local.id;
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
const resizeWidth = createMemo(() => {
|
|
920
|
+
const rs = resizeCtx?.getState();
|
|
921
|
+
if (!rs) return undefined;
|
|
922
|
+
const w = rs.getColumnWidth(local.id);
|
|
923
|
+
return w > 0 ? w : undefined;
|
|
924
|
+
});
|
|
925
|
+
|
|
620
926
|
const renderValues = createMemo<TableColumnRenderProps>(() => ({
|
|
621
927
|
isFocused: state.focusedKey === local.id,
|
|
622
928
|
isFocusVisible: isFocusVisible() && state.focusedKey === local.id,
|
|
623
929
|
isSortable: local.allowsSorting ?? false,
|
|
624
930
|
sortDirection: sortDirection(),
|
|
625
931
|
isHovered: isHovered(),
|
|
932
|
+
allowsResizing: local.allowsResizing ?? false,
|
|
933
|
+
isResizing: isResizing(),
|
|
626
934
|
}));
|
|
627
935
|
|
|
628
936
|
// Resolve render props (children rendered directly in JSX to avoid eager evaluation)
|
|
@@ -630,12 +938,11 @@ export function TableColumn(props: TableColumnProps): JSX.Element {
|
|
|
630
938
|
{
|
|
631
939
|
class: local.class,
|
|
632
940
|
style: local.style,
|
|
633
|
-
defaultClassName:
|
|
941
|
+
defaultClassName: "solidaria-Table-column",
|
|
634
942
|
},
|
|
635
|
-
renderValues
|
|
943
|
+
renderValues,
|
|
636
944
|
);
|
|
637
945
|
|
|
638
|
-
// Remove ref from spread props
|
|
639
946
|
const cleanColumnHeaderProps = () => {
|
|
640
947
|
const { ref: _ref1, ...rest } = columnHeaderAria.columnHeaderProps as Record<string, unknown>;
|
|
641
948
|
return rest;
|
|
@@ -649,24 +956,59 @@ export function TableColumn(props: TableColumnProps): JSX.Element {
|
|
|
649
956
|
return rest;
|
|
650
957
|
};
|
|
651
958
|
|
|
652
|
-
|
|
959
|
+
const columnStyle = createMemo(() => {
|
|
960
|
+
const base = renderProps.style();
|
|
961
|
+
const rw = resizeWidth();
|
|
962
|
+
if (rw == null) return base;
|
|
963
|
+
const widthStyle = { width: `${rw}px`, "min-width": `${rw}px`, "max-width": `${rw}px` };
|
|
964
|
+
if (!base) return widthStyle;
|
|
965
|
+
if (typeof base === "string") return widthStyle; // fallback
|
|
966
|
+
return { ...base, ...widthStyle };
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
const columnChildren = () =>
|
|
970
|
+
typeof local.children === "function" ? local.children(renderValues()) : local.children;
|
|
971
|
+
const columnProps = () =>
|
|
972
|
+
({
|
|
973
|
+
ref: (el: HTMLTableCellElement) => {
|
|
974
|
+
setRef(el);
|
|
975
|
+
assignRef(local.ref, el);
|
|
976
|
+
},
|
|
977
|
+
...domProps,
|
|
978
|
+
...mergeProps(cleanColumnHeaderProps(), cleanHoverProps(), cleanFocusProps()),
|
|
979
|
+
class: renderProps.class(),
|
|
980
|
+
style: columnStyle(),
|
|
981
|
+
"data-sortable": local.allowsSorting || undefined,
|
|
982
|
+
"data-sort-direction": sortDirection() || undefined,
|
|
983
|
+
"data-resizable": local.allowsResizing || undefined,
|
|
984
|
+
"data-resizing": isResizing() || undefined,
|
|
985
|
+
"data-hovered": isHovered() || undefined,
|
|
986
|
+
"data-focused": state.focusedKey === local.id || undefined,
|
|
987
|
+
"data-focus-visible": (isFocusVisible() && state.focusedKey === local.id) || undefined,
|
|
988
|
+
children: columnChildren(),
|
|
989
|
+
}) as JSX.ThHTMLAttributes<HTMLTableCellElement>;
|
|
990
|
+
|
|
991
|
+
return local.render ? (
|
|
992
|
+
local.render(columnProps(), renderValues())
|
|
993
|
+
) : (
|
|
653
994
|
<th
|
|
654
|
-
ref={
|
|
995
|
+
ref={(el) => {
|
|
996
|
+
setRef(el);
|
|
997
|
+
assignRef(local.ref, el);
|
|
998
|
+
}}
|
|
655
999
|
{...domProps}
|
|
656
|
-
{...cleanColumnHeaderProps()}
|
|
657
|
-
{...cleanHoverProps()}
|
|
658
|
-
{...cleanFocusProps()}
|
|
1000
|
+
{...mergeProps(cleanColumnHeaderProps(), cleanHoverProps(), cleanFocusProps())}
|
|
659
1001
|
class={renderProps.class()}
|
|
660
|
-
style={
|
|
1002
|
+
style={columnStyle()}
|
|
661
1003
|
data-sortable={local.allowsSorting || undefined}
|
|
662
1004
|
data-sort-direction={sortDirection() || undefined}
|
|
1005
|
+
data-resizable={local.allowsResizing || undefined}
|
|
1006
|
+
data-resizing={isResizing() || undefined}
|
|
663
1007
|
data-hovered={isHovered() || undefined}
|
|
664
1008
|
data-focused={state.focusedKey === local.id || undefined}
|
|
665
1009
|
data-focus-visible={(isFocusVisible() && state.focusedKey === local.id) || undefined}
|
|
666
1010
|
>
|
|
667
|
-
{
|
|
668
|
-
? local.children(renderValues())
|
|
669
|
-
: local.children}
|
|
1011
|
+
{columnChildren()}
|
|
670
1012
|
</th>
|
|
671
1013
|
);
|
|
672
1014
|
}
|
|
@@ -675,32 +1017,40 @@ export function TableColumn(props: TableColumnProps): JSX.Element {
|
|
|
675
1017
|
* The body of a table containing data rows.
|
|
676
1018
|
*/
|
|
677
1019
|
export function TableBody<T extends object>(props: TableBodyProps<T>): JSX.Element {
|
|
678
|
-
const [local, domProps] = splitProps(props, [
|
|
1020
|
+
const [local, domProps] = splitProps(props, [
|
|
1021
|
+
"items",
|
|
1022
|
+
"class",
|
|
1023
|
+
"style",
|
|
1024
|
+
"render",
|
|
1025
|
+
"slot",
|
|
1026
|
+
"renderEmptyState",
|
|
1027
|
+
"hasMore",
|
|
1028
|
+
"isLoading",
|
|
1029
|
+
"onLoadMore",
|
|
1030
|
+
"children",
|
|
1031
|
+
"ref",
|
|
1032
|
+
]);
|
|
679
1033
|
|
|
680
|
-
// Get context
|
|
681
1034
|
const context = useContext(TableContext);
|
|
682
1035
|
if (!context) {
|
|
683
|
-
throw new Error(
|
|
1036
|
+
throw new Error("TableBody must be used within a Table");
|
|
684
1037
|
}
|
|
685
1038
|
|
|
686
|
-
const { rowGroupProps } = createTableRowGroup(() => ({ type:
|
|
1039
|
+
const { rowGroupProps } = createTableRowGroup(() => ({ type: "tbody" }));
|
|
687
1040
|
|
|
688
|
-
// Use provided items or context items
|
|
689
1041
|
const items = createMemo(() => (local.items ?? context.items) as T[]);
|
|
690
1042
|
|
|
691
|
-
// Render props values
|
|
692
1043
|
const renderValues = createMemo<TableBodyRenderProps>(() => ({
|
|
693
1044
|
isEmpty: items().length === 0,
|
|
694
1045
|
}));
|
|
695
1046
|
|
|
696
|
-
// Resolve render props
|
|
697
1047
|
const renderProps = useRenderProps(
|
|
698
1048
|
{
|
|
699
1049
|
class: local.class,
|
|
700
1050
|
style: local.style,
|
|
701
|
-
defaultClassName:
|
|
1051
|
+
defaultClassName: "solidaria-Table-body",
|
|
702
1052
|
},
|
|
703
|
-
renderValues
|
|
1053
|
+
renderValues,
|
|
704
1054
|
);
|
|
705
1055
|
|
|
706
1056
|
const cleanRowGroupProps = () => {
|
|
@@ -711,12 +1061,14 @@ export function TableBody<T extends object>(props: TableBodyProps<T>): JSX.Eleme
|
|
|
711
1061
|
const isEmpty = () => items().length === 0;
|
|
712
1062
|
const virtualizer = useVirtualizerContext();
|
|
713
1063
|
const parentCollectionRenderer = useCollectionRenderer<T>();
|
|
714
|
-
const rowNodes = createMemo(() =>
|
|
1064
|
+
const rowNodes = createMemo(() =>
|
|
1065
|
+
Array.from(context.collection).filter((node) => node.type === "item"),
|
|
1066
|
+
);
|
|
715
1067
|
const persistedKeys = useDndPersistedKeys(
|
|
716
1068
|
{ focusedKey: () => context.state.focusedKey },
|
|
717
1069
|
context.dragAndDropHooks,
|
|
718
1070
|
context.dropState as { target?: DropTarget | null } | undefined,
|
|
719
|
-
context.collection
|
|
1071
|
+
context.collection,
|
|
720
1072
|
);
|
|
721
1073
|
const virtualRange = createMemo(() => {
|
|
722
1074
|
if (!virtualizer || !parentCollectionRenderer?.isVirtualized) return null;
|
|
@@ -728,16 +1080,28 @@ export function TableBody<T extends object>(props: TableBodyProps<T>): JSX.Eleme
|
|
|
728
1080
|
const dropTarget = (context.dropState as { target?: DropTarget | null } | undefined)?.target;
|
|
729
1081
|
const normalizedDropKey = getNormalizedDropTargetKey(dropTarget, context.collection);
|
|
730
1082
|
const focusedKey = context.state.focusedKey;
|
|
731
|
-
const focusedIndex =
|
|
1083
|
+
const focusedIndex =
|
|
1084
|
+
focusedKey != null ? rowNodes().findIndex((node) => node.key === focusedKey) : -1;
|
|
732
1085
|
const forceIncludeIndexes = [
|
|
733
|
-
dropTarget?.type ===
|
|
734
|
-
|
|
735
|
-
|
|
1086
|
+
dropTarget?.type === "item"
|
|
1087
|
+
? rowNodes().findIndex((node) => node.key === dropTarget.key)
|
|
1088
|
+
: -1,
|
|
1089
|
+
normalizedDropKey != null
|
|
1090
|
+
? rowNodes().findIndex((node) => node.key === normalizedDropKey)
|
|
1091
|
+
: -1,
|
|
1092
|
+
dropTarget?.type === "item" ? -1 : focusedIndex,
|
|
736
1093
|
].filter((index) => index >= 0);
|
|
737
|
-
return mergePersistedKeysIntoVirtualRange(
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
1094
|
+
return mergePersistedKeysIntoVirtualRange(
|
|
1095
|
+
baseRange,
|
|
1096
|
+
persistedIndexes,
|
|
1097
|
+
rowCount,
|
|
1098
|
+
virtualizer,
|
|
1099
|
+
80,
|
|
1100
|
+
{
|
|
1101
|
+
forceIncludeIndexes,
|
|
1102
|
+
forceIncludeMaxSpan: 320,
|
|
1103
|
+
},
|
|
1104
|
+
);
|
|
741
1105
|
});
|
|
742
1106
|
createEffect(() => {
|
|
743
1107
|
if (!virtualizer || !parentCollectionRenderer?.isVirtualized) return;
|
|
@@ -751,7 +1115,7 @@ export function TableBody<T extends object>(props: TableBodyProps<T>): JSX.Eleme
|
|
|
751
1115
|
if (!node) return target;
|
|
752
1116
|
return {
|
|
753
1117
|
...target,
|
|
754
|
-
key: typeof node.key ===
|
|
1118
|
+
key: typeof node.key === "string" || typeof node.key === "number" ? node.key : undefined,
|
|
755
1119
|
};
|
|
756
1120
|
});
|
|
757
1121
|
onCleanup(() => {
|
|
@@ -767,45 +1131,146 @@ export function TableBody<T extends object>(props: TableBodyProps<T>): JSX.Eleme
|
|
|
767
1131
|
});
|
|
768
1132
|
const spacerColSpan = () => context.columns.length + (context.showSelectionCheckboxes ? 1 : 0);
|
|
769
1133
|
|
|
770
|
-
|
|
771
|
-
|
|
1134
|
+
const bodyChildren = () => (
|
|
1135
|
+
<>
|
|
772
1136
|
<SharedElementTransition>
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
{
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
<
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
1137
|
+
<Show
|
|
1138
|
+
when={isEmpty() && local.renderEmptyState && !local.isLoading}
|
|
1139
|
+
fallback={
|
|
1140
|
+
<>
|
|
1141
|
+
{virtualRange()?.offsetTop ? (
|
|
1142
|
+
<tr role="presentation" aria-hidden="true" data-virtualizer-spacer="top">
|
|
1143
|
+
<td
|
|
1144
|
+
colSpan={spacerColSpan()}
|
|
1145
|
+
style={{ height: `${virtualRange()!.offsetTop}px`, padding: "0", border: "0" }}
|
|
1146
|
+
/>
|
|
1147
|
+
</tr>
|
|
1148
|
+
) : null}
|
|
1149
|
+
<For each={visibleItems()}>
|
|
1150
|
+
{(item, index) => {
|
|
1151
|
+
const itemIndex = () => (virtualRange()?.start ?? 0) + index();
|
|
1152
|
+
const beforeIndicator = () =>
|
|
1153
|
+
parentCollectionRenderer?.renderDropIndicator?.(itemIndex(), "before");
|
|
1154
|
+
const onIndicator = () =>
|
|
1155
|
+
parentCollectionRenderer?.renderDropIndicator?.(itemIndex(), "on");
|
|
1156
|
+
const afterIndicator = () =>
|
|
1157
|
+
parentCollectionRenderer?.renderDropIndicator?.(itemIndex(), "after");
|
|
1158
|
+
return (
|
|
1159
|
+
<>
|
|
1160
|
+
{beforeIndicator()}
|
|
1161
|
+
{onIndicator()}
|
|
1162
|
+
{local.children?.(item)}
|
|
1163
|
+
{afterIndicator()}
|
|
1164
|
+
</>
|
|
1165
|
+
);
|
|
1166
|
+
}}
|
|
1167
|
+
</For>
|
|
1168
|
+
{virtualRange()?.offsetBottom ? (
|
|
1169
|
+
<tr role="presentation" aria-hidden="true" data-virtualizer-spacer="bottom">
|
|
1170
|
+
<td
|
|
1171
|
+
colSpan={spacerColSpan()}
|
|
1172
|
+
style={{
|
|
1173
|
+
height: `${virtualRange()!.offsetBottom}px`,
|
|
1174
|
+
padding: "0",
|
|
1175
|
+
border: "0",
|
|
1176
|
+
}}
|
|
1177
|
+
/>
|
|
1178
|
+
</tr>
|
|
1179
|
+
) : null}
|
|
1180
|
+
</>
|
|
1181
|
+
}
|
|
1182
|
+
>
|
|
1183
|
+
<tr role="row" data-empty-state>
|
|
1184
|
+
<th role="rowheader" colSpan={spacerColSpan()}>
|
|
1185
|
+
{local.renderEmptyState?.()}
|
|
1186
|
+
</th>
|
|
1187
|
+
</tr>
|
|
1188
|
+
</Show>
|
|
1189
|
+
</SharedElementTransition>
|
|
1190
|
+
<Show when={local.hasMore && local.onLoadMore}>
|
|
1191
|
+
<TableLoadMoreItem
|
|
1192
|
+
onLoadMore={local.onLoadMore!}
|
|
1193
|
+
isLoading={local.isLoading}
|
|
1194
|
+
colSpan={spacerColSpan()}
|
|
1195
|
+
/>
|
|
808
1196
|
</Show>
|
|
1197
|
+
</>
|
|
1198
|
+
);
|
|
1199
|
+
const bodyProps = () =>
|
|
1200
|
+
({
|
|
1201
|
+
ref: (el: HTMLTableSectionElement) => assignRef(local.ref, el),
|
|
1202
|
+
...domProps,
|
|
1203
|
+
...cleanRowGroupProps(),
|
|
1204
|
+
class: renderProps.class(),
|
|
1205
|
+
style: renderProps.style(),
|
|
1206
|
+
"data-empty": isEmpty() || undefined,
|
|
1207
|
+
children: bodyChildren(),
|
|
1208
|
+
}) as JSX.HTMLAttributes<HTMLTableSectionElement>;
|
|
1209
|
+
|
|
1210
|
+
return local.render ? (
|
|
1211
|
+
local.render(bodyProps(), renderValues())
|
|
1212
|
+
) : (
|
|
1213
|
+
<tbody
|
|
1214
|
+
ref={(el) => assignRef(local.ref, el)}
|
|
1215
|
+
{...domProps}
|
|
1216
|
+
{...cleanRowGroupProps()}
|
|
1217
|
+
class={renderProps.class()}
|
|
1218
|
+
style={renderProps.style()}
|
|
1219
|
+
data-empty={isEmpty() || undefined}
|
|
1220
|
+
>
|
|
1221
|
+
<SharedElementTransition>
|
|
1222
|
+
<Show
|
|
1223
|
+
when={isEmpty() && local.renderEmptyState && !local.isLoading}
|
|
1224
|
+
fallback={
|
|
1225
|
+
<>
|
|
1226
|
+
{virtualRange()?.offsetTop ? (
|
|
1227
|
+
<tr role="presentation" aria-hidden="true" data-virtualizer-spacer="top">
|
|
1228
|
+
<td
|
|
1229
|
+
colSpan={spacerColSpan()}
|
|
1230
|
+
style={{ height: `${virtualRange()!.offsetTop}px`, padding: "0", border: "0" }}
|
|
1231
|
+
/>
|
|
1232
|
+
</tr>
|
|
1233
|
+
) : null}
|
|
1234
|
+
<For each={visibleItems()}>
|
|
1235
|
+
{(item, index) => {
|
|
1236
|
+
const itemIndex = () => (virtualRange()?.start ?? 0) + index();
|
|
1237
|
+
const beforeIndicator = () =>
|
|
1238
|
+
parentCollectionRenderer?.renderDropIndicator?.(itemIndex(), "before");
|
|
1239
|
+
const onIndicator = () =>
|
|
1240
|
+
parentCollectionRenderer?.renderDropIndicator?.(itemIndex(), "on");
|
|
1241
|
+
const afterIndicator = () =>
|
|
1242
|
+
parentCollectionRenderer?.renderDropIndicator?.(itemIndex(), "after");
|
|
1243
|
+
return (
|
|
1244
|
+
<>
|
|
1245
|
+
{beforeIndicator()}
|
|
1246
|
+
{onIndicator()}
|
|
1247
|
+
{local.children?.(item)}
|
|
1248
|
+
{afterIndicator()}
|
|
1249
|
+
</>
|
|
1250
|
+
);
|
|
1251
|
+
}}
|
|
1252
|
+
</For>
|
|
1253
|
+
{virtualRange()?.offsetBottom ? (
|
|
1254
|
+
<tr role="presentation" aria-hidden="true" data-virtualizer-spacer="bottom">
|
|
1255
|
+
<td
|
|
1256
|
+
colSpan={spacerColSpan()}
|
|
1257
|
+
style={{
|
|
1258
|
+
height: `${virtualRange()!.offsetBottom}px`,
|
|
1259
|
+
padding: "0",
|
|
1260
|
+
border: "0",
|
|
1261
|
+
}}
|
|
1262
|
+
/>
|
|
1263
|
+
</tr>
|
|
1264
|
+
) : null}
|
|
1265
|
+
</>
|
|
1266
|
+
}
|
|
1267
|
+
>
|
|
1268
|
+
<tr role="row" data-empty-state>
|
|
1269
|
+
<th role="rowheader" colSpan={spacerColSpan()}>
|
|
1270
|
+
{local.renderEmptyState?.()}
|
|
1271
|
+
</th>
|
|
1272
|
+
</tr>
|
|
1273
|
+
</Show>
|
|
809
1274
|
</SharedElementTransition>
|
|
810
1275
|
<Show when={local.hasMore && local.onLoadMore}>
|
|
811
1276
|
<TableLoadMoreItem
|
|
@@ -818,13 +1283,62 @@ export function TableBody<T extends object>(props: TableBodyProps<T>): JSX.Eleme
|
|
|
818
1283
|
);
|
|
819
1284
|
}
|
|
820
1285
|
|
|
1286
|
+
/**
|
|
1287
|
+
* The footer of a table containing summary rows.
|
|
1288
|
+
*/
|
|
1289
|
+
export function TableFooter<T extends object>(props: TableFooterProps<T>): JSX.Element {
|
|
1290
|
+
const [local, domProps] = splitProps(props, ["items", "class", "style", "slot", "children"]);
|
|
1291
|
+
|
|
1292
|
+
const context = useContext(TableContext);
|
|
1293
|
+
if (!context) {
|
|
1294
|
+
throw new Error("TableFooter must be used within a Table");
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
const { rowGroupProps } = createTableRowGroup(() => ({ type: "tfoot" }));
|
|
1298
|
+
const items = createMemo(() => local.items ?? []);
|
|
1299
|
+
|
|
1300
|
+
const renderValues = createMemo<TableFooterRenderProps>(() => ({
|
|
1301
|
+
isEmpty: items().length === 0,
|
|
1302
|
+
}));
|
|
1303
|
+
|
|
1304
|
+
const renderProps = useRenderProps(
|
|
1305
|
+
{
|
|
1306
|
+
class: local.class,
|
|
1307
|
+
style: local.style,
|
|
1308
|
+
defaultClassName: "solidaria-Table-footer",
|
|
1309
|
+
},
|
|
1310
|
+
renderValues,
|
|
1311
|
+
);
|
|
1312
|
+
|
|
1313
|
+
const cleanRowGroupProps = () => {
|
|
1314
|
+
const { ref: _ref, ...rest } = rowGroupProps as Record<string, unknown>;
|
|
1315
|
+
return rest;
|
|
1316
|
+
};
|
|
1317
|
+
|
|
1318
|
+
return (
|
|
1319
|
+
<tfoot
|
|
1320
|
+
{...domProps}
|
|
1321
|
+
{...cleanRowGroupProps()}
|
|
1322
|
+
class={renderProps.class()}
|
|
1323
|
+
style={renderProps.style()}
|
|
1324
|
+
>
|
|
1325
|
+
<Show
|
|
1326
|
+
when={local.items && typeof local.children === "function"}
|
|
1327
|
+
fallback={local.children as JSX.Element}
|
|
1328
|
+
>
|
|
1329
|
+
<For each={items()}>{(item) => (local.children as (item: T) => JSX.Element)(item)}</For>
|
|
1330
|
+
</Show>
|
|
1331
|
+
</tfoot>
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
|
|
821
1335
|
export function TableLoadMoreItem(props: TableLoadMoreItemProps): JSX.Element {
|
|
822
|
-
let
|
|
1336
|
+
let sentinelRef: HTMLDivElement | undefined;
|
|
823
1337
|
const [isPending, setIsPending] = createSignal(false);
|
|
824
1338
|
const isLoading = () => !!props.isLoading || isPending();
|
|
825
1339
|
|
|
826
1340
|
const triggerLoadMore = async () => {
|
|
827
|
-
if (
|
|
1341
|
+
if (isPending()) return;
|
|
828
1342
|
setIsPending(true);
|
|
829
1343
|
try {
|
|
830
1344
|
await props.onLoadMore();
|
|
@@ -834,40 +1348,61 @@ export function TableLoadMoreItem(props: TableLoadMoreItemProps): JSX.Element {
|
|
|
834
1348
|
};
|
|
835
1349
|
|
|
836
1350
|
createEffect(() => {
|
|
837
|
-
if (!
|
|
838
|
-
const
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
1351
|
+
if (!sentinelRef || typeof IntersectionObserver !== "function") return;
|
|
1352
|
+
const offset = props.scrollOffset ?? 1;
|
|
1353
|
+
const margin = `0px 0px ${100 * offset}% 0px`;
|
|
1354
|
+
const observer = new IntersectionObserver(
|
|
1355
|
+
(entries) => {
|
|
1356
|
+
if (entries[0]?.isIntersecting) {
|
|
1357
|
+
void triggerLoadMore();
|
|
1358
|
+
}
|
|
1359
|
+
},
|
|
1360
|
+
{ rootMargin: margin },
|
|
1361
|
+
);
|
|
1362
|
+
observer.observe(sentinelRef);
|
|
844
1363
|
return () => observer.disconnect();
|
|
845
1364
|
});
|
|
846
1365
|
|
|
847
1366
|
const renderProps = useRenderProps(
|
|
848
1367
|
{
|
|
849
|
-
children:
|
|
1368
|
+
children:
|
|
1369
|
+
props.children ??
|
|
1370
|
+
(() => (isLoading() ? <div role="progressbar" aria-label="loading" /> : null)),
|
|
850
1371
|
class: props.class,
|
|
851
1372
|
style: props.style,
|
|
852
|
-
defaultClassName:
|
|
1373
|
+
defaultClassName: "solidaria-Table-loadMore",
|
|
853
1374
|
},
|
|
854
|
-
() => ({ isLoading: isLoading() })
|
|
1375
|
+
() => ({ isLoading: isLoading() }),
|
|
855
1376
|
);
|
|
856
1377
|
|
|
857
1378
|
return (
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
1379
|
+
<>
|
|
1380
|
+
<tr style={{ position: "relative", width: 0, height: 0, overflow: "hidden" }} inert>
|
|
1381
|
+
<td>
|
|
1382
|
+
<div
|
|
1383
|
+
ref={sentinelRef}
|
|
1384
|
+
data-testid="loadMoreSentinel"
|
|
1385
|
+
style={{ position: "absolute", height: "1px", width: "1px" }}
|
|
1386
|
+
/>
|
|
1387
|
+
</td>
|
|
1388
|
+
</tr>
|
|
1389
|
+
<Show when={isLoading()}>
|
|
1390
|
+
<tr
|
|
1391
|
+
role="row"
|
|
1392
|
+
tabIndex={0}
|
|
1393
|
+
onFocus={() => {
|
|
1394
|
+
void triggerLoadMore();
|
|
1395
|
+
}}
|
|
1396
|
+
class={renderProps.class()}
|
|
1397
|
+
style={renderProps.style()}
|
|
1398
|
+
data-loading
|
|
1399
|
+
>
|
|
1400
|
+
<td role="rowheader" colSpan={props.colSpan ?? 1}>
|
|
1401
|
+
{renderProps.renderChildren()}
|
|
1402
|
+
</td>
|
|
1403
|
+
</tr>
|
|
1404
|
+
</Show>
|
|
1405
|
+
</>
|
|
871
1406
|
);
|
|
872
1407
|
}
|
|
873
1408
|
|
|
@@ -875,27 +1410,56 @@ export function TableLoadMoreItem(props: TableLoadMoreItemProps): JSX.Element {
|
|
|
875
1410
|
* A row in a table.
|
|
876
1411
|
*/
|
|
877
1412
|
export function TableRow<T extends object>(props: TableRowProps<T>): JSX.Element {
|
|
878
|
-
const [local, domProps] = splitProps(props, [
|
|
1413
|
+
const [local, domProps] = splitProps(props, [
|
|
1414
|
+
"class",
|
|
1415
|
+
"style",
|
|
1416
|
+
"render",
|
|
1417
|
+
"slot",
|
|
1418
|
+
"id",
|
|
1419
|
+
"item",
|
|
1420
|
+
"columns",
|
|
1421
|
+
"isDisabled",
|
|
1422
|
+
"onAction",
|
|
1423
|
+
"children",
|
|
1424
|
+
"ref",
|
|
1425
|
+
"href",
|
|
1426
|
+
"target",
|
|
1427
|
+
"rel",
|
|
1428
|
+
"download",
|
|
1429
|
+
"ping",
|
|
1430
|
+
"referrerPolicy",
|
|
1431
|
+
"routerOptions",
|
|
1432
|
+
]);
|
|
879
1433
|
|
|
880
|
-
// Get context
|
|
881
1434
|
const context = useContext(TableContext);
|
|
882
1435
|
if (!context) {
|
|
883
|
-
throw new Error(
|
|
1436
|
+
throw new Error("TableRow must be used within a Table");
|
|
884
1437
|
}
|
|
885
1438
|
const { state, collection } = context;
|
|
886
1439
|
const tableContext = context as unknown as TableContextValue<T>;
|
|
1440
|
+
const registeredCellIds: string[] = [];
|
|
1441
|
+
const generatedId = createUniqueId();
|
|
1442
|
+
const rowKey = () => local.id ?? generatedId;
|
|
1443
|
+
const router = useRouter();
|
|
1444
|
+
const linkProps = createMemo(() =>
|
|
1445
|
+
useLinkProps({
|
|
1446
|
+
href: local.href,
|
|
1447
|
+
target: local.target,
|
|
1448
|
+
rel: local.rel,
|
|
1449
|
+
download: local.download,
|
|
1450
|
+
ping: local.ping,
|
|
1451
|
+
referrerPolicy: local.referrerPolicy,
|
|
1452
|
+
}),
|
|
1453
|
+
);
|
|
887
1454
|
|
|
888
|
-
// Create ref signal
|
|
889
1455
|
const [ref, setRef] = createSignal<HTMLTableRowElement | null>(null);
|
|
890
1456
|
|
|
891
|
-
// Find the row node
|
|
892
1457
|
const rowNode = createMemo(() => {
|
|
893
|
-
const node = collection.getItem(
|
|
1458
|
+
const node = collection.getItem(rowKey());
|
|
894
1459
|
if (!node) {
|
|
895
|
-
// Create a simple node for the row
|
|
896
1460
|
return {
|
|
897
|
-
type:
|
|
898
|
-
key:
|
|
1461
|
+
type: "item" as const,
|
|
1462
|
+
key: rowKey(),
|
|
899
1463
|
value: local.item ?? null,
|
|
900
1464
|
textValue: String(local.id),
|
|
901
1465
|
level: 0,
|
|
@@ -907,52 +1471,84 @@ export function TableRow<T extends object>(props: TableRowProps<T>): JSX.Element
|
|
|
907
1471
|
return node;
|
|
908
1472
|
});
|
|
909
1473
|
|
|
910
|
-
// Create row aria props
|
|
911
1474
|
const rowAria = createTableRow<object>(
|
|
912
1475
|
() => ({
|
|
913
1476
|
node: rowNode(),
|
|
1477
|
+
isVirtualized: tableContext.isVirtualized,
|
|
1478
|
+
isDisabled: local.isDisabled,
|
|
914
1479
|
onAction: local.onAction,
|
|
1480
|
+
href: linkProps().href,
|
|
1481
|
+
onLinkAction: (event) => {
|
|
1482
|
+
const target = ref();
|
|
1483
|
+
if (!target || !local.href) return;
|
|
1484
|
+
router.open(target, event as RouterClickModifiers, local.href, local.routerOptions);
|
|
1485
|
+
},
|
|
915
1486
|
}),
|
|
916
1487
|
() => state as TableState<object, TableCollection<object>>,
|
|
917
|
-
ref
|
|
1488
|
+
ref,
|
|
918
1489
|
);
|
|
919
1490
|
const isSelected = () => rowAria.isSelected;
|
|
920
1491
|
const isDisabled = () => rowAria.isDisabled;
|
|
921
1492
|
const isPressed = () => rowAria.isPressed;
|
|
1493
|
+
const isInteractive = () => {
|
|
1494
|
+
const tableData = getTableData(state as TableState<object, TableCollection<object>>);
|
|
1495
|
+
return state.selectionMode !== "none" || !!tableData?.actions.onRowAction || !!local.onAction;
|
|
1496
|
+
};
|
|
922
1497
|
|
|
923
|
-
// Create hover
|
|
924
1498
|
const { isHovered, hoverProps } = createHover({
|
|
925
1499
|
get isDisabled() {
|
|
926
|
-
return isDisabled();
|
|
1500
|
+
return isDisabled() || !isInteractive();
|
|
1501
|
+
},
|
|
1502
|
+
onHoverStart(e) {
|
|
1503
|
+
(domProps as Record<string, (e: unknown) => void>).onHoverStart?.(e);
|
|
1504
|
+
},
|
|
1505
|
+
onHoverEnd(e) {
|
|
1506
|
+
(domProps as Record<string, (e: unknown) => void>).onHoverEnd?.(e);
|
|
1507
|
+
},
|
|
1508
|
+
onHoverChange(isHovering) {
|
|
1509
|
+
(domProps as Record<string, (isHovering: boolean) => void>).onHoverChange?.(isHovering);
|
|
927
1510
|
},
|
|
928
1511
|
});
|
|
929
1512
|
|
|
930
|
-
// Create focus ring
|
|
931
1513
|
const { isFocusVisible, focusProps } = createFocusRing();
|
|
1514
|
+
const [isFocusWithin, setIsFocusWithin] = createSignal(false);
|
|
1515
|
+
const focusWithinProps = {
|
|
1516
|
+
onFocusIn() {
|
|
1517
|
+
setIsFocusWithin(true);
|
|
1518
|
+
},
|
|
1519
|
+
onFocusOut(e: FocusEvent) {
|
|
1520
|
+
const currentTarget = e.currentTarget as HTMLElement;
|
|
1521
|
+
const nextTarget = e.relatedTarget as Node | null;
|
|
1522
|
+
if (!nextTarget || !currentTarget.contains(nextTarget)) {
|
|
1523
|
+
setIsFocusWithin(false);
|
|
1524
|
+
}
|
|
1525
|
+
},
|
|
1526
|
+
};
|
|
932
1527
|
|
|
933
|
-
|
|
934
|
-
const isFocused = createMemo(() => state.focusedKey === local.id);
|
|
1528
|
+
const isFocused = createMemo(() => state.focusedKey === rowKey());
|
|
935
1529
|
const draggableItem = createMemo(() => {
|
|
936
|
-
if (!tableContext.dragAndDropHooks?.useDraggableItem || !tableContext.dragState)
|
|
1530
|
+
if (!tableContext.dragAndDropHooks?.useDraggableItem || !tableContext.dragState)
|
|
1531
|
+
return undefined;
|
|
937
1532
|
return tableContext.dragAndDropHooks.useDraggableItem(
|
|
938
1533
|
{
|
|
939
|
-
key:
|
|
1534
|
+
key: rowKey() as string | number,
|
|
1535
|
+
hasDragButton: true,
|
|
940
1536
|
},
|
|
941
|
-
tableContext.dragState as Parameters<NonNullable<DragAndDropHooks<T>[
|
|
1537
|
+
tableContext.dragState as Parameters<NonNullable<DragAndDropHooks<T>["useDraggableItem"]>>[1],
|
|
942
1538
|
);
|
|
943
1539
|
});
|
|
944
1540
|
const droppableItem = createMemo(() => {
|
|
945
|
-
if (!tableContext.dragAndDropHooks?.useDroppableItem || !tableContext.dropState)
|
|
1541
|
+
if (!tableContext.dragAndDropHooks?.useDroppableItem || !tableContext.dropState)
|
|
1542
|
+
return undefined;
|
|
946
1543
|
return tableContext.dragAndDropHooks.useDroppableItem(
|
|
947
1544
|
{
|
|
948
|
-
key:
|
|
1545
|
+
key: rowKey() as string | number,
|
|
949
1546
|
},
|
|
950
|
-
tableContext.dropState as Parameters<NonNullable<DragAndDropHooks<T>[
|
|
951
|
-
() => ref()
|
|
1547
|
+
tableContext.dropState as Parameters<NonNullable<DragAndDropHooks<T>["useDroppableItem"]>>[1],
|
|
1548
|
+
() => ref(),
|
|
952
1549
|
);
|
|
953
1550
|
});
|
|
954
1551
|
|
|
955
|
-
// Render props values
|
|
956
1552
|
const renderValues = createMemo<TableRowRenderProps>(() => ({
|
|
957
1553
|
isSelected: isSelected(),
|
|
958
1554
|
isFocused: isFocused(),
|
|
@@ -967,12 +1563,11 @@ export function TableRow<T extends object>(props: TableRowProps<T>): JSX.Element
|
|
|
967
1563
|
{
|
|
968
1564
|
class: local.class,
|
|
969
1565
|
style: local.style,
|
|
970
|
-
defaultClassName:
|
|
1566
|
+
defaultClassName: "solidaria-Table-row",
|
|
971
1567
|
},
|
|
972
|
-
renderValues
|
|
1568
|
+
renderValues,
|
|
973
1569
|
);
|
|
974
1570
|
|
|
975
|
-
// Remove ref from spread props
|
|
976
1571
|
const cleanRowProps = () => {
|
|
977
1572
|
const { ref: _ref1, ...rest } = rowAria.rowProps as Record<string, unknown>;
|
|
978
1573
|
return rest;
|
|
@@ -987,37 +1582,101 @@ export function TableRow<T extends object>(props: TableRowProps<T>): JSX.Element
|
|
|
987
1582
|
};
|
|
988
1583
|
|
|
989
1584
|
const rowContextValue: TableRowContextValue = {
|
|
990
|
-
rowKey:
|
|
1585
|
+
rowKey: rowKey(),
|
|
991
1586
|
rowNode: rowNode(),
|
|
1587
|
+
getCellColumnKey(cellId, explicitId) {
|
|
1588
|
+
if (explicitId === "__selection__") {
|
|
1589
|
+
return explicitId;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
let index = registeredCellIds.indexOf(cellId);
|
|
1593
|
+
if (index < 0) {
|
|
1594
|
+
index = registeredCellIds.length;
|
|
1595
|
+
registeredCellIds.push(cellId);
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
return explicitId ?? tableContext.columns[index]?.key;
|
|
1599
|
+
},
|
|
992
1600
|
};
|
|
1601
|
+
const dragButtonProps = createMemo<ButtonProps>(() => {
|
|
1602
|
+
const props = (draggableItem()?.dragButtonProps as ButtonProps | undefined) ?? {};
|
|
1603
|
+
const textValue = (rowNode().textValue || String(rowKey())).trim();
|
|
1604
|
+
return {
|
|
1605
|
+
...props,
|
|
1606
|
+
"aria-label": `Drag ${textValue}`,
|
|
1607
|
+
style: {
|
|
1608
|
+
...(typeof props.style === "object" ? props.style : {}),
|
|
1609
|
+
"pointer-events": "none",
|
|
1610
|
+
},
|
|
1611
|
+
};
|
|
1612
|
+
});
|
|
1613
|
+
const buttonContextValue = createMemo(() => ({
|
|
1614
|
+
slots: {
|
|
1615
|
+
default: {},
|
|
1616
|
+
drag: dragButtonProps(),
|
|
1617
|
+
},
|
|
1618
|
+
}));
|
|
1619
|
+
|
|
1620
|
+
const rowChildren = () => (
|
|
1621
|
+
<ButtonContext.Provider value={buttonContextValue()}>
|
|
1622
|
+
{typeof local.children === "function" ? (
|
|
1623
|
+
local.columns ? (
|
|
1624
|
+
<For each={local.columns}>
|
|
1625
|
+
{(column) =>
|
|
1626
|
+
(local.children as (column: TableColumnDefinition<T>) => JSX.Element)(column)
|
|
1627
|
+
}
|
|
1628
|
+
</For>
|
|
1629
|
+
) : (
|
|
1630
|
+
(local.children as (renderProps: TableRowRenderProps) => JSX.Element)(renderValues())
|
|
1631
|
+
)
|
|
1632
|
+
) : (
|
|
1633
|
+
local.children
|
|
1634
|
+
)}
|
|
1635
|
+
</ButtonContext.Provider>
|
|
1636
|
+
);
|
|
1637
|
+
const tableRowProps = () =>
|
|
1638
|
+
({
|
|
1639
|
+
ref: (el: HTMLTableRowElement) => {
|
|
1640
|
+
setRef(el);
|
|
1641
|
+
assignRef(local.ref, el);
|
|
1642
|
+
},
|
|
1643
|
+
...domProps,
|
|
1644
|
+
...mergeProps(
|
|
1645
|
+
cleanRowProps(),
|
|
1646
|
+
cleanHoverProps(),
|
|
1647
|
+
cleanFocusProps(),
|
|
1648
|
+
focusWithinProps as Record<string, unknown>,
|
|
1649
|
+
(draggableItem()?.dragProps as Record<string, unknown> | undefined) ?? {},
|
|
1650
|
+
(droppableItem()?.dropProps as Record<string, unknown> | undefined) ?? {},
|
|
1651
|
+
),
|
|
1652
|
+
class: renderProps.class(),
|
|
1653
|
+
style: renderProps.style(),
|
|
1654
|
+
"data-selected": isSelected() || undefined,
|
|
1655
|
+
"data-focused": isFocused() || undefined,
|
|
1656
|
+
"data-focus-visible": (isFocusVisible() && isFocused()) || undefined,
|
|
1657
|
+
"data-focus-visible-within": dataAttr(isFocusWithin() && isGlobalFocusVisible()),
|
|
1658
|
+
"data-pressed": isPressed() || undefined,
|
|
1659
|
+
"data-hovered": isHovered() || undefined,
|
|
1660
|
+
"data-disabled": isDisabled() || undefined,
|
|
1661
|
+
"data-href": linkProps().href,
|
|
1662
|
+
"data-target": linkProps().target,
|
|
1663
|
+
"data-rel": linkProps().rel,
|
|
1664
|
+
"data-download":
|
|
1665
|
+
typeof linkProps().download === "string"
|
|
1666
|
+
? linkProps().download
|
|
1667
|
+
: linkProps().download
|
|
1668
|
+
? ""
|
|
1669
|
+
: undefined,
|
|
1670
|
+
"data-ping": linkProps().ping,
|
|
1671
|
+
"data-referrer-policy": linkProps().referrerPolicy,
|
|
1672
|
+
"data-dragging": draggableItem()?.isDragging || undefined,
|
|
1673
|
+
"data-drop-target": droppableItem()?.isDropTarget || undefined,
|
|
1674
|
+
children: rowChildren(),
|
|
1675
|
+
}) as JSX.HTMLAttributes<HTMLTableRowElement>;
|
|
993
1676
|
|
|
994
1677
|
return (
|
|
995
1678
|
<TableRowContext.Provider value={rowContextValue}>
|
|
996
|
-
<tr
|
|
997
|
-
ref={setRef}
|
|
998
|
-
{...domProps}
|
|
999
|
-
{...mergeProps(
|
|
1000
|
-
cleanRowProps(),
|
|
1001
|
-
cleanHoverProps(),
|
|
1002
|
-
cleanFocusProps(),
|
|
1003
|
-
(draggableItem()?.dragProps as Record<string, unknown> | undefined) ?? {},
|
|
1004
|
-
(droppableItem()?.dropProps as Record<string, unknown> | undefined) ?? {}
|
|
1005
|
-
)}
|
|
1006
|
-
class={renderProps.class()}
|
|
1007
|
-
style={renderProps.style()}
|
|
1008
|
-
data-selected={isSelected() || undefined}
|
|
1009
|
-
data-focused={isFocused() || undefined}
|
|
1010
|
-
data-focus-visible={(isFocusVisible() && isFocused()) || undefined}
|
|
1011
|
-
data-pressed={isPressed() || undefined}
|
|
1012
|
-
data-hovered={isHovered() || undefined}
|
|
1013
|
-
data-disabled={isDisabled() || undefined}
|
|
1014
|
-
data-dragging={draggableItem()?.isDragging || undefined}
|
|
1015
|
-
data-drop-target={droppableItem()?.isDropTarget || undefined}
|
|
1016
|
-
>
|
|
1017
|
-
{typeof local.children === 'function'
|
|
1018
|
-
? local.children(renderValues())
|
|
1019
|
-
: local.children}
|
|
1020
|
-
</tr>
|
|
1679
|
+
{local.render ? local.render(tableRowProps(), renderValues()) : <tr {...tableRowProps()} />}
|
|
1021
1680
|
</TableRowContext.Provider>
|
|
1022
1681
|
);
|
|
1023
1682
|
}
|
|
@@ -1026,40 +1685,47 @@ export function TableRow<T extends object>(props: TableRowProps<T>): JSX.Element
|
|
|
1026
1685
|
* A cell in a table row.
|
|
1027
1686
|
*/
|
|
1028
1687
|
export function TableCell(props: TableCellProps): JSX.Element {
|
|
1029
|
-
const [local, domProps] = splitProps(props, [
|
|
1688
|
+
const [local, domProps] = splitProps(props, [
|
|
1689
|
+
"class",
|
|
1690
|
+
"style",
|
|
1691
|
+
"render",
|
|
1692
|
+
"slot",
|
|
1693
|
+
"id",
|
|
1694
|
+
"colSpan",
|
|
1695
|
+
"children",
|
|
1696
|
+
"ref",
|
|
1697
|
+
]);
|
|
1030
1698
|
|
|
1031
|
-
// Get context
|
|
1032
1699
|
const tableContext = useContext(TableContext);
|
|
1033
1700
|
const rowContext = useContext(TableRowContext);
|
|
1034
1701
|
|
|
1035
1702
|
if (!tableContext) {
|
|
1036
|
-
throw new Error(
|
|
1703
|
+
throw new Error("TableCell must be used within a Table");
|
|
1037
1704
|
}
|
|
1038
1705
|
if (!rowContext) {
|
|
1039
|
-
throw new Error(
|
|
1706
|
+
throw new Error("TableCell must be used within a Table");
|
|
1040
1707
|
}
|
|
1041
1708
|
|
|
1042
1709
|
const { state, collection } = tableContext;
|
|
1043
1710
|
const { rowKey, rowNode } = rowContext;
|
|
1044
1711
|
|
|
1045
|
-
// Create ref signal
|
|
1046
1712
|
const [ref, setRef] = createSignal<HTMLTableCellElement | null>(null);
|
|
1713
|
+
const cellId = createUniqueId();
|
|
1714
|
+
const columnKey = createMemo(() => rowContext.getCellColumnKey(cellId, local.id));
|
|
1047
1715
|
|
|
1048
|
-
// Find the cell node
|
|
1049
1716
|
const cellNode = createMemo(() => {
|
|
1050
|
-
|
|
1051
|
-
if (
|
|
1052
|
-
const cellKey = `${rowKey}-${
|
|
1717
|
+
const key = columnKey();
|
|
1718
|
+
if (key != null) {
|
|
1719
|
+
const cellKey = `${rowKey}-${key}`;
|
|
1053
1720
|
const node = collection.getItem(cellKey);
|
|
1054
1721
|
if (node) return node;
|
|
1055
1722
|
}
|
|
1056
1723
|
|
|
1057
|
-
// Otherwise create a simple node
|
|
1058
1724
|
return {
|
|
1059
|
-
type:
|
|
1060
|
-
key:
|
|
1725
|
+
type: "cell" as const,
|
|
1726
|
+
key: key ?? `${rowKey}-cell`,
|
|
1061
1727
|
value: rowNode.value,
|
|
1062
|
-
textValue:
|
|
1728
|
+
textValue: "",
|
|
1063
1729
|
level: 1,
|
|
1064
1730
|
index: 0,
|
|
1065
1731
|
parentKey: rowKey,
|
|
@@ -1067,32 +1733,34 @@ export function TableCell(props: TableCellProps): JSX.Element {
|
|
|
1067
1733
|
childNodes: [],
|
|
1068
1734
|
} as GridNode<unknown>;
|
|
1069
1735
|
});
|
|
1736
|
+
const cellColumnIndex = createMemo(() => {
|
|
1737
|
+
const key = columnKey();
|
|
1738
|
+
if (key == null) return undefined;
|
|
1739
|
+
const cellKey = `${rowKey}-${key}`;
|
|
1740
|
+
return collection.getItem(cellKey) ? (cellNode().index ?? 0) : undefined;
|
|
1741
|
+
});
|
|
1070
1742
|
|
|
1071
|
-
// Create cell aria props
|
|
1072
1743
|
const cellAria = createTableCell<object>(
|
|
1073
1744
|
() => ({
|
|
1074
1745
|
node: cellNode(),
|
|
1075
1746
|
}),
|
|
1076
1747
|
() => state as TableState<object, TableCollection<object>>,
|
|
1077
|
-
ref
|
|
1748
|
+
ref,
|
|
1078
1749
|
);
|
|
1079
1750
|
const isPressed = () => cellAria.isPressed;
|
|
1080
1751
|
|
|
1081
|
-
// Create hover
|
|
1082
1752
|
const { isHovered, hoverProps } = createHover({
|
|
1083
1753
|
isDisabled: false,
|
|
1084
1754
|
});
|
|
1085
1755
|
|
|
1086
|
-
// Create focus ring
|
|
1087
1756
|
const { isFocusVisible, focusProps } = createFocusRing();
|
|
1088
1757
|
|
|
1089
|
-
// Check if focused
|
|
1090
1758
|
const isFocused = createMemo(() => state.focusedKey === cellNode().key);
|
|
1091
1759
|
|
|
1092
|
-
// Render props values
|
|
1093
1760
|
const renderValues = createMemo<TableCellRenderProps>(() => ({
|
|
1094
1761
|
isFocused: isFocused(),
|
|
1095
1762
|
isFocusVisible: isFocusVisible() && isFocused(),
|
|
1763
|
+
columnIndex: cellColumnIndex() ?? 0,
|
|
1096
1764
|
isPressed: isPressed(),
|
|
1097
1765
|
isHovered: isHovered(),
|
|
1098
1766
|
}));
|
|
@@ -1102,12 +1770,11 @@ export function TableCell(props: TableCellProps): JSX.Element {
|
|
|
1102
1770
|
{
|
|
1103
1771
|
class: local.class,
|
|
1104
1772
|
style: local.style,
|
|
1105
|
-
defaultClassName:
|
|
1773
|
+
defaultClassName: "solidaria-Table-cell",
|
|
1106
1774
|
},
|
|
1107
|
-
renderValues
|
|
1775
|
+
renderValues,
|
|
1108
1776
|
);
|
|
1109
1777
|
|
|
1110
|
-
// Remove ref from spread props
|
|
1111
1778
|
const cleanCellProps = () => {
|
|
1112
1779
|
const { ref: _ref1, ...rest } = cellAria.gridCellProps as Record<string, unknown>;
|
|
1113
1780
|
return rest;
|
|
@@ -1121,65 +1788,117 @@ export function TableCell(props: TableCellProps): JSX.Element {
|
|
|
1121
1788
|
return rest;
|
|
1122
1789
|
};
|
|
1123
1790
|
|
|
1124
|
-
|
|
1791
|
+
const cellChildren = () =>
|
|
1792
|
+
typeof local.children === "function" ? local.children(renderValues()) : local.children;
|
|
1793
|
+
const tableCellProps = () =>
|
|
1794
|
+
({
|
|
1795
|
+
ref: (el: HTMLTableCellElement) => {
|
|
1796
|
+
setRef(el);
|
|
1797
|
+
assignRef(local.ref, el);
|
|
1798
|
+
},
|
|
1799
|
+
...domProps,
|
|
1800
|
+
...mergeProps(cleanCellProps(), cleanHoverProps(), cleanFocusProps()),
|
|
1801
|
+
colSpan: local.colSpan,
|
|
1802
|
+
class: renderProps.class(),
|
|
1803
|
+
style: renderProps.style(),
|
|
1804
|
+
"data-focused": isFocused() || undefined,
|
|
1805
|
+
"data-focus-visible": (isFocusVisible() && isFocused()) || undefined,
|
|
1806
|
+
"data-column-index": cellColumnIndex(),
|
|
1807
|
+
"data-pressed": isPressed() || undefined,
|
|
1808
|
+
"data-hovered": isHovered() || undefined,
|
|
1809
|
+
children: cellChildren(),
|
|
1810
|
+
}) as JSX.TdHTMLAttributes<HTMLTableCellElement>;
|
|
1811
|
+
|
|
1812
|
+
return local.render ? (
|
|
1813
|
+
local.render(tableCellProps(), renderValues())
|
|
1814
|
+
) : (
|
|
1125
1815
|
<td
|
|
1126
|
-
ref={
|
|
1816
|
+
ref={(el) => {
|
|
1817
|
+
setRef(el);
|
|
1818
|
+
assignRef(local.ref, el);
|
|
1819
|
+
}}
|
|
1127
1820
|
{...domProps}
|
|
1128
|
-
{...cleanCellProps()}
|
|
1129
|
-
{
|
|
1130
|
-
{...cleanFocusProps()}
|
|
1821
|
+
{...mergeProps(cleanCellProps(), cleanHoverProps(), cleanFocusProps())}
|
|
1822
|
+
colSpan={local.colSpan}
|
|
1131
1823
|
class={renderProps.class()}
|
|
1132
1824
|
style={renderProps.style()}
|
|
1133
1825
|
data-focused={isFocused() || undefined}
|
|
1134
1826
|
data-focus-visible={(isFocusVisible() && isFocused()) || undefined}
|
|
1827
|
+
data-column-index={cellColumnIndex()}
|
|
1135
1828
|
data-pressed={isPressed() || undefined}
|
|
1136
1829
|
data-hovered={isHovered() || undefined}
|
|
1137
1830
|
>
|
|
1138
|
-
{
|
|
1139
|
-
? local.children(renderValues())
|
|
1140
|
-
: local.children}
|
|
1831
|
+
{cellChildren()}
|
|
1141
1832
|
</td>
|
|
1142
1833
|
);
|
|
1143
1834
|
}
|
|
1144
1835
|
|
|
1836
|
+
export interface TableSelectionCheckboxProps {
|
|
1837
|
+
rowKey: Key;
|
|
1838
|
+
class?: string;
|
|
1839
|
+
style?: JSX.CSSProperties;
|
|
1840
|
+
excludeFromTabOrder?: boolean;
|
|
1841
|
+
"aria-label"?: string;
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1145
1844
|
/**
|
|
1146
1845
|
* A checkbox cell for row selection.
|
|
1147
1846
|
*/
|
|
1148
|
-
export function TableSelectionCheckbox(props:
|
|
1847
|
+
export function TableSelectionCheckbox(props: TableSelectionCheckboxProps): JSX.Element {
|
|
1149
1848
|
const context = useContext(TableContext);
|
|
1150
1849
|
if (!context) {
|
|
1151
|
-
throw new Error(
|
|
1850
|
+
throw new Error("TableSelectionCheckbox must be used within a Table");
|
|
1152
1851
|
}
|
|
1153
1852
|
|
|
1154
1853
|
const { state } = context;
|
|
1155
1854
|
|
|
1156
1855
|
const selectionCheckboxAria = createTableSelectionCheckbox<object>(
|
|
1157
1856
|
() => ({ key: props.rowKey }),
|
|
1158
|
-
() => state as TableState<object, TableCollection<object
|
|
1857
|
+
() => state as TableState<object, TableCollection<object>>,
|
|
1159
1858
|
);
|
|
1160
1859
|
|
|
1161
|
-
return
|
|
1860
|
+
return (
|
|
1861
|
+
<input
|
|
1862
|
+
{...selectionCheckboxAria.checkboxProps}
|
|
1863
|
+
class={props.class}
|
|
1864
|
+
style={props.style}
|
|
1865
|
+
tabIndex={props.excludeFromTabOrder ? -1 : selectionCheckboxAria.checkboxProps.tabIndex}
|
|
1866
|
+
aria-label={props["aria-label"] ?? selectionCheckboxAria.checkboxProps["aria-label"]}
|
|
1867
|
+
/>
|
|
1868
|
+
);
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
export interface TableSelectAllCheckboxProps {
|
|
1872
|
+
class?: string;
|
|
1873
|
+
style?: JSX.CSSProperties;
|
|
1874
|
+
"aria-label"?: string;
|
|
1162
1875
|
}
|
|
1163
1876
|
|
|
1164
1877
|
/**
|
|
1165
1878
|
* A checkbox for select-all functionality.
|
|
1166
1879
|
*/
|
|
1167
|
-
export function TableSelectAllCheckbox(): JSX.Element {
|
|
1880
|
+
export function TableSelectAllCheckbox(props: TableSelectAllCheckboxProps = {}): JSX.Element {
|
|
1168
1881
|
const context = useContext(TableContext);
|
|
1169
1882
|
if (!context) {
|
|
1170
|
-
throw new Error(
|
|
1883
|
+
throw new Error("TableSelectAllCheckbox must be used within a Table");
|
|
1171
1884
|
}
|
|
1172
1885
|
|
|
1173
1886
|
const { state } = context;
|
|
1174
1887
|
|
|
1175
1888
|
const selectAllCheckboxAria = createTableSelectAllCheckbox<object>(
|
|
1176
|
-
() => state as TableState<object, TableCollection<object
|
|
1889
|
+
() => state as TableState<object, TableCollection<object>>,
|
|
1177
1890
|
);
|
|
1178
1891
|
|
|
1179
|
-
return
|
|
1892
|
+
return (
|
|
1893
|
+
<input
|
|
1894
|
+
{...selectAllCheckboxAria.checkboxProps}
|
|
1895
|
+
class={props.class}
|
|
1896
|
+
style={props.style}
|
|
1897
|
+
aria-label={props["aria-label"] ?? selectAllCheckboxAria.checkboxProps["aria-label"]}
|
|
1898
|
+
/>
|
|
1899
|
+
);
|
|
1180
1900
|
}
|
|
1181
1901
|
|
|
1182
|
-
// Attach components as static properties
|
|
1183
1902
|
Table.Header = TableHeader;
|
|
1184
1903
|
Table.Column = TableColumn;
|
|
1185
1904
|
Table.Body = TableBody;
|
|
@@ -1189,29 +1908,276 @@ Table.Cell = TableCell;
|
|
|
1189
1908
|
Table.SelectionCheckbox = TableSelectionCheckbox;
|
|
1190
1909
|
Table.SelectAllCheckbox = TableSelectAllCheckbox;
|
|
1191
1910
|
|
|
1911
|
+
export interface ColumnResizerRenderProps {
|
|
1912
|
+
/** Whether the resizer handle is hovered. */
|
|
1913
|
+
isHovered: boolean;
|
|
1914
|
+
/** Whether the resizer's hidden input is focused. */
|
|
1915
|
+
isFocused: boolean;
|
|
1916
|
+
/** Whether the column is currently being resized. */
|
|
1917
|
+
isResizing: boolean;
|
|
1918
|
+
/** The direction(s) the column can be resized: 'both', 'left', 'right'. */
|
|
1919
|
+
resizableDirection: "both" | "left" | "right";
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1192
1922
|
export interface ColumnResizerProps extends SlotProps {
|
|
1193
|
-
|
|
1194
|
-
|
|
1923
|
+
/** The column key this resizer belongs to. */
|
|
1924
|
+
column: { key: Key };
|
|
1925
|
+
/** Accessible label for the resizer. */
|
|
1926
|
+
"aria-label"?: string;
|
|
1927
|
+
/** Whether resizing is disabled. */
|
|
1928
|
+
isDisabled?: boolean;
|
|
1929
|
+
/** Called when resize starts. */
|
|
1930
|
+
onResizeStart?: (widths: Map<Key, number>) => void;
|
|
1931
|
+
/** Called during resize. */
|
|
1932
|
+
onResize?: (widths: Map<Key, number>) => void;
|
|
1933
|
+
/** Called when resize ends. */
|
|
1934
|
+
onResizeEnd?: (widths: Map<Key, number>) => void;
|
|
1935
|
+
/** CSS class — can be a string or function of render props. */
|
|
1936
|
+
class?: ClassNameOrFunction<ColumnResizerRenderProps>;
|
|
1937
|
+
/** Inline style — can be object or function of render props. */
|
|
1938
|
+
style?: StyleOrFunction<ColumnResizerRenderProps>;
|
|
1939
|
+
/** Children — can be JSX or render function. */
|
|
1940
|
+
children?: JSX.Element | RenderChildren<ColumnResizerRenderProps>;
|
|
1195
1941
|
}
|
|
1196
1942
|
|
|
1197
1943
|
export function ColumnResizer(props: ColumnResizerProps): JSX.Element {
|
|
1198
|
-
|
|
1944
|
+
const [local, domProps] = splitProps(props, [
|
|
1945
|
+
"column",
|
|
1946
|
+
"aria-label",
|
|
1947
|
+
"isDisabled",
|
|
1948
|
+
"onResizeStart",
|
|
1949
|
+
"onResize",
|
|
1950
|
+
"onResizeEnd",
|
|
1951
|
+
"class",
|
|
1952
|
+
"style",
|
|
1953
|
+
"slot",
|
|
1954
|
+
"children",
|
|
1955
|
+
]);
|
|
1956
|
+
|
|
1957
|
+
// Register this column with the ResizableTableContainer (auto-collect columns)
|
|
1958
|
+
const registerColumn = useContext(ResizableTableRegisterContext);
|
|
1959
|
+
if (registerColumn) {
|
|
1960
|
+
registerColumn(local.column.key, { key: local.column.key });
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
const resizeCtx = useContext(TableColumnResizeStateContext);
|
|
1964
|
+
const hasResizeContext = !!resizeCtx;
|
|
1965
|
+
|
|
1966
|
+
// Create a fallback "no-op" resize state for when there's no ResizableTableContainer
|
|
1967
|
+
const noopResizeState: TableColumnResizeState = {
|
|
1968
|
+
resizingColumn: () => null,
|
|
1969
|
+
columnWidths: () => new Map(),
|
|
1970
|
+
startResize() {},
|
|
1971
|
+
endResize() {},
|
|
1972
|
+
updateResizedColumns(_key: Key, _width: number) {
|
|
1973
|
+
return new Map();
|
|
1974
|
+
},
|
|
1975
|
+
getColumnWidth() {
|
|
1976
|
+
return 0;
|
|
1977
|
+
},
|
|
1978
|
+
getColumnMinWidth() {
|
|
1979
|
+
return 75;
|
|
1980
|
+
},
|
|
1981
|
+
getColumnMaxWidth() {
|
|
1982
|
+
return Infinity;
|
|
1983
|
+
},
|
|
1984
|
+
};
|
|
1985
|
+
|
|
1986
|
+
const { isHovered, hoverProps } = createHover({ isDisabled: local.isDisabled ?? false });
|
|
1987
|
+
const [isFocused, setIsFocused] = createSignal(false);
|
|
1988
|
+
|
|
1989
|
+
// Create the ARIA resize hook — always create it but use reactive state getter
|
|
1990
|
+
const columnResize = createTableColumnResize(
|
|
1991
|
+
() => ({
|
|
1992
|
+
column: local.column,
|
|
1993
|
+
"aria-label": local["aria-label"] ?? "Resizer",
|
|
1994
|
+
isDisabled: local.isDisabled,
|
|
1995
|
+
onResizeStart: (widths) => {
|
|
1996
|
+
resizeCtx?.getCallbacks?.().onResizeStart?.(widths);
|
|
1997
|
+
local.onResizeStart?.(widths);
|
|
1998
|
+
},
|
|
1999
|
+
onResize: (widths) => {
|
|
2000
|
+
resizeCtx?.getCallbacks?.().onResize?.(widths);
|
|
2001
|
+
local.onResize?.(widths);
|
|
2002
|
+
},
|
|
2003
|
+
onResizeEnd: (widths) => {
|
|
2004
|
+
resizeCtx?.getCallbacks?.().onResizeEnd?.(widths);
|
|
2005
|
+
local.onResizeEnd?.(widths);
|
|
2006
|
+
},
|
|
2007
|
+
}),
|
|
2008
|
+
() => resizeCtx?.getState() ?? noopResizeState,
|
|
2009
|
+
);
|
|
2010
|
+
|
|
2011
|
+
const renderValues = createMemo<ColumnResizerRenderProps>(() => ({
|
|
2012
|
+
isHovered: isHovered(),
|
|
2013
|
+
isFocused: isFocused(),
|
|
2014
|
+
isResizing: columnResize.isResizing(),
|
|
2015
|
+
resizableDirection: "both",
|
|
2016
|
+
}));
|
|
2017
|
+
|
|
2018
|
+
const renderProps = useRenderProps(
|
|
2019
|
+
{
|
|
2020
|
+
class: local.class,
|
|
2021
|
+
style: local.style,
|
|
2022
|
+
defaultClassName: "solidaria-Table-columnResizer",
|
|
2023
|
+
},
|
|
2024
|
+
renderValues,
|
|
2025
|
+
);
|
|
2026
|
+
|
|
2027
|
+
const cleanHoverProps = () => {
|
|
2028
|
+
const { ref: _ref, ...rest } = hoverProps as Record<string, unknown>;
|
|
2029
|
+
return rest;
|
|
2030
|
+
};
|
|
2031
|
+
|
|
2032
|
+
return (
|
|
2033
|
+
<div
|
|
2034
|
+
{...domProps}
|
|
2035
|
+
{...columnResize.resizerProps}
|
|
2036
|
+
{...cleanHoverProps()}
|
|
2037
|
+
class={renderProps.class()}
|
|
2038
|
+
style={renderProps.style()}
|
|
2039
|
+
data-hovered={isHovered() || undefined}
|
|
2040
|
+
data-resizing={columnResize.isResizing() || undefined}
|
|
2041
|
+
>
|
|
2042
|
+
<Show when={hasResizeContext}>
|
|
2043
|
+
<input
|
|
2044
|
+
{...columnResize.inputProps}
|
|
2045
|
+
onFocus={() => setIsFocused(true)}
|
|
2046
|
+
onBlur={() => setIsFocused(false)}
|
|
2047
|
+
/>
|
|
2048
|
+
</Show>
|
|
2049
|
+
{typeof local.children === "function"
|
|
2050
|
+
? (local.children as (props: ColumnResizerRenderProps) => JSX.Element)(renderValues())
|
|
2051
|
+
: local.children}
|
|
2052
|
+
</div>
|
|
2053
|
+
);
|
|
1199
2054
|
}
|
|
1200
2055
|
|
|
1201
2056
|
export interface ResizableTableContainerProps extends SlotProps {
|
|
2057
|
+
/** Children (should contain a Table). */
|
|
1202
2058
|
children?: JSX.Element;
|
|
2059
|
+
/** Column resize definitions. If not provided, columns from child ColumnResizers are auto-detected. */
|
|
2060
|
+
columns?: Array<{ key: Key; width?: ColumnSize; minWidth?: number; maxWidth?: number }>;
|
|
2061
|
+
/** CSS class name. */
|
|
1203
2062
|
class?: string;
|
|
2063
|
+
/** Inline style. */
|
|
1204
2064
|
style?: JSX.CSSProperties;
|
|
2065
|
+
/** Called when column resize starts. */
|
|
2066
|
+
onResizeStart?: (widths: Map<Key, number>) => void;
|
|
2067
|
+
/** Called during column resize. */
|
|
2068
|
+
onResize?: (widths: Map<Key, number>) => void;
|
|
2069
|
+
/** Called when column resize ends. */
|
|
2070
|
+
onResizeEnd?: (widths: Map<Key, number>) => void;
|
|
1205
2071
|
}
|
|
1206
2072
|
|
|
1207
2073
|
export function ResizableTableContainer(props: ResizableTableContainerProps): JSX.Element {
|
|
2074
|
+
const [local, domProps] = splitProps(props, [
|
|
2075
|
+
"class",
|
|
2076
|
+
"style",
|
|
2077
|
+
"slot",
|
|
2078
|
+
"children",
|
|
2079
|
+
"columns",
|
|
2080
|
+
"onResizeStart",
|
|
2081
|
+
"onResize",
|
|
2082
|
+
"onResizeEnd",
|
|
2083
|
+
]);
|
|
2084
|
+
|
|
2085
|
+
const [containerRef, setContainerRef] = createSignal<HTMLDivElement | null>(null);
|
|
2086
|
+
const [tableWidth, setTableWidth] = createSignal(0);
|
|
2087
|
+
|
|
2088
|
+
// Track container width via ResizeObserver
|
|
2089
|
+
createEffect(() => {
|
|
2090
|
+
const el = containerRef();
|
|
2091
|
+
if (!el) return;
|
|
2092
|
+
|
|
2093
|
+
// Initial measurement
|
|
2094
|
+
setTableWidth(el.clientWidth);
|
|
2095
|
+
|
|
2096
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
2097
|
+
const observer = new ResizeObserver((entries) => {
|
|
2098
|
+
for (const entry of entries) {
|
|
2099
|
+
setTableWidth(entry.contentRect.width);
|
|
2100
|
+
}
|
|
2101
|
+
});
|
|
2102
|
+
observer.observe(el);
|
|
2103
|
+
onCleanup(() => observer.disconnect());
|
|
2104
|
+
}
|
|
2105
|
+
});
|
|
2106
|
+
|
|
2107
|
+
// Auto-collected columns from ColumnResizer children
|
|
2108
|
+
const [autoColumns, setAutoColumns] = createSignal<
|
|
2109
|
+
Map<Key, { key: Key; width?: ColumnSize; minWidth?: number; maxWidth?: number }>
|
|
2110
|
+
>(new Map());
|
|
2111
|
+
|
|
2112
|
+
const registerColumn = (
|
|
2113
|
+
key: Key,
|
|
2114
|
+
def: { key: Key; width?: ColumnSize; minWidth?: number; maxWidth?: number },
|
|
2115
|
+
) => {
|
|
2116
|
+
setAutoColumns((prev) => {
|
|
2117
|
+
const next = new Map(prev);
|
|
2118
|
+
next.set(key, def);
|
|
2119
|
+
return next;
|
|
2120
|
+
});
|
|
2121
|
+
};
|
|
2122
|
+
|
|
2123
|
+
// Columns: prefer explicit prop, fall back to auto-collected
|
|
2124
|
+
const effectiveColumns = createMemo(() => {
|
|
2125
|
+
if (local.columns && local.columns.length > 0) return local.columns;
|
|
2126
|
+
return Array.from(autoColumns().values());
|
|
2127
|
+
});
|
|
2128
|
+
|
|
2129
|
+
// Use measured width, with a reasonable fallback for environments without layout (e.g. jsdom)
|
|
2130
|
+
const effectiveWidth = createMemo(() => {
|
|
2131
|
+
const w = tableWidth();
|
|
2132
|
+
return w > 0 ? w : 800; // fallback to 800px
|
|
2133
|
+
});
|
|
2134
|
+
|
|
2135
|
+
// Create resize state
|
|
2136
|
+
const resizeState = createMemo(() => {
|
|
2137
|
+
const cols = effectiveColumns();
|
|
2138
|
+
if (cols.length === 0) return null;
|
|
2139
|
+
|
|
2140
|
+
return createTableColumnResizeState(() => ({
|
|
2141
|
+
tableWidth: effectiveWidth(),
|
|
2142
|
+
columns: cols,
|
|
2143
|
+
}));
|
|
2144
|
+
});
|
|
2145
|
+
|
|
2146
|
+
// Provide a stable context object with a reactive getter
|
|
2147
|
+
const contextValue = {
|
|
2148
|
+
getState: () => resizeState(),
|
|
2149
|
+
getCallbacks: () => ({
|
|
2150
|
+
onResizeStart: local.onResizeStart,
|
|
2151
|
+
onResize: local.onResize,
|
|
2152
|
+
onResizeEnd: local.onResizeEnd,
|
|
2153
|
+
}),
|
|
2154
|
+
};
|
|
2155
|
+
|
|
1208
2156
|
return (
|
|
1209
|
-
<
|
|
1210
|
-
{
|
|
1211
|
-
|
|
2157
|
+
<ResizableTableRegisterContext.Provider value={registerColumn}>
|
|
2158
|
+
<TableColumnResizeStateContext.Provider value={contextValue}>
|
|
2159
|
+
<div
|
|
2160
|
+
ref={setContainerRef}
|
|
2161
|
+
{...domProps}
|
|
2162
|
+
class={local.class ?? "solidaria-ResizableTableContainer"}
|
|
2163
|
+
style={{ position: "relative", overflow: "auto", ...local.style }}
|
|
2164
|
+
>
|
|
2165
|
+
{local.children}
|
|
2166
|
+
</div>
|
|
2167
|
+
</TableColumnResizeStateContext.Provider>
|
|
2168
|
+
</ResizableTableRegisterContext.Provider>
|
|
1212
2169
|
);
|
|
1213
2170
|
}
|
|
1214
2171
|
|
|
2172
|
+
/** Internal context for ColumnResizer to register itself with ResizableTableContainer */
|
|
2173
|
+
const ResizableTableRegisterContext = createContext<
|
|
2174
|
+
| ((
|
|
2175
|
+
key: Key,
|
|
2176
|
+
def: { key: Key; width?: ColumnSize; minWidth?: number; maxWidth?: number },
|
|
2177
|
+
) => void)
|
|
2178
|
+
| null
|
|
2179
|
+
>(null);
|
|
2180
|
+
|
|
1215
2181
|
export function useTableOptions() {
|
|
1216
2182
|
return useContext(TableContext);
|
|
1217
2183
|
}
|