@iyulab/flex-table 0.31.2 → 0.32.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 CHANGED
@@ -1,5 +1,72 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.32.0] - 2026-09-08
4
+
5
+ ### Added
6
+
7
+ - **Both source hooks accept their initial page, search term and sort.**
8
+ `useODataSource` and `useArraySource` now take `initialPage` (zero-based, the same
9
+ axis as the returned `page`/`setPage`), `initialSearch` and `initialSort`. They are
10
+ read on the first render only — the same contract `defaultOrderBy` has always had —
11
+ and every default matches today's behaviour exactly, so existing callers are
12
+ unaffected.
13
+
14
+ Restoring a list the way the user left it — returning from a detail screen, or
15
+ restoring from a URL — was previously only expressible as a mount effect calling
16
+ `setPage()` after the hook had already fetched page 0. That shape carries two problems
17
+ a caller cannot solve from outside: the first request is issued and then discarded,
18
+ and `setSearch()` resets the page by design, so "restore the page, then set the search
19
+ term" has no ordering that works.
20
+
21
+ `initialSort` takes precedence over `defaultOrderBy` — the two express the same thing
22
+ in different notations, and the array form is the shape `onSortChange` hands back, so a
23
+ stored sort round-trips without being re-serialized. An empty `initialSort: []` means
24
+ *no sort* and does not fall back to `defaultOrderBy`.
25
+
26
+ ### Internal
27
+
28
+ - Both hooks now resolve their initial state through one shared function rather than two
29
+ copies, so the two sources cannot drift apart on the shape the README declares they
30
+ share.
31
+
32
+ ## [0.31.5] - 2026-09-04
33
+
34
+ ### Fixed
35
+
36
+ - **`0.31.3` and `0.31.4` both failed to reach npm for the same reason**: the
37
+ publish and demo-deploy workflows pinned Node 22, and npm's dependency
38
+ resolver crashes with a null-pointer error during a lockfile-less install
39
+ on that runner (this repo doesn't commit a `package-lock.json`) — the CI
40
+ workflow, which already runs Node 24, never hit it. Both workflows now run
41
+ Node 24 to match CI. No package code changed; this release exists to get
42
+ `0.31.3`'s content onto the registry with a working pipeline.
43
+
44
+ ## [0.31.4] - 2026-09-04
45
+
46
+ ### Fixed
47
+
48
+ - **`0.31.3`'s release pipeline never actually published to npm** — the
49
+ publish step's `npm install` crashed with a null-pointer error inside
50
+ npm's own dependency-resolution engine, unrelated to this package's
51
+ dependency graph (the lockfile is byte-for-byte unchanged from the prior,
52
+ successfully published `0.31.2`, and two separate retries reproduced the
53
+ identical crash). No package code changed; this release exists to get
54
+ `0.31.3`'s content onto the registry with a working pipeline.
55
+
56
+ ## [0.31.3] - 2026-09-04
57
+
58
+ ### Fixed
59
+
60
+ - **Header resize/reorder drag and row-number drag reorder could spuriously
61
+ toggle column sort or extend row selection on drop.** A native `click`
62
+ event the browser synthesizes after `mouseup` bubbles to the header
63
+ cell's own sort listener (or the row-number cell's select listener)
64
+ even though `stopPropagation()` on the drag handle's `mousedown` had
65
+ already fired — that call only blocks the `mousedown` itself, not the
66
+ separate `click` that follows. Both interactions now track whether the
67
+ gesture that just ended was a genuine resize/drag and skip the next
68
+ click when it was.
69
+
3
70
  ## [0.31.2] - 2026-09-02
4
71
 
5
72
  ### Fixed
package/README.md CHANGED
@@ -584,6 +584,9 @@ const source = useODataSource('/api/orders', {
584
584
  |---|---|---|
585
585
  | `pageSize` | `20` | Rows per page |
586
586
  | `defaultOrderBy` | — | Initial `$orderby` (e.g. `'name asc'`) |
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 |
587
590
  | `fixedFilter` | — | Filter always applied in addition to search |
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 |
@@ -591,6 +594,27 @@ const source = useODataSource('/api/orders', {
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
+ The three `initial*` options are read **on the first render only** (the same contract
598
+ `defaultOrderBy` has always had); use `setPage`/`setSearch` to move afterwards. Reach for
599
+ them when a list has to come back the way the user left it — going to a detail screen and
600
+ returning, or restoring from a URL:
601
+
602
+ ```tsx
603
+ const source = useODataSource('/api/orders', {
604
+ pageSize: 20,
605
+ initialPage: restored.page, // no mount effect, no discarded first request
606
+ initialSearch: restored.search,
607
+ initialSort: restored.sort,
608
+ });
609
+ ```
610
+
611
+ Setting them up front rather than calling `setPage(...)` from a mount effect matters for
612
+ two reasons that are otherwise hard to work around: the effect version issues a request for
613
+ page 0 that is thrown away as soon as the second one lands, and `setSearch` resets the page
614
+ to 0 by design — so "restore the page, then set the search term" is not expressible from
615
+ outside the hook. Passing an empty `initialSort: []` means *no sort*, and does not fall
616
+ back to `defaultOrderBy`.
617
+
594
618
  The hook returns:
595
619
 
596
620
  | Field | Description |
@@ -648,6 +672,7 @@ const source = useArraySource(joined, {
648
672
  |---|---|---|
649
673
  | `pageSize` | `20` | Rows per page |
650
674
  | `defaultOrderBy` | — | Initial sort (e.g. `'name asc'`), same syntax as `useODataSource` |
675
+ | `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
676
  | `columns` | — | `ColumnDefinition[]` — enables value-aware sort (numbers/dates/booleans compared by value, not as text). Omit and every column sorts as text. |
652
677
  | `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
678
 
@@ -1,5 +1,5 @@
1
1
  import { t as e } from "../sorting-CjfjRxwL.js";
2
- import { n as t } from "../use-odata-source-5gOJiMFj.js";
2
+ import { r as t } from "../use-odata-source--wXFYeqA.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 }) {
@@ -12,39 +12,44 @@ function a(t, { search: n, sortCriteria: r, page: i, pageSize: a, columns: o, se
12
12
  };
13
13
  }
14
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) => {
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 && (m(t), f(0));
20
- }, []), y = n(() => {}, []), { data: b, totalCount: x } = r(() => a(e, {
21
- search: h,
22
- sortCriteria: p,
23
- page: d,
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: l,
26
- searchFields: u
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
- l,
34
- u
38
+ f,
39
+ p
35
40
  ]);
36
41
  return {
37
- data: b,
38
- totalCount: x,
42
+ data: w,
43
+ totalCount: T,
39
44
  loading: !1,
40
45
  error: null,
41
- page: d,
42
- setPage: f,
43
- sortCriteria: p,
44
- onSortChange: v,
45
- search: h,
46
- setSearch: _,
47
- refresh: y
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
@@ -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
  * 텍스트로 비교한다(숫자 컬럼도 문자열 정렬 순서를 따름) — 그리드에 이미 넘기는
@@ -1515,7 +1515,7 @@ var J = {
1515
1515
  lte: "≤"
1516
1516
  }, Y = 120, X = 40, Z = 32, Q = 5, $ = class extends n {
1517
1517
  constructor(...e) {
1518
- super(...e), this.columns = [], this._data = [], this._inUndoRedo = !1, this.clearUndoOnDataChange = !1, this.clearSelectionOnDataChange = !1, this._rowHeightFromCss = Z, this.showRowNumbers = !1, this.theme = void 0, this.maxRows = 0, this.editable = !0, this.showFilters = !1, this.showContextMenu = !1, this.frozenRows = 0, this.emptyMessage = "No data", this.noMatchingMessage = "No matching data", this.loading = !1, this.importEnabled = !1, this.selectable = !1, this.dataMode = "client", this.footerData = null, this.stylesheets = [], this._scrollTop = 0, this._scrollLeft = 0, this._viewportHeight = 0, this._viewportWidth = 0, this._colLeftOffsets = [], this._totalRowWidth = 0, this._baseStyleSheets = [], this._activeCell = null, this._editingCell = null, this._sortCriteria = [], this._selection = new x(), this._editing = new S(), this._rowSelection = new C(), this._undo = new E(), this._filters = [], this._filteredIndices = [], this._sortedIndices = [], this._openFilterKey = null, this._rowSelectionVersion = 0, this._lastCheckboxRowIndex = null, this._checkboxShiftPending = !1, this._autocompleteState = null, this._headerMenu = null, this._bodyContextMenu = null, this._commentPopup = null, this._viewDirty = !0, this._isDragging = !1, this._wasDrag = !1, this._resizing = null, this._resizeCleanup = null, this._colDrag = null, this._colDragIndicatorLeft = null, this._fillDrag = null, this._rowDrag = null, this._rowDragIndicatorY = null, this._findState = null, this._columnWidths = /* @__PURE__ */ new Map(), this._hostResizeObserver = null, this._comments = /* @__PURE__ */ new Map(), this._isDragOver = !1, this._onCommentPopupOutsideClick = () => {
1518
+ super(...e), this.columns = [], this._data = [], this._inUndoRedo = !1, this.clearUndoOnDataChange = !1, this.clearSelectionOnDataChange = !1, this._rowHeightFromCss = Z, this.showRowNumbers = !1, this.theme = void 0, this.maxRows = 0, this.editable = !0, this.showFilters = !1, this.showContextMenu = !1, this.frozenRows = 0, this.emptyMessage = "No data", this.noMatchingMessage = "No matching data", this.loading = !1, this.importEnabled = !1, this.selectable = !1, this.dataMode = "client", this.footerData = null, this.stylesheets = [], this._scrollTop = 0, this._scrollLeft = 0, this._viewportHeight = 0, this._viewportWidth = 0, this._colLeftOffsets = [], this._totalRowWidth = 0, this._baseStyleSheets = [], this._activeCell = null, this._editingCell = null, this._sortCriteria = [], this._selection = new x(), this._editing = new S(), this._rowSelection = new C(), this._undo = new E(), this._filters = [], this._filteredIndices = [], this._sortedIndices = [], this._openFilterKey = null, this._rowSelectionVersion = 0, this._lastCheckboxRowIndex = null, this._checkboxShiftPending = !1, this._autocompleteState = null, this._headerMenu = null, this._bodyContextMenu = null, this._commentPopup = null, this._viewDirty = !0, this._isDragging = !1, this._wasDrag = !1, this._resizing = null, this._resizeCleanup = null, this._wasHeaderDrag = !1, this._wasRowDrag = !1, this._colDrag = null, this._colDragIndicatorLeft = null, this._fillDrag = null, this._rowDrag = null, this._rowDragIndicatorY = null, this._findState = null, this._columnWidths = /* @__PURE__ */ new Map(), this._hostResizeObserver = null, this._comments = /* @__PURE__ */ new Map(), this._isDragOver = !1, this._onCommentPopupOutsideClick = () => {
1519
1519
  this._commitCommentPopup();
1520
1520
  }, this._invalidCells = /* @__PURE__ */ new Map(), this._textFilterState = /* @__PURE__ */ new Map(), this._numberFilterState = /* @__PURE__ */ new Map(), this._dateFilterState = /* @__PURE__ */ new Map(), this._emptyFilterState = /* @__PURE__ */ new Map();
1521
1521
  }
@@ -2222,6 +2222,10 @@ var J = {
2222
2222
  this._isDragging && (this._wasDrag = !0, this._selection.setActiveWithRange(e, t), this._activeCell = this._selection.activeCell ? { ...this._selection.activeCell } : null, this._dispatchSelectionEvent(), this.requestUpdate());
2223
2223
  }
2224
2224
  _onRowNumberClick(e) {
2225
+ if (this._wasRowDrag) {
2226
+ this._wasRowDrag = !1;
2227
+ return;
2228
+ }
2225
2229
  this._editing.current && this._commitEdit();
2226
2230
  let t = this.visibleColumns.length - 1;
2227
2231
  this._selection.setActive(e, 0), this._selection.setActiveWithRange(e, t), this._activeCell = this._selection.activeCell ? { ...this._selection.activeCell } : null, this._dispatchSelectionEvent(), this.requestUpdate();
@@ -2739,6 +2743,10 @@ var J = {
2739
2743
  }));
2740
2744
  }
2741
2745
  _onHeaderClick(t, n) {
2746
+ if (this._wasHeaderDrag) {
2747
+ this._wasHeaderDrag = !1;
2748
+ return;
2749
+ }
2742
2750
  let r = this.visibleColumns.indexOf(n);
2743
2751
  if (t.ctrlKey || t.metaKey) {
2744
2752
  this._selectColumn(r);
@@ -2821,7 +2829,7 @@ var J = {
2821
2829
  ` : ""}
2822
2830
  <div class="ft-resize-handle"
2823
2831
  @mousedown=${(e) => {
2824
- e.stopPropagation(), this._onResizeStart(e, t);
2832
+ e.stopPropagation(), this._wasHeaderDrag = !0, this._onResizeStart(e, t);
2825
2833
  }}
2826
2834
  @dblclick=${(e) => this._onResizeAutoFit(e, t)}></div>
2827
2835
  </div>
@@ -3379,7 +3387,7 @@ var J = {
3379
3387
  let t = Math.abs(e.clientX - this._colDrag.startX);
3380
3388
  !this._colDrag.active && t > 5 && (this._colDrag.active = !0, this._startColumnGhost(e), this.requestUpdate()), this._colDrag.active && this._updateColumnDrag(e);
3381
3389
  }, i = (e) => {
3382
- document.removeEventListener("mousemove", r), document.removeEventListener("mouseup", i), this._colDrag?.active && this._finishColumnDrag(), this._colDrag?.ghost && this._colDrag.ghost.remove(), this._colDrag = null, this._colDragIndicatorLeft = null, this.requestUpdate();
3390
+ document.removeEventListener("mousemove", r), document.removeEventListener("mouseup", i), this._colDrag?.active && (this._wasHeaderDrag = !0, this._finishColumnDrag()), this._colDrag?.ghost && this._colDrag.ghost.remove(), this._colDrag = null, this._colDragIndicatorLeft = null, this.requestUpdate();
3383
3391
  };
3384
3392
  document.addEventListener("mousemove", r), document.addEventListener("mouseup", i);
3385
3393
  }
@@ -3546,7 +3554,7 @@ var J = {
3546
3554
  let n = Math.abs(e.clientY - this._rowDrag.startY);
3547
3555
  !this._rowDrag.active && n > 5 && (this._rowDrag.active = !0, this._startRowGhost(e, t), this.requestUpdate()), this._rowDrag.active && this._updateRowDrag(e);
3548
3556
  }, r = () => {
3549
- document.removeEventListener("mousemove", n), document.removeEventListener("mouseup", r), this._rowDrag?.active && this._finishRowDrag(), this._rowDrag?.ghost && this._rowDrag.ghost.remove(), this._rowDrag = null, this._rowDragIndicatorY = null, this.requestUpdate();
3557
+ document.removeEventListener("mousemove", n), document.removeEventListener("mouseup", r), this._rowDrag?.active && (this._wasRowDrag = !0, this._finishRowDrag()), this._rowDrag?.ghost && this._rowDrag.ghost.remove(), this._rowDrag = null, this._rowDragIndicatorY = null, this.requestUpdate();
3550
3558
  };
3551
3559
  document.addEventListener("mousemove", n), document.addEventListener("mouseup", r);
3552
3560
  }
@@ -128,6 +128,13 @@ export declare class FlexTable extends LitElement {
128
128
  private _wasDrag;
129
129
  private _resizing;
130
130
  private _resizeCleanup;
131
+ /** Set by a resize or a real column-drag reorder; consumed once by the next header `click` to
132
+ * suppress the browser's synthetic click that follows mouseup (same pattern as `_wasDrag` for
133
+ * cell-selection drags below). */
134
+ private _wasHeaderDrag;
135
+ /** Same as `_wasHeaderDrag`, for the row-number gutter's real row-drag reorder vs. its own
136
+ * `click`-to-select-row listener. */
137
+ private _wasRowDrag;
131
138
  private _colDrag;
132
139
  private _colDragIndicatorLeft;
133
140
  private _fillDrag;
@@ -1,2 +1,2 @@
1
- import { a as e, i as t, n, r, t as i } from "./flex-table-2xx4M0ti.js";
1
+ import { a as e, i as t, n, r, t as i } from "./flex-table-D4W1C_QA.js";
2
2
  export { i as FlexTable, t as RowSelectionState, r as UndoStack, n as exportData, e as renderCell };
@@ -1,2 +1,2 @@
1
- import { n as e, r as t, t as n } from "../use-odata-source-5gOJiMFj.js";
2
- export { n as buildSearchExpression, e as parseOrderBy, t as useODataSource };
1
+ import { i as e, n as t, t as n } from "../use-odata-source--wXFYeqA.js";
2
+ export { n as buildSearchExpression, t as parseOrderBy, e as useODataSource };
@@ -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"와 함께 사용한다.
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as e } from "./flex-table-2xx4M0ti.js";
1
+ import { t as e } from "./flex-table-D4W1C_QA.js";
2
2
  import t from "react";
3
3
  import { createComponent as n } from "@lit/react";
4
4
  var r = n({
@@ -0,0 +1,96 @@
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 = n(null), L = e(() => {
23
+ F((e) => e + 1);
24
+ }, []), R = e((e) => {
25
+ N(e), k(0);
26
+ }, []), z = e((e) => {
27
+ let t = e.detail?.criteria;
28
+ t && (j(t), k(0));
29
+ }, []);
30
+ return t(() => {
31
+ I.current?.abort();
32
+ let e = new AbortController();
33
+ I.current = e, w(!0), E(null);
34
+ let t = A.length > 0 ? A.map((e) => `${e.key} ${e.direction}`).join(", ") : u, n = {
35
+ top: l,
36
+ skip: O * l,
37
+ count: !0
38
+ };
39
+ if (t && (n.orderBy = t), m && (n.filter = m), M) {
40
+ let e = a(M);
41
+ e && (n.search = e);
42
+ }
43
+ let r = i(n), o = `${h ?? window.location.origin}${s}${r}`;
44
+ return g(o, { signal: e.signal }).then(async (e) => {
45
+ if (!e.ok) {
46
+ (e.status === 401 || e.status === 403) && _ && _(e);
47
+ let t = await e.text().catch(() => ""), n = `Request failed (${e.status})`;
48
+ try {
49
+ let e = JSON.parse(t);
50
+ n = e?.error?.message ?? e?.message ?? n;
51
+ } catch {}
52
+ throw Error(n);
53
+ }
54
+ return e.json();
55
+ }).then((t) => {
56
+ e.signal.aborted || (b(t.value ?? t), S(t["@odata.count"] ?? 0), E(null));
57
+ }).catch((t) => {
58
+ e.signal.aborted || t.name !== "AbortError" && (E(t.message), b([]), S(0));
59
+ }).finally(() => {
60
+ e.signal.aborted || w(!1);
61
+ }), () => e.abort();
62
+ }, [
63
+ s,
64
+ O,
65
+ l,
66
+ A,
67
+ M,
68
+ v,
69
+ u,
70
+ P,
71
+ h
72
+ ]), {
73
+ data: y,
74
+ totalCount: x,
75
+ loading: C,
76
+ error: T,
77
+ page: O,
78
+ setPage: k,
79
+ sortCriteria: A,
80
+ onSortChange: z,
81
+ search: M,
82
+ setSearch: R,
83
+ refresh: L
84
+ };
85
+ }
86
+ function c(e) {
87
+ return e.split(",").map((e) => {
88
+ let t = e.trim().split(/\s+/);
89
+ return {
90
+ key: t[0],
91
+ direction: t[1]?.toLowerCase() === "desc" ? "desc" : "asc"
92
+ };
93
+ });
94
+ }
95
+ //#endregion
96
+ 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.31.2",
3
+ "version": "0.32.0",
4
4
  "description": "A minimalist, input-centric data grid web component",
5
5
  "type": "module",
6
6
  "main": "./dist/flex-table.js",
@@ -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 };