@iyulab/flex-table 0.29.0 → 0.31.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,42 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.31.1] - 2026-09-01
4
+
5
+ ### Fixed
6
+
7
+ - **`0.31.0`'s release pipeline never actually published to npm** — the repo's
8
+ `preversion` guard script assumed a monorepo layout this repo doesn't have,
9
+ so the workflow's own version-sync step failed before publishing. No package
10
+ code changed; this release exists to get `0.31.0`'s content onto the
11
+ registry with a working pipeline.
12
+
13
+ ## [0.31.0] - 2026-08-31
14
+
15
+ ### Added
16
+
17
+ - **`useArraySource`, a client-array sibling to `useODataSource`.** `useODataSource`
18
+ is server-mode only, so a lookup table needing a client-side join (a display
19
+ field living on a different endpoint than the row) had no library-level path
20
+ and required reimplementing filter/sort/pagination by hand. `useArraySource`
21
+ returns the exact same shape (`data`/`totalCount`/`loading`/`error`/`page`/
22
+ `setPage`/`sortCriteria`/`onSortChange`/`search`/`setSearch`/`refresh`) driven
23
+ off a local array instead of a fetch, so the same `<FlexTableReact
24
+ dataMode="server" ...>` binding code works with either source. Sorting reuses
25
+ the grid's own `computeSortedIndices` for value-aware (not string) comparison.
26
+ New `./array` subpath export, mirroring `./odata`'s shape.
27
+
28
+ ## [0.30.0] - 2026-08-28
29
+
30
+ ### Added
31
+
32
+ - **`buildSearchExpression` and `parseOrderBy` are now exported from
33
+ `./odata`.** Both were already implemented as pure, tested functions used
34
+ internally by `useODataSource`, but the public barrel only re-exported
35
+ the hook itself. A consumer needing the same `$search`/`$orderby`
36
+ encoding without the pagination hook (a typeahead, a standalone sort
37
+ control) had to reimplement the OData 4.0 search-quoting workaround
38
+ from scratch.
39
+
3
40
  ## [0.29.0] - 2026-08-28
4
41
 
5
42
  ### Added
package/README.md CHANGED
@@ -606,6 +606,53 @@ The hook returns:
606
606
 
607
607
  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.
608
608
 
609
+ 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:
610
+
611
+ ```ts
612
+ import { buildSearchExpression, parseOrderBy } from '@iyulab/flex-table/odata';
613
+
614
+ buildSearchExpression('red shirt'); // '"red" AND "shirt"'
615
+ buildSearchExpression(''); // undefined
616
+ parseOrderBy('name desc'); // [{ key: 'name', direction: 'desc' }]
617
+ ```
618
+
619
+ ### Array Source Hook (React)
620
+
621
+ `useArraySource(data, options)` runs search/sort/pagination over an in-memory array and
622
+ returns the **same shape** as `useODataSource` — `data`/`totalCount`/`loading`/`error`/
623
+ `page`/`setPage`/`sortCriteria`/`onSortChange`/`search`/`setSearch`/`refresh` — so the
624
+ same `<FlexTableReact dataMode="server" ...>` binding code works with either source.
625
+
626
+ Reach for this when the rows come from a client-side join a server query can't express —
627
+ e.g. a lookup table whose display name lives on a different endpoint than the row itself,
628
+ so search/sort has to run after the join, in memory:
629
+
630
+ ```tsx
631
+ import { useArraySource } from '@iyulab/flex-table/array';
632
+
633
+ const joined = useMemo(
634
+ () => seasonPrices.map(p => ({ ...p, productName: productsById[p.productId]?.name ?? '' })),
635
+ [seasonPrices, productsById]
636
+ );
637
+
638
+ const source = useArraySource(joined, {
639
+ pageSize: 20,
640
+ columns, // pass the same ColumnDefinition[] used by <FlexTableReact> for value-aware sort
641
+ });
642
+ ```
643
+
644
+ | Option | Default | Description |
645
+ |---|---|---|
646
+ | `pageSize` | `20` | Rows per page |
647
+ | `defaultOrderBy` | — | Initial sort (e.g. `'name asc'`), same syntax as `useODataSource` |
648
+ | `columns` | — | `ColumnDefinition[]` — enables value-aware sort (numbers/dates/booleans compared by value, not as text). Omit and every column sorts as text. |
649
+ | `searchFields` | all values | `(row) => value[]` — narrows or widens what free-text search matches; the default searches every property on the row, including client-joined ones |
650
+
651
+ The returned fields mean the same thing as `useODataSource`'s, except `totalCount` is the
652
+ count after search (not a server-reported total), and `loading`/`error` are always
653
+ `false`/`null` — there's no request to fail. `refresh` is a no-op kept only so a
654
+ "refresh" button wired unconditionally against either hook doesn't need a branch.
655
+
609
656
  ## Development
610
657
 
611
658
  ```bash
@@ -0,0 +1,3 @@
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';
@@ -0,0 +1,51 @@
1
+ import { t as e } from "../sorting-CjfjRxwL.js";
2
+ import { n as t } from "../use-odata-source-5gOJiMFj.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 = i * a;
9
+ return {
10
+ data: u.slice(f, f + a),
11
+ totalCount: d
12
+ };
13
+ }
14
+ function o(e, o = {}) {
15
+ let { pageSize: s = 20, defaultOrderBy: c, columns: l, searchFields: u } = o, [d, f] = i(0), [p, m] = i(() => c ? t(c) : []), [h, g] = i(""), _ = n((e) => {
16
+ g(e), f(0);
17
+ }, []), v = n((e) => {
18
+ let t = e.detail?.criteria;
19
+ t && (m(t), f(0));
20
+ }, []), y = n(() => {}, []), { data: b, totalCount: x } = r(() => a(e, {
21
+ search: h,
22
+ sortCriteria: p,
23
+ page: d,
24
+ pageSize: s,
25
+ columns: l,
26
+ searchFields: u
27
+ }), [
28
+ e,
29
+ h,
30
+ p,
31
+ d,
32
+ s,
33
+ l,
34
+ u
35
+ ]);
36
+ return {
37
+ data: b,
38
+ totalCount: x,
39
+ loading: !1,
40
+ error: null,
41
+ page: d,
42
+ setPage: f,
43
+ sortCriteria: p,
44
+ onSortChange: v,
45
+ search: h,
46
+ setSearch: _,
47
+ refresh: y
48
+ };
49
+ }
50
+ //#endregion
51
+ export { a as computeArrayView, o as useArraySource };
@@ -0,0 +1,39 @@
1
+ import type { SortCriteria } from '../core/sorting.js';
2
+ export interface UseArraySourceOptions<T> {
3
+ /** 페이지당 행 수. `useODataSource`와 동일 기본값. */
4
+ pageSize?: number;
5
+ /** 초기 정렬(`'a asc, b desc'` 형식, `useODataSource`와 동일 문법). */
6
+ defaultOrderBy?: string;
7
+ /**
8
+ * 타입 인지 정렬 비교(숫자/불리언/날짜/텍스트)에 쓸 컬럼 정의. 생략하면 전부
9
+ * 텍스트로 비교한다(숫자 컬럼도 문자열 정렬 순서를 따름) — 그리드에 이미 넘기는
10
+ * `columns`를 그대로 전달하면 된다.
11
+ */
12
+ columns?: import('../models/types.js').ColumnDefinition<T>[];
13
+ /**
14
+ * 자유 텍스트 검색이 대조할 값들을 행에서 뽑아낸다. 생략하면 행의 모든 값
15
+ * (`Object.values`)을 대상으로 한다 — 서버 read 모델에 없는 클라이언트 조인
16
+ * 파생 필드(예: 조인해 붙인 상품명)도 이 행 객체에 실제로 들어있는 한 기본
17
+ * 동작만으로 검색된다. 명시적으로 검색 범위를 좁히거나 넓히고 싶을 때만 지정.
18
+ */
19
+ searchFields?: (row: T) => Array<string | number | boolean | null | undefined>;
20
+ }
21
+ export interface UseArraySourceResult<T> {
22
+ /** 현재 페이지의 행(검색+정렬 적용 후 slice). */
23
+ data: T[];
24
+ /** 검색+정렬 적용 후, 페이지 나누기 전의 총 건수(`useODataSource`의 `@odata.count`와 동일 의미). */
25
+ totalCount: number;
26
+ /** 항상 `false` — 로컬 배열은 동기 처리라 로딩 상태가 없다. `useODataSource`와의 반환 형태 동일성을 위해 유지. */
27
+ loading: boolean;
28
+ /** 항상 `null` — 로컬 배열 처리는 실패하지 않는다. 위와 같은 이유로 유지. */
29
+ error: string | null;
30
+ page: number;
31
+ setPage: (page: number) => void;
32
+ sortCriteria: SortCriteria[];
33
+ onSortChange: (e: CustomEvent) => void;
34
+ setSearch: (term: string) => void;
35
+ search: string;
36
+ /** no-op — 로컬 배열에는 다시 불러올 원격 상태가 없다. 소비자가 `useODataSource`와
37
+ * 같은 자리에 무조건 배선한 refresh 버튼이 있어도 안전하게 아무 일도 하지 않는다. */
38
+ refresh: () => void;
39
+ }
@@ -0,0 +1,39 @@
1
+ import type { SortCriteria } from '../core/sorting.js';
2
+ import type { ColumnDefinition, DataRow } from '../models/types.js';
3
+ 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
+ /**
27
+ * 클라이언트 배열(이미 메모리에 있는 데이터, 흔히 여러 출처를 조인해 만든 파생
28
+ * 행 배열) 전용 React 훅. `useODataSource`와 **같은 반환 형태**(`data`/`totalCount`/
29
+ * `loading`/`error`/`page`/`setPage`/`sortCriteria`/`onSortChange`/`search`/
30
+ * `setSearch`/`refresh`)를 제공해, `dataMode="server"` + `<FlexTableReact>` 배선
31
+ * 코드를 서버·클라이언트 소스 사이에서 그대로 재사용할 수 있게 한다.
32
+ *
33
+ * `useODataSource`가 서버에 `$search`/`$orderby`/`$top`/`$skip`을 보내 처리를
34
+ * 위임하는 것과 달리, 이 훅은 검색·정렬·페이지 나누기를 전부 로컬에서 계산한다
35
+ * (`computeArrayView`) — 서버 read 모델에 없는 클라이언트 조인 파생 필드(예: 별도
36
+ * 엔드포인트에서 가져와 붙인 상품명)로 검색/정렬해야 해서 서버 쿼리로 표현할 수
37
+ * 없는 lookup 테이블에 쓴다.
38
+ */
39
+ export declare function useArraySource<T extends DataRow = DataRow>(data: T[], options?: UseArraySourceOptions<T>): UseArraySourceResult<T>;
@@ -0,0 +1 @@
1
+ export {};