@iyulab/flex-table 0.45.0 → 0.47.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 +50 -0
- package/README.md +9 -4
- package/dist/array/types.d.ts +2 -1
- package/dist/core/source-error.d.ts +35 -0
- package/dist/{flex-table-Bv41ls8b.js → flex-table-h2noRChg.js} +250 -159
- package/dist/flex-table.d.ts +13 -0
- package/dist/flex-table.js +1 -1
- package/dist/odata/types.d.ts +6 -1
- package/dist/react.d.ts +1 -0
- package/dist/react.js +82 -55
- package/package.json +3 -2
- package/skills/iyulab-flex-table/SKILL.md +2 -2
- package/skills/iyulab-flex-table/references/react.md +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.47.0] - 2026-10-05
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Breaking: `error` from `useODataSource` and `useArraySource` is a structured failure, not a
|
|
8
|
+
string** — `SourceError | null`, where `SourceError` is `{ message, status?, code?, details?, body? }`
|
|
9
|
+
(exported from `@iyulab/flex-table/react` with `SourceErrorDetail`). The message alone lost which
|
|
10
|
+
failure it was, so an app could not tell a 403 the server marked with its own code (for example
|
|
11
|
+
"password change required") from any other 403, or a 404 from a 409, without wrapping `fetcher`
|
|
12
|
+
to watch the status. Now `status` is the HTTP status, `code` the server's rejection code (OData
|
|
13
|
+
`error.code`), `details` the OData `error.details` entries, and `body` the parsed response (or its
|
|
14
|
+
text). A failure with no response — a network error, or a `@odata.nextLink` the hook refused — has
|
|
15
|
+
a `message` only. The message itself is unchanged.
|
|
16
|
+
|
|
17
|
+
Migration: render `error.message` where you rendered `error`
|
|
18
|
+
(`{source.error && <p role="alert">{source.error.message}</p>}`), and compare `error?.message`
|
|
19
|
+
where you compared the string.
|
|
20
|
+
|
|
21
|
+
- **Date and datetime column filters use `u-date-picker`** for their From and To bounds, like the
|
|
22
|
+
cell editor since 0.46. The native date inputs they replace showed the browser's UI language
|
|
23
|
+
(`10/02/2026` in an English browser) while the table shows ISO; the bounds now read and show
|
|
24
|
+
`YYYY-MM-DD` (`YYYY-MM-DD HH:mm`) everywhere, with a calendar beside each. The bounds are labelled
|
|
25
|
+
(**From**, **To**), each calendar stops at the other bound, and either bound may still stay empty.
|
|
26
|
+
A bound now applies when it is committed — Enter, leaving the box, or a day picked in the calendar —
|
|
27
|
+
rather than on every keystroke. While a calendar is open, Escape closes the calendar and the next
|
|
28
|
+
Escape closes the filter. Opening the filter from the column menu focuses the From bound.
|
|
29
|
+
|
|
30
|
+
## [0.46.0] - 2026-10-05
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- **Date and datetime cells edit with `u-date-picker`** from `@iyulab/components` — the same text box
|
|
35
|
+
(`YYYY-MM-DD`, `YYYY-MM-DD HH:mm`, the same short forms and the same rejection of text that is not a
|
|
36
|
+
date) with a calendar beside it. A day picked in a `date` cell's calendar is the new value; in a
|
|
37
|
+
`datetime` cell the day and time are applied together with Apply. While the calendar is open,
|
|
38
|
+
Escape closes it and the next Escape cancels the edit. Stored values are unchanged: `YYYY-MM-DD`,
|
|
39
|
+
and the local `YYYY-MM-DDTHH:mm`. **Requires `@iyulab/components` 2.0.1** (peer `>=2.0.1`, was
|
|
40
|
+
`>=1.56.0`) — the release in which pressing the calendar keeps focus in the picker.
|
|
41
|
+
|
|
42
|
+
- **A paste leaves the cells it wrote selected**, as spreadsheets do: after Ctrl+V the pasted block
|
|
43
|
+
(including rows it appended) is the selection, so it can be seen, copied, cleared or undone as one
|
|
44
|
+
block. The active cell stays where the paste began. A single-value paste keeps the single-cell
|
|
45
|
+
selection.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- **Typing into a cell editor keeps every key.** The editor was focused and its text selected again on
|
|
50
|
+
every update of the table, so in an autocomplete column — whose list updates on each key — the next
|
|
51
|
+
key replaced what was typed (`apx` became `x`). It is now focused once, when editing starts.
|
|
52
|
+
|
|
3
53
|
## [0.45.0] - 2026-10-05
|
|
4
54
|
|
|
5
55
|
### Added
|
package/README.md
CHANGED
|
@@ -161,7 +161,7 @@ The `validator` callback returns `null` if valid, or an error message string. On
|
|
|
161
161
|
|
|
162
162
|
A `number` column's built-in editor reads numbers the way people type them in the active `Locale` — `1,5` on a comma-decimal page is 1.5, `1.234,5` is 1234.5 — and shows the value with that locale's decimal separator. Text that is not a number is rejected the same way as a validator failure (`error` is the localized "Enter a number"). The number filter's conditions and pasted values are read the same way; pasted text that is not a number stays text.
|
|
163
163
|
|
|
164
|
-
A `date` column's built-in editor is a text box that shows and takes `YYYY-MM-DD` in every browser language (the native date input would show the browser's UI language, e.g. `10/02/2026`). It also reads `2026/10/2`, `20261002` and `10-02` (this year), stores the ISO date string, and rejects text that is not a date (`error` is the localized "Enter a date as YYYY-MM-DD"). Pasted dates are read the same way. A `datetime` column's editor works the same with a time: it shows `YYYY-MM-DD HH:mm` in local time, reads `2026-10-02 14:05` (a date alone is midnight), and stores the local `YYYY-MM-DDTHH:mm` string.
|
|
164
|
+
A `date` column's built-in editor is `u-date-picker` from `@iyulab/components`: a text box that shows and takes `YYYY-MM-DD` in every browser language (the native date input would show the browser's UI language, e.g. `10/02/2026`), with a calendar beside it — click the box or press ArrowDown, and a picked day is the new value. It also reads `2026/10/2`, `20261002` and `10-02` (this year), stores the ISO date string, and rejects text that is not a date (`error` is the localized "Enter a date as YYYY-MM-DD"). Pasted dates are read the same way. A `datetime` column's editor works the same with a time: it shows `YYYY-MM-DD HH:mm` in local time, reads `2026-10-02 14:05` (a date alone is midnight), and stores the local `YYYY-MM-DDTHH:mm` string; in its calendar a day and a time are applied together with Apply. While the calendar is open, Escape closes the calendar; the next Escape cancels the edit.
|
|
165
165
|
|
|
166
166
|
### `format` vs `render`
|
|
167
167
|
|
|
@@ -684,8 +684,13 @@ button is highlighted while its column has an active filter.
|
|
|
684
684
|
- **text**: case-insensitive substring search
|
|
685
685
|
- **number**: min/max range inputs
|
|
686
686
|
- **boolean**: All / True / False select
|
|
687
|
-
- **date**:
|
|
688
|
-
-
|
|
687
|
+
- **date**: **From** and **To** `u-date-picker`s (`@iyulab/components`) — the cell editor's control, so the
|
|
688
|
+
bounds read and show `YYYY-MM-DD` in every browser language, with a calendar beside each. Either
|
|
689
|
+
bound may stay empty; the end day is inclusive, and each calendar stops at the other bound.
|
|
690
|
+
- **datetime**: the same pickers with a time (`YYYY-MM-DD HH:mm`)
|
|
691
|
+
|
|
692
|
+
A bound applies when it is committed — Enter, leaving the box, or a day picked in its calendar. While a
|
|
693
|
+
calendar is open, Escape closes the calendar; the next Escape closes the filter.
|
|
689
694
|
|
|
690
695
|
Filters set via the UI and the programmatic API (`setFilter()`) share the same filter state. Filter dropdowns automatically flip upward when near the viewport bottom.
|
|
691
696
|
|
|
@@ -787,7 +792,7 @@ The hook returns:
|
|
|
787
792
|
|---|---|
|
|
788
793
|
| `data` / `totalCount` | Current page rows and the server's total (`@odata.count`). When the server pages its response (`@odata.nextLink`, e.g. a page size smaller than `pageSize`), the hook follows the link until the page is filled; a link outside the request's origin, or one that returns to a page already read, is reported through `error` instead of showing a short page |
|
|
789
794
|
| `loading` | A request is in flight |
|
|
790
|
-
| `error` |
|
|
795
|
+
| `error` | The last failed request, or `null`: `{ message, status?, code?, details?, body? }`. **Render `error.message`** — a failed request otherwise leaves the grid silently empty. Branch on `status` (HTTP), `code` (the server's rejection code, OData `error.code`) and `details` (OData `error.details`); `body` is the parsed response (or its text). A failure with no response — a network error, or a `@odata.nextLink` the hook refused — has a `message` only |
|
|
791
796
|
| `page` / `setPage` | Zero-based page index |
|
|
792
797
|
| `sortCriteria` / `onSortChange` | Bind `onSortChange` to the table's `sort-change` event |
|
|
793
798
|
| `search` / `setSearch` | Current search term and its setter (resets to page 0) |
|
package/dist/array/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SortCriteria } from '../core/sorting.js';
|
|
2
|
+
import type { SourceError } from '../core/source-error.js';
|
|
2
3
|
export interface UseArraySourceOptions<T> {
|
|
3
4
|
/** 페이지당 행 수. `useODataSource`와 동일 기본값. */
|
|
4
5
|
pageSize?: number;
|
|
@@ -36,7 +37,7 @@ export interface UseArraySourceResult<T> {
|
|
|
36
37
|
/** 항상 `false` — 로컬 배열은 동기 처리라 로딩 상태가 없다. `useODataSource`와의 반환 형태 동일성을 위해 유지. */
|
|
37
38
|
loading: boolean;
|
|
38
39
|
/** 항상 `null` — 로컬 배열 처리는 실패하지 않는다. 위와 같은 이유로 유지. */
|
|
39
|
-
error:
|
|
40
|
+
error: SourceError | null;
|
|
40
41
|
page: number;
|
|
41
42
|
setPage: (page: number) => void;
|
|
42
43
|
sortCriteria: SortCriteria[];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** OData v4 오류 봉투의 `error.details` 항목 — 필드별 검증 실패 상세. */
|
|
2
|
+
export interface SourceErrorDetail {
|
|
3
|
+
code: string;
|
|
4
|
+
message: string;
|
|
5
|
+
target?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* 데이터 소스 훅(`useODataSource`/`useArraySource`)의 실패 값.
|
|
9
|
+
*
|
|
10
|
+
* 문자열 하나가 아니라 구조로 싣는 이유: 소비자는 «어떤 실패인가» 로 갈라야 한다 — 403 중에서도
|
|
11
|
+
* 서버가 코드로 구분한 거절(«비밀번호를 바꿔야 한다»), 404(지워진 행), 409(동시성), 429(제한).
|
|
12
|
+
* 메시지만 남기면 그 정보가 소스 경계에서 사라지고, 소비자는 전송(`fetcher`)을 감싸 상태를
|
|
13
|
+
* 엿보는 것 말고는 길이 없다.
|
|
14
|
+
*/
|
|
15
|
+
export interface SourceError {
|
|
16
|
+
/** 보여 줄 문장 — 서버가 준 메시지가 있으면 그것, 없으면 `Request failed (<status>)`. */
|
|
17
|
+
message: string;
|
|
18
|
+
/** HTTP 상태. 응답이 없던 실패(네트워크 오류 · `@odata.nextLink` 검사)에는 없다. */
|
|
19
|
+
status?: number;
|
|
20
|
+
/** 서버가 정한 거절 코드 — OData 오류 봉투의 `error.code`(또는 최상위 `code`). */
|
|
21
|
+
code?: string;
|
|
22
|
+
/** 오류 봉투의 `error.details` 중 `code`·`message` 가 둘 다 문자열인 항목. */
|
|
23
|
+
details?: SourceErrorDetail[];
|
|
24
|
+
/** 응답 본문 — JSON 이면 파싱한 값, 아니면 텍스트. 비어 있으면 없다. */
|
|
25
|
+
body?: unknown;
|
|
26
|
+
}
|
|
27
|
+
/** 실패 응답을 `SourceError` 로 읽는다. 본문을 읽을 수 없어도 상태와 기본 문장은 남긴다. */
|
|
28
|
+
export declare function readFailedResponse(res: Response): Promise<SourceError>;
|
|
29
|
+
/** 실패 응답을 실어 나르는 내부 예외 — `catch` 가 상태를 잃지 않고 `SourceError` 를 꺼낸다. */
|
|
30
|
+
export declare class SourceRequestError extends Error {
|
|
31
|
+
readonly failure: SourceError;
|
|
32
|
+
constructor(failure: SourceError);
|
|
33
|
+
}
|
|
34
|
+
/** 잡힌 예외를 `SourceError` 로 — 응답이 있던 실패는 그 구조 그대로, 나머지는 메시지만. */
|
|
35
|
+
export declare function toSourceError(err: unknown): SourceError;
|