@iyulab/flex-table 0.54.0 → 0.55.1

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,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.55.1] - 2026-10-07
4
+
5
+ ### Fixed
6
+
7
+ - **The empty-state texts follow the locale.** `emptyMessage` and `noMatchingMessage` defaulted to the English
8
+ strings themselves, so a table with no rows said "No data" in every language. They now default to empty and the
9
+ table draws the locale strings `noData` / `noMatchingData` (Korean built in); a value you set still wins.
10
+
11
+ ## [0.55.0] - 2026-10-07
12
+
13
+ ### Added
14
+
15
+ - **`createArraySource(rows, options)`** and **`ArraySourceController`** (`@iyulab/flex-table/array`) — the in-memory
16
+ source without React, with the members of `createODataSource` (`getState`, `subscribe`, `setPage`, `setSort`,
17
+ `setSearch`, `refresh`) plus `update(rows, options)`. A custom-element screen can now switch between a server and
18
+ an in-memory source the way a React screen switches between `useODataSource` and `useArraySource`.
19
+
3
20
  ## [0.54.0] - 2026-10-07
4
21
 
5
22
  ### Added
package/README.md CHANGED
@@ -111,8 +111,8 @@ guarantee about a *constrained* host. `height-model.browser.test.ts` pins both s
111
111
  | `selectionMode` | `selection-mode` | `'single' \| 'multi'` | `'multi'` | Row selection mode |
112
112
  | `dataMode` | `data-mode` | `'client' \| 'server'` | `'client'` | Client-side or server-side data processing |
113
113
  | `footerData` | `footer-data` | `Record<string, string \| TemplateResult> \| null` | `null` | Footer/summary row data (keys match column keys) |
114
- | `emptyMessage` | `empty-message` | `string` | `'No data'` | Shown when `data` is empty |
115
- | `noMatchingMessage` | `no-matching-message` | `string` | `'No matching data'` | Shown when `data` has rows but every one is hidden by an active column filter |
114
+ | `emptyMessage` | `empty-message` | `string` | `''` | Shown when `data` is empty. Empty uses the locale string `noData` ('No data') |
115
+ | `noMatchingMessage` | `no-matching-message` | `string` | `''` | Shown when `data` has rows but every one is hidden by an active column filter. Empty uses the locale string `noMatchingData` |
116
116
  | `error` | — | `{ message: string } \| null` | `null` | The last load failure. While set, `error.message` is shown as an alert where the rows or the empty state would be, so a failed query does not look like "no data". Pass a data source's `error` as is |
117
117
  | `stylesheets` | — | `CSSStyleSheet[]` | `[]` | Constructable stylesheets adopted into the shadow root alongside the grid's own styles — the escape hatch for styling content a `render` function inserts, since document CSS doesn't cross the shadow boundary. Reassigning swaps the previous set, it doesn't accumulate |
118
118
 
@@ -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,4 @@
1
- import { n as e } from "./locale-B1SiVDS1.js";
1
+ import { n as e } from "./locale-Cfm-vJji.js";
2
2
  import { n as t, t as n } from "./sorting-CjfjRxwL.js";
3
3
  import { LitElement as r, css as i, html as a, nothing as o } from "lit";
4
4
  import { customElement as s, property as c, state as l } from "lit/decorators.js";
@@ -1756,7 +1756,7 @@ var K = {
1756
1756
  lte: "≤"
1757
1757
  }, q = 120, J = 40, Y = 20, X = 32, Z = 5, Xe = 200, Q = class extends r {
1758
1758
  constructor(...e) {
1759
- super(...e), this.columns = [], this._data = [], this._inUndoRedo = !1, this.clearUndoOnDataChange = !1, this.clearSelectionOnDataChange = !1, this._rowHeightFromCss = X, this._targetFloor = 0, 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.error = null, 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._headerCol = null, this._focusedCellEl = null, this._editingCell = null, this._sortCriteria = [], this._selection = new oe(), this._editing = new se(), this._rowSelection = new k(), this._undo = new A(), 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._warnedUnconstrained = !1, this._onHostFocus = (e) => {
1759
+ super(...e), this.columns = [], this._data = [], this._inUndoRedo = !1, this.clearUndoOnDataChange = !1, this.clearSelectionOnDataChange = !1, this._rowHeightFromCss = X, this._targetFloor = 0, this.showRowNumbers = !1, this.theme = void 0, this.maxRows = 0, this.editable = !0, this.showFilters = !1, this.showContextMenu = !1, this.frozenRows = 0, this.emptyMessage = "", this.noMatchingMessage = "", this.error = null, 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._headerCol = null, this._focusedCellEl = null, this._editingCell = null, this._sortCriteria = [], this._selection = new oe(), this._editing = new se(), this._rowSelection = new k(), this._undo = new A(), 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._warnedUnconstrained = !1, this._onHostFocus = (e) => {
1760
1760
  e.composedPath()[0] !== this || this._activeCell || this._headerCol !== null || this.visibleColumns.length !== 0 && this.matches(":focus-visible") && (this._visibleRowCount > 0 ? (this._selection.setActive(0, 0), this._syncActiveCell()) : this._headerCol = 0);
1761
1761
  }, this._onFocusIn = (e) => {
1762
1762
  let t = e.composedPath()[0];
@@ -4158,7 +4158,7 @@ var K = {
4158
4158
  for (let e = c; e < l; e++) h.push(this._renderHeaderCell(t[e], e));
4159
4159
  let g = this._colDragIndicatorLeft == null ? "" : a`<div class="ft-drop-indicator" style="left:${this._colDragIndicatorLeft}px"></div>`;
4160
4160
  if (this.error || this.data.length === 0 || this._visibleRowCount === 0) {
4161
- let e = this.error ? a`<div class="ft-empty ft-error" role="alert">${this.error.message}</div>` : a`<div class="ft-empty">${this.data.length === 0 ? this.emptyMessage : this.noMatchingMessage}</div>`;
4161
+ let t = this.error ? a`<div class="ft-empty ft-error" role="alert">${this.error.message}</div>` : a`<div class="ft-empty">${this.data.length === 0 ? this.emptyMessage || e("noData") : this.noMatchingMessage || e("noMatchingData")}</div>`;
4162
4162
  return a`
4163
4163
  ${r}
4164
4164
  <div class="ft-header" role="row" aria-rowindex="1" style="width: ${s}px; height: ${i}px;">
@@ -4166,7 +4166,7 @@ var K = {
4166
4166
  ${h}
4167
4167
  ${g}
4168
4168
  </div>
4169
- ${e}
4169
+ ${t}
4170
4170
  `;
4171
4171
  }
4172
4172
  let { start: _, end: v } = this.visibleRange, y = this._frozenRowCount, b = [];
@@ -65,11 +65,12 @@ export declare class FlexTable extends LitElement {
65
65
  showContextMenu: boolean;
66
66
  /** Number of rows to freeze at the top (always visible during vertical scroll). */
67
67
  frozenRows: number;
68
- /** Message shown when `data` is empty (no rows at all). Default: 'No data'. */
68
+ /** Message shown when `data` is empty (no rows at all). Empty (the default) uses the locale string (`noData` — 'No data'). */
69
69
  emptyMessage: string;
70
70
  /**
71
71
  * Message shown when `data` has rows but every one is hidden by the active
72
- * column filters (0 visible rows, non-empty `data`). Default: 'No matching data'.
72
+ * column filters (0 visible rows, non-empty `data`). Empty (the default) uses the locale string (`noMatchingData` —
73
+ * 'No matching data').
73
74
  */
74
75
  noMatchingMessage: string;
75
76
  /**
@@ -1,3 +1,3 @@
1
- import { a as e, i as t, n, o as r, r as i, t as a } from "./flex-table-DlvIBu4z.js";
2
- import { t as o } from "./locale-B1SiVDS1.js";
1
+ import { a as e, i as t, n, o as r, r as i, t as a } from "./flex-table-D3E9npd9.js";
2
+ import { t as o } from "./locale-Cfm-vJji.js";
3
3
  export { a as FlexTable, e as RowSelectionState, t as UndoStack, n as effectiveAlign, i as exportData, o as flexTableLocale, r as renderCell };
@@ -9,6 +9,8 @@ t.register("en", {
9
9
  addCommentPlaceholder: "Add a comment… (Ctrl+Enter to save)",
10
10
  cancel: "Cancel",
11
11
  save: "Save",
12
+ noData: "No data",
13
+ noMatchingData: "No matching data",
12
14
  clear: "Clear",
13
15
  blankCells: "Blank cells",
14
16
  emptyOnly: "Empty only",
@@ -79,6 +81,8 @@ t.register("en", {
79
81
  addCommentPlaceholder: "메모 입력… (Ctrl+Enter 로 저장)",
80
82
  cancel: "취소",
81
83
  save: "저장",
84
+ noData: "데이터 없음",
85
+ noMatchingData: "일치하는 데이터 없음",
82
86
  clear: "지우기",
83
87
  blankCells: "빈 셀",
84
88
  emptyOnly: "빈 값만",
package/dist/locale.d.ts CHANGED
@@ -25,7 +25,7 @@
25
25
  * (아이콘 자리의 글리프)는 어느 로케일에서도 같은 것을 뜻하며, 번역하면 오히려 읽히지 않는다.
26
26
  * 그 판단은 «번역하지 않기로 한 것»이지 «빠뜨린 것»이 아니다.
27
27
  */
28
- export type FlexTableMessageKey = 'showHiddenColumns' | 'columnMenu' | 'cellActions' | 'noColumnsDefined' | 'addCommentPlaceholder' | 'cancel' | 'save' | 'clear' | 'blankCells' | 'emptyOnly' | 'nonEmptyOnly' | 'blankAll' | 'all' | 'contains' | 'startsWith' | 'endsWith' | 'wildcard' | 'searchPlaceholder' | 'valuePlaceholder' | 'fromPlaceholder' | 'toPlaceholder' | 'findPlaceholder' | 'findPrevious' | 'findNext' | 'matchCase' | 'wholeCell' | 'closeFind' | 'findNoResults' | 'replaceWithPlaceholder' | 'replace' | 'replaceAll' | 'findInput' | 'replaceInput' | 'filterFor' | 'filterMatchMode' | 'filterText' | 'conditionOperator' | 'conditionValue' | 'conditionJoin' | 'sortAscending' | 'sortDescending' | 'filter' | 'clearFilter' | 'hideColumn' | 'showColumn' | 'autoFitWidth' | 'wider' | 'narrower' | 'columnMenuFor' | 'columnMenuRegion' | 'copy' | 'insertRowAbove' | 'insertRowBelow' | 'deleteRow' | 'selectRow' | 'selectAllRows' | 'filterByThisValue' | 'addComment' | 'editComment' | 'deleteComment' | 'booleanTrue' | 'booleanFalse' | 'dropFileToImport' | 'notANumber' | 'notADate' | 'notADateTime' | 'notInList' | 'requestFailed' | 'networkFailed';
28
+ export type FlexTableMessageKey = 'showHiddenColumns' | 'columnMenu' | 'cellActions' | 'noColumnsDefined' | 'addCommentPlaceholder' | 'cancel' | 'save' | 'noData' | 'noMatchingData' | 'clear' | 'blankCells' | 'emptyOnly' | 'nonEmptyOnly' | 'blankAll' | 'all' | 'contains' | 'startsWith' | 'endsWith' | 'wildcard' | 'searchPlaceholder' | 'valuePlaceholder' | 'fromPlaceholder' | 'toPlaceholder' | 'findPlaceholder' | 'findPrevious' | 'findNext' | 'matchCase' | 'wholeCell' | 'closeFind' | 'findNoResults' | 'replaceWithPlaceholder' | 'replace' | 'replaceAll' | 'findInput' | 'replaceInput' | 'filterFor' | 'filterMatchMode' | 'filterText' | 'conditionOperator' | 'conditionValue' | 'conditionJoin' | 'sortAscending' | 'sortDescending' | 'filter' | 'clearFilter' | 'hideColumn' | 'showColumn' | 'autoFitWidth' | 'wider' | 'narrower' | 'columnMenuFor' | 'columnMenuRegion' | 'copy' | 'insertRowAbove' | 'insertRowBelow' | 'deleteRow' | 'selectRow' | 'selectAllRows' | 'filterByThisValue' | 'addComment' | 'editComment' | 'deleteComment' | 'booleanTrue' | 'booleanFalse' | 'dropFileToImport' | 'notANumber' | 'notADate' | 'notADateTime' | 'notInList' | 'requestFailed' | 'networkFailed';
29
29
  /** 이 패키지의 chrome 문자열 묶음. 소비자가 `register()` 로 언어를 더하거나 문구를 덮을 수 있다. */
30
30
  export declare const flexTableLocale: import("@iyulab/components/dist/utilities/Locale.js").LocaleNamespace<FlexTableMessageKey>;
31
31
  /** 짧은 조회 별칭 — 렌더 코드가 읽히게 유지한다. */
@@ -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--q5s2Oti.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
- import { t as e } from "./flex-table-DlvIBu4z.js";
2
- import { a as t, t as n } from "./source-CwbD-6ea.js";
1
+ import { t as e } from "./flex-table-D3E9npd9.js";
2
+ import { i as t } from "./query-Dw4iO0ir.js";
3
+ import { t as n } from "./source--q5s2Oti.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
- 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
1
+ import { n as e } from "./locale-Cfm-vJji.js";
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.1",
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
 
@@ -21,8 +21,8 @@ All names below are members of the `FlexTable` class (`<flex-table>`).
21
21
  | `selectionMode` | `selection-mode` | `'single' \| 'multi'` | `'multi'` | |
22
22
  | `dataMode` | `data-mode` | `'client' \| 'server'` | `'client'` | Server mode only emits sort/filter events |
23
23
  | `footerData` | `footer-data` | `Record<string, string \| TemplateResult> \| null` | `null` | Summary row keyed by column key |
24
- | `emptyMessage` | `empty-message` | `string` | `'No data'` | |
25
- | `noMatchingMessage` | `no-matching-message` | `string` | `'No matching data'` | All rows hidden by filters |
24
+ | `emptyMessage` | `empty-message` | `string` | `''` | Empty: locale `noData` |
25
+ | `noMatchingMessage` | `no-matching-message` | `string` | `''` | All rows hidden by filters. Empty: locale `noMatchingData` |
26
26
  | `error` | — | `{ message: string } \| null` | `null` | Load failure shown as an alert instead of rows / empty state (a source's `error` as is) |
27
27
  | `loading` | `loading` | `boolean` | `false` | Loading overlay + `aria-busy` |
28
28
  | `importEnabled` | `import-enabled` | `boolean` | `false` | Drag-and-drop `.xlsx` / `.csv` import |
@@ -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' }]