@iyulab/flex-table 0.39.0 → 0.40.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,65 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.40.1] - 2026-09-30
4
+
5
+ ### Fixed
6
+
7
+ - **The Enter that finishes an IME composition no longer commits the cell edit and moves down**, and
8
+ no longer jumps to the next match in the find panel. Typing Korean (or another composed language)
9
+ committed the cell before the last syllable was in. Requires `@iyulab/components` 1.51.0 or later.
10
+
11
+ - **The React wrapper maps every custom event.** `row-reorder`, `column-visibility-change`,
12
+ `comment-change`, `data-import`, `fill-handle-apply`, `find-replace` and `header-context-menu` had no
13
+ `on*` prop; they are now `onRowReorder`, `onColumnVisibilityChange`, `onCommentChange`,
14
+ `onDataImport`, `onFillHandleApply`, `onFindReplace` and `onHeaderContextMenu`.
15
+
16
+ ### Documentation
17
+
18
+ - README corrected against the source: the `context-menu` detail and that it is cancelable,
19
+ `cell-select` can be `null`, XLSX export, the `footerData` type, and the `format` function's
20
+ arguments.
21
+ - The package now ships an agent skill (`skills/iyulab-flex-table/`) — the component API, the React
22
+ wrapper and data-source hooks, and styling in a form coding agents load directly.
23
+
24
+ ## [0.40.0] - 2026-09-28
25
+
26
+ ### Changed
27
+
28
+ - 🔴 **Breaking — the React hooks moved to `./react`.** `useODataSource` and `useArraySource` (and
29
+ their option/result types) are now exported from `@iyulab/flex-table/react`, next to
30
+ `FlexTableReact`. `./odata` and `./array` keep only the pure functions — `buildSearchExpression`,
31
+ `parseOrderBy`, `computeArrayView`, and the new `buildODataQuery` — and no longer load React, so an
32
+ app without React (a typeahead that is not a table, a Lit app) can use them without installing it.
33
+ Before, importing `buildSearchExpression` from `./odata` required `react` statically.
34
+ Migration: `import { useODataSource } from '@iyulab/flex-table/odata'` →
35
+ `import { useODataSource } from '@iyulab/flex-table/react'` (same for `useArraySource` from
36
+ `./array`).
37
+
38
+ - 🔴 **Breaking — column definitions use the same words as `u-rich-table`.** `header` → `label` and
39
+ `renderer` → `render`, so one column list reads the same in either table (`header-context-menu`'s detail likewise carries
40
+ `label` instead of `header`). Migration: rename the two keys
41
+ in your `ColumnDefinition` objects; the types reject the old names.
42
+ - **Headers follow their values.** A header's default alignment is now the column's cell alignment,
43
+ so a `number` column's header is right-aligned over its right-aligned values instead of sitting
44
+ at the left edge (on a wide column it read as the neighbour's header). Set `headerAlign` to keep
45
+ a different header alignment. Cell alignment uses logical values (`text-align: end`), so
46
+ right-to-left locales mirror.
47
+
48
+ - **The column menu, the cell context menu and the drag ghost read the shared stacking tokens.**
49
+ The menus sit on `--u-layer-floating` (1000 — they were at 200, below every other floating
50
+ surface) and the drag ghost on `--u-layer-overlay` (9999, as before), so they stack the same way
51
+ as popovers and dialogs from `@iyulab/components`.
52
+
53
+ ### Added
54
+
55
+ - **`align` on `ColumnDefinition`** (`'start' | 'center' | 'end'`) — cell alignment. Default comes
56
+ from `type` as before (`number` → end, `boolean` → center, otherwise start); set it for a code or
57
+ Y/N column that is text but should center. `effectiveAlign(col)` returns what a column renders with.
58
+ - **`buildODataQuery(state)` on `./odata`** — the step `useODataSource` runs on every request (page,
59
+ page size, sort, search, fixed filter → OData query string), now callable without React. A Lit
60
+ table or a non-table list doing server paging can send exactly the query the hook would, instead
61
+ of re-deriving `$top`/`$skip`/`$orderby`/`$search`. The hook now calls it.
62
+
3
63
  ## [0.39.0] - 2026-09-27
4
64
 
5
65
  ### Added
package/README.md CHANGED
@@ -28,9 +28,9 @@ npm install @iyulab/flex-table
28
28
  const table = document.getElementById('table');
29
29
 
30
30
  table.columns = [
31
- { key: 'name', header: 'Name', type: 'text', width: 200 },
32
- { key: 'age', header: 'Age', type: 'number', width: 100 },
33
- { key: 'active', header: 'Active', type: 'boolean', width: 80 },
31
+ { key: 'name', label: 'Name', type: 'text', width: 200 },
32
+ { key: 'age', label: 'Age', type: 'number', width: 100 },
33
+ { key: 'active', label: 'Active', type: 'boolean', width: 80 },
34
34
  ];
35
35
 
36
36
  table.data = [
@@ -60,7 +60,7 @@ npm install @iyulab/flex-table
60
60
  - **Filter Types** — Text search, number range, boolean toggle, date/datetime range picker
61
61
  - **Row Operations** — `addRow()`, `deleteRows()`, `updateRows()` with undo
62
62
  - **Undo/Redo** — Ctrl+Z / Ctrl+Y for all operations; configurable stack size
63
- - **Export** — CSV, TSV, JSON; full data or selection-only
63
+ - **Export** — CSV, TSV, JSON, XLSX; full data or selection-only
64
64
  - **Dark Theme** — Auto via `prefers-color-scheme`, or manual `theme="dark"`
65
65
  - **Row Numbers** — Optional `show-row-numbers` attribute with sticky positioning
66
66
  - **Footer Row** — Summary/aggregate row via `footer-data` property
@@ -110,10 +110,10 @@ guarantee about a *constrained* host. `height-model.browser.test.ts` pins both s
110
110
  | `selectable` | `selectable` | `boolean` | `false` | Enable row-level checkbox selection |
111
111
  | `selectionMode` | `selection-mode` | `'single' \| 'multi'` | `'multi'` | Row selection mode |
112
112
  | `dataMode` | `data-mode` | `'client' \| 'server'` | `'client'` | Client-side or server-side data processing |
113
- | `footerData` | `footer-data` | `Record<string, string>` | `null` | Footer/summary row data (keys match column keys) |
113
+ | `footerData` | `footer-data` | `Record<string, string \| TemplateResult> \| null` | `null` | Footer/summary row data (keys match column keys) |
114
114
  | `emptyMessage` | `empty-message` | `string` | `'No data'` | Shown when `data` is empty |
115
115
  | `noMatchingMessage` | `no-matching-message` | `string` | `'No matching data'` | Shown when `data` has rows but every one is hidden by an active column filter |
116
- | `stylesheets` | — | `CSSStyleSheet[]` | `[]` | Constructable stylesheets adopted into the shadow root alongside the grid's own styles — the escape hatch for styling content a `renderer` inserts, since document CSS doesn't cross the shadow boundary. Reassigning swaps the previous set, it doesn't accumulate |
116
+ | `stylesheets` | — | `CSSStyleSheet[]` | `[]` | Constructable stylesheets adopted into the shadow root alongside the grid's own styles — the escape hatch for styling content a `render` function inserts, since document CSS doesn't cross the shadow boundary. Reassigning swaps the previous set, it doesn't accumulate |
117
117
 
118
118
  ### Read-only Properties
119
119
 
@@ -133,19 +133,20 @@ guarantee about a *constrained* host. `height-model.browser.test.ts` pins both s
133
133
  ```typescript
134
134
  interface ColumnDefinition {
135
135
  key: string; // Unique key matching data property names
136
- header: string; // Display header text
136
+ label: string; // Column header text
137
137
  type?: ColumnType; // 'text' | 'number' | 'boolean' | 'date' | 'datetime' | 'select' (any other string falls back to 'text')
138
138
  width?: number; // Column width in pixels (default: auto)
139
139
  minWidth?: number; // Minimum width in pixels (default: 40, enforced in rendering)
140
140
  hidden?: boolean; // Hide column from view
141
141
  sortable?: boolean; // Enable sorting (default: true)
142
- headerAlign?: 'start' | 'center' | 'end'; // Header label alignment (default: 'start'), independent of cell content alignment
142
+ align?: 'start' | 'center' | 'end'; // Cell alignment (default from type: number → end, boolean → center, else start)
143
+ headerAlign?: 'start' | 'center' | 'end'; // Header alignment (default: the cell alignment above)
143
144
  editable?: boolean; // Per-column edit control (follows global editable)
144
145
  pinned?: 'left' | 'right'; // Freeze column during horizontal scroll
145
146
  options?: string[] | SelectOption[]; // Allowed values for type: 'select' (SelectOption = { label, value })
146
147
  autocomplete?: boolean | 'strict'; // Suggest existing column values while editing; 'strict' rejects values not in the list
147
- format?: string | ((value, row, col) => string); // Display format, see "format vs renderer" below
148
- renderer?: CellRenderer; // Custom cell render: (value, row, col) => TemplateResult | string
148
+ format?: string | ((value, row, col) => string); // Display format, see "format vs render" below
149
+ render?: CellRenderer; // Custom cell render: (value, row, col) => TemplateResult | string
149
150
  editor?: CellEditor; // Custom cell editor: (value, row, col) => TemplateResult
150
151
  validator?: CellValidator; // Validate before commit: (value, row, col) => string | null
151
152
  conditionalRules?: ConditionalRule[]; // Per-cell style rules, see below
@@ -156,32 +157,32 @@ The `editor` callback must return a Lit `TemplateResult` containing an input ele
156
157
 
157
158
  The `validator` callback returns `null` if valid, or an error message string. On failure, the cell shows a red border for 3 seconds and a `validation-error` event is dispatched.
158
159
 
159
- ### `format` vs `renderer`
160
+ ### `format` vs `render`
160
161
 
161
162
  Both control how a cell's raw value is displayed, but they differ in what they replace:
162
163
 
163
- - **`format`**: a plain string pattern (Excel-style, e.g. `'#,##0.00'`, `'0.00%'`, `'$#,##0'`, `'yyyy-MM-dd'`) or a `(value) => string` function. Only the *displayed text* changes — editing, sorting, filtering, and export all keep operating on the raw underlying value. Use this for number/date/currency display formatting.
164
- - **`renderer`**: a `(value, row, col) => TemplateResult | string` function that replaces the cell's rendered content entirely — badges, links, icons, multi-field composites. Sorting/filtering still use the raw value, but the visual output is fully custom.
164
+ - **`format`**: a plain string pattern (Excel-style, e.g. `'#,##0.00'`, `'0.00%'`, `'$#,##0'`, `'yyyy-MM-dd'`) or a `(value, row, col) => string` function. Only the *displayed text* changes — editing, sorting, filtering, and export all keep operating on the raw underlying value. Use this for number/date/currency display formatting.
165
+ - **`render`**: a `(value, row, col) => TemplateResult | string` function that replaces the cell's rendered content entirely — badges, links, icons, multi-field composites. Sorting/filtering still use the raw value, but the visual output is fully custom.
165
166
 
166
167
  ```typescript
167
168
  const columns: ColumnDefinition<Order>[] = [
168
- { key: 'total', header: 'Total', format: '#,##0.00' }, // "1,234.50"
169
- { key: 'placedAt', header: 'Placed', format: 'yyyy-MM-dd' }, // date pattern
170
- { key: 'status', header: 'Status', renderer: (v) => html`<span class="badge badge-${v}">${v}</span>` },
169
+ { key: 'total', label: 'Total', format: '#,##0.00' }, // "1,234.50"
170
+ { key: 'placedAt', label: 'Placed', format: 'yyyy-MM-dd' }, // date pattern
171
+ { key: 'status', label: 'Status', render: (v) => html`<span class="badge badge-${v}">${v}</span>` },
171
172
  ];
172
173
  ```
173
174
 
174
- If both are set on the same column, `renderer` takes precedence — `format` has no effect once a custom `renderer` fully controls the cell's output.
175
+ If both are set on the same column, `render` takes precedence — `format` has no effect once a custom `render` fully controls the cell's output.
175
176
 
176
177
  ### Conditional Formatting
177
178
 
178
- `conditionalRules` applies a style to a cell when its `when` predicate matches — a declarative alternative to writing a `renderer` just to color-code status/threshold values:
179
+ `conditionalRules` applies a style to a cell when its `when` predicate matches — a declarative alternative to writing a `render` function just to color-code status/threshold values:
179
180
 
180
181
  ```typescript
181
182
  const columns: ColumnDefinition<Order>[] = [
182
183
  {
183
184
  key: 'status',
184
- header: 'Status',
185
+ label: 'Status',
185
186
  conditionalRules: [
186
187
  { when: (v) => v === 'overdue', style: { color: '#dc2626', fontWeight: 'bold' } },
187
188
  { when: (v) => v === 'paid', style: { color: '#16a34a' } },
@@ -241,7 +242,7 @@ Default is `false`, matching `clear-undo-on-data-change`.
241
242
 
242
243
  | Method | Returns | Description |
243
244
  |--------|---------|-------------|
244
- | `exportToString(format, options?)` | `string` | Export to `'csv'` / `'tsv'` / `'json'`. Pass `{ selectionOnly: true }` for selection range |
245
+ | `exportToString(format, options?)` | `string \| Uint8Array` | Export to `'csv'` / `'tsv'` / `'json'` (a string) or `'xlsx'` (bytes). Pass `{ selectionOnly: true }` for selection range |
245
246
  | `exportToFile(format, filename?)` | `void` | Export and trigger browser file download |
246
247
 
247
248
  ## Events
@@ -252,7 +253,7 @@ All events use `CustomEvent` with `bubbles: true, composed: true`.
252
253
 
253
254
  | Event | Detail | Description |
254
255
  |-------|--------|-------------|
255
- | `cell-select` | `{ row, col }` | Cell focus changed |
256
+ | `cell-select` | `{ row, col }` or `null` | Cell focus changed (`null` when no cell is active) |
256
257
  | `cell-edit-start` | `{ row, col, key, value }` | Cell editing started |
257
258
  | `cell-edit-commit` | `{ row, col, key, oldValue, newValue }` | Cell value committed |
258
259
  | `cell-edit-cancel` | `{ row, col }` | Cell edit cancelled (Escape) |
@@ -305,7 +306,7 @@ All events use `CustomEvent` with `bubbles: true, composed: true`.
305
306
  | Event | Detail | Description |
306
307
  |-------|--------|-------------|
307
308
  | `undo-state-change` | `{ canUndo, canRedo }` | Undo/redo availability changed |
308
- | `context-menu` | `{ x, y, row, col, dataRow, column }` | Right-click on cell |
309
+ | `context-menu` | `{ x, y, row, col, key, value, rowData }` | Right-click on cell. Cancelable — `preventDefault()` suppresses the built-in menu |
309
310
 
310
311
  ## CSS Custom Properties
311
312
 
@@ -489,8 +490,8 @@ import { FlexTableReact } from '@iyulab/flex-table/react';
489
490
 
490
491
  function App() {
491
492
  const columns = [
492
- { key: 'name', header: 'Name', type: 'text' },
493
- { key: 'age', header: 'Age', type: 'number' },
493
+ { key: 'name', label: 'Name', type: 'text' },
494
+ { key: 'age', label: 'Age', type: 'number' },
494
495
  ];
495
496
 
496
497
  const data = [
@@ -547,8 +548,8 @@ interface Order {
547
548
  }
548
549
 
549
550
  const columns: ColumnDefinition<Order>[] = [
550
- { key: 'id', header: 'ID' },
551
- { key: 'total', header: 'Total', renderer: (_value, row) => `${row.total} ${row.currency}` },
551
+ { key: 'id', label: 'ID' },
552
+ { key: 'total', label: 'Total', render: (_value, row) => `${row.total} ${row.currency}` },
552
553
  ];
553
554
 
554
555
  <FlexTableReact<Order> data={orders} columns={columns} />
@@ -566,7 +567,7 @@ import { html } from 'lit';
566
567
  table.columns = [
567
568
  {
568
569
  key: 'color',
569
- header: 'Color',
570
+ label: 'Color',
570
571
  type: 'text',
571
572
  editor: (value) => html`
572
573
  <input class="ft-editor" type="color" .value=${String(value ?? '#000000')}
@@ -593,7 +594,7 @@ Use the `validator` callback to validate input before committing. Returns `null`
593
594
  table.columns = [
594
595
  {
595
596
  key: 'age',
596
- header: 'Age',
597
+ label: 'Age',
597
598
  type: 'number',
598
599
  validator: (value) => {
599
600
  const n = Number(value);
@@ -612,10 +613,10 @@ Freeze columns on either side during horizontal scroll:
612
613
 
613
614
  ```typescript
614
615
  table.columns = [
615
- { key: 'id', header: 'ID', pinned: 'left' },
616
- { key: 'name', header: 'Name' },
616
+ { key: 'id', label: 'ID', pinned: 'left' },
617
+ { key: 'name', label: 'Name' },
617
618
  // ... many columns ...
618
- { key: 'actions', header: 'Actions', pinned: 'right' },
619
+ { key: 'actions', label: 'Actions', pinned: 'right' },
619
620
  ];
620
621
  ```
621
622
 
@@ -668,7 +669,7 @@ table.addEventListener('sort-change', (e) => {
668
669
  `useODataSource(url, options)` fetches paginated/sorted/filtered data from an OData v4 endpoint and returns props ready to bind to `<FlexTableReact dataMode="server" ...>`.
669
670
 
670
671
  ```tsx
671
- import { useODataSource } from '@iyulab/flex-table/odata';
672
+ import { useODataSource } from '@iyulab/flex-table/react';
672
673
 
673
674
  const source = useODataSource('/api/orders', {
674
675
  pageSize: 20,
@@ -760,7 +761,7 @@ The hook returns:
760
761
 
761
762
  Terms are always quoted because OData 4.0 only allows letters in an unquoted `searchWord`, so `2026` or `ZT-E2E-A` would be rejected by servers that follow it (4.01 relaxed this, but [Microsoft.OData still lexes as 4.0](https://github.com/OData/odata.net/issues/2445)). Quoting keeps any term valid regardless of server version. Since a `$search` phrase cannot contain `"` and OData defines no escape for it, double quotes are stripped from the term.
762
763
 
763
- The quoting/escaping logic above is also available standalone as `buildSearchExpression(term)`, for consumers that need the same `$search` encoding without the pagination hook (e.g. a typeahead/combobox that isn't a table). `parseOrderBy(orderBy)` (`'a asc, b desc'` → `SortCriteria[]`) is exported the same way, for consumers driving a sort UI that isn't `useODataSource` either:
764
+ The quoting/escaping logic above is also available standalone as `buildSearchExpression(term)`, for consumers that need the same `$search` encoding without the pagination hook (e.g. a typeahead/combobox that isn't a table). `parseOrderBy(orderBy)` (`'a asc, b desc'` → `SortCriteria[]`) is exported the same way, for consumers driving a sort UI that isn't `useODataSource` either. The `./odata` entry holds only pure functions and does not load React, so an app without React can use it (the hooks live on `./react`):
764
765
 
765
766
  ```ts
766
767
  import { buildSearchExpression, parseOrderBy } from '@iyulab/flex-table/odata';
@@ -770,6 +771,20 @@ buildSearchExpression(''); // undefined
770
771
  parseOrderBy('name desc'); // [{ key: 'name', direction: 'desc' }]
771
772
  ```
772
773
 
774
+ `buildODataQuery(state)` is the step the hook runs on every request — table state in, OData query string out. Use it to drive server paging without React (a Lit table, a list that is not a table) with exactly the query the hook would send:
775
+
776
+ ```ts
777
+ import { buildODataQuery } from '@iyulab/flex-table/odata';
778
+
779
+ buildODataQuery({
780
+ page: 2, pageSize: 20, // 0-based page → $skip=40
781
+ sortCriteria: [{ key: 'name', direction: 'desc' }], // else defaultOrderBy
782
+ search: 'red shirt', // encoded by buildSearchExpression
783
+ fixedFilter: { IsActive: true }, // odata-query filter object
784
+ });
785
+ // '?$filter=IsActive eq true&$orderby=name desc&$count=true&$top=20&$skip=40&$search=%22red%22%20AND%20%22shirt%22'
786
+ ```
787
+
773
788
  ### Array Source Hook (React)
774
789
 
775
790
  `useArraySource(data, options)` runs search/sort/pagination over an in-memory array and
@@ -788,7 +803,7 @@ e.g. a lookup table whose display name lives on a different endpoint than the ro
788
803
  so search/sort has to run after the join, in memory:
789
804
 
790
805
  ```tsx
791
- import { useArraySource } from '@iyulab/flex-table/array';
806
+ import { useArraySource } from '@iyulab/flex-table/react';
792
807
 
793
808
  const joined = useMemo(
794
809
  () => seasonPrices.map(p => ({ ...p, productName: productsById[p.productId]?.name ?? '' })),
@@ -1,3 +1,2 @@
1
- export { useArraySource, computeArrayView } from './use-array-source.js';
2
- export type { UseArraySourceOptions, UseArraySourceResult } from './types.js';
3
- export type { ComputeArrayViewOptions } from './use-array-source.js';
1
+ export { computeArrayView } from './view.js';
2
+ export type { ComputeArrayViewOptions } from './view.js';
@@ -1,56 +1,2 @@
1
- import { t as e } from "../sorting-CjfjRxwL.js";
2
- import { r as t } from "../use-odata-source-BiZrIEVV.js";
3
- import { useCallback as n, useMemo as r, useState as i } from "react";
4
- //#region src/array/use-array-source.ts
5
- function a(t, { search: n, sortCriteria: r, page: i, pageSize: a, columns: o, searchFields: s }) {
6
- let c = n.trim().toLowerCase(), l = c ? t.filter((e) => (s ? s(e) : Object.values(e)).some((e) => e != null && String(e).toLowerCase().includes(c))) : t, u = l;
7
- r.length > 0 && (u = e(l, r, o ?? []).map((e) => l[e]));
8
- let d = u.length, f = d === 0 ? 0 : Math.ceil(d / a) - 1, p = Math.min(Math.max(i, 0), f) * a;
9
- return {
10
- data: u.slice(p, p + a),
11
- totalCount: d
12
- };
13
- }
14
- function o(e, o = {}) {
15
- let { pageSize: s = 20, defaultOrderBy: c, initialPage: l = 0, initialSearch: u = "", initialSort: d, columns: f, searchFields: p } = o, m = t({
16
- defaultOrderBy: c,
17
- initialPage: l,
18
- initialSearch: u,
19
- initialSort: d
20
- }), [h, g] = i(m.page), [_, v] = i(m.sortCriteria), [y, b] = i(m.search), x = n((e) => {
21
- b(e), g(0);
22
- }, []), S = n((e) => {
23
- let t = e.detail?.criteria;
24
- t && (v(t), g(0));
25
- }, []), C = n(() => {}, []), { data: w, totalCount: T } = r(() => a(e, {
26
- search: y,
27
- sortCriteria: _,
28
- page: h,
29
- pageSize: s,
30
- columns: f,
31
- searchFields: p
32
- }), [
33
- e,
34
- y,
35
- _,
36
- h,
37
- s,
38
- f,
39
- p
40
- ]), E = T === 0 ? 0 : Math.ceil(T / s) - 1;
41
- return h > E && g(E), {
42
- data: w,
43
- totalCount: T,
44
- loading: !1,
45
- error: null,
46
- page: h,
47
- setPage: g,
48
- sortCriteria: _,
49
- onSortChange: S,
50
- search: y,
51
- setSearch: x,
52
- refresh: C
53
- };
54
- }
55
- //#endregion
56
- export { a as computeArrayView, o as useArraySource };
1
+ import { t as e } from "../view-DiLr-TdY.js";
2
+ export { e as computeArrayView };
@@ -1,28 +1,5 @@
1
- import type { SortCriteria } from '../core/sorting.js';
2
- import type { ColumnDefinition, DataRow } from '../models/types.js';
1
+ import type { DataRow } from '../models/types.js';
3
2
  import type { UseArraySourceOptions, UseArraySourceResult } from './types.js';
4
- export interface ComputeArrayViewOptions<T> {
5
- search: string;
6
- sortCriteria: SortCriteria[];
7
- page: number;
8
- pageSize: number;
9
- columns?: ColumnDefinition<T>[];
10
- searchFields?: (row: T) => Array<string | number | boolean | null | undefined>;
11
- }
12
- /**
13
- * `useArraySource`의 검색→정렬→페이지 파이프라인을 React 없이 순수하게 계산한다
14
- * (훅은 이 함수를 `useMemo`로 감싸기만 한다) — React 훅 렌더 인프라 없이 이 패키지의
15
- * 다른 순수 헬퍼(`buildSearchExpression`/`parseOrderBy`)와 같은 방식으로 단위
16
- * 테스트하기 위해 분리했다.
17
- *
18
- * 정렬은 그리드 자신의 클라이언트 정렬 파이프라인이 쓰는 `computeSortedIndices`를
19
- * 그대로 재사용한다 — `columns`를 넘기면 숫자/날짜/불리언도 값 기준으로 비교되고,
20
- * 생략하면 전부 텍스트 비교로 낮아진다.
21
- */
22
- export declare function computeArrayView<T extends DataRow>(data: T[], { search, sortCriteria, page, pageSize, columns, searchFields }: ComputeArrayViewOptions<T>): {
23
- data: T[];
24
- totalCount: number;
25
- };
26
3
  /**
27
4
  * 클라이언트 배열(이미 메모리에 있는 데이터, 흔히 여러 출처를 조인해 만든 파생
28
5
  * 행 배열) 전용 React 훅. `useODataSource`와 **같은 반환 형태**(`data`/`totalCount`/
@@ -0,0 +1,24 @@
1
+ import type { SortCriteria } from '../core/sorting.js';
2
+ import type { ColumnDefinition, DataRow } from '../models/types.js';
3
+ export interface ComputeArrayViewOptions<T> {
4
+ search: string;
5
+ sortCriteria: SortCriteria[];
6
+ page: number;
7
+ pageSize: number;
8
+ columns?: ColumnDefinition<T>[];
9
+ searchFields?: (row: T) => Array<string | number | boolean | null | undefined>;
10
+ }
11
+ /**
12
+ * `useArraySource`의 검색→정렬→페이지 파이프라인을 React 없이 순수하게 계산한다
13
+ * (훅은 이 함수를 `useMemo`로 감싸기만 한다) — React 훅 렌더 인프라 없이 이 패키지의
14
+ * 다른 순수 헬퍼(`buildSearchExpression`/`parseOrderBy`)와 같은 방식으로 단위
15
+ * 테스트하기 위해 분리했다.
16
+ *
17
+ * 정렬은 그리드 자신의 클라이언트 정렬 파이프라인이 쓰는 `computeSortedIndices`를
18
+ * 그대로 재사용한다 — `columns`를 넘기면 숫자/날짜/불리언도 값 기준으로 비교되고,
19
+ * 생략하면 전부 텍스트 비교로 낮아진다.
20
+ */
21
+ export declare function computeArrayView<T extends DataRow>(data: T[], { search, sortCriteria, page, pageSize, columns, searchFields }: ComputeArrayViewOptions<T>): {
22
+ data: T[];
23
+ totalCount: number;
24
+ };