@iyulab/flex-table 0.31.5 → 0.33.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/CHANGELOG.md +70 -0
- package/README.md +45 -1
- package/dist/array/index.js +33 -28
- package/dist/array/types.d.ts +10 -0
- package/dist/array/use-array-source.hooks.test.d.ts +1 -0
- package/dist/odata/index.js +2 -2
- package/dist/odata/types.d.ts +16 -0
- package/dist/odata/use-odata-source.d.ts +28 -0
- package/dist/odata/use-odata-source.hooks.test.d.ts +1 -0
- package/dist/use-odata-source-6fVz-CLu.js +102 -0
- package/package.json +3 -1
- package/dist/use-odata-source-5gOJiMFj.js +0 -83
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,75 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.33.0] - 2026-09-08
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **A page could outlive the result set that holds it, leaving an empty table next
|
|
8
|
+
to a non-zero total and a pager pointing at a page that no longer exists.**
|
|
9
|
+
Three axes change the size of a result set, and only two of them moved the page.
|
|
10
|
+
|
|
11
|
+
- **`useODataSource`: changing `fixedFilter` now resets the page to `0`**, the
|
|
12
|
+
same way `setSearch` and `onSortChange` already did. Narrowing a filter while
|
|
13
|
+
on page 5 previously kept the old `$skip` and asked for a range the new set
|
|
14
|
+
does not have. Comparison is by value, so passing a fresh object literal on
|
|
15
|
+
every render does not reset anything on its own, and the reset does not run on
|
|
16
|
+
mount, so it never overrides `initialPage`.
|
|
17
|
+
- **`useODataSource`: a response reporting fewer rows than the current page needs
|
|
18
|
+
falls back to the last page that exists.** A result set can shrink without
|
|
19
|
+
being asked. Reaching that state costs one extra request, only in that case;
|
|
20
|
+
the page only ever moves down, so it cannot loop.
|
|
21
|
+
- **`useArraySource`: paging is clamped to the array passed in.** Shrinking the
|
|
22
|
+
array — the in-memory equivalent of narrowing a filter — falls back to the last
|
|
23
|
+
page that exists instead of rendering nothing.
|
|
24
|
+
|
|
25
|
+
Shrinking clamps rather than resets on purpose: an array or a total can change on
|
|
26
|
+
a routine refresh, and resetting there would move the page out from under the
|
|
27
|
+
reader. The dividing question is whether the change was asked for.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- **`computeArrayView` returns the last page that exists when given a page past the
|
|
32
|
+
end**, instead of an empty array. It is exported, so a direct caller had the same
|
|
33
|
+
defect; returning nothing for an out-of-range page was never the intended
|
|
34
|
+
contract.
|
|
35
|
+
|
|
36
|
+
### Internal
|
|
37
|
+
|
|
38
|
+
- First hook tests for this package — everything here was pure functions until now,
|
|
39
|
+
so contracts the README states had no executed evidence behind them. They cover
|
|
40
|
+
the reset and clamp on both hooks, request abort on re-request and on unmount,
|
|
41
|
+
`onUnauthorized` firing on `401`/`403` but not on `500`, initial state being read
|
|
42
|
+
on first render only, and `refresh` being a no-op on the array source.
|
|
43
|
+
|
|
44
|
+
## [0.32.0] - 2026-09-08
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
|
|
48
|
+
- **Both source hooks accept their initial page, search term and sort.**
|
|
49
|
+
`useODataSource` and `useArraySource` now take `initialPage` (zero-based, the same
|
|
50
|
+
axis as the returned `page`/`setPage`), `initialSearch` and `initialSort`. They are
|
|
51
|
+
read on the first render only — the same contract `defaultOrderBy` has always had —
|
|
52
|
+
and every default matches today's behaviour exactly, so existing callers are
|
|
53
|
+
unaffected.
|
|
54
|
+
|
|
55
|
+
Restoring a list the way the user left it — returning from a detail screen, or
|
|
56
|
+
restoring from a URL — was previously only expressible as a mount effect calling
|
|
57
|
+
`setPage()` after the hook had already fetched page 0. That shape carries two problems
|
|
58
|
+
a caller cannot solve from outside: the first request is issued and then discarded,
|
|
59
|
+
and `setSearch()` resets the page by design, so "restore the page, then set the search
|
|
60
|
+
term" has no ordering that works.
|
|
61
|
+
|
|
62
|
+
`initialSort` takes precedence over `defaultOrderBy` — the two express the same thing
|
|
63
|
+
in different notations, and the array form is the shape `onSortChange` hands back, so a
|
|
64
|
+
stored sort round-trips without being re-serialized. An empty `initialSort: []` means
|
|
65
|
+
*no sort* and does not fall back to `defaultOrderBy`.
|
|
66
|
+
|
|
67
|
+
### Internal
|
|
68
|
+
|
|
69
|
+
- Both hooks now resolve their initial state through one shared function rather than two
|
|
70
|
+
copies, so the two sources cannot drift apart on the shape the README declares they
|
|
71
|
+
share.
|
|
72
|
+
|
|
3
73
|
## [0.31.5] - 2026-09-04
|
|
4
74
|
|
|
5
75
|
### Fixed
|
package/README.md
CHANGED
|
@@ -584,13 +584,50 @@ const source = useODataSource('/api/orders', {
|
|
|
584
584
|
|---|---|---|
|
|
585
585
|
| `pageSize` | `20` | Rows per page |
|
|
586
586
|
| `defaultOrderBy` | — | Initial `$orderby` (e.g. `'name asc'`) |
|
|
587
|
-
| `
|
|
587
|
+
| `initialPage` | `0` | Initial page, **zero-based** — the same axis as the returned `page`/`setPage`, and `$skip` is `page * pageSize` |
|
|
588
|
+
| `initialSearch` | `''` | Initial search term |
|
|
589
|
+
| `initialSort` | — | Initial sort as `SortCriteria[]`. Takes precedence over `defaultOrderBy` — it is the shape `onSortChange` hands you, so a stored sort round-trips without re-serializing it |
|
|
590
|
+
| `fixedFilter` | — | Filter always applied in addition to search. Changing it resets the page to 0 — see below |
|
|
588
591
|
| `baseUrl` | `window.location.origin` | Override the request origin (proxy/BFF setups) |
|
|
589
592
|
| `fetcher` | global `fetch` | Custom transport — pass a wrapper that injects auth headers |
|
|
590
593
|
| `onUnauthorized` | — | Called on `401`/`403` responses, before the generic error is set |
|
|
591
594
|
|
|
592
595
|
`fetcher`/`onUnauthorized` should be stable references (e.g. wrap in `useCallback`) — they are intentionally excluded from the hook's internal effect dependencies to avoid refetch loops on every render.
|
|
593
596
|
|
|
597
|
+
Changing `fixedFilter` resets the page to `0`, the same way `setSearch` and `onSortChange`
|
|
598
|
+
already do. All three change the size of the result set, so keeping the old `$skip` would
|
|
599
|
+
request a range the new set no longer has — a filter applied while on page 5 would come back
|
|
600
|
+
empty. Comparison is by value, not by reference, so passing a fresh object literal on every
|
|
601
|
+
render (`fixedFilter={{ IsActive: true }}`) does not reset anything on its own. The reset
|
|
602
|
+
does not run on mount, so it never overrides `initialPage`.
|
|
603
|
+
|
|
604
|
+
If a response reports fewer rows than the current page needs, the page falls back to the
|
|
605
|
+
last page that exists — a result set can shrink without you asking (another user deleting
|
|
606
|
+
rows, a `refresh` landing after a change), and holding the old `$skip` would leave an empty
|
|
607
|
+
table next to a non-zero total. Reaching that state costs one extra request, only in that
|
|
608
|
+
case; the page only ever moves down, so it cannot loop.
|
|
609
|
+
|
|
610
|
+
The three `initial*` options are read **on the first render only** (the same contract
|
|
611
|
+
`defaultOrderBy` has always had); use `setPage`/`setSearch` to move afterwards. Reach for
|
|
612
|
+
them when a list has to come back the way the user left it — going to a detail screen and
|
|
613
|
+
returning, or restoring from a URL:
|
|
614
|
+
|
|
615
|
+
```tsx
|
|
616
|
+
const source = useODataSource('/api/orders', {
|
|
617
|
+
pageSize: 20,
|
|
618
|
+
initialPage: restored.page, // no mount effect, no discarded first request
|
|
619
|
+
initialSearch: restored.search,
|
|
620
|
+
initialSort: restored.sort,
|
|
621
|
+
});
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
Setting them up front rather than calling `setPage(...)` from a mount effect matters for
|
|
625
|
+
two reasons that are otherwise hard to work around: the effect version issues a request for
|
|
626
|
+
page 0 that is thrown away as soon as the second one lands, and `setSearch` resets the page
|
|
627
|
+
to 0 by design — so "restore the page, then set the search term" is not expressible from
|
|
628
|
+
outside the hook. Passing an empty `initialSort: []` means *no sort*, and does not fall
|
|
629
|
+
back to `defaultOrderBy`.
|
|
630
|
+
|
|
594
631
|
The hook returns:
|
|
595
632
|
|
|
596
633
|
| Field | Description |
|
|
@@ -626,6 +663,12 @@ returns the **same shape** as `useODataSource` — `data`/`totalCount`/`loading`
|
|
|
626
663
|
`page`/`setPage`/`sortCriteria`/`onSortChange`/`search`/`setSearch`/`refresh` — so the
|
|
627
664
|
same `<FlexTableReact dataMode="server" ...>` binding code works with either source.
|
|
628
665
|
|
|
666
|
+
Paging is clamped to the data you pass: if the array shrinks below the current page — the
|
|
667
|
+
in-memory equivalent of narrowing a filter, usually `rows.filter(...)` upstream — the hook
|
|
668
|
+
falls back to the last page that exists instead of rendering an empty table. It only ever
|
|
669
|
+
moves the page down, so an array that changes identity on every poll without changing length
|
|
670
|
+
leaves the current page alone.
|
|
671
|
+
|
|
629
672
|
Reach for this when the rows come from a client-side join a server query can't express —
|
|
630
673
|
e.g. a lookup table whose display name lives on a different endpoint than the row itself,
|
|
631
674
|
so search/sort has to run after the join, in memory:
|
|
@@ -648,6 +691,7 @@ const source = useArraySource(joined, {
|
|
|
648
691
|
|---|---|---|
|
|
649
692
|
| `pageSize` | `20` | Rows per page |
|
|
650
693
|
| `defaultOrderBy` | — | Initial sort (e.g. `'name asc'`), same syntax as `useODataSource` |
|
|
694
|
+
| `initialPage` / `initialSearch` / `initialSort` | `0` / `''` / — | Same names, same meanings, same first-render-only contract as `useODataSource` — the shared shape covers initial state too, so swapping sources needs no other change |
|
|
651
695
|
| `columns` | — | `ColumnDefinition[]` — enables value-aware sort (numbers/dates/booleans compared by value, not as text). Omit and every column sorts as text. |
|
|
652
696
|
| `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 |
|
|
653
697
|
|
package/dist/array/index.js
CHANGED
|
@@ -1,50 +1,55 @@
|
|
|
1
1
|
import { t as e } from "../sorting-CjfjRxwL.js";
|
|
2
|
-
import {
|
|
2
|
+
import { r as t } from "../use-odata-source-6fVz-CLu.js";
|
|
3
3
|
import { useCallback as n, useMemo as r, useState as i } from "react";
|
|
4
4
|
//#region src/array/use-array-source.ts
|
|
5
5
|
function a(t, { search: n, sortCriteria: r, page: i, pageSize: a, columns: o, searchFields: s }) {
|
|
6
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
7
|
r.length > 0 && (u = e(l, r, o ?? []).map((e) => l[e]));
|
|
8
|
-
let d = u.length, f = i * a;
|
|
8
|
+
let d = u.length, f = d === 0 ? 0 : Math.ceil(d / a) - 1, p = Math.min(Math.max(i, 0), f) * a;
|
|
9
9
|
return {
|
|
10
|
-
data: u.slice(
|
|
10
|
+
data: u.slice(p, p + a),
|
|
11
11
|
totalCount: d
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
14
|
function o(e, o = {}) {
|
|
15
|
-
let { pageSize: s = 20, defaultOrderBy: c,
|
|
16
|
-
|
|
17
|
-
|
|
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) => {
|
|
18
23
|
let t = e.detail?.criteria;
|
|
19
|
-
t && (
|
|
20
|
-
}, []),
|
|
21
|
-
search:
|
|
22
|
-
sortCriteria:
|
|
23
|
-
page:
|
|
24
|
+
t && (v(t), g(0));
|
|
25
|
+
}, []), C = n(() => {}, []), { data: w, totalCount: T } = r(() => a(e, {
|
|
26
|
+
search: y,
|
|
27
|
+
sortCriteria: _,
|
|
28
|
+
page: h,
|
|
24
29
|
pageSize: s,
|
|
25
|
-
columns:
|
|
26
|
-
searchFields:
|
|
30
|
+
columns: f,
|
|
31
|
+
searchFields: p
|
|
27
32
|
}), [
|
|
28
33
|
e,
|
|
34
|
+
y,
|
|
35
|
+
_,
|
|
29
36
|
h,
|
|
30
|
-
p,
|
|
31
|
-
d,
|
|
32
37
|
s,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
]);
|
|
36
|
-
return {
|
|
37
|
-
data:
|
|
38
|
-
totalCount:
|
|
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,
|
|
39
44
|
loading: !1,
|
|
40
45
|
error: null,
|
|
41
|
-
page:
|
|
42
|
-
setPage:
|
|
43
|
-
sortCriteria:
|
|
44
|
-
onSortChange:
|
|
45
|
-
search:
|
|
46
|
-
setSearch:
|
|
47
|
-
refresh:
|
|
46
|
+
page: h,
|
|
47
|
+
setPage: g,
|
|
48
|
+
sortCriteria: _,
|
|
49
|
+
onSortChange: S,
|
|
50
|
+
search: y,
|
|
51
|
+
setSearch: x,
|
|
52
|
+
refresh: C
|
|
48
53
|
};
|
|
49
54
|
}
|
|
50
55
|
//#endregion
|
package/dist/array/types.d.ts
CHANGED
|
@@ -4,6 +4,16 @@ export interface UseArraySourceOptions<T> {
|
|
|
4
4
|
pageSize?: number;
|
|
5
5
|
/** 초기 정렬(`'a asc, b desc'` 형식, `useODataSource`와 동일 문법). */
|
|
6
6
|
defaultOrderBy?: string;
|
|
7
|
+
/**
|
|
8
|
+
* 초기 페이지. **0-based** — `useODataSource`와 같은 축이다. 기본값 `0`.
|
|
9
|
+
*
|
|
10
|
+
* ⚠`useState`의 초기값이라 **첫 렌더에서만** 읽힌다. 이후 이동은 `setPage`.
|
|
11
|
+
*/
|
|
12
|
+
initialPage?: number;
|
|
13
|
+
/** 초기 검색어. 기본값 `''`. `initialPage`와 같은 「첫 렌더에서만」 계약이다. */
|
|
14
|
+
initialSearch?: string;
|
|
15
|
+
/** 초기 정렬. 주면 `defaultOrderBy` 파싱보다 **우선**한다(`useODataSource`와 동일). */
|
|
16
|
+
initialSort?: SortCriteria[];
|
|
7
17
|
/**
|
|
8
18
|
* 타입 인지 정렬 비교(숫자/불리언/날짜/텍스트)에 쓸 컬럼 정의. 생략하면 전부
|
|
9
19
|
* 텍스트로 비교한다(숫자 컬럼도 문자열 정렬 순서를 따름) — 그리드에 이미 넘기는
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/odata/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { n as buildSearchExpression,
|
|
1
|
+
import { i as e, n as t, t as n } from "../use-odata-source-6fVz-CLu.js";
|
|
2
|
+
export { n as buildSearchExpression, t as parseOrderBy, e as useODataSource };
|
package/dist/odata/types.d.ts
CHANGED
|
@@ -2,6 +2,22 @@ import type { SortCriteria } from '../core/sorting.js';
|
|
|
2
2
|
export interface UseODataSourceOptions {
|
|
3
3
|
pageSize?: number;
|
|
4
4
|
defaultOrderBy?: string;
|
|
5
|
+
/**
|
|
6
|
+
* 초기 페이지. **0-based** — 반환되는 `page`/`setPage`와 같은 축이고, 요청의
|
|
7
|
+
* `$skip`은 `page * pageSize`다. 기본값 `0`.
|
|
8
|
+
*
|
|
9
|
+
* ⚠`useState`의 초기값이므로 **첫 렌더에서만** 읽힌다(`defaultOrderBy`와 같은 계약).
|
|
10
|
+
* 마운트 이후에 페이지를 옮기려면 `setPage`를 쓴다.
|
|
11
|
+
*/
|
|
12
|
+
initialPage?: number;
|
|
13
|
+
/** 초기 검색어. 기본값 `''`. `initialPage`와 같은 「첫 렌더에서만」 계약이다. */
|
|
14
|
+
initialSearch?: string;
|
|
15
|
+
/**
|
|
16
|
+
* 초기 정렬. 주면 `defaultOrderBy` 파싱보다 **우선**한다 — 두 옵션은 같은 것을 서로
|
|
17
|
+
* 다른 표기로 말하고, 이쪽은 `sortCriteria`/`onSortChange`가 주고받는 모양 그대로라
|
|
18
|
+
* 저장해 둔 정렬 상태를 파싱 없이 되돌릴 수 있다.
|
|
19
|
+
*/
|
|
20
|
+
initialSort?: SortCriteria[];
|
|
5
21
|
fixedFilter?: Record<string, unknown>;
|
|
6
22
|
/** 기본값: `window.location.origin`. 프록시/BFF 등 다른 origin으로 요청해야 할 때 지정. */
|
|
7
23
|
baseUrl?: string;
|
|
@@ -17,6 +17,34 @@ import type { UseODataSourceOptions, UseODataSourceResult } from './types.js';
|
|
|
17
17
|
* @returns `$search` 표현식, 또는 유효 토큰이 없으면 `undefined`
|
|
18
18
|
*/
|
|
19
19
|
export declare function buildSearchExpression(term: string): string | undefined;
|
|
20
|
+
/** `resolveInitialState`가 읽는 옵션 — 두 소스 훅의 옵션 타입이 공통으로 갖는 부분. */
|
|
21
|
+
export interface InitialSourceStateOptions {
|
|
22
|
+
defaultOrderBy?: string;
|
|
23
|
+
initialPage?: number;
|
|
24
|
+
initialSearch?: string;
|
|
25
|
+
initialSort?: SortCriteria[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 옵션에서 `page`/`search`/`sortCriteria`의 **초기값**을 뽑는다.
|
|
29
|
+
*
|
|
30
|
+
* ★**두 훅이 이 함수 하나를 공유하는 것이 요점이다.** README가 *"same shape … so the same
|
|
31
|
+
* binding code works with either source"*를 계약으로 선언하는데, 초기값 해석을 양쪽에
|
|
32
|
+
* 복제하면 그 계약이 **문장으로만** 유지된다 — 이 리포가 반복 기록한 실패 형태다.
|
|
33
|
+
* 구현이 하나면 드리프트가 없다.
|
|
34
|
+
*
|
|
35
|
+
* ⚠**`initialSort`가 `defaultOrderBy`를 이긴다.** 둘은 같은 것을 서로 다른 표기로
|
|
36
|
+
* 말하고(`SortCriteria[]` ↔ `$orderby` 문자열), 더 구체적인 쪽을 우선한다.
|
|
37
|
+
* `initialSort`는 `onSortChange`가 주는 모양 그대로라 저장해 둔 정렬을 파싱 없이 되돌린다.
|
|
38
|
+
*
|
|
39
|
+
* ⚠**React가 useState 초기값을 첫 렌더에서만 읽는다는 사실이 계약의 일부다** — 이후의
|
|
40
|
+
* 옵션 변경은 무시되고, 이동은 `setPage`/`setSearch`로 한다. `defaultOrderBy`가 이미
|
|
41
|
+
* 그렇게 동작해 왔으므로 새 규칙이 아니다.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveInitialState(options: InitialSourceStateOptions): {
|
|
44
|
+
page: number;
|
|
45
|
+
search: string;
|
|
46
|
+
sortCriteria: SortCriteria[];
|
|
47
|
+
};
|
|
20
48
|
/**
|
|
21
49
|
* OData v4 서버 사이드 데이터소스 React 훅.
|
|
22
50
|
* flex-table의 dataMode="server"와 함께 사용한다.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { useCallback as e, useEffect as t, useRef as n, useState as r } from "react";
|
|
2
|
+
import i from "odata-query";
|
|
3
|
+
//#region src/odata/use-odata-source.ts
|
|
4
|
+
function a(e) {
|
|
5
|
+
let t = e.split(/\s+/).map((e) => e.replace(/"/g, "")).filter((e) => e.length > 0);
|
|
6
|
+
if (t.length !== 0) return t.map((e) => `"${e}"`).join(" AND ");
|
|
7
|
+
}
|
|
8
|
+
function o(e) {
|
|
9
|
+
let { defaultOrderBy: t, initialPage: n = 0, initialSearch: r = "", initialSort: i } = e;
|
|
10
|
+
return {
|
|
11
|
+
page: n,
|
|
12
|
+
search: r,
|
|
13
|
+
sortCriteria: i ?? (t ? c(t) : [])
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function s(s, c = {}) {
|
|
17
|
+
let { pageSize: l = 20, defaultOrderBy: u, initialPage: d = 0, initialSearch: f = "", initialSort: p, fixedFilter: m, baseUrl: h, fetcher: g = fetch, onUnauthorized: _ } = c, v = m ? JSON.stringify(m) : "", [y, b] = r([]), [x, S] = r(0), [C, w] = r(!1), [T, E] = r(null), D = o({
|
|
18
|
+
defaultOrderBy: u,
|
|
19
|
+
initialPage: d,
|
|
20
|
+
initialSearch: f,
|
|
21
|
+
initialSort: p
|
|
22
|
+
}), [O, k] = r(D.page), [A, j] = r(D.sortCriteria), [M, N] = r(D.search), [P, F] = r(0), [I, L] = r(v);
|
|
23
|
+
v !== I && (L(v), k(0));
|
|
24
|
+
let R = n(null), z = e(() => {
|
|
25
|
+
F((e) => e + 1);
|
|
26
|
+
}, []), B = e((e) => {
|
|
27
|
+
N(e), k(0);
|
|
28
|
+
}, []), V = e((e) => {
|
|
29
|
+
let t = e.detail?.criteria;
|
|
30
|
+
t && (j(t), k(0));
|
|
31
|
+
}, []);
|
|
32
|
+
return t(() => {
|
|
33
|
+
R.current?.abort();
|
|
34
|
+
let e = new AbortController();
|
|
35
|
+
R.current = e, w(!0), E(null);
|
|
36
|
+
let t = A.length > 0 ? A.map((e) => `${e.key} ${e.direction}`).join(", ") : u, n = {
|
|
37
|
+
top: l,
|
|
38
|
+
skip: O * l,
|
|
39
|
+
count: !0
|
|
40
|
+
};
|
|
41
|
+
if (t && (n.orderBy = t), m && (n.filter = m), M) {
|
|
42
|
+
let e = a(M);
|
|
43
|
+
e && (n.search = e);
|
|
44
|
+
}
|
|
45
|
+
let r = i(n), o = `${h ?? window.location.origin}${s}${r}`;
|
|
46
|
+
return g(o, { signal: e.signal }).then(async (e) => {
|
|
47
|
+
if (!e.ok) {
|
|
48
|
+
(e.status === 401 || e.status === 403) && _ && _(e);
|
|
49
|
+
let t = await e.text().catch(() => ""), n = `Request failed (${e.status})`;
|
|
50
|
+
try {
|
|
51
|
+
let e = JSON.parse(t);
|
|
52
|
+
n = e?.error?.message ?? e?.message ?? n;
|
|
53
|
+
} catch {}
|
|
54
|
+
throw Error(n);
|
|
55
|
+
}
|
|
56
|
+
return e.json();
|
|
57
|
+
}).then((t) => {
|
|
58
|
+
if (e.signal.aborted) return;
|
|
59
|
+
let n = t["@odata.count"] ?? 0;
|
|
60
|
+
b(t.value ?? t), S(n), E(null);
|
|
61
|
+
let r = n === 0 ? 0 : Math.ceil(n / l) - 1;
|
|
62
|
+
O > r && k(r);
|
|
63
|
+
}).catch((t) => {
|
|
64
|
+
e.signal.aborted || t.name !== "AbortError" && (E(t.message), b([]), S(0));
|
|
65
|
+
}).finally(() => {
|
|
66
|
+
e.signal.aborted || w(!1);
|
|
67
|
+
}), () => e.abort();
|
|
68
|
+
}, [
|
|
69
|
+
s,
|
|
70
|
+
O,
|
|
71
|
+
l,
|
|
72
|
+
A,
|
|
73
|
+
M,
|
|
74
|
+
v,
|
|
75
|
+
u,
|
|
76
|
+
P,
|
|
77
|
+
h
|
|
78
|
+
]), {
|
|
79
|
+
data: y,
|
|
80
|
+
totalCount: x,
|
|
81
|
+
loading: C,
|
|
82
|
+
error: T,
|
|
83
|
+
page: O,
|
|
84
|
+
setPage: k,
|
|
85
|
+
sortCriteria: A,
|
|
86
|
+
onSortChange: V,
|
|
87
|
+
search: M,
|
|
88
|
+
setSearch: B,
|
|
89
|
+
refresh: z
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function c(e) {
|
|
93
|
+
return e.split(",").map((e) => {
|
|
94
|
+
let t = e.trim().split(/\s+/);
|
|
95
|
+
return {
|
|
96
|
+
key: t[0],
|
|
97
|
+
direction: t[1]?.toLowerCase() === "desc" ? "desc" : "asc"
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
export { s as i, c as n, o as r, a as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iyulab/flex-table",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"description": "A minimalist, input-centric data grid web component",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/flex-table.js",
|
|
@@ -88,6 +88,7 @@
|
|
|
88
88
|
"@lit/react": "^1.0.8",
|
|
89
89
|
"@types/node": "^26.1.1",
|
|
90
90
|
"@types/react": "^19.2.17",
|
|
91
|
+
"@types/react-dom": "^19.2.7",
|
|
91
92
|
"@typescript-eslint/eslint-plugin": "^8.64.0",
|
|
92
93
|
"@typescript-eslint/parser": "^8.64.0",
|
|
93
94
|
"eslint": "^10.1.0",
|
|
@@ -95,6 +96,7 @@
|
|
|
95
96
|
"jsdom": "^29.1.1",
|
|
96
97
|
"loupe": "^3.2.1",
|
|
97
98
|
"react": "^19.2.7",
|
|
99
|
+
"react-dom": "^19.2.8",
|
|
98
100
|
"strip-literal": "^3.1.0",
|
|
99
101
|
"typescript": "~5.7.0",
|
|
100
102
|
"vite": "^8.1.4",
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { useCallback as e, useEffect as t, useRef as n, useState as r } from "react";
|
|
2
|
-
import i from "odata-query";
|
|
3
|
-
//#region src/odata/use-odata-source.ts
|
|
4
|
-
function a(e) {
|
|
5
|
-
let t = e.split(/\s+/).map((e) => e.replace(/"/g, "")).filter((e) => e.length > 0);
|
|
6
|
-
if (t.length !== 0) return t.map((e) => `"${e}"`).join(" AND ");
|
|
7
|
-
}
|
|
8
|
-
function o(o, c = {}) {
|
|
9
|
-
let { pageSize: l = 20, defaultOrderBy: u, fixedFilter: d, baseUrl: f, fetcher: p = fetch, onUnauthorized: m } = c, h = d ? JSON.stringify(d) : "", [g, _] = r([]), [v, y] = r(0), [b, x] = r(!1), [S, C] = r(null), [w, T] = r(0), [E, D] = r(() => u ? s(u) : []), [O, k] = r(""), [A, j] = r(0), M = n(null), N = e(() => {
|
|
10
|
-
j((e) => e + 1);
|
|
11
|
-
}, []), P = e((e) => {
|
|
12
|
-
k(e), T(0);
|
|
13
|
-
}, []), F = e((e) => {
|
|
14
|
-
let t = e.detail?.criteria;
|
|
15
|
-
t && (D(t), T(0));
|
|
16
|
-
}, []);
|
|
17
|
-
return t(() => {
|
|
18
|
-
M.current?.abort();
|
|
19
|
-
let e = new AbortController();
|
|
20
|
-
M.current = e, x(!0), C(null);
|
|
21
|
-
let t = E.length > 0 ? E.map((e) => `${e.key} ${e.direction}`).join(", ") : u, n = {
|
|
22
|
-
top: l,
|
|
23
|
-
skip: w * l,
|
|
24
|
-
count: !0
|
|
25
|
-
};
|
|
26
|
-
if (t && (n.orderBy = t), d && (n.filter = d), O) {
|
|
27
|
-
let e = a(O);
|
|
28
|
-
e && (n.search = e);
|
|
29
|
-
}
|
|
30
|
-
let r = i(n), s = `${f ?? window.location.origin}${o}${r}`;
|
|
31
|
-
return p(s, { signal: e.signal }).then(async (e) => {
|
|
32
|
-
if (!e.ok) {
|
|
33
|
-
(e.status === 401 || e.status === 403) && m && m(e);
|
|
34
|
-
let t = await e.text().catch(() => ""), n = `Request failed (${e.status})`;
|
|
35
|
-
try {
|
|
36
|
-
let e = JSON.parse(t);
|
|
37
|
-
n = e?.error?.message ?? e?.message ?? n;
|
|
38
|
-
} catch {}
|
|
39
|
-
throw Error(n);
|
|
40
|
-
}
|
|
41
|
-
return e.json();
|
|
42
|
-
}).then((t) => {
|
|
43
|
-
e.signal.aborted || (_(t.value ?? t), y(t["@odata.count"] ?? 0), C(null));
|
|
44
|
-
}).catch((t) => {
|
|
45
|
-
e.signal.aborted || t.name !== "AbortError" && (C(t.message), _([]), y(0));
|
|
46
|
-
}).finally(() => {
|
|
47
|
-
e.signal.aborted || x(!1);
|
|
48
|
-
}), () => e.abort();
|
|
49
|
-
}, [
|
|
50
|
-
o,
|
|
51
|
-
w,
|
|
52
|
-
l,
|
|
53
|
-
E,
|
|
54
|
-
O,
|
|
55
|
-
h,
|
|
56
|
-
u,
|
|
57
|
-
A,
|
|
58
|
-
f
|
|
59
|
-
]), {
|
|
60
|
-
data: g,
|
|
61
|
-
totalCount: v,
|
|
62
|
-
loading: b,
|
|
63
|
-
error: S,
|
|
64
|
-
page: w,
|
|
65
|
-
setPage: T,
|
|
66
|
-
sortCriteria: E,
|
|
67
|
-
onSortChange: F,
|
|
68
|
-
search: O,
|
|
69
|
-
setSearch: P,
|
|
70
|
-
refresh: N
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
function s(e) {
|
|
74
|
-
return e.split(",").map((e) => {
|
|
75
|
-
let t = e.trim().split(/\s+/);
|
|
76
|
-
return {
|
|
77
|
-
key: t[0],
|
|
78
|
-
direction: t[1]?.toLowerCase() === "desc" ? "desc" : "asc"
|
|
79
|
-
};
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
//#endregion
|
|
83
|
-
export { s as n, o as r, a as t };
|