cenitu-table 0.3.0 → 0.4.0-beta.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/dist/react.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const _=require("react/jsx-runtime"),r=require("react"),C=require("./index-BHarWDHD.cjs"),T=["fixedRowsTop","fixedColumnsStart","readOnly","columns","colHeaders","mergeCells","columnSorting","filters","fillHandle","locale","language","contextMenu","search"],E=r.forwardRef(function(e,g){const l=r.useRef(null),t=r.useRef(null),u=r.useRef(e);u.current=e,r.useLayoutEffect(()=>{const{className:c,style:h,onChange:S,onSelection:j,onSort:w,onFilter:v,...x}=u.current,d=new C.CenituGrid(l.current,x);return d.addHook("afterChange",(s,a)=>{var n,i;return(i=(n=u.current).onChange)==null?void 0:i.call(n,s,a)}),d.addHook("afterSelection",(s,a,n,i)=>{var y,R;return(R=(y=u.current).onSelection)==null?void 0:R.call(y,s,a,n,i)}),d.addHook("afterColumnSort",(s,a)=>{var n,i;return(i=(n=u.current).onSort)==null?void 0:i.call(n,s,a)}),d.addHook("afterFilter",s=>{var a,n;return(n=(a=u.current).onFilter)==null?void 0:n.call(a,s)}),t.current=d,()=>{d.destroy(),t.current=null}},[]),r.useImperativeHandle(g,()=>t.current,[]);const m=r.useRef(!1);r.useEffect(()=>{if(!m.current)return;const c=t.current;c&&e.data&&!H(c,e.data)&&c.loadData(e.data)},[e.data]);const f=T.map(c=>e[c]);return r.useEffect(()=>{var h;if(!m.current)return;const c={};T.forEach(S=>e[S]!==void 0&&(c[S]=e[S])),(h=t.current)==null||h.updateSettings(c)},f),r.useEffect(()=>{m.current=!0},[]),_.jsx("div",{ref:l,className:e.className,style:e.style})});function H(o,e){if(!C.isObjectData(e))return b(o.getSourceData(),e);if(o.getSourceObjects()===e)return!0;const g=Array.from({length:o.countCols()},(l,t)=>o.colToProp(t));return b(o.getBoundRows(),C.ObjectBinding.create(e,g).toRows())}function b(o,e){const g=Math.max(o.length,e.length);for(let l=0;l<g;l++){const t=o[l]??[],u=e[l]??[],m=Math.max(t.length,u.length);for(let f=0;f<m;f++)if((t[f]??null)!==(u[f]??null))return!1}return!0}exports.CenituGrid=C.CenituGrid;exports.CenituTable=E;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const b=require("react/jsx-runtime"),s=require("react"),h=require("./index-Bidvqh7P.cjs"),w=["fixedRowsTop","fixedColumnsStart","readOnly","columns","colHeaders","mergeCells","columnSorting","filters","fillHandle","locale","language","contextMenu","search","query","totalCount","loading","loadError"],H=s.forwardRef(function(e,C){const f=s.useRef(null),u=s.useRef(null),r=s.useRef(e);r.current=e,s.useLayoutEffect(()=>{const{className:a,style:y,onChange:m,onSelection:x,onSort:E,onFilter:D,onQueryChange:j,onRowClick:v,onRowDoubleClick:q,onColumnLayoutChange:L,..._}=r.current,c=new h.CenituGrid(f.current,_);return c.addHook("afterChange",(o,n)=>{var t,l;return(l=(t=r.current).onChange)==null?void 0:l.call(t,o,n)}),c.addHook("afterSelection",(o,n,t,l)=>{var R,S;return(S=(R=r.current).onSelection)==null?void 0:S.call(R,o,n,t,l)}),c.addHook("afterColumnSort",(o,n)=>{var t,l;return(l=(t=r.current).onSort)==null?void 0:l.call(t,o,n)}),c.addHook("afterFilter",o=>{var n,t;return(t=(n=r.current).onFilter)==null?void 0:t.call(n,o)}),c.addHook("afterQueryChange",(o,n)=>{var t,l;return(l=(t=r.current).onQueryChange)==null?void 0:l.call(t,o,n)}),c.addHook("afterRowClick",o=>{var n,t;return(t=(n=r.current).onRowClick)==null?void 0:t.call(n,o)}),c.addHook("afterRowDoubleClick",o=>{var n,t;return(t=(n=r.current).onRowDoubleClick)==null?void 0:t.call(n,o)}),c.addHook("afterColumnLayout",o=>{var n,t;return(t=(n=r.current).onColumnLayoutChange)==null?void 0:t.call(n,o)}),u.current=c,()=>{c.destroy(),u.current=null}},[]),s.useImperativeHandle(C,()=>u.current,[]);const g=s.useRef(!1);s.useEffect(()=>{if(!g.current)return;const a=u.current;!a||!e.data||(e.dataMode==="server"?a.setPageRows(e.data,e.totalCount):T(a,e.data)||a.loadData(e.data))},[e.data]);const d=w.map(a=>e[a]);return s.useEffect(()=>{var y;if(!g.current)return;const a={};w.forEach(m=>e[m]!==void 0&&(a[m]=e[m])),(y=u.current)==null||y.updateSettings(a)},d),s.useEffect(()=>{g.current=!0},[]),b.jsx("div",{ref:f,className:e.className,style:e.style})});function T(i,e){if(!h.isObjectData(e))return k(i.getSourceData(),e);if(i.getSourceObjects()===e)return!0;const C=Array.from({length:i.countCols()},(f,u)=>i.colToProp(u));return k(i.getBoundRows(),h.ObjectBinding.create(e,C).toRows())}function k(i,e){const C=Math.max(i.length,e.length);for(let f=0;f<C;f++){const u=i[f]??[],r=e[f]??[],g=Math.max(u.length,r.length);for(let d=0;d<g;d++)if((u[d]??null)!==(r[d]??null))return!1}return!0}exports.CenituGrid=h.CenituGrid;exports.CenituTable=H;
package/dist/react.js CHANGED
@@ -1,7 +1,7 @@
1
- import { jsx as T } from "react/jsx-runtime";
2
- import { forwardRef as E, useRef as h, useLayoutEffect as O, useImperativeHandle as R, useEffect as C } from "react";
3
- import { C as b, i as j, O as v } from "./index-BpjIy-X9.js";
4
- const _ = [
1
+ import { jsx as H } from "react/jsx-runtime";
2
+ import { forwardRef as x, useRef as g, useLayoutEffect as b, useImperativeHandle as D, useEffect as y } from "react";
3
+ import { C as E, i as T, O as v } from "./index-BIH30fQ2.js";
4
+ const k = [
5
5
  "fixedRowsTop",
6
6
  "fixedColumnsStart",
7
7
  "readOnly",
@@ -14,58 +14,74 @@ const _ = [
14
14
  "locale",
15
15
  "language",
16
16
  "contextMenu",
17
- "search"
18
- ], G = E(function(t, d) {
19
- const a = h(null), e = h(null), o = h(t);
20
- o.current = t, O(() => {
21
- const { className: c, style: S, onChange: g, onSelection: N, onSort: F, onFilter: I, ...w } = o.current, f = new b(a.current, w);
22
- return f.addHook("afterChange", (u, l) => {
23
- var n, s;
24
- return (s = (n = o.current).onChange) == null ? void 0 : s.call(n, u, l);
25
- }), f.addHook("afterSelection", (u, l, n, s) => {
26
- var x, y;
27
- return (y = (x = o.current).onSelection) == null ? void 0 : y.call(x, u, l, n, s);
28
- }), f.addHook("afterColumnSort", (u, l) => {
29
- var n, s;
30
- return (s = (n = o.current).onSort) == null ? void 0 : s.call(n, u, l);
31
- }), f.addHook("afterFilter", (u) => {
32
- var l, n;
33
- return (n = (l = o.current).onFilter) == null ? void 0 : n.call(l, u);
34
- }), e.current = f, () => {
35
- f.destroy(), e.current = null;
17
+ "search",
18
+ "query",
19
+ "totalCount",
20
+ "loading",
21
+ "loadError"
22
+ ], P = x(function(e, d) {
23
+ const i = g(null), a = g(null), r = g(e);
24
+ r.current = e, b(() => {
25
+ const { className: u, style: h, onChange: m, onSelection: O, onSort: j, onFilter: M, onQueryChange: N, onRowClick: F, onRowDoubleClick: I, onColumnLayoutChange: Q, ..._ } = r.current, l = new E(i.current, _);
26
+ return l.addHook("afterChange", (o, n) => {
27
+ var t, c;
28
+ return (c = (t = r.current).onChange) == null ? void 0 : c.call(t, o, n);
29
+ }), l.addHook("afterSelection", (o, n, t, c) => {
30
+ var w, S;
31
+ return (S = (w = r.current).onSelection) == null ? void 0 : S.call(w, o, n, t, c);
32
+ }), l.addHook("afterColumnSort", (o, n) => {
33
+ var t, c;
34
+ return (c = (t = r.current).onSort) == null ? void 0 : c.call(t, o, n);
35
+ }), l.addHook("afterFilter", (o) => {
36
+ var n, t;
37
+ return (t = (n = r.current).onFilter) == null ? void 0 : t.call(n, o);
38
+ }), l.addHook("afterQueryChange", (o, n) => {
39
+ var t, c;
40
+ return (c = (t = r.current).onQueryChange) == null ? void 0 : c.call(t, o, n);
41
+ }), l.addHook("afterRowClick", (o) => {
42
+ var n, t;
43
+ return (t = (n = r.current).onRowClick) == null ? void 0 : t.call(n, o);
44
+ }), l.addHook("afterRowDoubleClick", (o) => {
45
+ var n, t;
46
+ return (t = (n = r.current).onRowDoubleClick) == null ? void 0 : t.call(n, o);
47
+ }), l.addHook("afterColumnLayout", (o) => {
48
+ var n, t;
49
+ return (t = (n = r.current).onColumnLayoutChange) == null ? void 0 : t.call(n, o);
50
+ }), a.current = l, () => {
51
+ l.destroy(), a.current = null;
36
52
  };
37
- }, []), R(d, () => e.current, []);
38
- const m = h(!1);
39
- C(() => {
40
- if (!m.current) return;
41
- const c = e.current;
42
- c && t.data && !D(c, t.data) && c.loadData(t.data);
43
- }, [t.data]);
44
- const i = _.map((c) => t[c]);
45
- return C(() => {
46
- var S;
47
- if (!m.current) return;
48
- const c = {};
49
- _.forEach((g) => t[g] !== void 0 && (c[g] = t[g])), (S = e.current) == null || S.updateSettings(c);
50
- }, i), C(() => {
51
- m.current = !0;
52
- }, []), /* @__PURE__ */ T("div", { ref: a, className: t.className, style: t.style });
53
+ }, []), D(d, () => a.current, []);
54
+ const C = g(!1);
55
+ y(() => {
56
+ if (!C.current) return;
57
+ const u = a.current;
58
+ !u || !e.data || (e.dataMode === "server" ? u.setPageRows(e.data, e.totalCount) : L(u, e.data) || u.loadData(e.data));
59
+ }, [e.data]);
60
+ const f = k.map((u) => e[u]);
61
+ return y(() => {
62
+ var h;
63
+ if (!C.current) return;
64
+ const u = {};
65
+ k.forEach((m) => e[m] !== void 0 && (u[m] = e[m])), (h = a.current) == null || h.updateSettings(u);
66
+ }, f), y(() => {
67
+ C.current = !0;
68
+ }, []), /* @__PURE__ */ H("div", { ref: i, className: e.className, style: e.style });
53
69
  });
54
- function D(r, t) {
55
- if (!j(t)) return H(r.getSourceData(), t);
56
- if (r.getSourceObjects() === t) return !0;
57
- const d = Array.from({ length: r.countCols() }, (a, e) => r.colToProp(e));
58
- return H(r.getBoundRows(), v.create(t, d).toRows());
70
+ function L(s, e) {
71
+ if (!T(e)) return R(s.getSourceData(), e);
72
+ if (s.getSourceObjects() === e) return !0;
73
+ const d = Array.from({ length: s.countCols() }, (i, a) => s.colToProp(a));
74
+ return R(s.getBoundRows(), v.create(e, d).toRows());
59
75
  }
60
- function H(r, t) {
61
- const d = Math.max(r.length, t.length);
62
- for (let a = 0; a < d; a++) {
63
- const e = r[a] ?? [], o = t[a] ?? [], m = Math.max(e.length, o.length);
64
- for (let i = 0; i < m; i++) if ((e[i] ?? null) !== (o[i] ?? null)) return !1;
76
+ function R(s, e) {
77
+ const d = Math.max(s.length, e.length);
78
+ for (let i = 0; i < d; i++) {
79
+ const a = s[i] ?? [], r = e[i] ?? [], C = Math.max(a.length, r.length);
80
+ for (let f = 0; f < C; f++) if ((a[f] ?? null) !== (r[f] ?? null)) return !1;
65
81
  }
66
82
  return !0;
67
83
  }
68
84
  export {
69
- b as CenituGrid,
70
- G as CenituTable
85
+ E as CenituGrid,
86
+ P as CenituTable
71
87
  };
@@ -1,4 +1,5 @@
1
1
  import type { ConditionOp } from './filters.js';
2
+ import type { ServerFilterDef } from './serverMode.js';
2
3
  import { type CellValue, type RawCell } from '../formula/values.js';
3
4
  export type CellType = 'text' | 'numeric' | 'date' | 'checkbox' | 'dropdown';
4
5
  export interface NumericFormat {
@@ -43,6 +44,12 @@ export interface CellRenderContext {
43
44
  }
44
45
  /** Settings that can be given per column (`columns`) or per cell (`cells`). */
45
46
  export interface CellMeta {
47
+ /** Stable column id used in server queries and change events; default `data`. */
48
+ id?: string;
49
+ /** Server mode: the server can sort this column. */
50
+ sortable?: boolean;
51
+ /** Server mode: what the server can filter this column by. */
52
+ serverFilter?: ServerFilterDef;
46
53
  /** Object mode: property path bound to this column, e.g. "qty" or "customer.name". */
47
54
  data?: string;
48
55
  type?: CellType;
@@ -1,5 +1,6 @@
1
1
  import type { ColumnFilter, SortOrder } from './filters.js';
2
2
  import type { Messages } from './messages.js';
3
+ import { type ServerFilter, type ServerFilterDef } from './serverMode.js';
3
4
  export interface ColumnMenuOptions {
4
5
  anchor: HTMLElement;
5
6
  messages: Messages;
@@ -14,6 +15,12 @@ export interface ColumnMenuOptions {
14
15
  filterable: boolean;
15
16
  onSort(order: SortOrder | null, add?: boolean): void;
16
17
  onFilter(filter: ColumnFilter | null): void;
18
+ /** Server mode: replaces the local filter controls. */
19
+ server?: {
20
+ def: ServerFilterDef;
21
+ value: ServerFilter | undefined;
22
+ onFilter(filter: ServerFilter | null): void;
23
+ };
17
24
  onClose(): void;
18
25
  }
19
26
  /** Excel-like column dropdown: sort buttons, a condition filter and a value checklist. */
@@ -11,6 +11,7 @@ import { type ColumnFilter, type SortOrder } from './filters.js';
11
11
  import { type MergeArea } from './merges.js';
12
12
  import { type Messages } from './messages.js';
13
13
  import { type SearchOptions } from './search.js';
14
+ import { type QueryChangeReason, type ServerFilter, type ServerQuery } from './serverMode.js';
14
15
  export interface GridOptions {
15
16
  /** Rows as arrays, or as objects bound to columns through `columns[i].data` (edits are written back). */
16
17
  data?: RawCell[][] | DataRow[];
@@ -66,6 +67,52 @@ export interface GridOptions {
66
67
  search?: boolean;
67
68
  /** Shows a bar above the grid with the active cell's address and content; editable. */
68
69
  formulaBar?: boolean;
70
+ /**
71
+ * 'server': the application fetches pages; the grid never sorts or filters itself and reports
72
+ * what it needs through the `afterQueryChange` hook. Formulas are off unless `formulas` is set,
73
+ * and rows cannot be added locally. Default 'client'.
74
+ */
75
+ dataMode?: 'client' | 'server';
76
+ /** Server mode: object property (or function) identifying a row across pages. */
77
+ rowKey?: string | ((row: DataRow | RawCell[]) => unknown);
78
+ /** Server mode: page, sort and filters. Set again to change them from outside. */
79
+ query?: Partial<ServerQuery>;
80
+ /** Server mode: records matching the query, for the page bar. */
81
+ totalCount?: number;
82
+ /** Server mode: old rows stay visible, dimmed, until new ones arrive. */
83
+ loading?: boolean;
84
+ /** Server mode: true shows the default message, a string shows that message. */
85
+ loadError?: boolean | string;
86
+ /** Server mode: page sizes offered (default 25, 50, 100). */
87
+ pageSizes?: number[];
88
+ /** Server mode: page bar below the grid. Default true. */
89
+ pager?: boolean;
90
+ /** Server mode: fills the page bar's slot for application buttons. */
91
+ pagerActions?: (slot: HTMLElement) => void;
92
+ /** Server mode: replaces the content of the empty, loading and error messages. */
93
+ renderStatus?: (el: HTMLElement, state: 'empty' | 'loading' | 'error') => void;
94
+ /** Column widths, order and visibility to start with, from `getColumnLayout`. */
95
+ columnLayout?: ColumnLayout;
96
+ }
97
+ /** Saved column arrangement. Only columns with an id (`columns[i].id` or `data`) are included. */
98
+ export interface ColumnLayout {
99
+ columns: {
100
+ id: string;
101
+ width: number;
102
+ hidden: boolean;
103
+ }[];
104
+ }
105
+ export interface RowEvent {
106
+ /** Visual row. */
107
+ row: number;
108
+ physicalRow: number;
109
+ col: number;
110
+ columnId?: string;
111
+ /** Needs the `rowKey` option. */
112
+ rowKey?: unknown;
113
+ /** The source object (object mode) or raw row. */
114
+ rowData: DataRow | RawCell[] | undefined;
115
+ originalEvent: MouseEvent;
69
116
  }
70
117
  export interface SortKey {
71
118
  col: number;
@@ -78,6 +125,10 @@ export interface CellChange {
78
125
  col: number;
79
126
  /** Bound property in object mode. */
80
127
  prop?: string;
128
+ /** Column id, when the column has one. */
129
+ columnId?: string;
130
+ /** Server mode: `rowKey` value of the row; indexes change between pages, this does not. */
131
+ rowKey?: unknown;
81
132
  oldValue: RawCell;
82
133
  newValue: RawCell;
83
134
  }
@@ -91,6 +142,15 @@ interface HookMap {
91
142
  afterAlter: (action: AlterAction, index: number, amount: number) => void;
92
143
  afterColumnSort: (col: number, order: SortOrder | null) => void;
93
144
  afterSort: (keys: SortKey[]) => void;
145
+ afterRowClick: (e: RowEvent) => void;
146
+ /** Return false to keep the cell editor closed, e.g. when double click opens the record. */
147
+ afterRowDoubleClick: (e: RowEvent) => boolean | void;
148
+ /** Columns were resized, moved, hidden or shown; save the layout here. */
149
+ afterColumnLayout: (layout: ColumnLayout) => void;
150
+ /** Server mode: fetch the rows for `query`. */
151
+ afterQueryChange: (query: ServerQuery, reason: QueryChangeReason) => void;
152
+ /** Server mode: a paste did not fit the loaded rows; `lost` rows were not pasted. */
153
+ afterPasteClipped: (lost: number) => void;
94
154
  afterFilter: (filters: Map<number, ColumnFilter>) => void;
95
155
  afterAutofill: (source: CellRange, target: CellRange) => void;
96
156
  afterValidate: (row: number, col: number, error: string | undefined) => void;
@@ -163,9 +223,27 @@ export declare class CenituGrid {
163
223
  /** Matches shown highlighted while the search box is open (physical cell keys). */
164
224
  private searchHits;
165
225
  private bar?;
226
+ private server?;
166
227
  private frame;
167
228
  private cleanup;
168
229
  constructor(container: HTMLElement, options?: GridOptions);
230
+ /** Current query (server mode). */
231
+ getQuery(): ServerQuery | undefined;
232
+ /** Changes the query and asks for new rows. Anything but a page change returns to page 1. */
233
+ setQuery(change: Partial<ServerQuery>, reason?: QueryChangeReason): void;
234
+ /**
235
+ * Shows a fetched page. Unlike `loadData`, sort marks, filters, column layout and scroll stay,
236
+ * and the selection follows its row by `rowKey` when the row is still there. Undo history ends
237
+ * here: it refers to rows that are gone.
238
+ */
239
+ setPageRows(rows: RawCell[][] | DataRow[], totalCount?: number): void;
240
+ private rowKeyAt;
241
+ /** Stable id of a column: `columns[i].id`, then its bound property. */
242
+ getColumnId(col: number): string | undefined;
243
+ private colOfId;
244
+ private buildServerMode;
245
+ /** Page bar, busy state and the empty/loading/error message. */
246
+ private renderServerState;
169
247
  /** Number of visible rows. */
170
248
  countRows(): number;
171
249
  countSourceRows(): number;
@@ -216,7 +294,7 @@ export declare class CenituGrid {
216
294
  getCellMeta(row: number, col: number): CellMeta;
217
295
  setCellMeta(row: number, col: number, meta: CellMeta): void;
218
296
  /** Updates a subset of options at runtime. */
219
- updateSettings(s: Pick<GridOptions, 'fixedRowsTop' | 'fixedColumnsStart' | 'readOnly' | 'columns' | 'colHeaders' | 'mergeCells' | 'columnSorting' | 'filters' | 'fillHandle' | 'locale' | 'language' | 'contextMenu' | 'conditionalFormats' | 'search'>): void;
297
+ updateSettings(s: Pick<GridOptions, 'fixedRowsTop' | 'fixedColumnsStart' | 'readOnly' | 'columns' | 'colHeaders' | 'mergeCells' | 'columnSorting' | 'filters' | 'fillHandle' | 'locale' | 'language' | 'contextMenu' | 'conditionalFormats' | 'search' | 'query' | 'totalCount' | 'loading' | 'loadError'>): void;
220
298
  selectCell(row: number, col: number, row2?: number, col2?: number): void;
221
299
  private announce;
222
300
  /** Selection as [[row, col, row2, col2]] (visual). */
@@ -234,6 +312,16 @@ export declare class CenituGrid {
234
312
  toggleCellStyle(flag: 'bold' | 'italic' | 'underline' | 'strike'): void;
235
313
  setRowHeight(row: number, height: number): void;
236
314
  getRowHeight(row: number): number;
315
+ /** Current widths, order and visibility; store it and give it back with `setColumnLayout`. */
316
+ getColumnLayout(): ColumnLayout;
317
+ /**
318
+ * Restores a saved layout. Ids that no longer exist are skipped and new columns stay after the
319
+ * saved ones, so a layout survives application updates. Clears undo history when columns moved:
320
+ * its entries refer to the old order.
321
+ */
322
+ setColumnLayout(layout: ColumnLayout): void;
323
+ private applyColumnLayout;
324
+ private layoutChanged;
237
325
  hideColumns(cols: number[]): void;
238
326
  /** Shows the given hidden columns, or all of them. */
239
327
  showColumns(cols?: number[]): void;
@@ -258,6 +346,8 @@ export declare class CenituGrid {
258
346
  /** The primary sort key, or null. */
259
347
  getSortState(): SortKey | null;
260
348
  getSortKeys(): SortKey[];
349
+ /** Server mode: sets or clears the filter of a column; asks for rows from page 1. */
350
+ setServerFilter(columnId: string, filter: ServerFilter | null): void;
261
351
  setFilter(col: number, filter: ColumnFilter | null): void;
262
352
  clearFilters(): void;
263
353
  getFilters(): Map<number, ColumnFilter>;
@@ -294,6 +384,7 @@ export declare class CenituGrid {
294
384
  * is a no-op, out of range, or would split a merged cell.
295
385
  */
296
386
  moveColumns(start: number, count: number, target: number): boolean;
387
+ private relocateColumns;
297
388
  canMoveColumns(start: number, count: number, target: number): boolean;
298
389
  undo(): void;
299
390
  redo(): void;
@@ -418,6 +509,8 @@ export declare class CenituGrid {
418
509
  private onMouseDown;
419
510
  private onContextMenu;
420
511
  private onDoubleClick;
512
+ /** The row a click landed on; null for headers, checkboxes and drags that ended on another cell. */
513
+ private rowEventAt;
421
514
  private onDrag;
422
515
  private onMouseUp;
423
516
  private dropLine?;
@@ -428,6 +521,7 @@ export declare class CenituGrid {
428
521
  private fillToDataEnd;
429
522
  private startResize;
430
523
  private startRowResize;
524
+ private openServerMenu;
431
525
  private openMenu;
432
526
  private toggleCheckboxes;
433
527
  private moveActive;
@@ -60,6 +60,16 @@ export interface Messages {
60
60
  previous: string;
61
61
  close: string;
62
62
  formulaBar: string;
63
+ /** {from}, {to} and {total} are record numbers. */
64
+ pagerRange: string;
65
+ pageSize: string;
66
+ noRows: string;
67
+ loading: string;
68
+ loadError: string;
69
+ dateFrom: string;
70
+ dateTo: string;
71
+ yes: string;
72
+ no: string;
63
73
  }
64
74
  export declare const enUS: Messages;
65
75
  export declare const trTR: Messages;
@@ -0,0 +1,66 @@
1
+ import type { Messages } from './messages.js';
2
+ /** What a column can be filtered by on the server. */
3
+ export type ServerFilterDef = {
4
+ kind: 'text';
5
+ } | {
6
+ kind: 'list';
7
+ options: {
8
+ value: string;
9
+ label?: string;
10
+ }[];
11
+ } | {
12
+ kind: 'date';
13
+ } | {
14
+ kind: 'boolean';
15
+ };
16
+ /** A filter value in the query; date bounds are as the date inputs give them (YYYY-MM-DD). */
17
+ export type ServerFilter = {
18
+ kind: 'text';
19
+ value: string;
20
+ } | {
21
+ kind: 'list';
22
+ values: string[];
23
+ } | {
24
+ kind: 'date';
25
+ from?: string;
26
+ to?: string;
27
+ } | {
28
+ kind: 'boolean';
29
+ value: boolean;
30
+ };
31
+ export interface ServerSort {
32
+ field: string;
33
+ desc: boolean;
34
+ }
35
+ /** Describes the rows the application should fetch. Fields are column ids, never indexes. */
36
+ export interface ServerQuery {
37
+ page: number;
38
+ pageSize: number;
39
+ sort: ServerSort[];
40
+ filters: Record<string, ServerFilter>;
41
+ search?: string;
42
+ }
43
+ export type QueryChangeReason = 'page' | 'pageSize' | 'sort' | 'filter' | 'search';
44
+ export declare const DEFAULT_PAGE_SIZES: number[];
45
+ export declare function initialQuery(q: Partial<ServerQuery> | undefined, pageSizes: number[]): ServerQuery;
46
+ export declare const pageCount: (total: number, pageSize: number) => number;
47
+ /** The controls for one filter; `read` gives the entered filter, or null when it is empty. */
48
+ export declare function serverFilterSection(doc: Document, m: Messages, def: ServerFilterDef, value: ServerFilter | undefined): {
49
+ el: HTMLElement;
50
+ read(): ServerFilter | null;
51
+ };
52
+ /** Page bar: custom actions, record range, page size, previous and next. */
53
+ export declare class Pager {
54
+ private readonly m;
55
+ readonly el: HTMLDivElement;
56
+ /** Slot for application buttons. */
57
+ readonly actions: HTMLDivElement;
58
+ private readonly range;
59
+ private readonly size;
60
+ private readonly position;
61
+ private readonly prev;
62
+ private readonly next;
63
+ constructor(doc: Document, m: Messages, pageSizes: number[], onPage: (delta: number) => void, onSize: (size: number) => void);
64
+ /** `total` undefined keeps the last figures, so the bar does not flicker while a page loads. */
65
+ update(q: ServerQuery, total: number | undefined): void;
66
+ }
@@ -1,8 +1,9 @@
1
- export { CenituGrid, type GridOptions, type CellChange, type ChangeSource, type AlterAction, type SortKey, type HookName, } from './grid/grid.js';
1
+ export { CenituGrid, type GridOptions, type CellChange, type ChangeSource, type AlterAction, type SortKey, type ColumnLayout, type RowEvent, type HookName, } from './grid/grid.js';
2
2
  export type { CellMeta, CellStyle, ConditionalFormat, NumericFormat, CellRenderContext, CellType as GridCellType } from './grid/cellTypes.js';
3
3
  export type { ColumnFilter, ConditionOp, SortOrder } from './grid/filters.js';
4
4
  export type { MergeArea } from './grid/merges.js';
5
5
  export type { SearchOptions } from './grid/search.js';
6
+ export type { ServerQuery, ServerFilter, ServerFilterDef, ServerSort, QueryChangeReason } from './grid/serverMode.js';
6
7
  export type { MenuEntry, MenuItem } from './grid/contextMenu.js';
7
8
  export type { DataRow } from './grid/binding.js';
8
9
  export { enUS, trTR, type Messages } from './grid/messages.js';
@@ -1,6 +1,7 @@
1
1
  import { type CSSProperties } from 'react';
2
2
  import type { ColumnFilter, SortOrder } from '../grid/filters.js';
3
- import { CenituGrid, type CellChange, type ChangeSource, type GridOptions } from '../grid/grid.js';
3
+ import { CenituGrid, type CellChange, type ChangeSource, type ColumnLayout, type GridOptions, type RowEvent } from '../grid/grid.js';
4
+ import type { QueryChangeReason, ServerQuery } from '../grid/serverMode.js';
4
5
  export interface CenituTableProps extends GridOptions {
5
6
  className?: string;
6
7
  style?: CSSProperties;
@@ -8,6 +9,12 @@ export interface CenituTableProps extends GridOptions {
8
9
  onSelection?(row: number, col: number, row2: number, col2: number): void;
9
10
  onSort?(col: number, order: SortOrder | null): void;
10
11
  onFilter?(filters: Map<number, ColumnFilter>): void;
12
+ onRowClick?(event: RowEvent): void;
13
+ /** Return false to keep the cell editor closed. */
14
+ onRowDoubleClick?(event: RowEvent): boolean | void;
15
+ onColumnLayoutChange?(layout: ColumnLayout): void;
16
+ /** Server mode: fetch the rows for `query`. */
17
+ onQueryChange?(query: ServerQuery, reason: QueryChangeReason): void;
11
18
  }
12
19
  /**
13
20
  * React component around CenituGrid. The ref exposes the grid instance for imperative calls.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cenitu-table",
3
- "version": "0.3.0",
3
+ "version": "0.4.0-beta.0",
4
4
  "description": "Spreadsheet-like data grid with a built-in multi-sheet formula engine. Zero runtime dependencies.",
5
5
  "type": "module",
6
6
  "license": "MIT",