@metaobjectsdev/tanstack 0.24.5 → 1.0.0-rc.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.
@@ -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;AAGvC,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,CA0B7D,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"}
@@ -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
- datetime: (ctx) => {
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
- return v ? new Date(v).toLocaleString() : "";
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,EAAE,MAAM,6BAA6B,CAAC;AAI7D;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAiC;IAChE,IAAI,EAAM,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC/C,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC/C,MAAM,EAAI,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC/C,IAAI,EAAM,CAAC,GAAG,EAAE,EAAE;QAChB,MAAM,CAAC,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;QACzB,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAW,CAAC,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7D,CAAC;IACD,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE;QAChB,MAAM,CAAC,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;QACzB,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAW,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACzD,CAAC;IACD,OAAO,EAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IAClD,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE;QAChB,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC7B,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAAE,OAAO,EAAE,CAAC;QAChE,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,IAAI,EAAE,CAA2C,CAAC;QACzF,IAAI,CAAC;YACH,OAAO,cAAc,CAAC,KAAe,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACrE,CAAC;QAAC,MAAM,CAAC;YACP,uEAAuE;YACvE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IACD,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC/C,QAAQ,EAAE,GAAG,EAAE,CAAC,OAAO;CACxB,CAAC"}
1
+ {"version":3,"file":"cell-renderers.js","sourceRoot":"","sources":["../src/cell-renderers.tsx"],"names":[],"mappings":"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"}
@@ -1,11 +1,27 @@
1
1
  import { type ReactNode } from "react";
2
- import type { EntityFetcher } from "@metaobjectsdev/runtime-web";
2
+ import { type EntityFetcher } from "@metaobjectsdev/runtime-web";
3
3
  export interface EntityFetcherProviderProps {
4
- value: EntityFetcher;
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({ value, children }: EntityFetcherProviderProps): import("react").JSX.Element;
9
- /** Reads the fetcher from context. Throws if not provided. */
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,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAIjE,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,aAAa,CAAC;IACrB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,0BAA0B,+BAEpF;AAED,8DAA8D;AAC9D,wBAAgB,gBAAgB,IAAI,aAAa,CAShD"}
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"}
@@ -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({ value, children }) {
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. Throws if not provided. */
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 value={...}.");
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;AAGlE,MAAM,oBAAoB,GAAG,aAAa,CAAuB,IAAI,CAAC,CAAC;AAOvE,+EAA+E;AAC/E,MAAM,UAAU,qBAAqB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAA8B;IACnF,OAAO,KAAC,oBAAoB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAAiC,CAAC;AACjG,CAAC;AAED,8DAA8D;AAC9D,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,iFAAiF,CACpF,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,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";
@@ -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,EAAE,oBAAoB,EAAE,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC9E,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"}
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,EAAE,oBAAoB,EAAqB,MAAM,qBAAqB,CAAC;AAC9E,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"}
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.24.5",
3
+ "version": "1.0.0-rc.1",
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.24.5"
48
+ "@metaobjectsdev/runtime-web": "1.0.0-rc.1"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "@tanstack/react-query": "^5.90.0",
@@ -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
- datetime: (ctx) => {
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
- return v ? new Date(v as string).toLocaleString() : "";
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
+ }
@@ -1,25 +1,53 @@
1
- import { createContext, useContext, type ReactNode } from "react";
2
- import type { EntityFetcher } from "@metaobjectsdev/runtime-web";
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
- value: EntityFetcher;
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({ value, children }: EntityFetcherProviderProps) {
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. Throws if not provided. */
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 value={...}.",
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 { defaultCellRenderers, type CellRenderer } from "./cell-renderers.js";
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,