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

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 (43) hide show
  1. package/AGENTS.md +2 -0
  2. package/README.md +117 -15
  3. package/bin/orderly-init-shop.mjs +41 -16
  4. package/custom-elements.json +37 -4
  5. package/dist/browser/orderly-web-components.define.global.js +924 -219
  6. package/dist/browser/orderly-web-components.define.global.js.map +1 -1
  7. package/dist/browser/orderly-web-components.global.js +924 -219
  8. package/dist/browser/orderly-web-components.global.js.map +1 -1
  9. package/dist/{default-shop-DgX6uy10.d.ts → default-shop-Qipu74bd.d.ts} +51 -9
  10. package/dist/default-shop.d.ts +2 -2
  11. package/dist/default-shop.js +86 -0
  12. package/dist/default-shop.js.map +1 -1
  13. package/dist/define-jKa_4XhP.d.ts +9 -0
  14. package/dist/define.d.ts +1 -1
  15. package/dist/define.js +2116 -379
  16. package/dist/define.js.map +1 -1
  17. package/dist/index.d.ts +53 -9
  18. package/dist/index.js +2624 -386
  19. package/dist/index.js.map +1 -1
  20. package/dist/navigation.d.ts +5 -2
  21. package/dist/navigation.js +238 -38
  22. package/dist/navigation.js.map +1 -1
  23. package/dist/{shop-BgQhGRzS.d.ts → shop-Dm2UjjIN.d.ts} +6 -1
  24. package/dist/shop.d.ts +4 -4
  25. package/dist/shop.js +2655 -498
  26. package/dist/shop.js.map +1 -1
  27. package/dist/stores.d.ts +26 -3
  28. package/dist/stores.js +158 -1
  29. package/dist/stores.js.map +1 -1
  30. package/dist/taxonomy.d.ts +2 -2
  31. package/dist/types-CXEwL2xS.d.ts +170 -0
  32. package/docs/components/README.md +76 -5
  33. package/docs/components/product-grid.md +1 -2
  34. package/examples/shop/README.md +2 -0
  35. package/examples/shop/src/includes/head.html +16 -4
  36. package/examples/shop/src/payment-failure.html +20 -0
  37. package/examples/shop/src/payment-success.html +20 -0
  38. package/examples/shop/src/storefront-router.ts +151 -0
  39. package/examples/shop/src/templates/page-layouts.html +199 -1
  40. package/html-custom-data.json +33 -4
  41. package/package.json +2 -2
  42. package/dist/define-BNMhl19n.d.ts +0 -9
  43. 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`. 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
 
@@ -467,27 +548,49 @@ collection.query = searchQuery;
467
548
  ## Components
468
549
 
469
550
  - `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.
551
+ - `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.
552
+ - `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
553
  - `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
554
  - `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
555
  - `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
556
  - `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.
557
+ - `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
558
  - `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
559
  - `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
560
  - `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
561
  - `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
562
  - `orderly-collection-page` accepts `query: SearchQuery`, title, description, and hero image, then delegates fetching to `orderly-product-grid`.
482
563
  - `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.
564
+ - `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"`.
565
+ - `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`.
566
+ - `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
567
  - `orderly-search-box`, `orderly-sort-select`, `orderly-filter-panel`, and `orderly-load-more` bind to `orderly-product-grid`.
487
568
 
488
569
  ## Rendering And Styling
489
570
 
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.
571
+ 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.
572
+
573
+ 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.
574
+
575
+ Example:
576
+
577
+ ```css
578
+ :root {
579
+ --orderly-color-primary: #1f7a43;
580
+ --orderly-color-primary-soft: #e7f4eb;
581
+ --orderly-color-primary-contrast: #ffffff;
582
+ --orderly-link-accent-color: var(--orderly-color-primary);
583
+ --orderly-selection-border: var(--orderly-color-primary);
584
+ --orderly-selection-background: var(--orderly-color-primary-soft);
585
+ --orderly-badge-background: var(--orderly-color-primary);
586
+ --orderly-badge-color: var(--orderly-color-primary-contrast);
587
+ --orderly-action-primary-background: var(--orderly-color-primary);
588
+ --orderly-action-primary-border: var(--orderly-color-primary);
589
+ --orderly-action-primary-color: var(--orderly-color-primary-contrast);
590
+ --orderly-action-secondary-border: var(--orderly-color-primary);
591
+ --orderly-action-secondary-color: var(--orderly-color-primary);
592
+ }
593
+ ```
491
594
 
492
595
  Rendering can be replaced with templates:
493
596
 
@@ -537,7 +640,7 @@ Rendering can be replaced with templates:
537
640
 
538
641
  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
642
 
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`.
643
+ 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
644
 
542
645
  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
646
 
@@ -671,15 +774,14 @@ productImage.image = searchObject.Images[0];
671
774
 
672
775
  ## State And Security
673
776
 
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.
777
+ 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
778
 
676
- Basket persistence uses `LocalStorageBasketStore`. Checkout profile persistence uses `LocalStorageCheckoutProfileStore`. Both can be replaced:
779
+ Persistence uses `LocalStorageBasketStore` and stores the full `DraftOrder`:
677
780
 
678
781
  ```ts
679
782
  import { createBasketController } from "@orderlyshop/web-components";
680
783
 
681
784
  basket.basketController = createBasketController({ store: myBasketStore });
682
- checkout.profileStore = myCheckoutProfileStore;
683
785
  ```
684
786
 
685
787
  Authentication remains owned by `@orderlyshop/core-client`. Browser cookie-backed sessions work through the core browser client and credentialed gRPC-web requests.
@@ -32,14 +32,6 @@ if (!skipPackageJson) {
32
32
  planned.push("package.json");
33
33
  }
34
34
 
35
- const existing = files
36
- .filter((file) => !file.skipIfExists)
37
- .map((file) => file.path)
38
- .filter((file) => existsSync(resolve(targetRoot, file)));
39
- if (existing.length && !force) {
40
- throw new Error(`Refusing to overwrite existing files: ${existing.join(", ")}. Re-run with --force to overwrite scaffold files.`);
41
- }
42
-
43
35
  if (dryRun) {
44
36
  console.log(`Would initialize ${shopName} in ${relative(cwd, targetRoot) || "."}.`);
45
37
  for (const file of planned) {
@@ -51,13 +43,15 @@ if (dryRun) {
51
43
  mkdirSync(targetRoot, { recursive: true });
52
44
  for (const file of files) {
53
45
  const target = resolve(targetRoot, file.path);
54
- if (file.skipIfExists && existsSync(target) && !force) {
55
- console.log(`Skipped existing ${relative(cwd, target)}.`);
46
+ const relativeTarget = relative(cwd, target);
47
+ const targetExists = existsSync(target);
48
+ if (targetExists && !force) {
49
+ console.warn(`Warning: ${relativeTarget} already exists; leaving it unchanged. Re-run with --force to overwrite.`);
56
50
  continue;
57
51
  }
58
52
  mkdirSync(dirname(target), { recursive: true });
59
53
  writeFileSync(target, file.content, "utf8");
60
- console.log(`Created ${relative(cwd, target)}.`);
54
+ console.log(`${targetExists ? "Overwrote" : "Created"} ${relativeTarget}.`);
61
55
  }
62
56
 
63
57
  if (!skipPackageJson) {
@@ -84,6 +78,8 @@ function scaffoldFiles(options) {
84
78
  { path: "src/category.html", content: pageSource({ title: `Kategori | ${options.shopName}`, element: "<orderly-category-page></orderly-category-page>" }) },
85
79
  { path: "src/product.html", content: pageSource({ title: `Produkt | ${options.shopName}`, element: productDetailElementSource() }) },
86
80
  { path: "src/checkout.html", content: pageSource({ title: `Betaling | ${options.shopName}`, element: checkoutElementSource() }) },
81
+ { path: "src/payment-success.html", content: pageSource({ title: `Tak for din ordre | ${options.shopName}`, element: paymentSuccessElementSource() }) },
82
+ { path: "src/payment-failure.html", content: pageSource({ title: `Betaling kunne ikke gennemføres | ${options.shopName}`, element: paymentFailureElementSource() }) },
87
83
  { path: "src/includes/head.html", content: headIncludeSource(options) },
88
84
  { path: "src/includes/body-start.html", content: bodyStartSource() },
89
85
  { path: "src/includes/body-end.html", content: "<!-- Add shared body-end snippets here. -->\n" },
@@ -141,6 +137,7 @@ function agentsSource() {
141
137
 
142
138
  - This shop was scaffolded with \`npx orderly-init-shop\` from \`@orderlyshop/web-components\`.
143
139
  - Keep storefront logic in Orderly web components. Customize navigation, content, templates, and CSS instead of replacing package components.
140
+ - Start visual theming from the package \`DESIGN.md\` semantic tokens before using component-local selectors.
144
141
  - Configure shop-wide backend, default query, navigation, page layout, and component registration in \`src/includes/head.html\` through \`configureShop(...)\`.
145
142
  - Use \`src/navigation.ts\` for category/navigation structure. Every category needs a stable globally unique \`slug\`.
146
143
  - Use \`src/shop-query.ts\` for the shop-wide Core \`SearchQuery\`, such as tenant/account scoping.
@@ -164,6 +161,8 @@ npm run build
164
161
  npm run preview
165
162
  \`\`\`
166
163
 
164
+ Test the shop locally from \`http://localhost:61677\` during normal development, or \`https://localhost:61677\` if a local TLS proxy terminates HTTPS in front of the dev server. Keep the origin on \`localhost:61677\`, because the backend CORS development policy expects that local origin.
165
+
167
166
  Override the backend during local development:
168
167
 
169
168
  \`\`\`sh
@@ -180,6 +179,7 @@ Use \`npm run generate:categories\` when you want to inspect generated source pa
180
179
 
181
180
  ## Customize
182
181
 
182
+ - For consistent theming across all components, start with the semantic token contract in \`node_modules/@orderlyshop/web-components/DESIGN.md\`.
183
183
  - \`src/navigation.ts\` defines storefront navigation and category queries.
184
184
  - \`src/shop-query.ts\` defines the shop-wide Core \`SearchQuery\`.
185
185
  - \`src/includes/head.html\` calls \`configureShop(...)\`.
@@ -444,6 +444,26 @@ function checkoutElementSource() {
444
444
  </orderly-checkout-page>`;
445
445
  }
446
446
 
447
+ function paymentSuccessElementSource() {
448
+ return `<orderly-payment-success-page>
449
+ <div slot="utility" class="shop-utility-strip" aria-label="Kundefordele">
450
+ <span>Hurtig levering</span>
451
+ <span>Nem retur</span>
452
+ <span>Sikker betaling</span>
453
+ </div>
454
+ </orderly-payment-success-page>`;
455
+ }
456
+
457
+ function paymentFailureElementSource() {
458
+ return `<orderly-payment-failure-page>
459
+ <div slot="utility" class="shop-utility-strip" aria-label="Kundefordele">
460
+ <span>Hurtig levering</span>
461
+ <span>Nem retur</span>
462
+ <span>Sikker betaling</span>
463
+ </div>
464
+ </orderly-payment-failure-page>`;
465
+ }
466
+
447
467
  function headIncludeSource(options) {
448
468
  return `<!-- Add shared head snippets here, for example analytics, consent, or preconnect tags. -->
449
469
  <link rel="icon" href="https://orderly.shop/home/App_Icon.svg" type="image/svg+xml">
@@ -575,7 +595,9 @@ body.orderly-navigation-menu-open {
575
595
  orderly-home-page:not(:defined),
576
596
  orderly-category-page:not(:defined),
577
597
  orderly-product-detail-page:not(:defined),
578
- orderly-checkout-page:not(:defined) {
598
+ orderly-checkout-page:not(:defined),
599
+ orderly-payment-success-page:not(:defined),
600
+ orderly-payment-failure-page:not(:defined) {
579
601
  display: block;
580
602
  min-height: 100vh;
581
603
  background: #f6f8f9;
@@ -584,7 +606,9 @@ orderly-checkout-page:not(:defined) {
584
606
  orderly-home-page:not(:defined)::before,
585
607
  orderly-category-page:not(:defined)::before,
586
608
  orderly-product-detail-page:not(:defined)::before,
587
- orderly-checkout-page:not(:defined)::before {
609
+ orderly-checkout-page:not(:defined)::before,
610
+ orderly-payment-success-page:not(:defined)::before,
611
+ orderly-payment-failure-page:not(:defined)::before {
588
612
  content: "";
589
613
  display: block;
590
614
  height: 62px;
@@ -843,9 +867,10 @@ Options:
843
867
 
844
868
  Agent workflow:
845
869
  1. If no shop scaffold exists, run: npx orderly-init-shop
846
- 2. Ask the user for the shop account id. Re-run with --account-id <id> or edit src/shop-query.ts.
847
- 3. Customize src/navigation.ts with categories and SearchQuery values.
848
- 4. Customize src/templates/*.html and src/style.css for the shop look and feel.
870
+ 2. Existing scaffold files are skipped with warnings. Use --force only when the user explicitly wants overwrite.
871
+ 3. Ask the user for the shop account id. Re-run with --account-id <id> or edit src/shop-query.ts.
872
+ 4. Customize src/navigation.ts with categories and SearchQuery values.
873
+ 5. Customize src/templates/*.html and src/style.css for the shop look and feel.
849
874
  `);
850
875
  }
851
876
 
@@ -130,6 +130,39 @@
130
130
  { "name": "orderly-error", "type": { "text": "CustomEvent<{ error: unknown }>" } }
131
131
  ]
132
132
  },
133
+ {
134
+ "kind": "class",
135
+ "name": "OrderlyPaymentSuccessPageElement",
136
+ "tagName": "orderly-payment-success-page",
137
+ "customElement": true,
138
+ "description": "Payment success callback page that loads a stored order from a base64 encoded protobuf OrderId.",
139
+ "attributes": [
140
+ { "name": "order-id", "description": "Base64 encoded protobuf OrderId. Defaults to the orderid URL parameter." },
141
+ { "name": "brand-label", "description": "Brand text." },
142
+ { "name": "title", "description": "Page heading." },
143
+ { "name": "description", "description": "Page intro text." },
144
+ { "name": "reference-label", "description": "Order reference label." },
145
+ { "name": "order-title", "description": "Order summary heading." },
146
+ { "name": "empty-label", "description": "Text shown when the order is not found locally." }
147
+ ]
148
+ },
149
+ {
150
+ "kind": "class",
151
+ "name": "OrderlyPaymentFailurePageElement",
152
+ "tagName": "orderly-payment-failure-page",
153
+ "customElement": true,
154
+ "description": "Payment failure callback page that lets the customer retry the stored order payment link.",
155
+ "attributes": [
156
+ { "name": "order-id", "description": "Base64 encoded protobuf OrderId. Defaults to the orderid URL parameter." },
157
+ { "name": "brand-label", "description": "Brand text." },
158
+ { "name": "title", "description": "Page heading." },
159
+ { "name": "description", "description": "Page intro text." },
160
+ { "name": "reference-label", "description": "Order reference label." },
161
+ { "name": "order-title", "description": "Order summary heading." },
162
+ { "name": "empty-label", "description": "Text shown when the order is not found locally." },
163
+ { "name": "retry-label", "description": "Retry payment link label." }
164
+ ]
165
+ },
133
166
  {
134
167
  "kind": "class",
135
168
  "name": "OrderlyProductGridElement",
@@ -278,7 +311,7 @@
278
311
  "name": "OrderlyProductTileElement",
279
312
  "tagName": "orderly-product-tile",
280
313
  "customElement": true,
281
- "description": "Product tile for SearchObject with product selection and basket toggle behavior.",
314
+ "description": "Product tile for SearchObject with product selection, basket toggle behavior, and configurable share-url binding for raw share URLs or physical product routes.",
282
315
  "attributes": [
283
316
  { "name": "add-label", "description": "Add-to-basket accessible label." },
284
317
  { "name": "remove-label", "description": "Remove-from-basket accessible label." }
@@ -369,7 +402,7 @@
369
402
  "name": "OrderlyBasketElement",
370
403
  "tagName": "orderly-basket",
371
404
  "customElement": true,
372
- "description": "DraftOrder basket overview with persistence, remove actions, quantity display, verification, summary, and checkout link.",
405
+ "description": "DraftOrder basket overview with persistence, remove actions, quantity display, backend VerifyDraft summary rendering, order-level and line-level errors, and checkout link.",
373
406
  "attributes": [
374
407
  { "name": "base-url", "description": "Backend URL used when no client property is assigned." },
375
408
  { "name": "empty-label", "description": "Empty basket text." },
@@ -423,9 +456,9 @@
423
456
  "name": "OrderlyNavigationMenuElement",
424
457
  "tagName": "orderly-navigation",
425
458
  "customElement": true,
426
- "description": "Navigation menu with vertical or horizontal layouts, nested disclosure behavior, desktop tiered navigation support, horizontal overflow scrolling, projected inline content, and selection events.",
459
+ "description": "Navigation menu with vertical, horizontal, or burgermenu layouts, nested disclosure behavior, desktop tiered horizontal navigation support, horizontal overflow scrolling, projected inline content, and selection events.",
427
460
  "attributes": [
428
- { "name": "layout", "description": "Navigation layout. Use vertical for a side menu or horizontal for a tiered desktop menu with a scrolling top row, full-width second row, and third-level dropdowns." },
461
+ { "name": "layout", "description": "Navigation layout. Use vertical for a side menu, horizontal for a tiered desktop menu with a scrolling top row, full-width second row, and third-level dropdowns, or burgermenu for an icon trigger that opens a nested disclosure menu inside a floating panel." },
429
462
  { "name": "sticky", "description": "Set to false to opt out of sticky positioning." }
430
463
  ],
431
464
  "members": [