@shop-on/theme-routes 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/LICENSE.md +39 -0
  2. package/dist/adapter.d.ts +216 -0
  3. package/dist/adapter.js +0 -0
  4. package/dist/components/article-page.d.ts +6 -0
  5. package/dist/components/article-page.js +20 -0
  6. package/dist/components/home-page.d.ts +6 -0
  7. package/dist/components/home-page.js +15 -0
  8. package/dist/components/locale-layout.d.ts +5 -0
  9. package/dist/components/locale-layout.js +10 -0
  10. package/dist/components/product-page.d.ts +10 -0
  11. package/dist/components/product-page.js +23 -0
  12. package/dist/components/section-preview-page.d.ts +15 -0
  13. package/dist/components/section-preview-page.js +42 -0
  14. package/dist/components/template-page.d.ts +9 -0
  15. package/dist/components/template-page.js +22 -0
  16. package/dist/index.d.ts +29 -0
  17. package/dist/index.js +27 -0
  18. package/dist/preload-links.d.ts +6 -0
  19. package/dist/preload-links.js +11 -0
  20. package/dist/router.d.ts +27 -0
  21. package/dist/router.js +54 -0
  22. package/dist/routes/account.d.ts +35 -0
  23. package/dist/routes/account.js +43 -0
  24. package/dist/routes/blog.d.ts +37 -0
  25. package/dist/routes/blog.js +56 -0
  26. package/dist/routes/cart.d.ts +14 -0
  27. package/dist/routes/cart.js +27 -0
  28. package/dist/routes/collection.d.ts +44 -0
  29. package/dist/routes/collection.js +78 -0
  30. package/dist/routes/home.d.ts +16 -0
  31. package/dist/routes/home.js +42 -0
  32. package/dist/routes/locale-layout.d.ts +23 -0
  33. package/dist/routes/locale-layout.js +21 -0
  34. package/dist/routes/not-found.d.ts +13 -0
  35. package/dist/routes/not-found.js +25 -0
  36. package/dist/routes/page.d.ts +32 -0
  37. package/dist/routes/page.js +57 -0
  38. package/dist/routes/password.d.ts +15 -0
  39. package/dist/routes/password.js +32 -0
  40. package/dist/routes/preview-section.d.ts +35 -0
  41. package/dist/routes/preview-section.js +35 -0
  42. package/dist/routes/product.d.ts +36 -0
  43. package/dist/routes/product.js +91 -0
  44. package/dist/routes/search.d.ts +20 -0
  45. package/dist/routes/search.js +35 -0
  46. package/dist/routes/server/analytics-script.d.ts +10 -0
  47. package/dist/routes/server/analytics-script.js +25 -0
  48. package/dist/routes/server/analytics.d.ts +24 -0
  49. package/dist/routes/server/analytics.js +32 -0
  50. package/dist/routes/server/customer-logout.d.ts +6 -0
  51. package/dist/routes/server/customer-logout.js +6 -0
  52. package/dist/routes/server/seo-proxy.d.ts +21 -0
  53. package/dist/routes/server/seo-proxy.js +91 -0
  54. package/dist/seo-head.d.ts +40 -0
  55. package/dist/seo-head.js +79 -0
  56. package/dist/types.d.ts +113 -0
  57. package/dist/types.js +0 -0
  58. package/dist/use-template.d.ts +7 -0
  59. package/dist/use-template.js +12 -0
  60. package/package.json +42 -3
  61. package/README.md +0 -3
@@ -0,0 +1,39 @@
1
+ # Cody Theme Development License
2
+
3
+ Copyright (c) 2026 Cody LLC. All rights reserved except as granted below.
4
+
5
+ "The Software" is this package, its source, and its documentation. "The
6
+ Platform" is Cody, the commerce platform operated by Cody LLC.
7
+
8
+ ## 1. Grant
9
+
10
+ You may use, copy and modify the Software, and distribute modified copies, solely
11
+ to develop, test, and distribute themes, apps and other extensions that run on
12
+ the Platform.
13
+
14
+ ## 2. Restrictions
15
+
16
+ You may not:
17
+
18
+ 1. use the Software, or anything derived from it, with any commerce platform,
19
+ storefront or hosting service other than the Platform;
20
+ 2. use the Software to build a product that competes with the Platform;
21
+ 3. sublicense the Software, or distribute it other than as part of a theme,
22
+ app or extension for the Platform;
23
+ 4. remove or alter this license or any copyright notice in the Software.
24
+
25
+ ## 3. Your work
26
+
27
+ Themes, apps and extensions you build with the Software are yours. The parts of
28
+ the Software they include stay under this license.
29
+
30
+ ## 4. Termination
31
+
32
+ This license ends automatically if you break any of its terms. You must then
33
+ stop using the Software and delete your copies of it.
34
+
35
+ ## 5. No warranty
36
+
37
+ The Software is provided "as is", without warranty of any kind. Cody LLC is not
38
+ liable for any claim, damages or other liability arising from the Software or
39
+ its use.
@@ -0,0 +1,216 @@
1
+ import { ParsedBlogSearch, ParsedListingSearch, SeoHeadResult } from "./types.js";
2
+ import { ComponentType, ReactNode } from "react";
3
+ import { TemplateJSON } from "@shop-on/theme-runtime/template-types";
4
+ //#region src/adapter.d.ts
5
+ declare module "@tanstack/react-router" {
6
+ interface StaticDataRouteOption {
7
+ templateId?: string;
8
+ }
9
+ }
10
+ /** A theme's `createServerFn` caller. Typed by its input only — every template
11
+ * fn's return is cast to `TemplateJSON` at the call site, exactly as the
12
+ * per-theme routes did before this package existed (the serializer forces the
13
+ * handlers' return type to `any`).
14
+ *
15
+ * METHOD syntax throughout this file is deliberate. A theme's fn narrows what
16
+ * this package can express — `filter?: unknown` here vs a generated
17
+ * `CollectionFilterInput` there, `subpath?: string` vs a literal union — and
18
+ * `strictFunctionTypes` makes property-syntax function types contravariant,
19
+ * which would reject exactly those narrowings. Method declarations stay
20
+ * bivariant, which is the right call for a seam whose whole job is to accept a
21
+ * theme's more specific version of the same function. */
22
+ export interface TemplateFn<TData> {
23
+ (opts: {
24
+ data: TData;
25
+ }): Promise<unknown>;
26
+ }
27
+ /** The theme's own `import.meta.glob` registry — the immovable seam, since the
28
+ * globs resolve relative to the file that declares them. */
29
+ export interface RendererSeam {
30
+ TemplateRenderer: (props: {
31
+ template: TemplateJSON;
32
+ }) => ReactNode;
33
+ preloadTemplate: (template: TemplateJSON) => Promise<void>;
34
+ }
35
+ export interface I18nSeam {
36
+ /** Active content locale, `undefined` on the primary one. NEVER `params.locale`
37
+ * — the router rewrite de-localizes URLs before matching, so that param is
38
+ * never populated. */
39
+ getContentLocale: () => string | undefined;
40
+ }
41
+ export interface SeoSeam {
42
+ templateSeoHead: (loaderData: unknown, path: string, opts?: {
43
+ ogType?: "website" | "product" | "article";
44
+ preview?: boolean;
45
+ robots?: string;
46
+ }) => SeoHeadResult;
47
+ isPreviewSearch: (search: unknown) => boolean;
48
+ paginatedPath: (pathname: string, page: number | undefined) => string;
49
+ }
50
+ export interface TemplateByIdArgs {
51
+ templateId: string;
52
+ sid?: string;
53
+ locale?: string;
54
+ }
55
+ export interface HomeTemplateArgs {
56
+ sid?: string;
57
+ locale?: string;
58
+ }
59
+ export interface ProductTemplateArgs {
60
+ handle: string;
61
+ collectionHandle?: string;
62
+ sid?: string;
63
+ variantId?: string;
64
+ previewTemplate?: string;
65
+ locale?: string;
66
+ }
67
+ export interface CollectionPageTemplateArgs {
68
+ handle: string;
69
+ sid?: string;
70
+ previewTemplate?: string;
71
+ filter?: unknown;
72
+ sort?: string;
73
+ page?: number;
74
+ locale?: string;
75
+ }
76
+ export interface CollectionsTemplateArgs {
77
+ sid?: string;
78
+ after?: string;
79
+ page?: number;
80
+ locale?: string;
81
+ }
82
+ export interface SearchPageTemplateArgs {
83
+ q?: string;
84
+ /** Which result kind the page shows, for a theme whose search page has
85
+ * collection / content tabs. Absent = products. */
86
+ type?: string;
87
+ sid?: string;
88
+ filter?: unknown;
89
+ sort?: string;
90
+ page?: number;
91
+ locale?: string;
92
+ }
93
+ export interface BlogPageTemplateArgs {
94
+ handle: string;
95
+ sid?: string;
96
+ tag?: string;
97
+ page?: number;
98
+ locale?: string;
99
+ }
100
+ export interface ArticleTemplateArgs {
101
+ blogHandle: string;
102
+ handle: string;
103
+ sid?: string;
104
+ locale?: string;
105
+ }
106
+ export interface PageTemplateArgs {
107
+ handle: string;
108
+ sid?: string;
109
+ previewTemplate?: string;
110
+ page?: number;
111
+ locale?: string;
112
+ }
113
+ export interface PolicyTemplateArgs {
114
+ handle: string;
115
+ sid?: string;
116
+ locale?: string;
117
+ }
118
+ export interface PasswordTemplateArgs {
119
+ sid?: string;
120
+ locale?: string;
121
+ }
122
+ export interface SectionPreviewArgs {
123
+ type?: string;
124
+ preset?: number;
125
+ block?: string;
126
+ }
127
+ export interface TemplateFns {
128
+ getTemplateFn(opts: {
129
+ data: TemplateByIdArgs;
130
+ }): Promise<unknown>;
131
+ getHomeTemplateFn(opts: {
132
+ data: HomeTemplateArgs;
133
+ }): Promise<unknown>;
134
+ getProductTemplateFn(opts: {
135
+ data: ProductTemplateArgs;
136
+ }): Promise<unknown>;
137
+ getCollectionPageTemplateFn(opts: {
138
+ data: CollectionPageTemplateArgs;
139
+ }): Promise<unknown>;
140
+ getCollectionsTemplateFn(opts: {
141
+ data: CollectionsTemplateArgs;
142
+ }): Promise<unknown>;
143
+ getSearchPageTemplateFn(opts: {
144
+ data: SearchPageTemplateArgs;
145
+ }): Promise<unknown>;
146
+ getBlogPageTemplateFn(opts: {
147
+ data: BlogPageTemplateArgs;
148
+ }): Promise<unknown>;
149
+ getArticleTemplateFn(opts: {
150
+ data: ArticleTemplateArgs;
151
+ }): Promise<unknown>;
152
+ getPageTemplateFn(opts: {
153
+ data: PageTemplateArgs;
154
+ }): Promise<unknown>;
155
+ getPolicyTemplateFn(opts: {
156
+ data: PolicyTemplateArgs;
157
+ }): Promise<unknown>;
158
+ getPasswordTemplateFn(opts: {
159
+ data: PasswordTemplateArgs;
160
+ }): Promise<unknown>;
161
+ getSectionPreviewTemplateFn(opts: {
162
+ data: SectionPreviewArgs;
163
+ }): Promise<unknown>;
164
+ }
165
+ /** Collection / search listing grammar. */
166
+ export interface CollectionSearchSeam {
167
+ parseCollectionSearch(search: unknown): ParsedListingSearch;
168
+ searchToFilterInput(state: ParsedListingSearch): unknown;
169
+ /** Self-canonical for a narrowed (filtered / sorted) listing view, rebuilt
170
+ * from the parsed state so one selection has one canonical. */
171
+ narrowedPath(pathname: string, parsed: ParsedListingSearch): string;
172
+ DEFAULT_COLLECTION_SORT: string;
173
+ }
174
+ export interface ProductSearchSeam {
175
+ parseProductSearch(search: unknown): ParsedListingSearch & {
176
+ q?: string;
177
+ type?: string;
178
+ };
179
+ searchToProductFilterInput(state: ParsedListingSearch & {
180
+ q?: string;
181
+ }): unknown;
182
+ }
183
+ export interface BlogSearchSeam {
184
+ parseBlogSearch(search: unknown): ParsedBlogSearch;
185
+ }
186
+ export interface SearchParsers extends CollectionSearchSeam, ProductSearchSeam, BlogSearchSeam {}
187
+ export interface AccountSeam {
188
+ /** Resolves the central account app's absolute URL for a theme-side bounce. */
189
+ getAccountRedirectUrlFn(opts: {
190
+ data: {
191
+ subpath?: string;
192
+ returnToPath?: string;
193
+ };
194
+ }): Promise<{
195
+ url: string;
196
+ }>;
197
+ }
198
+ export interface PreviewSeam {
199
+ /** Rendered when there is nothing to preview, or when the section throws. */
200
+ Unavailable: ComponentType;
201
+ /** Optional per-theme provider wrapped around the previewed section — care
202
+ * uses it for `SectionPreviewContext`; kito has no such concept. */
203
+ Wrapper?: ComponentType<{
204
+ children: ReactNode;
205
+ }>;
206
+ }
207
+ /** What a theme's `src/route-adapter.tsx` exports. Every factory takes a
208
+ * narrower structural slice of this, so one object satisfies them all. */
209
+ export interface ThemeRouteAdapter extends RendererSeam, I18nSeam {
210
+ seo: SeoSeam;
211
+ fns: TemplateFns;
212
+ parsers: SearchParsers;
213
+ account: AccountSeam;
214
+ preview: PreviewSeam;
215
+ }
216
+ //#endregion
File without changes
@@ -0,0 +1,6 @@
1
+ import { RendererSeam } from "../adapter.js";
2
+ //#region src/components/article-page.d.ts
3
+ /** Article detail body. Kept out of `routes/blog.ts` so the JSON-LD modules ride
4
+ * the split chunk (see `template-page.tsx`). */
5
+ export declare function articleRouteComponent(adapter: RendererSeam): () => React.ReactNode;
6
+ //#endregion
@@ -0,0 +1,20 @@
1
+ import { useTemplate } from "../use-template.js";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { ArticleJsonLd } from "@shop-on/theme-react/article-json-ld";
4
+ import { BreadcrumbJsonLd } from "@shop-on/theme-react/breadcrumb-json-ld";
5
+ //#region src/components/article-page.tsx
6
+ /** Article detail body. Kept out of `routes/blog.ts` so the JSON-LD modules ride
7
+ * the split chunk (see `template-page.tsx`). */
8
+ function articleRouteComponent(adapter) {
9
+ const { TemplateRenderer } = adapter;
10
+ return function ArticlePage() {
11
+ const template = useTemplate();
12
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
13
+ /* @__PURE__ */ jsx(TemplateRenderer, { template }),
14
+ template.articleSchema ? /* @__PURE__ */ jsx(ArticleJsonLd, { data: template.articleSchema }) : null,
15
+ template.breadcrumbSchema ? /* @__PURE__ */ jsx(BreadcrumbJsonLd, { data: template.breadcrumbSchema }) : null
16
+ ] });
17
+ };
18
+ }
19
+ //#endregion
20
+ export { articleRouteComponent };
@@ -0,0 +1,6 @@
1
+ import { RendererSeam } from "../adapter.js";
2
+ //#region src/components/home-page.d.ts
3
+ /** Homepage body. Kept out of `routes/home.ts` so `WebsiteJsonLd` rides the
4
+ * split chunk instead of the entry chunk (see `template-page.tsx`). */
5
+ export declare function homeRouteComponent(adapter: RendererSeam): () => React.ReactNode;
6
+ //#endregion
@@ -0,0 +1,15 @@
1
+ import { useTemplate } from "../use-template.js";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { WebsiteJsonLd } from "@shop-on/theme-react/website-json-ld";
4
+ //#region src/components/home-page.tsx
5
+ /** Homepage body. Kept out of `routes/home.ts` so `WebsiteJsonLd` rides the
6
+ * split chunk instead of the entry chunk (see `template-page.tsx`). */
7
+ function homeRouteComponent(adapter) {
8
+ const { TemplateRenderer } = adapter;
9
+ return function HomePage() {
10
+ const template = useTemplate();
11
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(TemplateRenderer, { template }), template.websiteSchema ? /* @__PURE__ */ jsx(WebsiteJsonLd, { data: template.websiteSchema }) : null] });
12
+ };
13
+ }
14
+ //#endregion
15
+ export { homeRouteComponent };
@@ -0,0 +1,5 @@
1
+ //#region src/components/locale-layout.d.ts
2
+ /** The `{-$locale}` layout body — a pass-through. Separate module so the route
3
+ * file's `component` binding is splittable (see `template-page.tsx`). */
4
+ export declare function localeLayoutComponent(): React.ReactNode;
5
+ //#endregion
@@ -0,0 +1,10 @@
1
+ import { Outlet } from "@tanstack/react-router";
2
+ import { jsx } from "react/jsx-runtime";
3
+ //#region src/components/locale-layout.tsx
4
+ /** The `{-$locale}` layout body — a pass-through. Separate module so the route
5
+ * file's `component` binding is splittable (see `template-page.tsx`). */
6
+ function localeLayoutComponent() {
7
+ return /* @__PURE__ */ jsx(Outlet, {});
8
+ }
9
+ //#endregion
10
+ export { localeLayoutComponent };
@@ -0,0 +1,10 @@
1
+ import { RendererSeam } from "../adapter.js";
2
+ //#region src/components/product-page.d.ts
3
+ /** PDP body. `withBreadcrumb` is the only difference between the bare
4
+ * `/products/$handle` route and the collection-scoped one, whose path supplies
5
+ * a parent crumb. Kept out of `routes/product.ts` so the two JSON-LD modules
6
+ * ride the split chunk (see `template-page.tsx`). */
7
+ export declare function productRouteComponent(adapter: RendererSeam, opts?: {
8
+ withBreadcrumb?: boolean;
9
+ }): () => React.ReactNode;
10
+ //#endregion
@@ -0,0 +1,23 @@
1
+ import { useTemplate } from "../use-template.js";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { BreadcrumbJsonLd } from "@shop-on/theme-react/breadcrumb-json-ld";
4
+ import { ProductJsonLd } from "@shop-on/theme-react/product-json-ld";
5
+ //#region src/components/product-page.tsx
6
+ /** PDP body. `withBreadcrumb` is the only difference between the bare
7
+ * `/products/$handle` route and the collection-scoped one, whose path supplies
8
+ * a parent crumb. Kept out of `routes/product.ts` so the two JSON-LD modules
9
+ * ride the split chunk (see `template-page.tsx`). */
10
+ function productRouteComponent(adapter, opts) {
11
+ const { TemplateRenderer } = adapter;
12
+ const withBreadcrumb = opts?.withBreadcrumb ?? false;
13
+ return function ProductPage() {
14
+ const template = useTemplate();
15
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
16
+ /* @__PURE__ */ jsx(TemplateRenderer, { template }),
17
+ template.productSchema ? /* @__PURE__ */ jsx(ProductJsonLd, { data: template.productSchema }) : null,
18
+ withBreadcrumb && template.breadcrumbSchema ? /* @__PURE__ */ jsx(BreadcrumbJsonLd, { data: template.breadcrumbSchema }) : null
19
+ ] });
20
+ };
21
+ }
22
+ //#endregion
23
+ export { productRouteComponent };
@@ -0,0 +1,15 @@
1
+ import { PreviewSeam, RendererSeam } from "../adapter.js";
2
+ //#region src/components/section-preview-page.d.ts
3
+ export interface SectionPreviewComponentAdapter extends RendererSeam {
4
+ preview: PreviewSeam;
5
+ }
6
+ /** The picker's single-section preview body. Kept out of
7
+ * `routes/preview-section.ts` so the height-reporting effect and the theme's
8
+ * preview wrapper ride the split chunk (see `template-page.tsx`). */
9
+ export declare function sectionPreviewRouteComponent(adapter: SectionPreviewComponentAdapter): () => React.ReactNode;
10
+ /** `errorComponent` for the same route — a section that throws must not blank
11
+ * the picker, it must say so in place. */
12
+ export declare function sectionPreviewErrorComponent(adapter: {
13
+ preview: PreviewSeam;
14
+ }): () => React.ReactNode;
15
+ //#endregion
@@ -0,0 +1,42 @@
1
+ import { useOptionalTemplate } from "../use-template.js";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useEffect } from "react";
4
+ //#region src/components/section-preview-page.tsx
5
+ /** The picker's single-section preview body. Kept out of
6
+ * `routes/preview-section.ts` so the height-reporting effect and the theme's
7
+ * preview wrapper ride the split chunk (see `template-page.tsx`). */
8
+ function sectionPreviewRouteComponent(adapter) {
9
+ const { TemplateRenderer } = adapter;
10
+ const { Unavailable, Wrapper } = adapter.preview;
11
+ return function SectionPreviewPage() {
12
+ const template = useOptionalTemplate();
13
+ useReportPreviewHeight();
14
+ if (!template) return /* @__PURE__ */ jsx(Unavailable, {});
15
+ const rendered = /* @__PURE__ */ jsx(TemplateRenderer, { template });
16
+ return Wrapper ? /* @__PURE__ */ jsx(Wrapper, { children: rendered }) : rendered;
17
+ };
18
+ }
19
+ /** `errorComponent` for the same route — a section that throws must not blank
20
+ * the picker, it must say so in place. */
21
+ function sectionPreviewErrorComponent(adapter) {
22
+ const { Unavailable } = adapter.preview;
23
+ return function SectionPreviewError() {
24
+ return /* @__PURE__ */ jsx(Unavailable, {});
25
+ };
26
+ }
27
+ function useReportPreviewHeight() {
28
+ useEffect(() => {
29
+ if (window.parent === window) return;
30
+ const target = document.querySelector("main") ?? document.body;
31
+ const report = () => window.parent.postMessage({
32
+ type: "section-preview:height",
33
+ height: target.scrollHeight
34
+ }, "*");
35
+ report();
36
+ const ro = new ResizeObserver(report);
37
+ ro.observe(target);
38
+ return () => ro.disconnect();
39
+ }, []);
40
+ }
41
+ //#endregion
42
+ export { sectionPreviewErrorComponent, sectionPreviewRouteComponent };
@@ -0,0 +1,9 @@
1
+ import { RendererSeam } from "../adapter.js";
2
+ //#region src/components/template-page.d.ts
3
+ /** The plain case: render the loaded template, nothing else. Used by cart,
4
+ * search, page, policy, collection, collections index and blog listing. */
5
+ export declare function templatePageComponent(adapter: RendererSeam): () => React.ReactNode;
6
+ /** Same, for the routes whose loader may return null (404, password) — renders
7
+ * nothing rather than a bare shell when the template file is absent. */
8
+ export declare function optionalTemplatePageComponent(adapter: RendererSeam): () => React.ReactNode;
9
+ //#endregion
@@ -0,0 +1,22 @@
1
+ import { useOptionalTemplate, useTemplate } from "../use-template.js";
2
+ import { jsx } from "react/jsx-runtime";
3
+ //#region src/components/template-page.tsx
4
+ /** The plain case: render the loaded template, nothing else. Used by cart,
5
+ * search, page, policy, collection, collections index and blog listing. */
6
+ function templatePageComponent(adapter) {
7
+ const { TemplateRenderer } = adapter;
8
+ return function TemplatePage() {
9
+ return /* @__PURE__ */ jsx(TemplateRenderer, { template: useTemplate() });
10
+ };
11
+ }
12
+ /** Same, for the routes whose loader may return null (404, password) — renders
13
+ * nothing rather than a bare shell when the template file is absent. */
14
+ function optionalTemplatePageComponent(adapter) {
15
+ const { TemplateRenderer } = adapter;
16
+ return function OptionalTemplatePage() {
17
+ const template = useOptionalTemplate();
18
+ return template ? /* @__PURE__ */ jsx(TemplateRenderer, { template }) : null;
19
+ };
20
+ }
21
+ //#endregion
22
+ export { optionalTemplatePageComponent, templatePageComponent };
@@ -0,0 +1,29 @@
1
+ import { DepsArg, HeadArg, LoaderArg, NullableHeadArg, NullableTemplateRouteOptions, ParsedBlogSearch, ParsedListingSearch, PreviewDeps, SeoHeadResult, ServerRouteHandler, TemplateRouteOptions } from "./types.js";
2
+ import { AccountSeam, ArticleTemplateArgs, BlogPageTemplateArgs, BlogSearchSeam, CollectionPageTemplateArgs, CollectionSearchSeam, CollectionsTemplateArgs, HomeTemplateArgs, I18nSeam, PageTemplateArgs, PasswordTemplateArgs, PolicyTemplateArgs, PreviewSeam, ProductSearchSeam, ProductTemplateArgs, RendererSeam, SearchPageTemplateArgs, SearchParsers, SectionPreviewArgs, SeoSeam, TemplateByIdArgs, TemplateFn, TemplateFns, ThemeRouteAdapter } from "./adapter.js";
3
+ import { templatePreloadLinks } from "./preload-links.js";
4
+ import { SeoHelpers, SeoHelpersOptions, createSeoHelpers, isPreviewSearch, paginatedPath } from "./seo-head.js";
5
+ import { ThemeRouterOptions, createThemeRouter } from "./router.js";
6
+ import { useOptionalTemplate, useTemplate } from "./use-template.js";
7
+ import { accountBounceOptions, accountIndexRouteOptions } from "./routes/account.js";
8
+ import { articleRouteOptions, blogRouteOptions } from "./routes/blog.js";
9
+ import { cartRouteOptions } from "./routes/cart.js";
10
+ import { collectionRouteOptions, collectionsIndexRouteOptions } from "./routes/collection.js";
11
+ import { homeRouteOptions } from "./routes/home.js";
12
+ import { localeLayoutRouteOptions } from "./routes/locale-layout.js";
13
+ import { notFoundRouteOptions } from "./routes/not-found.js";
14
+ import { pageRouteOptions, policyRouteOptions } from "./routes/page.js";
15
+ import { passwordRouteOptions } from "./routes/password.js";
16
+ import { sectionPreviewRouteOptions } from "./routes/preview-section.js";
17
+ import { collectionProductRouteOptions, productRouteOptions } from "./routes/product.js";
18
+ import { searchRouteOptions } from "./routes/search.js";
19
+ import { articleRouteComponent } from "./components/article-page.js";
20
+ import { homeRouteComponent } from "./components/home-page.js";
21
+ import { localeLayoutComponent } from "./components/locale-layout.js";
22
+ import { productRouteComponent } from "./components/product-page.js";
23
+ import { sectionPreviewErrorComponent, sectionPreviewRouteComponent } from "./components/section-preview-page.js";
24
+ import { optionalTemplatePageComponent, templatePageComponent } from "./components/template-page.js";
25
+ import { analyticsEventsHandler, analyticsVisitsHandler } from "./routes/server/analytics.js";
26
+ import { analyticsScriptHandler } from "./routes/server/analytics-script.js";
27
+ import { customerLogoutHandler } from "./routes/server/customer-logout.js";
28
+ import { robotsHandler, sitemapHandler } from "./routes/server/seo-proxy.js";
29
+ export { type AccountSeam, type ArticleTemplateArgs, type BlogPageTemplateArgs, type BlogSearchSeam, type CollectionPageTemplateArgs, type CollectionSearchSeam, type CollectionsTemplateArgs, type DepsArg, type HeadArg, type HomeTemplateArgs, type I18nSeam, type LoaderArg, type NullableHeadArg, type NullableTemplateRouteOptions, type PageTemplateArgs, type ParsedBlogSearch, type ParsedListingSearch, type PasswordTemplateArgs, type PolicyTemplateArgs, type PreviewDeps, type PreviewSeam, type ProductSearchSeam, type ProductTemplateArgs, type RendererSeam, type SearchPageTemplateArgs, type SearchParsers, type SectionPreviewArgs, type SeoHeadResult, type SeoHelpers, type SeoHelpersOptions, type SeoSeam, type ServerRouteHandler, type TemplateByIdArgs, type TemplateFn, type TemplateFns, type TemplateRouteOptions, type ThemeRouteAdapter, type ThemeRouterOptions, accountBounceOptions, accountIndexRouteOptions, analyticsEventsHandler, analyticsScriptHandler, analyticsVisitsHandler, articleRouteComponent, articleRouteOptions, blogRouteOptions, cartRouteOptions, collectionProductRouteOptions, collectionRouteOptions, collectionsIndexRouteOptions, createSeoHelpers, createThemeRouter, customerLogoutHandler, homeRouteComponent, homeRouteOptions, isPreviewSearch, localeLayoutComponent, localeLayoutRouteOptions, notFoundRouteOptions, optionalTemplatePageComponent, pageRouteOptions, paginatedPath, passwordRouteOptions, policyRouteOptions, productRouteComponent, productRouteOptions, robotsHandler, searchRouteOptions, sectionPreviewErrorComponent, sectionPreviewRouteComponent, sectionPreviewRouteOptions, sitemapHandler, templatePageComponent, templatePreloadLinks, useOptionalTemplate, useTemplate };
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ import { templatePreloadLinks } from "./preload-links.js";
2
+ import { createSeoHelpers, isPreviewSearch, paginatedPath } from "./seo-head.js";
3
+ import { createThemeRouter } from "./router.js";
4
+ import { useOptionalTemplate, useTemplate } from "./use-template.js";
5
+ import { accountBounceOptions, accountIndexRouteOptions } from "./routes/account.js";
6
+ import { articleRouteOptions, blogRouteOptions } from "./routes/blog.js";
7
+ import { cartRouteOptions } from "./routes/cart.js";
8
+ import { collectionRouteOptions, collectionsIndexRouteOptions } from "./routes/collection.js";
9
+ import { homeRouteOptions } from "./routes/home.js";
10
+ import { localeLayoutRouteOptions } from "./routes/locale-layout.js";
11
+ import { notFoundRouteOptions } from "./routes/not-found.js";
12
+ import { pageRouteOptions, policyRouteOptions } from "./routes/page.js";
13
+ import { passwordRouteOptions } from "./routes/password.js";
14
+ import { sectionPreviewRouteOptions } from "./routes/preview-section.js";
15
+ import { collectionProductRouteOptions, productRouteOptions } from "./routes/product.js";
16
+ import { searchRouteOptions } from "./routes/search.js";
17
+ import { articleRouteComponent } from "./components/article-page.js";
18
+ import { homeRouteComponent } from "./components/home-page.js";
19
+ import { localeLayoutComponent } from "./components/locale-layout.js";
20
+ import { productRouteComponent } from "./components/product-page.js";
21
+ import { sectionPreviewErrorComponent, sectionPreviewRouteComponent } from "./components/section-preview-page.js";
22
+ import { optionalTemplatePageComponent, templatePageComponent } from "./components/template-page.js";
23
+ import { analyticsEventsHandler, analyticsVisitsHandler } from "./routes/server/analytics.js";
24
+ import { analyticsScriptHandler } from "./routes/server/analytics-script.js";
25
+ import { customerLogoutHandler } from "./routes/server/customer-logout.js";
26
+ import { robotsHandler, sitemapHandler } from "./routes/server/seo-proxy.js";
27
+ export { accountBounceOptions, accountIndexRouteOptions, analyticsEventsHandler, analyticsScriptHandler, analyticsVisitsHandler, articleRouteComponent, articleRouteOptions, blogRouteOptions, cartRouteOptions, collectionProductRouteOptions, collectionRouteOptions, collectionsIndexRouteOptions, createSeoHelpers, createThemeRouter, customerLogoutHandler, homeRouteComponent, homeRouteOptions, isPreviewSearch, localeLayoutComponent, localeLayoutRouteOptions, notFoundRouteOptions, optionalTemplatePageComponent, pageRouteOptions, paginatedPath, passwordRouteOptions, policyRouteOptions, productRouteComponent, productRouteOptions, robotsHandler, searchRouteOptions, sectionPreviewErrorComponent, sectionPreviewRouteComponent, sectionPreviewRouteOptions, sitemapHandler, templatePageComponent, templatePreloadLinks, useOptionalTemplate, useTemplate };
@@ -0,0 +1,6 @@
1
+ //#region src/preload-links.d.ts
2
+ export declare function templatePreloadLinks(loaderData: unknown): Array<{
3
+ rel: "modulepreload";
4
+ href: string;
5
+ }>;
6
+ //#endregion
@@ -0,0 +1,11 @@
1
+ //#region src/preload-links.ts
2
+ function templatePreloadLinks(loaderData) {
3
+ const hrefs = loaderData?.preloadHrefs;
4
+ if (!Array.isArray(hrefs)) return [];
5
+ return hrefs.map((href) => ({
6
+ rel: "modulepreload",
7
+ href: String(href)
8
+ }));
9
+ }
10
+ //#endregion
11
+ export { templatePreloadLinks };
@@ -0,0 +1,27 @@
1
+ import { AnyRoute, createRouter } from "@tanstack/react-router";
2
+ import { TemplateTypes } from "@shop-on/theme-react/template-renderer";
3
+ //#region src/router.d.ts
4
+ export interface ThemeRouterOptions {
5
+ /** The theme's generated `routeTree.gen.ts` export. */
6
+ routeTree: AnyRoute;
7
+ /** URL rewrite pair from the theme's `src/lib/i18n`. */
8
+ deLocalizeUrl: (url: URL) => URL;
9
+ localizeUrl: (url: URL) => URL;
10
+ /** The theme's glob-registry preloader (`src/components/template-renderer`). */
11
+ preloadTypes: (types: TemplateTypes) => Promise<void>;
12
+ /** Boxes that scroll the page instead of the window. The router resets only
13
+ * the window on navigation and CARRIES every other scroller's position to
14
+ * the next page, so a theme whose layout scrolls an inner element names it. */
15
+ scrollToTopSelectors?: string[];
16
+ }
17
+ /**
18
+ * The one router every theme runs on. A theme's `src/router.tsx` is a shim that
19
+ * hands over its route tree, its localize pair and its template preloader.
20
+ *
21
+ * Not-found handling lives HERE, at router level, rather than as a
22
+ * `notFoundComponent` on the locale layout: one definition covers unmatched
23
+ * paths AND every `notFound()` a loader throws, and it is what stops TanStack
24
+ * falling back to its bare `<p>Not Found</p>`.
25
+ */
26
+ export declare function createThemeRouter({ routeTree, deLocalizeUrl, localizeUrl, preloadTypes, scrollToTopSelectors }: ThemeRouterOptions): ReturnType<typeof createRouter>;
27
+ //#endregion
package/dist/router.js ADDED
@@ -0,0 +1,54 @@
1
+ import { Navigate, createRouter } from "@tanstack/react-router";
2
+ import { BProgress } from "@bprogress/core";
3
+ import { collectTemplateTypes } from "@shop-on/theme-react/template-renderer";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region src/router.tsx
6
+ function isTemplate(value) {
7
+ if (!value || typeof value !== "object") return false;
8
+ const v = value;
9
+ return typeof v.sections === "object" && v.sections !== null && Array.isArray(v.order);
10
+ }
11
+ /**
12
+ * The one router every theme runs on. A theme's `src/router.tsx` is a shim that
13
+ * hands over its route tree, its localize pair and its template preloader.
14
+ *
15
+ * Not-found handling lives HERE, at router level, rather than as a
16
+ * `notFoundComponent` on the locale layout: one definition covers unmatched
17
+ * paths AND every `notFound()` a loader throws, and it is what stops TanStack
18
+ * falling back to its bare `<p>Not Found</p>`.
19
+ */
20
+ function createThemeRouter({ routeTree, deLocalizeUrl, localizeUrl, preloadTypes, scrollToTopSelectors }) {
21
+ let router;
22
+ router = createRouter({
23
+ routeTree,
24
+ scrollRestoration: true,
25
+ scrollRestorationBehavior: "instant",
26
+ scrollToTopSelectors,
27
+ defaultNotFoundComponent: () => /* @__PURE__ */ jsx(Navigate, {
28
+ to: "/{-$locale}/404",
29
+ params: { locale: void 0 },
30
+ search: { preview: void 0 },
31
+ replace: true
32
+ }),
33
+ rewrite: {
34
+ input: ({ url }) => deLocalizeUrl(url),
35
+ output: ({ url }) => localizeUrl(url)
36
+ },
37
+ dehydrate: () => ({ templateTypes: collectTemplateTypes(router.state.matches.map((m) => m.loaderData).filter(isTemplate)) }),
38
+ hydrate: async ({ templateTypes }) => {
39
+ if (templateTypes) await preloadTypes(templateTypes);
40
+ }
41
+ });
42
+ if (typeof document !== "undefined") {
43
+ BProgress.configure({ showSpinner: false });
44
+ router.subscribe("onBeforeNavigate", ({ pathChanged }) => {
45
+ if (pathChanged) BProgress.start();
46
+ });
47
+ router.subscribe("onResolved", () => {
48
+ BProgress.done();
49
+ });
50
+ }
51
+ return router;
52
+ }
53
+ //#endregion
54
+ export { createThemeRouter };