@akshar-technosoft/ui 1.2.1 → 1.3.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 +51 -8
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +83 -13
- package/dist/index.d.ts +83 -13
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Colors ride the app's theme CSS variables (`--sidebar`, `--border`, `--backgroun
|
|
|
15
15
|
| `DataTable`, `DataTableProps`, `GroupColumnDef` | The data table and its types |
|
|
16
16
|
| `DataTableActionBar`, `DataTableActionBarAction`, `DataTableActionBarSelection` | Floating bulk-action bar for selected rows |
|
|
17
17
|
| `getTableExportView`, `TableExportView`, `TableExportColumn` | Export snapshot builder (see [Export](#export)) |
|
|
18
|
-
| `readPersistedExternal`, `PersistConfig`, `PersistGroupRegistry` | Persist helpers (see [Persist](#persist)) |
|
|
18
|
+
| `readPersistedExternal`, `useTableFilterParam`, `FilterKeys`, `PersistConfig`, `PersistGroupRegistry`, `PersistGroupId`, `PersistSlotId`, `PersistSlotFor` | Persist + URL-sync helpers (see [Persist](#persist)) |
|
|
19
19
|
| `DataTemplate`, `DataTemplateProps` + action-bar components | Card/grid sibling of DataTable |
|
|
20
20
|
| `AppContainer`, `AppHeader`, `AppContent`, `AppContentHeader`, `AppContentFooter`, `AppSheet`, `AppFooter` | Page layout system |
|
|
21
21
|
| `GeneralHelper` | Static utils: `formatDate`, `formatINR`, `toProperCase`, `copyToClipboard`, `CopyToClipboard`, `StatusBadge` |
|
|
@@ -238,7 +238,7 @@ The toolbar's **Aggregations menu** (user-facing toggles, per-column function su
|
|
|
238
238
|
|
|
239
239
|
## Persist
|
|
240
240
|
|
|
241
|
-
Opt-in state persistence across unmount/back-navigation:
|
|
241
|
+
Opt-in state persistence across unmount/back-navigation, **and** the URL query string:
|
|
242
242
|
|
|
243
243
|
```tsx
|
|
244
244
|
persist="qc-completed" // shorthand for { key }
|
|
@@ -247,13 +247,32 @@ persist={{ key: "qc-completed", group: "inhouse", local: true }}
|
|
|
247
247
|
|
|
248
248
|
**Persists:** column filter values, global search text, the field-picker selection, external (server) filter values, pagination (`pagination: false` to skip page state). **Never persists:** selection, sorting, visibility, pinning, sizing.
|
|
249
249
|
|
|
250
|
-
**
|
|
250
|
+
**Three legs, one consistency rule:** in-memory (always, whenever `persist` is set) + the URL query string (always) + localStorage (opt-in via `local: true`). While a persist-enabled table is mounted, **mem and the URL always mirror each other** — mem is the primary; `local: true` folds localStorage into the same guarantee. This is what makes each of the following work without you wiring anything:
|
|
251
251
|
|
|
252
|
-
|
|
252
|
+
- Filter something, navigate away (even a bare nav-link click to the same route, no remount), come back — filter AND url both restored, always in sync.
|
|
253
|
+
- Filter something, hit refresh — the URL alone survives it, no `local` needed.
|
|
254
|
+
- Share/bookmark a URL with `?LotNo=X` — deep-links straight into a pre-filtered table.
|
|
255
|
+
- `local: true` additionally survives a full close/reopen of the browser (URL alone doesn't).
|
|
256
|
+
|
|
257
|
+
**The one protocol you should know if you're driving the URL yourself** (`useTableFilterParam`, a manual `setSearchParams`, a `<TypedLink>` with a query): an **absent** param is never treated as a delete — a bare navigation naturally drops the query string without the table's state changing, so on remount/re-render a missing param gets **restored** into the URL from mem, not treated as "user cleared it." To actually clear a filter from outside the table, write the param **present but empty** (`?LotNo=`) — `useTableFilterParam`'s setter already does this for you.
|
|
258
|
+
|
|
259
|
+
**How it works otherwise:** values are stored per **filter identity** (`key`/`id`), not as raw table state, in an in-memory map — a refresh with no `local` starts clean except for whatever the URL still carries. `local: true` mirrors to localStorage (`atsui:tbl:v1:*`); note it's per-browser, not per-login, and not live across tabs (another already-open tab picks up a change on its next mount/navigation, not instantly). Restore is synchronous, seeded before first render — no flash of unfiltered data. Dates serialize as one URL param, ISO, comma-separated for a range (`?Date=2026-07-01,2026-07-09`). Per-filter opt-out: `persist: false` on that filter (excluded from mem, localStorage, AND the URL).
|
|
260
|
+
|
|
261
|
+
**`key` must be app-unique.** Two mounted tables on one key overwrite each other's state (dev warning). One page with two tables = two keys. Two DIFFERENT tables sharing a filter id (or both using the global search) on the SAME route will cross-talk through the URL, since query params are one flat namespace per page — usually not a real scenario since that'd mean two tables on one screen filtering the same field.
|
|
262
|
+
|
|
263
|
+
### Reading/writing a table's filter from OUTSIDE it
|
|
264
|
+
|
|
265
|
+
`useTableFilterParam(filters, key)` gives you a live, typed `[value, setValue]` for one filter — typed straight off the same `filters` array you already pass to `<DataTable>`, no separate registry to keep in sync. Works because of the URL-mirror guarantee above: reading the URL param IS reading the filter's live value.
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
const [lotNo, setLotNo] = useTableFilterParam(lotReportFilters, "LotNo")
|
|
269
|
+
// lotNo: string | undefined — live, updates as the table's own filter changes
|
|
270
|
+
// setLotNo(undefined) clears it (writes the present-but-empty param, not a delete)
|
|
271
|
+
```
|
|
253
272
|
|
|
254
273
|
### Shared slots — one value across pages
|
|
255
274
|
|
|
256
|
-
`shared: "party"` on a filter publishes its VALUE into a named slot; every table in the same `persist.group` with a filter bound to that slot picks it up — even when the pages use different filter `key`s, or one side is an `externalSearch` filter (values are stored by identity, which is what makes this possible). Search a party on one page; the next page in the group opens already filtered to it. Two tables mounted at once live-sync through the slot.
|
|
275
|
+
`shared: "party"` on a filter publishes its VALUE into a named slot; every table in the same `persist.group` with a filter bound to that slot picks it up — even when the pages use different filter `key`s, or one side is an `externalSearch` filter (values are stored by identity, which is what makes this possible). Search a party on one page; the next page in the group opens already filtered to it. Two tables mounted at once live-sync through the slot. Shared filters also go through the URL leg (a redirect from OUTSIDE the group has no other way to reach a `shared` filter), so the same absent-vs-empty protocol above applies to them too.
|
|
257
276
|
|
|
258
277
|
`shared` REQUIRES `persist.group`. Slots never cross groups — "party" in group `inhouse` is not "party" in `sales`. A `shared` filter on a group-less table is inert and warns.
|
|
259
278
|
|
|
@@ -271,7 +290,29 @@ declare module "@akshar-technosoft/ui" {
|
|
|
271
290
|
}
|
|
272
291
|
```
|
|
273
292
|
|
|
274
|
-
After this `group: "inhose"` is a compile error and `shared` autocompletes.
|
|
293
|
+
After this `group: "inhose"` is a compile error, and `shared` autocompletes/type-checks against **only that one table's own group's slots** — not the app-wide union of every group's slots. That narrowing is automatic when `filters` is written inline in JSX alongside `persist`:
|
|
294
|
+
|
|
295
|
+
```tsx
|
|
296
|
+
<DataTable
|
|
297
|
+
persist={{ key: "qc-completed", group: "inhouse" }}
|
|
298
|
+
filters={[
|
|
299
|
+
{ key: "PartyCode", placeholder: "Party", shared: "party" }, // ✓ autocompletes "party" | "material"
|
|
300
|
+
]}
|
|
301
|
+
/>
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
It does **NOT** auto-narrow when `filters` is a separately declared `const` — the common pattern in this codebase — because that array gets its type at its own declaration, independent of a `persist` prop written later in the same component. Pass the group as `DataTableProps`'s **second** type parameter there:
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
// QualityCheckPending.tsx — table's persist.group is "QC"
|
|
308
|
+
const filters: DataTableProps<ORPReportQualityCheckType, "QC">["filters"] = [
|
|
309
|
+
{ key: "Code", placeholder: "Taka No.", type: "text" },
|
|
310
|
+
{ key: "RefNo", placeholder: "Ref No", type: "text", shared: "taka" }, // ← now autocompletes/checks against ONLY group "QC"'s slots ("taka"), not every group's
|
|
311
|
+
// ...
|
|
312
|
+
]
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Without the `, "QC"` the array still compiles (falls back to the broad union of every group's slots, same as before this existed) — the second type param is what turns that into a real, group-scoped check.
|
|
275
316
|
|
|
276
317
|
### The double API call, and how to fix it
|
|
277
318
|
|
|
@@ -413,8 +454,8 @@ Every prop `DataTable` accepts — this is the whole surface, nothing else exist
|
|
|
413
454
|
| `placement` | no (`"bar"`) | `"menu"` = behind the field picker + chip |
|
|
414
455
|
| `externalSearch` | no (`false`) | Server-side |
|
|
415
456
|
| `width`, `icon`, `dateFormat` | no | Cosmetic |
|
|
416
|
-
| `shared` | no | Group-wide value slot (needs `persist.group`) |
|
|
417
|
-
| `persist` | no (`true`) | `false` = exclude from persistence |
|
|
457
|
+
| `shared` | no | Group-wide value slot (needs `persist.group`); typed against `DataTableProps`'s 2nd type param, see [Persist](#persist) |
|
|
458
|
+
| `persist` | no (`true`) | `false` = exclude from persistence (mem, localStorage, AND the URL) |
|
|
418
459
|
|
|
419
460
|
`GroupColumnDef` extras beyond TanStack: `headerAlign`, `columns`, `enablePinning`, `enableColumnVisibility`, `enableHeaderMenu`, `reserveMenuSpace`, `customFooter`, `exportValue`, `exportHeader` ([Columns](#columns)). `PersistConfig`: `key` (required), `group`, `local`, `pagination` ([Persist](#persist)). `AggregationConfig`: `enabled`, `functions`, `format`, `accessor` ([Aggregations](#aggregations)). `PaginationConfig`: `pageSize`, `pageSizeOptions`, `showInfo`, `showPageNumbers` ([Pagination](#pagination)).
|
|
420
461
|
|
|
@@ -426,6 +467,8 @@ Action bar: `DataTableActionBar` takes `table`, `visible` (default: auto — sho
|
|
|
426
467
|
- **Global search can't find a value that's visibly on screen** → that column has no accessor; add `accessorFn`.
|
|
427
468
|
- **A `key` filter does nothing** → the path doesn't match the data shape (it's a data path, not a column id).
|
|
428
469
|
- **Wrong rows selected after a refetch** → pass `getRowId`.
|
|
470
|
+
- **Filter cleared from outside (`useTableFilterParam`, manual `setSearchParams`) comes right back** → you deleted the param instead of setting it to `""`. Absent = restore-from-mem, present-but-empty = clear. `useTableFilterParam`'s setter already does this correctly.
|
|
471
|
+
- **`shared` autocompletes every group's slots, not just this table's** → `filters` is a separately declared `const`; add the group as `DataTableProps`'s 2nd type param: `DataTableProps<Row, "QC">["filters"]`. See [Persist](#persist).
|
|
429
472
|
- **`shared` does nothing** → no `persist.group` on the table (warns).
|
|
430
473
|
- **Two tables fight over saved state** → same `persist.key` (warns).
|
|
431
474
|
- **Field picker missing** → needs `enableGlobalFilter` on AND at least one `placement: 'menu'` filter.
|