@iyulab/flex-table 0.54.0 → 0.55.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,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.55.0] - 2026-10-07
4
+
5
+ ### Added
6
+
7
+ - **`createArraySource(rows, options)`** and **`ArraySourceController`** (`@iyulab/flex-table/array`) — the in-memory
8
+ source without React, with the members of `createODataSource` (`getState`, `subscribe`, `setPage`, `setSort`,
9
+ `setSearch`, `refresh`) plus `update(rows, options)`. A custom-element screen can now switch between a server and
10
+ an in-memory source the way a React screen switches between `useODataSource` and `useArraySource`.
11
+
3
12
  ## [0.54.0] - 2026-10-07
4
13
 
5
14
  ### Added
package/README.md CHANGED
@@ -939,6 +939,19 @@ count after search (not a server-reported total), and `loading`/`error` are alwa
939
939
  `false`/`null` — there's no request to fail. `refresh` is a no-op kept only so a
940
940
  "refresh" button wired unconditionally against either hook doesn't need a branch.
941
941
 
942
+ Without React, `createArraySource(rows, options)` (`@iyulab/flex-table/array`) is the same source with the same
943
+ members as `createODataSource` — `getState()`, `subscribe()`, `setPage`, `setSort`, `setSearch`, `refresh` (no-op) —
944
+ plus `update(rows, options)` when the rows change. `ArraySourceController` binds it to a Lit element:
945
+
946
+ ```ts
947
+ import { ArraySourceController } from '@iyulab/flex-table/array';
948
+
949
+ class PricesPanel extends LitElement {
950
+ private prices = new ArraySourceController(this, [], { pageSize: 20 });
951
+ set rows(rows: DataRow[]) { this.prices.source.update(rows, { pageSize: 20 }); }
952
+ }
953
+ ```
954
+
942
955
  ## Development
943
956
 
944
957
  ```bash
@@ -1,2 +1,4 @@
1
1
  export { computeArrayView } from './view.js';
2
2
  export type { ComputeArrayViewOptions } from './view.js';
3
+ export { createArraySource, ArraySourceController } from './source.js';
4
+ export type { ArraySource, ArraySourceOptions } from './source.js';
@@ -1,2 +1,71 @@
1
- import { t as e } from "../view-DiLr-TdY.js";
2
- export { e as computeArrayView };
1
+ import { i as e } from "../query-Dw4iO0ir.js";
2
+ import { t } from "../view-DiLr-TdY.js";
3
+ //#region src/array/source.ts
4
+ function n(n, r = {}) {
5
+ let i = n, a = r, o = e({
6
+ defaultOrderBy: r.defaultOrderBy,
7
+ initialPage: r.initialPage ?? 0,
8
+ initialSearch: r.initialSearch ?? "",
9
+ initialSort: r.initialSort
10
+ }), s = o.page, c = o.sortCriteria, l = o.search, u = f(), d = /* @__PURE__ */ new Set();
11
+ function f() {
12
+ let e = a.pageSize ?? 20, n = t(i, {
13
+ search: l,
14
+ sortCriteria: c,
15
+ page: s,
16
+ pageSize: e,
17
+ columns: a.columns,
18
+ searchFields: a.searchFields
19
+ }), r = n.totalCount === 0 ? 0 : Math.ceil(n.totalCount / e) - 1;
20
+ return s > r && (s = r), {
21
+ data: n.data,
22
+ totalCount: n.totalCount,
23
+ loading: !1,
24
+ error: null,
25
+ page: s,
26
+ sortCriteria: c,
27
+ search: l
28
+ };
29
+ }
30
+ function p() {
31
+ u = f();
32
+ for (let e of [...d]) e();
33
+ }
34
+ return {
35
+ getState: () => u,
36
+ subscribe(e) {
37
+ return d.add(e), () => {
38
+ d.delete(e);
39
+ };
40
+ },
41
+ setPage(e) {
42
+ s = e, p();
43
+ },
44
+ setSort(e) {
45
+ c = e, s = 0, p();
46
+ },
47
+ setSearch(e) {
48
+ l = e, s = 0, p();
49
+ },
50
+ refresh() {},
51
+ update(e, t = {}) {
52
+ i = e, a = t, p();
53
+ }
54
+ };
55
+ }
56
+ var r = class {
57
+ constructor(e, t, r = {}) {
58
+ this.host = e, this.source = n(t, r), e.addController(this);
59
+ }
60
+ get state() {
61
+ return this.source.getState();
62
+ }
63
+ hostConnected() {
64
+ this.unsubscribe ??= this.source.subscribe(() => this.host.requestUpdate());
65
+ }
66
+ hostDisconnected() {
67
+ this.unsubscribe?.(), this.unsubscribe = void 0;
68
+ }
69
+ };
70
+ //#endregion
71
+ export { r as ArraySourceController, t as computeArrayView, n as createArraySource };
@@ -0,0 +1,43 @@
1
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ import type { SortCriteria } from '../core/sorting.js';
3
+ import type { DataRow } from '../models/types.js';
4
+ import type { ODataSourceState } from '../odata/source.js';
5
+ import type { UseArraySourceOptions } from './types.js';
6
+ /** 메모리 소스의 설정 — `useArraySource` 의 옵션과 같다(`initial*` 은 만들 때만 읽는다). */
7
+ export type ArraySourceOptions<T> = UseArraySourceOptions<T>;
8
+ /** 메모리 소스 — `createODataSource` 와 같은 상태·조작 모양이라 같은 표 배선이 두 소스에서 돈다. */
9
+ export interface ArraySource<T> {
10
+ /** 현재 상태(`loading` 은 늘 `false`, `error` 는 늘 `null`). 바뀌지 않았으면 같은 객체다. */
11
+ getState(): ODataSourceState<T>;
12
+ /** 상태가 바뀔 때마다 부른다. 해지 함수를 돌려준다. */
13
+ subscribe(listener: () => void): () => void;
14
+ /** 0-based 페이지로 옮긴다(범위를 넘으면 마지막 페이지). */
15
+ setPage(page: number): void;
16
+ /** 정렬을 바꾸고 첫 장으로 간다. */
17
+ setSort(criteria: SortCriteria[]): void;
18
+ /** 검색어를 바꾸고 첫 장으로 간다. */
19
+ setSearch(term: string): void;
20
+ /** 아무 일도 하지 않는다 — 다시 불러올 원격 상태가 없다(OData 소스와 같은 자리에 이어도 안전하다). */
21
+ refresh(): void;
22
+ /** 행이나 설정이 바뀌었다 — 다시 계산한다. 결과가 줄어 지금 페이지가 없어지면 마지막 페이지로 내려온다. */
23
+ update(data: T[], options?: ArraySourceOptions<T>): void;
24
+ }
25
+ /**
26
+ * 이미 메모리에 있는 행의 데이터 소스 — 검색·정렬·페이지 나누기를 로컬에서 한다(`computeArrayView`). React 없이 쓴다;
27
+ * React 는 `useArraySource`, Lit 은 `ArraySourceController`.
28
+ */
29
+ export declare function createArraySource<T extends DataRow = DataRow>(data: T[], options?: ArraySourceOptions<T>): ArraySource<T>;
30
+ /**
31
+ * Lit 어댑터 — 메모리 소스를 호스트에 묶는다. 상태가 바뀌면 호스트를 다시 그린다. 행이 바뀌면 `source.update(rows)`.
32
+ */
33
+ export declare class ArraySourceController<T extends DataRow = DataRow> implements ReactiveController {
34
+ private readonly host;
35
+ /** 소스 — 조작(`setPage`·`setSort`·`setSearch`·`update`)은 이것으로 한다. */
36
+ readonly source: ArraySource<T>;
37
+ private unsubscribe?;
38
+ constructor(host: ReactiveControllerHost, data: T[], options?: ArraySourceOptions<T>);
39
+ /** 현재 상태. */
40
+ get state(): ODataSourceState<T>;
41
+ hostConnected(): void;
42
+ hostDisconnected(): void;
43
+ }
@@ -1,4 +1,5 @@
1
- import { i as e, n as t, r as n, t as r } from "../source-CwbD-6ea.js";
1
+ import { n as e, r as t, t as n } from "../query-Dw4iO0ir.js";
2
+ import { t as r } from "../source-r2Pv6Pvo.js";
2
3
  //#region src/odata/controller.ts
3
4
  var i = class {
4
5
  constructor(e, t, n = {}) {
@@ -15,4 +16,4 @@ var i = class {
15
16
  }
16
17
  };
17
18
  //#endregion
18
- export { i as ODataSourceController, t as buildODataQuery, n as buildSearchExpression, r as createODataSource, e as parseOrderBy };
19
+ export { i as ODataSourceController, n as buildODataQuery, e as buildSearchExpression, r as createODataSource, t as parseOrderBy };
@@ -0,0 +1,37 @@
1
+ import e from "odata-query";
2
+ //#region src/odata/query.ts
3
+ function t(e) {
4
+ let t = e.split(/\s+/).map((e) => e.replace(/"/g, "")).filter((e) => e.length > 0);
5
+ if (t.length !== 0) return t.map((e) => `"${e}"`).join(" AND ");
6
+ }
7
+ function n(e) {
8
+ let { defaultOrderBy: t, initialPage: n = 0, initialSearch: i = "", initialSort: a } = e;
9
+ return {
10
+ page: n,
11
+ search: i,
12
+ sortCriteria: a ?? (t ? r(t) : [])
13
+ };
14
+ }
15
+ function r(e) {
16
+ return e.split(",").map((e) => {
17
+ let t = e.trim().split(/\s+/);
18
+ return {
19
+ key: t[0],
20
+ direction: t[1]?.toLowerCase() === "desc" ? "desc" : "asc"
21
+ };
22
+ });
23
+ }
24
+ function i(n) {
25
+ let { page: r, pageSize: i, sortCriteria: a = [], defaultOrderBy: o, search: s, fixedFilter: c } = n, l = a.length > 0 ? a.map((e) => `${e.key} ${e.direction}`).join(", ") : o, u = {
26
+ top: i,
27
+ skip: r * i,
28
+ count: !0
29
+ };
30
+ if (l && (u.orderBy = l), c && (u.filter = c), s) {
31
+ let e = t(s);
32
+ e && (u.search = e);
33
+ }
34
+ return e(u);
35
+ }
36
+ //#endregion
37
+ export { n as i, t as n, r, i as t };
package/dist/react.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { t as e } from "./flex-table-DlvIBu4z.js";
2
- import { a as t, t as n } from "./source-CwbD-6ea.js";
2
+ import { i as t } from "./query-Dw4iO0ir.js";
3
+ import { t as n } from "./source-r2Pv6Pvo.js";
3
4
  import { t as r } from "./view-DiLr-TdY.js";
4
5
  import i, { useCallback as a, useEffect as o, useMemo as s, useState as c, useSyncExternalStore as l } from "react";
5
6
  import { createComponent as u } from "@lit/react";
@@ -1,58 +1,23 @@
1
1
  import { n as e } from "./locale-B1SiVDS1.js";
2
- import t from "odata-query";
3
- //#region src/odata/query.ts
4
- function n(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 r(e) {
9
- let { defaultOrderBy: t, initialPage: n = 0, initialSearch: r = "", initialSort: a } = e;
10
- return {
11
- page: n,
12
- search: r,
13
- sortCriteria: a ?? (t ? i(t) : [])
14
- };
15
- }
16
- function i(e) {
17
- return e.split(",").map((e) => {
18
- let t = e.trim().split(/\s+/);
19
- return {
20
- key: t[0],
21
- direction: t[1]?.toLowerCase() === "desc" ? "desc" : "asc"
22
- };
23
- });
24
- }
25
- function a(e) {
26
- let { page: r, pageSize: i, sortCriteria: a = [], defaultOrderBy: o, search: s, fixedFilter: c } = e, l = a.length > 0 ? a.map((e) => `${e.key} ${e.direction}`).join(", ") : o, u = {
27
- top: i,
28
- skip: r * i,
29
- count: !0
30
- };
31
- if (l && (u.orderBy = l), c && (u.filter = c), s) {
32
- let e = n(s);
33
- e && (u.search = e);
34
- }
35
- return t(u);
36
- }
37
- //#endregion
2
+ import { i as t, t as n } from "./query-Dw4iO0ir.js";
38
3
  //#region src/core/source-error.ts
39
- async function o(t) {
4
+ async function r(t) {
40
5
  let n = await t.text().catch(() => ""), r = {
41
6
  message: e("requestFailed", { status: t.status }),
42
7
  status: t.status
43
8
  };
44
9
  if (!n) return r;
45
- let i = n;
10
+ let a = n;
46
11
  try {
47
- i = JSON.parse(n);
12
+ a = JSON.parse(n);
48
13
  } catch {}
49
- if (r.body = i, !i || typeof i != "object") return r;
50
- let a = i, o = a.error && typeof a.error == "object" ? a.error : a;
51
- typeof o.message == "string" && o.message ? r.message = o.message : typeof a.message == "string" && a.message && (r.message = a.message), typeof o.code == "string" && o.code && (r.code = o.code);
52
- let c = s(o.details);
14
+ if (r.body = a, !a || typeof a != "object") return r;
15
+ let o = a, s = o.error && typeof o.error == "object" ? o.error : o;
16
+ typeof s.message == "string" && s.message ? r.message = s.message : typeof o.message == "string" && o.message && (r.message = o.message), typeof s.code == "string" && s.code && (r.code = s.code);
17
+ let c = i(s.details);
53
18
  return c && (r.details = c), r;
54
19
  }
55
- function s(e) {
20
+ function i(e) {
56
21
  if (!Array.isArray(e)) return;
57
22
  let t = e.filter((e) => {
58
23
  if (!e || typeof e != "object") return !1;
@@ -61,42 +26,42 @@ function s(e) {
61
26
  });
62
27
  return t.length > 0 ? t : void 0;
63
28
  }
64
- var c = class extends Error {
29
+ var a = class extends Error {
65
30
  constructor(e) {
66
31
  super(e.message), this.name = "SourceRequestError", this.failure = e;
67
32
  }
68
33
  };
69
- function l(t) {
70
- return new c({
34
+ function o(t) {
35
+ return new a({
71
36
  message: e("networkFailed"),
72
37
  cause: t
73
38
  });
74
39
  }
75
- function u(e) {
76
- return e instanceof c ? e.failure : e instanceof Error ? { message: e.message } : { message: String(e) };
40
+ function s(e) {
41
+ return e instanceof a ? e.failure : e instanceof Error ? { message: e.message } : { message: String(e) };
77
42
  }
78
43
  //#endregion
79
44
  //#region src/odata/source.ts
80
- function d(e, t = {}) {
81
- let n = e, i = t, s = f(t.fixedFilter), d = r({
82
- defaultOrderBy: t.defaultOrderBy,
83
- initialPage: t.initialPage ?? 0,
84
- initialSearch: t.initialSearch ?? "",
85
- initialSort: t.initialSort
45
+ function c(e, i = {}) {
46
+ let c = e, u = i, d = l(i.fixedFilter), f = t({
47
+ defaultOrderBy: i.defaultOrderBy,
48
+ initialPage: i.initialPage ?? 0,
49
+ initialSearch: i.initialSearch ?? "",
50
+ initialSort: i.initialSort
86
51
  }), p = {
87
52
  data: [],
88
53
  totalCount: 0,
89
54
  loading: !0,
90
55
  error: null,
91
- page: d.page,
92
- sortCriteria: d.sortCriteria,
93
- search: d.search
56
+ page: f.page,
57
+ sortCriteria: f.sortCriteria,
58
+ search: f.search
94
59
  }, m = x(), h = /* @__PURE__ */ new Set(), g = null, _ = !1, v = null;
95
60
  function y() {
96
- return i.enabled ?? !0;
61
+ return u.enabled ?? !0;
97
62
  }
98
63
  function b() {
99
- return i.pageSize ?? 20;
64
+ return u.pageSize ?? 20;
100
65
  }
101
66
  function x() {
102
67
  return {
@@ -119,11 +84,11 @@ function d(e, t = {}) {
119
84
  }
120
85
  function w() {
121
86
  return JSON.stringify([
122
- n,
123
- i.baseUrl,
87
+ c,
88
+ u.baseUrl,
124
89
  b(),
125
- i.defaultOrderBy,
126
- s,
90
+ u.defaultOrderBy,
91
+ d,
127
92
  p.page,
128
93
  p.sortCriteria,
129
94
  p.search
@@ -147,26 +112,26 @@ function d(e, t = {}) {
147
112
  g?.abort();
148
113
  let e = new AbortController();
149
114
  g = e;
150
- let { page: t, sortCriteria: r, search: s } = p, d = b(), f = i.fetcher ?? fetch, m = i.onUnauthorized;
115
+ let { page: t, sortCriteria: i, search: l } = p, d = b(), f = u.fetcher ?? fetch, m = u.onUnauthorized;
151
116
  S({
152
117
  loading: !0,
153
118
  error: null
154
119
  });
155
- let h = a({
120
+ let h = n({
156
121
  page: t,
157
122
  pageSize: d,
158
- sortCriteria: r,
159
- defaultOrderBy: i.defaultOrderBy,
160
- search: s,
161
- fixedFilter: i.fixedFilter
162
- }), _ = `${i.baseUrl ?? window.location.origin}${n}${h}`, v = async (t) => {
123
+ sortCriteria: i,
124
+ defaultOrderBy: u.defaultOrderBy,
125
+ search: l,
126
+ fixedFilter: u.fixedFilter
127
+ }), _ = `${u.baseUrl ?? window.location.origin}${c}${h}`, v = async (t) => {
163
128
  let n;
164
129
  try {
165
130
  n = await f(t, { signal: e.signal });
166
131
  } catch (e) {
167
- throw e?.name === "AbortError" ? e : l(e);
132
+ throw e?.name === "AbortError" ? e : o(e);
168
133
  }
169
- if (!n.ok) throw n.status === 401 && m && m(n), new c(await o(n));
134
+ if (!n.ok) throw n.status === 401 && m && m(n), new a(await r(n));
170
135
  return n.json();
171
136
  };
172
137
  (async () => {
@@ -198,7 +163,7 @@ function d(e, t = {}) {
198
163
  });
199
164
  }).catch((t) => {
200
165
  e.signal.aborted || t?.name !== "AbortError" && S({
201
- error: u(t),
166
+ error: s(t),
202
167
  data: [],
203
168
  totalCount: 0
204
169
  });
@@ -232,16 +197,16 @@ function d(e, t = {}) {
232
197
  y() && (v = null, T());
233
198
  },
234
199
  update(e, t = {}) {
235
- let r = f(t.fixedFilter), a = r !== s;
236
- n = e, i = t, s = r, a && (p = {
200
+ let n = l(t.fixedFilter), r = n !== d;
201
+ c = e, u = t, d = n, r && (p = {
237
202
  ...p,
238
203
  page: 0
239
204
  }), C(), T();
240
205
  }
241
206
  };
242
207
  }
243
- function f(e) {
208
+ function l(e) {
244
209
  return e ? JSON.stringify(e) : "";
245
210
  }
246
211
  //#endregion
247
- export { r as a, i, a as n, n as r, d as t };
212
+ export { c as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iyulab/flex-table",
3
- "version": "0.54.0",
3
+ "version": "0.55.0",
4
4
  "description": "A minimalist, input-centric data grid web component",
5
5
  "type": "module",
6
6
  "main": "./dist/flex-table.js",
@@ -28,7 +28,7 @@ npm install @lit/react react
28
28
  | `@iyulab/flex-table` | Registers `<flex-table>`; exports `FlexTable`, types, `exportData`, `renderCell`, `flexTableLocale`, `RowSelectionState`, `UndoStack`, `effectiveAlign` |
29
29
  | `@iyulab/flex-table/react` | `FlexTableReact`, `useODataSource`, `useArraySource` |
30
30
  | `@iyulab/flex-table/odata` | No React: `createODataSource` (the source `useODataSource` adapts), `ODataSourceController` (Lit), `buildODataQuery`, `buildSearchExpression`, `parseOrderBy` |
31
- | `@iyulab/flex-table/array` | Pure helper, no React: `computeArrayView` |
31
+ | `@iyulab/flex-table/array` | No React: `createArraySource` (same members as `createODataSource`, plus `update(rows)`), `ArraySourceController` (Lit), `computeArrayView` |
32
32
 
33
33
  ## Quick start
34
34
 
@@ -181,6 +181,7 @@ Pure helpers:
181
181
  ```ts
182
182
  import { buildODataQuery, buildSearchExpression, parseOrderBy } from '@iyulab/flex-table/odata';
183
183
  import { computeArrayView } from '@iyulab/flex-table/array';
184
+ // createArraySource / ArraySourceController: the in-memory source with createODataSource's members + update(rows)
184
185
 
185
186
  buildSearchExpression('red shirt'); // '"red" AND "shirt"' ('' → undefined)
186
187
  parseOrderBy('name desc'); // [{ key: 'name', direction: 'desc' }]