@iyulab/flex-table 0.52.0 → 0.53.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,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.53.0] - 2026-10-07
4
+
5
+ ### Added
6
+
7
+ - **`createODataSource(url, options)`** (`@iyulab/flex-table/odata`) — the OData source without React. It is what
8
+ `useODataSource` now runs on: the same options and state, the request, `@odata.nextLink` following, the page
9
+ fallback, the `fixedFilter` reset, `enabled`, cancellation and the structured `error`. `getState()` · `subscribe()` ·
10
+ `setPage` · `setSort` · `setSearch` · `refresh` · `update(url, options)`. Requests go out while someone is subscribed,
11
+ and changes in the same tick become one request.
12
+ - **`ODataSourceController`** — a Lit reactive controller over that source: subscribes when the element connects,
13
+ cancels when it disconnects, re-renders it on every change. A custom-element list page gets the same server paging
14
+ a React page gets from the hook.
15
+
16
+ ### Changed
17
+
18
+ - `useODataSource` reads `fetcher` and `onUnauthorized` when each request starts. A fresh function on every render no
19
+ longer needs `useCallback`; before, the one captured by the last request kept being used.
20
+
3
21
  ## [0.52.0] - 2026-10-07
4
22
 
5
23
  ### Changed
package/README.md CHANGED
@@ -758,7 +758,7 @@ const source = useODataSource('/api/orders', {
758
758
  | `onUnauthorized` | — | Called on `401` responses, before the generic error is set. A `403` (signed in, not permitted) does not call it — it surfaces as `error` |
759
759
  | `enabled` | `true` | While `false`, no request is made and `loading` stays `true` — see below |
760
760
 
761
- `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.
761
+ `fetcher`/`onUnauthorized` are read when each request starts, so a fresh function on every render is fine — it neither refetches nor is ignored.
762
762
 
763
763
  Changing `fixedFilter` resets the page to `0`, the same way `setSearch` and `onSortChange`
764
764
  already do. All three change the size of the result set, so keeping the old `$skip` would
@@ -828,7 +828,7 @@ The hook returns:
828
828
 
829
829
  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.
830
830
 
831
- The quoting/escaping logic above is also available standalone as `buildSearchExpression(term)`, for consumers that need the same `$search` encoding without the pagination hook (e.g. a typeahead/combobox that isn't a table). `parseOrderBy(orderBy)` (`'a asc, b desc'` → `SortCriteria[]`) is exported the same way, for consumers driving a sort UI that isn't `useODataSource` either. The `./odata` entry holds only pure functions and does not load React, so an app without React can use it (the hooks live on `./react`):
831
+ The quoting/escaping logic above is also available standalone as `buildSearchExpression(term)`, for consumers that need the same `$search` encoding without the pagination hook (e.g. a typeahead/combobox that isn't a table). `parseOrderBy(orderBy)` (`'a asc, b desc'` → `SortCriteria[]`) is exported the same way, for consumers driving a sort UI that isn't `useODataSource` either. The `./odata` entry does not load React, so an app without React can use it (the hooks live on `./react`):
832
832
 
833
833
  ```ts
834
834
  import { buildSearchExpression, parseOrderBy } from '@iyulab/flex-table/odata';
@@ -852,6 +852,49 @@ buildODataQuery({
852
852
  // '?$filter=IsActive eq true&$orderby=name desc&$count=true&$top=20&$skip=40&$search=%22red%22%20AND%20%22shirt%22'
853
853
  ```
854
854
 
855
+ ### OData Source without React
856
+
857
+ `useODataSource` is a thin adapter over a framework-neutral source, and that source is public: `createODataSource(url, options)` takes the same options and does everything the hook does — the request, `@odata.nextLink` following, page fallback, `fixedFilter` reset, `enabled`, cancellation and the structured `error`. Use it from a Lit element, another framework, or plain code.
858
+
859
+ ```ts
860
+ import { createODataSource } from '@iyulab/flex-table/odata';
861
+
862
+ const orders = createODataSource<Order>('/api/orders', { pageSize: 20 });
863
+ const off = orders.subscribe(() => render(orders.getState())); // the first subscriber starts loading
864
+ orders.setSort([{ key: 'name', direction: 'asc' }]); // also: setPage, setSearch, refresh
865
+ orders.update('/api/orders', { pageSize: 20, fixedFilter: { IsActive: true } }); // changed options
866
+ off(); // the last unsubscribe cancels a request in flight
867
+ ```
868
+
869
+ | Member | Description |
870
+ |---|---|
871
+ | `getState()` | `{ data, totalCount, loading, error, page, sortCriteria, search }` — the same fields the hook returns. A new object only when something changed |
872
+ | `subscribe(listener)` | Called on every change; returns the unsubscribe function. Requests go out only while someone is subscribed |
873
+ | `setPage(page)` | Zero-based |
874
+ | `setSort(criteria)` / `setSearch(term)` | Change the sort or search and go back to page 0 |
875
+ | `refresh()` | Re-read with the same conditions (nothing while `enabled: false`) |
876
+ | `update(url, options)` | New options. Only a changed request re-reads; a changed `fixedFilter` value goes back to page 0; `initial*` are read at creation only |
877
+
878
+ Changes made in the same tick become one request with the final conditions, so `setSearch` followed by `setPage` does not send the intermediate one.
879
+
880
+ For a Lit element, `ODataSourceController` ties a source to the element's lifecycle — it subscribes when the element connects, cancels when it disconnects, and re-renders it on every change:
881
+
882
+ ```ts
883
+ import { ODataSourceController } from '@iyulab/flex-table/odata';
884
+
885
+ class OrdersPage extends LitElement {
886
+ private orders = new ODataSourceController<Order>(this, '/api/orders', { pageSize: 20 });
887
+
888
+ render() {
889
+ const { data, loading, error } = this.orders.state;
890
+ return html`
891
+ ${error ? html`<p role="alert">${error.message}</p>` : ''}
892
+ <flex-table data-mode="server" .data=${data} .loading=${loading}
893
+ @sort-change=${(e: CustomEvent) => this.orders.source.setSort(e.detail.criteria)}></flex-table>`;
894
+ }
895
+ }
896
+ ```
897
+
855
898
  ### Array Source Hook (React)
856
899
 
857
900
  `useArraySource(data, options)` runs search/sort/pagination over an in-memory array and