@fluid-app/portal-sdk 0.1.245 → 0.1.246

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.
package/dist/index.cjs CHANGED
@@ -1867,6 +1867,68 @@ function SdkCompanySwitcher() {
1867
1867
  });
1868
1868
  }
1869
1869
  //#endregion
1870
+ //#region ../core/src/navigation/slug-utils.ts
1871
+ /**
1872
+ * Extract all slugs from a navigation tree, sorted by segment count descending.
1873
+ * Longest slugs first enables greedy prefix matching (e.g. "share/playlists"
1874
+ * is checked before "share").
1875
+ */
1876
+ function collectNavSlugs(items) {
1877
+ const slugs = [];
1878
+ for (const item of items) {
1879
+ if (item.slug) slugs.push(normalizeSlug(item.slug));
1880
+ for (const child of item.children ?? []) if (child.slug) slugs.push(normalizeSlug(child.slug));
1881
+ }
1882
+ return [...slugs].sort((a, b) => b.split("/").length - a.split("/").length);
1883
+ }
1884
+ /**
1885
+ * Find the longest registered nav slug that is a prefix of `fullSlug`.
1886
+ * Uses segment-boundary checking to prevent "shop" from matching "shopping".
1887
+ */
1888
+ function matchSlugPrefix(fullSlug, navSlugs) {
1889
+ const normalized = normalizeSlug(fullSlug);
1890
+ if (!normalized) return void 0;
1891
+ for (const navSlug of navSlugs) {
1892
+ if (normalized === navSlug) return {
1893
+ matchedSlug: navSlug,
1894
+ rest: ""
1895
+ };
1896
+ if (normalized.startsWith(navSlug + "/")) return {
1897
+ matchedSlug: navSlug,
1898
+ rest: normalized.slice(navSlug.length + 1)
1899
+ };
1900
+ }
1901
+ }
1902
+ /**
1903
+ * Extract the slug portion from a full pathname by stripping the basePath prefix.
1904
+ * Returns an empty string when the pathname equals the basePath exactly.
1905
+ *
1906
+ * Examples:
1907
+ * extractSlugFromPathname("/contacts/123", "/") → "contacts/123"
1908
+ * extractSlugFromPathname("/portal/contacts", "/portal") → "contacts"
1909
+ * extractSlugFromPathname("/portal", "/portal") → ""
1910
+ * extractSlugFromPathname("/", "/") → ""
1911
+ */
1912
+ function extractSlugFromPathname(pathname, basePath) {
1913
+ if (basePath === "/") return pathname.replace(/^\//, "");
1914
+ if (pathname.startsWith(basePath)) return pathname.slice(basePath.length).replace(/^\//, "");
1915
+ return pathname.replace(/^\//, "");
1916
+ }
1917
+ /**
1918
+ * Generate a URL-safe slug from a human-readable name. Mirrors the admin
1919
+ * builder's `generateSlugFromName` so a navigation item authored as
1920
+ * `slugifyName(screen.name)` can be matched back to the screen at runtime
1921
+ * even when the screen's own `slug` is opaque (e.g. `screen-{uuid}`).
1922
+ */
1923
+ function slugifyName(name) {
1924
+ return name.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
1925
+ }
1926
+ function isSlugInSection(item, currentSlug, navSlugs) {
1927
+ const baseSlug = matchSlugPrefix(currentSlug, navSlugs)?.matchedSlug ?? normalizeSlug(currentSlug);
1928
+ if (normalizeSlug(item.slug) === baseSlug) return true;
1929
+ return item.children?.some((child) => normalizeSlug(child.slug) === baseSlug) ?? false;
1930
+ }
1931
+ //#endregion
1870
1932
  //#region src/shell/go-back-or-home.ts
1871
1933
  /**
1872
1934
  * Navigate one step back in the browser history if anything is on the stack;
@@ -2082,7 +2144,14 @@ function PageRouter({ currentSlug, currentNavItem, customPages, screens, baseSlu
2082
2144
  }, [screens]);
2083
2145
  const screenBySlug = (0, react.useMemo)(() => {
2084
2146
  if (!screens || screens.length === 0) return void 0;
2085
- return new Map(screens.filter((s) => s.slug).map((s) => [s.slug, s]));
2147
+ const map = /* @__PURE__ */ new Map();
2148
+ for (const s of screens) if (s.slug) map.set(s.slug, s);
2149
+ for (const s of screens) {
2150
+ if (!s.name) continue;
2151
+ const nameSlug = slugifyName(s.name);
2152
+ if (nameSlug && !map.has(nameSlug)) map.set(nameSlug, s);
2153
+ }
2154
+ return map;
2086
2155
  }, [screens]);
2087
2156
  const builderScreen = (0, react.useMemo)(() => {
2088
2157
  if (currentNavItem?.screen_id && screenById) {
@@ -2124,59 +2193,6 @@ function PageRouter({ currentSlug, currentNavItem, customPages, screens, baseSlu
2124
2193
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScreenNotFound, { name: currentNavItem?.label ?? currentSlug });
2125
2194
  }
2126
2195
  //#endregion
2127
- //#region ../core/src/navigation/slug-utils.ts
2128
- /**
2129
- * Extract all slugs from a navigation tree, sorted by segment count descending.
2130
- * Longest slugs first enables greedy prefix matching (e.g. "share/playlists"
2131
- * is checked before "share").
2132
- */
2133
- function collectNavSlugs(items) {
2134
- const slugs = [];
2135
- for (const item of items) {
2136
- if (item.slug) slugs.push(normalizeSlug(item.slug));
2137
- for (const child of item.children ?? []) if (child.slug) slugs.push(normalizeSlug(child.slug));
2138
- }
2139
- return [...slugs].sort((a, b) => b.split("/").length - a.split("/").length);
2140
- }
2141
- /**
2142
- * Find the longest registered nav slug that is a prefix of `fullSlug`.
2143
- * Uses segment-boundary checking to prevent "shop" from matching "shopping".
2144
- */
2145
- function matchSlugPrefix(fullSlug, navSlugs) {
2146
- const normalized = normalizeSlug(fullSlug);
2147
- if (!normalized) return void 0;
2148
- for (const navSlug of navSlugs) {
2149
- if (normalized === navSlug) return {
2150
- matchedSlug: navSlug,
2151
- rest: ""
2152
- };
2153
- if (normalized.startsWith(navSlug + "/")) return {
2154
- matchedSlug: navSlug,
2155
- rest: normalized.slice(navSlug.length + 1)
2156
- };
2157
- }
2158
- }
2159
- /**
2160
- * Extract the slug portion from a full pathname by stripping the basePath prefix.
2161
- * Returns an empty string when the pathname equals the basePath exactly.
2162
- *
2163
- * Examples:
2164
- * extractSlugFromPathname("/contacts/123", "/") → "contacts/123"
2165
- * extractSlugFromPathname("/portal/contacts", "/portal") → "contacts"
2166
- * extractSlugFromPathname("/portal", "/portal") → ""
2167
- * extractSlugFromPathname("/", "/") → ""
2168
- */
2169
- function extractSlugFromPathname(pathname, basePath) {
2170
- if (basePath === "/") return pathname.replace(/^\//, "");
2171
- if (pathname.startsWith(basePath)) return pathname.slice(basePath.length).replace(/^\//, "");
2172
- return pathname.replace(/^\//, "");
2173
- }
2174
- function isSlugInSection(item, currentSlug, navSlugs) {
2175
- const baseSlug = matchSlugPrefix(currentSlug, navSlugs)?.matchedSlug ?? normalizeSlug(currentSlug);
2176
- if (normalizeSlug(item.slug) === baseSlug) return true;
2177
- return item.children?.some((child) => normalizeSlug(child.slug) === baseSlug) ?? false;
2178
- }
2179
- //#endregion
2180
2196
  //#region src/shell/AppShellErrorBoundary.tsx
2181
2197
  var AppShellErrorBoundary = class extends react.Component {
2182
2198
  state = { error: null };
@@ -2430,7 +2446,7 @@ function AppShell({ appData: appDataProp, navigation: navigationProp, customPage
2430
2446
  const navSlugs = (0, react.useMemo)(() => collectNavSlugs(filteredNavItems), [filteredNavItems]);
2431
2447
  const allNavSlugs = (0, react.useMemo)(() => {
2432
2448
  const mobileSlugs = collectNavSlugs(filteredMobileNavItems);
2433
- return Array.from(new Set([...navSlugs, ...mobileSlugs]));
2449
+ return Array.from(new Set([...navSlugs, ...mobileSlugs])).sort((a, b) => b.split("/").length - a.split("/").length);
2434
2450
  }, [navSlugs, filteredMobileNavItems]);
2435
2451
  const [themeMode, setThemeMode] = (0, react.useState)(getInitialThemeMode);
2436
2452
  const handleThemeModeChange = (0, react.useCallback)((mode) => {
@@ -2536,11 +2552,11 @@ function AppShell({ appData: appDataProp, navigation: navigationProp, customPage
2536
2552
  if (typeof orderToken === "string" && orderToken) handleNavigate(`orders/${orderToken}`);
2537
2553
  }, [handleNavigate]);
2538
2554
  const resolvedSidebarFooter = (0, react.useMemo)(() => sidebarFooter !== void 0 ? sidebarFooter : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SdkLogoutButton, { onLogout: logout }), [sidebarFooter, logout]);
2539
- const slugMatch = (0, react.useMemo)(() => matchSlugPrefix(activeSlug, navSlugs), [activeSlug, navSlugs]);
2555
+ const slugMatch = (0, react.useMemo)(() => matchSlugPrefix(activeSlug, allNavSlugs), [activeSlug, allNavSlugs]);
2540
2556
  const baseSlug = slugMatch?.matchedSlug ?? normalizeSlug(activeSlug);
2541
2557
  const restParams = slugMatch?.rest ?? "";
2542
2558
  const mobileSecondLevelTabs = findCurrentSection(filteredMobileNavItems, baseSlug)?.children ?? [];
2543
- const currentNavItem = findNavItem(filteredNavItems, baseSlug);
2559
+ const currentNavItem = findNavItem(filteredNavItems, baseSlug) ?? findNavItem(filteredMobileNavItems, baseSlug);
2544
2560
  const screenTitle = currentNavItem?.label || screens?.find((s) => s.id === currentNavItem?.screen_id)?.name || void 0;
2545
2561
  const content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppShellErrorBoundary, { children: typeof children === "function" ? children({
2546
2562
  currentSlug: activeSlug,