@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.
Files changed (56) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +30 -0
  3. package/README.md +103 -0
  4. package/dist/DataTable-12s-8LMBtS2u.cjs +1532 -0
  5. package/dist/DataTable-12s-Bd20OuMs.js +1482 -0
  6. package/dist/DataTable-12s-Zml1x9Kr-12s-CTRzgvMT.cjs +161 -0
  7. package/dist/DataTable-12s-Zml1x9Kr-12s-Coi-3lUa.js +153 -0
  8. package/dist/Menu-12s-Db_zr1AP.cjs +104 -0
  9. package/dist/Menu-12s-Di5JADW4.js +78 -0
  10. package/dist/Select-12s-1Uh7XbkG.js +219 -0
  11. package/dist/Select-12s-D8DdKT-U.cjs +250 -0
  12. package/dist/body-cells-12s-BnOVUTKN.js +453 -0
  13. package/dist/body-cells-12s-BoOTcg-l.cjs +462 -0
  14. package/dist/column-calc-12s-C6IPSOaF.js +129 -0
  15. package/dist/column-calc-12s-CKuSpe-l.cjs +149 -0
  16. package/dist/column-layout-12s-CTu5yT_D.cjs +120 -0
  17. package/dist/column-layout-12s-s7wnxkMN.js +99 -0
  18. package/dist/column-menu-12s-BPE9N1gs.cjs +427 -0
  19. package/dist/column-menu-12s-mRBk-4oa.js +407 -0
  20. package/dist/data-table-recipe.css +442 -0
  21. package/dist/data-table-recipe.layered.css +505 -0
  22. package/dist/editors-12s-BO9Mp034.js +525 -0
  23. package/dist/editors-12s-DO8TZkYV.cjs +555 -0
  24. package/dist/index.cjs +14 -0
  25. package/dist/index.d.ts +323 -0
  26. package/dist/index.js +10 -0
  27. package/dist/resize-12s-jQY-PX67.cjs +58 -0
  28. package/dist/resize-12s-nsMvrmLt.js +58 -0
  29. package/dist/states-12s-CLO_uVue.cjs +125 -0
  30. package/dist/states-12s-DEFSxJCO.js +122 -0
  31. package/dist/styles.css +14660 -0
  32. package/dist/toolbar-12s-AVU-sbDW.js +69 -0
  33. package/dist/toolbar-12s-CaLgJrDo.cjs +71 -0
  34. package/dist/useDataTable-12s-BHiDUubw-12s-CBeCwTe7.js +61 -0
  35. package/dist/useDataTable-12s-BHiDUubw-12s-Dgvt7XDk.cjs +61 -0
  36. package/dist/useDataTableColumnCalc-12s-DTQQTtEJ-12s-CvD14JYo.cjs +207 -0
  37. package/dist/useDataTableColumnCalc-12s-DTQQTtEJ-12s-DsEse3uq.js +183 -0
  38. package/dist/useDataTableColumns-12s-Cn2ZAzFL-12s-CD6goR59.js +289 -0
  39. package/dist/useDataTableColumns-12s-Cn2ZAzFL-12s-CUk_CpYT.cjs +292 -0
  40. package/dist/useDataTableEdit-12s-9Wb_UCrN-12s-BoOHq3GB.js +98 -0
  41. package/dist/useDataTableEdit-12s-9Wb_UCrN-12s-De6YHBba.cjs +98 -0
  42. package/dist/useDataTableFilter-12s-ZzbmWMin-12s-CWyBeFna.js +74 -0
  43. package/dist/useDataTableFilter-12s-ZzbmWMin-12s-lpjzZY3_.cjs +74 -0
  44. package/dist/useDataTableInfiniteScroll-12s-8ZG31URd-12s-BexjP6iO.js +104 -0
  45. package/dist/useDataTableInfiniteScroll-12s-8ZG31URd-12s-Bt8gojr9.cjs +104 -0
  46. package/dist/useDataTableRow-12s-CPYaDa1p-12s-BmUSAnPe.js +30 -0
  47. package/dist/useDataTableRow-12s-CPYaDa1p-12s-D-DRjwAe.cjs +30 -0
  48. package/dist/useDataTableScroll-12s-BKDCr0en-12s-C7f2vHeG.js +61 -0
  49. package/dist/useDataTableScroll-12s-BKDCr0en-12s-CUvUYMtt.cjs +61 -0
  50. package/dist/useDataTableSearch-12s-Ck3iWcpX-12s-B-65V6gH.js +37 -0
  51. package/dist/useDataTableSearch-12s-Ck3iWcpX-12s-DhVfN2Hq.cjs +37 -0
  52. package/dist/useDataTableSelection-12s-BIDtiRQq-12s-IS__VAnU.cjs +71 -0
  53. package/dist/useDataTableSelection-12s-BIDtiRQq-12s-O6M_BhrH.js +71 -0
  54. package/dist/useDataTableSort-12s-DvJYtmeS-12s-C5VGnsq4.cjs +78 -0
  55. package/dist/useDataTableSort-12s-DvJYtmeS-12s-DABSNt6U.js +78 -0
  56. 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;