@iyulab/flex-table 0.20.0 → 0.21.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/README.md CHANGED
@@ -524,12 +524,30 @@ const source = useODataSource('/api/orders', {
524
524
 
525
525
  `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.
526
526
 
527
+ The hook returns:
528
+
529
+ | Field | Description |
530
+ |---|---|
531
+ | `data` / `totalCount` | Current page rows and the server's total (`@odata.count`) |
532
+ | `loading` | A request is in flight |
533
+ | `error` | Message of the last failed request, or `null`. **Render this** — a failed request otherwise leaves the grid silently empty |
534
+ | `page` / `setPage` | Zero-based page index |
535
+ | `sortCriteria` / `onSortChange` | Bind `onSortChange` to the table's `sort-change` event |
536
+ | `search` / `setSearch` | Current search term and its setter (resets to page 0) |
537
+ | `refresh` | Re-run the current request |
538
+
539
+ #### Search semantics
540
+
541
+ `setSearch` takes **literal text, not an OData search expression**. Each whitespace-separated token is sent as a quoted `$search` phrase joined with `AND` — `red shirt` becomes `$search="red" AND "shirt"`, matching rows that contain both terms.
542
+
543
+ 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.
544
+
527
545
  ## Development
528
546
 
529
547
  ```bash
530
548
  npm install
531
549
  npm run dev # Dev server with demo
532
- npm test # Run tests (179 tests)
550
+ npm test # Run tests
533
551
  npm run build # Build library
534
552
  npm run lint # ESLint check
535
553
  ```
@@ -1222,7 +1222,7 @@ function k(e, t) {
1222
1222
  //#endregion
1223
1223
  //#region src/export/xlsx-writer.ts
1224
1224
  var te = (() => {
1225
- let e = new Uint32Array(256);
1225
+ let e = /* @__PURE__ */ new Uint32Array(256);
1226
1226
  for (let t = 0; t < 256; t++) {
1227
1227
  let n = t;
1228
1228
  for (let e = 0; e < 8; e++) n = n & 1 ? 3988292384 ^ n >>> 1 : n >>> 1;
@@ -1480,7 +1480,7 @@ async function be(e) {
1480
1480
  };
1481
1481
  }
1482
1482
  //#endregion
1483
- //#region \0@oxc-project+runtime@0.130.0/helpers/decorate.js
1483
+ //#region \0@oxc-project+runtime@0.139.0/helpers/esm/decorate.js
1484
1484
  function J(e, t, n, r) {
1485
1485
  var i = arguments.length, a = i < 3 ? t : r === null ? r = Object.getOwnPropertyDescriptor(t, n) : r, o;
1486
1486
  if (typeof Reflect == "object" && typeof Reflect.decorate == "function") a = Reflect.decorate(e, t, n, r);
@@ -1509,8 +1509,8 @@ var Y = {
1509
1509
  return this._data;
1510
1510
  }
1511
1511
  set data(e) {
1512
- let t = this._data;
1513
- this._data = e, this.clearUndoOnDataChange && !this._inUndoRedo && this._undo.clear(), this.clearSelectionOnDataChange && !this._inUndoRedo && this._rowSelection.selectedCount > 0 && (this._rowSelection.deselectAll(), this._rowSelectionVersion++, this._dispatchRowSelectionEvent()), this.requestUpdate("data", t);
1512
+ let t = this._data, n = e !== t;
1513
+ this._data = e, n && !this._inUndoRedo && (this.clearUndoOnDataChange && this._undo.clear(), this.clearSelectionOnDataChange && this._rowSelection.selectedCount > 0 && (this._rowSelection.deselectAll(), this._rowSelectionVersion++, this._dispatchRowSelectionEvent())), this.requestUpdate("data", t);
1514
1514
  }
1515
1515
  set selectionMode(e) {
1516
1516
  this._rowSelection.mode = e, this.requestUpdate();
@@ -1,2 +1,2 @@
1
- import { a as e, i as t, n, r, t as i } from "./flex-table-C_7PqhEb.js";
1
+ import { a as e, i as t, n, r, t as i } from "./flex-table-3VFQ-b5m.js";
2
2
  export { i as FlexTable, t as RowSelectionState, r as UndoStack, n as exportData, e as renderCell };
@@ -1,29 +1,36 @@
1
1
  import { useCallback as e, useEffect as t, useRef as n, useState as r } from "react";
2
2
  import i from "odata-query";
3
3
  //#region src/odata/use-odata-source.ts
4
- function a(a, s = {}) {
5
- let { pageSize: c = 20, defaultOrderBy: l, fixedFilter: u, baseUrl: d, fetcher: f = fetch, onUnauthorized: p } = s, m = u ? JSON.stringify(u) : "", [h, g] = r([]), [_, v] = r(0), [y, b] = r(!1), [x, S] = r(null), [C, w] = r(0), [T, E] = r(() => l ? o(l) : []), [D, O] = r(""), [k, A] = r(0), j = n(null), M = e(() => {
6
- A((e) => e + 1);
7
- }, []), N = e((e) => {
8
- O(e), w(0);
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);
9
11
  }, []), P = e((e) => {
12
+ k(e), T(0);
13
+ }, []), F = e((e) => {
10
14
  let t = e.detail?.criteria;
11
- t && (E(t), w(0));
15
+ t && (D(t), T(0));
12
16
  }, []);
13
17
  return t(() => {
14
- j.current?.abort();
18
+ M.current?.abort();
15
19
  let e = new AbortController();
16
- j.current = e, b(!0), S(null);
17
- let t = T.length > 0 ? T.map((e) => `${e.key} ${e.direction}`).join(", ") : l, n = {
18
- top: c,
19
- skip: C * c,
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,
20
24
  count: !0
21
25
  };
22
- t && (n.orderBy = t), u && (n.filter = u), D && (n.search = D);
23
- let r = i(n);
24
- return f(`${d ?? window.location.origin}${a}${r}`, { signal: e.signal }).then(async (e) => {
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) => {
25
32
  if (!e.ok) {
26
- (e.status === 401 || e.status === 403) && p && p(e);
33
+ (e.status === 401 || e.status === 403) && m && m(e);
27
34
  let t = await e.text().catch(() => ""), n = `요청 실패 (${e.status})`;
28
35
  try {
29
36
  let e = JSON.parse(t);
@@ -33,37 +40,37 @@ function a(a, s = {}) {
33
40
  }
34
41
  return e.json();
35
42
  }).then((t) => {
36
- e.signal.aborted || (g(t.value ?? t), v(t["@odata.count"] ?? 0), S(null));
43
+ e.signal.aborted || (_(t.value ?? t), y(t["@odata.count"] ?? 0), C(null));
37
44
  }).catch((t) => {
38
- e.signal.aborted || t.name !== "AbortError" && (S(t.message), g([]), v(0));
45
+ e.signal.aborted || t.name !== "AbortError" && (C(t.message), _([]), y(0));
39
46
  }).finally(() => {
40
- e.signal.aborted || b(!1);
47
+ e.signal.aborted || x(!1);
41
48
  }), () => e.abort();
42
49
  }, [
43
- a,
44
- C,
45
- c,
46
- T,
47
- D,
48
- m,
50
+ o,
51
+ w,
49
52
  l,
50
- k,
51
- d
53
+ E,
54
+ O,
55
+ h,
56
+ u,
57
+ A,
58
+ f
52
59
  ]), {
53
- data: h,
54
- totalCount: _,
55
- loading: y,
56
- error: x,
57
- page: C,
58
- setPage: w,
59
- sortCriteria: T,
60
- onSortChange: P,
61
- search: D,
62
- setSearch: N,
63
- refresh: M
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
64
71
  };
65
72
  }
66
- function o(e) {
73
+ function s(e) {
67
74
  return e.split(",").map((e) => {
68
75
  let t = e.trim().split(/\s+/);
69
76
  return {
@@ -73,4 +80,4 @@ function o(e) {
73
80
  });
74
81
  }
75
82
  //#endregion
76
- export { a as useODataSource };
83
+ export { o as useODataSource };
@@ -1,6 +1,29 @@
1
+ import type { SortCriteria } from '../core/sorting.js';
1
2
  import type { UseODataSourceOptions, UseODataSourceResult } from './types.js';
3
+ /**
4
+ * 검색어를 OData `$search` 표현식으로 인코딩한다 (`red shirt` → `"red" AND "shirt"`).
5
+ *
6
+ * 토큰을 인용하는 이유: OData 4.0의 `searchWord`는 문자(Unicode L/Nl)만 허용해
7
+ * `2026`·`ZT-E2E-A` 같은 검색어가 거부된다. 4.01이 숫자·하이픈을 허용하도록 완화했으나
8
+ * Microsoft.OData 렉서는 아직 4.0 규칙이다(odata.net#2445). `searchPhrase`는 두 버전
9
+ * 모두에서 적법하므로 서버 버전과 무관하게 안전하다.
10
+ *
11
+ * 통째로가 아니라 토큰별로 감싸는 이유: 인용 없는 다중 단어는 암묵 AND로 파싱되므로
12
+ * (`searchAndExpr = RWS [ 'AND' RWS ] searchExpr`), 전체를 한 phrase로 감싸면 연속
13
+ * 문자열 매칭으로 의미가 바뀐다. 토큰별 인용은 기존 의미론을 그대로 보존한다.
14
+ *
15
+ * `"`는 phrase 안에 넣을 수 없고 이스케이프 규칙도 없어(`qchar-no-AMP-DQUOTE`) 제거한다.
16
+ *
17
+ * @returns `$search` 표현식, 또는 유효 토큰이 없으면 `undefined`
18
+ */
19
+ export declare function buildSearchExpression(term: string): string | undefined;
2
20
  /**
3
21
  * OData v4 서버 사이드 데이터소스 React 훅.
4
22
  * flex-table의 dataMode="server"와 함께 사용한다.
5
23
  */
6
24
  export declare function useODataSource<T = Record<string, unknown>>(url: string, options?: UseODataSourceOptions): UseODataSourceResult<T>;
25
+ /**
26
+ * `$orderby` 문자열을 정렬 기준 배열로 파싱한다 (`'a asc, b desc'`).
27
+ * 방향이 생략되거나 `desc`가 아니면 `asc`로 본다(OData 기본값).
28
+ */
29
+ export declare function parseOrderBy(orderBy: string): SortCriteria[];
@@ -0,0 +1 @@
1
+ export {};
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as e } from "./flex-table-C_7PqhEb.js";
1
+ import { t as e } from "./flex-table-3VFQ-b5m.js";
2
2
  import t from "react";
3
3
  import { createComponent as n } from "@lit/react";
4
4
  var r = n({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iyulab/flex-table",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "A minimalist, input-centric data grid web component",
5
5
  "type": "module",
6
6
  "main": "./dist/flex-table.js",
@@ -53,7 +53,7 @@
53
53
  },
54
54
  "dependencies": {
55
55
  "lit": "^3.3.3",
56
- "odata-query": "^8.0.7"
56
+ "odata-query": "^8.1.0"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "@lit/react": "^1.0.0",
@@ -69,17 +69,17 @@
69
69
  },
70
70
  "devDependencies": {
71
71
  "@lit/react": "^1.0.8",
72
- "@types/react": "^19.2.14",
73
- "@typescript-eslint/eslint-plugin": "^8.59.3",
74
- "@typescript-eslint/parser": "^8.59.3",
72
+ "@types/react": "^19.2.17",
73
+ "@typescript-eslint/eslint-plugin": "^8.64.0",
74
+ "@typescript-eslint/parser": "^8.64.0",
75
75
  "eslint": "^10.1.0",
76
76
  "eslint-plugin-lit": "^2.3.1",
77
77
  "jsdom": "^29.1.1",
78
78
  "loupe": "^3.2.1",
79
- "react": "^19.2.6",
79
+ "react": "^19.2.7",
80
80
  "strip-literal": "^3.1.0",
81
81
  "typescript": "~5.7.0",
82
- "vite": "^8.0.13",
83
- "vitest": "^4.1.6"
82
+ "vite": "^8.1.4",
83
+ "vitest": "^4.1.10"
84
84
  }
85
85
  }