@orion-studios/payload-studio 0.5.0-beta.3 → 0.5.0-beta.30

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 (46) hide show
  1. package/dist/admin/client.js +68 -7
  2. package/dist/admin/client.mjs +68 -7
  3. package/dist/admin/index.d.mts +1 -1
  4. package/dist/admin/index.d.ts +1 -1
  5. package/dist/admin/index.js +37 -0
  6. package/dist/admin/index.mjs +3 -1
  7. package/dist/admin-app/client.d.mts +4 -0
  8. package/dist/admin-app/client.d.ts +4 -0
  9. package/dist/admin-app/client.js +705 -2
  10. package/dist/admin-app/client.mjs +701 -1
  11. package/dist/admin-app/index.d.mts +1 -1
  12. package/dist/admin-app/index.d.ts +1 -1
  13. package/dist/admin-app/index.js +167 -0
  14. package/dist/admin-app/index.mjs +13 -1
  15. package/dist/admin-app/styles.css +127 -0
  16. package/dist/blocks/index.js +193 -3
  17. package/dist/blocks/index.mjs +2 -2
  18. package/dist/{chunk-J7W5EE3B.mjs → chunk-7IGLXLUB.mjs} +37 -0
  19. package/dist/{chunk-ZLLNO5FM.mjs → chunk-GPQPDEB5.mjs} +44 -13
  20. package/dist/{chunk-PC5622T7.mjs → chunk-H7DSTEVT.mjs} +180 -4
  21. package/dist/{chunk-UJFU323N.mjs → chunk-QW24Y4UH.mjs} +18 -8
  22. package/dist/{chunk-ETRRXURT.mjs → chunk-SIL2J5MF.mjs} +14 -0
  23. package/dist/chunk-XVH5SCBD.mjs +234 -0
  24. package/dist/index-BBvk9b9i.d.mts +97 -0
  25. package/dist/index-BBvk9b9i.d.ts +97 -0
  26. package/dist/{index-DbH0Ljwp.d.mts → index-CpG3UHcS.d.mts} +1 -0
  27. package/dist/{index-DbH0Ljwp.d.ts → index-CpG3UHcS.d.ts} +1 -0
  28. package/dist/{index-DJFhANvJ.d.ts → index-Dj21uD_B.d.mts} +5 -2
  29. package/dist/{index-DJFhANvJ.d.mts → index-Dj21uD_B.d.ts} +5 -2
  30. package/dist/index.d.mts +3 -3
  31. package/dist/index.d.ts +3 -3
  32. package/dist/index.js +674 -250
  33. package/dist/index.mjs +11 -11
  34. package/dist/nextjs/index.d.mts +1 -1
  35. package/dist/nextjs/index.d.ts +1 -1
  36. package/dist/nextjs/index.js +276 -13
  37. package/dist/nextjs/index.mjs +4 -1
  38. package/dist/studio-pages/builder.css +25 -1
  39. package/dist/studio-pages/client.js +2063 -1118
  40. package/dist/studio-pages/client.mjs +2063 -1118
  41. package/dist/studio-pages/index.js +18 -7
  42. package/dist/studio-pages/index.mjs +2 -2
  43. package/package.json +2 -2
  44. package/dist/chunk-AAOHJDNS.mjs +0 -67
  45. package/dist/index-BallJs-K.d.mts +0 -43
  46. package/dist/index-BallJs-K.d.ts +0 -43
@@ -1,3 +1,9 @@
1
+ import {
2
+ assertStudioDocumentV1
3
+ } from "./chunk-N67KVM2S.mjs";
4
+ import {
5
+ studioDocumentToLayout
6
+ } from "./chunk-QW24Y4UH.mjs";
1
7
  import {
2
8
  __export
3
9
  } from "./chunk-6BWS3CLP.mjs";
@@ -27,6 +33,24 @@ function createPayloadClient(config) {
27
33
 
28
34
  // src/nextjs/queries/pages.ts
29
35
  import { unstable_cache } from "next/cache";
36
+ var PAGE_QUERY_CACHE_VERSION = "v2-studio-layout-published-only";
37
+ function withStudioDocumentLayout(page) {
38
+ if (!page) {
39
+ return null;
40
+ }
41
+ try {
42
+ const studioDocument = assertStudioDocumentV1(page.studioDocument);
43
+ const compiledLayout = studioDocumentToLayout(studioDocument);
44
+ if (Array.isArray(compiledLayout) && compiledLayout.length > 0) {
45
+ return {
46
+ ...page,
47
+ layout: compiledLayout
48
+ };
49
+ }
50
+ } catch {
51
+ }
52
+ return page;
53
+ }
30
54
  function normalizePath(segments) {
31
55
  if (!segments || segments.length === 0) {
32
56
  return "/";
@@ -35,35 +59,42 @@ function normalizePath(segments) {
35
59
  return cleaned.length > 0 ? `/${cleaned}` : "/";
36
60
  }
37
61
  async function queryPageByPath(payload, path, draft) {
62
+ const pathWhere = {
63
+ path: {
64
+ equals: path
65
+ }
66
+ };
67
+ const publishedWhere = {
68
+ _status: {
69
+ equals: "published"
70
+ }
71
+ };
38
72
  const result = await payload.find({
39
73
  collection: "pages",
40
74
  depth: 2,
41
75
  draft,
42
76
  limit: 1,
43
77
  overrideAccess: false,
44
- where: {
45
- path: {
46
- equals: path
47
- }
48
- }
78
+ where: draft ? pathWhere : { and: [pathWhere, publishedWhere] }
49
79
  });
50
80
  if (result.docs.length > 0) {
51
- return result.docs[0] || null;
81
+ return withStudioDocumentLayout(result.docs[0] || null);
52
82
  }
53
83
  if (path === "/") {
84
+ const homeWhere = {
85
+ slug: {
86
+ equals: "home"
87
+ }
88
+ };
54
89
  const homeResult = await payload.find({
55
90
  collection: "pages",
56
91
  depth: 2,
57
92
  draft,
58
93
  limit: 1,
59
94
  overrideAccess: false,
60
- where: {
61
- slug: {
62
- equals: "home"
63
- }
64
- }
95
+ where: draft ? homeWhere : { and: [homeWhere, publishedWhere] }
65
96
  });
66
- return homeResult.docs[0] || null;
97
+ return withStudioDocumentLayout(homeResult.docs[0] || null);
67
98
  }
68
99
  return null;
69
100
  }
@@ -73,7 +104,7 @@ function createPageQueries(getPayloadClient, contentTag = "website-content") {
73
104
  const payload = await getPayloadClient();
74
105
  return queryPageByPath(payload, path, false);
75
106
  },
76
- ["page-by-path"],
107
+ ["page-by-path", PAGE_QUERY_CACHE_VERSION],
77
108
  { tags: [contentTag] }
78
109
  );
79
110
  async function getPageBySegments(segments, draft = false) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sectionStyleFields
3
- } from "./chunk-ETRRXURT.mjs";
3
+ } from "./chunk-SIL2J5MF.mjs";
4
4
  import {
5
5
  __export
6
6
  } from "./chunk-6BWS3CLP.mjs";
@@ -81,6 +81,36 @@ var BeforeAfterBlock = {
81
81
  relationTo: "media",
82
82
  required: false
83
83
  },
84
+ {
85
+ name: "imageFit",
86
+ type: "select",
87
+ defaultValue: "cover",
88
+ options: [
89
+ { label: "Cover", value: "cover" },
90
+ { label: "Contain", value: "contain" }
91
+ ]
92
+ },
93
+ {
94
+ name: "imageCornerStyle",
95
+ type: "select",
96
+ defaultValue: "rounded",
97
+ options: [
98
+ { label: "Rounded", value: "rounded" },
99
+ { label: "Square", value: "square" }
100
+ ]
101
+ },
102
+ {
103
+ name: "imagePosition",
104
+ type: "select",
105
+ defaultValue: "center",
106
+ options: [
107
+ { label: "Center", value: "center" },
108
+ { label: "Top", value: "top" },
109
+ { label: "Bottom", value: "bottom" },
110
+ { label: "Left", value: "left" },
111
+ { label: "Right", value: "right" }
112
+ ]
113
+ },
84
114
  {
85
115
  name: "description",
86
116
  type: "textarea"
@@ -278,6 +308,36 @@ var FeatureGridBlock = {
278
308
  type: "upload",
279
309
  relationTo: "media",
280
310
  required: false
311
+ },
312
+ {
313
+ name: "imageFit",
314
+ type: "select",
315
+ defaultValue: "cover",
316
+ options: [
317
+ { label: "Cover", value: "cover" },
318
+ { label: "Contain", value: "contain" }
319
+ ]
320
+ },
321
+ {
322
+ name: "imageCornerStyle",
323
+ type: "select",
324
+ defaultValue: "rounded",
325
+ options: [
326
+ { label: "Rounded", value: "rounded" },
327
+ { label: "Square", value: "square" }
328
+ ]
329
+ },
330
+ {
331
+ name: "imagePosition",
332
+ type: "select",
333
+ defaultValue: "center",
334
+ options: [
335
+ { label: "Center", value: "center" },
336
+ { label: "Top", value: "top" },
337
+ { label: "Bottom", value: "bottom" },
338
+ { label: "Left", value: "left" },
339
+ { label: "Right", value: "right" }
340
+ ]
281
341
  }
282
342
  ]
283
343
  },
@@ -404,10 +464,44 @@ var HeroBlock = {
404
464
  relationTo: "media"
405
465
  },
406
466
  {
407
- name: "backgroundImageURL",
408
- type: "text",
467
+ name: "backgroundImageFit",
468
+ type: "select",
469
+ defaultValue: "cover",
470
+ options: [
471
+ { label: "Cover", value: "cover" },
472
+ { label: "Cover (Square)", value: "cover-square" },
473
+ { label: "Contain", value: "contain" },
474
+ { label: "Contain (Square)", value: "contain-square" }
475
+ ],
409
476
  admin: {
410
- description: "Optional external/background image URL override for this hero section."
477
+ description: "How the hero image should be sized within the section."
478
+ }
479
+ },
480
+ {
481
+ name: "backgroundImageCornerStyle",
482
+ type: "select",
483
+ defaultValue: "rounded",
484
+ options: [
485
+ { label: "Rounded", value: "rounded" },
486
+ { label: "Square", value: "square" }
487
+ ],
488
+ admin: {
489
+ description: "How the hero image corners should appear."
490
+ }
491
+ },
492
+ {
493
+ name: "backgroundImagePosition",
494
+ type: "select",
495
+ defaultValue: "center",
496
+ options: [
497
+ { label: "Center", value: "center" },
498
+ { label: "Top", value: "top" },
499
+ { label: "Bottom", value: "bottom" },
500
+ { label: "Left", value: "left" },
501
+ { label: "Right", value: "right" }
502
+ ],
503
+ admin: {
504
+ description: "Where the hero image should anchor inside the section."
411
505
  }
412
506
  },
413
507
  {
@@ -432,6 +526,28 @@ var HeroBlock = {
432
526
  }
433
527
  ]
434
528
  },
529
+ {
530
+ name: "heroHeight",
531
+ type: "select",
532
+ defaultValue: "sm",
533
+ options: [
534
+ {
535
+ label: "Small",
536
+ value: "sm"
537
+ },
538
+ {
539
+ label: "Medium (Half Screen)",
540
+ value: "md"
541
+ },
542
+ {
543
+ label: "Full Screen",
544
+ value: "full"
545
+ }
546
+ ],
547
+ admin: {
548
+ description: "Controls the vertical height of the hero section."
549
+ }
550
+ },
435
551
  ...sectionStyleFields()
436
552
  ]
437
553
  };
@@ -477,6 +593,36 @@ var LogoWallBlock = {
477
593
  relationTo: "media",
478
594
  required: false
479
595
  },
596
+ {
597
+ name: "imageFit",
598
+ type: "select",
599
+ defaultValue: "contain",
600
+ options: [
601
+ { label: "Cover", value: "cover" },
602
+ { label: "Contain", value: "contain" }
603
+ ]
604
+ },
605
+ {
606
+ name: "imageCornerStyle",
607
+ type: "select",
608
+ defaultValue: "rounded",
609
+ options: [
610
+ { label: "Rounded", value: "rounded" },
611
+ { label: "Square", value: "square" }
612
+ ]
613
+ },
614
+ {
615
+ name: "imagePosition",
616
+ type: "select",
617
+ defaultValue: "center",
618
+ options: [
619
+ { label: "Center", value: "center" },
620
+ { label: "Top", value: "top" },
621
+ { label: "Bottom", value: "bottom" },
622
+ { label: "Left", value: "left" },
623
+ { label: "Right", value: "right" }
624
+ ]
625
+ },
480
626
  {
481
627
  name: "href",
482
628
  type: "text"
@@ -527,6 +673,36 @@ var MediaBlock = {
527
673
  }
528
674
  ]
529
675
  },
676
+ {
677
+ name: "imageFit",
678
+ type: "select",
679
+ defaultValue: "cover",
680
+ options: [
681
+ { label: "Cover", value: "cover" },
682
+ { label: "Contain", value: "contain" }
683
+ ]
684
+ },
685
+ {
686
+ name: "imageCornerStyle",
687
+ type: "select",
688
+ defaultValue: "rounded",
689
+ options: [
690
+ { label: "Rounded", value: "rounded" },
691
+ { label: "Square", value: "square" }
692
+ ]
693
+ },
694
+ {
695
+ name: "imagePosition",
696
+ type: "select",
697
+ defaultValue: "center",
698
+ options: [
699
+ { label: "Center", value: "center" },
700
+ { label: "Top", value: "top" },
701
+ { label: "Bottom", value: "bottom" },
702
+ { label: "Left", value: "left" },
703
+ { label: "Right", value: "right" }
704
+ ]
705
+ },
530
706
  ...sectionStyleFields()
531
707
  ]
532
708
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sectionStyleDefaults
3
- } from "./chunk-ETRRXURT.mjs";
3
+ } from "./chunk-SIL2J5MF.mjs";
4
4
  import {
5
5
  __export
6
6
  } from "./chunk-6BWS3CLP.mjs";
@@ -32,6 +32,9 @@ var defaultNodeData = {
32
32
  items: [
33
33
  {
34
34
  description: "Before and after result summary.",
35
+ imageCornerStyle: "rounded",
36
+ imageFit: "cover",
37
+ imagePosition: "center",
35
38
  label: "Project One"
36
39
  }
37
40
  ],
@@ -55,9 +58,9 @@ var defaultNodeData = {
55
58
  featureGrid: {
56
59
  ...withSectionStyleDefaults({}),
57
60
  items: [
58
- { description: "Explain this point.", icon: "01", title: "Feature One" },
59
- { description: "Explain this point.", icon: "02", title: "Feature Two" },
60
- { description: "Explain this point.", icon: "03", title: "Feature Three" }
61
+ { description: "Explain this point.", icon: "01", imageCornerStyle: "rounded", imageFit: "cover", imagePosition: "center", title: "Feature One" },
62
+ { description: "Explain this point.", icon: "02", imageCornerStyle: "rounded", imageFit: "cover", imagePosition: "center", title: "Feature Two" },
63
+ { description: "Explain this point.", icon: "03", imageCornerStyle: "rounded", imageFit: "cover", imagePosition: "center", title: "Feature Three" }
61
64
  ],
62
65
  title: "Section Title",
63
66
  variant: "cards"
@@ -70,7 +73,11 @@ var defaultNodeData = {
70
73
  },
71
74
  hero: {
72
75
  ...withSectionStyleDefaults({}),
73
- backgroundColor: "#124a37",
76
+ backgroundColor: "",
77
+ backgroundImageCornerStyle: "rounded",
78
+ backgroundImageFit: "cover",
79
+ backgroundImagePosition: "center",
80
+ heroHeight: "sm",
74
81
  headline: "New Hero Section",
75
82
  kicker: "Optional kicker",
76
83
  primaryHref: "/contact",
@@ -83,13 +90,16 @@ var defaultNodeData = {
83
90
  media: {
84
91
  ...withSectionStyleDefaults({}),
85
92
  caption: "Add a caption",
93
+ imageCornerStyle: "rounded",
94
+ imageFit: "cover",
95
+ imagePosition: "center",
86
96
  size: "default"
87
97
  },
88
98
  logoWall: withSectionStyleDefaults({
89
99
  items: [
90
- { name: "Trusted Partner 1" },
91
- { name: "Trusted Partner 2" },
92
- { name: "Trusted Partner 3" }
100
+ { imageCornerStyle: "rounded", imageFit: "contain", imagePosition: "center", name: "Trusted Partner 1" },
101
+ { imageCornerStyle: "rounded", imageFit: "contain", imagePosition: "center", name: "Trusted Partner 2" },
102
+ { imageCornerStyle: "rounded", imageFit: "contain", imagePosition: "center", name: "Trusted Partner 3" }
93
103
  ],
94
104
  subtitle: "Trusted by teams and homeowners across Central Texas.",
95
105
  title: "Trusted by Local Organizations"
@@ -7,6 +7,7 @@ var sectionStyleDefaults = {
7
7
  contentGradientPreset: "none",
8
8
  contentGradientTo: "#f4f6f2",
9
9
  contentWidth: "inherit",
10
+ sectionPaddingX: "inherit",
10
11
  sectionBackgroundColor: "#ffffff",
11
12
  sectionBackgroundMode: "none",
12
13
  sectionGradientAngle: "135",
@@ -44,6 +45,19 @@ var sectionStyleFields = () => [
44
45
  type: "select",
45
46
  defaultValue: sectionStyleDefaults.sectionPaddingY,
46
47
  options: [
48
+ { label: "None", value: "none" },
49
+ { label: "Small", value: "sm" },
50
+ { label: "Medium", value: "md" },
51
+ { label: "Large", value: "lg" }
52
+ ]
53
+ },
54
+ {
55
+ name: "sectionPaddingX",
56
+ type: "select",
57
+ defaultValue: sectionStyleDefaults.sectionPaddingX,
58
+ options: [
59
+ { label: "Inherit", value: "inherit" },
60
+ { label: "None", value: "none" },
47
61
  { label: "Small", value: "sm" },
48
62
  { label: "Medium", value: "md" },
49
63
  { label: "Large", value: "lg" }
@@ -0,0 +1,234 @@
1
+ import {
2
+ __export
3
+ } from "./chunk-6BWS3CLP.mjs";
4
+
5
+ // src/admin-app/index.ts
6
+ var admin_app_exports = {};
7
+ __export(admin_app_exports, {
8
+ AdminBreadcrumbs: () => AdminBreadcrumbs,
9
+ AdminPage: () => AdminPage,
10
+ buildAdminPageLinkOptions: () => buildAdminPageLinkOptions,
11
+ buildNestedNavTree: () => buildNestedNavTree,
12
+ getAdminNavRows: () => getAdminNavRows,
13
+ navItemIsActive: () => navItemIsActive,
14
+ normalizeAdminNavInputs: () => normalizeAdminNavInputs,
15
+ normalizeNestedNavItems: () => normalizeNestedNavItems,
16
+ parseAdminHeaderNavFromForm: () => parseAdminHeaderNavFromForm,
17
+ roleCanAccessNav: () => roleCanAccessNav
18
+ });
19
+
20
+ // src/admin-app/components/AdminBreadcrumbs.tsx
21
+ import { jsx, jsxs } from "react/jsx-runtime";
22
+ function AdminBreadcrumbs({ items }) {
23
+ return /* @__PURE__ */ jsx("nav", { "aria-label": "Breadcrumb", className: "orion-admin-breadcrumbs", children: items.map((item, index) => {
24
+ const isLast = index === items.length - 1;
25
+ return /* @__PURE__ */ jsxs("span", { children: [
26
+ item.href && !isLast ? /* @__PURE__ */ jsx("a", { href: item.href, children: item.label }) : /* @__PURE__ */ jsx("span", { children: item.label }),
27
+ !isLast ? /* @__PURE__ */ jsx("span", { className: "orion-admin-breadcrumb-sep", children: "/" }) : null
28
+ ] }, `${item.label}-${index}`);
29
+ }) });
30
+ }
31
+
32
+ // src/admin-app/components/AdminPage.tsx
33
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
34
+ function AdminPage({ title, description, breadcrumbs, actions, children }) {
35
+ return /* @__PURE__ */ jsxs2("div", { className: "orion-admin-page", children: [
36
+ /* @__PURE__ */ jsxs2("div", { className: "orion-admin-page-header", children: [
37
+ /* @__PURE__ */ jsx2(AdminBreadcrumbs, { items: breadcrumbs }),
38
+ /* @__PURE__ */ jsxs2("div", { className: "orion-admin-page-title-row", children: [
39
+ /* @__PURE__ */ jsxs2("div", { children: [
40
+ /* @__PURE__ */ jsx2("h1", { children: title }),
41
+ description ? /* @__PURE__ */ jsx2("p", { children: description }) : null
42
+ ] }),
43
+ actions ? /* @__PURE__ */ jsx2("div", { className: "orion-admin-page-actions", children: actions }) : null
44
+ ] })
45
+ ] }),
46
+ /* @__PURE__ */ jsx2("div", { className: "orion-admin-page-content", children })
47
+ ] });
48
+ }
49
+
50
+ // src/admin-app/nestedNavigation.ts
51
+ var normalizeNestedNavItems = (items) => {
52
+ const deduped = [];
53
+ const seen = /* @__PURE__ */ new Set();
54
+ for (const item of items) {
55
+ const href = typeof item.href === "string" ? item.href.trim() : "";
56
+ const label = typeof item.label === "string" ? item.label.trim() : "";
57
+ const parentHref = typeof item.parentHref === "string" ? item.parentHref.trim() : "";
58
+ if (!href || !label || seen.has(href)) {
59
+ continue;
60
+ }
61
+ seen.add(href);
62
+ deduped.push({
63
+ href,
64
+ label,
65
+ ...parentHref ? { parentHref } : {}
66
+ });
67
+ }
68
+ const hrefs = new Set(deduped.map((item) => item.href));
69
+ return deduped.map((item) => ({
70
+ href: item.href,
71
+ label: item.label,
72
+ ...item.parentHref && item.parentHref !== item.href && hrefs.has(item.parentHref) ? { parentHref: item.parentHref } : {}
73
+ }));
74
+ };
75
+ var buildNestedNavTree = (items) => {
76
+ const childrenByParent = /* @__PURE__ */ new Map();
77
+ const topLevel = [];
78
+ for (const item of items) {
79
+ if (!item.parentHref) {
80
+ topLevel.push(item);
81
+ continue;
82
+ }
83
+ const children = childrenByParent.get(item.parentHref) || [];
84
+ children.push(item);
85
+ childrenByParent.set(item.parentHref, children);
86
+ }
87
+ if (topLevel.length === 0 && items.length > 0) {
88
+ return {
89
+ topLevel: items.map((item) => ({ href: item.href, label: item.label })),
90
+ childrenByParent: /* @__PURE__ */ new Map()
91
+ };
92
+ }
93
+ return { childrenByParent, topLevel };
94
+ };
95
+
96
+ // src/admin-app/navigationLinks.ts
97
+ var fallbackHomeOption = {
98
+ href: "/",
99
+ label: "Home",
100
+ title: "Home"
101
+ };
102
+ var buildAdminPageLinkOptions = (pages) => {
103
+ const options = pages.map((page) => {
104
+ const href = typeof page.path === "string" ? page.path.trim() : "";
105
+ const title = typeof page.title === "string" && page.title.trim().length > 0 ? page.title.trim() : "Untitled Page";
106
+ if (!href) {
107
+ return null;
108
+ }
109
+ const depth = href === "/" ? 0 : href.split("/").filter(Boolean).length;
110
+ const indentLevel = Math.max(depth - 1, 0);
111
+ const indent = indentLevel > 0 ? `${"\u21B3 ".repeat(indentLevel)}` : "";
112
+ return {
113
+ href,
114
+ label: `${indent}${title}`,
115
+ title
116
+ };
117
+ }).filter((option) => option !== null).sort((a, b) => {
118
+ if (a.href === "/" && b.href !== "/") return -1;
119
+ if (b.href === "/" && a.href !== "/") return 1;
120
+ return a.href.localeCompare(b.href);
121
+ });
122
+ if (options.length === 0) {
123
+ return [fallbackHomeOption];
124
+ }
125
+ return options;
126
+ };
127
+ var getAdminNavRows = (navItems, pageOptions, minRows = 6, maxRows = 20) => {
128
+ const preferredRows = Math.max(navItems.length + 2, Math.min(8, pageOptions.length));
129
+ return Math.min(Math.max(minRows, preferredRows), maxRows);
130
+ };
131
+ var normalizeAdminNavInputs = (rows, pageOptions) => {
132
+ const allowedLinks = new Map(pageOptions.map((option) => [option.href, option.title]));
133
+ const deduped = [];
134
+ const seen = /* @__PURE__ */ new Set();
135
+ for (const row of rows) {
136
+ const href = typeof row.href === "string" ? row.href.trim() : "";
137
+ const defaultLabel = allowedLinks.get(href);
138
+ const explicitLabel = typeof row.label === "string" ? row.label.trim() : "";
139
+ const parentHref = typeof row.parentHref === "string" ? row.parentHref.trim() : "";
140
+ if (!href || !defaultLabel || seen.has(href)) {
141
+ continue;
142
+ }
143
+ seen.add(href);
144
+ deduped.push({
145
+ href,
146
+ label: explicitLabel.length > 0 ? explicitLabel : defaultLabel,
147
+ ...parentHref.length > 0 ? { parentHref } : {}
148
+ });
149
+ }
150
+ const hrefs = new Set(deduped.map((item) => item.href));
151
+ const parentByHref = new Map(deduped.map((item) => [item.href, item.parentHref || ""]));
152
+ const createsCycle = (href, parentHref) => {
153
+ const visited = /* @__PURE__ */ new Set([href]);
154
+ let current = parentHref;
155
+ while (current) {
156
+ if (visited.has(current)) {
157
+ return true;
158
+ }
159
+ visited.add(current);
160
+ current = parentByHref.get(current) || "";
161
+ }
162
+ return false;
163
+ };
164
+ return deduped.map((item) => {
165
+ const parentHref = item.parentHref;
166
+ if (!parentHref || parentHref === item.href || !hrefs.has(parentHref) || createsCycle(item.href, parentHref)) {
167
+ return {
168
+ href: item.href,
169
+ label: item.label
170
+ };
171
+ }
172
+ return item;
173
+ });
174
+ };
175
+ var parseAdminHeaderNavFromForm = (formData, pageOptions, maxRows = 24) => {
176
+ const serialized = String(formData.get("navItemsState") || "").trim();
177
+ if (serialized.length > 0) {
178
+ try {
179
+ const parsed = JSON.parse(serialized);
180
+ if (Array.isArray(parsed)) {
181
+ return normalizeAdminNavInputs(parsed.slice(0, maxRows), pageOptions);
182
+ }
183
+ } catch {
184
+ }
185
+ }
186
+ const rawCount = Number(String(formData.get("navCount") || "0"));
187
+ const navCount = Number.isFinite(rawCount) ? Math.max(0, Math.min(rawCount, maxRows)) : 0;
188
+ const navRows = [];
189
+ for (let index = 0; index < navCount; index += 1) {
190
+ const href = String(formData.get(`navPage_${index}`) || "").trim();
191
+ const label = String(formData.get(`navLabel_${index}`) || "").trim();
192
+ const parentHref = String(formData.get(`navParentHref_${index}`) || "").trim();
193
+ navRows.push({
194
+ href,
195
+ label,
196
+ parentHref
197
+ });
198
+ }
199
+ if (navRows.length > 0) {
200
+ return normalizeAdminNavInputs(navRows, pageOptions);
201
+ }
202
+ return [];
203
+ };
204
+
205
+ // src/admin-app/routeRegistry.ts
206
+ var roleCanAccessNav = (role, item) => {
207
+ if (!item.roles || item.roles.length === 0) {
208
+ return true;
209
+ }
210
+ if (!role) {
211
+ return false;
212
+ }
213
+ return item.roles.includes(role);
214
+ };
215
+ var navItemIsActive = (pathname, item) => {
216
+ if (item.href === "/admin") {
217
+ return pathname === "/admin";
218
+ }
219
+ return item.matchPrefixes.some((prefix) => pathname.startsWith(prefix));
220
+ };
221
+
222
+ export {
223
+ AdminBreadcrumbs,
224
+ AdminPage,
225
+ normalizeNestedNavItems,
226
+ buildNestedNavTree,
227
+ buildAdminPageLinkOptions,
228
+ getAdminNavRows,
229
+ normalizeAdminNavInputs,
230
+ parseAdminHeaderNavFromForm,
231
+ roleCanAccessNav,
232
+ navItemIsActive,
233
+ admin_app_exports
234
+ };