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