@rudra-js/react 0.7.3 → 0.7.5

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 CHANGED
@@ -16,13 +16,13 @@ still reads it.
16
16
  ## Install
17
17
 
18
18
  ```sh
19
- npm install @rudra-js/react @rudra-js/core react
19
+ npm install @rudra-js/react @rudra-js/core @rudra-js/attested react
20
20
  ```
21
21
 
22
22
  Both `@rudra-js/core` and `react` are peer dependencies. The specification you pass in comes from
23
23
  your copy of core, and the elements this renders have to come from the same React your app renders.
24
24
  With two copies of either, you'd get a spec that fails its own type check, or a component tree React
25
- refuses to render.
25
+ refuses to render. `@rudra-js/attested` is core's peer: npm and pnpm add it for you, Yarn doesn't.
26
26
 
27
27
  ```tsx
28
28
  import { RudraComponent } from '@rudra-js/react';
@@ -69,8 +69,9 @@ later and may be the fresher of the two.
69
69
  ## Styling
70
70
 
71
71
  We ship no CSS. A stylesheet of ours would only fight whatever your site already has. So out of the
72
- box the block renders as a run-on line — every card element is inline, so titles and prices sit
73
- together with no separation. That's the starting point you style from.
72
+ box the block renders as a run-on line — every card element is inline, so a card's parts sit on one
73
+ line with spaces between them, and nothing separates one card from the next. That's the starting
74
+ point you style from.
74
75
 
75
76
  Check out `examples/shop/public/demo-styles.css`. It's a working stylesheet written against nothing
76
77
  but the table below, so copy it as a starting point rather than as a supported API. The example shop applies it by default, and `?styles=off`
@@ -98,6 +99,12 @@ keep working.
98
99
  here uses JavaScript to scroll it for you. `.rudra-card--featured` is applied alongside
99
100
  `.rudra-card`, so write `.rudra-card--featured { ... }` after the base rule and let it layer on top.
100
101
 
102
+ `.rudra-banner__cta` and `.rudra-bundle__cta` are plain text, not links or buttons, because there is
103
+ nothing for them to do. Don't style them as buttons unless a renderer of your own wraps them in one.
104
+ The hero's call to action sits inside the product link, so that one is clickable. Every card in a
105
+ carousel is a link, so tabbing through them scrolls the track, which keeps it usable by keyboard
106
+ even with the scrollbar hidden.
107
+
101
108
  ### Attributes
102
109
 
103
110
  The same markup carries what the model decided, so you can hang styling or analytics off it.
@@ -162,9 +169,10 @@ reach the renderer, since they don't depend on the catalog.
162
169
  ### What `products` may be
163
170
 
164
171
  A list of products, or anything keyed by SKU that answers `get(sku)` and `has(sku)`. A `Map` does
165
- it, and so does your own index. Those two methods are the only ones the renderers ever call, so if
166
- your catalog is too big to copy into a `Map` on every request, hand over a view of your own store
167
- instead.
172
+ it, and so does your own index. The renderers only ever call `get`, and `has` is how we tell a keyed
173
+ catalog from a list, so if your catalog is too big to copy into a `Map` on every request, hand over
174
+ a view of your own store instead. The prop is typed as a `ReadonlyMap`, so in TypeScript either
175
+ implement `ReadonlyMap` or cast with `as unknown as ReadonlyMap<string, Product>`.
168
176
 
169
177
  We look for those two methods rather than for `instanceof Map`, which is per-realm. A `Map` arriving
170
178
  from a worker or a `node:vm` sandbox is a perfectly good catalog and fails `instanceof` anyway.
@@ -1,13 +1,4 @@
1
- /**
2
- * Five of the six block renderers. The bundle one lives in `bundle-block.tsx`,
3
- * next door, since it draws a set the shop picked rather than the model.
4
- *
5
- * Each takes a block the model produced and a context the host owns. None of
6
- * them interpolate markup, and none of them read a product fact from the block
7
- * — that is what keeps generated output to the role of deciding layout, order
8
- * and wording.
9
- */
10
- import type { BannerBlock, CarouselBlock, CopyBlock, GridBlock, HeroBlock } from '@rudra-js/core';
1
+ import type { BannerBlock, BundleBlock, CarouselBlock, CopyBlock, GridBlock, HeroBlock } from '@rudra-js/core';
11
2
  import { type BlockRenderContext } from '../render-context.js';
12
3
  export declare function HeroRenderer({ block, context, }: {
13
4
  block: HeroBlock;
@@ -27,4 +18,8 @@ export declare function BannerRenderer({ block }: {
27
18
  export declare function CopyRenderer({ block }: {
28
19
  block: CopyBlock;
29
20
  }): import("react").JSX.Element;
21
+ export declare function BundleRenderer({ block, context, }: {
22
+ block: BundleBlock;
23
+ context: BlockRenderContext;
24
+ }): import("react").JSX.Element | null;
30
25
  //# sourceMappingURL=block-renderers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"block-renderers.d.ts","sourceRoot":"","sources":["../../src/blocks/block-renderers.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAClG,OAAO,EAAmB,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAGhF,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,kBAAkB,CAAC;CAC7B,+BAoBA;AAED,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,kBAAkB,CAAC;CAC7B,+BAWA;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,kBAAkB,CAAC;CAC7B,+BAaA;AAED,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,WAAW,CAAA;CAAE,+BAO/D;AAED,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,+BAO3D"}
1
+ {"version":3,"file":"block-renderers.d.ts","sourceRoot":"","sources":["../../src/blocks/block-renderers.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,aAAa,EACb,SAAS,EACT,SAAS,EACT,SAAS,EAEV,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAmB,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAGhF,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,kBAAkB,CAAC;CAC7B,+BAmBA;AAED,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,kBAAkB,CAAC;CAC7B,+BAWA;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,kBAAkB,CAAC;CAC7B,+BAWA;AAED,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,WAAW,CAAA;CAAE,+BAO/D;AAED,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,+BAO3D;AAED,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,kBAAkB,CAAC;CAC7B,sCA+BA"}
@@ -3,7 +3,7 @@ import { sellableProduct } from '../render-context.js';
3
3
  import { ProductCard } from './product-card.js';
4
4
  export function HeroRenderer({ block, context, }) {
5
5
  const product = block.sku ? sellableProduct(context.products, block.sku) : undefined;
6
- return (_jsxs("section", { className: "rudra-hero", children: [_jsx("h3", { className: "rudra-hero__headline", children: block.headline }), block.body ? _jsx("p", { className: "rudra-hero__body", children: block.body }) : null, product ? (_jsxs("a", { className: "rudra-hero__link", href: context.hrefForSku(product.sku), "data-rudra-sku": product.sku, children: [product.title, _jsx("span", { className: "rudra-hero__price", children: context.formatPrice(product) }), block.ctaLabel ? _jsx("span", { className: "rudra-hero__cta", children: block.ctaLabel }) : null] })) : null] }));
6
+ return (_jsxs("section", { className: "rudra-hero", children: [block.headline ? _jsx("h3", { className: "rudra-hero__headline", children: block.headline }) : null, block.body ? _jsx("p", { className: "rudra-hero__body", children: block.body }) : null, product ? (_jsxs("a", { className: "rudra-hero__link", href: context.hrefForSku(product.sku), "data-rudra-sku": product.sku, children: [product.title, " ", _jsx("span", { className: "rudra-hero__price", children: context.formatPrice(product) }), ' ', block.ctaLabel ? _jsx("span", { className: "rudra-hero__cta", children: block.ctaLabel }) : null] })) : null] }));
7
7
  }
8
8
  export function GridRenderer({ block, context, }) {
9
9
  return (_jsxs("section", { className: "rudra-grid", "data-rudra-columns": block.columns, children: [block.title ? _jsx("h3", { className: "rudra-grid__title", children: block.title }) : null, _jsx("div", { className: "rudra-grid__items", children: block.items.map((reference) => (_jsx(ProductCard, { reference: reference, context: context }, reference.sku))) })] }));
@@ -12,9 +12,22 @@ export function CarouselRenderer({ block, context, }) {
12
12
  return (_jsxs("section", { className: "rudra-carousel", children: [block.title ? _jsx("h3", { className: "rudra-carousel__title", children: block.title }) : null, _jsx("div", { className: "rudra-carousel__track", children: block.items.map((reference) => (_jsx(ProductCard, { reference: reference, context: context }, reference.sku))) })] }));
13
13
  }
14
14
  export function BannerRenderer({ block }) {
15
- return (_jsxs("aside", { className: "rudra-banner", "data-rudra-banner-tone": block.tone, children: [_jsx("span", { className: "rudra-banner__text", children: block.text }), block.ctaLabel ? _jsx("span", { className: "rudra-banner__cta", children: block.ctaLabel }) : null] }));
15
+ return (_jsxs("aside", { className: "rudra-banner", "data-rudra-banner-tone": block.tone, children: [_jsx("span", { className: "rudra-banner__text", children: block.text }), ' ', block.ctaLabel ? _jsx("span", { className: "rudra-banner__cta", children: block.ctaLabel }) : null] }));
16
16
  }
17
17
  export function CopyRenderer({ block }) {
18
18
  return (_jsxs("section", { className: "rudra-copy", children: [block.title ? _jsx("h3", { className: "rudra-copy__title", children: block.title }) : null, _jsx("p", { className: "rudra-copy__body", children: block.body })] }));
19
19
  }
20
+ export function BundleRenderer({ block, context, }) {
21
+ const bundle = block.bundleId === null ? undefined : context.bundles.get(block.bundleId);
22
+ if (!bundle)
23
+ return null;
24
+ const products = [];
25
+ for (const sku of bundle.skus) {
26
+ const product = sellableProduct(context.products, sku);
27
+ if (!product)
28
+ return null;
29
+ products.push(product);
30
+ }
31
+ return (_jsxs("section", { className: "rudra-bundle", children: [bundle.label ? _jsx("h3", { className: "rudra-bundle__label", children: bundle.label }) : null, block.title ? _jsx("p", { className: "rudra-bundle__title", children: block.title }) : null, block.body ? _jsx("p", { className: "rudra-bundle__body", children: block.body }) : null, _jsx("ul", { className: "rudra-bundle__items", children: products.map((product) => (_jsx("li", { className: "rudra-bundle__item", "data-rudra-sku": product.sku, children: _jsx("a", { className: "rudra-bundle__link", href: context.hrefForSku(product.sku), children: product.title }) }, product.sku))) }), _jsx("p", { className: "rudra-bundle__price", children: context.formatBundlePrice(bundle) }), block.ctaLabel ? _jsx("span", { className: "rudra-bundle__cta", children: block.ctaLabel }) : null] }));
32
+ }
20
33
  //# sourceMappingURL=block-renderers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"block-renderers.js","sourceRoot":"","sources":["../../src/blocks/block-renderers.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"block-renderers.js","sourceRoot":"","sources":["../../src/blocks/block-renderers.tsx"],"names":[],"mappings":"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"}
@@ -1,13 +1,5 @@
1
1
  import type { ProductReference } from '@rudra-js/core';
2
2
  import { type BlockRenderContext } from '../render-context.js';
3
- /**
4
- * One product.
5
- *
6
- * Every fact here — the title, the price, the image, the link — is read from
7
- * the catalog, not from the specification. The specification contributes only
8
- * `reason`, `badge` and `emphasis`, and React escapes all three on the way
9
- * into the markup.
10
- */
11
3
  export declare function ProductCard({ reference, context, }: {
12
4
  reference: ProductReference;
13
5
  context: BlockRenderContext;
@@ -1 +1 @@
1
- {"version":3,"file":"product-card.d.ts","sourceRoot":"","sources":["../../src/blocks/product-card.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAmB,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAEhF;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,EAC1B,SAAS,EACT,OAAO,GACR,EAAE;IACD,SAAS,EAAE,gBAAgB,CAAC;IAC5B,OAAO,EAAE,kBAAkB,CAAC;CAC7B,sCA2BA"}
1
+ {"version":3,"file":"product-card.d.ts","sourceRoot":"","sources":["../../src/blocks/product-card.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAmB,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAEhF,wBAAgB,WAAW,CAAC,EAC1B,SAAS,EACT,OAAO,GACR,EAAE;IACD,SAAS,EAAE,gBAAgB,CAAC;IAC5B,OAAO,EAAE,kBAAkB,CAAC;CAC7B,sCAyBA"}
@@ -1,20 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { sellableProduct } from '../render-context.js';
3
- /**
4
- * One product.
5
- *
6
- * Every fact here — the title, the price, the image, the link — is read from
7
- * the catalog, not from the specification. The specification contributes only
8
- * `reason`, `badge` and `emphasis`, and React escapes all three on the way
9
- * into the markup.
10
- */
11
3
  export function ProductCard({ reference, context, }) {
12
4
  const product = sellableProduct(context.products, reference.sku);
13
- // Reconciliation drops a SKU the catalog does not have, so this should be
14
- // unreachable. Rendering nothing beats rendering a card with holes in it.
15
5
  if (!product)
16
6
  return null;
17
7
  const isFeatured = reference.emphasis === 'featured';
18
- return (_jsxs("a", { href: context.hrefForSku(product.sku), "data-rudra-sku": product.sku, "data-rudra-basis": reference.basis, className: isFeatured ? 'rudra-card rudra-card--featured' : 'rudra-card', children: [product.imageUrl ? (_jsx("img", { className: "rudra-card__image", src: product.imageUrl, alt: "", loading: "lazy" })) : null, _jsxs("span", { className: "rudra-card__body", children: [reference.badge ? _jsx("span", { className: "rudra-card__badge", children: reference.badge }) : null, _jsx("span", { className: "rudra-card__title", children: product.title }), _jsx("span", { className: "rudra-card__price", children: context.formatPrice(product) }), reference.reason ? _jsx("span", { className: "rudra-card__reason", children: reference.reason }) : null] })] }));
8
+ return (_jsxs("a", { href: context.hrefForSku(product.sku), "data-rudra-sku": product.sku, "data-rudra-basis": reference.basis, className: isFeatured ? 'rudra-card rudra-card--featured' : 'rudra-card', children: [product.imageUrl ? (_jsx("img", { className: "rudra-card__image", src: product.imageUrl, alt: "", loading: "lazy" })) : null, _jsxs("span", { className: "rudra-card__body", children: [reference.badge ? _jsx("span", { className: "rudra-card__badge", children: reference.badge }) : null, ' ', _jsx("span", { className: "rudra-card__title", children: product.title }), ' ', _jsx("span", { className: "rudra-card__price", children: context.formatPrice(product) }), ' ', reference.reason ? _jsx("span", { className: "rudra-card__reason", children: reference.reason }) : null] })] }));
19
9
  }
20
10
  //# sourceMappingURL=product-card.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"product-card.js","sourceRoot":"","sources":["../../src/blocks/product-card.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,EAA2B,MAAM,sBAAsB,CAAC;AAEhF;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,EAC1B,SAAS,EACT,OAAO,GAIR;IACC,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC;IACjE,0EAA0E;IAC1E,0EAA0E;IAC1E,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAE1B,MAAM,UAAU,GAAG,SAAS,CAAC,QAAQ,KAAK,UAAU,CAAC;IAErD,OAAO,CACL,aACE,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,oBACrB,OAAO,CAAC,GAAG,sBACT,SAAS,CAAC,KAAK,EACjC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,iCAAiC,CAAC,CAAC,CAAC,YAAY,aAEvE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAClB,cAAK,SAAS,EAAC,mBAAmB,EAAC,GAAG,EAAE,OAAO,CAAC,QAAQ,EAAE,GAAG,EAAC,EAAE,EAAC,OAAO,EAAC,MAAM,GAAG,CACnF,CAAC,CAAC,CAAC,IAAI,EAER,gBAAM,SAAS,EAAC,kBAAkB,aAC/B,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,mBAAmB,YAAE,SAAS,CAAC,KAAK,GAAQ,CAAC,CAAC,CAAC,IAAI,EACtF,eAAM,SAAS,EAAC,mBAAmB,YAAE,OAAO,CAAC,KAAK,GAAQ,EAC1D,eAAM,SAAS,EAAC,mBAAmB,YAAE,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,GAAQ,EACxE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,oBAAoB,YAAE,SAAS,CAAC,MAAM,GAAQ,CAAC,CAAC,CAAC,IAAI,IACpF,IACL,CACL,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"product-card.js","sourceRoot":"","sources":["../../src/blocks/product-card.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,EAA2B,MAAM,sBAAsB,CAAC;AAEhF,MAAM,UAAU,WAAW,CAAC,EAC1B,SAAS,EACT,OAAO,GAIR;IACC,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC;IACjE,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAE1B,MAAM,UAAU,GAAG,SAAS,CAAC,QAAQ,KAAK,UAAU,CAAC;IAErD,OAAO,CACL,aACE,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,oBACrB,OAAO,CAAC,GAAG,sBACT,SAAS,CAAC,KAAK,EACjC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,iCAAiC,CAAC,CAAC,CAAC,YAAY,aAEvE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAClB,cAAK,SAAS,EAAC,mBAAmB,EAAC,GAAG,EAAE,OAAO,CAAC,QAAQ,EAAE,GAAG,EAAC,EAAE,EAAC,OAAO,EAAC,MAAM,GAAG,CACnF,CAAC,CAAC,CAAC,IAAI,EAER,gBAAM,SAAS,EAAC,kBAAkB,aAC/B,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,mBAAmB,YAAE,SAAS,CAAC,KAAK,GAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,EAC3F,eAAM,SAAS,EAAC,mBAAmB,YAAE,OAAO,CAAC,KAAK,GAAQ,EAAC,GAAG,EAC9D,eAAM,SAAS,EAAC,mBAAmB,YAAE,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,GAAQ,EAAC,GAAG,EAC5E,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,oBAAoB,YAAE,SAAS,CAAC,MAAM,GAAQ,CAAC,CAAC,CAAC,IAAI,IACpF,IACL,CACL,CAAC;AACJ,CAAC"}
package/dist/registry.js CHANGED
@@ -1,5 +1,4 @@
1
- import { BannerRenderer, CarouselRenderer, CopyRenderer, GridRenderer, HeroRenderer, } from './blocks/block-renderers.js';
2
- import { BundleRenderer } from './blocks/bundle-block.js';
1
+ import { BannerRenderer, BundleRenderer, CarouselRenderer, CopyRenderer, GridRenderer, HeroRenderer, } from './blocks/block-renderers.js';
3
2
  export const defaultRegistry = {
4
3
  hero: HeroRenderer,
5
4
  grid: GridRenderer,
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,YAAY,GACb,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAW1D,MAAM,CAAC,MAAM,eAAe,GAAkB;IAC5C,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE,YAAY;IAClB,QAAQ,EAAE,gBAAgB;IAC1B,MAAM,EAAE,cAAc;IACtB,IAAI,EAAE,YAAY;IAClB,MAAM,EAAE,cAAc;CACvB,CAAC;AAEF,MAAM,UAAU,cAAc,CAAC,SAAiC;IAC9D,OAAO;QACL,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,eAAe,CAAC,IAAI;QAC5C,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,eAAe,CAAC,IAAI;QAC5C,QAAQ,EAAE,SAAS,CAAC,QAAQ,IAAI,eAAe,CAAC,QAAQ;QACxD,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,eAAe,CAAC,MAAM;QAClD,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,eAAe,CAAC,IAAI;QAC5C,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,eAAe,CAAC,MAAM;KACnD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,YAAY,GACb,MAAM,6BAA6B,CAAC;AAWrC,MAAM,CAAC,MAAM,eAAe,GAAkB;IAC5C,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE,YAAY;IAClB,QAAQ,EAAE,gBAAgB;IAC1B,MAAM,EAAE,cAAc;IACtB,IAAI,EAAE,YAAY;IAClB,MAAM,EAAE,cAAc;CACvB,CAAC;AAEF,MAAM,UAAU,cAAc,CAAC,SAAiC;IAC9D,OAAO;QACL,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,eAAe,CAAC,IAAI;QAC5C,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,eAAe,CAAC,IAAI;QAC5C,QAAQ,EAAE,SAAS,CAAC,QAAQ,IAAI,eAAe,CAAC,QAAQ;QACxD,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,eAAe,CAAC,MAAM;QAClD,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,eAAe,CAAC,IAAI;QAC5C,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,eAAe,CAAC,MAAM;KACnD,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"render-context.d.ts","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAEtD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEhD,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE9C,QAAQ,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7C,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;IACnD,QAAQ,CAAC,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;CACxD;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,EACtC,GAAG,EAAE,MAAM,GACV,OAAO,GAAG,SAAS,CAGrB;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAgB5E;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAgBhF"}
1
+ {"version":3,"file":"render-context.d.ts","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAEtD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChD,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9C,QAAQ,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7C,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;IACnD,QAAQ,CAAC,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;CACxD;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,EACtC,GAAG,EAAE,MAAM,GACV,OAAO,GAAG,SAAS,CAGrB;AAYD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAQ5E;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAQhF"}
@@ -5,32 +5,28 @@ export function sellableProduct(products, sku) {
5
5
  const product = products.get(sku);
6
6
  return product?.isInStock === false ? undefined : product;
7
7
  }
8
- export function defaultFormatPrice(product, locale) {
9
- if (!Number.isFinite(product.price)) {
10
- throw new TypeError(`price for SKU ${product.sku} is ${String(product.price)}, not a finite number — ` +
11
- 'catalog objects must satisfy productSchema from @rudra-js/core');
12
- }
8
+ function formatMoney(price, currency, locale) {
13
9
  let formatter;
14
10
  try {
15
- formatter = new Intl.NumberFormat(locale, { style: 'currency', currency: product.currency });
11
+ formatter = new Intl.NumberFormat(locale, { style: 'currency', currency });
16
12
  }
17
13
  catch {
18
- return `${product.currency} ${product.price}`;
14
+ return `${currency} ${price}`;
15
+ }
16
+ return formatter.format(price);
17
+ }
18
+ export function defaultFormatPrice(product, locale) {
19
+ if (!Number.isFinite(product.price)) {
20
+ throw new TypeError(`price for SKU ${product.sku} is ${String(product.price)}, not a finite number — ` +
21
+ 'catalog objects must satisfy productSchema from @rudra-js/core');
19
22
  }
20
- return formatter.format(product.price);
23
+ return formatMoney(product.price, product.currency, locale);
21
24
  }
22
25
  export function defaultFormatBundlePrice(bundle, locale) {
23
26
  if (!Number.isFinite(bundle.price)) {
24
27
  throw new TypeError(`price for bundle ${bundle.id} is ${String(bundle.price)}, not a finite number — ` +
25
28
  'bundle objects must satisfy bundleSchema from @rudra-js/core');
26
29
  }
27
- let formatter;
28
- try {
29
- formatter = new Intl.NumberFormat(locale, { style: 'currency', currency: bundle.currency });
30
- }
31
- catch {
32
- return `${bundle.currency} ${bundle.price}`;
33
- }
34
- return formatter.format(bundle.price);
30
+ return formatMoney(bundle.price, bundle.currency, locale);
35
31
  }
36
32
  //# sourceMappingURL=render-context.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"render-context.js","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AAYA,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,OAAO,YAAY,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,QAAsC,EACtC,GAAW;IAEX,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,OAAO,EAAE,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAAgB,EAAE,MAAe;IAClE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,SAAS,CACjB,iBAAiB,OAAO,CAAC,GAAG,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,0BAA0B;YAChF,gEAAgE,CACnE,CAAC;IACJ,CAAC;IAED,IAAI,SAA4B,CAAC;IACjC,IAAI,CAAC;QACH,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC/F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;IAChD,CAAC;IAED,OAAO,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,MAAc,EAAE,MAAe;IACtE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,SAAS,CACjB,oBAAoB,MAAM,CAAC,EAAE,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B;YAChF,8DAA8D,CACjE,CAAC;IACJ,CAAC;IAED,IAAI,SAA4B,CAAC;IACjC,IAAI,CAAC;QACH,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC9F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;IAC9C,CAAC;IAED,OAAO,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACxC,CAAC"}
1
+ {"version":3,"file":"render-context.js","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,OAAO,YAAY,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,QAAsC,EACtC,GAAW;IAEX,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,OAAO,EAAE,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;AAC5D,CAAC;AAED,SAAS,WAAW,CAAC,KAAa,EAAE,QAAgB,EAAE,MAAe;IACnE,IAAI,SAA4B,CAAC;IACjC,IAAI,CAAC;QACH,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC7E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,QAAQ,IAAI,KAAK,EAAE,CAAC;IAChC,CAAC;IACD,OAAO,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAAgB,EAAE,MAAe;IAClE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,SAAS,CACjB,iBAAiB,OAAO,CAAC,GAAG,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,0BAA0B;YAChF,gEAAgE,CACnE,CAAC;IACJ,CAAC;IACD,OAAO,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,MAAc,EAAE,MAAe;IACtE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,SAAS,CACjB,oBAAoB,MAAM,CAAC,EAAE,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B;YAChF,8DAA8D,CACjE,CAAC;IACJ,CAAC;IACD,OAAO,WAAW,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AAC5D,CAAC"}
@@ -2,59 +2,16 @@ import type { Bundle, ComponentSpec, Product } from '@rudra-js/core';
2
2
  import { type BlockRegistry } from './registry.js';
3
3
  export interface RudraComponentProps {
4
4
  spec: ComponentSpec;
5
- /**
6
- * The host catalog. Every product fact on the page comes from here rather
7
- * than from the specification.
8
- *
9
- * A list of products, or anything keyed by SKU — a `Map`, or your own view
10
- * over a catalog too large to hold in one. The renderers only ever call
11
- * `get(sku)` and `has(sku)`, so a view needs nothing else to be fast.
12
- *
13
- * Validate them with `productSchema` from `@rudra-js/core` — the same schema
14
- * your candidates already passed — not with `parseTrackingInput`, which
15
- * parses a whole tracking payload and will reject a bare catalog.
16
- *
17
- * This is a second door into the framework. `imageUrl` lands in an
18
- * `<img src>`, and `productSchema` is the only thing that rejects a
19
- * protocol-relative `//evil.example/pixel.png` or a `data:` URL — React
20
- * neutralises `javascript:` on its own, but not those. A price that is not a
21
- * finite number throws rather than rendering as free.
22
- */
23
5
  products: ProductCatalog;
24
- /** Sets the shop sells together. Only needed if a spec can carry a bundle block. */
25
6
  bundles?: readonly Bundle[];
26
7
  registry?: BlockRegistry;
27
8
  hrefForSku?: (sku: string) => string;
28
9
  formatPrice?: (product: Product) => string;
29
- /** Same as `formatPrice`, but for a bundle — the shop's price, not a sum of the parts. */
30
10
  formatBundlePrice?: (bundle: Bundle) => string;
31
- /**
32
- * The shopper's locale, used to punctuate prices. Defaults to the server's,
33
- * which is almost never the shopper's — pass it if the shop serves more than
34
- * one. Ignored when `formatPrice` and `formatBundlePrice` are supplied.
35
- */
36
11
  locale?: string;
37
- /**
38
- * Adds the model's own reasoning, the provider and the model name to the
39
- * markup. Useful while developing and while benchmarking; it publishes which
40
- * vendor a shop uses and whether the component is currently degraded, so it
41
- * is off unless asked for.
42
- */
43
12
  hasDiagnostics?: boolean;
44
13
  className?: string;
45
14
  }
46
- /** A list of products, or anything keyed by SKU that answers `get` and `has`. */
47
15
  export type ProductCatalog = readonly Product[] | ReadonlyMap<string, Product>;
48
- /**
49
- * Renders a component specification.
50
- *
51
- * A Server Component: no hooks, no state, no effects, and therefore no client
52
- * bundle and no hydration for the recommendation area. The whole component
53
- * arrives in the initial HTML response, which is what removes the pop-in a
54
- * client-fetched recommendation rail has — and what makes the content visible
55
- * to a crawler that does not run JavaScript.
56
- *
57
- * Renders nothing at all when no block produced markup.
58
- */
59
16
  export declare function RudraComponent({ spec, products, bundles, registry, hrefForSku, formatPrice, formatBundlePrice, locale, hasDiagnostics, className, }: RudraComponentProps): import("react").JSX.Element | null;
60
17
  //# sourceMappingURL=rudra-component.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rudra-component.d.ts","sourceRoot":"","sources":["../src/rudra-component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAQ5E,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAEpE,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,aAAa,CAAC;IACpB;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,EAAE,cAAc,CAAC;IACzB,oFAAoF;IACpF,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;IAC3C,0FAA0F;IAC1F,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/C;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,iFAAiF;AACjF,MAAM,MAAM,cAAc,GAAG,SAAS,OAAO,EAAE,GAAG,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAiF/E;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,QAAQ,EACR,OAAO,EACP,QAA0B,EAC1B,UAA8B,EAC9B,WAAW,EACX,iBAAiB,EACjB,MAAM,EACN,cAAsB,EACtB,SAAS,GACV,EAAE,mBAAmB,sCA8CrB"}
1
+ {"version":3,"file":"rudra-component.d.ts","sourceRoot":"","sources":["../src/rudra-component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAQ5E,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAEpE,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,aAAa,CAAC;IACpB,QAAQ,EAAE,cAAc,CAAC;IACzB,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;IAC3C,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,cAAc,GAAG,SAAS,OAAO,EAAE,GAAG,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAsE/E,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,QAAQ,EACR,OAAO,EACP,QAA0B,EAC1B,UAA8B,EAC9B,WAAW,EACX,iBAAiB,EACjB,MAAM,EACN,cAAsB,EACtB,SAAS,GACV,EAAE,mBAAmB,sCAuCrB"}
@@ -1,10 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { defaultFormatBundlePrice, defaultFormatPrice, defaultHrefForSku, sellableProduct, } from './render-context.js';
3
3
  import { defaultRegistry } from './registry.js';
4
- // Asks what the renderers call rather than which class the host built, since
5
- // `instanceof Map` is per-realm. Checked before the list branch because a
6
- // collection can answer both: an Immutable.js map has `map`, and converting
7
- // through it yields `[sku, product]` pairs rather than products.
8
4
  function isKeyedBySku(catalog) {
9
5
  const candidate = catalog;
10
6
  return typeof candidate.get === 'function' && typeof candidate.has === 'function';
@@ -18,25 +14,24 @@ function toProductMap(catalog) {
18
14
  }
19
15
  return new Map(catalog.map((product) => [product.sku, product]));
20
16
  }
21
- // Whether a block still has anything to say once the catalog is applied. A SKU
22
- // can sell out between generating a spec and rendering it — the row goes, or it
23
- // is flagged out of stock, and both read the same here: grid and carousel lose
24
- // the products that did, a bundle loses itself if any one member did.
25
- function hasContent(block, products, bundles) {
17
+ function hasContent(block, context) {
18
+ const isSellable = (sku) => sellableProduct(context.products, sku) !== undefined;
26
19
  switch (block.kind) {
27
20
  case 'grid':
28
21
  case 'carousel':
29
- return block.items.some((reference) => sellableProduct(products, reference.sku) !== undefined);
22
+ return block.items.some((reference) => isSellable(reference.sku));
30
23
  case 'hero':
24
+ return (block.headline.length > 0 ||
25
+ (block.body !== null && block.body.length > 0) ||
26
+ (block.sku !== null && isSellable(block.sku)));
31
27
  case 'banner':
32
28
  case 'copy':
33
29
  return true;
34
30
  case 'bundle': {
35
31
  if (block.bundleId === null)
36
32
  return false;
37
- const bundle = bundles.get(block.bundleId);
38
- return (bundle !== undefined &&
39
- bundle.skus.every((sku) => sellableProduct(products, sku) !== undefined));
33
+ const bundle = context.bundles.get(block.bundleId);
34
+ return bundle !== undefined && bundle.skus.every((sku) => isSellable(sku));
40
35
  }
41
36
  default:
42
37
  block;
@@ -58,37 +53,21 @@ function renderBlock(block, context, registry, index) {
58
53
  case 'bundle':
59
54
  return _jsx(registry.bundle, { block: block, context: context }, index);
60
55
  default:
61
- // A block kind this renderer predates loses that block, not the page.
62
56
  block;
63
57
  return null;
64
58
  }
65
59
  }
66
- /**
67
- * Renders a component specification.
68
- *
69
- * A Server Component: no hooks, no state, no effects, and therefore no client
70
- * bundle and no hydration for the recommendation area. The whole component
71
- * arrives in the initial HTML response, which is what removes the pop-in a
72
- * client-fetched recommendation rail has — and what makes the content visible
73
- * to a crawler that does not run JavaScript.
74
- *
75
- * Renders nothing at all when no block produced markup.
76
- */
77
60
  export function RudraComponent({ spec, products, bundles, registry = defaultRegistry, hrefForSku = defaultHrefForSku, formatPrice, formatBundlePrice, locale, hasDiagnostics = false, className, }) {
78
- const productMap = toProductMap(products);
79
- const bundlesById = new Map((bundles ?? []).map((bundle) => [bundle.id, bundle]));
80
61
  const context = {
81
- products: productMap,
82
- bundles: bundlesById,
62
+ products: toProductMap(products),
63
+ bundles: new Map((bundles ?? []).map((bundle) => [bundle.id, bundle])),
83
64
  hrefForSku,
84
65
  formatPrice: formatPrice ?? ((product) => defaultFormatPrice(product, locale)),
85
66
  formatBundlePrice: formatBundlePrice ?? ((bundle) => defaultFormatBundlePrice(bundle, locale)),
86
67
  };
87
- const visible = spec.blocks.filter((block) => hasContent(block, context.products, context.bundles));
68
+ const visible = spec.blocks.filter((block) => hasContent(block, context));
88
69
  if (visible.length === 0)
89
70
  return null;
90
- // React drops a data-* attribute whose value is undefined, so degradedReason
91
- // needs no branch of its own.
92
71
  const diagnosticAttributes = hasDiagnostics
93
72
  ? {
94
73
  'data-rudra-provider': spec.provider ?? 'none',
@@ -1 +1 @@
1
- {"version":3,"file":"rudra-component.js","sourceRoot":"","sources":["../src/rudra-component.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rudra-component.js","sourceRoot":"","sources":["../src/rudra-component.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rudra-js/react",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "description": "Renders a rudra component specification with React 18 or 19, on the server or in the browser",
5
5
  "keywords": [
6
6
  "react",
@@ -51,11 +51,11 @@
51
51
  "access": "public"
52
52
  },
53
53
  "peerDependencies": {
54
- "@rudra-js/core": "^0.7.3",
54
+ "@rudra-js/core": "^0.7.5",
55
55
  "react": "^18.2.0 || ^19.0.0"
56
56
  },
57
57
  "devDependencies": {
58
- "@rudra-js/core": "0.7.3",
58
+ "@rudra-js/core": "0.7.5",
59
59
  "@types/react": "^19.3.0",
60
60
  "@types/react-dom": "^19.3.0",
61
61
  "react": "^19.3.0",
@@ -1,14 +1,12 @@
1
- /**
2
- * Five of the six block renderers. The bundle one lives in `bundle-block.tsx`,
3
- * next door, since it draws a set the shop picked rather than the model.
4
- *
5
- * Each takes a block the model produced and a context the host owns. None of
6
- * them interpolate markup, and none of them read a product fact from the block
7
- * — that is what keeps generated output to the role of deciding layout, order
8
- * and wording.
9
- */
10
-
11
- import type { BannerBlock, CarouselBlock, CopyBlock, GridBlock, HeroBlock } from '@rudra-js/core';
1
+ import type {
2
+ BannerBlock,
3
+ BundleBlock,
4
+ CarouselBlock,
5
+ CopyBlock,
6
+ GridBlock,
7
+ HeroBlock,
8
+ Product,
9
+ } from '@rudra-js/core';
12
10
  import { sellableProduct, type BlockRenderContext } from '../render-context.js';
13
11
  import { ProductCard } from './product-card.js';
14
12
 
@@ -23,7 +21,7 @@ export function HeroRenderer({
23
21
 
24
22
  return (
25
23
  <section className="rudra-hero">
26
- <h3 className="rudra-hero__headline">{block.headline}</h3>
24
+ {block.headline ? <h3 className="rudra-hero__headline">{block.headline}</h3> : null}
27
25
  {block.body ? <p className="rudra-hero__body">{block.body}</p> : null}
28
26
  {product ? (
29
27
  <a
@@ -31,8 +29,7 @@ export function HeroRenderer({
31
29
  href={context.hrefForSku(product.sku)}
32
30
  data-rudra-sku={product.sku}
33
31
  >
34
- {product.title}
35
- <span className="rudra-hero__price">{context.formatPrice(product)}</span>
32
+ {product.title} <span className="rudra-hero__price">{context.formatPrice(product)}</span>{' '}
36
33
  {block.ctaLabel ? <span className="rudra-hero__cta">{block.ctaLabel}</span> : null}
37
34
  </a>
38
35
  ) : null}
@@ -69,8 +66,6 @@ export function CarouselRenderer({
69
66
  return (
70
67
  <section className="rudra-carousel">
71
68
  {block.title ? <h3 className="rudra-carousel__title">{block.title}</h3> : null}
72
- {/* Scrolls with CSS overflow rather than JavaScript, so the whole
73
- component still needs no client bundle. */}
74
69
  <div className="rudra-carousel__track">
75
70
  {block.items.map((reference) => (
76
71
  <ProductCard key={reference.sku} reference={reference} context={context} />
@@ -83,7 +78,7 @@ export function CarouselRenderer({
83
78
  export function BannerRenderer({ block }: { block: BannerBlock }) {
84
79
  return (
85
80
  <aside className="rudra-banner" data-rudra-banner-tone={block.tone}>
86
- <span className="rudra-banner__text">{block.text}</span>
81
+ <span className="rudra-banner__text">{block.text}</span>{' '}
87
82
  {block.ctaLabel ? <span className="rudra-banner__cta">{block.ctaLabel}</span> : null}
88
83
  </aside>
89
84
  );
@@ -97,3 +92,42 @@ export function CopyRenderer({ block }: { block: CopyBlock }) {
97
92
  </section>
98
93
  );
99
94
  }
95
+
96
+ export function BundleRenderer({
97
+ block,
98
+ context,
99
+ }: {
100
+ block: BundleBlock;
101
+ context: BlockRenderContext;
102
+ }) {
103
+ const bundle = block.bundleId === null ? undefined : context.bundles.get(block.bundleId);
104
+ if (!bundle) return null;
105
+
106
+ const products: Product[] = [];
107
+ for (const sku of bundle.skus) {
108
+ const product = sellableProduct(context.products, sku);
109
+ if (!product) return null;
110
+ products.push(product);
111
+ }
112
+
113
+ return (
114
+ <section className="rudra-bundle">
115
+ {bundle.label ? <h3 className="rudra-bundle__label">{bundle.label}</h3> : null}
116
+ {block.title ? <p className="rudra-bundle__title">{block.title}</p> : null}
117
+ {block.body ? <p className="rudra-bundle__body">{block.body}</p> : null}
118
+
119
+ <ul className="rudra-bundle__items">
120
+ {products.map((product) => (
121
+ <li key={product.sku} className="rudra-bundle__item" data-rudra-sku={product.sku}>
122
+ <a className="rudra-bundle__link" href={context.hrefForSku(product.sku)}>
123
+ {product.title}
124
+ </a>
125
+ </li>
126
+ ))}
127
+ </ul>
128
+
129
+ <p className="rudra-bundle__price">{context.formatBundlePrice(bundle)}</p>
130
+ {block.ctaLabel ? <span className="rudra-bundle__cta">{block.ctaLabel}</span> : null}
131
+ </section>
132
+ );
133
+ }
@@ -1,14 +1,6 @@
1
1
  import type { ProductReference } from '@rudra-js/core';
2
2
  import { sellableProduct, type BlockRenderContext } from '../render-context.js';
3
3
 
4
- /**
5
- * One product.
6
- *
7
- * Every fact here — the title, the price, the image, the link — is read from
8
- * the catalog, not from the specification. The specification contributes only
9
- * `reason`, `badge` and `emphasis`, and React escapes all three on the way
10
- * into the markup.
11
- */
12
4
  export function ProductCard({
13
5
  reference,
14
6
  context,
@@ -17,8 +9,6 @@ export function ProductCard({
17
9
  context: BlockRenderContext;
18
10
  }) {
19
11
  const product = sellableProduct(context.products, reference.sku);
20
- // Reconciliation drops a SKU the catalog does not have, so this should be
21
- // unreachable. Rendering nothing beats rendering a card with holes in it.
22
12
  if (!product) return null;
23
13
 
24
14
  const isFeatured = reference.emphasis === 'featured';
@@ -35,9 +25,9 @@ export function ProductCard({
35
25
  ) : null}
36
26
 
37
27
  <span className="rudra-card__body">
38
- {reference.badge ? <span className="rudra-card__badge">{reference.badge}</span> : null}
39
- <span className="rudra-card__title">{product.title}</span>
40
- <span className="rudra-card__price">{context.formatPrice(product)}</span>
28
+ {reference.badge ? <span className="rudra-card__badge">{reference.badge}</span> : null}{' '}
29
+ <span className="rudra-card__title">{product.title}</span>{' '}
30
+ <span className="rudra-card__price">{context.formatPrice(product)}</span>{' '}
41
31
  {reference.reason ? <span className="rudra-card__reason">{reference.reason}</span> : null}
42
32
  </span>
43
33
  </a>
package/src/registry.ts CHANGED
@@ -3,12 +3,12 @@ import type { Block, BlockKind } from '@rudra-js/core';
3
3
  import type { BlockRenderContext } from './render-context.js';
4
4
  import {
5
5
  BannerRenderer,
6
+ BundleRenderer,
6
7
  CarouselRenderer,
7
8
  CopyRenderer,
8
9
  GridRenderer,
9
10
  HeroRenderer,
10
11
  } from './blocks/block-renderers.js';
11
- import { BundleRenderer } from './blocks/bundle-block.js';
12
12
 
13
13
  export type BlockRenderer<Kind extends BlockKind> = (props: {
14
14
  block: Extract<Block, { kind: Kind }>;
@@ -2,9 +2,7 @@ import type { Bundle, Product } from '@rudra-js/core';
2
2
 
3
3
  export interface BlockRenderContext {
4
4
  readonly products: ReadonlyMap<string, Product>;
5
-
6
5
  readonly bundles: ReadonlyMap<string, Bundle>;
7
-
8
6
  readonly hrefForSku: (sku: string) => string;
9
7
  readonly formatPrice: (product: Product) => string;
10
8
  readonly formatBundlePrice: (bundle: Bundle) => string;
@@ -22,6 +20,16 @@ export function sellableProduct(
22
20
  return product?.isInStock === false ? undefined : product;
23
21
  }
24
22
 
23
+ function formatMoney(price: number, currency: string, locale?: string): string {
24
+ let formatter: Intl.NumberFormat;
25
+ try {
26
+ formatter = new Intl.NumberFormat(locale, { style: 'currency', currency });
27
+ } catch {
28
+ return `${currency} ${price}`;
29
+ }
30
+ return formatter.format(price);
31
+ }
32
+
25
33
  export function defaultFormatPrice(product: Product, locale?: string): string {
26
34
  if (!Number.isFinite(product.price)) {
27
35
  throw new TypeError(
@@ -29,15 +37,7 @@ export function defaultFormatPrice(product: Product, locale?: string): string {
29
37
  'catalog objects must satisfy productSchema from @rudra-js/core',
30
38
  );
31
39
  }
32
-
33
- let formatter: Intl.NumberFormat;
34
- try {
35
- formatter = new Intl.NumberFormat(locale, { style: 'currency', currency: product.currency });
36
- } catch {
37
- return `${product.currency} ${product.price}`;
38
- }
39
-
40
- return formatter.format(product.price);
40
+ return formatMoney(product.price, product.currency, locale);
41
41
  }
42
42
 
43
43
  export function defaultFormatBundlePrice(bundle: Bundle, locale?: string): string {
@@ -47,13 +47,5 @@ export function defaultFormatBundlePrice(bundle: Bundle, locale?: string): strin
47
47
  'bundle objects must satisfy bundleSchema from @rudra-js/core',
48
48
  );
49
49
  }
50
-
51
- let formatter: Intl.NumberFormat;
52
- try {
53
- formatter = new Intl.NumberFormat(locale, { style: 'currency', currency: bundle.currency });
54
- } catch {
55
- return `${bundle.currency} ${bundle.price}`;
56
- }
57
-
58
- return formatter.format(bundle.price);
50
+ return formatMoney(bundle.price, bundle.currency, locale);
59
51
  }
@@ -10,55 +10,19 @@ import { defaultRegistry, type BlockRegistry } from './registry.js';
10
10
 
11
11
  export interface RudraComponentProps {
12
12
  spec: ComponentSpec;
13
- /**
14
- * The host catalog. Every product fact on the page comes from here rather
15
- * than from the specification.
16
- *
17
- * A list of products, or anything keyed by SKU — a `Map`, or your own view
18
- * over a catalog too large to hold in one. The renderers only ever call
19
- * `get(sku)` and `has(sku)`, so a view needs nothing else to be fast.
20
- *
21
- * Validate them with `productSchema` from `@rudra-js/core` — the same schema
22
- * your candidates already passed — not with `parseTrackingInput`, which
23
- * parses a whole tracking payload and will reject a bare catalog.
24
- *
25
- * This is a second door into the framework. `imageUrl` lands in an
26
- * `<img src>`, and `productSchema` is the only thing that rejects a
27
- * protocol-relative `//evil.example/pixel.png` or a `data:` URL — React
28
- * neutralises `javascript:` on its own, but not those. A price that is not a
29
- * finite number throws rather than rendering as free.
30
- */
31
13
  products: ProductCatalog;
32
- /** Sets the shop sells together. Only needed if a spec can carry a bundle block. */
33
14
  bundles?: readonly Bundle[];
34
15
  registry?: BlockRegistry;
35
16
  hrefForSku?: (sku: string) => string;
36
17
  formatPrice?: (product: Product) => string;
37
- /** Same as `formatPrice`, but for a bundle — the shop's price, not a sum of the parts. */
38
18
  formatBundlePrice?: (bundle: Bundle) => string;
39
- /**
40
- * The shopper's locale, used to punctuate prices. Defaults to the server's,
41
- * which is almost never the shopper's — pass it if the shop serves more than
42
- * one. Ignored when `formatPrice` and `formatBundlePrice` are supplied.
43
- */
44
19
  locale?: string;
45
- /**
46
- * Adds the model's own reasoning, the provider and the model name to the
47
- * markup. Useful while developing and while benchmarking; it publishes which
48
- * vendor a shop uses and whether the component is currently degraded, so it
49
- * is off unless asked for.
50
- */
51
20
  hasDiagnostics?: boolean;
52
21
  className?: string;
53
22
  }
54
23
 
55
- /** A list of products, or anything keyed by SKU that answers `get` and `has`. */
56
24
  export type ProductCatalog = readonly Product[] | ReadonlyMap<string, Product>;
57
25
 
58
- // Asks what the renderers call rather than which class the host built, since
59
- // `instanceof Map` is per-realm. Checked before the list branch because a
60
- // collection can answer both: an Immutable.js map has `map`, and converting
61
- // through it yields `[sku, product]` pairs rather than products.
62
26
  function isKeyedBySku(catalog: ProductCatalog): catalog is ReadonlyMap<string, Product> {
63
27
  const candidate = catalog as { get?: unknown; has?: unknown };
64
28
  return typeof candidate.get === 'function' && typeof candidate.has === 'function';
@@ -75,32 +39,26 @@ function toProductMap(catalog: ProductCatalog): ReadonlyMap<string, Product> {
75
39
  return new Map(catalog.map((product) => [product.sku, product]));
76
40
  }
77
41
 
78
- // Whether a block still has anything to say once the catalog is applied. A SKU
79
- // can sell out between generating a spec and rendering it — the row goes, or it
80
- // is flagged out of stock, and both read the same here: grid and carousel lose
81
- // the products that did, a bundle loses itself if any one member did.
82
- function hasContent(
83
- block: Block,
84
- products: ReadonlyMap<string, Product>,
85
- bundles: ReadonlyMap<string, Bundle>,
86
- ): boolean {
42
+ function hasContent(block: Block, context: BlockRenderContext): boolean {
43
+ const isSellable = (sku: string) => sellableProduct(context.products, sku) !== undefined;
44
+
87
45
  switch (block.kind) {
88
46
  case 'grid':
89
47
  case 'carousel':
90
- return block.items.some(
91
- (reference) => sellableProduct(products, reference.sku) !== undefined,
92
- );
48
+ return block.items.some((reference) => isSellable(reference.sku));
93
49
  case 'hero':
50
+ return (
51
+ block.headline.length > 0 ||
52
+ (block.body !== null && block.body.length > 0) ||
53
+ (block.sku !== null && isSellable(block.sku))
54
+ );
94
55
  case 'banner':
95
56
  case 'copy':
96
57
  return true;
97
58
  case 'bundle': {
98
59
  if (block.bundleId === null) return false;
99
- const bundle = bundles.get(block.bundleId);
100
- return (
101
- bundle !== undefined &&
102
- bundle.skus.every((sku) => sellableProduct(products, sku) !== undefined)
103
- );
60
+ const bundle = context.bundles.get(block.bundleId);
61
+ return bundle !== undefined && bundle.skus.every((sku) => isSellable(sku));
104
62
  }
105
63
  default:
106
64
  block satisfies never;
@@ -128,23 +86,11 @@ function renderBlock(
128
86
  case 'bundle':
129
87
  return <registry.bundle key={index} block={block} context={context} />;
130
88
  default:
131
- // A block kind this renderer predates loses that block, not the page.
132
89
  block satisfies never;
133
90
  return null;
134
91
  }
135
92
  }
136
93
 
137
- /**
138
- * Renders a component specification.
139
- *
140
- * A Server Component: no hooks, no state, no effects, and therefore no client
141
- * bundle and no hydration for the recommendation area. The whole component
142
- * arrives in the initial HTML response, which is what removes the pop-in a
143
- * client-fetched recommendation rail has — and what makes the content visible
144
- * to a crawler that does not run JavaScript.
145
- *
146
- * Renders nothing at all when no block produced markup.
147
- */
148
94
  export function RudraComponent({
149
95
  spec,
150
96
  products,
@@ -157,24 +103,17 @@ export function RudraComponent({
157
103
  hasDiagnostics = false,
158
104
  className,
159
105
  }: RudraComponentProps) {
160
- const productMap = toProductMap(products);
161
- const bundlesById = new Map((bundles ?? []).map((bundle) => [bundle.id, bundle]));
162
-
163
106
  const context: BlockRenderContext = {
164
- products: productMap,
165
- bundles: bundlesById,
107
+ products: toProductMap(products),
108
+ bundles: new Map((bundles ?? []).map((bundle) => [bundle.id, bundle])),
166
109
  hrefForSku,
167
110
  formatPrice: formatPrice ?? ((product) => defaultFormatPrice(product, locale)),
168
111
  formatBundlePrice: formatBundlePrice ?? ((bundle) => defaultFormatBundlePrice(bundle, locale)),
169
112
  };
170
113
 
171
- const visible = spec.blocks.filter((block) =>
172
- hasContent(block, context.products, context.bundles),
173
- );
114
+ const visible = spec.blocks.filter((block) => hasContent(block, context));
174
115
  if (visible.length === 0) return null;
175
116
 
176
- // React drops a data-* attribute whose value is undefined, so degradedReason
177
- // needs no branch of its own.
178
117
  const diagnosticAttributes = hasDiagnostics
179
118
  ? {
180
119
  'data-rudra-provider': spec.provider ?? 'none',
@@ -1,7 +0,0 @@
1
- import type { BundleBlock } from '@rudra-js/core';
2
- import { type BlockRenderContext } from '../render-context.js';
3
- export declare function BundleRenderer({ block, context, }: {
4
- block: BundleBlock;
5
- context: BlockRenderContext;
6
- }): import("react").JSX.Element | null;
7
- //# sourceMappingURL=bundle-block.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bundle-block.d.ts","sourceRoot":"","sources":["../../src/blocks/bundle-block.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAW,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAmB,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAEhF,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,kBAAkB,CAAC;CAC7B,sCAiCA"}
@@ -1,17 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { sellableProduct } from '../render-context.js';
3
- export function BundleRenderer({ block, context, }) {
4
- const bundle = block.bundleId === null ? undefined : context.bundles.get(block.bundleId);
5
- if (!bundle)
6
- return null;
7
- const products = [];
8
- for (const sku of bundle.skus) {
9
- const product = sellableProduct(context.products, sku);
10
- // A set missing one of its parts is not that set.
11
- if (!product)
12
- return null;
13
- products.push(product);
14
- }
15
- return (_jsxs("section", { className: "rudra-bundle", children: [bundle.label ? _jsx("h3", { className: "rudra-bundle__label", children: bundle.label }) : null, block.title ? _jsx("p", { className: "rudra-bundle__title", children: block.title }) : null, block.body ? _jsx("p", { className: "rudra-bundle__body", children: block.body }) : null, _jsx("ul", { className: "rudra-bundle__items", children: products.map((product) => (_jsx("li", { className: "rudra-bundle__item", "data-rudra-sku": product.sku, children: _jsx("a", { className: "rudra-bundle__link", href: context.hrefForSku(product.sku), children: product.title }) }, product.sku))) }), _jsx("p", { className: "rudra-bundle__price", children: context.formatBundlePrice(bundle) }), block.ctaLabel ? _jsx("span", { className: "rudra-bundle__cta", children: block.ctaLabel }) : null] }));
16
- }
17
- //# sourceMappingURL=bundle-block.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bundle-block.js","sourceRoot":"","sources":["../../src/blocks/bundle-block.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,EAA2B,MAAM,sBAAsB,CAAC;AAEhF,MAAM,UAAU,cAAc,CAAC,EAC7B,KAAK,EACL,OAAO,GAIR;IACC,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACzF,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QACvD,kDAAkD;QAClD,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC1B,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAED,OAAO,CACL,mBAAS,SAAS,EAAC,cAAc,aAE9B,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,aAAI,SAAS,EAAC,qBAAqB,YAAE,MAAM,CAAC,KAAK,GAAM,CAAC,CAAC,CAAC,IAAI,EAC7E,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,YAAG,SAAS,EAAC,qBAAqB,YAAE,KAAK,CAAC,KAAK,GAAK,CAAC,CAAC,CAAC,IAAI,EACzE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,YAAG,SAAS,EAAC,oBAAoB,YAAE,KAAK,CAAC,IAAI,GAAK,CAAC,CAAC,CAAC,IAAI,EAEvE,aAAI,SAAS,EAAC,qBAAqB,YAChC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CACzB,aAAsB,SAAS,EAAC,oBAAoB,oBAAiB,OAAO,CAAC,GAAG,YAC9E,YAAG,SAAS,EAAC,oBAAoB,EAAC,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,YACpE,OAAO,CAAC,KAAK,GACZ,IAHG,OAAO,CAAC,GAAG,CAIf,CACN,CAAC,GACC,EAEL,YAAG,SAAS,EAAC,qBAAqB,YAAE,OAAO,CAAC,iBAAiB,CAAC,MAAM,CAAC,GAAK,EACzE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,mBAAmB,YAAE,KAAK,CAAC,QAAQ,GAAQ,CAAC,CAAC,CAAC,IAAI,IAC5E,CACX,CAAC;AACJ,CAAC"}
@@ -1,43 +0,0 @@
1
- import type { BundleBlock, Product } from '@rudra-js/core';
2
- import { sellableProduct, type BlockRenderContext } from '../render-context.js';
3
-
4
- export function BundleRenderer({
5
- block,
6
- context,
7
- }: {
8
- block: BundleBlock;
9
- context: BlockRenderContext;
10
- }) {
11
- const bundle = block.bundleId === null ? undefined : context.bundles.get(block.bundleId);
12
- if (!bundle) return null;
13
-
14
- const products: Product[] = [];
15
- for (const sku of bundle.skus) {
16
- const product = sellableProduct(context.products, sku);
17
- // A set missing one of its parts is not that set.
18
- if (!product) return null;
19
- products.push(product);
20
- }
21
-
22
- return (
23
- <section className="rudra-bundle">
24
- {/* The shop's label is the one name here anyone can check, so it leads. */}
25
- {bundle.label ? <h3 className="rudra-bundle__label">{bundle.label}</h3> : null}
26
- {block.title ? <p className="rudra-bundle__title">{block.title}</p> : null}
27
- {block.body ? <p className="rudra-bundle__body">{block.body}</p> : null}
28
-
29
- <ul className="rudra-bundle__items">
30
- {products.map((product) => (
31
- <li key={product.sku} className="rudra-bundle__item" data-rudra-sku={product.sku}>
32
- <a className="rudra-bundle__link" href={context.hrefForSku(product.sku)}>
33
- {product.title}
34
- </a>
35
- </li>
36
- ))}
37
- </ul>
38
-
39
- <p className="rudra-bundle__price">{context.formatBundlePrice(bundle)}</p>
40
- {block.ctaLabel ? <span className="rudra-bundle__cta">{block.ctaLabel}</span> : null}
41
- </section>
42
- );
43
- }