cenitu-table 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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-BJIaMXd1.cjs"),T=["fixedRowsTop","fixedColumnsStart","readOnly","columns","colHeaders","mergeCells","columnSorting","filters","fillHandle","locale","language","contextMenu"],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 _=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;
package/dist/react.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx as T } from "react/jsx-runtime";
2
- import { forwardRef as E, useRef as S, useLayoutEffect as O, useImperativeHandle as R, useEffect as C } from "react";
3
- import { C as b, i as j, O as v } from "./index-DG98ykBX.js";
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
4
  const _ = [
5
5
  "fixedRowsTop",
6
6
  "fixedColumnsStart",
@@ -13,11 +13,12 @@ const _ = [
13
13
  "fillHandle",
14
14
  "locale",
15
15
  "language",
16
- "contextMenu"
16
+ "contextMenu",
17
+ "search"
17
18
  ], G = E(function(t, d) {
18
- const a = S(null), e = S(null), o = S(t);
19
+ const a = h(null), e = h(null), o = h(t);
19
20
  o.current = t, O(() => {
20
- const { className: c, style: h, onChange: g, onSelection: N, onSort: F, onFilter: I, ...w } = o.current, f = new b(a.current, w);
21
+ const { className: c, style: S, onChange: g, onSelection: N, onSort: F, onFilter: I, ...w } = o.current, f = new b(a.current, w);
21
22
  return f.addHook("afterChange", (u, l) => {
22
23
  var n, s;
23
24
  return (s = (n = o.current).onChange) == null ? void 0 : s.call(n, u, l);
@@ -34,7 +35,7 @@ const _ = [
34
35
  f.destroy(), e.current = null;
35
36
  };
36
37
  }, []), R(d, () => e.current, []);
37
- const m = S(!1);
38
+ const m = h(!1);
38
39
  C(() => {
39
40
  if (!m.current) return;
40
41
  const c = e.current;
@@ -42,10 +43,10 @@ const _ = [
42
43
  }, [t.data]);
43
44
  const i = _.map((c) => t[c]);
44
45
  return C(() => {
45
- var h;
46
+ var S;
46
47
  if (!m.current) return;
47
48
  const c = {};
48
- _.forEach((g) => t[g] !== void 0 && (c[g] = t[g])), (h = e.current) == null || h.updateSettings(c);
49
+ _.forEach((g) => t[g] !== void 0 && (c[g] = t[g])), (S = e.current) == null || S.updateSettings(c);
49
50
  }, i), C(() => {
50
51
  m.current = !0;
51
52
  }, []), /* @__PURE__ */ T("div", { ref: a, className: t.className, style: t.style });
@@ -4,3 +4,5 @@ export { toXLSX, toXLSXCompressed, xlsxParts, sanitizeSheetNames, pxToColWidth,
4
4
  export { numberFormatFromMeta, isDateFormat, formatDateCode, dateCodeFromPattern } from './numberFormat.js';
5
5
  export { downloadBlob, CSV_MIME } from './download.js';
6
6
  export { crc32, zipStore, zipDeflate, type ZipEntry } from './zip.js';
7
+ export { readXLSX, workbookEngine, type ImportOptions, type ImportSheet, type ImportWorkbook } from './xlsxRead.js';
8
+ export { ZipReader, type UnzipOptions } from './unzip.js';
@@ -6,6 +6,7 @@ export interface ExportCellFormat {
6
6
  bold?: boolean;
7
7
  italic?: boolean;
8
8
  underline?: boolean;
9
+ strike?: boolean;
9
10
  /** '#rrggbb' (or '#rgb'). */
10
11
  color?: string;
11
12
  background?: string;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Minimal ZIP reader (PKWARE APPNOTE 6.3), enough for spreadsheet packages: finds entries through
3
+ * the central directory and inflates them with the platform DecompressionStream. No ZIP64 and no
4
+ * encryption. Sizes are enforced while inflating, so a crafted archive cannot exhaust memory.
5
+ */
6
+ export interface UnzipOptions {
7
+ /** Upper limit for the total uncompressed size in bytes (default 1 GiB). */
8
+ maxSize?: number;
9
+ }
10
+ /** Lists a package and gives access to its entries by path. */
11
+ export declare class ZipReader {
12
+ private readonly bytes;
13
+ private readonly entries;
14
+ private remaining;
15
+ constructor(bytes: Uint8Array, opts?: UnzipOptions);
16
+ has(path: string): boolean;
17
+ get paths(): string[];
18
+ /** Uncompressed content of an entry; undefined when the package has no such entry. */
19
+ read(path: string): Promise<Uint8Array | undefined>;
20
+ readText(path: string): Promise<string | undefined>;
21
+ }
@@ -0,0 +1,35 @@
1
+ import { FormulaEngine } from '../formula/engine.js';
2
+ import type { ExportCellFormat, ExportSheet } from './types.js';
3
+ import { type UnzipOptions } from './unzip.js';
4
+ export interface ImportOptions extends UnzipOptions {
5
+ /** 'keep' (default) imports formulas; 'values' replaces them with the results stored in the file. */
6
+ formulas?: 'keep' | 'values';
7
+ }
8
+ /** One worksheet of a file; the same shape the exporters take, plus what only applies to imports. */
9
+ export interface ImportSheet extends ExportSheet {
10
+ hiddenColumns: number[];
11
+ /** Sheet is hidden in the file. */
12
+ hidden: boolean;
13
+ format: (row: number, col: number) => ExportCellFormat | undefined;
14
+ }
15
+ export interface ImportWorkbook {
16
+ sheets: ImportSheet[];
17
+ /** Workbook-level defined names: name -> expression (`=Sheet1!$A$1`). */
18
+ names: {
19
+ name: string;
20
+ expression: string;
21
+ }[];
22
+ }
23
+ type Binary = ArrayBuffer | Uint8Array | Blob;
24
+ /**
25
+ * Reads an .xlsx file: values, formulas, number formats, fonts, fills, alignment, column widths,
26
+ * row heights, frozen panes, merges and defined names. Charts, images, comments, conditional
27
+ * formats and data validation are not read.
28
+ */
29
+ export declare function readXLSX(file: Binary, opts?: ImportOptions): Promise<ImportWorkbook>;
30
+ /**
31
+ * Engine holding all sheets of a workbook (still empty) and its defined names. Create one grid per
32
+ * sheet on it and call `loadSheet`, so cross-sheet formulas find each other in any order.
33
+ */
34
+ export declare function workbookEngine(workbook: ImportWorkbook): FormulaEngine;
35
+ export {};
@@ -0,0 +1,23 @@
1
+ import type { CellMeta, NumericFormat } from '../grid/cellTypes.js';
2
+ import type { ExportCellFormat } from './types.js';
3
+ export declare function parseThemeColors(xml: string | undefined): string[];
4
+ /** Resolved cell formats of a workbook: cell `s` attribute -> grid-independent format. */
5
+ export declare class XlsxStyles {
6
+ private readonly theme;
7
+ private readonly codes;
8
+ private readonly fonts;
9
+ private readonly fills;
10
+ private readonly xfs;
11
+ private readonly cache;
12
+ constructor(xml: string | undefined, theme: string[]);
13
+ private parse;
14
+ numberFormat(style: number): string | undefined;
15
+ isDate(style: number): boolean;
16
+ format(style: number): ExportCellFormat | undefined;
17
+ }
18
+ /** Date pattern for a format code, when every token has a grid equivalent. */
19
+ export declare function patternFromDateCode(code: string): string | undefined;
20
+ /** Number display settings for a simple format code; complex codes keep the default display. */
21
+ export declare function numericFormatFromCode(code: string): NumericFormat | undefined;
22
+ /** Cell settings that make the grid display a format like the file does. */
23
+ export declare function metaFromFormat(f: ExportCellFormat): CellMeta;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Streaming XML scanner for package parts. Namespace prefixes are dropped from element and
3
+ * attribute names (`x:row` -> `row`), which is what spreadsheet parts need. DOCTYPE declarations
4
+ * are skipped and only the predefined and numeric entities are decoded, so a crafted file cannot
5
+ * expand entities or reach external resources.
6
+ */
7
+ export type XmlAttrs = Record<string, string>;
8
+ export interface XmlHandler {
9
+ open?(name: string, attrs: XmlAttrs, selfClosing: boolean): void;
10
+ close?(name: string): void;
11
+ text?(text: string): void;
12
+ }
13
+ export declare function decodeEntities(s: string): string;
14
+ /** Reports elements and text in document order; throws on an unterminated construct. */
15
+ export declare function scanXml(xml: string, h: XmlHandler): void;
@@ -51,7 +51,9 @@ export interface CellMeta {
51
51
  width?: number;
52
52
  readOnly?: boolean;
53
53
  numericFormat?: NumericFormat;
54
- /** Tokens: DD, MM, YYYY, YY (default "DD.MM.YYYY" for tr, "YYYY-MM-DD" otherwise). */
54
+ /** Spreadsheet format code written to XLSX instead of one derived from these settings (set on import). */
55
+ formatCode?: string;
56
+ /** Tokens: DD, MM, YYYY, YY, HH, mm, ss (default "DD.MM.YYYY" for tr, "YYYY-MM-DD" otherwise). */
55
57
  dateFormat?: string;
56
58
  /** Allowed values of a dropdown cell. */
57
59
  source?: string[];
@@ -0,0 +1,13 @@
1
+ import type { CellStyle } from './cellTypes.js';
2
+ /**
3
+ * The "text/html" clipboard flavour. Values always travel as tab-separated text; the HTML table
4
+ * only adds styling, which is what spreadsheet apps read from each other.
5
+ */
6
+ export declare function styleToCss(s: CellStyle): string;
7
+ export declare function tableHtml(texts: string[][], styles: (CellStyle | undefined)[][]): string;
8
+ export interface HtmlTable {
9
+ texts: string[][];
10
+ styles: (CellStyle | undefined)[][];
11
+ }
12
+ /** First table of a clipboard document, with spans expanded; null when there is none. */
13
+ export declare function parseHtmlTable(html: string, view: Window & typeof globalThis): HtmlTable | null;
@@ -3,13 +3,16 @@ import type { Messages } from './messages.js';
3
3
  export interface ColumnMenuOptions {
4
4
  anchor: HTMLElement;
5
5
  messages: Messages;
6
+ locale?: string;
6
7
  sort: SortOrder | null;
8
+ /** Another column is sorted, so this one can be added as a further key. */
9
+ sortedElsewhere: boolean;
7
10
  filter: ColumnFilter | undefined;
8
11
  /** Distinct display texts of the column ("" = blanks), already sorted. */
9
12
  values: string[];
10
13
  sortable: boolean;
11
14
  filterable: boolean;
12
- onSort(order: SortOrder | null): void;
15
+ onSort(order: SortOrder | null, add?: boolean): void;
13
16
  onFilter(filter: ColumnFilter | null): void;
14
17
  onClose(): void;
15
18
  }
@@ -9,6 +9,7 @@ export interface ColumnFilter {
9
9
  values?: string[];
10
10
  }
11
11
  export type SortOrder = 'asc' | 'desc';
12
- export declare function matchesFilter(value: CellValue, text: string, filter: ColumnFilter): boolean;
12
+ /** Text comparisons follow `locale`, so Turkish I/ı and İ/i pair up correctly. */
13
+ export declare function matchesFilter(value: CellValue, text: string, filter: ColumnFilter, locale?: string): boolean;
13
14
  /** Spreadsheet sort order: numbers, text, booleans, errors; blanks always last. */
14
- export declare function compareForSort(a: CellValue, b: CellValue, order: SortOrder): number;
15
+ export declare function compareForSort(a: CellValue, b: CellValue, order: SortOrder, locale?: string): number;
@@ -1,14 +1,16 @@
1
- import { type CellRange } from '../formula/address.js';
1
+ import { type CellAddress, type CellRange } from '../formula/address.js';
2
2
  import { FormulaEngine } from '../formula/engine.js';
3
3
  import { type CellValue, type RawCell } from '../formula/values.js';
4
4
  import { type DataRow } from './binding.js';
5
5
  import { type CellMeta, type CellStyle, type ConditionalFormat } from './cellTypes.js';
6
6
  import { type CsvOptions } from '../export/csv.js';
7
+ import { type ImportOptions, type ImportSheet } from '../export/xlsxRead.js';
7
8
  import type { ExportSheet } from '../export/types.js';
8
9
  import { type MenuEntry } from './contextMenu.js';
9
10
  import { type ColumnFilter, type SortOrder } from './filters.js';
10
11
  import { type MergeArea } from './merges.js';
11
12
  import { type Messages } from './messages.js';
13
+ import { type SearchOptions } from './search.js';
12
14
  export interface GridOptions {
13
15
  /** Rows as arrays, or as objects bound to columns through `columns[i].data` (edits are written back). */
14
16
  data?: RawCell[][] | DataRow[];
@@ -60,6 +62,14 @@ export interface GridOptions {
60
62
  conditionalFormats?: ConditionalFormat[];
61
63
  /** Columns hidden initially. */
62
64
  hiddenColumns?: number[];
65
+ /** Ctrl+F / Ctrl+H open a find and replace box (they stay the browser's shortcuts otherwise). */
66
+ search?: boolean;
67
+ /** Shows a bar above the grid with the active cell's address and content; editable. */
68
+ formulaBar?: boolean;
69
+ }
70
+ export interface SortKey {
71
+ col: number;
72
+ order: SortOrder;
63
73
  }
64
74
  /** Row indexes: `row` is visual (as displayed, -1 if hidden), `physicalRow` is the data row. */
65
75
  export interface CellChange {
@@ -71,7 +81,7 @@ export interface CellChange {
71
81
  oldValue: RawCell;
72
82
  newValue: RawCell;
73
83
  }
74
- export type ChangeSource = 'edit' | 'paste' | 'cut' | 'clear' | 'autofill' | 'undo' | 'redo' | 'api';
84
+ export type ChangeSource = 'edit' | 'paste' | 'cut' | 'clear' | 'autofill' | 'replace' | 'undo' | 'redo' | 'api';
75
85
  export type AlterAction = 'insert_row_above' | 'insert_row_below' | 'remove_row' | 'insert_col_start' | 'insert_col_end' | 'remove_col';
76
86
  interface HookMap {
77
87
  /** Return false to cancel; changes may be modified in place. */
@@ -80,6 +90,7 @@ interface HookMap {
80
90
  afterSelection: (row: number, col: number, row2: number, col2: number) => void;
81
91
  afterAlter: (action: AlterAction, index: number, amount: number) => void;
82
92
  afterColumnSort: (col: number, order: SortOrder | null) => void;
93
+ afterSort: (keys: SortKey[]) => void;
83
94
  afterFilter: (filters: Map<number, ColumnFilter>) => void;
84
95
  afterAutofill: (source: CellRange, target: CellRange) => void;
85
96
  afterValidate: (row: number, col: number, error: string | undefined) => void;
@@ -116,7 +127,7 @@ export declare class CenituGrid {
116
127
  private cellMeta;
117
128
  private labels;
118
129
  private filters;
119
- private sortState;
130
+ private sortKeys;
120
131
  private invalid;
121
132
  /** Custom row heights by physical row. */
122
133
  private rowHeights;
@@ -146,6 +157,12 @@ export declare class CenituGrid {
146
157
  private undoStack;
147
158
  private redoStack;
148
159
  private clipboard;
160
+ private searchPanel?;
161
+ private searchQuery;
162
+ private searchOptions;
163
+ /** Matches shown highlighted while the search box is open (physical cell keys). */
164
+ private searchHits;
165
+ private bar?;
149
166
  private frame;
150
167
  private cleanup;
151
168
  constructor(container: HTMLElement, options?: GridOptions);
@@ -183,11 +200,23 @@ export declare class CenituGrid {
183
200
  setDataAtCell(row: number, col: number, value: RawCell, source?: ChangeSource): void;
184
201
  setDataAtCell(changes: [number, number, RawCell][], source?: ChangeSource): void;
185
202
  loadData(data: RawCell[][] | DataRow[]): void;
203
+ /**
204
+ * Replaces the grid's content with an imported sheet, including its formats, sizes, frozen
205
+ * panes and merges. Hidden rows are shown: the grid only hides rows through filters.
206
+ */
207
+ loadSheet(sheet: ImportSheet): void;
208
+ /**
209
+ * Reads one sheet of an .xlsx file into this grid (default the first) and adds the workbook's
210
+ * defined names to the engine. References to other sheets need all of them: see `workbookEngine`.
211
+ */
212
+ importXLSX(file: ArrayBuffer | Uint8Array | Blob, opts?: ImportOptions & {
213
+ sheet?: number | string;
214
+ }): Promise<ImportSheet>;
186
215
  /** Effective settings of a cell at a visual position. */
187
216
  getCellMeta(row: number, col: number): CellMeta;
188
217
  setCellMeta(row: number, col: number, meta: CellMeta): void;
189
218
  /** Updates a subset of options at runtime. */
190
- updateSettings(s: Pick<GridOptions, 'fixedRowsTop' | 'fixedColumnsStart' | 'readOnly' | 'columns' | 'colHeaders' | 'mergeCells' | 'columnSorting' | 'filters' | 'fillHandle' | 'locale' | 'language' | 'contextMenu' | 'conditionalFormats'>): void;
219
+ updateSettings(s: Pick<GridOptions, 'fixedRowsTop' | 'fixedColumnsStart' | 'readOnly' | 'columns' | 'colHeaders' | 'mergeCells' | 'columnSorting' | 'filters' | 'fillHandle' | 'locale' | 'language' | 'contextMenu' | 'conditionalFormats' | 'search'>): void;
191
220
  selectCell(row: number, col: number, row2?: number, col2?: number): void;
192
221
  private announce;
193
222
  /** Selection as [[row, col, row2, col2]] (visual). */
@@ -223,15 +252,36 @@ export declare class CenituGrid {
223
252
  downloadXLSX(fileName?: string, others?: CenituGrid[]): Promise<void>;
224
253
  /** Sorts by one column (visual order only; data and formulas are untouched). null restores data order. */
225
254
  sort(col: number, order: SortOrder | null): void;
226
- getSortState(): {
227
- col: number;
228
- order: SortOrder;
229
- } | null;
255
+ /** Sorts by several columns: the first key decides, later keys break ties. Empty restores data order. */
256
+ sortBy(keys: SortKey[]): void;
257
+ private applySort;
258
+ /** The primary sort key, or null. */
259
+ getSortState(): SortKey | null;
260
+ getSortKeys(): SortKey[];
230
261
  setFilter(col: number, filter: ColumnFilter | null): void;
231
262
  clearFilters(): void;
232
263
  getFilters(): Map<number, ColumnFilter>;
233
264
  /** Re-evaluates the filters against current values (they are not re-applied automatically on edit). */
234
265
  applyFilters(): void;
266
+ /** Visual positions of the cells matching `query`, in reading order; hidden columns are skipped. */
267
+ findCells(query: string, opts?: SearchOptions): CellAddress[];
268
+ /** Selects the match after (or before) the active cell, wrapping around; null when there is none. */
269
+ findNext(query: string, opts?: SearchOptions, backward?: boolean): CellAddress | null;
270
+ /**
271
+ * Replaces the match in the active cell (when it is one) and moves to the next. Formula cells
272
+ * change only with `lookIn: 'formulas'`: replacing in their result would delete the formula.
273
+ */
274
+ replaceNext(query: string, replacement: string, opts?: SearchOptions): CellAddress | null;
275
+ /** Replaces every match in one undo step; returns the number of cells changed. */
276
+ replaceAll(query: string, replacement: string, opts?: SearchOptions): number;
277
+ /** Opens the find box (`search` option), with the replace row when `replace` is true. */
278
+ openSearch(replace?: boolean): void;
279
+ closeSearch(): void;
280
+ private searchText;
281
+ private nextHit;
282
+ private highlightHits;
283
+ private stepSearch;
284
+ private replaceIn;
235
285
  /** Merges an area (visual coordinates). Refused while sorting/filtering is active or across frozen edges. */
236
286
  mergeCells(row: number, col: number, rowspan: number, colspan: number): boolean;
237
287
  unmergeCells(row: number, col: number): boolean;
@@ -263,9 +313,11 @@ export declare class CenituGrid {
263
313
  copy(cut?: boolean): string | undefined;
264
314
  /**
265
315
  * Pastes text at the selection. Text from this grid's own copy keeps formulas, with relative
266
- * references moved to the new location. A single value fills the whole selection.
316
+ * references moved to the new location and its cell styles. `html` is the clipboard's HTML
317
+ * flavour from another app; only the styles of its table are used. A single value fills the
318
+ * whole selection.
267
319
  */
268
- paste(text: string): void;
320
+ paste(text: string, html?: string): void;
269
321
  /** Fills `target` (adjacent to `source`, visual coordinates) by extending the source series. */
270
322
  autofill(source: CellRange, target: CellRange): void;
271
323
  render(): void;
@@ -281,6 +333,7 @@ export declare class CenituGrid {
281
333
  private applyChanges;
282
334
  private clearRange;
283
335
  private replay;
336
+ private replayEntry;
284
337
  private applyMeta;
285
338
  private applyEngineOp;
286
339
  private undoEngineOp;
@@ -344,6 +397,11 @@ export declare class CenituGrid {
344
397
  openEditor(initial?: string): void;
345
398
  closeEditor(commit: boolean): void;
346
399
  private focusInput;
400
+ private barFocused;
401
+ private buildFormulaBar;
402
+ private onBarKey;
403
+ /** Shows the active cell in the bar; while editing, the bar mirrors the editor. */
404
+ private syncFormulaBar;
347
405
  /** Keeps the textarea over the active cell (in its pane), so IME windows appear in the right place. */
348
406
  private placeInput;
349
407
  private autoSizeInput;
@@ -2,6 +2,8 @@ export interface Messages {
2
2
  sortAsc: string;
3
3
  sortDesc: string;
4
4
  sortClear: string;
5
+ sortThenAsc: string;
6
+ sortThenDesc: string;
5
7
  filterByCondition: string;
6
8
  filterByValue: string;
7
9
  search: string;
@@ -42,6 +44,22 @@ export interface Messages {
42
44
  hideColumns: string;
43
45
  showColumns: string;
44
46
  cellAnnouncement: string;
47
+ find: string;
48
+ replace: string;
49
+ replaceAll: string;
50
+ replaceWith: string;
51
+ matchCase: string;
52
+ wholeCell: string;
53
+ lookInFormulas: string;
54
+ noMatches: string;
55
+ /** {total} is the number of matches. */
56
+ matchCount: string;
57
+ /** {count} is the number of cells changed. */
58
+ replacedCount: string;
59
+ next: string;
60
+ previous: string;
61
+ close: string;
62
+ formulaBar: string;
45
63
  }
46
64
  export declare const enUS: Messages;
47
65
  export declare const trTR: Messages;
@@ -0,0 +1,41 @@
1
+ import type { Messages } from './messages.js';
2
+ export interface SearchOptions {
3
+ matchCase?: boolean;
4
+ /** Match only when the whole cell equals the query. */
5
+ wholeCell?: boolean;
6
+ /** 'values' (default) searches what cells display; 'formulas' searches what was entered. */
7
+ lookIn?: 'values' | 'formulas';
8
+ }
9
+ type Span = [number, number];
10
+ /** Occurrences of `query` in `text` as [start, end) in `text`'s own indexes. */
11
+ export declare function matchSpans(text: string, query: string, o?: SearchOptions, locale?: string): Span[];
12
+ /** `text` with every occurrence of `query` replaced. */
13
+ export declare function replaceSpans(text: string, spans: Span[], replacement: string): string;
14
+ export interface SearchPanelOptions {
15
+ root: HTMLElement;
16
+ messages: Messages;
17
+ query: string;
18
+ options: SearchOptions;
19
+ /** Shows the replace row; false for read-only grids. */
20
+ replace: boolean;
21
+ /** Re-runs the search; returns the number of matches. */
22
+ onSearch(query: string, options: SearchOptions): number;
23
+ /** Moves to the next match; returns its 1-based position and the total. */
24
+ onNext(backward: boolean): {
25
+ index: number;
26
+ total: number;
27
+ };
28
+ onReplace(replacement: string): {
29
+ index: number;
30
+ total: number;
31
+ };
32
+ onReplaceAll(replacement: string): number;
33
+ onClose(): void;
34
+ }
35
+ export interface SearchPanel {
36
+ close(): void;
37
+ focus(replace: boolean): void;
38
+ }
39
+ /** Floating find/replace box over the grid's top right corner. */
40
+ export declare function openSearchPanel(o: SearchPanelOptions): SearchPanel;
41
+ export {};
@@ -1,7 +1,8 @@
1
- export { CenituGrid, type GridOptions, type CellChange, type ChangeSource, type AlterAction, type HookName, } from './grid/grid.js';
1
+ export { CenituGrid, type GridOptions, type CellChange, type ChangeSource, type AlterAction, type SortKey, 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
+ export type { SearchOptions } from './grid/search.js';
5
6
  export type { MenuEntry, MenuItem } from './grid/contextMenu.js';
6
7
  export type { DataRow } from './grid/binding.js';
7
8
  export { enUS, trTR, type Messages } from './grid/messages.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cenitu-table",
3
- "version": "0.2.0",
3
+ "version": "0.3.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",