@crispy-seed/data-table 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +30 -0
- package/README.md +103 -0
- package/dist/DataTable-12s-8LMBtS2u.cjs +1532 -0
- package/dist/DataTable-12s-Bd20OuMs.js +1482 -0
- package/dist/DataTable-12s-Zml1x9Kr-12s-CTRzgvMT.cjs +161 -0
- package/dist/DataTable-12s-Zml1x9Kr-12s-Coi-3lUa.js +153 -0
- package/dist/Menu-12s-Db_zr1AP.cjs +104 -0
- package/dist/Menu-12s-Di5JADW4.js +78 -0
- package/dist/Select-12s-1Uh7XbkG.js +219 -0
- package/dist/Select-12s-D8DdKT-U.cjs +250 -0
- package/dist/body-cells-12s-BnOVUTKN.js +453 -0
- package/dist/body-cells-12s-BoOTcg-l.cjs +462 -0
- package/dist/column-calc-12s-C6IPSOaF.js +129 -0
- package/dist/column-calc-12s-CKuSpe-l.cjs +149 -0
- package/dist/column-layout-12s-CTu5yT_D.cjs +120 -0
- package/dist/column-layout-12s-s7wnxkMN.js +99 -0
- package/dist/column-menu-12s-BPE9N1gs.cjs +427 -0
- package/dist/column-menu-12s-mRBk-4oa.js +407 -0
- package/dist/data-table-recipe.css +442 -0
- package/dist/data-table-recipe.layered.css +505 -0
- package/dist/editors-12s-BO9Mp034.js +525 -0
- package/dist/editors-12s-DO8TZkYV.cjs +555 -0
- package/dist/index.cjs +14 -0
- package/dist/index.d.ts +323 -0
- package/dist/index.js +10 -0
- package/dist/resize-12s-jQY-PX67.cjs +58 -0
- package/dist/resize-12s-nsMvrmLt.js +58 -0
- package/dist/states-12s-CLO_uVue.cjs +125 -0
- package/dist/states-12s-DEFSxJCO.js +122 -0
- package/dist/styles.css +14660 -0
- package/dist/toolbar-12s-AVU-sbDW.js +69 -0
- package/dist/toolbar-12s-CaLgJrDo.cjs +71 -0
- package/dist/useDataTable-12s-BHiDUubw-12s-CBeCwTe7.js +61 -0
- package/dist/useDataTable-12s-BHiDUubw-12s-Dgvt7XDk.cjs +61 -0
- package/dist/useDataTableColumnCalc-12s-DTQQTtEJ-12s-CvD14JYo.cjs +207 -0
- package/dist/useDataTableColumnCalc-12s-DTQQTtEJ-12s-DsEse3uq.js +183 -0
- package/dist/useDataTableColumns-12s-Cn2ZAzFL-12s-CD6goR59.js +289 -0
- package/dist/useDataTableColumns-12s-Cn2ZAzFL-12s-CUk_CpYT.cjs +292 -0
- package/dist/useDataTableEdit-12s-9Wb_UCrN-12s-BoOHq3GB.js +98 -0
- package/dist/useDataTableEdit-12s-9Wb_UCrN-12s-De6YHBba.cjs +98 -0
- package/dist/useDataTableFilter-12s-ZzbmWMin-12s-CWyBeFna.js +74 -0
- package/dist/useDataTableFilter-12s-ZzbmWMin-12s-lpjzZY3_.cjs +74 -0
- package/dist/useDataTableInfiniteScroll-12s-8ZG31URd-12s-BexjP6iO.js +104 -0
- package/dist/useDataTableInfiniteScroll-12s-8ZG31URd-12s-Bt8gojr9.cjs +104 -0
- package/dist/useDataTableRow-12s-CPYaDa1p-12s-BmUSAnPe.js +30 -0
- package/dist/useDataTableRow-12s-CPYaDa1p-12s-D-DRjwAe.cjs +30 -0
- package/dist/useDataTableScroll-12s-BKDCr0en-12s-C7f2vHeG.js +61 -0
- package/dist/useDataTableScroll-12s-BKDCr0en-12s-CUvUYMtt.cjs +61 -0
- package/dist/useDataTableSearch-12s-Ck3iWcpX-12s-B-65V6gH.js +37 -0
- package/dist/useDataTableSearch-12s-Ck3iWcpX-12s-DhVfN2Hq.cjs +37 -0
- package/dist/useDataTableSelection-12s-BIDtiRQq-12s-IS__VAnU.cjs +71 -0
- package/dist/useDataTableSelection-12s-BIDtiRQq-12s-O6M_BhrH.js +71 -0
- package/dist/useDataTableSort-12s-DvJYtmeS-12s-C5VGnsq4.cjs +78 -0
- package/dist/useDataTableSort-12s-DvJYtmeS-12s-DABSNt6U.js +78 -0
- package/package.json +78 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
function __insertCSS(code) {
|
|
3
|
+
if (!code || typeof document == 'undefined') return
|
|
4
|
+
let head = document.head || document.getElementsByTagName('head')[0]
|
|
5
|
+
let style = document.createElement('style')
|
|
6
|
+
style.type = 'text/css'
|
|
7
|
+
head.appendChild(style)
|
|
8
|
+
;style.styleSheet ? (style.styleSheet.cssText = code) : style.appendChild(document.createTextNode(code))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
var React = require('react');
|
|
12
|
+
|
|
13
|
+
function sameCell(a, id, key) {
|
|
14
|
+
return a !== null && a.id === id && a.key === key;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Owns which single cell is mid-edit, which one (if any) has a commit in flight, and
|
|
18
|
+
* which one last failed -- the way useDataTableColumnCalc owns which op is active per
|
|
19
|
+
* column. This hook never touches `TRow` or renders an editor itself; it only tracks
|
|
20
|
+
* WHICH cell is in which of those three states and orchestrates the async commit
|
|
21
|
+
* (crisp's own `commitEdit`, `datatable.tsx`) -- the styled layer (react/DataTable's
|
|
22
|
+
* `editors.tsx`) reads `isEditing`/`isPending`/`getEditError` to decide what to render
|
|
23
|
+
* for a given `(id, key)` and calls `startEdit`/`cancelEdit`/`commitEdit` in response
|
|
24
|
+
* to a double-click, Escape, Enter/blur, or an always-live widget's own change event.
|
|
25
|
+
*
|
|
26
|
+
* Only one cell can be mid-edit at a time (`editingCell`, not a set) -- matching
|
|
27
|
+
* crisp's own single `editing` state and this table's spec (no multi-cell/range
|
|
28
|
+
* editing is in scope here).
|
|
29
|
+
*/ function useDataTableEdit(props) {
|
|
30
|
+
const { onEdit } = props;
|
|
31
|
+
const [editingCell, setEditingCell] = React.useState(null);
|
|
32
|
+
const [pendingCell, setPendingCell] = React.useState(null);
|
|
33
|
+
const [editError, setEditError] = React.useState(null);
|
|
34
|
+
const isEditing = React.useCallback((id, key)=>sameCell(editingCell, id, key), [
|
|
35
|
+
editingCell
|
|
36
|
+
]);
|
|
37
|
+
const isPending = React.useCallback((id, key)=>sameCell(pendingCell, id, key), [
|
|
38
|
+
pendingCell
|
|
39
|
+
]);
|
|
40
|
+
const getEditError = React.useCallback((id, key)=>editError && editError.id === id && editError.key === key ? editError.message : null, [
|
|
41
|
+
editError
|
|
42
|
+
]);
|
|
43
|
+
const startEdit = React.useCallback((id, key)=>{
|
|
44
|
+
// Opening a cell's editor also clears any stale error left over from a PREVIOUS
|
|
45
|
+
// failed commit on it (crisp/#385 comment) -- otherwise a rejection from three
|
|
46
|
+
// edits ago would still read under a freshly-reopened editor.
|
|
47
|
+
setEditError(null);
|
|
48
|
+
setEditingCell({
|
|
49
|
+
id,
|
|
50
|
+
key
|
|
51
|
+
});
|
|
52
|
+
}, []);
|
|
53
|
+
const cancelEdit = React.useCallback(()=>setEditingCell(null), []);
|
|
54
|
+
// Async-aware (crisp/#385): a sync `onEdit` (returns undefined) commits exactly as
|
|
55
|
+
// before -- close the editor immediately. A Promise-returning `onEdit` keeps the
|
|
56
|
+
// editor open with a pending state while it settles, then either closes it
|
|
57
|
+
// (resolve) or closes it AND reverts to the last-known-good value while showing the
|
|
58
|
+
// rejection's message beside the cell (reject). The revert needs no extra code:
|
|
59
|
+
// neither this hook nor the styled layer ever mutates the caller's own row data, so
|
|
60
|
+
// once `editingCell` clears, the cell simply shows that data again.
|
|
61
|
+
const commitEdit = React.useCallback((id, key, value)=>{
|
|
62
|
+
setEditError(null);
|
|
63
|
+
const result = onEdit?.(id, key, value);
|
|
64
|
+
if (result && typeof result.then === "function") {
|
|
65
|
+
setPendingCell({
|
|
66
|
+
id,
|
|
67
|
+
key
|
|
68
|
+
});
|
|
69
|
+
result.then(()=>{
|
|
70
|
+
setPendingCell(null);
|
|
71
|
+
setEditingCell(null);
|
|
72
|
+
}, (err)=>{
|
|
73
|
+
setPendingCell(null);
|
|
74
|
+
setEditingCell(null);
|
|
75
|
+
setEditError({
|
|
76
|
+
id,
|
|
77
|
+
key,
|
|
78
|
+
message: err instanceof Error ? err.message : typeof err === "string" ? err : "Could not save"
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
setEditingCell(null);
|
|
84
|
+
}, [
|
|
85
|
+
onEdit
|
|
86
|
+
]);
|
|
87
|
+
return {
|
|
88
|
+
editingCell,
|
|
89
|
+
isEditing,
|
|
90
|
+
isPending,
|
|
91
|
+
getEditError,
|
|
92
|
+
startEdit,
|
|
93
|
+
cancelEdit,
|
|
94
|
+
commitEdit
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
exports.useDataTableEdit = useDataTableEdit;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
function __insertCSS(code) {
|
|
3
|
+
if (!code || typeof document == 'undefined') return
|
|
4
|
+
let head = document.head || document.getElementsByTagName('head')[0]
|
|
5
|
+
let style = document.createElement('style')
|
|
6
|
+
style.type = 'text/css'
|
|
7
|
+
head.appendChild(style)
|
|
8
|
+
;style.styleSheet ? (style.styleSheet.cssText = code) : style.appendChild(document.createTextNode(code))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
import { d as useControllableState } from './useDataTableColumnCalc-12s-DTQQTtEJ-12s-DsEse3uq.js';
|
|
12
|
+
import { useCallback } from 'react';
|
|
13
|
+
import { t as toggleMembership } from './useDataTableColumns-12s-Cn2ZAzFL-12s-CD6goR59.js';
|
|
14
|
+
|
|
15
|
+
const EMPTY_FILTERS = {};
|
|
16
|
+
// Owns one filter per column key -- the way useDataTableSelection owns one selected-id
|
|
17
|
+
// SET rather than one boolean per row. Applying a column's filter to `data` needs
|
|
18
|
+
// `TRow` (a type this headless package never sees), so -- same split as search/sort --
|
|
19
|
+
// this hook only tracks which mode/values are checked per column; the styled layer
|
|
20
|
+
// (DataTable.tsx's `getVisibleRows`) reads it to actually narrow rows.
|
|
21
|
+
function useDataTableFilter(props) {
|
|
22
|
+
const { filters: filtersProp, defaultFilters = EMPTY_FILTERS, onFiltersChange } = props;
|
|
23
|
+
const [filters = EMPTY_FILTERS, setFilters] = useControllableState({
|
|
24
|
+
prop: filtersProp,
|
|
25
|
+
defaultProp: defaultFilters,
|
|
26
|
+
onChange: onFiltersChange
|
|
27
|
+
});
|
|
28
|
+
const setFilterMode = useCallback((columnKey, mode)=>{
|
|
29
|
+
setFilters((current = EMPTY_FILTERS)=>({
|
|
30
|
+
...current,
|
|
31
|
+
[columnKey]: {
|
|
32
|
+
mode,
|
|
33
|
+
values: current[columnKey]?.values ?? []
|
|
34
|
+
}
|
|
35
|
+
}));
|
|
36
|
+
}, [
|
|
37
|
+
setFilters
|
|
38
|
+
]);
|
|
39
|
+
const toggleFilterValue = useCallback((columnKey, value)=>{
|
|
40
|
+
setFilters((current = EMPTY_FILTERS)=>({
|
|
41
|
+
...current,
|
|
42
|
+
[columnKey]: {
|
|
43
|
+
mode: current[columnKey]?.mode ?? "is",
|
|
44
|
+
values: toggleMembership(current[columnKey]?.values ?? [], value)
|
|
45
|
+
}
|
|
46
|
+
}));
|
|
47
|
+
}, [
|
|
48
|
+
setFilters
|
|
49
|
+
]);
|
|
50
|
+
const clearFilter = useCallback((columnKey)=>{
|
|
51
|
+
setFilters((current = EMPTY_FILTERS)=>{
|
|
52
|
+
if (!(columnKey in current)) return current;
|
|
53
|
+
const next = {
|
|
54
|
+
...current
|
|
55
|
+
};
|
|
56
|
+
delete next[columnKey];
|
|
57
|
+
return next;
|
|
58
|
+
});
|
|
59
|
+
}, [
|
|
60
|
+
setFilters
|
|
61
|
+
]);
|
|
62
|
+
const isFilterActive = useCallback((columnKey)=>(filters[columnKey]?.values.length ?? 0) > 0, [
|
|
63
|
+
filters
|
|
64
|
+
]);
|
|
65
|
+
return {
|
|
66
|
+
filters,
|
|
67
|
+
setFilterMode,
|
|
68
|
+
toggleFilterValue,
|
|
69
|
+
clearFilter,
|
|
70
|
+
isFilterActive
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { useDataTableFilter as u };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
function __insertCSS(code) {
|
|
3
|
+
if (!code || typeof document == 'undefined') return
|
|
4
|
+
let head = document.head || document.getElementsByTagName('head')[0]
|
|
5
|
+
let style = document.createElement('style')
|
|
6
|
+
style.type = 'text/css'
|
|
7
|
+
head.appendChild(style)
|
|
8
|
+
;style.styleSheet ? (style.styleSheet.cssText = code) : style.appendChild(document.createTextNode(code))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
var useDataTableColumnCalc12sDTQQTtEJ12s = require('./useDataTableColumnCalc-12s-DTQQTtEJ-12s-CvD14JYo.cjs');
|
|
12
|
+
var React = require('react');
|
|
13
|
+
var useDataTableColumns12sCn2ZAzFL12s = require('./useDataTableColumns-12s-Cn2ZAzFL-12s-CUk_CpYT.cjs');
|
|
14
|
+
|
|
15
|
+
const EMPTY_FILTERS = {};
|
|
16
|
+
// Owns one filter per column key -- the way useDataTableSelection owns one selected-id
|
|
17
|
+
// SET rather than one boolean per row. Applying a column's filter to `data` needs
|
|
18
|
+
// `TRow` (a type this headless package never sees), so -- same split as search/sort --
|
|
19
|
+
// this hook only tracks which mode/values are checked per column; the styled layer
|
|
20
|
+
// (DataTable.tsx's `getVisibleRows`) reads it to actually narrow rows.
|
|
21
|
+
function useDataTableFilter(props) {
|
|
22
|
+
const { filters: filtersProp, defaultFilters = EMPTY_FILTERS, onFiltersChange } = props;
|
|
23
|
+
const [filters = EMPTY_FILTERS, setFilters] = useDataTableColumnCalc12sDTQQTtEJ12s.useControllableState({
|
|
24
|
+
prop: filtersProp,
|
|
25
|
+
defaultProp: defaultFilters,
|
|
26
|
+
onChange: onFiltersChange
|
|
27
|
+
});
|
|
28
|
+
const setFilterMode = React.useCallback((columnKey, mode)=>{
|
|
29
|
+
setFilters((current = EMPTY_FILTERS)=>({
|
|
30
|
+
...current,
|
|
31
|
+
[columnKey]: {
|
|
32
|
+
mode,
|
|
33
|
+
values: current[columnKey]?.values ?? []
|
|
34
|
+
}
|
|
35
|
+
}));
|
|
36
|
+
}, [
|
|
37
|
+
setFilters
|
|
38
|
+
]);
|
|
39
|
+
const toggleFilterValue = React.useCallback((columnKey, value)=>{
|
|
40
|
+
setFilters((current = EMPTY_FILTERS)=>({
|
|
41
|
+
...current,
|
|
42
|
+
[columnKey]: {
|
|
43
|
+
mode: current[columnKey]?.mode ?? "is",
|
|
44
|
+
values: useDataTableColumns12sCn2ZAzFL12s.toggleMembership(current[columnKey]?.values ?? [], value)
|
|
45
|
+
}
|
|
46
|
+
}));
|
|
47
|
+
}, [
|
|
48
|
+
setFilters
|
|
49
|
+
]);
|
|
50
|
+
const clearFilter = React.useCallback((columnKey)=>{
|
|
51
|
+
setFilters((current = EMPTY_FILTERS)=>{
|
|
52
|
+
if (!(columnKey in current)) return current;
|
|
53
|
+
const next = {
|
|
54
|
+
...current
|
|
55
|
+
};
|
|
56
|
+
delete next[columnKey];
|
|
57
|
+
return next;
|
|
58
|
+
});
|
|
59
|
+
}, [
|
|
60
|
+
setFilters
|
|
61
|
+
]);
|
|
62
|
+
const isFilterActive = React.useCallback((columnKey)=>(filters[columnKey]?.values.length ?? 0) > 0, [
|
|
63
|
+
filters
|
|
64
|
+
]);
|
|
65
|
+
return {
|
|
66
|
+
filters,
|
|
67
|
+
setFilterMode,
|
|
68
|
+
toggleFilterValue,
|
|
69
|
+
clearFilter,
|
|
70
|
+
isFilterActive
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
exports.useDataTableFilter = useDataTableFilter;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
function __insertCSS(code) {
|
|
3
|
+
if (!code || typeof document == 'undefined') return
|
|
4
|
+
let head = document.head || document.getElementsByTagName('head')[0]
|
|
5
|
+
let style = document.createElement('style')
|
|
6
|
+
style.type = 'text/css'
|
|
7
|
+
head.appendChild(style)
|
|
8
|
+
;style.styleSheet ? (style.styleSheet.cssText = code) : style.appendChild(document.createTextNode(code))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
12
|
+
import { s as shouldTriggerLoadMore } from './useDataTableColumns-12s-Cn2ZAzFL-12s-CD6goR59.js';
|
|
13
|
+
|
|
14
|
+
// Triggers before the sentinel is actually on-screen -- Attio's own list prefetches
|
|
15
|
+
// a page ahead of the visible edge so the next batch of rows is already there by the
|
|
16
|
+
// time a fast scroll reaches them, rather than the user briefly out-running the data.
|
|
17
|
+
const ROOT_MARGIN = "160px";
|
|
18
|
+
/**
|
|
19
|
+
* Owns the "is the user close enough to the bottom to load more" concern -- the way
|
|
20
|
+
* useDataTableScroll owns the horizontal scroll-shadow edge state. An
|
|
21
|
+
* IntersectionObserver watching a single sentinel row the styled layer renders as the
|
|
22
|
+
* last child of `<tbody>` while `hasMore` is true (DataTable.tsx's own
|
|
23
|
+
* `InfiniteScrollRow`), observed against `scrollRootRef`'s element (useDataTable.ts
|
|
24
|
+
* wires this to useDataTableScroll's own scroll container) so a BOUNDED table
|
|
25
|
+
* triggers off its own internal scroll, not the browsing viewport.
|
|
26
|
+
*/ function useDataTableInfiniteScroll(props) {
|
|
27
|
+
const { onLoadMore, hasMore = true, loadingMore: loadingMoreProp, scrollRootRef } = props;
|
|
28
|
+
const [internalLoadingMore, setInternalLoadingMore] = useState(false);
|
|
29
|
+
const loadingMore = loadingMoreProp ?? internalLoadingMore;
|
|
30
|
+
// Mirrors the latest props into a ref the observer callback reads -- the callback
|
|
31
|
+
// is registered once per sentinel element (see the effect below) and must not
|
|
32
|
+
// recreate the whole IntersectionObserver on every render just because a fresh
|
|
33
|
+
// `onLoadMore` closure came in, the same "ref mirrors the latest value" shape a
|
|
34
|
+
// debounced/throttled handler needs.
|
|
35
|
+
const latest = useRef({
|
|
36
|
+
onLoadMore,
|
|
37
|
+
hasMore,
|
|
38
|
+
loadingMore
|
|
39
|
+
});
|
|
40
|
+
latest.current = {
|
|
41
|
+
onLoadMore,
|
|
42
|
+
hasMore,
|
|
43
|
+
loadingMore
|
|
44
|
+
};
|
|
45
|
+
const triggerLoadMore = useCallback(()=>{
|
|
46
|
+
const { onLoadMore: fn, hasMore: has, loadingMore: busy } = latest.current;
|
|
47
|
+
if (!shouldTriggerLoadMore(true, has, busy) || !fn) return;
|
|
48
|
+
const result = fn();
|
|
49
|
+
// Only a THENABLE result puts this hook in charge of `loadingMore` -- a
|
|
50
|
+
// synchronous `onLoadMore` (already-fetched data, a local page) commits
|
|
51
|
+
// immediately and never becomes "busy" at all, matching `loadingMoreProp`'s own
|
|
52
|
+
// controlled shape: nothing to suppress a very next scroll-triggered call with.
|
|
53
|
+
if (result && typeof result.then === "function") {
|
|
54
|
+
if (loadingMoreProp === undefined) setInternalLoadingMore(true);
|
|
55
|
+
result.finally(()=>{
|
|
56
|
+
if (loadingMoreProp === undefined) setInternalLoadingMore(false);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}, [
|
|
60
|
+
loadingMoreProp
|
|
61
|
+
]);
|
|
62
|
+
// A state slot (not a plain ref) so attaching the sentinel re-runs the effect below
|
|
63
|
+
// -- a callback ref's own node can't be an effect dependency otherwise.
|
|
64
|
+
const [sentinelNode, setSentinelNode] = useState(null);
|
|
65
|
+
const sentinelRef = useCallback((node)=>setSentinelNode(node), []);
|
|
66
|
+
useEffect(()=>{
|
|
67
|
+
if (!sentinelNode || !onLoadMore || typeof IntersectionObserver === "undefined") return;
|
|
68
|
+
// Effects run after every ref callback of the SAME commit (including
|
|
69
|
+
// `scrollRootRef`'s own, set by useDataTableScroll's `scrollAreaRef`) regardless
|
|
70
|
+
// of sibling ref attach order, so `scrollRootRef.current` is already populated
|
|
71
|
+
// here even on the very first render that mounts both elements at once. `null`
|
|
72
|
+
// (no scroll container yet, or none supplied) falls back to the browsing
|
|
73
|
+
// viewport, IntersectionObserver's own default.
|
|
74
|
+
const root = scrollRootRef?.current ?? null;
|
|
75
|
+
const observer = new IntersectionObserver((entries)=>{
|
|
76
|
+
if (entries.some((entry)=>entry.isIntersecting)) triggerLoadMore();
|
|
77
|
+
}, {
|
|
78
|
+
root,
|
|
79
|
+
rootMargin: ROOT_MARGIN
|
|
80
|
+
});
|
|
81
|
+
observer.observe(sentinelNode);
|
|
82
|
+
return ()=>observer.disconnect();
|
|
83
|
+
}, [
|
|
84
|
+
sentinelNode,
|
|
85
|
+
onLoadMore,
|
|
86
|
+
scrollRootRef,
|
|
87
|
+
triggerLoadMore
|
|
88
|
+
]);
|
|
89
|
+
return {
|
|
90
|
+
/** Attach to the sentinel row's DOM node (a ref callback, `DataTable.tsx`'s own
|
|
91
|
+
* `InfiniteScrollRow`). A no-op (no observer ever created) when `onLoadMore` is
|
|
92
|
+
* omitted -- the styled layer still shouldn't render the sentinel at all in that
|
|
93
|
+
* case (nothing to load more OF), this is just the inert fallback. */ sentinelRef,
|
|
94
|
+
/** Whether to keep rendering the sentinel row at all. */ hasMore,
|
|
95
|
+
/** A fetch is in flight -- shows the loading-more spinner row and gates further
|
|
96
|
+
* triggers, whichever side (controlled or internal) reports it. */ loadingMore,
|
|
97
|
+
/** Fires `onLoadMore` directly, subject to the same `hasMore`/`loadingMore` gate
|
|
98
|
+
* the observer itself uses -- exposed for a consumer that wants a manual retry
|
|
99
|
+
* affordance (e.g. after a failed load) rather than only the observer's own
|
|
100
|
+
* scroll trigger. */ triggerLoadMore
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export { useDataTableInfiniteScroll as u };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
function __insertCSS(code) {
|
|
3
|
+
if (!code || typeof document == 'undefined') return
|
|
4
|
+
let head = document.head || document.getElementsByTagName('head')[0]
|
|
5
|
+
let style = document.createElement('style')
|
|
6
|
+
style.type = 'text/css'
|
|
7
|
+
head.appendChild(style)
|
|
8
|
+
;style.styleSheet ? (style.styleSheet.cssText = code) : style.appendChild(document.createTextNode(code))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
var React = require('react');
|
|
12
|
+
var useDataTableColumns12sCn2ZAzFL12s = require('./useDataTableColumns-12s-Cn2ZAzFL-12s-CUk_CpYT.cjs');
|
|
13
|
+
|
|
14
|
+
// Triggers before the sentinel is actually on-screen -- Attio's own list prefetches
|
|
15
|
+
// a page ahead of the visible edge so the next batch of rows is already there by the
|
|
16
|
+
// time a fast scroll reaches them, rather than the user briefly out-running the data.
|
|
17
|
+
const ROOT_MARGIN = "160px";
|
|
18
|
+
/**
|
|
19
|
+
* Owns the "is the user close enough to the bottom to load more" concern -- the way
|
|
20
|
+
* useDataTableScroll owns the horizontal scroll-shadow edge state. An
|
|
21
|
+
* IntersectionObserver watching a single sentinel row the styled layer renders as the
|
|
22
|
+
* last child of `<tbody>` while `hasMore` is true (DataTable.tsx's own
|
|
23
|
+
* `InfiniteScrollRow`), observed against `scrollRootRef`'s element (useDataTable.ts
|
|
24
|
+
* wires this to useDataTableScroll's own scroll container) so a BOUNDED table
|
|
25
|
+
* triggers off its own internal scroll, not the browsing viewport.
|
|
26
|
+
*/ function useDataTableInfiniteScroll(props) {
|
|
27
|
+
const { onLoadMore, hasMore = true, loadingMore: loadingMoreProp, scrollRootRef } = props;
|
|
28
|
+
const [internalLoadingMore, setInternalLoadingMore] = React.useState(false);
|
|
29
|
+
const loadingMore = loadingMoreProp ?? internalLoadingMore;
|
|
30
|
+
// Mirrors the latest props into a ref the observer callback reads -- the callback
|
|
31
|
+
// is registered once per sentinel element (see the effect below) and must not
|
|
32
|
+
// recreate the whole IntersectionObserver on every render just because a fresh
|
|
33
|
+
// `onLoadMore` closure came in, the same "ref mirrors the latest value" shape a
|
|
34
|
+
// debounced/throttled handler needs.
|
|
35
|
+
const latest = React.useRef({
|
|
36
|
+
onLoadMore,
|
|
37
|
+
hasMore,
|
|
38
|
+
loadingMore
|
|
39
|
+
});
|
|
40
|
+
latest.current = {
|
|
41
|
+
onLoadMore,
|
|
42
|
+
hasMore,
|
|
43
|
+
loadingMore
|
|
44
|
+
};
|
|
45
|
+
const triggerLoadMore = React.useCallback(()=>{
|
|
46
|
+
const { onLoadMore: fn, hasMore: has, loadingMore: busy } = latest.current;
|
|
47
|
+
if (!useDataTableColumns12sCn2ZAzFL12s.shouldTriggerLoadMore(true, has, busy) || !fn) return;
|
|
48
|
+
const result = fn();
|
|
49
|
+
// Only a THENABLE result puts this hook in charge of `loadingMore` -- a
|
|
50
|
+
// synchronous `onLoadMore` (already-fetched data, a local page) commits
|
|
51
|
+
// immediately and never becomes "busy" at all, matching `loadingMoreProp`'s own
|
|
52
|
+
// controlled shape: nothing to suppress a very next scroll-triggered call with.
|
|
53
|
+
if (result && typeof result.then === "function") {
|
|
54
|
+
if (loadingMoreProp === undefined) setInternalLoadingMore(true);
|
|
55
|
+
result.finally(()=>{
|
|
56
|
+
if (loadingMoreProp === undefined) setInternalLoadingMore(false);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}, [
|
|
60
|
+
loadingMoreProp
|
|
61
|
+
]);
|
|
62
|
+
// A state slot (not a plain ref) so attaching the sentinel re-runs the effect below
|
|
63
|
+
// -- a callback ref's own node can't be an effect dependency otherwise.
|
|
64
|
+
const [sentinelNode, setSentinelNode] = React.useState(null);
|
|
65
|
+
const sentinelRef = React.useCallback((node)=>setSentinelNode(node), []);
|
|
66
|
+
React.useEffect(()=>{
|
|
67
|
+
if (!sentinelNode || !onLoadMore || typeof IntersectionObserver === "undefined") return;
|
|
68
|
+
// Effects run after every ref callback of the SAME commit (including
|
|
69
|
+
// `scrollRootRef`'s own, set by useDataTableScroll's `scrollAreaRef`) regardless
|
|
70
|
+
// of sibling ref attach order, so `scrollRootRef.current` is already populated
|
|
71
|
+
// here even on the very first render that mounts both elements at once. `null`
|
|
72
|
+
// (no scroll container yet, or none supplied) falls back to the browsing
|
|
73
|
+
// viewport, IntersectionObserver's own default.
|
|
74
|
+
const root = scrollRootRef?.current ?? null;
|
|
75
|
+
const observer = new IntersectionObserver((entries)=>{
|
|
76
|
+
if (entries.some((entry)=>entry.isIntersecting)) triggerLoadMore();
|
|
77
|
+
}, {
|
|
78
|
+
root,
|
|
79
|
+
rootMargin: ROOT_MARGIN
|
|
80
|
+
});
|
|
81
|
+
observer.observe(sentinelNode);
|
|
82
|
+
return ()=>observer.disconnect();
|
|
83
|
+
}, [
|
|
84
|
+
sentinelNode,
|
|
85
|
+
onLoadMore,
|
|
86
|
+
scrollRootRef,
|
|
87
|
+
triggerLoadMore
|
|
88
|
+
]);
|
|
89
|
+
return {
|
|
90
|
+
/** Attach to the sentinel row's DOM node (a ref callback, `DataTable.tsx`'s own
|
|
91
|
+
* `InfiniteScrollRow`). A no-op (no observer ever created) when `onLoadMore` is
|
|
92
|
+
* omitted -- the styled layer still shouldn't render the sentinel at all in that
|
|
93
|
+
* case (nothing to load more OF), this is just the inert fallback. */ sentinelRef,
|
|
94
|
+
/** Whether to keep rendering the sentinel row at all. */ hasMore,
|
|
95
|
+
/** A fetch is in flight -- shows the loading-more spinner row and gates further
|
|
96
|
+
* triggers, whichever side (controlled or internal) reports it. */ loadingMore,
|
|
97
|
+
/** Fires `onLoadMore` directly, subject to the same `hasMore`/`loadingMore` gate
|
|
98
|
+
* the observer itself uses -- exposed for a consumer that wants a manual retry
|
|
99
|
+
* affordance (e.g. after a failed load) rather than only the observer's own
|
|
100
|
+
* scroll trigger. */ triggerLoadMore
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
exports.useDataTableInfiniteScroll = useDataTableInfiniteScroll;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
function __insertCSS(code) {
|
|
3
|
+
if (!code || typeof document == 'undefined') return
|
|
4
|
+
let head = document.head || document.getElementsByTagName('head')[0]
|
|
5
|
+
let style = document.createElement('style')
|
|
6
|
+
style.type = 'text/css'
|
|
7
|
+
head.appendChild(style)
|
|
8
|
+
;style.styleSheet ? (style.styleSheet.cssText = code) : style.appendChild(document.createTextNode(code))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
import { u as useDataTableContext } from './DataTable-12s-Zml1x9Kr-12s-Coi-3lUa.js';
|
|
12
|
+
|
|
13
|
+
// Per-row read of the root's selection state -- the way useSelectItem reads
|
|
14
|
+
// `getItemProps` off the root for one option. Kept as its own hook (rather than
|
|
15
|
+
// inlined into the styled `DataTableRow`) so `DataTableCell`/`DataTableSelectionCell`
|
|
16
|
+
// can read the SAME row's `isSelected`/`toggleRow` through `useDataTableRowContext`
|
|
17
|
+
// without either subscribing to the whole table's context or being handed `rowId`
|
|
18
|
+
// again as a prop.
|
|
19
|
+
function useDataTableRow(props) {
|
|
20
|
+
const { rowId } = props;
|
|
21
|
+
const { isSelected, toggleRow, getRowProps } = useDataTableContext();
|
|
22
|
+
return {
|
|
23
|
+
rowId,
|
|
24
|
+
isSelected: isSelected(rowId),
|
|
25
|
+
toggleRow: ()=>toggleRow(rowId),
|
|
26
|
+
rootProps: getRowProps(rowId)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { useDataTableRow as u };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
function __insertCSS(code) {
|
|
3
|
+
if (!code || typeof document == 'undefined') return
|
|
4
|
+
let head = document.head || document.getElementsByTagName('head')[0]
|
|
5
|
+
let style = document.createElement('style')
|
|
6
|
+
style.type = 'text/css'
|
|
7
|
+
head.appendChild(style)
|
|
8
|
+
;style.styleSheet ? (style.styleSheet.cssText = code) : style.appendChild(document.createTextNode(code))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
var DataTable12sZml1x9Kr12s = require('./DataTable-12s-Zml1x9Kr-12s-CTRzgvMT.cjs');
|
|
12
|
+
|
|
13
|
+
// Per-row read of the root's selection state -- the way useSelectItem reads
|
|
14
|
+
// `getItemProps` off the root for one option. Kept as its own hook (rather than
|
|
15
|
+
// inlined into the styled `DataTableRow`) so `DataTableCell`/`DataTableSelectionCell`
|
|
16
|
+
// can read the SAME row's `isSelected`/`toggleRow` through `useDataTableRowContext`
|
|
17
|
+
// without either subscribing to the whole table's context or being handed `rowId`
|
|
18
|
+
// again as a prop.
|
|
19
|
+
function useDataTableRow(props) {
|
|
20
|
+
const { rowId } = props;
|
|
21
|
+
const { isSelected, toggleRow, getRowProps } = DataTable12sZml1x9Kr12s.useDataTableContext();
|
|
22
|
+
return {
|
|
23
|
+
rowId,
|
|
24
|
+
isSelected: isSelected(rowId),
|
|
25
|
+
toggleRow: ()=>toggleRow(rowId),
|
|
26
|
+
rootProps: getRowProps(rowId)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
exports.useDataTableRow = useDataTableRow;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
function __insertCSS(code) {
|
|
3
|
+
if (!code || typeof document == 'undefined') return
|
|
4
|
+
let head = document.head || document.getElementsByTagName('head')[0]
|
|
5
|
+
let style = document.createElement('style')
|
|
6
|
+
style.type = 'text/css'
|
|
7
|
+
head.appendChild(style)
|
|
8
|
+
;style.styleSheet ? (style.styleSheet.cssText = code) : style.appendChild(document.createTextNode(code))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
import { useRef, useState, useCallback } from 'react';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Tracks a horizontally-scrolling element's edge state for the sticky-column scroll
|
|
15
|
+
* shadow (DATATABLE-ATTIO-SPEC.md's `stickyFirst`/`stickyLast`) -- crisp's own
|
|
16
|
+
* `.crisp-table-scroll[data-scrolled-x]`/`[data-scrolled-x-end]`. Owns the DOM
|
|
17
|
+
* measurement the way `useSelectFloating` owns floating-ui's own DOM measurement: a
|
|
18
|
+
* real ref to a real scrollable element, re-measured on every scroll (and whenever the
|
|
19
|
+
* styled layer calls `remeasure`, e.g. after a column resize/reorder changes the
|
|
20
|
+
* scrollable width without the element itself ever firing a `scroll` event).
|
|
21
|
+
*/ function useDataTableScroll() {
|
|
22
|
+
const elementRef = useRef(null);
|
|
23
|
+
const [scrolledStart, setScrolledStart] = useState(false);
|
|
24
|
+
const [scrolledEnd, setScrolledEnd] = useState(false);
|
|
25
|
+
const remeasure = useCallback(()=>{
|
|
26
|
+
const element = elementRef.current;
|
|
27
|
+
if (!element) return;
|
|
28
|
+
setScrolledStart(element.scrollLeft > 0);
|
|
29
|
+
// A 1px slop: sub-pixel layout can leave `scrollLeft + clientWidth` a fraction
|
|
30
|
+
// short of `scrollWidth` even when fully scrolled, which would otherwise leave
|
|
31
|
+
// the trailing shadow stuck on forever.
|
|
32
|
+
setScrolledEnd(element.scrollLeft + element.clientWidth < element.scrollWidth - 1);
|
|
33
|
+
}, []);
|
|
34
|
+
const scrollAreaRef = useCallback((node)=>{
|
|
35
|
+
elementRef.current = node;
|
|
36
|
+
if (node) remeasure();
|
|
37
|
+
}, [
|
|
38
|
+
remeasure
|
|
39
|
+
]);
|
|
40
|
+
const onScroll = useCallback(()=>remeasure(), [
|
|
41
|
+
remeasure
|
|
42
|
+
]);
|
|
43
|
+
return {
|
|
44
|
+
scrollAreaRef,
|
|
45
|
+
scrolledStart,
|
|
46
|
+
scrolledEnd,
|
|
47
|
+
onScroll,
|
|
48
|
+
remeasure,
|
|
49
|
+
// The raw ref object itself (not just the callback ref above) -- useDataTable.ts
|
|
50
|
+
// hands this to useDataTableInfiniteScroll as the IntersectionObserver `root` for
|
|
51
|
+
// its sentinel row, so a scrolled/bounded table (DATATABLE-ATTIO-SPEC.md's
|
|
52
|
+
// full-body-height feature) auto-loads against ITS OWN scroll boundary rather
|
|
53
|
+
// than the browsing viewport. A stable object identity (a plain ref, not state):
|
|
54
|
+
// useDataTableInfiniteScroll reads `.current` inside an effect, which always runs
|
|
55
|
+
// after every ref callback of the same commit (including this one), so the
|
|
56
|
+
// element is never read before it's attached regardless of sibling ref order.
|
|
57
|
+
elementRef
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { useDataTableScroll as u };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
function __insertCSS(code) {
|
|
3
|
+
if (!code || typeof document == 'undefined') return
|
|
4
|
+
let head = document.head || document.getElementsByTagName('head')[0]
|
|
5
|
+
let style = document.createElement('style')
|
|
6
|
+
style.type = 'text/css'
|
|
7
|
+
head.appendChild(style)
|
|
8
|
+
;style.styleSheet ? (style.styleSheet.cssText = code) : style.appendChild(document.createTextNode(code))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
var React = require('react');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Tracks a horizontally-scrolling element's edge state for the sticky-column scroll
|
|
15
|
+
* shadow (DATATABLE-ATTIO-SPEC.md's `stickyFirst`/`stickyLast`) -- crisp's own
|
|
16
|
+
* `.crisp-table-scroll[data-scrolled-x]`/`[data-scrolled-x-end]`. Owns the DOM
|
|
17
|
+
* measurement the way `useSelectFloating` owns floating-ui's own DOM measurement: a
|
|
18
|
+
* real ref to a real scrollable element, re-measured on every scroll (and whenever the
|
|
19
|
+
* styled layer calls `remeasure`, e.g. after a column resize/reorder changes the
|
|
20
|
+
* scrollable width without the element itself ever firing a `scroll` event).
|
|
21
|
+
*/ function useDataTableScroll() {
|
|
22
|
+
const elementRef = React.useRef(null);
|
|
23
|
+
const [scrolledStart, setScrolledStart] = React.useState(false);
|
|
24
|
+
const [scrolledEnd, setScrolledEnd] = React.useState(false);
|
|
25
|
+
const remeasure = React.useCallback(()=>{
|
|
26
|
+
const element = elementRef.current;
|
|
27
|
+
if (!element) return;
|
|
28
|
+
setScrolledStart(element.scrollLeft > 0);
|
|
29
|
+
// A 1px slop: sub-pixel layout can leave `scrollLeft + clientWidth` a fraction
|
|
30
|
+
// short of `scrollWidth` even when fully scrolled, which would otherwise leave
|
|
31
|
+
// the trailing shadow stuck on forever.
|
|
32
|
+
setScrolledEnd(element.scrollLeft + element.clientWidth < element.scrollWidth - 1);
|
|
33
|
+
}, []);
|
|
34
|
+
const scrollAreaRef = React.useCallback((node)=>{
|
|
35
|
+
elementRef.current = node;
|
|
36
|
+
if (node) remeasure();
|
|
37
|
+
}, [
|
|
38
|
+
remeasure
|
|
39
|
+
]);
|
|
40
|
+
const onScroll = React.useCallback(()=>remeasure(), [
|
|
41
|
+
remeasure
|
|
42
|
+
]);
|
|
43
|
+
return {
|
|
44
|
+
scrollAreaRef,
|
|
45
|
+
scrolledStart,
|
|
46
|
+
scrolledEnd,
|
|
47
|
+
onScroll,
|
|
48
|
+
remeasure,
|
|
49
|
+
// The raw ref object itself (not just the callback ref above) -- useDataTable.ts
|
|
50
|
+
// hands this to useDataTableInfiniteScroll as the IntersectionObserver `root` for
|
|
51
|
+
// its sentinel row, so a scrolled/bounded table (DATATABLE-ATTIO-SPEC.md's
|
|
52
|
+
// full-body-height feature) auto-loads against ITS OWN scroll boundary rather
|
|
53
|
+
// than the browsing viewport. A stable object identity (a plain ref, not state):
|
|
54
|
+
// useDataTableInfiniteScroll reads `.current` inside an effect, which always runs
|
|
55
|
+
// after every ref callback of the same commit (including this one), so the
|
|
56
|
+
// element is never read before it's attached regardless of sibling ref order.
|
|
57
|
+
elementRef
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
exports.useDataTableScroll = useDataTableScroll;
|