@metaobjectsdev/tanstack 0.24.4 → 0.25.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 +14 -0
- package/dist/cell-renderers.d.ts +28 -0
- package/dist/cell-renderers.d.ts.map +1 -1
- package/dist/cell-renderers.js +79 -3
- package/dist/cell-renderers.js.map +1 -1
- package/dist/entity-fetcher.d.ts +20 -4
- package/dist/entity-fetcher.d.ts.map +1 -1
- package/dist/entity-fetcher.js +8 -4
- package/dist/entity-fetcher.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/cell-renderers.tsx +98 -4
- package/src/entity-fetcher.tsx +34 -6
- package/src/index.ts +6 -1
package/README.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
TanStack runtime for metaobjects: `EntityFetcherProvider`, `<EntityGrid>`, `CellRendererProvider`, and `defaultCellRenderers`. Pairs with `@metaobjectsdev/codegen-ts-tanstack`, which generates `<Entity>.hooks.ts` (every entity) plus `<Entity>.columns.tsx` and `<Entity>.grid.ts` (entities declaring a `layout.dataGrid`) that import from this package. `<EntityGrid>` is fully controlled — pair the generated columns with the generated `use<Entity><Grid>Grid()` hook, which returns its whole prop shape.
|
|
4
4
|
|
|
5
|
+
## Install: TanStack Table v8 is required
|
|
6
|
+
|
|
7
|
+
This package supports `@tanstack/react-table` **v8** (`^8.20.0`). The registry's
|
|
8
|
+
`latest` is v9, which removed `useReactTable` and `getCoreRowModel` — both used by
|
|
9
|
+
`<EntityGrid>` — so v9 is a migration, not a version bump.
|
|
10
|
+
|
|
11
|
+
Install the supported major explicitly:
|
|
12
|
+
|
|
13
|
+
npm i @tanstack/react-table@^8.21.3
|
|
14
|
+
|
|
15
|
+
A bare `npm i @tanstack/react-table` resolves v9, which does not satisfy this package's
|
|
16
|
+
peer range. npm then fails **every subsequent install in that project** with `ERESOLVE`
|
|
17
|
+
until v8 is pinned — so this is worth getting right the first time.
|
|
18
|
+
|
|
5
19
|
## Install
|
|
6
20
|
|
|
7
21
|
```bash
|
package/dist/cell-renderers.d.ts
CHANGED
|
@@ -1,9 +1,37 @@
|
|
|
1
1
|
import type { CellContext } from "@tanstack/react-table";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
|
+
import { type ImageUploadAdapter } from "@metaobjectsdev/runtime-web";
|
|
3
4
|
export type CellRenderer = (ctx: CellContext<any, any>) => ReactNode;
|
|
4
5
|
/**
|
|
5
6
|
* Default cell renderers keyed by field `view` subtype.
|
|
6
7
|
* Apps override per-key via <CellRendererProvider>.
|
|
7
8
|
*/
|
|
8
9
|
export declare const defaultCellRenderers: Record<string, CellRenderer>;
|
|
10
|
+
/** Presentation knobs for {@link imageCell}. Both have defaults; neither is metadata —
|
|
11
|
+
* a column's `meta` carries only view/sortable/width/renderer, so `view.image`'s
|
|
12
|
+
* @aspectRatio / @maxEdge do not reach the grid. */
|
|
13
|
+
export interface ImageCellOptions {
|
|
14
|
+
/** Rendered edge length in px (square thumbnail). Default 32. */
|
|
15
|
+
size?: number;
|
|
16
|
+
/** Alt text. Defaults to "" — the row around the cell already carries the meaning,
|
|
17
|
+
* and announcing an opaque storage key is worse than announcing nothing. */
|
|
18
|
+
alt?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Cell renderer for a `view.image` column, closed over the app's upload/serve adapter.
|
|
22
|
+
*
|
|
23
|
+
* A FACTORY rather than a `defaultCellRenderers` key, and deliberately: the field stores
|
|
24
|
+
* an opaque storage key, so turning it into a `src` requires `ImageUploadAdapter.imageUrl`
|
|
25
|
+
* — and the adapter's React context lives in `@metaobjectsdev/react`, which declares
|
|
26
|
+
* `react-hook-form` and `@hookform/resolvers` as REQUIRED peers. A dependency edge from
|
|
27
|
+
* this package would make every grid-only consumer responsible for a form stack it never
|
|
28
|
+
* uses, and `peerDependencies` are declared per PACKAGE, so no subpath or export-map change
|
|
29
|
+
* reaches that. The app supplies the adapter instead:
|
|
30
|
+
*
|
|
31
|
+
* <CellRendererProvider value={{ image: imageCell(adapter) }}>
|
|
32
|
+
*
|
|
33
|
+
* Only the adapter's TYPE is imported here, from a package this one already depends on, so
|
|
34
|
+
* the helper costs nothing at install or bundle time.
|
|
35
|
+
*/
|
|
36
|
+
export declare function imageCell(adapter: ImageUploadAdapter, opts?: ImageCellOptions): CellRenderer;
|
|
9
37
|
//# sourceMappingURL=cell-renderers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cell-renderers.d.ts","sourceRoot":"","sources":["../src/cell-renderers.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"cell-renderers.d.ts","sourceRoot":"","sources":["../src/cell-renderers.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAEtF,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,SAAS,CAAC;AAErE;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CA+D7D,CAAC;AAEF;;qDAEqD;AACrD,MAAM,WAAW,gBAAgB;IAC/B,iEAAiE;IACjE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;iFAC6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,kBAAkB,EAAE,IAAI,GAAE,gBAAqB,GAAG,YAAY,CA4BhG"}
|
package/dist/cell-renderers.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
2
|
import { formatCurrency } from "@metaobjectsdev/runtime-web";
|
|
2
3
|
/**
|
|
3
4
|
* Default cell renderers keyed by field `view` subtype.
|
|
@@ -11,11 +12,44 @@ export const defaultCellRenderers = {
|
|
|
11
12
|
const v = ctx.getValue();
|
|
12
13
|
return v ? new Date(v).toLocaleDateString() : "";
|
|
13
14
|
},
|
|
14
|
-
|
|
15
|
+
// #355 — keyed by REGISTERED view subtype. `checkbox` is the registered subtype for a
|
|
16
|
+
// boolean-valued view; this renderer previously sat under `boolean`, which no subtype
|
|
17
|
+
// produces, so it was unreachable and a checkbox column rendered a raw true/false.
|
|
18
|
+
checkbox: (ctx) => (ctx.getValue() ? "Yes" : "No"),
|
|
19
|
+
// `view.month` is registered as "Month picker / month display", and rendered the raw
|
|
20
|
+
// stored string. Parsed field-wise rather than through `new Date("2026-09")`, which is
|
|
21
|
+
// UTC midnight and so displays the PREVIOUS month for every viewer west of Greenwich.
|
|
22
|
+
month: (ctx) => {
|
|
15
23
|
const v = ctx.getValue();
|
|
16
|
-
|
|
24
|
+
if (v == null || v === "")
|
|
25
|
+
return "";
|
|
26
|
+
const m = /^(\d{4})-(\d{2})/.exec(String(v));
|
|
27
|
+
const d = m ? new Date(Number(m[1]), Number(m[2]) - 1, 1) : new Date(String(v));
|
|
28
|
+
return Number.isNaN(d.getTime())
|
|
29
|
+
? String(v)
|
|
30
|
+
: d.toLocaleDateString(undefined, { year: "numeric", month: "long" });
|
|
31
|
+
},
|
|
32
|
+
// `view.hotlink` is registered as "Renders the value as a clickable link" and rendered
|
|
33
|
+
// plain text — the same broken promise `checkbox` had. The scheme is checked because
|
|
34
|
+
// this value comes from the database: an anchor built blindly from a stored string is
|
|
35
|
+
// a `javascript:` URL away from executing it on click. Anything not http/https/mailto
|
|
36
|
+
// renders as text, which is what it did before, so the guard can only ever be safe.
|
|
37
|
+
hotlink: (ctx) => {
|
|
38
|
+
const v = ctx.getValue();
|
|
39
|
+
const s = v == null ? "" : String(v);
|
|
40
|
+
if (s === "")
|
|
41
|
+
return "";
|
|
42
|
+
let scheme;
|
|
43
|
+
try {
|
|
44
|
+
scheme = new URL(s).protocol;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return s; // not an absolute URL at all — render it as the text it is
|
|
48
|
+
}
|
|
49
|
+
if (scheme !== "http:" && scheme !== "https:" && scheme !== "mailto:")
|
|
50
|
+
return s;
|
|
51
|
+
return (_jsx("a", { href: s, rel: "noreferrer noopener", target: "_blank", children: s }));
|
|
17
52
|
},
|
|
18
|
-
boolean: (ctx) => (ctx.getValue() ? "Yes" : "No"),
|
|
19
53
|
currency: (ctx) => {
|
|
20
54
|
const value = ctx.getValue();
|
|
21
55
|
if (value == null || !Number.isFinite(Number(value)))
|
|
@@ -30,6 +64,48 @@ export const defaultCellRenderers = {
|
|
|
30
64
|
}
|
|
31
65
|
},
|
|
32
66
|
dropdown: (ctx) => String(ctx.getValue() ?? ""),
|
|
67
|
+
// Same rendering as `dropdown` — a selection control's cell is its chosen value. Keyed
|
|
68
|
+
// explicitly rather than left to EntityGrid's no-renderer fall-through, so that "a radio
|
|
69
|
+
// column shows its value" is a decision this map records instead of an accident.
|
|
70
|
+
radio: (ctx) => String(ctx.getValue() ?? ""),
|
|
33
71
|
password: () => "•••••",
|
|
34
72
|
};
|
|
73
|
+
/**
|
|
74
|
+
* Cell renderer for a `view.image` column, closed over the app's upload/serve adapter.
|
|
75
|
+
*
|
|
76
|
+
* A FACTORY rather than a `defaultCellRenderers` key, and deliberately: the field stores
|
|
77
|
+
* an opaque storage key, so turning it into a `src` requires `ImageUploadAdapter.imageUrl`
|
|
78
|
+
* — and the adapter's React context lives in `@metaobjectsdev/react`, which declares
|
|
79
|
+
* `react-hook-form` and `@hookform/resolvers` as REQUIRED peers. A dependency edge from
|
|
80
|
+
* this package would make every grid-only consumer responsible for a form stack it never
|
|
81
|
+
* uses, and `peerDependencies` are declared per PACKAGE, so no subpath or export-map change
|
|
82
|
+
* reaches that. The app supplies the adapter instead:
|
|
83
|
+
*
|
|
84
|
+
* <CellRendererProvider value={{ image: imageCell(adapter) }}>
|
|
85
|
+
*
|
|
86
|
+
* Only the adapter's TYPE is imported here, from a package this one already depends on, so
|
|
87
|
+
* the helper costs nothing at install or bundle time.
|
|
88
|
+
*/
|
|
89
|
+
export function imageCell(adapter, opts = {}) {
|
|
90
|
+
const size = opts.size ?? 32;
|
|
91
|
+
const alt = opts.alt ?? "";
|
|
92
|
+
return (ctx) => {
|
|
93
|
+
const v = ctx.getValue();
|
|
94
|
+
const key = v == null ? "" : String(v);
|
|
95
|
+
if (key === "")
|
|
96
|
+
return "";
|
|
97
|
+
let src;
|
|
98
|
+
try {
|
|
99
|
+
src = adapter.imageUrl(key);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// An adapter that cannot resolve the key: render the key as the text it is, the
|
|
103
|
+
// same fall-back `hotlink` makes for a value that is not a URL. There is no
|
|
104
|
+
// harmless empty-src alternative — an <img src=""> resolves to the PAGE url and
|
|
105
|
+
// re-requests it.
|
|
106
|
+
return key;
|
|
107
|
+
}
|
|
108
|
+
return (_jsx("img", { src: src, alt: alt, width: size, height: size, loading: "lazy", style: { objectFit: "cover" } }));
|
|
109
|
+
};
|
|
110
|
+
}
|
|
35
111
|
//# sourceMappingURL=cell-renderers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cell-renderers.js","sourceRoot":"","sources":["../src/cell-renderers.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"cell-renderers.js","sourceRoot":"","sources":["../src/cell-renderers.tsx"],"names":[],"mappings":"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"}
|
package/dist/entity-fetcher.d.ts
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import type
|
|
2
|
+
import { type EntityFetcher } from "@metaobjectsdev/runtime-web";
|
|
3
3
|
export interface EntityFetcherProviderProps {
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Transport only. It receives an already-prefixed URL and never learns about
|
|
6
|
+
* routing — that split is why `baseUrl` is a sibling option here rather than
|
|
7
|
+
* something each app hides inside its own fetch helper.
|
|
8
|
+
*/
|
|
9
|
+
fetcher: EntityFetcher;
|
|
10
|
+
/**
|
|
11
|
+
* Where the API lives — a path (`/api`) or a full origin
|
|
12
|
+
* (`https://api.example.com/v1`).
|
|
13
|
+
*
|
|
14
|
+
* OPTIONAL, default `""`: same-origin at the root, which is what `meta init`
|
|
15
|
+
* scaffolds (`apiPrefix: ""`). Generated hooks emit entity-relative paths, so this
|
|
16
|
+
* is the single place the base is decided, and deciding it at RUNTIME rather than
|
|
17
|
+
* at `meta gen` time is what lets one client bundle serve a dev proxy, a preview
|
|
18
|
+
* environment and a separate API host without regenerating.
|
|
19
|
+
*/
|
|
20
|
+
baseUrl?: string | undefined;
|
|
5
21
|
children: ReactNode;
|
|
6
22
|
}
|
|
7
23
|
/** Wrap your app (or admin subtree) to supply a fetcher to generated hooks. */
|
|
8
|
-
export declare function EntityFetcherProvider({
|
|
9
|
-
/** Reads the fetcher from context
|
|
24
|
+
export declare function EntityFetcherProvider({ fetcher, baseUrl, children, }: EntityFetcherProviderProps): import("react").JSX.Element;
|
|
25
|
+
/** Reads the fetcher from context, already bound to `baseUrl`. Throws if not provided. */
|
|
10
26
|
export declare function useEntityFetcher(): EntityFetcher;
|
|
11
27
|
//# sourceMappingURL=entity-fetcher.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-fetcher.d.ts","sourceRoot":"","sources":["../src/entity-fetcher.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"entity-fetcher.d.ts","sourceRoot":"","sources":["../src/entity-fetcher.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC3E,OAAO,EAAe,KAAK,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAI9E,MAAM,WAAW,0BAA0B;IACzC;;;;OAIG;IACH,OAAO,EAAE,aAAa,CAAC;IACvB;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,EACpC,OAAO,EACP,OAAO,EACP,QAAQ,GACT,EAAE,0BAA0B,+BAU5B;AAED,0FAA0F;AAC1F,wBAAgB,gBAAgB,IAAI,aAAa,CAShD"}
|
package/dist/entity-fetcher.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext } from "react";
|
|
2
|
+
import { createContext, useContext, useMemo } from "react";
|
|
3
|
+
import { joinBaseUrl } from "@metaobjectsdev/runtime-web";
|
|
3
4
|
const EntityFetcherContext = createContext(null);
|
|
4
5
|
/** Wrap your app (or admin subtree) to supply a fetcher to generated hooks. */
|
|
5
|
-
export function EntityFetcherProvider({
|
|
6
|
+
export function EntityFetcherProvider({ fetcher, baseUrl, children, }) {
|
|
7
|
+
// Memoized so the wrapped identity is stable across renders. An unmemoized wrapper is
|
|
8
|
+
// a new function every render, which churns everything downstream that captures it.
|
|
9
|
+
const value = useMemo(() => ((path, init) => fetcher(joinBaseUrl(baseUrl, path), init)), [fetcher, baseUrl]);
|
|
6
10
|
return _jsx(EntityFetcherContext.Provider, { value: value, children: children });
|
|
7
11
|
}
|
|
8
|
-
/** Reads the fetcher from context
|
|
12
|
+
/** Reads the fetcher from context, already bound to `baseUrl`. Throws if not provided. */
|
|
9
13
|
export function useEntityFetcher() {
|
|
10
14
|
const fetcher = useContext(EntityFetcherContext);
|
|
11
15
|
if (!fetcher) {
|
|
12
16
|
throw new Error("useEntityFetcher() called outside <EntityFetcherProvider>. " +
|
|
13
|
-
"Wrap your app (or the relevant subtree) with EntityFetcherProvider
|
|
17
|
+
"Wrap your app (or the relevant subtree) with EntityFetcherProvider fetcher={...}.");
|
|
14
18
|
}
|
|
15
19
|
return fetcher;
|
|
16
20
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-fetcher.js","sourceRoot":"","sources":["../src/entity-fetcher.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAkB,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"entity-fetcher.js","sourceRoot":"","sources":["../src/entity-fetcher.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,OAAO,EAAkB,MAAM,OAAO,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAsB,MAAM,6BAA6B,CAAC;AAE9E,MAAM,oBAAoB,GAAG,aAAa,CAAuB,IAAI,CAAC,CAAC;AAuBvE,+EAA+E;AAC/E,MAAM,UAAU,qBAAqB,CAAC,EACpC,OAAO,EACP,OAAO,EACP,QAAQ,GACmB;IAC3B,sFAAsF;IACtF,oFAAoF;IACpF,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CACH,CAAC,CAAK,IAAY,EAAE,IAAkB,EAAE,EAAE,CACxC,OAAO,CAAI,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAkB,EAClE,CAAC,OAAO,EAAE,OAAO,CAAC,CACnB,CAAC;IACF,OAAO,KAAC,oBAAoB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAAiC,CAAC;AACjG,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,gBAAgB;IAC9B,MAAM,OAAO,GAAG,UAAU,CAAC,oBAAoB,CAAC,CAAC;IACjD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,6DAA6D;YAC3D,mFAAmF,CACtF,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { EntityFetcherProvider, useEntityFetcher } from "./entity-fetcher.js";
|
|
2
2
|
export type { EntityFetcherProviderProps } from "./entity-fetcher.js";
|
|
3
3
|
export type { EntityFetcher, GridConfig } from "@metaobjectsdev/runtime-web";
|
|
4
|
-
export { defaultCellRenderers, type CellRenderer } from "./cell-renderers.js";
|
|
4
|
+
export { defaultCellRenderers, imageCell, type CellRenderer, type ImageCellOptions, } from "./cell-renderers.js";
|
|
5
5
|
export { CellRendererProvider, useCellRenderers, type CellRendererProviderProps, } from "./cell-renderer-provider.js";
|
|
6
6
|
export { EntityGrid, type EntityGridProps, type EntityGridState } from "./entity-grid.js";
|
|
7
7
|
export { buildFilterQs } from "@metaobjectsdev/runtime-web";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,YAAY,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACtE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC7E,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,YAAY,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACtE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EACL,oBAAoB,EACpB,SAAS,EACT,KAAK,YAAY,EACjB,KAAK,gBAAgB,GACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,KAAK,yBAAyB,GAC/B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAC1F,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Public API surface for @metaobjectsdev/tanstack.
|
|
2
2
|
export { EntityFetcherProvider, useEntityFetcher } from "./entity-fetcher.js";
|
|
3
|
-
export { defaultCellRenderers } from "./cell-renderers.js";
|
|
3
|
+
export { defaultCellRenderers, imageCell, } from "./cell-renderers.js";
|
|
4
4
|
export { CellRendererProvider, useCellRenderers, } from "./cell-renderer-provider.js";
|
|
5
5
|
export { EntityGrid } from "./entity-grid.js";
|
|
6
6
|
export { buildFilterQs } from "@metaobjectsdev/runtime-web";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG9E,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG9E,OAAO,EACL,oBAAoB,EACpB,SAAS,GAGV,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,oBAAoB,EACpB,gBAAgB,GAEjB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,UAAU,EAA8C,MAAM,kBAAkB,CAAC;AAC1F,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metaobjectsdev/tanstack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "TanStack runtime for metaobjects: EntityFetcherProvider, EntityGrid, default cell renderers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"access": "public"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@metaobjectsdev/runtime-web": "0.
|
|
48
|
+
"@metaobjectsdev/runtime-web": "0.25.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"@tanstack/react-query": "^5.90.0",
|
package/src/cell-renderers.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CellContext } from "@tanstack/react-table";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
|
-
import { formatCurrency } from "@metaobjectsdev/runtime-web";
|
|
3
|
+
import { formatCurrency, type ImageUploadAdapter } from "@metaobjectsdev/runtime-web";
|
|
4
4
|
|
|
5
5
|
export type CellRenderer = (ctx: CellContext<any, any>) => ReactNode;
|
|
6
6
|
|
|
@@ -16,11 +16,44 @@ export const defaultCellRenderers: Record<string, CellRenderer> = {
|
|
|
16
16
|
const v = ctx.getValue();
|
|
17
17
|
return v ? new Date(v as string).toLocaleDateString() : "";
|
|
18
18
|
},
|
|
19
|
-
|
|
19
|
+
// #355 — keyed by REGISTERED view subtype. `checkbox` is the registered subtype for a
|
|
20
|
+
// boolean-valued view; this renderer previously sat under `boolean`, which no subtype
|
|
21
|
+
// produces, so it was unreachable and a checkbox column rendered a raw true/false.
|
|
22
|
+
checkbox: (ctx) => (ctx.getValue() ? "Yes" : "No"),
|
|
23
|
+
// `view.month` is registered as "Month picker / month display", and rendered the raw
|
|
24
|
+
// stored string. Parsed field-wise rather than through `new Date("2026-09")`, which is
|
|
25
|
+
// UTC midnight and so displays the PREVIOUS month for every viewer west of Greenwich.
|
|
26
|
+
month: (ctx) => {
|
|
20
27
|
const v = ctx.getValue();
|
|
21
|
-
|
|
28
|
+
if (v == null || v === "") return "";
|
|
29
|
+
const m = /^(\d{4})-(\d{2})/.exec(String(v));
|
|
30
|
+
const d = m ? new Date(Number(m[1]), Number(m[2]) - 1, 1) : new Date(String(v));
|
|
31
|
+
return Number.isNaN(d.getTime())
|
|
32
|
+
? String(v)
|
|
33
|
+
: d.toLocaleDateString(undefined, { year: "numeric", month: "long" });
|
|
34
|
+
},
|
|
35
|
+
// `view.hotlink` is registered as "Renders the value as a clickable link" and rendered
|
|
36
|
+
// plain text — the same broken promise `checkbox` had. The scheme is checked because
|
|
37
|
+
// this value comes from the database: an anchor built blindly from a stored string is
|
|
38
|
+
// a `javascript:` URL away from executing it on click. Anything not http/https/mailto
|
|
39
|
+
// renders as text, which is what it did before, so the guard can only ever be safe.
|
|
40
|
+
hotlink: (ctx) => {
|
|
41
|
+
const v = ctx.getValue();
|
|
42
|
+
const s = v == null ? "" : String(v);
|
|
43
|
+
if (s === "") return "";
|
|
44
|
+
let scheme: string;
|
|
45
|
+
try {
|
|
46
|
+
scheme = new URL(s).protocol;
|
|
47
|
+
} catch {
|
|
48
|
+
return s; // not an absolute URL at all — render it as the text it is
|
|
49
|
+
}
|
|
50
|
+
if (scheme !== "http:" && scheme !== "https:" && scheme !== "mailto:") return s;
|
|
51
|
+
return (
|
|
52
|
+
<a href={s} rel="noreferrer noopener" target="_blank">
|
|
53
|
+
{s}
|
|
54
|
+
</a>
|
|
55
|
+
);
|
|
22
56
|
},
|
|
23
|
-
boolean: (ctx) => (ctx.getValue() ? "Yes" : "No"),
|
|
24
57
|
currency: (ctx) => {
|
|
25
58
|
const value = ctx.getValue();
|
|
26
59
|
if (value == null || !Number.isFinite(Number(value))) return "";
|
|
@@ -33,5 +66,66 @@ export const defaultCellRenderers: Record<string, CellRenderer> = {
|
|
|
33
66
|
}
|
|
34
67
|
},
|
|
35
68
|
dropdown: (ctx) => String(ctx.getValue() ?? ""),
|
|
69
|
+
// Same rendering as `dropdown` — a selection control's cell is its chosen value. Keyed
|
|
70
|
+
// explicitly rather than left to EntityGrid's no-renderer fall-through, so that "a radio
|
|
71
|
+
// column shows its value" is a decision this map records instead of an accident.
|
|
72
|
+
radio: (ctx) => String(ctx.getValue() ?? ""),
|
|
36
73
|
password: () => "•••••",
|
|
37
74
|
};
|
|
75
|
+
|
|
76
|
+
/** Presentation knobs for {@link imageCell}. Both have defaults; neither is metadata —
|
|
77
|
+
* a column's `meta` carries only view/sortable/width/renderer, so `view.image`'s
|
|
78
|
+
* @aspectRatio / @maxEdge do not reach the grid. */
|
|
79
|
+
export interface ImageCellOptions {
|
|
80
|
+
/** Rendered edge length in px (square thumbnail). Default 32. */
|
|
81
|
+
size?: number;
|
|
82
|
+
/** Alt text. Defaults to "" — the row around the cell already carries the meaning,
|
|
83
|
+
* and announcing an opaque storage key is worse than announcing nothing. */
|
|
84
|
+
alt?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Cell renderer for a `view.image` column, closed over the app's upload/serve adapter.
|
|
89
|
+
*
|
|
90
|
+
* A FACTORY rather than a `defaultCellRenderers` key, and deliberately: the field stores
|
|
91
|
+
* an opaque storage key, so turning it into a `src` requires `ImageUploadAdapter.imageUrl`
|
|
92
|
+
* — and the adapter's React context lives in `@metaobjectsdev/react`, which declares
|
|
93
|
+
* `react-hook-form` and `@hookform/resolvers` as REQUIRED peers. A dependency edge from
|
|
94
|
+
* this package would make every grid-only consumer responsible for a form stack it never
|
|
95
|
+
* uses, and `peerDependencies` are declared per PACKAGE, so no subpath or export-map change
|
|
96
|
+
* reaches that. The app supplies the adapter instead:
|
|
97
|
+
*
|
|
98
|
+
* <CellRendererProvider value={{ image: imageCell(adapter) }}>
|
|
99
|
+
*
|
|
100
|
+
* Only the adapter's TYPE is imported here, from a package this one already depends on, so
|
|
101
|
+
* the helper costs nothing at install or bundle time.
|
|
102
|
+
*/
|
|
103
|
+
export function imageCell(adapter: ImageUploadAdapter, opts: ImageCellOptions = {}): CellRenderer {
|
|
104
|
+
const size = opts.size ?? 32;
|
|
105
|
+
const alt = opts.alt ?? "";
|
|
106
|
+
return (ctx) => {
|
|
107
|
+
const v = ctx.getValue();
|
|
108
|
+
const key = v == null ? "" : String(v);
|
|
109
|
+
if (key === "") return "";
|
|
110
|
+
let src: string;
|
|
111
|
+
try {
|
|
112
|
+
src = adapter.imageUrl(key);
|
|
113
|
+
} catch {
|
|
114
|
+
// An adapter that cannot resolve the key: render the key as the text it is, the
|
|
115
|
+
// same fall-back `hotlink` makes for a value that is not a URL. There is no
|
|
116
|
+
// harmless empty-src alternative — an <img src=""> resolves to the PAGE url and
|
|
117
|
+
// re-requests it.
|
|
118
|
+
return key;
|
|
119
|
+
}
|
|
120
|
+
return (
|
|
121
|
+
<img
|
|
122
|
+
src={src}
|
|
123
|
+
alt={alt}
|
|
124
|
+
width={size}
|
|
125
|
+
height={size}
|
|
126
|
+
loading="lazy"
|
|
127
|
+
style={{ objectFit: "cover" }}
|
|
128
|
+
/>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
}
|
package/src/entity-fetcher.tsx
CHANGED
|
@@ -1,25 +1,53 @@
|
|
|
1
|
-
import { createContext, useContext, type ReactNode } from "react";
|
|
2
|
-
import type
|
|
1
|
+
import { createContext, useContext, useMemo, type ReactNode } from "react";
|
|
2
|
+
import { joinBaseUrl, type EntityFetcher } from "@metaobjectsdev/runtime-web";
|
|
3
3
|
|
|
4
4
|
const EntityFetcherContext = createContext<EntityFetcher | null>(null);
|
|
5
5
|
|
|
6
6
|
export interface EntityFetcherProviderProps {
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Transport only. It receives an already-prefixed URL and never learns about
|
|
9
|
+
* routing — that split is why `baseUrl` is a sibling option here rather than
|
|
10
|
+
* something each app hides inside its own fetch helper.
|
|
11
|
+
*/
|
|
12
|
+
fetcher: EntityFetcher;
|
|
13
|
+
/**
|
|
14
|
+
* Where the API lives — a path (`/api`) or a full origin
|
|
15
|
+
* (`https://api.example.com/v1`).
|
|
16
|
+
*
|
|
17
|
+
* OPTIONAL, default `""`: same-origin at the root, which is what `meta init`
|
|
18
|
+
* scaffolds (`apiPrefix: ""`). Generated hooks emit entity-relative paths, so this
|
|
19
|
+
* is the single place the base is decided, and deciding it at RUNTIME rather than
|
|
20
|
+
* at `meta gen` time is what lets one client bundle serve a dev proxy, a preview
|
|
21
|
+
* environment and a separate API host without regenerating.
|
|
22
|
+
*/
|
|
23
|
+
baseUrl?: string | undefined;
|
|
8
24
|
children: ReactNode;
|
|
9
25
|
}
|
|
10
26
|
|
|
11
27
|
/** Wrap your app (or admin subtree) to supply a fetcher to generated hooks. */
|
|
12
|
-
export function EntityFetcherProvider({
|
|
28
|
+
export function EntityFetcherProvider({
|
|
29
|
+
fetcher,
|
|
30
|
+
baseUrl,
|
|
31
|
+
children,
|
|
32
|
+
}: EntityFetcherProviderProps) {
|
|
33
|
+
// Memoized so the wrapped identity is stable across renders. An unmemoized wrapper is
|
|
34
|
+
// a new function every render, which churns everything downstream that captures it.
|
|
35
|
+
const value = useMemo<EntityFetcher>(
|
|
36
|
+
() =>
|
|
37
|
+
(<T,>(path: string, init?: RequestInit) =>
|
|
38
|
+
fetcher<T>(joinBaseUrl(baseUrl, path), init)) as EntityFetcher,
|
|
39
|
+
[fetcher, baseUrl],
|
|
40
|
+
);
|
|
13
41
|
return <EntityFetcherContext.Provider value={value}>{children}</EntityFetcherContext.Provider>;
|
|
14
42
|
}
|
|
15
43
|
|
|
16
|
-
/** Reads the fetcher from context
|
|
44
|
+
/** Reads the fetcher from context, already bound to `baseUrl`. Throws if not provided. */
|
|
17
45
|
export function useEntityFetcher(): EntityFetcher {
|
|
18
46
|
const fetcher = useContext(EntityFetcherContext);
|
|
19
47
|
if (!fetcher) {
|
|
20
48
|
throw new Error(
|
|
21
49
|
"useEntityFetcher() called outside <EntityFetcherProvider>. " +
|
|
22
|
-
"Wrap your app (or the relevant subtree) with EntityFetcherProvider
|
|
50
|
+
"Wrap your app (or the relevant subtree) with EntityFetcherProvider fetcher={...}.",
|
|
23
51
|
);
|
|
24
52
|
}
|
|
25
53
|
return fetcher;
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
export { EntityFetcherProvider, useEntityFetcher } from "./entity-fetcher.js";
|
|
3
3
|
export type { EntityFetcherProviderProps } from "./entity-fetcher.js";
|
|
4
4
|
export type { EntityFetcher, GridConfig } from "@metaobjectsdev/runtime-web";
|
|
5
|
-
export {
|
|
5
|
+
export {
|
|
6
|
+
defaultCellRenderers,
|
|
7
|
+
imageCell,
|
|
8
|
+
type CellRenderer,
|
|
9
|
+
type ImageCellOptions,
|
|
10
|
+
} from "./cell-renderers.js";
|
|
6
11
|
export {
|
|
7
12
|
CellRendererProvider,
|
|
8
13
|
useCellRenderers,
|