@orderlyshop/web-components 0.1.0-build.7050 → 0.1.0-build.7056

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.
Files changed (50) hide show
  1. package/AGENTS.md +2 -0
  2. package/README.md +148 -34
  3. package/bin/orderly-generate-server-renderers.mjs +624 -0
  4. package/bin/orderly-hydrate-static-pages.mjs +3 -2
  5. package/bin/orderly-init-shop.mjs +56 -88
  6. package/custom-elements.json +37 -4
  7. package/dist/browser/orderly-web-components.define.global.js +967 -251
  8. package/dist/browser/orderly-web-components.define.global.js.map +1 -1
  9. package/dist/browser/orderly-web-components.global.js +967 -251
  10. package/dist/browser/orderly-web-components.global.js.map +1 -1
  11. package/dist/{default-shop-DgX6uy10.d.ts → default-shop-D3ONKY6z.d.ts} +54 -9
  12. package/dist/default-shop.d.ts +2 -2
  13. package/dist/default-shop.js +88 -0
  14. package/dist/default-shop.js.map +1 -1
  15. package/dist/define-B607vO7u.d.ts +9 -0
  16. package/dist/define.d.ts +1 -1
  17. package/dist/define.js +2506 -429
  18. package/dist/define.js.map +1 -1
  19. package/dist/index.d.ts +67 -10
  20. package/dist/index.js +3018 -438
  21. package/dist/index.js.map +1 -1
  22. package/dist/navigation.d.ts +5 -2
  23. package/dist/navigation.js +240 -38
  24. package/dist/navigation.js.map +1 -1
  25. package/dist/{shop-BgQhGRzS.d.ts → shop-D1M9VnvQ.d.ts} +6 -1
  26. package/dist/shop.d.ts +4 -4
  27. package/dist/shop.js +2948 -451
  28. package/dist/shop.js.map +1 -1
  29. package/dist/stores.d.ts +26 -3
  30. package/dist/stores.js +200 -2
  31. package/dist/stores.js.map +1 -1
  32. package/dist/taxonomy.d.ts +2 -2
  33. package/dist/types-CXEwL2xS.d.ts +170 -0
  34. package/docs/components/README.md +76 -5
  35. package/docs/components/product-grid.md +1 -2
  36. package/examples/shop/README.md +8 -4
  37. package/examples/shop/package.json +2 -2
  38. package/examples/shop/src/includes/head.html +16 -4
  39. package/examples/shop/src/payment-failure.html +20 -0
  40. package/examples/shop/src/payment-success.html +20 -0
  41. package/examples/shop/src/storefront-router.ts +151 -0
  42. package/examples/shop/src/templates/page-layouts.html +199 -1
  43. package/examples/shop/vite.config.mjs +2 -67
  44. package/html-custom-data.json +33 -4
  45. package/package.json +7 -3
  46. package/server/README.md +57 -80
  47. package/server/ssr.mjs +783 -0
  48. package/server/vite.mjs +48 -0
  49. package/dist/define-BNMhl19n.d.ts +0 -9
  50. package/dist/types-Bjez59Hr.d.ts +0 -96
package/AGENTS.md CHANGED
@@ -5,6 +5,7 @@ Use this package as a headless storefront component library. Prefer native custo
5
5
  ## Discovery
6
6
 
7
7
  - Read `README.md` for setup and package-wide concepts.
8
+ - Read `DESIGN.md` before changing the visual theme. It defines the semantic design tokens that should be changed first.
8
9
  - Read `docs/components/README.md` for component APIs.
9
10
  - Read `docs/components/product-rail.md` and `docs/components/product-grid.md` before implementing product listing UI.
10
11
  - Read `custom-elements.json` for machine-readable custom element metadata.
@@ -34,6 +35,7 @@ Use this package as a headless storefront component library. Prefer native custo
34
35
  ## Implementation Defaults
35
36
 
36
37
  - Keep components headless. Add behavior and default light-DOM markup, not bundled theme CSS.
38
+ - Start theme work from the semantic tokens in `DESIGN.md`. Use component-local selectors and hooks only for deliberate exceptions after the shared tokens are set.
37
39
  - Built-in UI copy defaults to Danish. Use `configureShop({ uiLanguage: "EN" })` for English shops, and prefer shop attributes/templates for wording that is unique to one storefront.
38
40
  - Preserve template override support through `data-orderly-template`, `data-orderly-bind`, `data-orderly-slot`, and `data-orderly-action`.
39
41
  - Put shop-wide templates in a shared document-level registry, for example template files included from `body-start.html`, using `data-orderly-template` plus `data-orderly-for`.
package/README.md CHANGED
@@ -20,13 +20,15 @@ npm install
20
20
  npm run dev
21
21
  ```
22
22
 
23
- Agents and developers should use `npx orderly-init-shop` before hand-writing shop files. It creates `src/index.html`, `src/category.html`, `src/product.html`, `src/checkout.html`, `src/includes/head.html`, `src/navigation.ts`, `src/shop-query.ts`, `src/templates/*.html`, `src/style.css`, `vite.config.mjs`, and package scripts. Pass an account id when the shop-wide default query should be tenant-scoped from the start:
23
+ Agents and developers should use `npx orderly-init-shop` before hand-writing shop files. It creates `src/index.html`, `src/category.html`, `src/product.html`, `src/checkout.html`, `src/payment-success.html`, `src/payment-failure.html`, `src/includes/head.html`, `src/navigation.ts`, `src/shop-query.ts`, `src/templates/*.html`, `src/style.css`, `vite.config.mjs`, and package scripts. The payment pages are physical callback URLs for payment providers and use the package `orderly-payment-success-page` and `orderly-payment-failure-page` components. Pass an account id when the shop-wide default query should be tenant-scoped from the start:
24
24
 
25
25
  ```sh
26
26
  npx orderly-init-shop --account-id 00000000-0000-0000-0000-000000000000
27
27
  ```
28
28
 
29
- The scaffold uses Vite for local development and static builds, but Vite is added to the generated shop's `devDependencies`. Local development is configured for `http://localhost:61677` because that is the backend CORS development origin. `@orderlyshop/web-components` does not depend on Vite at runtime.
29
+ Re-running `npx orderly-init-shop` in an existing shop is non-destructive by default. Existing scaffold files are left unchanged with warnings, while missing new scaffold files are added. Use `--force` only when you explicitly want scaffold files overwritten.
30
+
31
+ The scaffold uses Vite for local development and static builds, but Vite is added to the generated shop's `devDependencies`. Dev mode SSR for product/category URLs is enabled by default, so View Source on local category/product URLs shows semantic server-rendered markup. Consumers should test locally from `http://localhost:61677` or `https://localhost:61677` because those are the expected local development origins for backend CORS. The generated Vite setup defaults to `http://localhost:61677`; if a local reverse proxy or TLS terminator is added, keep the same `localhost:61677` origin over HTTPS. `@orderlyshop/web-components` does not depend on Vite at runtime.
30
32
 
31
33
  ## Browser Standalone Bundle
32
34
 
@@ -66,7 +68,7 @@ Use the auto-register bundle for the built-in default shop or pages that configu
66
68
 
67
69
  The normal package entrypoints remain ESM for bundlers. The standalone files are classic browser scripts and expose `window.OrderlyWebComponents`.
68
70
 
69
- `configureShop()` is the recommended one-call setup for HTML head scripts. Its parameter is strongly typed as `ShopConfig`, so VS Code/TypeScript can provide completions directly on the object literal. All fields are optional: `uiLanguage`, `defaultShop`, `pageLayout`, `responsiveTemplates`, `shopFooter`, `storedImageUrls`, and `components`. It applies configuration first and then registers custom elements. Pass `components: false` only when elements are registered elsewhere.
71
+ `configureShop()` is the recommended one-call setup for HTML head scripts. Its parameter is strongly typed as `ShopConfig`, so VS Code/TypeScript can provide completions directly on the object literal. All fields are optional: `uiLanguage`, `defaultShop`, `storefrontRouter`, `pageLayout`, `responsiveTemplates`, `shopFooter`, `storedImageUrls`, and `components`. It applies configuration first and then registers custom elements. The built-in SPA storefront router is enabled by default; pass `storefrontRouter: { enabled: false }` only when a shop needs traditional full-page navigation for every internal link. Pass `components: false` only when elements are registered elsewhere.
70
72
 
71
73
  Built-in component copy is Danish by default (`uiLanguage: "DA"`). Set `uiLanguage: "EN"` to switch built-in labels, empty states, checkout copy, navigation controls, and page defaults to English. Shop-specific attributes, templates, and `defaultShop` label fields still take precedence over language defaults.
72
74
 
@@ -74,6 +76,7 @@ Built-in component copy is Danish by default (`uiLanguage: "DA"`). Set `uiLangua
74
76
 
75
77
  Developers and coding agents should start with these package-owned docs:
76
78
 
79
+ - [`DESIGN.md`](./DESIGN.md) defines the package-wide design tokens and the recommended theming workflow for consistent styling across all components.
77
80
  - [`docs/components/README.md`](./docs/components/README.md) lists every custom element, common attributes, typed properties, events, and template hooks.
78
81
  - [`docs/components/product-grid.md`](./docs/components/product-grid.md) documents search loading, sorting, paging, default query merging, and declarative query markup.
79
82
  - [`docs/components/product-rail.md`](./docs/components/product-rail.md) documents homepage/category rails and their query configuration.
@@ -118,6 +121,82 @@ import "@orderlyshop/web-components/define";
118
121
 
119
122
  The default backend is `https://service.orderly.shop`. Category links use real `/categories/<category-path>/` URLs by default so production builds can ship server-rendered category HTML. Use `categoryUrlMode: "hash"` only when a shop explicitly wants the older `/category.html#id=<category>` routing.
120
123
 
124
+ ## SPA Storefront Navigation
125
+
126
+ SPA-style storefront navigation is the default when a shop calls `configureShop()`. It is implemented as progressive enhancement, not as a replacement for normal URLs:
127
+
128
+ - Links remain normal same-origin `<a href>` links, so Google, no-JS browsers, new tabs, copied links, and server-rendered entry pages still work.
129
+ - Ordinary left-click navigation between known storefront routes is intercepted in the browser and rendered without a physical reload.
130
+ - The router uses `history.pushState`, `popstate`, scroll restoration, canonical URLs, meta description updates, document title updates, and optional View Transitions.
131
+ - A shared `BasketController` is kept alive across home, category, product, checkout, payment result, and configured static routes.
132
+ - Unknown routes, external links, modified clicks, downloads, `_blank` links, and hash-only links fall back to the browser.
133
+
134
+ The default route coverage is:
135
+
136
+ - home: `defaultShop.homeHref`, default `/`
137
+ - categories: real category paths generated from `navigationDefinitions`, default `/categories/<category-path>/`
138
+ - product details: either hash or path product URLs, controlled by `defaultShop.productUrlMode`
139
+ - checkout: `defaultShop.checkoutHref`, default `/checkout.html`
140
+ - payment result pages: `/payment-success.html?orderid=...` and `/payment-failure.html?orderid=...`
141
+ - static pages: routes explicitly supplied in `storefrontRouter.staticRoutes`
142
+
143
+ The simplest setup needs no router block:
144
+
145
+ ```ts
146
+ configureShop({
147
+ defaultShop: {
148
+ homeHref: "/",
149
+ productHref: "/products/",
150
+ productUrlMode: "path",
151
+ productPathRoot: "/products/",
152
+ checkoutHref: "/checkout.html",
153
+ navigationDefinitions
154
+ }
155
+ });
156
+ ```
157
+
158
+ Use `storefrontRouter` only when the shop needs custom static pages, custom page factories, View Transition control, or an explicit opt-out:
159
+
160
+ ```ts
161
+ configureShop({
162
+ defaultShop: {
163
+ homeHref: "/",
164
+ productHref: "/products/",
165
+ productUrlMode: "path",
166
+ productPathRoot: "/products/",
167
+ checkoutHref: "/checkout.html"
168
+ },
169
+ storefrontRouter: {
170
+ product: {
171
+ mode: "path",
172
+ pathRoot: "/products/"
173
+ },
174
+ staticRoutes: [
175
+ {
176
+ path: "/forretningsbetingelser.html",
177
+ title: "Forretningsbetingelser | Example Shop",
178
+ description: "Køb, levering, betaling, retur og reklamation.",
179
+ create: () => document.querySelector("#termsPage")!.cloneNode(true) as HTMLElement
180
+ }
181
+ ]
182
+ }
183
+ });
184
+ ```
185
+
186
+ Disable SPA navigation explicitly when a host application owns routing or when every internal link must perform a full document navigation:
187
+
188
+ ```ts
189
+ configureShop({
190
+ storefrontRouter: {
191
+ enabled: false
192
+ }
193
+ });
194
+ ```
195
+
196
+ If a shop still uses hash-based product URLs, set `productUrlMode: "hash"` or `storefrontRouter.product.mode: "hash"`. The package supports both `#url=<encoded-share-url>` and real product paths; path URLs are preferred for SEO, while hash URLs remain a fallback for shops that cannot add server-side product routing yet.
197
+
198
+ For SEO, keep physical pages or static generated pages available for every URL the router can render dynamically. The router improves navigation after the first load; it does not remove the need for server/static support for direct requests to category, product, checkout, and content URLs.
199
+
121
200
  Customize the default once per shop from the HTML head:
122
201
 
123
202
  ```ts
@@ -194,13 +273,15 @@ After installing from the repository workspace, run it with:
194
273
  npm run dev --workspace @orderlyshop/example-shop
195
274
  ```
196
275
 
276
+ Open the local shop from `http://localhost:61677` during normal development, or `https://localhost:61677` when a local TLS proxy is in front of the dev server. Consumers should keep that exact localhost origin because the backend CORS development policy expects it.
277
+
197
278
  Inside an installed package, inspect the same reference implementation at:
198
279
 
199
280
  ```text
200
281
  node_modules/@orderlyshop/web-components/examples/shop
201
282
  ```
202
283
 
203
- The example demonstrates how to customize the package default with Danish navigation, shop copy, and declarative home/category/product/checkout pages while exercising the base component renderers directly. It intentionally avoids local CSS and templates while the package default look and feel is developed. Shared component configuration lives in `src/includes/head.html`; the strongly typed navigation data lives in `src/navigation.ts`. It uses the package-level `orderly-generate-category-pages` and `orderly-build-category-pages` commands for simple real-URL category pages. It is included as readable source code and is not exported as part of the runtime API.
284
+ The example demonstrates how to customize the package default with Danish navigation, shop copy, declarative home/category/product/checkout pages, and the default SPA router. Its router config only adds static content routes and page factories; `enabled: true` is not required. It intentionally avoids local CSS and templates while the package default look and feel is developed. Shared component configuration lives in `src/includes/head.html`; the strongly typed navigation data lives in `src/navigation.ts`. It uses the package-level `orderly-generate-category-pages` and `orderly-build-category-pages` commands for simple real-URL category pages. It is included as readable source code and is not exported as part of the runtime API.
204
285
 
205
286
  ## Navigation Setup
206
287
 
@@ -296,7 +377,7 @@ Build with generated category pages and then clean the generated source files. S
296
377
  npx orderly-build-category-pages
297
378
  ```
298
379
 
299
- `orderly-build-category-pages` also hydrates the built `dist/index.html` and generated `dist/categories/**/index.html` pages with SEO-friendly fallback HTML. The hydration step embeds a small product snapshot per category using `SearchService.Search`; it reads `--base-url`, `VITE_ORDERLY_BASE_URL`, or `ORDERLY_BASE_URL`, and otherwise uses the default `https://service.orderly.shop` backend. Live web components hide the fallback when their client-side search data has loaded, so users avoid a blank category page while Google receives meaningful initial HTML.
380
+ `orderly-build-category-pages` also hydrates the built `dist/index.html` and generated `dist/categories/**/index.html` pages with SEO-friendly fallback HTML. The hydration step embeds semantic product cards per category using `SearchService.Search`; it reads `--base-url`, `VITE_ORDERLY_BASE_URL`, or `ORDERLY_BASE_URL`, and otherwise uses the default `https://service.orderly.shop` backend. Live web components read the semantic fallback as initial state, hide it, and then continue as normal client components.
300
381
 
301
382
  Hydrate an already-built site manually, for example in a nightly job:
302
383
 
@@ -308,6 +389,26 @@ Use `--skip-products` for metadata-only hydration, `--strict-products` when a ni
308
389
 
309
390
  The category generator expects `src/navigation.ts` or `src/navigation.js` to export `navigationDefinitions` and the template page to contain `<orderly-category-page></orderly-category-page>`. TypeScript navigation files use the current shop project's `typescript` dependency for one-off transpilation. Useful options include `--navigation`, `--template`, `--categories-dir`, `--site-title`, `--component-tag`, and `--export`. The older `--taxonomy` flag and `categoryDefinitions` export are still accepted as compatibility aliases.
310
391
 
392
+ ## Local SSR Dev Mode
393
+
394
+ Scaffolded shops use SSR in dev mode by default through the package Vite middleware:
395
+
396
+ ```js
397
+ import { orderlySsrDevServer } from "@orderlyshop/web-components/server/vite";
398
+
399
+ export default defineConfig({
400
+ plugins: [htmlIncludes(), orderlySsrDevServer(), rootHtmlOutput()]
401
+ });
402
+ ```
403
+
404
+ When you run `npm run dev`, product and category URLs such as `http://localhost:61677/products/example.html/` and `http://localhost:61677/categories/sko/` are served from the normal `src/product.html` and `src/category.html` templates with semantic SSR HTML injected before Vite transforms the page. Use browser View Source to inspect the server-rendered markup.
405
+
406
+ SSR responses include a small inline critical CSS block for the semantic fallback markup. Normal browser requests hide that fallback and route component light DOM, such as utility banners, until the web components hydrate, so users do not see an unstyled intermediate layout. Googlebot user agents receive the visible fallback layout instead. The HTML source is identical in both cases; only fallback visibility changes.
407
+
408
+ After the first SSR entry render, the default storefront router intercepts same-origin category, product, checkout, payment-result, and configured static-page links. Navigation then stays virtual with `pushState` and persistent app-shell state, while the anchors still keep real `href` values for SEO, no-JS clients, copied links, and direct entry URLs. Set `storefrontRouter: { enabled: false }` only if the host app deliberately wants physical navigation after every click.
409
+
410
+ Set `ORDERLY_DEV_SSR=0` or run `npm run dev -- --no-ssr` to debug pure client-side rendering. Dev SSR uses `VITE_ORDERLY_BASE_URL`, `ORDERLY_BASE_URL`, or `https://service.orderly.shop`, defaults to `grpc-web`, and keeps a short per-URL in-memory cache. Override the cache with `ORDERLY_DEV_SSR_CACHE_TTL_MS`.
411
+
311
412
  ## Publish Static Sites
312
413
 
313
414
  Installing `@orderlyshop/web-components` also installs a publish command for the consuming shop project. Run it from the project directory. The command generates category pages, builds, hydrates the built homepage/category pages, and publishes the resulting `dist` folder as vanilla HTML/JS/CSS.
@@ -338,33 +439,25 @@ Re-enter saved FTP settings:
338
439
  npx orderly-publish-site --target ftp --configure
339
440
  ```
340
441
 
341
- ## Server Side Product URLs
442
+ ## Server Side Product And Category URLs
342
443
 
343
- Category pages can be generated and hydrated statically. Product pages usually need real URLs without generating one HTML file per product. The package ships server helper files under `server/` for this pattern:
344
-
345
- - `server/apache/.htaccess` rewrites unknown product-like `.html` URLs to PHP.
346
- - `server/php/orderly-product.php` reuses the built `product.html`, injects declarative SSR fallback markup into `<orderly-product-detail-page>`, and preserves built JS/CSS references.
347
- - `server/nginx/orderly-products.conf` provides the equivalent Nginx rewrite for PHP-FPM.
348
- - `server/node/product-snapshot-server.mjs` is an optional Node snapshot endpoint that calls `SearchService.Search` through `@orderlyshop/core-client` and returns product title, brand, price, image, and description as JSON.
349
-
350
- Apache deployment example:
444
+ Production SSR for Apache/PHP is generated into the built site:
351
445
 
352
446
  ```sh
353
447
  npm run build
354
- cp node_modules/@orderlyshop/web-components/server/apache/.htaccess dist/.htaccess
355
- cp node_modules/@orderlyshop/web-components/server/php/orderly-product.php dist/orderly-product.php
448
+ npx orderly-generate-server-renderers --site-title "My Shop"
356
449
  ```
357
450
 
358
- If PHP should SSR real product fields, configure a server-side snapshot endpoint:
451
+ Scaffolded shops run that generator from `npm run build` by default. It writes `dist/product.php`, `dist/category.php`, `dist/.htaccess`, and `dist/orderly-ssr-manifest.json`. The PHP renderers call `SearchService.Search` over gRPC-web, inject semantic HTML into the built `product.html` or `category.html`, and preserve the normal web-component scripts and styles.
359
452
 
360
- ```sh
361
- ORDERLY_BACKEND_URL=https://service.orderly.shop \
362
- node node_modules/@orderlyshop/web-components/server/node/product-snapshot-server.mjs
453
+ Runtime options:
363
454
 
364
- export ORDERLY_PRODUCT_SNAPSHOT_ENDPOINT=http://127.0.0.1:4107/orderly-product-snapshot
365
- ```
455
+ - `ORDERLY_BASE_URL` overrides the backend URL used by PHP.
456
+ - `ORDERLY_SSR_TIMEOUT` controls PHP backend timeout in seconds.
457
+ - `ORDERLY_SSR_HEADERS` can add internal server-side request headers. Do not expose API keys or bearer tokens to browser JavaScript.
458
+ - `ORDERLY_SSR_MANIFEST` can point PHP to a manifest outside the web root.
366
459
 
367
- The PHP renderer emits a small declarative fallback with `data-orderly-ssr-fallback`; `orderly-product-detail-page` hides that fallback when the browser has loaded the live `SearchObject`. See [`server/README.md`](./server/README.md) for Apache, Nginx, PHP, Node, caching, and SEO notes.
460
+ The semantic fallback uses real headings, links, images, schema.org Product/Offer metadata, and small `data-orderly-*` attributes only for behavior-critical values. Components hydrate from that HTML, then hide the fallback and continue as normal interactive web components. Generated SSR output keeps the same HTML for users and crawlers, hides fallback visibility for normal browser hydration, and serves a visible fallback layout to Googlebot. See [`server/README.md`](./server/README.md) for details.
368
461
 
369
462
  ## Define Elements
370
463
 
@@ -467,27 +560,49 @@ collection.query = searchQuery;
467
560
  ## Components
468
561
 
469
562
  - `orderly-page-layout` provides reusable page regions for header, header actions, left/right sidebars, content, footer, and overlay content. It includes a default logo image using `https://orderly.shop/home/App_Icon.svg`; override it with `logo-src`, `logo-alt`, and `logo-href`.
470
- - `orderly-home-page` composes page layout, side navigation, one product rail per top-level category, basket icon, basket drawer, product detail dialog, and footer. Configure it with `configureShop({ defaultShop })` plus attributes such as `title`, `eyebrow`, `rail-cta-label`, and `product-href`. The product dialog includes an expand icon link to `product-href#url=<encoded-share-url>`; `https://orderly.shop/` is stripped from Orderly product URLs before encoding.
471
- - `orderly-category-page` composes page layout, navigation, search, product grid, product detail, basket icon, and basket around a navigation category. Without properties it uses the package default shop configuration. Override with `configureShop({ defaultShop })` once per shop, set `base-url` or `product-href` in markup, or assign `category`, `navigationItems`, `sortOptions`, `client`, `defaultQuery`, and `basketController` from JavaScript for advanced cases. The product dialog includes an expand icon link built from `SearchObject.ShareURL.URL`, using compact hashes for `https://orderly.shop/...` product URLs.
563
+ - `orderly-home-page` composes page layout, side navigation, one product rail per top-level category, basket icon, basket drawer, product detail dialog, and footer. Configure it with `configureShop({ defaultShop })` plus attributes such as `title`, `eyebrow`, `rail-cta-label`, and `product-href`. The product dialog expand link follows the configured product URL mode, and compact `https://orderly.shop/...` product URLs are preserved when it builds hash or path routes.
564
+ - `orderly-category-page` composes page layout, navigation, search, product grid, product detail, basket icon, and basket around a navigation category. Without properties it uses the package default shop configuration. Override with `configureShop({ defaultShop })` once per shop, set `base-url` or `product-href` in markup, or assign `category`, `navigationItems`, `sortOptions`, `client`, `defaultQuery`, and `basketController` from JavaScript for advanced cases. The product dialog expand link uses the configured product URL mode and still supports explicit per-page `product-href` overrides when a shop wants a different physical product page URL.
472
565
  - `orderly-checkout-page` composes page layout, side navigation, checkout form, basket order summary, footer, shared basket state, delivery loading, and order-created cleanup. Delivery methods render as radio cards after address entry, service points render as radio-card pickup choices when required, and delivery changes verify the draft so totals update. Configure copy through `configureShop({ defaultShop: { checkoutPageTitle, checkoutPageDescription, checkoutOrderTitle, checkoutTermsHref, checkoutLabels, basketLabels } })`, or assign `client`, `basketController`, `navigationItems`, `checkoutLabels`, and `basketLabels` from JavaScript.
473
566
  - `orderly-product-detail-page` composes page layout, navigation, product detail, basket drawer, and footer for a product route. Set `share-url`, the `shareUrl` property, or pass `#url=<encoded-share-url>` in the page URL; compact Orderly slugs are restored to `https://orderly.shop/...` before it calls `SearchService.Search` with `SearchQuery.ShareUrl` and renders the resolved `SearchObject`.
474
567
  - `orderly-stored-image` accepts `image: StoredImage`, resolves image URLs from the configured prefix, and applies `RotationDeg`, crop, and full-size metadata in the browser.
475
568
  - `orderly-credit` accepts `credit: Credit` or declarative `amount` and `currency` attributes, then renders stylable money markup. DKK values are formatted as `kr. <pris>`, for example `kr. 100,00`.
476
- - `orderly-product-tile` accepts `product: SearchObject` and emits `orderly-product-selected`, `orderly-add-to-basket`, and `orderly-remove-from-basket`. Templates can bind the product share URL with `data-orderly-bind="share-url"`; anchors receive `href` and other elements receive `share-url`. If an anchor already has `href="/product.html"`, the bound link becomes `/product.html#url=<encoded-share-url>`, using the compact path for `https://orderly.shop/...` product URLs. Default tiles and statically hydrated category tiles include `schema.org/Product` and `schema.org/Offer` microdata for name, image, URL, description, SKU, brand, price, currency, and in-stock availability when those fields are present. When a `BasketController` is assigned through `basketController`, the default action button toggles between add and remove, using an icon-only button next to the price.
569
+ - `orderly-product-tile` accepts `product: SearchObject` and emits `orderly-product-selected`, `orderly-add-to-basket`, and `orderly-remove-from-basket`. Templates can bind the product share URL with `data-orderly-bind="share-url"`; anchors without an existing `href` keep the raw share URL, while anchors that already point at a product page are rewritten to the configured physical product route using hash or path mode. Default tiles and statically hydrated category tiles include `schema.org/Product` and `schema.org/Offer` microdata for name, image, URL, description, SKU, brand, price, currency, and in-stock availability when those fields are present. When a `BasketController` is assigned through `basketController`, the default action button toggles between add and remove, using an icon-only button next to the price.
477
570
  - `orderly-product-page` accepts `product: SearchObject`, renders all product image thumbnails, switches the selected image on thumbnail click, and exposes product details plus add-to-basket behavior.
478
571
  - `orderly-search-box` binds to a target `orderly-product-grid`. Use `mode="textbox"` for an inline search input or `mode="icon"` for a header icon that opens a full-page search overlay. Icon mode searches automatically with a 500ms debounce and renders matching product tiles below the search field. Default home and category pages use icon mode next to the basket icon.
479
572
  - `orderly-product-grid` accepts `query: SearchQuery`, merges the configured shop query scope, calls `SearchService.Search`, renders its own sort control from `sortOptions`, supports opaque `ContinuationToken` paging, manual paging, dynamic/infinite scroll, and default loading placeholders while the first page is pending.
480
573
  - `orderly-product-rail` accepts `query: SearchQuery`, title, CTA label, and CTA href, then renders the search as a horizontal scroll list by composing `orderly-product-grid`. It is useful for homepages and editorial rows where several category searches should be stacked vertically.
481
574
  - `orderly-collection-page` accepts `query: SearchQuery`, title, description, and hero image, then delegates fetching to `orderly-product-grid`.
482
575
  - `orderly-shop-footer` renders configurable logo, about text, address, contact information, opening hours, and information links.
483
- - `orderly-basket-icon` and `orderly-basket` share a `BasketController` backed by `DraftOrder` persistence. Basket state events such as `orderly-basket-open`, `orderly-basket-change`, and `orderly-basket-verified` use the current `DraftOrder` directly as `event.detail`, so consumers can derive counts and totals from Core contracts instead of an internal basket data shape. `orderly-basket` renders an item overview, verifies changed drafts through `OrderService.VerifyDraft` when a client or `base-url` is configured, includes a configurable checkout link through `checkout-href` and `checkout-label`, and only exposes quantity selection for lines where `MaxQuantity > 1`.
484
- - `orderly-checkout` persists checkout profile fields, looks up Danish city names from the entered postal code through Dataforsyningen's `https://api.dataforsyningen.dk/postnumre/{postnr}` endpoint, loads delivery methods and service points, verifies the draft, and calls `OrderService.Create`.
485
- - `orderly-navigation` uses `NavigationController` and can be subclassed with site-specific `NavigationItem[]`. It is sticky by default, including when placed in `left`, `right`, or `primary-nav`; set `sticky="false"` to opt out.
576
+ - `orderly-basket-icon` and `orderly-basket` share a `BasketController` backed by `DraftOrder` persistence. Basket state events such as `orderly-basket-open`, `orderly-basket-change`, and `orderly-basket-verified` use the current `DraftOrder` directly as `event.detail`, so consumers can derive counts and totals from Core contracts instead of an internal basket data shape. `orderly-basket` renders an item overview, verifies non-empty persisted drafts on load plus later basket mutations through `OrderService.VerifyDraft` when a client or `base-url` is configured, includes a configurable checkout link through `checkout-href` and `checkout-label`, and only exposes quantity selection for lines where `MaxQuantity > 1`. Summary rows now render only backend-provided `DraftOrder` values, never local subtotal/total math, and the component renders both `DraftOrder.Errors` and `DraftOrderLine.Errors`. Custom basket templates can project order-level errors through `data-orderly-slot="errors"`.
577
+ - `orderly-checkout` persists checkout contact and address fields directly on `DraftOrder.Transport`, defaults the phone country picker to `+45`, validates email and phone values with built-in component logic, looks up Danish city names from the entered postal code through Dataforsyningen's `https://api.dataforsyningen.dk/postnumre/{postnr}` endpoint, loads delivery methods and service points, verifies the draft, and calls `OrderService.Create`.
578
+ - `orderly-navigation` uses `NavigationController` and can be subclassed with site-specific `NavigationItem[]`. It is sticky by default, including when placed in `left`, `right`, or `primary-nav`; set `sticky="false"` to opt out. Use `layout="vertical"` for disclosure side navigation, `layout="horizontal"` for the tiered desktop bar, or `layout="burgermenu"` for an icon trigger that expands into the same nested disclosure menu inside a floating panel.
486
579
  - `orderly-search-box`, `orderly-sort-select`, `orderly-filter-panel`, and `orderly-load-more` bind to `orderly-product-grid`.
487
580
 
488
581
  ## Rendering And Styling
489
582
 
490
- The package ships baseline default CSS and uses light DOM. Default markup uses stable `orderly-*` class names and CSS variables such as `--orderly-color-accent`, `--orderly-color-page`, `--orderly-color-surface`, `--orderly-color-text`, `--orderly-color-muted`, and `--orderly-content-max-width`, so host CSS can style or replace the default look directly.
583
+ The package ships baseline default CSS and uses light DOM. Default markup uses stable `orderly-*` class names and CSS variables, so host CSS can style or replace the default look directly.
584
+
585
+ For consistent theming across the whole package, start with the semantic design contract in [`DESIGN.md`](./DESIGN.md). In practice that means overriding `--orderly-color-primary`, `--orderly-color-primary-soft`, `--orderly-color-primary-contrast`, the `--orderly-action-primary-*` and `--orderly-action-secondary-*` tokens, plus shared tokens such as `--orderly-link-accent-color`, `--orderly-selection-*`, and `--orderly-badge-*`. `--orderly-color-accent` is kept as a compatibility alias, but new themes should treat `--orderly-color-primary` as the source of truth.
586
+
587
+ Example:
588
+
589
+ ```css
590
+ :root {
591
+ --orderly-color-primary: #1f7a43;
592
+ --orderly-color-primary-soft: #e7f4eb;
593
+ --orderly-color-primary-contrast: #ffffff;
594
+ --orderly-link-accent-color: var(--orderly-color-primary);
595
+ --orderly-selection-border: var(--orderly-color-primary);
596
+ --orderly-selection-background: var(--orderly-color-primary-soft);
597
+ --orderly-badge-background: var(--orderly-color-primary);
598
+ --orderly-badge-color: var(--orderly-color-primary-contrast);
599
+ --orderly-action-primary-background: var(--orderly-color-primary);
600
+ --orderly-action-primary-border: var(--orderly-color-primary);
601
+ --orderly-action-primary-color: var(--orderly-color-primary-contrast);
602
+ --orderly-action-secondary-border: var(--orderly-color-primary);
603
+ --orderly-action-secondary-color: var(--orderly-color-primary);
604
+ }
605
+ ```
491
606
 
492
607
  Rendering can be replaced with templates:
493
608
 
@@ -537,7 +652,7 @@ Rendering can be replaced with templates:
537
652
 
538
653
  For dynamic paging, `orderly-product-grid` keeps the returned `PageResult.Continuation` as an opaque `ContinuationToken` and sends it as `SearchQuery.Continuation` when the sentinel enters the viewport. Dynamic and infinite paging do not render a load-more button. Use `paging="button"` only when a visible load-more button is desired, `paging="manual"` when another control calls `loadNextPage()`, and `paging="dynamic"` or `paging="infinite"` for automatic loading.
539
654
 
540
- Every component has a default light-DOM implementation and matching template hooks. Use `data-orderly-bind` for values, `data-orderly-action` for behavior, and `data-orderly-slot` for repeated or projected content. Product tile templates can bind `basket-action-icon`, `basket-action-label`, and `basket-action-state` and use `data-orderly-action="add-to-basket"` for the add/remove toggle. Product page galleries use `data-orderly-slot="thumbnails"` and `data-orderly-action="select-image"` for custom thumbnail markup. Product rail templates can replace the outer `layout`, inner `grid`, product item, loading state, and empty state. The main template names are `layout`, `grid`, `footer`, `product`, `thumbnail`, `image`, `basket`, `basket-icon`, `line`, `checkout`, `navigation`, `item`, `search-box`, `sort`, `sort-select`, `filter-panel`, `load-more`, `address-line`, `contact-item`, `opening-hour`, and `information-link`.
655
+ Every component has a default light-DOM implementation and matching template hooks. Use `data-orderly-bind` for values, `data-orderly-action` for behavior, and `data-orderly-slot` for repeated or projected content. Product tile templates can bind `basket-action-icon`, `basket-action-label`, and `basket-action-state` and use `data-orderly-action="add-to-basket"` for the add/remove toggle. Product page galleries use `data-orderly-slot="thumbnails"` and `data-orderly-action="select-image"` for custom thumbnail markup. Basket templates can add `data-orderly-slot="errors"` to place order-level `DraftOrder.Errors` exactly where a shop wants them. Product rail templates can replace the outer `layout`, inner `grid`, product item, loading state, and empty state. The main template names are `layout`, `grid`, `footer`, `product`, `thumbnail`, `image`, `basket`, `basket-icon`, `line`, `checkout`, `navigation`, `item`, `search-box`, `sort`, `sort-select`, `filter-panel`, `load-more`, `address-line`, `contact-item`, `opening-hour`, and `information-link`.
541
656
 
542
657
  Product sorting belongs to `orderly-product-grid`: assign `grid.sortOptions = [{ label, orderBy }]` or pass sort options through `orderly-collection-page.sortOptions`. The default grid layout renders the sort select above the products. Custom grid layouts should include `data-orderly-slot="sort"` where the control should appear, and custom sort templates can use `data-orderly-template="sort"` with a `<select data-orderly-action="sort">`.
543
658
 
@@ -671,15 +786,14 @@ productImage.image = searchObject.Images[0];
671
786
 
672
787
  ## State And Security
673
788
 
674
- Basket drafts and checkout profile fields are persisted to localStorage by default. Checkout profile data can include PII such as name, address, email, and phone. Provide custom stores to change or disable that behavior. This package never stores tokens, API keys, OAuth responses, or cookie values.
789
+ Basket drafts are persisted to localStorage by default. Checkout contact and address data are stored directly on `DraftOrder.Transport`, so basket items, delivery selection, and checkout form state all share the same persisted draft. That draft can contain PII such as name, address, email, and phone. Provide a custom basket store to change or disable that behavior. This package never stores tokens, API keys, OAuth responses, or cookie values.
675
790
 
676
- Basket persistence uses `LocalStorageBasketStore`. Checkout profile persistence uses `LocalStorageCheckoutProfileStore`. Both can be replaced:
791
+ Persistence uses `LocalStorageBasketStore` and stores the full `DraftOrder`:
677
792
 
678
793
  ```ts
679
794
  import { createBasketController } from "@orderlyshop/web-components";
680
795
 
681
796
  basket.basketController = createBasketController({ store: myBasketStore });
682
- checkout.profileStore = myCheckoutProfileStore;
683
797
  ```
684
798
 
685
799
  Authentication remains owned by `@orderlyshop/core-client`. Browser cookie-backed sessions work through the core browser client and credentialed gRPC-web requests.