@stapel/categories-react 0.1.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 (205) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/MODULE.md +194 -0
  3. package/README.md +191 -0
  4. package/dist/api/categoriesApi.d.ts +121 -0
  5. package/dist/api/categoriesApi.d.ts.map +1 -0
  6. package/dist/api/categoriesApi.js +48 -0
  7. package/dist/api/categoriesApi.js.map +1 -0
  8. package/dist/api/generated/schema.d.ts +2321 -0
  9. package/dist/api/generated/schema.d.ts.map +1 -0
  10. package/dist/api/generated/schema.js +2 -0
  11. package/dist/api/generated/schema.js.map +1 -0
  12. package/dist/api/types.d.ts +92 -0
  13. package/dist/api/types.d.ts.map +1 -0
  14. package/dist/api/types.js +2 -0
  15. package/dist/api/types.js.map +1 -0
  16. package/dist/catalog/labels.d.ts +103 -0
  17. package/dist/catalog/labels.d.ts.map +1 -0
  18. package/dist/catalog/labels.js +56 -0
  19. package/dist/catalog/labels.js.map +1 -0
  20. package/dist/catalog/pks.d.ts +25 -0
  21. package/dist/catalog/pks.d.ts.map +1 -0
  22. package/dist/catalog/pks.js +38 -0
  23. package/dist/catalog/pks.js.map +1 -0
  24. package/dist/catalog/sync.d.ts +103 -0
  25. package/dist/catalog/sync.d.ts.map +1 -0
  26. package/dist/catalog/sync.js +114 -0
  27. package/dist/catalog/sync.js.map +1 -0
  28. package/dist/catalog/tree.d.ts +119 -0
  29. package/dist/catalog/tree.d.ts.map +1 -0
  30. package/dist/catalog/tree.js +166 -0
  31. package/dist/catalog/tree.js.map +1 -0
  32. package/dist/default/CatalogPage.d.ts +16 -0
  33. package/dist/default/CatalogPage.d.ts.map +1 -0
  34. package/dist/default/CatalogPage.js +13 -0
  35. package/dist/default/CatalogPage.js.map +1 -0
  36. package/dist/default/CategoryBreadcrumbsBar.d.ts +20 -0
  37. package/dist/default/CategoryBreadcrumbsBar.d.ts.map +1 -0
  38. package/dist/default/CategoryBreadcrumbsBar.js +35 -0
  39. package/dist/default/CategoryBreadcrumbsBar.js.map +1 -0
  40. package/dist/default/CategoryCarouselStrip.d.ts +22 -0
  41. package/dist/default/CategoryCarouselStrip.d.ts.map +1 -0
  42. package/dist/default/CategoryCarouselStrip.js +24 -0
  43. package/dist/default/CategoryCarouselStrip.js.map +1 -0
  44. package/dist/default/CategoryFeatureList.d.ts +23 -0
  45. package/dist/default/CategoryFeatureList.d.ts.map +1 -0
  46. package/dist/default/CategoryFeatureList.js +28 -0
  47. package/dist/default/CategoryFeatureList.js.map +1 -0
  48. package/dist/default/CategoryPage.d.ts +43 -0
  49. package/dist/default/CategoryPage.d.ts.map +1 -0
  50. package/dist/default/CategoryPage.js +21 -0
  51. package/dist/default/CategoryPage.js.map +1 -0
  52. package/dist/default/CategoryPickerField.d.ts +26 -0
  53. package/dist/default/CategoryPickerField.d.ts.map +1 -0
  54. package/dist/default/CategoryPickerField.js +40 -0
  55. package/dist/default/CategoryPickerField.js.map +1 -0
  56. package/dist/default/CategoryTreePane.d.ts +28 -0
  57. package/dist/default/CategoryTreePane.d.ts.map +1 -0
  58. package/dist/default/CategoryTreePane.js +25 -0
  59. package/dist/default/CategoryTreePane.js.map +1 -0
  60. package/dist/default/ErrorAlert.d.ts +9 -0
  61. package/dist/default/ErrorAlert.d.ts.map +1 -0
  62. package/dist/default/ErrorAlert.js +27 -0
  63. package/dist/default/ErrorAlert.js.map +1 -0
  64. package/dist/default/index.d.ts +37 -0
  65. package/dist/default/index.d.ts.map +1 -0
  66. package/dist/default/index.js +28 -0
  67. package/dist/default/index.js.map +1 -0
  68. package/dist/default/theme.d.ts +25 -0
  69. package/dist/default/theme.d.ts.map +1 -0
  70. package/dist/default/theme.js +40 -0
  71. package/dist/default/theme.js.map +1 -0
  72. package/dist/default/types.d.ts +12 -0
  73. package/dist/default/types.d.ts.map +1 -0
  74. package/dist/default/types.js +2 -0
  75. package/dist/default/types.js.map +1 -0
  76. package/dist/headless/CategoriesProvider.d.ts +19 -0
  77. package/dist/headless/CategoriesProvider.d.ts.map +1 -0
  78. package/dist/headless/CategoriesProvider.js +15 -0
  79. package/dist/headless/CategoriesProvider.js.map +1 -0
  80. package/dist/headless/CategoryBreadcrumbs.d.ts +40 -0
  81. package/dist/headless/CategoryBreadcrumbs.d.ts.map +1 -0
  82. package/dist/headless/CategoryBreadcrumbs.js +42 -0
  83. package/dist/headless/CategoryBreadcrumbs.js.map +1 -0
  84. package/dist/headless/CategoryCarousel.d.ts +48 -0
  85. package/dist/headless/CategoryCarousel.d.ts.map +1 -0
  86. package/dist/headless/CategoryCarousel.js +34 -0
  87. package/dist/headless/CategoryCarousel.js.map +1 -0
  88. package/dist/headless/CategoryFeatures.d.ts +67 -0
  89. package/dist/headless/CategoryFeatures.d.ts.map +1 -0
  90. package/dist/headless/CategoryFeatures.js +46 -0
  91. package/dist/headless/CategoryFeatures.js.map +1 -0
  92. package/dist/headless/CategoryPicker.d.ts +69 -0
  93. package/dist/headless/CategoryPicker.d.ts.map +1 -0
  94. package/dist/headless/CategoryPicker.js +86 -0
  95. package/dist/headless/CategoryPicker.js.map +1 -0
  96. package/dist/headless/CategoryTree.d.ts +55 -0
  97. package/dist/headless/CategoryTree.d.ts.map +1 -0
  98. package/dist/headless/CategoryTree.js +51 -0
  99. package/dist/headless/CategoryTree.js.map +1 -0
  100. package/dist/i18n/errorsMap.d.ts +22 -0
  101. package/dist/i18n/errorsMap.d.ts.map +1 -0
  102. package/dist/i18n/errorsMap.js +31 -0
  103. package/dist/i18n/errorsMap.js.map +1 -0
  104. package/dist/i18n/es.d.ts +25 -0
  105. package/dist/i18n/es.d.ts.map +1 -0
  106. package/dist/i18n/es.js +74 -0
  107. package/dist/i18n/es.js.map +1 -0
  108. package/dist/i18n/generated/errors.es.gen.d.ts +16 -0
  109. package/dist/i18n/generated/errors.es.gen.d.ts.map +1 -0
  110. package/dist/i18n/generated/errors.es.gen.js +58 -0
  111. package/dist/i18n/generated/errors.es.gen.js.map +1 -0
  112. package/dist/i18n/generated/errors.gen.d.ts +407 -0
  113. package/dist/i18n/generated/errors.gen.d.ts.map +1 -0
  114. package/dist/i18n/generated/errors.gen.js +207 -0
  115. package/dist/i18n/generated/errors.gen.js.map +1 -0
  116. package/dist/i18n/generated/errors.ru.gen.d.ts +16 -0
  117. package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -0
  118. package/dist/i18n/generated/errors.ru.gen.js +58 -0
  119. package/dist/i18n/generated/errors.ru.gen.js.map +1 -0
  120. package/dist/i18n/keys.d.ts +89 -0
  121. package/dist/i18n/keys.d.ts.map +1 -0
  122. package/dist/i18n/keys.js +134 -0
  123. package/dist/i18n/keys.js.map +1 -0
  124. package/dist/i18n/ru.d.ts +41 -0
  125. package/dist/i18n/ru.d.ts.map +1 -0
  126. package/dist/i18n/ru.js +90 -0
  127. package/dist/i18n/ru.js.map +1 -0
  128. package/dist/index.d.ts +88 -0
  129. package/dist/index.d.ts.map +1 -0
  130. package/dist/index.js +78 -0
  131. package/dist/index.js.map +1 -0
  132. package/dist/model/catalogStore.d.ts +23 -0
  133. package/dist/model/catalogStore.d.ts.map +1 -0
  134. package/dist/model/catalogStore.js +85 -0
  135. package/dist/model/catalogStore.js.map +1 -0
  136. package/dist/model/catalogSync.d.ts +49 -0
  137. package/dist/model/catalogSync.d.ts.map +1 -0
  138. package/dist/model/catalogSync.js +64 -0
  139. package/dist/model/catalogSync.js.map +1 -0
  140. package/dist/model/context.d.ts +11 -0
  141. package/dist/model/context.d.ts.map +1 -0
  142. package/dist/model/context.js +16 -0
  143. package/dist/model/context.js.map +1 -0
  144. package/dist/model/queries.d.ts +124 -0
  145. package/dist/model/queries.d.ts.map +1 -0
  146. package/dist/model/queries.js +147 -0
  147. package/dist/model/queries.js.map +1 -0
  148. package/dist/model/queryKeys.d.ts +30 -0
  149. package/dist/model/queryKeys.d.ts.map +1 -0
  150. package/dist/model/queryKeys.js +16 -0
  151. package/dist/model/queryKeys.js.map +1 -0
  152. package/dist/model/runtime.d.ts +31 -0
  153. package/dist/model/runtime.d.ts.map +1 -0
  154. package/dist/model/runtime.js +6 -0
  155. package/dist/model/runtime.js.map +1 -0
  156. package/dist/nav/manifest.d.ts +31 -0
  157. package/dist/nav/manifest.d.ts.map +1 -0
  158. package/dist/nav/manifest.js +37 -0
  159. package/dist/nav/manifest.js.map +1 -0
  160. package/llms.txt +125 -0
  161. package/manifest.json +918 -0
  162. package/nav-manifest.json +44 -0
  163. package/package.json +119 -0
  164. package/src/analytics/generated/events.json +7 -0
  165. package/src/api/categoriesApi.ts +179 -0
  166. package/src/api/generated/schema.ts +2323 -0
  167. package/src/api/types.ts +99 -0
  168. package/src/catalog/labels.ts +129 -0
  169. package/src/catalog/pks.ts +35 -0
  170. package/src/catalog/sync.ts +169 -0
  171. package/src/catalog/tree.ts +242 -0
  172. package/src/default/CatalogPage.tsx +40 -0
  173. package/src/default/CategoryBreadcrumbsBar.tsx +93 -0
  174. package/src/default/CategoryCarouselStrip.tsx +102 -0
  175. package/src/default/CategoryFeatureList.tsx +125 -0
  176. package/src/default/CategoryPage.tsx +114 -0
  177. package/src/default/CategoryPickerField.tsx +173 -0
  178. package/src/default/CategoryTreePane.tsx +134 -0
  179. package/src/default/ErrorAlert.tsx +46 -0
  180. package/src/default/index.ts +36 -0
  181. package/src/default/theme.tsx +67 -0
  182. package/src/default/types.ts +12 -0
  183. package/src/headless/CategoriesProvider.tsx +20 -0
  184. package/src/headless/CategoryBreadcrumbs.tsx +78 -0
  185. package/src/headless/CategoryCarousel.tsx +80 -0
  186. package/src/headless/CategoryFeatures.tsx +100 -0
  187. package/src/headless/CategoryPicker.tsx +173 -0
  188. package/src/headless/CategoryTree.tsx +115 -0
  189. package/src/i18n/errorsMap.ts +46 -0
  190. package/src/i18n/es.ts +92 -0
  191. package/src/i18n/generated/errors.es.gen.ts +64 -0
  192. package/src/i18n/generated/errors.gen.ts +238 -0
  193. package/src/i18n/generated/errors.json +495 -0
  194. package/src/i18n/generated/errors.ru.gen.ts +64 -0
  195. package/src/i18n/keys.ts +156 -0
  196. package/src/i18n/ru.ts +110 -0
  197. package/src/index.ts +191 -0
  198. package/src/model/catalogStore.ts +102 -0
  199. package/src/model/catalogSync.ts +115 -0
  200. package/src/model/context.tsx +28 -0
  201. package/src/model/queries.ts +226 -0
  202. package/src/model/queryKeys.ts +50 -0
  203. package/src/model/runtime.ts +39 -0
  204. package/src/nav/manifest.ts +66 -0
  205. package/tsconfig.json +26 -0
@@ -0,0 +1,114 @@
1
+ /** An empty snapshot — the state before the first sync. */
2
+ export const EMPTY_SNAPSHOT = {
3
+ version: 1,
4
+ cursor: 0,
5
+ rows: [],
6
+ };
7
+ /** A snapshot with no rows has never synced, whatever its cursor says. */
8
+ export function isEmptySnapshot(snapshot) {
9
+ return snapshot.rows.length === 0;
10
+ }
11
+ /**
12
+ * Accept a value read back from persistence, or reject it.
13
+ *
14
+ * A repository read can hand back anything: a snapshot written by a previous
15
+ * version of this pair, a half-written value, `undefined` after a cleared
16
+ * origin. Every one of those must degrade to "no snapshot" — a cache that
17
+ * throws on read is worse than no cache.
18
+ */
19
+ export function parseSnapshot(value) {
20
+ if (typeof value !== "object" || value === null)
21
+ return undefined;
22
+ const candidate = value;
23
+ if (candidate.version !== 1)
24
+ return undefined;
25
+ if (typeof candidate.cursor !== "number" || !Number.isFinite(candidate.cursor)) {
26
+ return undefined;
27
+ }
28
+ if (!Array.isArray(candidate.rows))
29
+ return undefined;
30
+ for (const row of candidate.rows) {
31
+ if (typeof row !== "object" || row === null)
32
+ return undefined;
33
+ if (typeof row.id !== "number")
34
+ return undefined;
35
+ }
36
+ return {
37
+ version: 1,
38
+ cursor: candidate.cursor,
39
+ rows: candidate.rows,
40
+ };
41
+ }
42
+ /**
43
+ * The request that starts a sync from `snapshot`.
44
+ *
45
+ * A cold start asks for everything with `include_deleted: false` — nothing is
46
+ * cached, so a tombstone has nothing to evict and the payload is smaller. A
47
+ * warm start asks for the delta with `include_deleted: true`, because there
48
+ * the tombstones are the point.
49
+ */
50
+ export function firstPageRequest(snapshot) {
51
+ if (isEmptySnapshot(snapshot)) {
52
+ return { includeDeleted: false, page: 1 };
53
+ }
54
+ return { minRevision: snapshot.cursor, includeDeleted: true, page: 1 };
55
+ }
56
+ /**
57
+ * The request for the next page of an in-flight walk, or `undefined` when the
58
+ * walk is done.
59
+ *
60
+ * `maxRevision` pins the window to what the first page reported, so the walk
61
+ * reads one consistent snapshot of the table instead of a moving one.
62
+ */
63
+ export function nextPageRequest(previous, page) {
64
+ if (!page.pagination.has_next)
65
+ return undefined;
66
+ return {
67
+ ...previous,
68
+ page: page.pagination.page + 1,
69
+ maxRevision: previous.maxRevision ?? page.revisions.global_max,
70
+ };
71
+ }
72
+ /**
73
+ * Fold one page into an accumulator.
74
+ *
75
+ * There is one merge rule, and the FULL/DELTA distinction lives in what the
76
+ * caller starts from, not in a flag here: a delta folds into the stored
77
+ * snapshot, a full sync folds into {@link EMPTY_SNAPSHOT}. That is the whole
78
+ * difference — a row the server no longer lists is gone from a full listing
79
+ * precisely because it was never folded in. Expressing it as a flag on the
80
+ * merge invites the two silent failures instead: a delta treated as full
81
+ * empties the catalogue, a full treated as delta resurrects rows deleted while
82
+ * the client was away.
83
+ *
84
+ * The new snapshot is not published until the walk COMPLETES (see
85
+ * `model/catalogSync.ts`), so a walk that fails halfway leaves the previous
86
+ * catalogue on screen rather than a truncated one.
87
+ *
88
+ * The cursor only ever moves FORWARD, and never past `cursorLimit`:
89
+ * `global_max` is the table's live maximum, which can already exceed the
90
+ * window a walk pinned with `max_revision`. Taking it verbatim would record
91
+ * having read rows the walk never asked for.
92
+ */
93
+ export function applyCategoryPage(accumulator, page, options = {}) {
94
+ const byId = new Map();
95
+ for (const row of accumulator.rows)
96
+ byId.set(row.id, row);
97
+ for (const row of page.results) {
98
+ if (row.deleted === true)
99
+ byId.delete(row.id);
100
+ else
101
+ byId.set(row.id, row);
102
+ }
103
+ for (const id of page.revisions.deleted_ids)
104
+ byId.delete(id);
105
+ const observed = options.cursorLimit !== undefined
106
+ ? Math.min(page.revisions.global_max, options.cursorLimit)
107
+ : page.revisions.global_max;
108
+ return {
109
+ version: 1,
110
+ cursor: Math.max(accumulator.cursor, observed),
111
+ rows: [...byId.values()],
112
+ };
113
+ }
114
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/catalog/sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * The tree the server does not build.
3
+ *
4
+ * `stapel-categories` has no tree endpoint. `GET /categories/` returns FLAT
5
+ * rows ordered by `revision`, each carrying `tn_parent`, `tn_priority` and the
6
+ * treenode ancestry columns — the client assembles the hierarchy (spec §4.3).
7
+ * Everything in this file is pure: no React, no fetch, no storage, so the same
8
+ * functions serve a browser, an SSR render and a test.
9
+ *
10
+ * ── Three server facts this module encodes, each a bug if forgotten ────────
11
+ *
12
+ * 1. **`deleted` rows are served.** `include_deleted` defaults to `true`, and
13
+ * the delta protocol NEEDS it that way. A tree built without filtering
14
+ * shows soft-deleted categories in a menu.
15
+ * 2. **`active: false` rows are served too**, and nothing on the list endpoint
16
+ * filters them — only `carousel` does. "Active" is the storefront's
17
+ * visibility switch, so the public tree drops them by default and the
18
+ * option to keep them is explicit.
19
+ * 3. **Order is `tn_priority` DESCENDING**, which is what both `children` and
20
+ * `carousel` do server-side (`views.py: order_by("-tn_priority")`). Ties
21
+ * are broken by `id` so a rebuild is deterministic — priority defaults to
22
+ * `0`, so a catalogue that never set it is ALL ties, and an unstable order
23
+ * there means the menu reshuffles between renders.
24
+ */
25
+ import type { Category } from "../api/types.js";
26
+ /** One node of the assembled tree. */
27
+ export interface CategoryNode {
28
+ readonly category: Category;
29
+ readonly id: number;
30
+ readonly depth: number;
31
+ readonly children: readonly CategoryNode[];
32
+ }
33
+ /**
34
+ * A built catalogue: the rows, plus the lookups a screen actually asks for.
35
+ *
36
+ * Built once per snapshot and shared, because every one of these questions is
37
+ * asked on every render of every category page.
38
+ */
39
+ export interface CategoryIndex {
40
+ /** Roots, in display order. */
41
+ readonly roots: readonly CategoryNode[];
42
+ /** Every node by id, including ones filtered out of `roots`' subtrees? No —
43
+ * only the nodes that survived the filter. What is not in the tree is not
44
+ * addressable. */
45
+ readonly byId: ReadonlyMap<number, CategoryNode>;
46
+ /** Slug → node. See `resolveCategorySlug` for the collision rule. */
47
+ readonly bySlug: ReadonlyMap<string, CategoryNode>;
48
+ /** How many rows the source snapshot had before filtering. */
49
+ readonly totalRows: number;
50
+ }
51
+ export interface BuildCategoryTreeOptions {
52
+ /** Keep `deleted: true` rows. Default `false`. */
53
+ readonly includeDeleted?: boolean;
54
+ /** Keep `active: false` rows. Default `false` — the storefront's answer.
55
+ * A catalogue admin passes `true`. */
56
+ readonly includeInactive?: boolean;
57
+ }
58
+ /**
59
+ * Assemble flat rows into a tree.
60
+ *
61
+ * A row whose `tn_parent` is not in the surviving set becomes a ROOT rather
62
+ * than disappearing. That is not tidiness — it is the only safe answer to the
63
+ * two ways it legitimately happens: a delta page that brought a child before
64
+ * its parent, and a parent filtered out by `active: false` while the child was
65
+ * left active by a catalogue editor. Dropping the subtree would delete a live
66
+ * branch of the catalogue from the menu and give nobody a reason.
67
+ */
68
+ export declare function buildCategoryTree(rows: Iterable<Category>, options?: BuildCategoryTreeOptions): CategoryIndex;
69
+ /**
70
+ * Resolve a URL slug to a category — the lookup the SERVER does not offer.
71
+ *
72
+ * `CategoryViewSet` never overrides `lookup_field` and the list endpoint has
73
+ * no slug filter, so `GET /categories/<slug>/` is a 404 and `?slug=` is
74
+ * ignored. The storefront's `/c/:slug` therefore resolves against the synced
75
+ * tree (spec §4.3), which is one more reason the catalogue is cached rather
76
+ * than fetched per page.
77
+ *
78
+ * `Category.slug` is `unique=True` at the model level, so a collision cannot
79
+ * come from the database. It CAN come from the client's own snapshot — a
80
+ * delta that renamed a slug from A to B arrives as one row, and a stale row
81
+ * still holding B has not been evicted yet. First-in-display-order wins,
82
+ * deterministically, and the loser is reachable by id.
83
+ */
84
+ export declare function resolveCategorySlug(index: CategoryIndex, slug: string): CategoryNode | undefined;
85
+ /**
86
+ * The path from the root down to `id`, inclusive — the breadcrumb.
87
+ *
88
+ * Walks `tn_parent` through the built index rather than trusting
89
+ * `tn_ancestors_pks`, because the index is what the screen is showing: if a
90
+ * filter removed an ancestor, the crumb must not name a category the visitor
91
+ * cannot open. `categoryAncestorIds` is the other answer — the server's — and
92
+ * the two are compared in `test/tree.test.ts` precisely so a divergence is a
93
+ * red test rather than a subtly wrong crumb.
94
+ *
95
+ * An unknown id gives `[]`, not a partial path: half a breadcrumb reads as a
96
+ * top-level category.
97
+ */
98
+ export declare function categoryBreadcrumbs(index: CategoryIndex, id: number | null | undefined): readonly CategoryNode[];
99
+ /**
100
+ * The server's own answer to "who are this row's ancestors", root-first, from
101
+ * `tn_ancestors_pks`.
102
+ *
103
+ * Useful without a built index (a single category fetched on its own), and the
104
+ * cross-check for {@link categoryBreadcrumbs}.
105
+ */
106
+ export declare function categoryAncestorIds(category: Category): readonly number[];
107
+ /**
108
+ * The server's own answer to "who are this row's children", from
109
+ * `tn_children_pks`.
110
+ *
111
+ * This is the FULL child set, including soft-deleted and inactive rows — the
112
+ * column is maintained by treenode, which knows nothing about either flag. Use
113
+ * it to detect "this category has sub-categories at all"; use the built node's
114
+ * `children` to render them.
115
+ */
116
+ export declare function categoryChildIds(category: Category): readonly number[];
117
+ /** Depth-first walk of a subtree (or the whole forest), in display order. */
118
+ export declare function flattenCategoryNodes(nodes: readonly CategoryNode[]): readonly CategoryNode[];
119
+ //# sourceMappingURL=tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../../src/catalog/tree.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAGhD,sCAAsC;AACtC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;CAC5C;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,+BAA+B;IAC/B,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;IACxC;;sBAEkB;IAClB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACjD,qEAAqE;IACrE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACnD,8DAA8D;IAC9D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,wBAAwB;IACvC,kDAAkD;IAClD,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC;0CACsC;IACtC,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;CACpC;AAkBD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC,EACxB,OAAO,GAAE,wBAA6B,GACrC,aAAa,CAuDf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,MAAM,GACX,YAAY,GAAG,SAAS,CAE1B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,aAAa,EACpB,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC5B,SAAS,YAAY,EAAE,CAgBzB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,MAAM,EAAE,CAEzE;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,MAAM,EAAE,CAEtE;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,YAAY,EAAE,GAC7B,SAAS,YAAY,EAAE,CAUzB"}
@@ -0,0 +1,166 @@
1
+ import { parseTreenodePks } from "./pks.js";
2
+ function displayOrder(a, b) {
3
+ const pa = a.tn_priority ?? 0;
4
+ const pb = b.tn_priority ?? 0;
5
+ if (pa !== pb)
6
+ return pb - pa;
7
+ return a.id - b.id;
8
+ }
9
+ function keeps(row, options) {
10
+ if (row.deleted === true && options.includeDeleted !== true)
11
+ return false;
12
+ // `active` is optional in the schema and defaults to true on the model, so
13
+ // the test is "not explicitly false" — an absent field is an active
14
+ // category, never a hidden one.
15
+ if (row.active === false && options.includeInactive !== true)
16
+ return false;
17
+ return true;
18
+ }
19
+ /**
20
+ * Assemble flat rows into a tree.
21
+ *
22
+ * A row whose `tn_parent` is not in the surviving set becomes a ROOT rather
23
+ * than disappearing. That is not tidiness — it is the only safe answer to the
24
+ * two ways it legitimately happens: a delta page that brought a child before
25
+ * its parent, and a parent filtered out by `active: false` while the child was
26
+ * left active by a catalogue editor. Dropping the subtree would delete a live
27
+ * branch of the catalogue from the menu and give nobody a reason.
28
+ */
29
+ export function buildCategoryTree(rows, options = {}) {
30
+ const all = [...rows];
31
+ const kept = all.filter((row) => keeps(row, options)).sort(displayOrder);
32
+ const childrenOf = new Map();
33
+ const present = new Set(kept.map((row) => row.id));
34
+ const roots = [];
35
+ for (const row of kept) {
36
+ const parent = row.tn_parent;
37
+ if (parent === null || parent === undefined || !present.has(parent)) {
38
+ roots.push(row);
39
+ continue;
40
+ }
41
+ const bucket = childrenOf.get(parent);
42
+ if (bucket === undefined)
43
+ childrenOf.set(parent, [row]);
44
+ else
45
+ bucket.push(row);
46
+ }
47
+ const byId = new Map();
48
+ const bySlug = new Map();
49
+ // Iterative build (not recursion): a catalogue is authored data, and a cycle
50
+ // introduced by a bad `tn_parent` would blow the stack instead of rendering.
51
+ // `seen` makes a cycle a finite, visible truncation.
52
+ function build(row, depth, seen) {
53
+ const kids = childrenOf.get(row.id) ?? [];
54
+ const nextSeen = new Set(seen).add(row.id);
55
+ const node = {
56
+ category: row,
57
+ id: row.id,
58
+ depth,
59
+ children: kids
60
+ .filter((kid) => !nextSeen.has(kid.id))
61
+ .map((kid) => build(kid, depth + 1, nextSeen)),
62
+ };
63
+ byId.set(row.id, node);
64
+ if (row.slug !== "" && !bySlug.has(row.slug))
65
+ bySlug.set(row.slug, node);
66
+ return node;
67
+ }
68
+ const builtRoots = roots.map((row) => build(row, 0, new Set()));
69
+ // A row that no root can reach is a row inside a PARENT CYCLE (`a.parent =
70
+ // b, b.parent = a` — authored data, so possible). The recursion above
71
+ // refuses to follow the cycle, which keeps the stack safe but would drop
72
+ // those rows entirely: they would vanish from the menu with no error
73
+ // anywhere. Promote whatever is left to roots instead, so a broken branch
74
+ // is VISIBLE and fixable rather than absent.
75
+ for (const row of kept) {
76
+ if (byId.has(row.id))
77
+ continue;
78
+ builtRoots.push(build(row, 0, new Set()));
79
+ }
80
+ return { roots: builtRoots, byId, bySlug, totalRows: all.length };
81
+ }
82
+ /**
83
+ * Resolve a URL slug to a category — the lookup the SERVER does not offer.
84
+ *
85
+ * `CategoryViewSet` never overrides `lookup_field` and the list endpoint has
86
+ * no slug filter, so `GET /categories/<slug>/` is a 404 and `?slug=` is
87
+ * ignored. The storefront's `/c/:slug` therefore resolves against the synced
88
+ * tree (spec §4.3), which is one more reason the catalogue is cached rather
89
+ * than fetched per page.
90
+ *
91
+ * `Category.slug` is `unique=True` at the model level, so a collision cannot
92
+ * come from the database. It CAN come from the client's own snapshot — a
93
+ * delta that renamed a slug from A to B arrives as one row, and a stale row
94
+ * still holding B has not been evicted yet. First-in-display-order wins,
95
+ * deterministically, and the loser is reachable by id.
96
+ */
97
+ export function resolveCategorySlug(index, slug) {
98
+ return index.bySlug.get(slug);
99
+ }
100
+ /**
101
+ * The path from the root down to `id`, inclusive — the breadcrumb.
102
+ *
103
+ * Walks `tn_parent` through the built index rather than trusting
104
+ * `tn_ancestors_pks`, because the index is what the screen is showing: if a
105
+ * filter removed an ancestor, the crumb must not name a category the visitor
106
+ * cannot open. `categoryAncestorIds` is the other answer — the server's — and
107
+ * the two are compared in `test/tree.test.ts` precisely so a divergence is a
108
+ * red test rather than a subtly wrong crumb.
109
+ *
110
+ * An unknown id gives `[]`, not a partial path: half a breadcrumb reads as a
111
+ * top-level category.
112
+ */
113
+ export function categoryBreadcrumbs(index, id) {
114
+ if (id === null || id === undefined)
115
+ return [];
116
+ const start = index.byId.get(id);
117
+ if (start === undefined)
118
+ return [];
119
+ const path = [start];
120
+ const seen = new Set([start.id]);
121
+ let cursor = start.category.tn_parent;
122
+ while (cursor !== null && cursor !== undefined && !seen.has(cursor)) {
123
+ const parent = index.byId.get(cursor);
124
+ if (parent === undefined)
125
+ break;
126
+ path.push(parent);
127
+ seen.add(parent.id);
128
+ cursor = parent.category.tn_parent;
129
+ }
130
+ return path.reverse();
131
+ }
132
+ /**
133
+ * The server's own answer to "who are this row's ancestors", root-first, from
134
+ * `tn_ancestors_pks`.
135
+ *
136
+ * Useful without a built index (a single category fetched on its own), and the
137
+ * cross-check for {@link categoryBreadcrumbs}.
138
+ */
139
+ export function categoryAncestorIds(category) {
140
+ return parseTreenodePks(category.tn_ancestors_pks);
141
+ }
142
+ /**
143
+ * The server's own answer to "who are this row's children", from
144
+ * `tn_children_pks`.
145
+ *
146
+ * This is the FULL child set, including soft-deleted and inactive rows — the
147
+ * column is maintained by treenode, which knows nothing about either flag. Use
148
+ * it to detect "this category has sub-categories at all"; use the built node's
149
+ * `children` to render them.
150
+ */
151
+ export function categoryChildIds(category) {
152
+ return parseTreenodePks(category.tn_children_pks);
153
+ }
154
+ /** Depth-first walk of a subtree (or the whole forest), in display order. */
155
+ export function flattenCategoryNodes(nodes) {
156
+ const out = [];
157
+ const walk = (list) => {
158
+ for (const node of list) {
159
+ out.push(node);
160
+ walk(node.children);
161
+ }
162
+ };
163
+ walk(nodes);
164
+ return out;
165
+ }
166
+ //# sourceMappingURL=tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.js","sourceRoot":"","sources":["../../src/catalog/tree.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * `<CatalogPage>` — the catalogue root at `/c`: the carousel over the top-level
3
+ * tree.
4
+ *
5
+ * This is a single-pair screen, which is exactly why it has a nav entry while
6
+ * `/` does not. The storefront's landing composes THIS pair's carousel with
7
+ * search's newest-listings feed (spec §5.1), and a composed route belongs to
8
+ * the container that composes it — see `nav/manifest.ts`.
9
+ */
10
+ import type { ReactElement } from "react";
11
+ import type { ThemeModeProp } from "./types.js";
12
+ export interface CatalogPageProps extends ThemeModeProp {
13
+ readonly basePath?: string;
14
+ }
15
+ export declare function CatalogPage(props: CatalogPageProps): ReactElement;
16
+ //# sourceMappingURL=CatalogPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CatalogPage.d.ts","sourceRoot":"","sources":["../../src/default/CatalogPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAO1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACrD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,YAAY,CAiBjE"}
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Flex, Typography } from "antd";
3
+ import { useT } from "@stapel/core";
4
+ import { CATEGORIES_I18N_KEYS } from "../i18n/keys.js";
5
+ import { CategoryCarouselStrip } from "./CategoryCarouselStrip.js";
6
+ import { CategoryTreePane } from "./CategoryTreePane.js";
7
+ import { CategoriesSkinTheme } from "./theme.js";
8
+ export function CatalogPage(props) {
9
+ const t = useT();
10
+ const base = props.basePath ?? "/c";
11
+ return (_jsx(CategoriesSkinTheme, { ...(props.mode !== undefined ? { mode: props.mode } : {}), children: _jsxs(Flex, { vertical: true, gap: 16, "data-testid": "categories-catalog-page", children: [_jsx(Typography.Title, { level: 3, style: { margin: 0 }, children: t(CATEGORIES_I18N_KEYS.catalogTitle) }), _jsx(CategoryCarouselStrip, { basePath: base }), _jsx(CategoryTreePane, { basePath: base })] }) }));
12
+ }
13
+ //# sourceMappingURL=CatalogPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CatalogPage.js","sourceRoot":"","sources":["../../src/default/CatalogPage.tsx"],"names":[],"mappings":";AAUA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAOjD,MAAM,UAAU,WAAW,CAAC,KAAuB;IACjD,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACjB,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC;IAEpC,OAAO,CACL,KAAC,mBAAmB,OACd,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,YAE1D,MAAC,IAAI,IAAC,QAAQ,QAAC,GAAG,EAAE,EAAE,iBAAc,yBAAyB,aAC3D,KAAC,UAAU,CAAC,KAAK,IAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,YAC7C,CAAC,CAAC,oBAAoB,CAAC,YAAY,CAAC,GACpB,EACnB,KAAC,qBAAqB,IAAC,QAAQ,EAAE,IAAI,GAAI,EACzC,KAAC,gBAAgB,IAAC,QAAQ,EAAE,IAAI,GAAI,IAC/B,GACa,CACvB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `<CategoryBreadcrumbsBar>` — root → current, as antd breadcrumbs.
3
+ *
4
+ * Every caption goes through `renderCategoryLabel`, which is the one place
5
+ * this skin decides between "translate this" and "print this": a category's
6
+ * `name` is a translation KEY unless the row says `translatable: false`
7
+ * (`catalog/labels.ts`). A skin that called `t()` unconditionally would print
8
+ * the stored string for an untranslatable category twice-wrapped, and one that
9
+ * never called it would show `category.electronics` at a visitor.
10
+ */
11
+ import type { ReactElement } from "react";
12
+ import type { ThemeModeProp } from "./types.js";
13
+ export interface CategoryBreadcrumbsBarProps extends ThemeModeProp {
14
+ readonly slug?: string;
15
+ readonly categoryId?: number | null;
16
+ /** Path prefix for a crumb's link. Default `/c`. */
17
+ readonly basePath?: string;
18
+ }
19
+ export declare function CategoryBreadcrumbsBar(props: CategoryBreadcrumbsBarProps): ReactElement;
20
+ //# sourceMappingURL=CategoryBreadcrumbsBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CategoryBreadcrumbsBar.d.ts","sourceRoot":"","sources":["../../src/default/CategoryBreadcrumbsBar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAQ1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,2BAA4B,SAAQ,aAAa;IAChE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,2BAA2B,GACjC,YAAY,CA+Dd"}
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Breadcrumb, Skeleton, Typography } from "antd";
3
+ import { matchLoad, toFlowError, useDescribeFlowError, useT } from "@stapel/core";
4
+ import { renderCategoryLabel } from "../catalog/labels.js";
5
+ import { CategoryBreadcrumbs } from "../headless/CategoryBreadcrumbs.js";
6
+ import { CATEGORIES_I18N_KEYS } from "../i18n/keys.js";
7
+ import { ErrorAlert } from "./ErrorAlert.js";
8
+ import { CategoriesSkinTheme } from "./theme.js";
9
+ export function CategoryBreadcrumbsBar(props) {
10
+ const t = useT();
11
+ const describe = useDescribeFlowError();
12
+ const base = props.basePath ?? "/c";
13
+ return (_jsx(CategoriesSkinTheme, { ...(props.mode !== undefined ? { mode: props.mode } : {}), children: _jsx(CategoryBreadcrumbs, { ...(props.slug !== undefined ? { slug: props.slug } : {}), ...(props.categoryId !== undefined
14
+ ? { categoryId: props.categoryId }
15
+ : {}), children: (bag) => matchLoad(bag.state, {
16
+ loading: () => (_jsx(Skeleton.Input, { active: true, size: "small", "data-testid": "categories-breadcrumbs-loading" })),
17
+ failed: (error) => (_jsx(ErrorAlert, { testId: "categories-breadcrumbs-failed", error: {
18
+ ...describe(toFlowError(error)),
19
+ message: t(CATEGORIES_I18N_KEYS.catalogLoadFailed),
20
+ } })),
21
+ ready: (crumbs) => bag.unknownSlug ? (_jsx(Typography.Text, { type: "secondary", "data-testid": "categories-breadcrumbs-unknown", children: t(CATEGORIES_I18N_KEYS.categoryUnknownSlug) })) : (_jsx(Breadcrumb, { "data-testid": "categories-breadcrumbs", items: [
22
+ {
23
+ title: t(CATEGORIES_I18N_KEYS.breadcrumbsRoot),
24
+ href: base,
25
+ },
26
+ ...crumbs.map((crumb) => ({
27
+ title: renderCategoryLabel(crumb.label, t),
28
+ ...(crumb.isCurrent
29
+ ? {}
30
+ : { href: `${base}/${crumb.node.category.slug}` }),
31
+ })),
32
+ ] })),
33
+ }) }) }));
34
+ }
35
+ //# sourceMappingURL=CategoryBreadcrumbsBar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CategoryBreadcrumbsBar.js","sourceRoot":"","sources":["../../src/default/CategoryBreadcrumbsBar.tsx"],"names":[],"mappings":";AAWA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,oBAAoB,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAUjD,MAAM,UAAU,sBAAsB,CACpC,KAAkC;IAElC,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACjB,MAAM,QAAQ,GAAG,oBAAoB,EAAE,CAAC;IACxC,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC;IAEpC,OAAO,CACL,KAAC,mBAAmB,OACd,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,YAE1D,KAAC,mBAAmB,OACd,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KACtD,CAAC,KAAK,CAAC,UAAU,KAAK,SAAS;gBACjC,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE;gBAClC,CAAC,CAAC,EAAE,CAAC,YAEN,CAAC,GAAG,EAAE,EAAE,CACP,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE;gBACnB,OAAO,EAAE,GAAG,EAAE,CAAC,CACb,KAAC,QAAQ,CAAC,KAAK,IACb,MAAM,QACN,IAAI,EAAC,OAAO,iBACA,gCAAgC,GAC5C,CACH;gBACD,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CACjB,KAAC,UAAU,IACT,MAAM,EAAC,+BAA+B,EACtC,KAAK,EAAE;wBACL,GAAG,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;wBAC/B,OAAO,EAAE,CAAC,CAAC,oBAAoB,CAAC,iBAAiB,CAAC;qBACnD,GACD,CACH;gBACD,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE,CAChB,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAChB,KAAC,UAAU,CAAC,IAAI,IACd,IAAI,EAAC,WAAW,iBACJ,gCAAgC,YAE3C,CAAC,CAAC,oBAAoB,CAAC,mBAAmB,CAAC,GAC5B,CACnB,CAAC,CAAC,CAAC,CACF,KAAC,UAAU,mBACG,wBAAwB,EACpC,KAAK,EAAE;wBACL;4BACE,KAAK,EAAE,CAAC,CAAC,oBAAoB,CAAC,eAAe,CAAC;4BAC9C,IAAI,EAAE,IAAI;yBACX;wBACD,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;4BACxB,KAAK,EAAE,mBAAmB,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;4BAC1C,GAAG,CAAC,KAAK,CAAC,SAAS;gCACjB,CAAC,CAAC,EAAE;gCACJ,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;yBACrD,CAAC,CAAC;qBACJ,GACD,CACH;aACJ,CAAC,GAEgB,GACF,CACvB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * `<CategoryCarouselStrip>` — the landing page's row of category tiles.
3
+ *
4
+ * The icon references (`carousel_icon` / `catalog_icon`) are OPAQUE STRINGS
5
+ * that the backend deliberately does not resolve ("Decoupled from stapel-cdn:
6
+ * an opaque string, resolved by the host if at all"). This skin therefore
7
+ * renders no `<img>` and builds no URL: it hands the reference to the host
8
+ * through `renderIcon`, and draws nothing when the host does not supply one. A
9
+ * guessed CDN path would be a broken image on every deployment that guessed
10
+ * differently, and a broken image is worse than no image.
11
+ */
12
+ import type { ReactElement, ReactNode } from "react";
13
+ import type { CarouselEntry } from "../headless/CategoryCarousel.js";
14
+ import type { ThemeModeProp } from "./types.js";
15
+ export interface CategoryCarouselStripProps extends ThemeModeProp {
16
+ readonly basePath?: string;
17
+ /** Turn an opaque icon reference into something renderable. Absent means no
18
+ * icon is drawn — see this file's header. */
19
+ readonly renderIcon?: (reference: string, entry: CarouselEntry) => ReactNode;
20
+ }
21
+ export declare function CategoryCarouselStrip(props: CategoryCarouselStripProps): ReactElement;
22
+ //# sourceMappingURL=CategoryCarouselStrip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CategoryCarouselStrip.d.ts","sourceRoot":"","sources":["../../src/default/CategoryCarouselStrip.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAIrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,0BAA2B,SAAQ,aAAa;IAC/D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;iDAC6C;IAC7C,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,KAAK,SAAS,CAAC;CAC9E;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,0BAA0B,GAChC,YAAY,CAsEd"}
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Card, Empty, Flex, Skeleton, Typography } from "antd";
3
+ import { matchList, toFlowError, useDescribeFlowError, useT } from "@stapel/core";
4
+ import { renderCategoryLabel } from "../catalog/labels.js";
5
+ import { CategoryCarousel } from "../headless/CategoryCarousel.js";
6
+ import { CATEGORIES_I18N_KEYS } from "../i18n/keys.js";
7
+ import { ErrorAlert } from "./ErrorAlert.js";
8
+ import { CategoriesSkinTheme } from "./theme.js";
9
+ export function CategoryCarouselStrip(props) {
10
+ const t = useT();
11
+ const describe = useDescribeFlowError();
12
+ return (_jsx(CategoriesSkinTheme, { ...(props.mode !== undefined ? { mode: props.mode } : {}), children: _jsx(CategoryCarousel, { ...(props.basePath !== undefined ? { basePath: props.basePath } : {}), children: (bag) => (_jsxs(Flex, { vertical: true, gap: 8, "data-testid": "categories-carousel", children: [_jsx(Typography.Title, { level: 5, style: { margin: 0 }, children: t(CATEGORIES_I18N_KEYS.carouselTitle) }), matchList(bag.state, {
13
+ loading: () => (_jsxs(Flex, { gap: 8, children: [_jsx(Skeleton.Button, { active: true, "data-testid": "categories-carousel-loading" }), _jsx(Skeleton.Button, { active: true }), _jsx(Skeleton.Button, { active: true })] })),
14
+ failed: (error) => (_jsx(ErrorAlert, { testId: "categories-carousel-failed", error: {
15
+ ...describe(toFlowError(error)),
16
+ message: t(CATEGORIES_I18N_KEYS.carouselLoadFailed),
17
+ } })),
18
+ empty: () => (_jsx(Empty, { "data-testid": "categories-carousel-empty", description: t(CATEGORIES_I18N_KEYS.carouselEmpty) })),
19
+ ready: (entries) => (_jsx(Flex, { gap: 8, wrap: true, "data-testid": "categories-carousel-list", children: entries.map((entry) => (_jsx(Card, { size: "small", hoverable: true, "data-category-slug": entry.category.slug, children: _jsx(Typography.Link, { href: entry.href, children: _jsxs(Flex, { align: "center", gap: 8, children: [entry.icon !== null && props.renderIcon !== undefined
20
+ ? props.renderIcon(entry.icon, entry)
21
+ : null, _jsx("span", { children: renderCategoryLabel(entry.label, t) })] }) }) }, entry.category.id))) })),
22
+ })] })) }) }));
23
+ }
24
+ //# sourceMappingURL=CategoryCarouselStrip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CategoryCarouselStrip.js","sourceRoot":"","sources":["../../src/default/CategoryCarouselStrip.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `<CategoryFeatureList>` — a category's feature SCHEMA, rendered.
3
+ *
4
+ * The display integration with `@stapel/attributes-react` in the direction
5
+ * this pair owns: categories serves the definitions, attributes knows what a
6
+ * definition MEANS. So the type badge is not a string this file invents —
7
+ * `featureType` is attributes-react's single reader of `config.type`, and
8
+ * `BUILTIN_VALUE_EDITOR_TYPES` is its own statement of which types a build can
9
+ * actually draw. A type outside that set is marked here, on the schema, rather
10
+ * than discovered when somebody opens the compose form and finds a missing
11
+ * field.
12
+ *
13
+ * The other direction — values, not definitions — belongs to attributes-react
14
+ * outright: `formatFeatureValue`, `<FeatureBadges>`, `<FeatureValueList>`. A
15
+ * listing card imports those directly and this pair is not in the path.
16
+ */
17
+ import type { ReactElement } from "react";
18
+ import type { ThemeModeProp } from "./types.js";
19
+ export interface CategoryFeatureListProps extends ThemeModeProp {
20
+ readonly categoryId: number | null | undefined;
21
+ }
22
+ export declare function CategoryFeatureList(props: CategoryFeatureListProps): ReactElement;
23
+ //# sourceMappingURL=CategoryFeatureList.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CategoryFeatureList.d.ts","sourceRoot":"","sources":["../../src/default/CategoryFeatureList.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAU1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,wBAAyB,SAAQ,aAAa;IAC7D,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CAChD;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,wBAAwB,GAC9B,YAAY,CA0Fd"}