# FreeGrid — full reference for AI assistants FreeGrid is a fast JavaScript data grid, free for personal use: virtual scrolling, grouping, pivot tables, charts, Excel import and export, 14 languages. Vanilla JS, React, Vue, Svelte and Angular. ## Frequently asked questions ### What is FreeGrid? FreeGrid is a JavaScript data grid for web applications. It shows and edits large tables (100,000 rows and more) with sorting, filtering, pinned columns, grouping, pivot tables, charts and Excel import and export. It works with plain JavaScript, React, Vue, Svelte and Angular, and has no dependencies. ### Is FreeGrid free? FreeGrid is free for personal, educational and non-commercial use, with every feature included. Companies need a license for each domain: $149 per domain per year, or $299 per domain once for a perpetual license, which includes updates within the major version bought (for example every 1.x release). Each subdomain is licensed separately. ### How do I install FreeGrid? Run npm i @freegridjs/core and import createGrid. React, Vue, Svelte and Angular components are in @freegridjs/react, @freegridjs/vue, @freegridjs/svelte and @freegridjs/angular. Without a build step, load freegrid.min.js and freegrid.css from the npm CDN at unpkg.com/@freegridjs/core. ### Can FreeGrid copy and paste with Excel? Yes. Select a range of cells and copy it into Excel, or paste cells from Excel into editable columns. The grid also exports real .xlsx and CSV files and imports them back. ### How many rows can FreeGrid handle? Row and column virtualization keeps 100,000 rows smooth in the browser. For larger data, the server-side row model loads rows in blocks as people scroll. ### Which languages does FreeGrid support? English, Korean, Simplified and Traditional Chinese, Japanese, Vietnamese, Thai, Indonesian, Spanish, Portuguese (Brazil), French, German, Italian and Russian, with locale-aware number and date formats. Korean dates default to YYYY-MM-DD. ### Does it work with Spring Boot or eGovFrame? Yes. The grid runs in the browser, and four plain Java 8 classes (a free download in the docs) turn its requests into safe SQL for JdbcTemplate or MyBatis on Spring Boot and eGovFrame 3.x to 5.x: paging, sorting, filters, grouping and saving changes. Spring Security CSRF tokens are handled by csrfHeaders. ### What counts as company use? Any use by or for a company, organization or public body: internal systems, products you sell, and sites you build for clients. A freelancer building a site for a company client needs a license for that client domain. ### How do we pay? By card at checkout on this page. Paddle.com, our reseller and Merchant of Record, processes the payment, adds VAT or sales tax where it applies and issues the invoice. Your license key is shown as soon as the payment is confirmed. ### Can we cancel or get a refund? Annual licenses can be cancelled anytime; the license runs to the end of the paid year and is not charged again. New purchases and annual renewals can be refunded in full within 14 days. See the Refund Policy for details. ### What if we have several domains? Each domain needs its own license, and so does each subdomain: erp.example.com and admin.example.com are two more licenses besides example.com. www.example.com is included with example.com. ### What does the grid send to freegrid.app? On public domains, once a day: the domain, the page address without query string, the version and whether a key is set. Never the data in the grid or anything about your users. Local and private network addresses send nothing. ## 자주 묻는 질문 (Korean) ### FreeGrid는 무엇인가요? FreeGrid는 웹 애플리케이션용 자바스크립트 데이터 그리드입니다. 10만 건이 넘는 표를 빠르게 보여주고 편집하며, 정렬, 필터, 컬럼 고정, 그룹핑, 피벗 테이블, 차트, 엑셀 내보내기·가져오기를 지원합니다. 순수 자바스크립트, React, Vue, Svelte, Angular에서 쓸 수 있고 외부 라이브러리가 필요 없습니다. ### FreeGrid는 무료인가요? 개인, 교육, 비상업적 용도는 모든 기능을 무료로 쓸 수 있습니다. 회사에서 쓰려면 도메인마다 라이선스가 필요하며, 도메인 1개당 연 200,000원, 영구 구매는 도메인당 400,000원이며 구매한 메이저 버전의 업데이트(예: 1.x 전체)까지 포함합니다. 서브도메인은 별도로 요금이 책정됩니다. ### 어떻게 설치하나요? npm i @freegridjs/core로 설치한 뒤 createGrid를 불러오면 됩니다. React는 @freegridjs/react, Vue는 @freegridjs/vue, Svelte는 @freegridjs/svelte, Angular는 @freegridjs/angular를 쓰세요. 빌드 도구 없이 쓰려면 unpkg.com/@freegridjs/core의 freegrid.min.js와 freegrid.css를 불러오면 됩니다. ### 엑셀과 복사·붙여넣기가 되나요? 네. 셀 범위를 드래그해 엑셀로 복사하고, 엑셀에서 복사한 내용을 편집 가능한 컬럼에 그대로 붙여넣을 수 있습니다. 진짜 .xlsx와 CSV 파일로 내보내고 다시 가져오는 것도 됩니다. ### 몇 건까지 다룰 수 있나요? 보이는 행과 열만 그리는 가상 스크롤 덕분에 브라우저에서 10만 건도 부드럽게 움직입니다. 더 큰 데이터는 서버 행 모델로 스크롤에 맞춰 필요한 만큼만 불러옵니다. ### 어떤 언어를 지원하나요? 영어, 한국어, 중국어(간체·번체), 일본어, 베트남어, 태국어, 인도네시아어, 스페인어, 포르투갈어, 프랑스어, 독일어, 이탈리아어, 러시아어 14개 언어를 지원하고, 숫자와 날짜도 언어에 맞게 표시합니다. 한국어 날짜는 기본으로 YYYY-MM-DD 형식입니다. ### Spring Boot나 전자정부 표준프레임워크에서 쓸 수 있나요? 네. 그리드는 브라우저에서 동작하고, 서버 쪽은 Java 8 클래스 네 개(사용법에서 무료로 내려받기)가 그리드 요청을 안전한 SQL로 바꿔 줍니다. Spring Boot와 전자정부 표준프레임워크 3.x~5.x, JdbcTemplate·MyBatis에서 페이지·정렬·필터·그룹·저장을 처리하고, Spring Security CSRF 토큰은 csrfHeaders가 붙여 줍니다. ## Pricing - Personal: Free. Personal, educational and non-commercial projects; Every feature, no limits; Community forum. - Company: $149 per domain, per year. One domain (www included); subdomains are licensed separately; Internal tools, products and customer sites; License key that turns off the console notice; Pay by card; renews each year, cancel anytime; 30-day evaluation without a license. - Company · perpetual: $299 per domain, one-time. One payment, no expiry; updates within the major version bought (e.g. all of 1.x); One domain (www included); subdomains are licensed separately; Internal tools, products and customer sites; Pay by card; invoice included. - Buy: https://freegrid.app/pricing/ (card checkout through Paddle; questions: help@2nd-korea.com). Terms: https://freegrid.app/terms/ · Refunds: https://freegrid.app/refund/ ## Quick start ```js import { createGrid } from '@freegridjs/core'; import '@freegridjs/core/styles.css'; const grid = createGrid(document.getElementById('grid'), { columns: [{ field: 'name', header: 'Name' }, { field: 'total', header: 'Total', type: 'number', format: '#,##0' }], data: rows, }); ``` The container element needs a height. ## Docs pages (each with a live demo) - Getting started: FreeGrid is a framework-free TypeScript grid with thin React, Vue, Svelte and Angular wrappers. Install the core package, or the wrapper for your… (https://freegrid.app/docs/getting-started/) - Writing code with AI: When you ask ChatGPT, Claude, Gemini, Cursor or another AI tool for FreeGrid code, have it read https://freegrid.app/llms-full.txt first: every option,… (https://freegrid.app/docs/ai/) - Grid options: Every option of createGrid: data, row ids, selection, editing, pagination, summary rows, live updates, conditional formats, the AI assistant, themes, fonts and language. (https://freegrid.app/docs/options/) - Data and row ids: Rows are ordinary objects. Without getRowId rows are identified by position, so replacing the data clears the selection. With it, the selection survives… (https://freegrid.app/docs/data/) - Column definitions: Columns are plain objects. The column id is id if you set one, otherwise field; sorting, filters and column state all refer to columns by that id. (https://freegrid.app/docs/columns/) - Pinning and column groups: Pinned columns stay visible while the middle section scrolls horizontally. Users can pin and unpin from the column menu. Columns with children become… (https://freegrid.app/docs/pinning-groups/) - Column customization: Users can drag headers to reorder, drag header edges to resize, and use the column menu to autosize, hide, pin or reset columns. Choose columns opens a… (https://freegrid.app/docs/customization/) - Spans, wrapping and layout: colSpan and rowSpan decide per cell how many columns or rows it covers: a note row across several columns, or a region name spanning the rows your data… (https://freegrid.app/docs/layout/) - Formatting and renderers: Numbers and dates are formatted with the browser's Intl APIs in the grid language, so 1234.5 becomes 1,234.5 in English and 1.234,5 in German. Use… (https://freegrid.app/docs/rendering/) - Conditional formatting: Colour cells by value or formula, highlight the top and bottom values, averages and duplicates, and draw data bars, colour scales and icon sets that carry into Excel, print and PDF. (https://freegrid.app/docs/conditional-formatting/) - Sorting: Click a header to cycle ascending, descending and unsorted. Shift-click (or Ctrl/⌘-click) adds the column as another sort key; a small number shows its… (https://freegrid.app/docs/sorting/) - Filtering: Each column's ⋮ menu holds a filter that matches its type: text (contains, equals, starts/ends with, blank…), number (=, ≠, <, ≤, >, ≥, between, blank),… (https://freegrid.app/docs/filtering/) - Grouping, trees and details: Pass groupBy with one or more column ids and rows are gathered under group rows that show the value and the number of rows. Give a column an aggFunc such… (https://freegrid.app/docs/grouping/) - Summary rows: Summary rows show totals, averages and other aggregates. Put them at the top or bottom; keep them pinned outside the scroll area, or let them scroll with… (https://freegrid.app/docs/summary/) - Pivot tables and charts: setPivot() turns the rows into a summary table. Fields in rows become the rows; with more than one, the earlier ones become groups with their own… (https://freegrid.app/docs/pivot-charts/) - Editing: Mark a column editable: true and users can edit its cells: double-click, press Enter or F2, or just start typing. Enter saves and moves down, Tab saves… (https://freegrid.app/docs/editing/) - Formulas: A spreadsheet-style formula engine: computed columns, about 60 functions, whole-column totals with COL(), running totals with PREV(), formulas typed into cells and your own functions. (https://freegrid.app/docs/formulas/) - Spreadsheet tools: Set merge: true on a column and consecutive rows with the same value become one tall cell, which is handy for reports grouped by region or team. Sort by… (https://freegrid.app/docs/spreadsheet/) - Copy, paste and right-click: Select cells like in Excel: drag across them, Shift-click, use Shift with the arrow keys, or press Ctrl+A. Ctrl+C copies the range in a format Excel and… (https://freegrid.app/docs/clipboard/) - Row selection: With rowSelection the grid adds a checkbox column. Click selects a row, Ctrl/⌘-click toggles, Shift-click selects a range, the header checkbox selects… (https://freegrid.app/docs/selection/) - Keyboard: Click a cell or tab into the grid, then: (https://freegrid.app/docs/keyboard/) - Real-time updates: Update rows by id from WebSockets, server-sent events or polling; only changed rows repaint, numbers flash up or down, and connectLive reconnects and pauses. (https://freegrid.app/docs/live-data/) - AI assistant: Let people ask the grid in plain words. Your server asks a model and answers with checked actions: sorting, filters, groups, formats, computed columns and charts, each with an undo. (https://freegrid.app/docs/assistant/) - Pages and server data: With pagination the grid shows one page at a time and puts a pager under the rows. People can jump to the first, previous, next or last page, and pick the… (https://freegrid.app/docs/server-data/) - Server examples (Java, Node, Python, PHP, C#, Go): Server helpers in six languages turn the grid’s requests into safe SQL: paging, sorting, filters, quick search, the advanced filter, server-side grouping and saving changes. (https://freegrid.app/docs/server/) - Export and import: exportXlsx() saves the rows as a real Excel file. Numbers stay numbers with the column's format, dates stay dates, column groups become merged header… (https://freegrid.app/docs/export/) - Print and PDF: Print or save a PDF with column headers and chosen rows repeated on every page, group rows, totals, conditional formats, a title, a footer and page numbers. (https://freegrid.app/docs/print/) - Themes and fonts: Built-in themes: light, dark, and four simple Korean-style themes — board (bulletin board: thick top rule, horizontal lines), erp (every cell bordered,… (https://freegrid.app/docs/themes/) - Languages: English is built in. Korean, Simplified and Traditional Chinese, Japanese, Vietnamese, Thai, Indonesian, Spanish, Portuguese (Brazil), French, German,… (https://freegrid.app/docs/i18n/) - Accessibility and Shadow DOM: The grid follows the WAI-ARIA grid pattern: role="grid" on the rows area (treegrid when grouped or a tree), header rows, row and column indexes on cells,… (https://freegrid.app/docs/accessibility/) - React: @freegridjs/react exports . Every grid option is a prop and every event is an on… prop. The ref is the grid API. Changing data, columns, theme,… (https://freegrid.app/docs/react/) - Vue: @freegridjs/vue exports for Vue 3.3+. Events are kebab-case (@cell-click, @selection-change…), and the component ref exposes getApi(). (https://freegrid.app/docs/vue/) - Svelte: @freegridjs/svelte has a Svelte 5 component and a use:freegrid action. Every grid option is a prop and events are on… props such as… (https://freegrid.app/docs/svelte/) - Angular: @freegridjs/angular has a standalone component for Angular 17+. Events are outputs with the same names, except (gridPaste) and (gridScroll).… (https://freegrid.app/docs/angular/) - API reference: createGrid(element, options) returns this API. Calls after destroy() do nothing. (https://freegrid.app/docs/api/) - Events: (https://freegrid.app/docs/events/) ## Questions by topic ### How do I install FreeGrid? Run npm i @freegridjs/core, import createGrid and the stylesheet, and call createGrid(element, { columns, data }) on an element with a height. Without a build step, load freegrid.min.js and freegrid.css from a CDN. ### Does FreeGrid need React, Vue or another library? No. The core has no dependencies and works with plain JavaScript; thin wrappers for React, Vue, Svelte and Angular are separate packages. ### Can a FreeGrid column be calculated from other columns? Yes. Give the column a formula such as =qty * price and an id; it is worked out for every row and used for display, sorting, filtering, summaries and exports. ### How do I make a running total in a grid? Use PREV: a computed column with formula =amount + PREV(running) adds each row to the total of the row above, following the order of the data. ### Which spreadsheet functions does FreeGrid support? About 60, including SUM, AVG, ROUND, IF, IFS, IFERROR, SUMIF, COUNTIF, LOOKUP, RANK, LEFT, MID, TEXT, YEAR, DAYS and EDATE, plus your own through registerFormulaFunction. ### How do I highlight cells by value in FreeGrid? Add a rule to the conditionalFormats option, for example { columns: ["sales"], op: "gte", value: 1000, style: { background: "#c6efce" } }, or use Conditional formatting… in the column menu. ### Does FreeGrid support data bars and colour scales? Yes: type "dataBar", "colorScale" and "iconSet" rules draw bars, colour gradients and arrow or traffic-light icons, and they carry into Excel, print and PDF. ### How do I update a grid in real time from a WebSocket? Give the grid getRowId and call connectLive(grid, { url: "wss://…" }), or call applyTransactionAsync({ update: rows }) for each message. Only the changed rows repaint. ### Do live updates lose the user’s selection or scroll position? No. Rows are updated in place by id, so the selection, the scroll position and a cell being edited stay where they are. ### Can users ask a FreeGrid grid questions in plain language? Yes. With the assistant option the grid sends the request and column summaries to your server, which asks an AI model and answers with checked actions such as sorting, filters, grouping, formats and charts. ### Does the AI assistant send my data to the model? Only what your server forwards. The grid sends column summaries and a few sample rows (sampleRows, 0 for none), and the model API key stays on your server. ### Can FreeGrid export a PDF? Yes. grid.exportPdf("file.pdf", options) saves the print layout as a PDF in the browser, with any language, and getPdf() returns the bytes. ### How do I repeat the header row on every printed page? Headers repeat by default (repeatHeader). repeatRows: n also repeats the first n rows, and repeatTitle repeats the title; Excel exports get the same print titles. ### Can FreeGrid export to Excel, CSV and JSON? Yes: exportXlsx writes a real .xlsx file with formats, merged headers and colours, exportCsv writes UTF-8 CSV that opens in Excel, and exportJson writes an array of row objects. ### Can users import Excel files into the grid? Yes. importFile reads .xlsx and CSV files, matches columns by header or field, and checks values the same way as pasting. ### Which back ends work with FreeGrid? Any back end that answers JSON. Ready-made helpers turn the grid’s requests into safe SQL for Java (Spring Boot, eGovFrame), Node.js, Python, PHP, C# (.NET) and Go. ### Is the server-side filtering safe from SQL injection? Yes. Only the columns you list can be sorted or filtered, every value is a bound parameter, and LIKE searches escape % and _. ### What filters does FreeGrid have? Text, number, date and set (list of values) filters in the column menu, a filter row under the header, a quick search box, and an advanced filter that combines conditions across columns with AND and OR. ### How do I make cells editable in FreeGrid? Set editable: true on a column (or a function per row). Text, number, date, select, time and textarea editors are built in, with validation, undo and change tracking for saving. ### How do I listen to grid events? Call grid.on("cellClick", handler); it returns a function that stops listening. The wrappers use the same events as onCellClick (React), @cell-click (Vue) and (cellClick) (Angular). ### Can I stop a sort or an edit before it happens? Yes. beforeSort, beforeFilter, beforePageChange, beforeEdit and beforeCellChange arrive first; call cancel() to stop the change. ### How do I use FreeGrid in React? Install @freegridjs/react and render ; props map to grid options and events to on… props, and a ref gives the full API. ### How do I use FreeGrid in Vue? Install @freegridjs/vue and use ; getApi() returns the grid API. ## Formula functions - `Math` (SUM AVG AVERAGE MIN MAX MEDIAN COUNT COUNTA COUNTBLANK PRODUCT ROUND ROUNDUP ROUNDDOWN INT TRUNC ABS SIGN SQRT POWER EXP LN LOG10 MOD CEILING FLOOR PI): Numbers. SUM, AVG, MIN and MAX accept COL(field) and skip text in it. - `Text` (CONCAT TEXTJOIN LEN LEFT RIGHT MID UPPER LOWER PROPER TRIM SUBSTITUTE REPLACE REPT FIND SEARCH VALUE TEXT): Text. TEXT(n, "#,##0.00") formats numbers, TEXT(d, "yyyy-mm-dd") dates. - `Logic` (IF IFS IFERROR IFNA SWITCH CHOOSE AND OR XOR NOT ISBLANK ISNUMBER ISTEXT ISERROR): IF, IFS, IFERROR, SWITCH and CHOOSE only work out the branch they take. - `Dates` (TODAY NOW DATE YEAR MONTH DAY WEEKDAY DAYS EDATE EOMONTH): Dates as "YYYY-MM-DD" text, like date columns. DAYS(end, start) counts days. - `Across rows` (COL(field) PREV(field, n) NEXT(field, n) ROW() SUMIF COUNTIF AVERAGEIF LOOKUP XLOOKUP RANK): COL is a whole column; PREV / NEXT read n rows up or down; criteria like ">10", "<>x", "A*". ## Conditional format rules - `'value'` (op: 'gt' | 'gte' | 'lt' | 'lte' | 'eq' | 'ne' | 'between' | 'notBetween' | 'contains' | 'notContains' | 'startsWith' | 'endsWith' | 'blank' | 'notBlank'): Compare the cell with value (and valueTo). The default type. - `'formula'` (formula: '=amount > target'): A formula over the row; true applies the style. - `'top' / 'bottom'` (rank?: number, percent?: boolean): The highest or lowest n values (or n %) of the column. - `'aboveAverage' / 'belowAverage'` (—): Above or below the column average. - `'duplicate' / 'unique'` (—): Values that appear more than once, or once. - `'dataBar'` (color?, min?, max?): A bar in the cell, as long as the value; negative numbers grow from zero. - `'colorScale'` (colors?: ['#f8696b', '#ffeb84', '#63be7b']): Two or three colours from the lowest to the highest value. - `'iconSet'` (icons?: 'arrows' | 'traffic' | 'signs'): An icon for the top, middle and bottom third. - `style` ({ color?, background?, bold?, italic?, strike? }): The look of matching cells. row: true styles the whole row; stopIfTrue stops later rules. ## connectLive options (real-time updates) - `url` (string): ws:// or wss:// opens a WebSocket; anything else server-sent events (EventSource). - `poll / interval` (() => Promise / number): Or ask your server every interval ms (default 2000). - `parse` ((message) => RowTransaction | Row[] | { rows } | null): Turn your message format into a transaction. Default: JSON; an array is an upsert; { rows } replaces all rows. - `reconnect / maxDelay` (boolean / number): Reconnect after a drop, waiting 0.5 s, 1 s, 2 s … up to maxDelay (default 30 s). - `immediate` (boolean): Apply each message at once instead of batching (applyTransactionAsync). - `onStatus / onError` ((status: 'connecting' | 'open' | 'paused' | 'closed' | 'error') => void): Show the connection state; onError gets messages that could not be read. ## AI assistant actions (what your server answers) - `sort / filter / clearFilters / quickFilter / advancedFilter` ({ type, sort } / { type, colId, model }): Sorting and filters, with the same models as setSort and setFilter. - `groupBy / pivot` ({ type, colIds, expand? } / { type, pivot }): Group rows or switch to a pivot table. - `showColumns / hideColumns` ({ type, colIds }): Show or hide columns. - `conditionalFormat / clearConditionalFormats` ({ type, format }): Add a conditional format. - `addColumn` ({ type, column: { id, header, formula, type } }): A computed column; the formula must parse. - `summary / chart / message` ({ type, summary } / { type, chartType, colIds } / { type, text }): Summary rows, a chart over columns, or just an answer. ## Print and PDF options - `title / subtitle / footer` (string): Heading on top, a line under it, text at the bottom left of every page. - `paper / orientation` ('A4' | 'A3' | 'A5' | 'B4' | 'B5' | 'letter' | 'legal' / 'auto' | 'portrait' | 'landscape'): Default A4 and 'auto' (sideways when the columns are too wide). - `repeatHeader` (boolean): Column headers on every page (default true). Excel exports get the same print titles. - `repeatRows` (number): Also repeat the first n rows on every page, under the headers. - `repeatTitle` (boolean): The title block on every page, not only the first. - `fitToWidth / groups / summary / striped / pageNumbers / printedAt / rowNumbers` (boolean): Scale columns to the page, group rows with totals, summary rows, stripes, "1 / 3", print time, a # column. - `resolution / quality` (number): PDF only: page image sharpness (default 2) and JPEG quality (default 0.92). ## Column definition - `field` (string): Property of the row object shown in this column. - `id` (string): Column id. Defaults to field; required when there is no field. - `header` (string): Header text. Defaults to field. - `type` ('text' | 'number' | 'date' | 'boolean'): Drives alignment, formatting, sorting and the default filter. - `format` (string | Intl options): Number: '#,##0.00', '0.0%', 'currency:KRW', 'integer'. Date: 'date', 'datetime', 'time', 'short', 'medium', 'long'. - `width / minWidth / maxWidth` (number): Pixel widths. Default width 120, min 40. - `flex` (number): Share of the leftover width. Resizing turns it into a fixed width. - `pinned` ('left' | 'right'): Keep the column visible while scrolling horizontally. - `hide` (boolean): Start hidden. Users can show it from the column chooser. - `align` ('left' | 'center' | 'right'): Cell alignment. Numbers default to right. - `sortable` (boolean): Default true. - `resizable` (boolean): Default true. - `filter` ('text' | 'number' | 'date' | 'set' | false): Filter UI in the column menu. Default depends on type. - `comparator` ((a, b, rowA, rowB) => number): Custom sort order. - `valueGetter` ((row) => unknown): Computed value, e.g. quantity × price. Used for display, sort, filter and summaries. - `renderer` ((params) => string | HTMLElement): Custom cell content. Strings are inserted as HTML — escape untrusted data. - `cellClass` (string | (params) => string): Extra CSS class on cells. Its text color, background and bold also go into Excel exports. - `wrapText` (boolean): Wrap long text onto more lines; with autoRowHeight the row grows to fit. - `rowSpan / colSpan` (({ value, row, rowIndex }) => number): Cells covering several rows or columns, decided per cell. rowSpan takes precedence over merge. - `headerRenderer` (({ header, column }) => string | HTMLElement): Header content (icons, buttons, links); sorting and the menu button stay. - `headerClass` (string | (column) => string): Extra class names on the header cell (also for column groups). - `copyable` (boolean | ({ value, row, column }) => boolean): false keeps values out of copies: blocked cells copy as empty text. - `tooltip / headerTooltip` (boolean | (params) => string / string): Hover text: true shows the value, a function returns the text; headerTooltip explains the header. Cut-off text shows in full anyway (overflowTooltip). - `columnGroupShow / openByDefault` ('open' | 'closed' / boolean): Collapsible column groups: show a child only while its group is open or closed; the group header gets a toggle. - `sparkline` (true | { type?: 'line' | 'bar' | 'area', color? }): The value is an array of numbers drawn as a small chart in the cell. - `children` (ColumnDef[]): Makes this a column group header. - `merge` (boolean | (a, b, rowA, rowB) => boolean): Merge consecutive rows with the same value into one tall cell. - `wrapHeaderText` (boolean): Let a long header label wrap. - `aggFunc` ('sum' | 'avg' | 'min' | 'max' | 'count' | (values, rows) => value): Aggregate shown in group rows. - `chooser` (boolean): false keeps the column out of the column chooser. - `lockVisible` (boolean): Users cannot hide this column. - `editable` (boolean | ({ value, row, rowIndex, column }) => boolean): Allow editing and pasting (needs field); a function decides per cell. - `editor` ({ type: 'select', options } | { type: 'textarea' | 'time' | 'datetime' } | (params) => editor): Editor for this column. options can be a function of the row (dependent selects); an option with disabled: true cannot be chosen or pasted. 'datetime' is the default for date columns with format 'datetime'. - `required` (boolean): Reject blank values. - `validate` ((value, row) => true | string): Return a message to reject a value. - `unique` (boolean): Reject a value another row already has in this column (blank values are allowed). - `formulas` (boolean): Cells may hold formulas like '=qty*price' (same-row fields, + - * / % ^ &, ROUND, ABS, MIN, MAX, SUM, AVG, IF). The result is shown, sorted and exported; the editor keeps the formula. ## Grid options - `columns` (ColumnDef[]): Required. Column definitions. - `data` (object[]): Rows. - `getRowId` ((row) => string): Stable id; keeps selection across setData. - `rowSelection` ('single' | 'multiple'): Adds a checkbox column and click selection. - `rowHeight / headerHeight` (number): Pixels. Default follows the font size (36 / 40 at 13px). - `theme` (string): 'light', 'dark', 'board', 'erp', 'excel', 'classic' or your own. - `fontSize / fontFamily` (number / string): Grid font. Row heights follow the size. - `locale` (string | LocalePack): UI language, e.g. 'ko' or an imported pack. Default: the browser's language. - `sideBar` (boolean | { open }): Dock the Columns panel on the right. - `summary` (SummaryOptions): Summary rows. See Summary rows. - `rangeSelection` (boolean): Excel-style cell ranges by drag, Shift+click, Shift+arrows, Ctrl+A. Default true. - `statusBar` (boolean): Row counts plus count/sum/average/min/max of the selected cells. - `fillHandle` (boolean): Drag the selection corner to fill editable cells. Default true. - `pinnedTopRows / pinnedBottomRows` (Row[]): Rows fixed above or below the scrolling rows. - `findBar / wrapHeaderText` (boolean): Ctrl+F find bar (default true); wrap all header labels. - `groupBy` (string[]): Group rows by these column ids, in order. - `groupDefaultExpanded` (number): Levels open at start: 0 none (default), n levels, -1 all. Also applies to tree data. - `groupPanel` (boolean): Bar above the header where headers can be dropped to group. - `treeData` ({ getParentId(row), setParentId?, hasChildren?, loadChildren? }): Nest rows under their parent id (needs getRowId). setParentId lets rows be dragged to another parent; hasChildren + loadChildren fetch children from a server on first expand. - `detailRenderer / detailHeight / isRowMaster` ((params) => Element | string / number / (row) => boolean): Expandable panel under a row (default height 240); limit which rows can expand. - `rowDrag` (boolean | { group?, canDrop? }): Handle column for dragging rows into a new order (off while sorted, filtered or grouped). In trees the middle of a row drops inside it. Grids with the same group exchange rows; canDrop({ row, target, position, fromOtherGrid }) can refuse a target. - `pagination` (boolean | { pageSize?, pageSizes? }): Show one page at a time with a pager (default 100 rows per page). - `datasource` ({ getRows({ start, end, sort, filters, quickFilter, advancedFilter, groupBy, groupKeys }) }): Load rows from your server in blocks; must resolve to { rows, total? }. With groupBy, levels above the rows answer one row per group, e.g. { status, count, qty }. - `blockSize / maxBlocks` (number): Rows per request (100) and blocks kept in memory (100). - `sideBar` (boolean | { open?, panels?: ('columns' | 'filters')[] }): Docked panels for columns and filters. - `pivot` ({ rows, columns?, values: { colId, aggFunc? }[], totals? }): Show the data as a pivot table. pivotPanel: true adds the Pivot side panel. - `autoRowHeight` (boolean): Grow each row to fit its wrapped cells (wrapText columns); rowHeight is the minimum. - `direction` ('ltr' | 'rtl'): 'rtl' lays the grid out right to left (Arabic, Hebrew): first column on the right. - `cardView` (boolean | number): Show rows as cards when the grid is narrower than this many px (true = 640). Phones: tap a focused cell to edit, long-press for the menu. - `isRowSelectable / headerCheckbox` ((row) => boolean / boolean): Which rows can be checked (others show a disabled box); hide the select-all box with headerCheckbox: false. - `rowNumbers` (boolean | { width?, header? }): A pinned column numbering data rows 1, 2, 3… (continues across pages). Click a number to select the row. - `filterRow` (boolean): Boxes under the header that filter as you type: text contains, numbers >10, <=5, 10~20, dates >2026-01-01, a list for set columns. - `groupSelectsChildren` (boolean): Tree data: checking a parent also checks its children. Group rows always have a checkbox for their rows. - `editType` ('cell' | 'row'): 'row': a row is edited as a whole — Tab stays in the row, Enter or leaving saves it, Escape puts it back (rowEditStart / rowEditStop). - `getRowHeight` ((row) => number | undefined): A height for each row (a minimum with autoRowHeight). - `autoHeight` (boolean): The grid grows with its rows instead of scrolling inside; best for up to a few hundred rows. - `density` ('compact' | 'normal' | 'comfortable'): Row spacing; a fixed rowHeight wins. Change it later with setDensity. - `flashCells` (boolean): Cells changed by applyTransaction flash briefly (default true). - `flashDuration / asyncTransactionWait` (number): How long changed cells flash (ms, default 1000; numbers flash green up and red down) and how long applyTransactionAsync collects updates (ms, default 50). - `conditionalFormats` (ConditionalFormat[]): Rules that colour cells by value or formula, top / bottom, averages and duplicates, plus data bars, colour scales and icon sets. conditionalFormatMenu: false hides the column menu item. - `assistant` ({ url, headers?, sampleRows?, context?, suggestions? }): The AI assistant: requests go to your server, which answers with grid actions. - `noRowsOverlay / loadingOverlay` (string | ({ filtered }) => string | HTMLElement): What to show when there are no rows (filtered: the filters hide them all) or while server rows load. - `overflowTooltip / advancedFilter` (boolean): false turns off the full-text tooltip of cut-off cells / the "Advanced filter…" menu item. - `keymap` (Record): Remap or disable keys, e.g. { Enter: false, 'Ctrl+E': 'edit' }. - `contextMenu` (false | (params) => items): false keeps the browser menu; a function customizes the right-click menu. ## API - `setData(rows, { scrollToTop? })` (void): Replace all rows. The scroll position stays unless scrollToTop is set. - `getData()` (Row[]): Current rows (unfiltered, unsorted). - `setColumns(defs)` (void): Replace column definitions. - `getColumnState() / applyColumnState(state)` (ColumnState[]): Save and restore order, width, visibility and pinning. - `resetColumnState()` (void): Back to the column definitions. - `setColumnVisible(colId, visible)` (void): Show or hide a column. - `autoSizeColumns(colIds?)` (void): Fit widths to header and visible cells. - `openColumnChooser() / closeColumnChooser()` (void): Floating column chooser. - `toggleSideBar(open?)` (void): Open or close the docked Columns panel. - `setSort(items) / getSort()` (SortItem[]): e.g. [{ colId: 'total', dir: 'desc' }]. - `setFilter(colId, model | null) / getFilterModel()` (FilterModel): Set or clear one column filter. - `setQuickFilter(text)` (void): Search all visible columns. - `getSelectedRows() / selectAll() / deselectAll()` (Row[]): Selection helpers. selectAll selects the filtered rows. - `setSummary(options | null) / getSummaryValues()` (object[]): Change summary rows; read raw totals. - `getRange() / setRange(range | null)` (GridRange): Read or set the active cell range. - `getRanges() / setRanges(ranges)` (GridRange[]): All ranges, including those added with Ctrl+click or Ctrl+drag; the last one is active. - `copySelection({ headers? })` (string): Copy the range as Excel TSV and return it. - `pasteText(text)` ({ changed, skipped }): Paste TSV/Excel text at the range or focused cell. - `startEdit(rowIndex, colId) / stopEdit(cancel?)` (boolean / void): Open or close the editor. - `addRows(rows, index?) / removeRows(rows | fn) / removeSelectedRows()` (Row[]): Insert or remove rows (tracked, undoable). - `getModifiedRows() / getChanges() / hasChanges()` (object): Created/updated/deleted rows and column-level diffs. - `commitChanges() / revertChanges()` (void): Accept changes as the new baseline, or undo all of them. - `undo() / redo() / canUndo() / canRedo()` (void / boolean): Undo history for edits, pastes and row changes. - `fillRange(target)` (void): Fill from the selection into a larger range (series aware). - `find(text) / findNext() / findPrevious() / clearFind()` (number / void): Highlight and step through matching cells. - `setPinnedRows({ top?, bottom? })` (void): Change pinned rows. - `setGroupBy(colIds) / getGroupBy()` (void / string[]): Change or read the grouping. - `expandAll() / collapseAll() / setGroupExpanded(key, open)` (void): Open or close groups and tree rows. Group keys look like 'country:KR|city:Seoul'; tree keys are row ids. - `setDetailExpanded(row, open) / isDetailExpanded(row)` (void / boolean): Open, close or check a detail panel. - `dropRow(row, target, position?)` (boolean): Move a row 'before', 'after' or (tree data) 'inside' another row; null target = to the end. False when refused. - `moveRow(row, toIndex)` (boolean): Move a row in the source data (undoable). False while sorting, filtering or grouping. - `setPage(n) / getPage() / getPageCount()` (void / number): Move between pages (0-based). - `setPageSize(n) / getPageSize()` (void / number): Change rows per page. - `refreshRows()` (void): Server data: drop cached rows and load again. - `exportXlsx(filename?, options?) / getXlsx(options?)` (void / Uint8Array): Excel file of the rows. Options: columns, rows (filtered | all | selected), headers, sheetName, styles (column align and cellClass colors, default true). - `exportCsv(filename?, options?) / getCsv(options?)` (void / string): CSV with a BOM so Excel reads Korean; same options plus separator. - `exportJson(filename?, options?) / getJson(options?)` (void / string): The rows as an array of objects keyed by column id (keys: 'header' for headers); formatted: true saves the text as shown. - `exportPdf(filename?, options?) / getPdf(options?)` (void / Uint8Array | null): The print layout as a PDF made in the browser (any script), with repeatHeader, repeatRows and repeatTitle. - `importFile(file, { mode?, sheet? })` (Promise<{ rows, skipped }>): Read .xlsx or CSV; headers are matched to columns; append (default) or replace. - `toggleSideBar(open?, panel?)` (void): Open or close the side bar, optionally on 'columns' or 'filters'. - `setPivot(config | null) / getPivot()` (void / PivotConfig | null): Turn the pivot on, change it, or return to the normal view. - `createChart({ type?, range?, container?, title? })` ({ el, setType, update, destroy }): SVG chart of the range or the whole view: 'bar', 'stackedBar', 'line', 'area', 'pie'. - `version` (string): Exported constant with the package version, e.g. import { version }. - `getDisplayedRowCount()` (number): Rows after filtering. - `scrollToRow(index)` (void): Scroll a displayed row into view. - `getScrollPosition() / setScrollPosition({ top?, left? })` ({ top, left }): Read or set the scroll offsets of the rows area in px. - `syncScroll(otherGrid, axis?)` (() => void): Scroll two grids together ('both', 'vertical' or 'horizontal'); returns a function that unlinks them. - `validate()` ({ row, rowIndex, colId, message }[]): Check every editable cell against required, unique and validate; empty when all rows are valid. - `getState() / setState(state, { parts? })` (SavedGridState): The whole view as plain JSON — columns, sort, filters, advanced filter, grouping, open groups, column groups, page, selection — to save per user and restore. - `applyTransaction({ add?, addIndex?, update?, remove? }) / applyTransactionAsync(tx)` (TransactionResult / void): Change rows by id (live data). Not undoable, not tracked as edits; changed cells flash. The async version merges calls into one update about every 50 ms. - `flushAsyncTransactions()` (void): Apply the queued applyTransactionAsync updates now. A transaction may also have upsert: rows updated by id or added. - `setConditionalFormats(list) / getConditionalFormats() / openConditionalFormats(colId)` (void / ConditionalFormat[] / void): Replace or read the conditional formats; open the dialog for a column. - `askAssistant(prompt) / openAssistant()` (Promise / void): Ask the AI assistant from code (the result has message, applied, skipped and undo()), or open its chat panel. - `setAdvancedFilter(filter | null) / getAdvancedFilter() / openFilterBuilder()` (void / AdvancedFilter | null / void): Conditions across columns with nested and / or: { operator: 'or', conditions: [{ colId, model }, { operator: 'and', conditions: [...] }] }. - `cutSelection()` (string): Copy the range, then clear its editable cells (one undo step). Also Ctrl+X. - `stopRowEdit(cancel?)` (void): editType 'row': finish the row being edited. - `setColumnGroupOpened(groupId, open) / isColumnGroupOpened(groupId)` (void / boolean): Open or close a collapsible column group. - `print(options?) / getPrintHtml(options?)` (void / string): Print the rows as a table: group and summary rows, page numbers. Options: the export options plus title, subtitle, footer, paper, orientation, fitToWidth, groups, summary, striped, pageNumbers, printedAt, rowNumbers. - `getXlsxSheet(options?)` (XlsxSheet): One worksheet of the rows, for buildWorkbook / exportWorkbook. - `setDensity(density) / getDensity()` (void / string): 'compact', 'normal' or 'comfortable'. - `showOverlay(kind | null)` (void): Show the 'loading' or 'noRows' overlay yourself; null goes back to automatic. - `restDatasource(url, options?) · loadData(grid, url, options?) · saveChanges(grid, url, options?) · csrfHeaders()` (module functions): Server helpers (Spring Boot, eGovFrame…): a datasource over POST JSON, "search, edit, save" screens, and the Spring Security CSRF header. See Server integration. - `buildWorkbook(sheets) / exportWorkbook(filename, sheets)` (module functions): One .xlsx with a sheet per grid: [{ grid, sheetName, options }]. - `setTheme(name)` (void): Switch theme. - `setFont({ size?, family? })` (void): Change font; family null restores the theme font. - `setLocale(codeOrPack)` (void): Switch UI language. - `on(event, handler)` (() => void): Subscribe; returns an unsubscribe function. - `destroy()` (void): Remove the grid and its listeners. ## Events - `cellClick / cellDoubleClick` ({ row, rowIndex, colId, value, event }): A data cell was clicked. - `selectionChange` ({ selected: Row[] }): Selected rows changed. - `sortChange` ({ sort }): Sort changed. - `filterChange` ({ filters }): A column filter changed. - `columnResize` ({ colId, width }): Resize or autosize finished. - `columnMove` ({ colId, toIndex }): A column was dragged to a new position. - `columnVisible` ({ colId, visible }): A column was shown or hidden. - `rangeChange` ({ range }): The cell range changed. - `cellValueChanged` ({ row, rowIndex, colId, oldValue, newValue }): A paste changed a cell. - `paste` ({ changed, skipped }): A paste finished. - `editStart / editStop` ({ rowIndex, colId, committed? }): An editor opened or closed. - `validationFailed` ({ rowIndex, colId, value, message }): An edited value was rejected. - `rowsAdded / rowsRemoved` ({ rows }): Rows were inserted or removed. - `groupChange / groupToggle` ({ groupBy } / { key, expanded }): Grouping changed; a group or tree row opened or closed. - `detailToggle` ({ row, expanded }): A detail panel opened or closed. - `rowDragEnd` ({ row, fromIndex, toIndex }): A row was dragged to a new position. - `pageChange` ({ page, pageSize, pageCount }): The page or page size changed. - `rowsLoaded / rowsLoadFailed` ({ start, end, total? } / { start, end, error }): A server block arrived or failed. - `pivotChange` ({ pivot }): Pivot mode was turned on, changed or turned off. - `focusChange` ({ rowIndex, colId }): The focused cell moved. - `beforeSort / beforeFilter / beforePageChange` ({ sort | colId, model | page, pageSize, cancel() }): Fired before the change; call cancel() to stop it (for example to sort or filter on your server instead). - `beforeEdit / beforeCellChange` ({ row, rowIndex, colId, oldValue?, newValue?, cancel() }): Fired before an editor opens or a value is written (edit, paste, fill, delete); cancel() stops it. - `rowDrop` ({ row, target, position, fromOtherGrid, fromGrid }): A row was dropped on this grid, also from another grid of the same drag group. - `scroll` ({ top, left }): The rows area scrolled. - `childrenLoaded / childrenLoadFailed` ({ row, rows } / { row, error }): treeData.loadChildren finished or failed for a row. - `transactionApplied` ({ added, updated, removed, notFound }): applyTransaction (or a batch of applyTransactionAsync) finished. - `conditionalFormatsChange` ({ formats }): The conditional formats changed (API or the dialog). - `assistantAnswer` ({ prompt, message, applied, skipped }): The assistant panel got an answer and applied its actions. - `rowEditStart / rowEditStop` ({ row, rowIndex } / { row, rowIndex, committed, changes }): editType 'row': a row started or finished editing; changes lists the changed cells. - `advancedFilterChange` ({ advancedFilter }): The advanced filter changed. - `columnGroupOpened` ({ groupId, open }): A collapsible column group was opened or closed. ## Releases ### 1.6.0 (2026-10-16) — AI assistant - People ask the grid in plain words; the grid sends the request with column summaries (not the rows) to your server, and your server answers with actions - Sorting, filters, grouping, pivots, conditional formats, computed columns, summary rows and charts; every action is checked against the columns before it runs, and every answer can be undone - A chat panel from the right-click menu or api.openAssistant(), askAssistant(prompt) from code, and the assistantAnswer event - Server examples for Node.js (Express) and Python (FastAPI) with Claude; the model API key stays on the server - Server helpers for Node.js, Python, PHP, C# and Go next to the Java ones: paging, sorting, filters, quick search, the advanced filter, server grouping and saving, with only allowed columns and bound values ### 1.5.0 (2026-10-16) — PDF, JSON and rows repeated on every page - exportPdf / getPdf save the print layout as a PDF made in the browser, so Korean, Japanese and every other script come out exactly as on screen - Print and PDF repeat the column headers on every page (repeatHeader), the first n rows (repeatRows) and the title (repeatTitle); Excel files get the same print titles - exportJson / getJson save the rows as objects with typed values, keyed by column id or header - Export JSON and Export PDF in the right-click menu, in all fourteen languages - The side bar's Columns and Filters strip looks like tabs while its panel is open ### 1.4.0 (2026-10-16) — Conditional formatting - Rules by value, formula, top / bottom, above / below average and duplicates, styling a cell or the whole row - Data bars, colour scales and icon sets drawn from the column's values - A Conditional formatting… dialog in the column menu; the conditionalFormats option, setConditionalFormats and saved views (getState) - The formats follow live updates and carry into Excel, print and PDF ### 1.3.0 (2026-10-16) — Formula engine - Computed columns: a formula on the column (formula: '=qty * price') is worked out for every row, no field needed - About 60 functions: maths, text, logic, dates, SUMIF, COUNTIF, AVERAGEIF, LOOKUP and RANK - Across rows: COL(field) for whole-column totals, PREV / NEXT for running totals, ROW() - registerFormulaFunction adds your own functions; formulas are parsed once and cached, so large grids stay fast ### 1.2.0 (2026-10-16) — Faster real-time updates - Updates that no sort, filter or group reads keep the row order without sorting again, and only the changed rows repaint - Numbers flash green going up and red going down; flashDuration sets how long - connectLive keeps a grid up to date from a WebSocket, server-sent events or polling, reconnects after a drop and can pause - upsert in transactions (update by id or add), asyncTransactionWait and flushAsyncTransactions ### 1.1.0 (2026-10-15) — Printing that looks finished - print() prints group rows with counts and totals (every group, open or not), summary rows, row numbers and cellClass colors - Choose the paper (A4, A3, A5, B4, B5, letter, legal); wide grids turn the page sideways, and columns keep their proportions on the page - A title, subtitle and print time on top; a footer and page numbers at the bottom; the header repeats on every page - Print… in the right-click menu, in all fourteen languages; new PrintOptions type ### 1.0.1 (2026-10-15) — Fixes for editing, live updates and server data - An open cell editor follows its row when live updates add, remove or re-sort rows, so a value is never saved into another row - Cut (Ctrl+X) clears only the cells it copied; cancelling a row edit leaves nothing to undo - Queued live updates apply in order and stay fast on large data; change tracking keeps server values - restDatasource sends server-side grouping and the advanced filter; saveChanges sends one request at a time and keeps edits made meanwhile - Server group levels report failures and retry, load past 10,000 rows and ask a few groups at a time - The filter row keeps what you are typing during live updates and rejects dates that do not exist - Safer Excel import (huge or over-compressed files are refused) and faster chained formulas ### 1.0.0 (2026-10-15) — FreeGrid 1.0 - Row numbers, a filter row under the header, cell and header tooltips, and row spacing (density) - Two conditions per column filter, and an advanced filter across columns with a builder - Save and restore the whole view (getState / setState): columns, sort, filters, grouping, page, selection - Live updates by id with flashing cells (applyTransaction, applyTransactionAsync) - Row editing mode (editType 'row'), cut with Ctrl+X, a height per row (getRowHeight) - Group row checkboxes, collapsible column groups, and server-side grouping with counts and totals - Grids that grow with their rows (autoHeight) and printing (print) - Sparklines in cells; donut, bar-and-line and scatter charts; Excel files with several sheets - Custom empty and loading screens - Server helpers for Spring Boot and eGovFrame (restDatasource, loadData, saveChanges, csrfHeaders) with tested Java examples - Seven more languages: Traditional Chinese, Vietnamese, Thai, Indonesian, Russian, Portuguese (Brazil), Italian ### 0.8.1 (2026-10-15) — Domain matching for license keys - A license key for example.com now covers example.com and www.example.com; other subdomains need their own key, or a key issued for *.example.com ### 0.8.0 (2026-10-15) — Layout, editing rules, row drag and new framework components - Cell spans per cell (colSpan, rowSpan), wrapped text with automatic row height, right-to-left layout - Card view and touch editing for phones; custom header content and header classes - Per-cell editable, per-row checkbox locks, time and date-time editors, disabled select options - Unique-value validation, validate() for all rows, and cell formulas such as =qty*price - Cancellable events: beforeSort, beforeFilter, beforePageChange, beforeEdit, beforeCellChange - Row drag inside trees (change parent), drop rules, and dragging rows between grids - Tree children loaded from a server on first expand - Scroll position API, synced scrolling between grids, scroll kept when data changes - Several ranges with Ctrl+click or Ctrl+drag; cells that cannot be copied - Excel export keeps column alignment and cell colors - Accessibility improvements (ARIA grid structure, announced focus) and Shadow DOM support - New packages: @freegridjs/svelte and @freegridjs/angular ### 0.7.1 (2026-10-15) — Small improvements - Stability improvements and small fixes ### 0.7.0 (2026-10-15) — Pivot tables and charts - Pivot tables: row fields, column fields and values (sum, avg, min, max, count), with totals and grouped subtotals computed from the source rows - Pivot side panel to build the table without code - SVG charts with no dependencies: bar, stacked bar, line, area and pie - Charts from a selected range through the right-click menu; they redraw when cells change - Standalone renderChart; new API setPivot, getPivot, createChart; event pivotChange ### 0.6.0 (2026-10-15) — Export, import and filters panel - Excel (.xlsx) export with number and date formats, merged group headers, a frozen header and AutoFilter - CSV export with a BOM for Excel and protection against formula injection - Excel and CSV import that matches headers to columns and validates values like a paste (undoable) - Filters panel in the side bar, with "Clear all filters" - Export items in the right-click menu - Standalone readXlsx and writeXlsx, with no dependencies ### 0.5.0 (2026-10-15) — Pages and server data - Pagination with a pager bar and a rows-per-page choice - Server-side rows: load blocks from your API as people scroll, with placeholder rows while loading - Infinite scroll when the total is unknown - Server-side pages: one request per page - Sorting, filters and quick search are sent to the server - New API: setPage, setPageSize, getPageCount, refreshRows; events pageChange, rowsLoaded, rowsLoadFailed ### 0.4.0 (2026-10-15) — Grouping and trees - Row grouping by one or more columns, with group counts and per-column aggregates (aggFunc) - Group panel: drag a column header above the grid to group by it; reorder or remove chips - Tree data with getParentId; filters keep the ancestors of matching rows - Master/detail: an expandable full-width panel under a row, with its own height - Row drag handle to reorder rows (undoable) - New API: setGroupBy, expandAll, collapseAll, setGroupExpanded, setDetailExpanded, moveRow ### 0.3.0 (2026-10-15) — Spreadsheet tools - Cell merging for consecutive equal values (merge) - Fill handle that continues number, date and text series - Status bar with row counts and totals of the selected cells - Pinned top and bottom rows - Ctrl+F find bar and find API - Wrapping header labels ### 0.2.0 (2026-10-15) — Editing and clipboard - Excel-style cell ranges, copy to and paste from Excel, right-click menu - In-cell editors: text, number, date, select (with dependent options), textarea and custom - Validation, change tracking, undo and redo - Adding and removing rows, custom key bindings