@flamingo-stack/openframe-frontend-core 0.0.591 → 0.0.592
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/{chunk-BQE4HTDR.js → chunk-2JNCZDJR.js} +2 -2
- package/dist/{chunk-YEKPHSWC.cjs → chunk-3JOBM57M.cjs} +22 -22
- package/dist/{chunk-YEKPHSWC.cjs.map → chunk-3JOBM57M.cjs.map} +1 -1
- package/dist/{chunk-WKOJL6A2.cjs → chunk-5LJZWTOE.cjs} +10 -10
- package/dist/{chunk-WKOJL6A2.cjs.map → chunk-5LJZWTOE.cjs.map} +1 -1
- package/dist/{chunk-NXYOUBOL.cjs → chunk-5MHDMDPX.cjs} +21 -21
- package/dist/{chunk-NXYOUBOL.cjs.map → chunk-5MHDMDPX.cjs.map} +1 -1
- package/dist/{chunk-U2XRBHBX.js → chunk-6GMMR667.js} +9 -9
- package/dist/{chunk-WIMGAPVH.js → chunk-ASFEUMFW.js} +4 -4
- package/dist/{chunk-4UP2SGSO.js → chunk-AUA6OSU7.js} +3 -3
- package/dist/{chunk-YMAHVHCX.cjs → chunk-BZEWPW5A.cjs} +3 -3
- package/dist/{chunk-YMAHVHCX.cjs.map → chunk-BZEWPW5A.cjs.map} +1 -1
- package/dist/{chunk-SW3CZ2KS.cjs → chunk-COY57SZP.cjs} +129 -84
- package/dist/chunk-COY57SZP.cjs.map +1 -0
- package/dist/{chunk-VGBTCUPG.js → chunk-CRW4WRMH.js} +2 -2
- package/dist/{chunk-E455M3WJ.js → chunk-F543W3F2.js} +4 -4
- package/dist/{chunk-ZZF6Z7KB.cjs → chunk-FKXDDJEQ.cjs} +11 -11
- package/dist/{chunk-ZZF6Z7KB.cjs.map → chunk-FKXDDJEQ.cjs.map} +1 -1
- package/dist/{chunk-RGCBUNMJ.js → chunk-FSCQOWXI.js} +2 -2
- package/dist/{chunk-USDG6IWK.cjs → chunk-FVADHGDP.cjs} +7 -7
- package/dist/{chunk-USDG6IWK.cjs.map → chunk-FVADHGDP.cjs.map} +1 -1
- package/dist/{chunk-OKWGSJAI.js → chunk-GGQZBSFN.js} +1 -1
- package/dist/chunk-GGQZBSFN.js.map +1 -0
- package/dist/{chunk-RNXY35DZ.cjs → chunk-H64ZHSYR.cjs} +1 -1
- package/dist/chunk-H64ZHSYR.cjs.map +1 -0
- package/dist/{chunk-OS72RV57.cjs → chunk-HGEA6PW7.cjs} +3 -3
- package/dist/{chunk-OS72RV57.cjs.map → chunk-HGEA6PW7.cjs.map} +1 -1
- package/dist/{chunk-ZFHIMK4W.js → chunk-JIINHJIT.js} +5 -5
- package/dist/{chunk-CWO3YFVZ.js → chunk-LRQCKSZC.js} +2 -2
- package/dist/{chunk-LL6J6N76.js → chunk-LXD2QJFG.js} +3 -3
- package/dist/{chunk-2TJSDSXW.js → chunk-MOHSA65M.js} +2 -2
- package/dist/{chunk-JHVCH3CK.cjs → chunk-NEFNARFF.cjs} +6 -6
- package/dist/{chunk-JHVCH3CK.cjs.map → chunk-NEFNARFF.cjs.map} +1 -1
- package/dist/{chunk-XK7SLEZB.cjs → chunk-NICIJRZ4.cjs} +3 -3
- package/dist/{chunk-XK7SLEZB.cjs.map → chunk-NICIJRZ4.cjs.map} +1 -1
- package/dist/{chunk-LUWFLC3T.js → chunk-NYH3NVUF.js} +3 -3
- package/dist/{chunk-BOED5ISE.js → chunk-NZQYZKIN.js} +6 -6
- package/dist/{chunk-AZ4A4RBL.cjs → chunk-O4VGEJ7I.cjs} +60 -60
- package/dist/{chunk-AZ4A4RBL.cjs.map → chunk-O4VGEJ7I.cjs.map} +1 -1
- package/dist/{chunk-B7ZCBGUN.cjs → chunk-O4VZOA2Q.cjs} +3 -3
- package/dist/{chunk-B7ZCBGUN.cjs.map → chunk-O4VZOA2Q.cjs.map} +1 -1
- package/dist/{chunk-QVAKJB64.js → chunk-OO6AMBHV.js} +2 -2
- package/dist/{chunk-U3V4TPT5.js → chunk-R25AUM2R.js} +3 -3
- package/dist/{chunk-VGY7D6LG.cjs → chunk-STYNZ4QM.cjs} +3 -3
- package/dist/{chunk-VGY7D6LG.cjs.map → chunk-STYNZ4QM.cjs.map} +1 -1
- package/dist/{chunk-FHKP4H6N.cjs → chunk-TQMZFXWZ.cjs} +5 -5
- package/dist/{chunk-FHKP4H6N.cjs.map → chunk-TQMZFXWZ.cjs.map} +1 -1
- package/dist/{chunk-AMCARR6Y.cjs → chunk-V7RUX5CX.cjs} +15 -15
- package/dist/{chunk-AMCARR6Y.cjs.map → chunk-V7RUX5CX.cjs.map} +1 -1
- package/dist/{chunk-CXILZQFT.cjs → chunk-W2RUBABW.cjs} +19 -19
- package/dist/{chunk-CXILZQFT.cjs.map → chunk-W2RUBABW.cjs.map} +1 -1
- package/dist/{chunk-DKVNW2QD.cjs → chunk-WOGLPNK3.cjs} +3 -3
- package/dist/{chunk-DKVNW2QD.cjs.map → chunk-WOGLPNK3.cjs.map} +1 -1
- package/dist/{chunk-HV6MO4HG.cjs → chunk-WP3AVAMP.cjs} +37 -37
- package/dist/{chunk-HV6MO4HG.cjs.map → chunk-WP3AVAMP.cjs.map} +1 -1
- package/dist/{chunk-ZYA6274A.js → chunk-WVHQQ3NY.js} +87 -42
- package/dist/chunk-WVHQQ3NY.js.map +1 -0
- package/dist/{chunk-WMZ5AUTB.js → chunk-WZJOILPU.js} +2 -2
- package/dist/{chunk-ALTSOKHP.js → chunk-XHDBU5EI.js} +2 -2
- package/dist/{chunk-T6RY2MC6.js → chunk-XNGE5EE5.js} +8 -8
- package/dist/{chunk-5SJSG5RV.cjs → chunk-YBENO4XG.cjs} +8 -8
- package/dist/{chunk-5SJSG5RV.cjs.map → chunk-YBENO4XG.cjs.map} +1 -1
- package/dist/{chunk-OBJEFMOI.cjs → chunk-ZT2Z2BYH.cjs} +66 -66
- package/dist/{chunk-OBJEFMOI.cjs.map → chunk-ZT2Z2BYH.cjs.map} +1 -1
- package/dist/{chunk-BATTVHZT.js → chunk-ZZHHKE2Q.js} +3 -3
- package/dist/components/case-studies/index.cjs +10 -10
- package/dist/components/case-studies/index.js +4 -4
- package/dist/components/contact/index.cjs +5 -5
- package/dist/components/contact/index.js +4 -4
- package/dist/components/docs/index.cjs +8 -8
- package/dist/components/docs/index.js +7 -7
- package/dist/components/embeds/index.cjs +5 -5
- package/dist/components/embeds/index.js +4 -4
- package/dist/components/faq/index.cjs +6 -6
- package/dist/components/faq/index.js +5 -5
- package/dist/components/features/index.cjs +4 -4
- package/dist/components/features/index.js +3 -3
- package/dist/components/help-center-pages/index.cjs +27 -27
- package/dist/components/help-center-pages/index.js +18 -18
- package/dist/components/index.cjs +40 -40
- package/dist/components/index.js +18 -18
- package/dist/components/meeting-scheduler/index.cjs +31 -31
- package/dist/components/meeting-scheduler/index.js +5 -5
- package/dist/components/navigation/index.cjs +7 -7
- package/dist/components/navigation/index.js +6 -6
- package/dist/components/onboarding-guides/index.cjs +8 -8
- package/dist/components/onboarding-guides/index.js +7 -7
- package/dist/components/related-content/index.cjs +4 -4
- package/dist/components/related-content/index.js +3 -3
- package/dist/components/tickets/index.cjs +9 -9
- package/dist/components/tickets/index.js +8 -8
- package/dist/components/ui/data-table/data-table-body.d.ts +18 -1
- package/dist/components/ui/data-table/data-table-body.d.ts.map +1 -1
- package/dist/components/ui/data-table/data-table-row.d.ts +8 -1
- package/dist/components/ui/data-table/data-table-row.d.ts.map +1 -1
- package/dist/components/ui/data-table/data-table-skeleton.d.ts +8 -1
- package/dist/components/ui/data-table/data-table-skeleton.d.ts.map +1 -1
- package/dist/components/ui/data-table/index.d.ts +1 -1
- package/dist/components/ui/entity-image.d.ts +12 -1
- package/dist/components/ui/entity-image.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +4 -4
- package/dist/components/ui/index.js +3 -3
- package/dist/hooks/index.cjs +3 -3
- package/dist/hooks/index.js +2 -2
- package/dist/index.cjs +8 -8
- package/dist/index.js +7 -7
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/search-params.cjs.map +1 -1
- package/dist/utils/search-params.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/data-table-row-height.test.tsx +139 -0
- package/src/__tests__/mlg-platform-leaves.test.ts +1 -1
- package/src/components/ui/data-table/data-table-body.tsx +37 -7
- package/src/components/ui/data-table/data-table-row.tsx +24 -6
- package/src/components/ui/data-table/data-table-skeleton.tsx +24 -3
- package/src/components/ui/entity-image.tsx +19 -3
- package/src/hooks/state/__tests__/use-api-params.test.ts +1 -1
- package/src/utils/search-params.ts +4 -4
- package/dist/chunk-OKWGSJAI.js.map +0 -1
- package/dist/chunk-RNXY35DZ.cjs.map +0 -1
- package/dist/chunk-SW3CZ2KS.cjs.map +0 -1
- package/dist/chunk-ZYA6274A.js.map +0 -1
- /package/dist/{chunk-BQE4HTDR.js.map → chunk-2JNCZDJR.js.map} +0 -0
- /package/dist/{chunk-U2XRBHBX.js.map → chunk-6GMMR667.js.map} +0 -0
- /package/dist/{chunk-WIMGAPVH.js.map → chunk-ASFEUMFW.js.map} +0 -0
- /package/dist/{chunk-4UP2SGSO.js.map → chunk-AUA6OSU7.js.map} +0 -0
- /package/dist/{chunk-VGBTCUPG.js.map → chunk-CRW4WRMH.js.map} +0 -0
- /package/dist/{chunk-E455M3WJ.js.map → chunk-F543W3F2.js.map} +0 -0
- /package/dist/{chunk-RGCBUNMJ.js.map → chunk-FSCQOWXI.js.map} +0 -0
- /package/dist/{chunk-ZFHIMK4W.js.map → chunk-JIINHJIT.js.map} +0 -0
- /package/dist/{chunk-CWO3YFVZ.js.map → chunk-LRQCKSZC.js.map} +0 -0
- /package/dist/{chunk-LL6J6N76.js.map → chunk-LXD2QJFG.js.map} +0 -0
- /package/dist/{chunk-2TJSDSXW.js.map → chunk-MOHSA65M.js.map} +0 -0
- /package/dist/{chunk-LUWFLC3T.js.map → chunk-NYH3NVUF.js.map} +0 -0
- /package/dist/{chunk-BOED5ISE.js.map → chunk-NZQYZKIN.js.map} +0 -0
- /package/dist/{chunk-QVAKJB64.js.map → chunk-OO6AMBHV.js.map} +0 -0
- /package/dist/{chunk-U3V4TPT5.js.map → chunk-R25AUM2R.js.map} +0 -0
- /package/dist/{chunk-WMZ5AUTB.js.map → chunk-WZJOILPU.js.map} +0 -0
- /package/dist/{chunk-ALTSOKHP.js.map → chunk-XHDBU5EI.js.map} +0 -0
- /package/dist/{chunk-T6RY2MC6.js.map → chunk-XNGE5EE5.js.map} +0 -0
- /package/dist/{chunk-BATTVHZT.js.map → chunk-ZZHHKE2Q.js.map} +0 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
var _chunkN6CULJYRcjs = require('./chunk-N6CULJYR.cjs');
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
var
|
|
7
|
+
var _chunkH64ZHSYRcjs = require('./chunk-H64ZHSYR.cjs');
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
|
|
@@ -164,7 +164,7 @@ function UnifiedPagination({
|
|
|
164
164
|
if (syncUrl) {
|
|
165
165
|
const params = new URLSearchParams(searchParams.toString());
|
|
166
166
|
params.set("page", page.toString());
|
|
167
|
-
window.history.replaceState(null, "",
|
|
167
|
+
window.history.replaceState(null, "", _chunkH64ZHSYRcjs.withQuery.call(void 0, pathname, params.toString()));
|
|
168
168
|
}
|
|
169
169
|
setTimeout(() => {
|
|
170
170
|
window.scrollTo({
|
|
@@ -185,4 +185,4 @@ function UnifiedPagination({
|
|
|
185
185
|
|
|
186
186
|
|
|
187
187
|
exports.EmptyState = EmptyState; exports.UnifiedPagination = UnifiedPagination;
|
|
188
|
-
//# sourceMappingURL=chunk-
|
|
188
|
+
//# sourceMappingURL=chunk-O4VZOA2Q.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-B7ZCBGUN.cjs","../src/components/empty-state.tsx","../src/components/unified-pagination.tsx"],"names":["jsx"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-B7ZCBGUN.cjs","sourcesContent":[null,"'use client';\n\nimport { Search, FileText, Package } from 'lucide-react';\nimport { useRouter } from '../embed-shims/next-navigation';\nimport { Button } from './ui/button';\n\nexport interface EmptyStateProps {\n type: 'vendors' | 'posts' | 'search' | 'generic';\n title?: string;\n description?: string;\n showBackButton?: boolean;\n onGoBack?: () => void;\n backButtonText?: string;\n // New CTA properties\n showCTA?: boolean;\n ctaText?: string;\n onCtaClick?: () => void;\n /**\n * Link-CTA mode: render the CTA as a navigation link (`href` — plain or\n * `mailto:`). Wins over `onCtaClick`, and SHORT-CIRCUITS the smart-CTA\n * path-sniffing below (which reads `window.location.pathname` — an\n * SSR/client divergence server-rendered pages must never enter).\n */\n ctaHref?: string;\n ctaVariant?: 'primary' | 'secondary';\n}\n\nexport function EmptyState({\n type,\n title,\n description,\n showBackButton = false,\n onGoBack,\n backButtonText = 'Go Back',\n showCTA = true,\n ctaText,\n onCtaClick,\n ctaHref,\n ctaVariant = 'primary',\n}: EmptyStateProps) {\n const router = useRouter();\n\n // Default content based on type\n const getDefaultContent = () => {\n switch (type) {\n case 'vendors':\n return {\n icon: <Package className=\"h-full w-full\" />,\n title: 'No vendors found',\n description:\n \"We couldn't find any vendors matching your criteria. Try adjusting your filters or search terms.\",\n };\n case 'posts':\n return {\n icon: <FileText className=\"h-full w-full\" />,\n title: 'No articles found',\n description:\n \"We couldn't find any articles matching your criteria. Try different categories, tags, or search terms.\",\n };\n case 'search':\n return {\n icon: <Search className=\"h-full w-full\" />,\n title: 'No results found',\n description: \"Your search didn't return any results. Try different keywords or browse our categories.\",\n };\n default:\n return {\n icon: <Search className=\"h-full w-full\" />,\n title: 'Nothing found',\n description: \"We couldn't find what you're looking for. Try adjusting your search or filters.\",\n };\n }\n };\n\n // Smart CTA logic based on context\n const getSmartCTA = () => {\n // If custom CTA is provided, use it\n if (ctaText && onCtaClick) {\n return {\n text: ctaText,\n action: onCtaClick,\n };\n }\n\n // Check if we're on the client side\n const isClient = typeof window !== 'undefined';\n const currentPath = isClient ? window.location.pathname : '';\n\n // Surgical filter reset (promoted from the hub's diverged clone during\n // the EmptyState convergence): delete ONLY the filter params the surface\n // owns — nuking the whole query string threw away unrelated state — and\n // use `replace` with `scroll: false` so resetting filters neither adds a\n // history entry nor jumps the page.\n const resetParams = (paramsToDelete: string[]) => {\n if (!isClient) return;\n const params = new URLSearchParams(window.location.search);\n for (const p of paramsToDelete) params.delete(p);\n const queryString = params.toString();\n // Keep the hash — a filter reset must not clear an unrelated\n // deep-link anchor (e.g. #delivery-123).\n const hash = window.location.hash;\n router.replace(`${window.location.pathname}${queryString ? `?${queryString}` : ''}${hash}`, { scroll: false });\n };\n\n // Smart defaults based on type and context\n switch (type) {\n case 'search':\n return {\n text: 'Reset Filters',\n action: () => resetParams(['search', 'page']),\n };\n case 'posts':\n // Blog-style listing pages (blog, case studies) reset their filters\n if (currentPath.includes('/blog') || currentPath.includes('/case-studies')) {\n return {\n text: 'Reset Filters',\n action: () => resetParams(['search', 'page', 'category', 'tags']),\n };\n } else if (currentPath.includes('/profile')) {\n return {\n text: 'Browse Vendors',\n action: () => router.push('/vendors'),\n };\n }\n return {\n text: 'View All Posts',\n action: () => router.push('/blog'),\n };\n case 'vendors':\n // If we're in profile or other pages, direct to main content\n if (currentPath.includes('/profile')) {\n return {\n text: 'Browse Vendors',\n action: () => router.push('/vendors'),\n };\n } else if (currentPath.includes('/vendors') || currentPath.includes('/margin-increase/compare')) {\n return {\n text: 'Reset Filters',\n action: () => resetParams(['search', 'page', 'category', 'subcategory']),\n };\n }\n return {\n text: 'Browse Vendors',\n action: () => router.push('/vendors'),\n };\n default:\n return {\n text: 'Browse Vendors',\n action: () => router.push('/vendors'),\n };\n }\n };\n\n const defaultContent = getDefaultContent();\n const displayTitle = title || defaultContent.title;\n const displayDescription = description || defaultContent.description;\n // ctaHref short-circuits BEFORE getSmartCTA(): the smart defaults read\n // window.location.pathname (SSR-empty, client-real), so a server-rendered\n // page with a link CTA must never enter that branch. Existing callers\n // without ctaHref keep today's smart-default behavior unchanged.\n const smartCTA = ctaHref ? null : getSmartCTA();\n const ctaClassName =\n ctaVariant === 'primary'\n ? 'w-full bg-ods-accent text-ods-text-on-accent hover:bg-ods-accent-hover transition-all duration-150 font-body font-medium'\n : 'w-full bg-transparent border border-ods-border text-ods-text-primary hover:border-ods-accent hover:text-ods-accent transition-all duration-150 font-body font-medium';\n\n return (\n <div className=\"flex flex-col items-center justify-center px-6 py-6 text-center md:py-16\">\n {/* Icon */}\n <div className=\"mb-3 flex items-center justify-center md:mb-6\">\n <div className=\"rounded-full border border-ods-border bg-ods-card p-3 md:p-6\">\n <div className=\"flex h-8 w-8 items-center justify-center text-ods-text-secondary md:h-16 md:w-16\">\n {defaultContent.icon}\n </div>\n </div>\n </div>\n\n {/* Title */}\n <h2 className=\"mb-2 text-ods-text-primary text-h3 md:mb-3\">{displayTitle}</h2>\n\n {/* Description */}\n <p className=\"mb-4 max-w-md text-ods-text-secondary text-h6 md:mb-8\">{displayDescription}</p>\n\n {/* Link CTA (ctaHref) — SSR-safe, no path sniffing */}\n {showCTA && ctaHref && (\n <div className=\"mb-3 w-full max-w-xs\">\n <Button href={ctaHref} className={ctaClassName}>\n {ctaText || 'Contact us'}\n </Button>\n </div>\n )}\n\n {/* Smart CTA Button */}\n {showCTA && !ctaHref && smartCTA && (\n <div className=\"mb-3 w-full max-w-xs\">\n <Button onClick={smartCTA.action} className={ctaClassName}>\n {smartCTA.text}\n </Button>\n </div>\n )}\n\n {/* Optional Back Button */}\n {showBackButton && onGoBack && (\n <div className=\"w-full max-w-xs\">\n <Button\n onClick={onGoBack}\n variant=\"outline\"\n className=\"w-full font-body font-medium transition-all duration-150\"\n >\n {backButtonText}\n </Button>\n </div>\n )}\n </div>\n );\n}\n","'use client';\n\nimport { useSearchParams, usePathname } from '../embed-shims/next-navigation';\nimport { withQuery } from '../utils/search-params';\nimport { Pagination } from './pagination';\n\ninterface UnifiedPaginationProps {\n currentPage: number;\n totalPages: number;\n onPageChange?: (page: number) => void;\n className?: string;\n /**\n * Write `?page=` into the URL on every change (default true, the historical\n * behaviour). Set false when the CALLER owns the URL — a surface whose whole\n * filter state is one schema would otherwise get two writers racing on it.\n */\n syncUrl?: boolean;\n /**\n * Keep the row's height when there is only one page, instead of rendering\n * nothing. Stops a one-page result from shifting the content above it.\n */\n reserveSpace?: boolean;\n}\n\nexport function UnifiedPagination({\n currentPage,\n totalPages,\n onPageChange,\n className = 'mt-8 flex justify-center w-full',\n syncUrl = true,\n reserveSpace = false,\n}: UnifiedPaginationProps) {\n const searchParams = useSearchParams();\n const pathname = usePathname();\n\n const handlePageChange = (page: number) => {\n // Preserve current scroll position\n const currentScrollY = window.scrollY;\n\n // Call the callback to update local state (prevents reload)\n if (onPageChange) {\n onPageChange(page);\n }\n\n // Update URL for bookmarking without navigation — unless the caller owns\n // the URL (`syncUrl={false}`), in which case a second writer here would\n // race the caller's own param write.\n if (syncUrl) {\n const params = new URLSearchParams(searchParams.toString());\n params.set('page', page.toString());\n window.history.replaceState(null, '', withQuery(pathname, params.toString()));\n }\n\n // Restore scroll position after a brief delay to allow content to render\n setTimeout(() => {\n window.scrollTo({\n top: currentScrollY,\n behavior: 'instant', // Instant to prevent any scroll animation\n });\n }, 0);\n };\n\n // Don't render pagination if there's only one page (optionally holding the\n // row's height so the content above it does not shift).\n //\n // The reserved row renders the REAL control, hidden — an empty div reserves\n // nothing, and a hardcoded spacer height silently drifts the day the control\n // changes. `invisible` keeps the box, `aria-hidden` + `pointer-events-none`\n // keep it out of the accessibility tree and out of the way.\n if (totalPages <= 1) {\n return reserveSpace ? (\n <div className={className} aria-hidden=\"true\">\n <div className=\"pointer-events-none invisible\">\n <Pagination currentPage={1} totalPages={1} onPageChange={() => {}} />\n </div>\n </div>\n ) : null;\n }\n\n return (\n <div className={className}>\n <Pagination currentPage={currentPage} totalPages={totalPages} onPageChange={handlePageChange} />\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-O4VZOA2Q.cjs","../src/components/empty-state.tsx","../src/components/unified-pagination.tsx"],"names":["jsx"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-O4VZOA2Q.cjs","sourcesContent":[null,"'use client';\n\nimport { Search, FileText, Package } from 'lucide-react';\nimport { useRouter } from '../embed-shims/next-navigation';\nimport { Button } from './ui/button';\n\nexport interface EmptyStateProps {\n type: 'vendors' | 'posts' | 'search' | 'generic';\n title?: string;\n description?: string;\n showBackButton?: boolean;\n onGoBack?: () => void;\n backButtonText?: string;\n // New CTA properties\n showCTA?: boolean;\n ctaText?: string;\n onCtaClick?: () => void;\n /**\n * Link-CTA mode: render the CTA as a navigation link (`href` — plain or\n * `mailto:`). Wins over `onCtaClick`, and SHORT-CIRCUITS the smart-CTA\n * path-sniffing below (which reads `window.location.pathname` — an\n * SSR/client divergence server-rendered pages must never enter).\n */\n ctaHref?: string;\n ctaVariant?: 'primary' | 'secondary';\n}\n\nexport function EmptyState({\n type,\n title,\n description,\n showBackButton = false,\n onGoBack,\n backButtonText = 'Go Back',\n showCTA = true,\n ctaText,\n onCtaClick,\n ctaHref,\n ctaVariant = 'primary',\n}: EmptyStateProps) {\n const router = useRouter();\n\n // Default content based on type\n const getDefaultContent = () => {\n switch (type) {\n case 'vendors':\n return {\n icon: <Package className=\"h-full w-full\" />,\n title: 'No vendors found',\n description:\n \"We couldn't find any vendors matching your criteria. Try adjusting your filters or search terms.\",\n };\n case 'posts':\n return {\n icon: <FileText className=\"h-full w-full\" />,\n title: 'No articles found',\n description:\n \"We couldn't find any articles matching your criteria. Try different categories, tags, or search terms.\",\n };\n case 'search':\n return {\n icon: <Search className=\"h-full w-full\" />,\n title: 'No results found',\n description: \"Your search didn't return any results. Try different keywords or browse our categories.\",\n };\n default:\n return {\n icon: <Search className=\"h-full w-full\" />,\n title: 'Nothing found',\n description: \"We couldn't find what you're looking for. Try adjusting your search or filters.\",\n };\n }\n };\n\n // Smart CTA logic based on context\n const getSmartCTA = () => {\n // If custom CTA is provided, use it\n if (ctaText && onCtaClick) {\n return {\n text: ctaText,\n action: onCtaClick,\n };\n }\n\n // Check if we're on the client side\n const isClient = typeof window !== 'undefined';\n const currentPath = isClient ? window.location.pathname : '';\n\n // Surgical filter reset (promoted from the hub's diverged clone during\n // the EmptyState convergence): delete ONLY the filter params the surface\n // owns — nuking the whole query string threw away unrelated state — and\n // use `replace` with `scroll: false` so resetting filters neither adds a\n // history entry nor jumps the page.\n const resetParams = (paramsToDelete: string[]) => {\n if (!isClient) return;\n const params = new URLSearchParams(window.location.search);\n for (const p of paramsToDelete) params.delete(p);\n const queryString = params.toString();\n // Keep the hash — a filter reset must not clear an unrelated\n // deep-link anchor (e.g. #delivery-123).\n const hash = window.location.hash;\n router.replace(`${window.location.pathname}${queryString ? `?${queryString}` : ''}${hash}`, { scroll: false });\n };\n\n // Smart defaults based on type and context\n switch (type) {\n case 'search':\n return {\n text: 'Reset Filters',\n action: () => resetParams(['search', 'page']),\n };\n case 'posts':\n // Blog-style listing pages (blog, case studies) reset their filters\n if (currentPath.includes('/blog') || currentPath.includes('/case-studies')) {\n return {\n text: 'Reset Filters',\n action: () => resetParams(['search', 'page', 'category', 'tags']),\n };\n } else if (currentPath.includes('/profile')) {\n return {\n text: 'Browse Vendors',\n action: () => router.push('/vendors'),\n };\n }\n return {\n text: 'View All Posts',\n action: () => router.push('/blog'),\n };\n case 'vendors':\n // If we're in profile or other pages, direct to main content\n if (currentPath.includes('/profile')) {\n return {\n text: 'Browse Vendors',\n action: () => router.push('/vendors'),\n };\n } else if (currentPath.includes('/vendors') || currentPath.includes('/margin-increase/compare')) {\n return {\n text: 'Reset Filters',\n action: () => resetParams(['search', 'page', 'category', 'subcategory']),\n };\n }\n return {\n text: 'Browse Vendors',\n action: () => router.push('/vendors'),\n };\n default:\n return {\n text: 'Browse Vendors',\n action: () => router.push('/vendors'),\n };\n }\n };\n\n const defaultContent = getDefaultContent();\n const displayTitle = title || defaultContent.title;\n const displayDescription = description || defaultContent.description;\n // ctaHref short-circuits BEFORE getSmartCTA(): the smart defaults read\n // window.location.pathname (SSR-empty, client-real), so a server-rendered\n // page with a link CTA must never enter that branch. Existing callers\n // without ctaHref keep today's smart-default behavior unchanged.\n const smartCTA = ctaHref ? null : getSmartCTA();\n const ctaClassName =\n ctaVariant === 'primary'\n ? 'w-full bg-ods-accent text-ods-text-on-accent hover:bg-ods-accent-hover transition-all duration-150 font-body font-medium'\n : 'w-full bg-transparent border border-ods-border text-ods-text-primary hover:border-ods-accent hover:text-ods-accent transition-all duration-150 font-body font-medium';\n\n return (\n <div className=\"flex flex-col items-center justify-center px-6 py-6 text-center md:py-16\">\n {/* Icon */}\n <div className=\"mb-3 flex items-center justify-center md:mb-6\">\n <div className=\"rounded-full border border-ods-border bg-ods-card p-3 md:p-6\">\n <div className=\"flex h-8 w-8 items-center justify-center text-ods-text-secondary md:h-16 md:w-16\">\n {defaultContent.icon}\n </div>\n </div>\n </div>\n\n {/* Title */}\n <h2 className=\"mb-2 text-ods-text-primary text-h3 md:mb-3\">{displayTitle}</h2>\n\n {/* Description */}\n <p className=\"mb-4 max-w-md text-ods-text-secondary text-h6 md:mb-8\">{displayDescription}</p>\n\n {/* Link CTA (ctaHref) — SSR-safe, no path sniffing */}\n {showCTA && ctaHref && (\n <div className=\"mb-3 w-full max-w-xs\">\n <Button href={ctaHref} className={ctaClassName}>\n {ctaText || 'Contact us'}\n </Button>\n </div>\n )}\n\n {/* Smart CTA Button */}\n {showCTA && !ctaHref && smartCTA && (\n <div className=\"mb-3 w-full max-w-xs\">\n <Button onClick={smartCTA.action} className={ctaClassName}>\n {smartCTA.text}\n </Button>\n </div>\n )}\n\n {/* Optional Back Button */}\n {showBackButton && onGoBack && (\n <div className=\"w-full max-w-xs\">\n <Button\n onClick={onGoBack}\n variant=\"outline\"\n className=\"w-full font-body font-medium transition-all duration-150\"\n >\n {backButtonText}\n </Button>\n </div>\n )}\n </div>\n );\n}\n","'use client';\n\nimport { useSearchParams, usePathname } from '../embed-shims/next-navigation';\nimport { withQuery } from '../utils/search-params';\nimport { Pagination } from './pagination';\n\ninterface UnifiedPaginationProps {\n currentPage: number;\n totalPages: number;\n onPageChange?: (page: number) => void;\n className?: string;\n /**\n * Write `?page=` into the URL on every change (default true, the historical\n * behaviour). Set false when the CALLER owns the URL — a surface whose whole\n * filter state is one schema would otherwise get two writers racing on it.\n */\n syncUrl?: boolean;\n /**\n * Keep the row's height when there is only one page, instead of rendering\n * nothing. Stops a one-page result from shifting the content above it.\n */\n reserveSpace?: boolean;\n}\n\nexport function UnifiedPagination({\n currentPage,\n totalPages,\n onPageChange,\n className = 'mt-8 flex justify-center w-full',\n syncUrl = true,\n reserveSpace = false,\n}: UnifiedPaginationProps) {\n const searchParams = useSearchParams();\n const pathname = usePathname();\n\n const handlePageChange = (page: number) => {\n // Preserve current scroll position\n const currentScrollY = window.scrollY;\n\n // Call the callback to update local state (prevents reload)\n if (onPageChange) {\n onPageChange(page);\n }\n\n // Update URL for bookmarking without navigation — unless the caller owns\n // the URL (`syncUrl={false}`), in which case a second writer here would\n // race the caller's own param write.\n if (syncUrl) {\n const params = new URLSearchParams(searchParams.toString());\n params.set('page', page.toString());\n window.history.replaceState(null, '', withQuery(pathname, params.toString()));\n }\n\n // Restore scroll position after a brief delay to allow content to render\n setTimeout(() => {\n window.scrollTo({\n top: currentScrollY,\n behavior: 'instant', // Instant to prevent any scroll animation\n });\n }, 0);\n };\n\n // Don't render pagination if there's only one page (optionally holding the\n // row's height so the content above it does not shift).\n //\n // The reserved row renders the REAL control, hidden — an empty div reserves\n // nothing, and a hardcoded spacer height silently drifts the day the control\n // changes. `invisible` keeps the box, `aria-hidden` + `pointer-events-none`\n // keep it out of the accessibility tree and out of the way.\n if (totalPages <= 1) {\n return reserveSpace ? (\n <div className={className} aria-hidden=\"true\">\n <div className=\"pointer-events-none invisible\">\n <Pagination currentPage={1} totalPages={1} onPageChange={() => {}} />\n </div>\n </div>\n ) : null;\n }\n\n return (\n <div className={className}>\n <Pagination currentPage={currentPage} totalPages={totalPages} onPageChange={handlePageChange} />\n </div>\n );\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import {
|
|
3
3
|
createSseSubscription
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-WVHQQ3NY.js";
|
|
5
5
|
import {
|
|
6
6
|
useChatIdentity
|
|
7
7
|
} from "./chunk-I7JYX5O3.js";
|
|
@@ -275,4 +275,4 @@ export {
|
|
|
275
275
|
TICKET_MARK_READ_DEBOUNCE_MS,
|
|
276
276
|
TOAST_COPY
|
|
277
277
|
};
|
|
278
|
-
//# sourceMappingURL=chunk-
|
|
278
|
+
//# sourceMappingURL=chunk-OO6AMBHV.js.map
|
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
getOSLabel,
|
|
5
5
|
getShellLabel,
|
|
6
6
|
getToolLabel
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-WVHQQ3NY.js";
|
|
8
8
|
import {
|
|
9
9
|
useOnboardingState
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-MOHSA65M.js";
|
|
11
11
|
import {
|
|
12
12
|
Input
|
|
13
13
|
} from "./chunk-T2NQSKH4.js";
|
|
@@ -524,4 +524,4 @@ export {
|
|
|
524
524
|
OnboardingStepCard,
|
|
525
525
|
OnboardingWalkthrough
|
|
526
526
|
};
|
|
527
|
-
//# sourceMappingURL=chunk-
|
|
527
|
+
//# sourceMappingURL=chunk-R25AUM2R.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }"use client";
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var _chunkH64ZHSYRcjs = require('./chunk-H64ZHSYR.cjs');
|
|
5
5
|
|
|
6
6
|
// src/hooks/use-self-fetch.ts
|
|
7
7
|
var _react = require('react');
|
|
@@ -34,7 +34,7 @@ function useSelfFetch(url, options) {
|
|
|
34
34
|
try {
|
|
35
35
|
setIsLoading(true);
|
|
36
36
|
setError(false);
|
|
37
|
-
const res = await
|
|
37
|
+
const res = await _chunkH64ZHSYRcjs.contentFetch.call(void 0, url, { signal: ctrl.signal });
|
|
38
38
|
if (!res.ok) throw new Error(`Request failed (${res.status})`);
|
|
39
39
|
const json = await res.json();
|
|
40
40
|
if (!cancelled) {
|
|
@@ -83,4 +83,4 @@ function useSelfFetch(url, options) {
|
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
exports.useSelfFetch = useSelfFetch;
|
|
86
|
-
//# sourceMappingURL=chunk-
|
|
86
|
+
//# sourceMappingURL=chunk-STYNZ4QM.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-VGY7D6LG.cjs","../src/hooks/use-self-fetch.ts"],"names":[],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-VGY7D6LG.cjs","sourcesContent":[null,"'use client';\n\nimport { useEffect, useRef, useState, type Dispatch, type SetStateAction } from 'react';\nimport { contentFetch } from '../utils/embed-content-fetch';\n\nexport interface UseSelfFetchResult<T> {\n data: T | null;\n /** Imperatively patch the fetched data (e.g. optimistic vote updates). */\n setData: Dispatch<SetStateAction<T | null>>;\n isLoading: boolean;\n error: boolean;\n /** Re-run the fetch (error-retry affordance). */\n reload: () => void;\n}\n\n/**\n * The single source for the 4 self-fetching content views\n * (`ProductReleasesView`, `RoadmapView`, the onboarding catalog/detail). GETs a\n * configured `url` into component state with **plain `fetch` + `useEffect`** —\n * deliberately NO react-query, so embedders don't need a QueryClient. This is\n * the same technology choice the shipped `DeliveryLists` makes; that component\n * predates this hook and still hand-rolls the loop (a future pass can migrate\n * it onto a two-url variant of this hook).\n *\n * Behaviour:\n * - `url = null` → fetching is DISABLED (controlled / SSR mode, or missing\n * config); returns `initialData ?? null` and never fetches.\n * - `initialData` provided → hydrates immediately and SKIPS the first fetch\n * (so the hub's server-rendered data isn't re-fetched on mount); later\n * `url` changes still re-fetch.\n * - a `cancelled` guard ensures an in-flight response from a STALE `url`\n * (e.g. a fast pagination / section toggle) can't overwrite a newer one.\n * - `reload()` bumps an internal key to retry after an error.\n * - `revalidateOnVisibleAfterMs` (opt-in) → re-fetch when the tab becomes\n * visible AND the held data is older than the threshold. Event-driven\n * freshness with ZERO background requests — the replacement for\n * setInterval polling. Data age counts from the last completed fetch, or\n * from mount when `initialData` seeded it (an SSR seed is milliseconds\n * old — without that, the first refocus would always refire).\n *\n * Re-fetches whenever `url` changes, so callers fold all query params INTO the\n * url string (the url IS the cache key).\n */\nexport function useSelfFetch<T>(\n url: string | null,\n options?: { initialData?: T; revalidateOnVisibleAfterMs?: number },\n): UseSelfFetchResult<T> {\n const initialData = options?.initialData;\n const revalidateOnVisibleAfterMs = options?.revalidateOnVisibleAfterMs;\n const [data, setData] = useState<T | null>(initialData ?? null);\n const [isLoading, setIsLoading] = useState(initialData === undefined && url !== null);\n const [error, setError] = useState(false);\n const [reloadKey, setReloadKey] = useState(0);\n // When the held data was obtained: seeded at mount for SSR-hydrated data,\n // stamped after each completed fetch. Drives the visibility revalidation age\n // check. 0 = nothing held yet (first visible event may fetch).\n // The mount timestamp comes from a lazy `useState` initialiser — a\n // `useRef(Date.now())` argument is re-evaluated on every render.\n const [mountedAt] = useState(() => Date.now());\n const dataAtRef = useRef<number>(initialData !== undefined ? mountedAt : 0);\n // The url whose data we currently hold — seeded from SSR `initialData`, then set after\n // each completed fetch. The effect skips the fetch when this equals `url` (so server-\n // rendered data isn't re-fetched on mount). A VALUE compare (not a one-shot flag) so the\n // SSR-hydration skip survives React 18 StrictMode's mount→unmount→remount in dev;\n // `reload()` nulls it to force a re-fetch of the same url.\n const dataUrlRef = useRef<string | null>(initialData !== undefined ? url : null);\n\n // Both resets below are prop-driven, so they use React's \"adjust state while\n // rendering\" pattern rather than an effect. From an effect each one commits a\n // frame showing the superseded data (or a spinner that will never resolve)\n // and then immediately re-renders to correct it; done here the wrong frame\n // never reaches the screen.\n const [syncedProps, setSyncedProps] = useState({ url, initialData });\n if (syncedProps.url !== url || syncedProps.initialData !== initialData) {\n setSyncedProps({ url, initialData });\n // A CONTROLLED `initialData` changed (e.g. the host navigates between\n // detail slugs without remounting). No-op in self-fetch mode, where\n // `initialData` is `undefined`.\n if (syncedProps.initialData !== initialData && initialData !== undefined) {\n setData(initialData);\n }\n // Fetching just got disabled — nothing will land to clear the spinner.\n if (syncedProps.url !== url && url === null) {\n setIsLoading(false);\n }\n }\n\n useEffect(() => {\n if (url === null) return undefined;\n // Already hold data for this exact url (SSR-hydrated, or a prior completed fetch) →\n // skip. `reload()` clears `dataUrlRef` so a retry of the same url still fetches.\n if (dataUrlRef.current === url) return undefined;\n const ctrl = new AbortController();\n let cancelled = false;\n async function load() {\n try {\n setIsLoading(true);\n setError(false);\n const res = await contentFetch(url as string, { signal: ctrl.signal });\n if (!res.ok) throw new Error(`Request failed (${res.status})`);\n const json = (await res.json()) as T;\n if (!cancelled) {\n setData(json);\n dataUrlRef.current = url; // remember the url we now hold data for\n dataAtRef.current = Date.now();\n }\n } catch (err) {\n // AbortError on cleanup (unmount / stale-url change / React StrictMode's dev\n // double-invoke) is EXPECTED — the request was intentionally aborted. Aborting\n // also means the orphaned StrictMode fetch shows as \"cancelled\" instead of\n // completing as a wasted duplicate (parity with useChatIdentity). Only real\n // failures fall through to the error state + console.\n if (cancelled || (err as Error)?.name === 'AbortError') return;\n setError(true);\n console.error('useSelfFetch:', url, err);\n } finally {\n if (!cancelled) setIsLoading(false);\n }\n }\n // Never rejects — try/catch/finally, every write gated on `cancelled`.\n void load();\n return () => {\n cancelled = true;\n ctrl.abort();\n };\n // `url` folds in every query param; `reloadKey` drives retry.\n }, [url, reloadKey]);\n\n // ONE force-refetch mechanic shared by reload() and the visibility\n // revalidation: clear the held-url skip, bump the effect key.\n const forceReload = () => {\n dataUrlRef.current = null;\n setReloadKey(k => k + 1);\n };\n\n // Opt-in visibility-driven revalidation. Registered only when the option is\n // set and fetching is enabled; SSR-safe (effects don't run on the server)\n // and StrictMode-safe (idempotent listener, cleaned up per mount cycle).\n useEffect(() => {\n if (!revalidateOnVisibleAfterMs || url === null) return undefined;\n const onVisible = () => {\n if (document.visibilityState !== 'visible') return;\n if (Date.now() - dataAtRef.current < revalidateOnVisibleAfterMs) return;\n forceReload();\n };\n document.addEventListener('visibilitychange', onVisible);\n return () => document.removeEventListener('visibilitychange', onVisible);\n }, [url, revalidateOnVisibleAfterMs]);\n\n return {\n data,\n setData,\n isLoading,\n error,\n // Force a re-fetch of the current url (error-retry affordance).\n reload: forceReload,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-STYNZ4QM.cjs","../src/hooks/use-self-fetch.ts"],"names":[],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-STYNZ4QM.cjs","sourcesContent":[null,"'use client';\n\nimport { useEffect, useRef, useState, type Dispatch, type SetStateAction } from 'react';\nimport { contentFetch } from '../utils/embed-content-fetch';\n\nexport interface UseSelfFetchResult<T> {\n data: T | null;\n /** Imperatively patch the fetched data (e.g. optimistic vote updates). */\n setData: Dispatch<SetStateAction<T | null>>;\n isLoading: boolean;\n error: boolean;\n /** Re-run the fetch (error-retry affordance). */\n reload: () => void;\n}\n\n/**\n * The single source for the 4 self-fetching content views\n * (`ProductReleasesView`, `RoadmapView`, the onboarding catalog/detail). GETs a\n * configured `url` into component state with **plain `fetch` + `useEffect`** —\n * deliberately NO react-query, so embedders don't need a QueryClient. This is\n * the same technology choice the shipped `DeliveryLists` makes; that component\n * predates this hook and still hand-rolls the loop (a future pass can migrate\n * it onto a two-url variant of this hook).\n *\n * Behaviour:\n * - `url = null` → fetching is DISABLED (controlled / SSR mode, or missing\n * config); returns `initialData ?? null` and never fetches.\n * - `initialData` provided → hydrates immediately and SKIPS the first fetch\n * (so the hub's server-rendered data isn't re-fetched on mount); later\n * `url` changes still re-fetch.\n * - a `cancelled` guard ensures an in-flight response from a STALE `url`\n * (e.g. a fast pagination / section toggle) can't overwrite a newer one.\n * - `reload()` bumps an internal key to retry after an error.\n * - `revalidateOnVisibleAfterMs` (opt-in) → re-fetch when the tab becomes\n * visible AND the held data is older than the threshold. Event-driven\n * freshness with ZERO background requests — the replacement for\n * setInterval polling. Data age counts from the last completed fetch, or\n * from mount when `initialData` seeded it (an SSR seed is milliseconds\n * old — without that, the first refocus would always refire).\n *\n * Re-fetches whenever `url` changes, so callers fold all query params INTO the\n * url string (the url IS the cache key).\n */\nexport function useSelfFetch<T>(\n url: string | null,\n options?: { initialData?: T; revalidateOnVisibleAfterMs?: number },\n): UseSelfFetchResult<T> {\n const initialData = options?.initialData;\n const revalidateOnVisibleAfterMs = options?.revalidateOnVisibleAfterMs;\n const [data, setData] = useState<T | null>(initialData ?? null);\n const [isLoading, setIsLoading] = useState(initialData === undefined && url !== null);\n const [error, setError] = useState(false);\n const [reloadKey, setReloadKey] = useState(0);\n // When the held data was obtained: seeded at mount for SSR-hydrated data,\n // stamped after each completed fetch. Drives the visibility revalidation age\n // check. 0 = nothing held yet (first visible event may fetch).\n // The mount timestamp comes from a lazy `useState` initialiser — a\n // `useRef(Date.now())` argument is re-evaluated on every render.\n const [mountedAt] = useState(() => Date.now());\n const dataAtRef = useRef<number>(initialData !== undefined ? mountedAt : 0);\n // The url whose data we currently hold — seeded from SSR `initialData`, then set after\n // each completed fetch. The effect skips the fetch when this equals `url` (so server-\n // rendered data isn't re-fetched on mount). A VALUE compare (not a one-shot flag) so the\n // SSR-hydration skip survives React 18 StrictMode's mount→unmount→remount in dev;\n // `reload()` nulls it to force a re-fetch of the same url.\n const dataUrlRef = useRef<string | null>(initialData !== undefined ? url : null);\n\n // Both resets below are prop-driven, so they use React's \"adjust state while\n // rendering\" pattern rather than an effect. From an effect each one commits a\n // frame showing the superseded data (or a spinner that will never resolve)\n // and then immediately re-renders to correct it; done here the wrong frame\n // never reaches the screen.\n const [syncedProps, setSyncedProps] = useState({ url, initialData });\n if (syncedProps.url !== url || syncedProps.initialData !== initialData) {\n setSyncedProps({ url, initialData });\n // A CONTROLLED `initialData` changed (e.g. the host navigates between\n // detail slugs without remounting). No-op in self-fetch mode, where\n // `initialData` is `undefined`.\n if (syncedProps.initialData !== initialData && initialData !== undefined) {\n setData(initialData);\n }\n // Fetching just got disabled — nothing will land to clear the spinner.\n if (syncedProps.url !== url && url === null) {\n setIsLoading(false);\n }\n }\n\n useEffect(() => {\n if (url === null) return undefined;\n // Already hold data for this exact url (SSR-hydrated, or a prior completed fetch) →\n // skip. `reload()` clears `dataUrlRef` so a retry of the same url still fetches.\n if (dataUrlRef.current === url) return undefined;\n const ctrl = new AbortController();\n let cancelled = false;\n async function load() {\n try {\n setIsLoading(true);\n setError(false);\n const res = await contentFetch(url as string, { signal: ctrl.signal });\n if (!res.ok) throw new Error(`Request failed (${res.status})`);\n const json = (await res.json()) as T;\n if (!cancelled) {\n setData(json);\n dataUrlRef.current = url; // remember the url we now hold data for\n dataAtRef.current = Date.now();\n }\n } catch (err) {\n // AbortError on cleanup (unmount / stale-url change / React StrictMode's dev\n // double-invoke) is EXPECTED — the request was intentionally aborted. Aborting\n // also means the orphaned StrictMode fetch shows as \"cancelled\" instead of\n // completing as a wasted duplicate (parity with useChatIdentity). Only real\n // failures fall through to the error state + console.\n if (cancelled || (err as Error)?.name === 'AbortError') return;\n setError(true);\n console.error('useSelfFetch:', url, err);\n } finally {\n if (!cancelled) setIsLoading(false);\n }\n }\n // Never rejects — try/catch/finally, every write gated on `cancelled`.\n void load();\n return () => {\n cancelled = true;\n ctrl.abort();\n };\n // `url` folds in every query param; `reloadKey` drives retry.\n }, [url, reloadKey]);\n\n // ONE force-refetch mechanic shared by reload() and the visibility\n // revalidation: clear the held-url skip, bump the effect key.\n const forceReload = () => {\n dataUrlRef.current = null;\n setReloadKey(k => k + 1);\n };\n\n // Opt-in visibility-driven revalidation. Registered only when the option is\n // set and fetching is enabled; SSR-safe (effects don't run on the server)\n // and StrictMode-safe (idempotent listener, cleaned up per mount cycle).\n useEffect(() => {\n if (!revalidateOnVisibleAfterMs || url === null) return undefined;\n const onVisible = () => {\n if (document.visibilityState !== 'visible') return;\n if (Date.now() - dataAtRef.current < revalidateOnVisibleAfterMs) return;\n forceReload();\n };\n document.addEventListener('visibilitychange', onVisible);\n return () => document.removeEventListener('visibilitychange', onVisible);\n }, [url, revalidateOnVisibleAfterMs]);\n\n return {\n data,\n setData,\n isLoading,\n error,\n // Force a re-fetch of the current url (error-retry affordance).\n reload: forceReload,\n };\n}\n"]}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }"use client";
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var _chunkCOY57SZPcjs = require('./chunk-COY57SZP.cjs');
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
var
|
|
7
|
+
var _chunkH64ZHSYRcjs = require('./chunk-H64ZHSYR.cjs');
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
|
|
@@ -67,7 +67,7 @@ function DocSearchBar({
|
|
|
67
67
|
renderResult
|
|
68
68
|
}) {
|
|
69
69
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
70
|
-
|
|
70
|
+
_chunkCOY57SZPcjs.SearchInput,
|
|
71
71
|
{
|
|
72
72
|
placeholder,
|
|
73
73
|
value: query,
|
|
@@ -287,7 +287,7 @@ function useDocSearch(config) {
|
|
|
287
287
|
limit: "10"
|
|
288
288
|
});
|
|
289
289
|
if (tableIdsKey) params.set("tableIds", tableIdsKey);
|
|
290
|
-
const response = await
|
|
290
|
+
const response = await _chunkH64ZHSYRcjs.contentFetch.call(void 0, `${resolvedSearchEndpoint}?${params.toString()}`);
|
|
291
291
|
if (!response.ok) {
|
|
292
292
|
throw new Error(`Search request failed: ${response.status}`);
|
|
293
293
|
}
|
|
@@ -381,4 +381,4 @@ function useDocSearch(config) {
|
|
|
381
381
|
|
|
382
382
|
|
|
383
383
|
exports.formatRelativePath = formatRelativePath; exports.DocSearchResultRow = DocSearchResultRow; exports.DocSearchBar = DocSearchBar; exports.mapDocSearchResults = mapDocSearchResults; exports.resolveSearchResultAction = resolveSearchResultAction; exports.useDocSearch = useDocSearch;
|
|
384
|
-
//# sourceMappingURL=chunk-
|
|
384
|
+
//# sourceMappingURL=chunk-TQMZFXWZ.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-FHKP4H6N.cjs","../src/components/shared/doc-search/format-relative-path.ts","../src/components/shared/doc-search/doc-search-result-row.tsx","../src/components/shared/doc-search/doc-search-bar.tsx","../src/components/shared/doc-search/map-doc-search-results.ts","../src/components/shared/doc-search/resolve-search-result-action.ts","../src/components/shared/doc-search/use-doc-search.ts"],"names":["jsx"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-FHKP4H6N.cjs","sourcesContent":[null,"/**\n * Format a full document path as a breadcrumb trail.\n * Shows parent folders only (excludes the last segment / filename).\n *\n * @example\n * formatRelativePath('openframe-oss-tenant/architecture/api-controllers.md')\n * // → 'Openframe oss tenant / Architecture'\n */\nexport function formatRelativePath(fullPath: string): string {\n if (!fullPath) return '';\n const segments = fullPath.replace(/\\.md$/, '').split('/');\n // Show only parent path (exclude the filename itself since the title already shows it)\n const parentSegments = segments.length > 1 ? segments.slice(0, -1) : segments;\n return parentSegments.map(seg => seg.charAt(0).toUpperCase() + seg.slice(1).replace(/-/g, ' ')).join(' / ');\n}\n","'use client';\n\n/**\n * Single row in the `<SearchInput>` dropdown — the standard layout\n * used by every doc-search-backed surface (company-hub data-room\n * search bar, onboarding-guide catalog search, …). Single source of\n * truth for the row appearance so search dropdowns are visually\n * identical everywhere.\n *\n * Resolves the source icon via the same `resolveSourceIcon()`\n * registry the inline chat-card refs use, so a row pointing at e.g.\n * an onboarding-guide surfaces the SAME `<GraduationCap>` glyph the\n * chat card surfaces — no cross-surface drift.\n */\n\nimport { resolveSourceIcon } from '../../chat/utils/source-row-cta';\nimport { formatRelativePath } from './format-relative-path';\n\n/**\n * Minimal result shape this row renders. Compatible with any\n * doc-search hook whose result type exposes `{ title?, path?,\n * metadata? }`. The two hub consumers (onboarding-guide catalog,\n * data-room sidebar) both satisfy this shape via their\n * `useDocSearch` hook result.\n */\nexport interface DocSearchResultRowEntry {\n title?: string;\n path?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface DocSearchResultRowProps {\n result: DocSearchResultRowEntry;\n isHighlighted: boolean;\n}\n\nexport function DocSearchResultRow({ result, isHighlighted }: DocSearchResultRowProps) {\n const docType = (result.metadata?.documentType as string) || undefined;\n const sourceRepo = (result.metadata?.sourceRepo as string) || undefined;\n const { Icon: SourceIcon, label: iconLabel } = resolveSourceIcon({\n sourceRepo,\n documentType: docType,\n });\n const isGroup = result.metadata?.isGroup as boolean | undefined;\n\n return (\n <div className=\"flex w-full min-w-0 items-center gap-3\">\n <span className=\"flex-shrink-0 text-ods-text-secondary\" title={iconLabel}>\n <SourceIcon className=\"size-4\" />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className={`truncate text-h6 ${isHighlighted ? 'text-ods-accent' : 'text-ods-text-primary'}`}>\n {result.title || result.path}\n </div>\n {!isGroup && result.path?.includes('/') && (\n <div className=\"mt-0.5 truncate text-ods-text-secondary text-h6\">{formatRelativePath(result.path)}</div>\n )}\n </div>\n </div>\n );\n}\n","'use client';\n\n/**\n * `<DocSearchBar>` — the canonical RAG-search dropdown surface.\n *\n * Mounted by every doc-search consumer (data-room sidebar, onboarding-\n * guide catalog, and any future surface that needs typeahead against\n * `/api/docs/search`). Wraps `<SearchInput>` with the lib's standard\n * `<DocSearchResultRow>` so the dropdown looks identical everywhere.\n *\n * ## Why a presentation component, not a \"search bar that owns its\n * own hook\"\n *\n * The data-fetching hook (`useDocSearch`) lives hub-side because it\n * depends on hub-only context (`useDocNavigation`, the rag-table-\n * config registry, the hub's `decideNewTab` helper). Moving the hook\n * would cascade ~5 more file migrations into the lib.\n *\n * Instead, the hook stays hub-side and callers pass its result into\n * this component as plain props. Both consumers shrink to ~5 lines.\n */\n\nimport type { ReactNode } from 'react';\nimport { SearchInput, type SearchResult } from '../../ui/search-input';\nimport { DocSearchResultRow } from './doc-search-result-row';\n\nexport interface DocSearchBarProps {\n placeholder: string;\n query: string;\n onQueryChange: (value: string) => void;\n /** Hook-fetched results. Reuses the lib's `<SearchInput>` `SearchResult`\n * shape directly so callers don't translate. */\n results: SearchResult[];\n isLoading: boolean;\n /** Result selection handler. Mirrors `<SearchInput>` — the second\n * `modifiers` argument is preserved so cmd-click / shift-click on\n * a result row still forces new-tab behavior. Hub `useDocSearch`\n * reads these to short-circuit to `window.open()`. */\n onResultSelect: (\n result: SearchResult,\n modifiers?: {\n metaKey?: boolean;\n ctrlKey?: boolean;\n shiftKey?: boolean;\n altKey?: boolean;\n button?: number;\n },\n ) => void;\n /** Lets the caller's hook force the dropdown open after a recent\n * internal action (e.g. result navigation). `undefined` falls back\n * to `<SearchInput>`'s built-in focus/hover heuristics. */\n showDropdown?: boolean;\n /** Defaults to 2 — matches the existing data-room and onboarding-\n * guide consumers. Override only if a surface needs different\n * typeahead semantics. */\n minQueryLength?: number;\n /** Defaults to 0 — both existing consumers debounce inside the\n * hook, not the input. */\n debounceMs?: number;\n className?: string;\n /** Optional row-renderer override. Defaults to the lib's standard\n * `<DocSearchResultRow>` (source icon + title + path breadcrumb).\n * Override only when a surface needs custom row chrome. */\n renderResult?: (result: SearchResult, isHighlighted: boolean) => ReactNode;\n}\n\nexport function DocSearchBar({\n placeholder,\n query,\n onQueryChange,\n results,\n isLoading,\n onResultSelect,\n showDropdown,\n minQueryLength = 2,\n debounceMs = 0,\n className = 'w-full',\n renderResult,\n}: DocSearchBarProps) {\n return (\n <SearchInput\n placeholder={placeholder}\n value={query}\n onChange={onQueryChange}\n results={results}\n isLoading={isLoading}\n onResultSelect={onResultSelect}\n showDropdown={showDropdown || undefined}\n debounceMs={debounceMs}\n minQueryLength={minQueryLength}\n className={className}\n renderResult={\n renderResult ??\n ((result, isHighlighted) => <DocSearchResultRow result={result} isHighlighted={isHighlighted} />)\n }\n />\n );\n}\n","/**\n * Map RAG `/api/docs/search` wire results into the `<DocSearchBar>`\n * dropdown's row shape, collapsing entity-table rows into grouped\n * results so the dropdown lists ONE \"Cap Table (12 records)\" row\n * instead of 12 individual rows.\n *\n * Pure transform — no telemetry, no navigation, no React deps. Lifted\n * from the hub's `hooks/use-docs.ts:mapDocSearchResults` (the hub's\n * `traceCompose` call was hub-only telemetry and is intentionally\n * dropped — callers that want logging can wrap this helper).\n */\n\nimport type { SearchResult } from '../../ui/search-input';\nimport type { DocSearchResult } from './types';\n\n/** Source repos that should be collapsed into grouped results in the search bar.\n * Only financial tables (all rows link to the same admin page).\n * Content tables (blog, webinar, podcast, etc.) stay individual since each has a unique URL. */\nconst SEARCH_GROUP_REPOS = new Set([\n 'financial-cap-table',\n 'financial-kpis',\n 'financial-pnl',\n 'financial-balance-sheet',\n 'financial-cash-flow',\n]);\n\nconst ENTITY_LABELS: Record<string, string> = {\n 'financial-cap-table': 'Cap Table',\n 'financial-kpis': 'Financial KPIs',\n 'financial-pnl': 'Profit & Loss',\n 'financial-balance-sheet': 'Balance Sheets',\n 'financial-cash-flow': 'Cash Flow',\n 'blog-posts': 'Blog Posts',\n 'product-releases': 'Product Releases',\n 'case-studies': 'Case Studies',\n webinars: 'Webinars',\n events: 'Events',\n podcasts: 'Podcasts',\n};\n\nexport function mapDocSearchResults(docs: DocSearchResult[]): SearchResult[] {\n const entityGroups = new Map<string, DocSearchResult[]>();\n // Track insertion order — groups appear where the FIRST row of that\n // repo appeared in the response.\n const order: Array<{ type: 'entity'; repo: string } | { type: 'doc'; doc: DocSearchResult }> = [];\n const seenRepos = new Set<string>();\n\n for (const doc of docs) {\n if (doc.sourceRepo && SEARCH_GROUP_REPOS.has(doc.sourceRepo)) {\n const group = entityGroups.get(doc.sourceRepo) || [];\n group.push(doc);\n entityGroups.set(doc.sourceRepo, group);\n if (!seenRepos.has(doc.sourceRepo)) {\n seenRepos.add(doc.sourceRepo);\n order.push({ type: 'entity', repo: doc.sourceRepo });\n }\n } else {\n order.push({ type: 'doc', doc });\n }\n }\n\n const results: SearchResult[] = [];\n for (const entry of order) {\n if (entry.type === 'entity') {\n // An 'entity' entry is only pushed AFTER its group lands in the map, so\n // a miss is unreachable — but dropping one group beats throwing away the\n // whole search dropdown if that ever stops holding.\n const rows = entityGroups.get(entry.repo);\n if (!rows || rows.length === 0) continue;\n const label = ENTITY_LABELS[entry.repo] || entry.repo;\n results.push({\n id: `group-${entry.repo}`,\n title: `${label} (${rows.length} ${rows.length === 1 ? 'record' : 'records'})`,\n path: rows[0].path,\n type: 'file',\n metadata: {\n documentType: rows[0].documentType,\n externalUrl: rows[0].externalUrl,\n sourceRepo: entry.repo,\n id: rows[0].entityId,\n isGroup: true,\n items: rows.map(r => ({\n name: r.name,\n externalUrl: r.externalUrl,\n id: r.entityId,\n sourceRepo: r.sourceRepo,\n documentType: r.documentType,\n })),\n },\n });\n } else {\n const doc = entry.doc;\n const isNonMarkdown = doc.documentType && doc.documentType !== 'markdown';\n results.push({\n id: doc.path,\n title: doc.name,\n description: isNonMarkdown ? doc.name : doc.snippet,\n path: doc.path,\n type: doc.type,\n metadata: {\n matchType: doc.matchType,\n ...(doc.documentType ? { documentType: doc.documentType } : {}),\n ...(doc.externalUrl ? { externalUrl: doc.externalUrl } : {}),\n ...(doc.targetPlatform != null ? { targetPlatform: doc.targetPlatform } : {}),\n ...(doc.sourceRepo ? { sourceRepo: doc.sourceRepo } : {}),\n ...(doc.entityId ? { id: doc.entityId } : {}),\n },\n });\n }\n }\n\n return results;\n}\n","/**\n * Resolve what should happen when the user picks a search result.\n * Returns one of five typed actions so the caller is a single switch.\n *\n * Resolution order:\n * 1. `externalUrl` present → resolve the href through the host's\n * `composeContentUrl` seam (when wired), then use `decideNewTab` to\n * choose same-tab vs new-tab against the resolved `targetPlatform`.\n * 2. Row has `id` + `sourceRepo` + `documentType` → synth an Ask-AI\n * action (entity drill-in via primary key, no URL).\n * 3. Row has only `path` → legacy navigation fallback.\n * 4. Nothing actionable → noop.\n *\n * Lifted from the hub's `hooks/use-docs.ts:resolveSearchResultAction`.\n * Pure — no React, no telemetry.\n */\n\nimport type { ComposeContentUrl } from '../../../utils/content-href';\nimport type { ChatRef } from '../../chat/chat-ref.types';\nimport { decideNewTab } from '../../chat/utils/decide-new-tab';\nimport type { SearchResult } from '../../ui/search-input';\n\n/** True for `https://…` and protocol-relative `//…`. A composed href that is\n * NOT absolute is an in-app route the host claimed via `hostedTypes` /\n * `overrides`. */\nfunction isAbsoluteHref(href: string): boolean {\n return /^(?:[a-z][a-z0-9+.-]*:)?\\/\\//i.test(href);\n}\n\nexport type SearchResultAction =\n | { kind: 'navigate-same-tab'; href: string }\n | { kind: 'navigate-new-tab'; href: string }\n | { kind: 'ask-ai'; detail: { source: string; ref: ChatRef } }\n | { kind: 'route'; path: string }\n | { kind: 'noop' };\n\nexport function resolveSearchResultAction(\n result: SearchResult,\n source: string,\n runtimeMode?: 'host' | 'embed',\n /** The host's unified content-href seam (`runtime.composeContentUrl`).\n * When wired, an entity row's RAG `externalUrl` is re-resolved through it,\n * so a type the host serves in-app (`hostedTypes`) or deep-links itself\n * (`overrides`) lands on the SAME href the catalog cards and chat cards\n * use. Omitted → the legacy verbatim-`externalUrl` behavior. */\n composeContentUrl?: ComposeContentUrl,\n): SearchResultAction {\n const meta = result.metadata ?? {};\n const externalUrl = meta.externalUrl as string | undefined;\n const rowId = meta.id as string | undefined;\n const sourceRepo = meta.sourceRepo as string | undefined;\n const documentType = meta.documentType as string | undefined;\n if (externalUrl) {\n const targetPlatform = meta.targetPlatform as string | null | undefined;\n // Unified content-href seam — the same call `resolveSourceRowCTA` makes\n // for chat rows. Hosted types relativize the slug out of `externalUrl`;\n // everything else comes back verbatim, so an unwired host (or a row with\n // no `documentType`) keeps today's behavior byte-for-byte.\n const composed =\n composeContentUrl && documentType\n ? composeContentUrl({\n type: documentType,\n identifier: rowId ?? '',\n externalUrl,\n targetPlatform: targetPlatform ?? null,\n })\n : null;\n const href = composed?.href || externalUrl;\n // A RELATIVE composed href means the host claimed this row as in-app.\n // Short-circuit BEFORE `decideNewTab`: in embed mode that helper forces\n // new-tab unconditionally, which would fire `window.open()` on a path\n // that only resolves inside this app.\n if (composed && !isAbsoluteHref(href)) {\n return { kind: 'navigate-same-tab', href };\n }\n // Same pure helper `useNavLink` and `useUnifiedNav` call — single\n // decision rule across cards, chips, and autocomplete rows. Thread\n // the caller's `source` as `currentSource` so the platform-vs-\n // platform comparison matches the hub's pre-migration behavior.\n const isNewTab = decideNewTab({\n href,\n targetPlatform: composed ? composed.targetPlatform : targetPlatform,\n surface: 'useUnifiedNav',\n runtimeMode,\n currentSource: source,\n });\n return isNewTab ? { kind: 'navigate-new-tab', href } : { kind: 'navigate-same-tab', href };\n }\n if (rowId && sourceRepo && documentType) {\n return {\n kind: 'ask-ai',\n detail: {\n source,\n ref: { type: documentType, id: rowId, title: result.title, url: null },\n },\n };\n }\n if (result.path) {\n return { kind: 'route', path: result.path };\n }\n return { kind: 'noop' };\n}\n","'use client';\n\n/**\n * `useDocSearch` — debounced RAG-search hook against `/api/docs/search`.\n *\n * Pure fetch + navigation glue. Embedders can mount this directly\n * (any host with a reverse-proxy that exposes `/api/docs/search` will\n * work). Hub callers wire it into the lib `<DocSearchBar>` for the\n * canonical typeahead dropdown.\n *\n * ## What moved from hub to lib\n *\n * Lifted from `multi-platform-hub/hooks/use-docs.ts:useDocSearch`. Two\n * hub-only concerns are now optional injection points instead of\n * direct imports:\n *\n * - `useDocNavigation()` (hub's in-page doc-tree swap) → optional\n * `onInPageSwap?: (path: string) => boolean` config callback. When\n * present and returns true, the hook treats a same-origin result\n * click as \"handled in-page\"; when absent or returns false, the\n * hook falls back to `onNavigate(path)` (`router.push` on hub,\n * `window.location.assign` on bare embedders).\n * - `traceCompose` (hub-only telemetry) → dropped. The lib has no\n * equivalent runtime-context yet; bring it back when there is one.\n *\n * Everything else (debounce, `useChatRuntime` for embed-mode short-\n * circuit, embed-shim router, the action-resolver + result-mapper) is\n * now lib-resident.\n *\n * ## Result hrefs go through the content-href seam\n *\n * A row's href is NOT the RAG `externalUrl` verbatim any more: when the\n * host wires `runtime.composeContentUrl` (or passes `composeContentUrl`\n * here), the row is re-resolved through it — the SAME seam the catalog\n * cards and the chat entity cards use. So a host that serves onboarding\n * guides at `/help-center/onboarding-guides/<slug>` gets that path from\n * the dropdown too, instead of bouncing the user to the canonical hub\n * URL. Unwired hosts keep the verbatim-`externalUrl` behavior.\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { useChatRuntime } from '../../../contexts/chat-runtime-context';\nimport { useRouter } from '../../../embed-shims';\nimport { useDebounce } from '../../../hooks/ui/use-debounce';\nimport type { ComposeContentUrl } from '../../../utils/content-href';\nimport { contentFetch } from '../../../utils/embed-content-fetch';\nimport {\n resolveExternalNavigation,\n stripSameOriginToPath,\n NEW_TAB_FEATURES,\n} from '../../chat/utils/chat-nav-resolution';\nimport type { SearchResult } from '../../ui/search-input';\nimport { mapDocSearchResults } from './map-doc-search-results';\nimport { resolveSearchResultAction } from './resolve-search-result-action';\nimport type { DocSearchResult } from './types';\n\nexport interface UseDocSearchConfig {\n /** Discriminator passed to `/api/docs/search?source=` (e.g.\n * `'openframe'`). Embedders set it to whatever discriminator their\n * reverse-proxy expects. */\n source: string;\n /** Base route prefix this search lives under (e.g. `'/onboarding-guides'`).\n * When a result's href starts with `${baseRoute}/`, the hook\n * attempts the optional in-page swap path before falling through\n * to a full nav. */\n baseRoute: string;\n /** Imperative navigation fallback. Called when no override\n * (in-page swap, new-tab) applies. Hub callers pass\n * `(path) => router.push(path)`; embedders pass an equivalent. */\n onNavigate: (path: string) => void;\n /** Optional `RagTableConfig.id` list to narrow the search to specific\n * tables (e.g. `['onboarding-guides']`). Forwarded to\n * `/api/docs/search?tableIds=…` which intersects with the source's\n * standing set. */\n tableIds?: string[];\n /** Optional in-page swap callback. When the result's href is under\n * `baseRoute` AND this callback returns true, the hook treats the\n * click as handled in-page (no router push). Hub's\n * `<DocumentationSection>` wires this to\n * `useDocNavigation().navigate(path)`. */\n onInPageSwap?: (path: string) => boolean;\n /** Optional endpoint override. Defaults to `'/api/docs/search'`\n * (the hub's reverse-proxy route). Embedders with a different\n * path can override. */\n searchEndpoint?: string;\n /** Optional content-href seam override. Same fall-back chain as\n * `searchEndpoint`: prop → `runtime.composeContentUrl` → none. Wired,\n * it makes a result row honor the host's `hostedTypes` / `overrides`\n * instead of navigating to the RAG's canonical hub URL — so a row\n * lands where the catalog card for the same entity lands. */\n composeContentUrl?: ComposeContentUrl;\n}\n\n/** One shared empty array, so \"no matches\" keeps a stable identity. */\nconst NO_RESULTS: SearchResult[] = [];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/** `Array.isArray` narrows `unknown` to `any[]`; this keeps the elements `unknown`. */\nfunction isUnknownArray(value: unknown): value is readonly unknown[] {\n return Array.isArray(value);\n}\n\nfunction readString(source: Record<string, unknown>, key: string): string | undefined {\n const value = source[key];\n return typeof value === 'string' ? value : undefined;\n}\n\n/**\n * Decode `/api/docs/search`'s `{ success, data }` envelope.\n *\n * `Response.json()` is typed `any`, so the rows used to reach\n * `mapDocSearchResults` through an `as DocSearchResult[]` that asserted a\n * shape nothing had checked. `path` and `name` are the two that bite: the\n * mapper uses `path` as the row's React `id` and `name` as its title, so a row\n * missing either renders as a blank, key-colliding entry in the dropdown.\n * Such rows are dropped; the rest of the result set still lists.\n *\n * Returns null when the envelope itself is not a successful result set.\n */\nfunction toDocSearchResults(payload: unknown): DocSearchResult[] | null {\n if (!isRecord(payload) || !payload.success || !isUnknownArray(payload.data)) return null;\n\n const rows: DocSearchResult[] = [];\n for (const entry of payload.data) {\n if (!isRecord(entry)) continue;\n const path = readString(entry, 'path');\n const name = readString(entry, 'name');\n const type = entry.type;\n if (path === undefined || name === undefined) continue;\n\n const targetPlatform = entry.targetPlatform;\n rows.push({\n path,\n name,\n type: type === 'folder' ? 'folder' : 'file',\n snippet: readString(entry, 'snippet') ?? '',\n matchType: entry.matchType === 'title' ? 'title' : 'content',\n documentType: readString(entry, 'documentType'),\n externalUrl: readString(entry, 'externalUrl'),\n targetPlatform: typeof targetPlatform === 'string' ? targetPlatform : null,\n sourceRepo: readString(entry, 'sourceRepo'),\n entityId: readString(entry, 'entityId'),\n });\n }\n return rows;\n}\n\nexport function useDocSearch(config: UseDocSearchConfig) {\n const { source, baseRoute, onNavigate, tableIds, onInPageSwap, searchEndpoint, composeContentUrl } = config;\n const tableIdsKey = tableIds && tableIds.length > 0 ? tableIds.join(',') : '';\n\n const router = useRouter();\n // Optional chat-runtime read — when present and mode='embed' the\n // search-result row click short-circuits to a new-tab open against\n // the absolutized URL. Null/host preserves today's behavior.\n // Also used as the proxy-prefix fallback for `searchEndpoint`, matching\n // how tickets resolves `findTicketUrl`.\n const runtime = useChatRuntime();\n const resolvedSearchEndpoint = searchEndpoint ?? runtime?.endpoints.docsSearchUrl ?? '/api/docs/search';\n // Content-href seam, same prop → runtime → none chain. Threading it into\n // `resolveSearchResultAction` is what makes the dropdown obey the host's\n // route map; without it the row navigates to the RAG's canonical hub URL\n // even when the host serves that content in-app under a different path.\n const resolvedComposeContentUrl = composeContentUrl ?? runtime?.composeContentUrl;\n\n const [query, setQuery] = useState('');\n const [results, setResults] = useState<SearchResult[]>(NO_RESULTS);\n const [isFetching, setIsFetching] = useState(false);\n const debouncedQuery = useDebounce(query, 300);\n\n // Falling below the two-character floor empties the dropdown. Adjusted while\n // rendering (React's documented pattern for a reset the render already has\n // the answer to) rather than from an effect, which committed one more frame\n // of the previous matches under a query that no longer searches for them.\n const searchable = debouncedQuery.trim().length >= 2;\n const [wasSearchable, setWasSearchable] = useState(searchable);\n if (wasSearchable !== searchable) {\n setWasSearchable(searchable);\n if (!searchable) {\n setResults(NO_RESULTS);\n setIsFetching(false);\n }\n }\n\n useEffect(() => {\n if (!searchable) return undefined;\n\n let cancelled = false;\n\n async function fetchResults() {\n setIsFetching(true);\n try {\n const params = new URLSearchParams({\n q: debouncedQuery,\n source,\n limit: '10',\n });\n if (tableIdsKey) params.set('tableIds', tableIdsKey);\n\n const response = await contentFetch(`${resolvedSearchEndpoint}?${params.toString()}`);\n if (!response.ok) {\n throw new Error(`Search request failed: ${response.status}`);\n }\n\n const json: unknown = await response.json();\n\n if (!cancelled) {\n // An envelope that is not a successful result set now clears the\n // dropdown instead of leaving it untouched. Previously a 200 that\n // carried `success: false` (or a non-array `data`) fell through\n // every branch, so the PREVIOUS query's matches stayed on screen\n // under the new query — the throw path already reset them.\n const rows = toDocSearchResults(json);\n setResults(rows ? mapDocSearchResults(rows) : NO_RESULTS);\n }\n } catch (error) {\n console.error('Doc search error:', error);\n if (!cancelled) {\n setResults(NO_RESULTS);\n }\n } finally {\n if (!cancelled) {\n setIsFetching(false);\n }\n }\n }\n\n // Never rejects — try/catch/finally, every write gated on `cancelled`.\n void fetchResults();\n\n return () => {\n cancelled = true;\n };\n }, [debouncedQuery, searchable, source, tableIdsKey, resolvedSearchEndpoint]);\n\n // Derived loading state — single source of truth for \"should the\n // dropdown show 'Loading...' instead of 'No results found'\":\n const isLoading = query.trim().length >= 2 && (query !== debouncedQuery || isFetching);\n\n // Track whether dropdown should stay open (external link opened in new tab).\n const [keepOpen, setKeepOpen] = useState(false);\n\n const handleResultSelect = useCallback(\n (\n result: SearchResult,\n modifiers?: {\n metaKey?: boolean;\n ctrlKey?: boolean;\n shiftKey?: boolean;\n altKey?: boolean;\n button?: number;\n },\n ) => {\n const action = resolveSearchResultAction(result, source, runtime?.navigation.mode, resolvedComposeContentUrl);\n // Modifier / non-primary mouse click → force new tab regardless of\n // same-tab/new-tab decision. The dropdown row is a `<div>`, not an\n // `<a target=\"_blank\">`, so the browser doesn't background-tab\n // natively on cmd-click. Honor it explicitly here for parity with\n // the anchor-based surfaces (cards, chips, related-content). Plain\n // Enter from the keyboard passes `modifiers === undefined`.\n const wantsNewTab =\n modifiers &&\n (modifiers.metaKey ||\n modifiers.ctrlKey ||\n modifiers.shiftKey ||\n modifiers.altKey ||\n (typeof modifiers.button === 'number' && modifiers.button !== 0));\n switch (action.kind) {\n case 'navigate-same-tab': {\n if (wantsNewTab) {\n setKeepOpen(true);\n window.open(action.href, '_blank', NEW_TAB_FEATURES);\n return;\n }\n // Same-origin click:\n // 1. If the href is under the current doc-tree's baseRoute AND\n // an `onInPageSwap` callback is wired AND returns true →\n // consider in-page swap handled.\n // 2. Otherwise → embed-shim `router.push()` (soft RSC nav on\n // Next.js hosts, window.location.assign on bare hosts).\n setKeepOpen(false);\n const path =\n baseRoute && action.href.startsWith(`${baseRoute}/`) ? action.href.slice(baseRoute.length + 1) : null;\n if (path && onInPageSwap?.(path)) return;\n router.push(stripSameOriginToPath(action.href));\n return;\n }\n case 'navigate-new-tab':\n // Cross-origin (e.g. clicking a flamingo.run release from\n // product-hub) — open in a new tab. Keep dropdown open so the\n // user can pick another result without re-searching.\n setKeepOpen(true);\n // Embed-mode short-circuit — the row was clicked while the panel is\n // hosted inside an embedding app, so absolutize against the\n // embedder-supplied content origin and honor its `openExternal`\n // override. Not the only path an embed host takes: a composed\n // RELATIVE href resolves to `navigate-same-tab` above and never\n // reaches `decideNewTab`, which is what lets an embedder keep its\n // own in-app routes in-app.\n if (runtime?.navigation.mode === 'embed') {\n const targetPlatform = (result.metadata?.targetPlatform as string | null | undefined) ?? null;\n resolveExternalNavigation({\n href: action.href,\n targetPlatform,\n runtime,\n }).open();\n return;\n }\n window.open(action.href, '_blank', NEW_TAB_FEATURES);\n return;\n case 'ask-ai':\n // Row is searchable-but-not-openable (cap_table positions,\n // financial-kpi snapshots, anything backed by\n // `resolveUrl: () => null`). Dispatch a CustomEvent that\n // GlobalAskAI listens for — opens chat + drills via\n // `entityIdFilter` (primary-key only, same as inline-card Ask).\n setKeepOpen(false);\n window.dispatchEvent(new CustomEvent('ask-ai:open-with-ref', { detail: action.detail }));\n return;\n case 'route':\n // Final fallback: legacy navigation by path. Hits when a row\n // has neither URL nor pk metadata — a mapper/API regression.\n setKeepOpen(false);\n onNavigate(action.path);\n return;\n case 'noop':\n return;\n }\n },\n [onNavigate, source, baseRoute, router, onInPageSwap, runtime, resolvedComposeContentUrl],\n );\n\n // Reset keepOpen when query changes. Adjusted while rendering, not from an\n // effect: the dropdown's open state is read from this in the same render the\n // keystroke produced, so an effect held it pinned open for one extra frame.\n const [keepOpenForQuery, setKeepOpenForQuery] = useState(query);\n if (keepOpenForQuery !== query) {\n setKeepOpenForQuery(query);\n setKeepOpen(false);\n }\n\n return {\n query,\n setQuery,\n results,\n isLoading,\n handleResultSelect,\n keepDropdownOpen: keepOpen,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-TQMZFXWZ.cjs","../src/components/shared/doc-search/format-relative-path.ts","../src/components/shared/doc-search/doc-search-result-row.tsx","../src/components/shared/doc-search/doc-search-bar.tsx","../src/components/shared/doc-search/map-doc-search-results.ts","../src/components/shared/doc-search/resolve-search-result-action.ts","../src/components/shared/doc-search/use-doc-search.ts"],"names":["jsx"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-TQMZFXWZ.cjs","sourcesContent":[null,"/**\n * Format a full document path as a breadcrumb trail.\n * Shows parent folders only (excludes the last segment / filename).\n *\n * @example\n * formatRelativePath('openframe-oss-tenant/architecture/api-controllers.md')\n * // → 'Openframe oss tenant / Architecture'\n */\nexport function formatRelativePath(fullPath: string): string {\n if (!fullPath) return '';\n const segments = fullPath.replace(/\\.md$/, '').split('/');\n // Show only parent path (exclude the filename itself since the title already shows it)\n const parentSegments = segments.length > 1 ? segments.slice(0, -1) : segments;\n return parentSegments.map(seg => seg.charAt(0).toUpperCase() + seg.slice(1).replace(/-/g, ' ')).join(' / ');\n}\n","'use client';\n\n/**\n * Single row in the `<SearchInput>` dropdown — the standard layout\n * used by every doc-search-backed surface (company-hub data-room\n * search bar, onboarding-guide catalog search, …). Single source of\n * truth for the row appearance so search dropdowns are visually\n * identical everywhere.\n *\n * Resolves the source icon via the same `resolveSourceIcon()`\n * registry the inline chat-card refs use, so a row pointing at e.g.\n * an onboarding-guide surfaces the SAME `<GraduationCap>` glyph the\n * chat card surfaces — no cross-surface drift.\n */\n\nimport { resolveSourceIcon } from '../../chat/utils/source-row-cta';\nimport { formatRelativePath } from './format-relative-path';\n\n/**\n * Minimal result shape this row renders. Compatible with any\n * doc-search hook whose result type exposes `{ title?, path?,\n * metadata? }`. The two hub consumers (onboarding-guide catalog,\n * data-room sidebar) both satisfy this shape via their\n * `useDocSearch` hook result.\n */\nexport interface DocSearchResultRowEntry {\n title?: string;\n path?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface DocSearchResultRowProps {\n result: DocSearchResultRowEntry;\n isHighlighted: boolean;\n}\n\nexport function DocSearchResultRow({ result, isHighlighted }: DocSearchResultRowProps) {\n const docType = (result.metadata?.documentType as string) || undefined;\n const sourceRepo = (result.metadata?.sourceRepo as string) || undefined;\n const { Icon: SourceIcon, label: iconLabel } = resolveSourceIcon({\n sourceRepo,\n documentType: docType,\n });\n const isGroup = result.metadata?.isGroup as boolean | undefined;\n\n return (\n <div className=\"flex w-full min-w-0 items-center gap-3\">\n <span className=\"flex-shrink-0 text-ods-text-secondary\" title={iconLabel}>\n <SourceIcon className=\"size-4\" />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className={`truncate text-h6 ${isHighlighted ? 'text-ods-accent' : 'text-ods-text-primary'}`}>\n {result.title || result.path}\n </div>\n {!isGroup && result.path?.includes('/') && (\n <div className=\"mt-0.5 truncate text-ods-text-secondary text-h6\">{formatRelativePath(result.path)}</div>\n )}\n </div>\n </div>\n );\n}\n","'use client';\n\n/**\n * `<DocSearchBar>` — the canonical RAG-search dropdown surface.\n *\n * Mounted by every doc-search consumer (data-room sidebar, onboarding-\n * guide catalog, and any future surface that needs typeahead against\n * `/api/docs/search`). Wraps `<SearchInput>` with the lib's standard\n * `<DocSearchResultRow>` so the dropdown looks identical everywhere.\n *\n * ## Why a presentation component, not a \"search bar that owns its\n * own hook\"\n *\n * The data-fetching hook (`useDocSearch`) lives hub-side because it\n * depends on hub-only context (`useDocNavigation`, the rag-table-\n * config registry, the hub's `decideNewTab` helper). Moving the hook\n * would cascade ~5 more file migrations into the lib.\n *\n * Instead, the hook stays hub-side and callers pass its result into\n * this component as plain props. Both consumers shrink to ~5 lines.\n */\n\nimport type { ReactNode } from 'react';\nimport { SearchInput, type SearchResult } from '../../ui/search-input';\nimport { DocSearchResultRow } from './doc-search-result-row';\n\nexport interface DocSearchBarProps {\n placeholder: string;\n query: string;\n onQueryChange: (value: string) => void;\n /** Hook-fetched results. Reuses the lib's `<SearchInput>` `SearchResult`\n * shape directly so callers don't translate. */\n results: SearchResult[];\n isLoading: boolean;\n /** Result selection handler. Mirrors `<SearchInput>` — the second\n * `modifiers` argument is preserved so cmd-click / shift-click on\n * a result row still forces new-tab behavior. Hub `useDocSearch`\n * reads these to short-circuit to `window.open()`. */\n onResultSelect: (\n result: SearchResult,\n modifiers?: {\n metaKey?: boolean;\n ctrlKey?: boolean;\n shiftKey?: boolean;\n altKey?: boolean;\n button?: number;\n },\n ) => void;\n /** Lets the caller's hook force the dropdown open after a recent\n * internal action (e.g. result navigation). `undefined` falls back\n * to `<SearchInput>`'s built-in focus/hover heuristics. */\n showDropdown?: boolean;\n /** Defaults to 2 — matches the existing data-room and onboarding-\n * guide consumers. Override only if a surface needs different\n * typeahead semantics. */\n minQueryLength?: number;\n /** Defaults to 0 — both existing consumers debounce inside the\n * hook, not the input. */\n debounceMs?: number;\n className?: string;\n /** Optional row-renderer override. Defaults to the lib's standard\n * `<DocSearchResultRow>` (source icon + title + path breadcrumb).\n * Override only when a surface needs custom row chrome. */\n renderResult?: (result: SearchResult, isHighlighted: boolean) => ReactNode;\n}\n\nexport function DocSearchBar({\n placeholder,\n query,\n onQueryChange,\n results,\n isLoading,\n onResultSelect,\n showDropdown,\n minQueryLength = 2,\n debounceMs = 0,\n className = 'w-full',\n renderResult,\n}: DocSearchBarProps) {\n return (\n <SearchInput\n placeholder={placeholder}\n value={query}\n onChange={onQueryChange}\n results={results}\n isLoading={isLoading}\n onResultSelect={onResultSelect}\n showDropdown={showDropdown || undefined}\n debounceMs={debounceMs}\n minQueryLength={minQueryLength}\n className={className}\n renderResult={\n renderResult ??\n ((result, isHighlighted) => <DocSearchResultRow result={result} isHighlighted={isHighlighted} />)\n }\n />\n );\n}\n","/**\n * Map RAG `/api/docs/search` wire results into the `<DocSearchBar>`\n * dropdown's row shape, collapsing entity-table rows into grouped\n * results so the dropdown lists ONE \"Cap Table (12 records)\" row\n * instead of 12 individual rows.\n *\n * Pure transform — no telemetry, no navigation, no React deps. Lifted\n * from the hub's `hooks/use-docs.ts:mapDocSearchResults` (the hub's\n * `traceCompose` call was hub-only telemetry and is intentionally\n * dropped — callers that want logging can wrap this helper).\n */\n\nimport type { SearchResult } from '../../ui/search-input';\nimport type { DocSearchResult } from './types';\n\n/** Source repos that should be collapsed into grouped results in the search bar.\n * Only financial tables (all rows link to the same admin page).\n * Content tables (blog, webinar, podcast, etc.) stay individual since each has a unique URL. */\nconst SEARCH_GROUP_REPOS = new Set([\n 'financial-cap-table',\n 'financial-kpis',\n 'financial-pnl',\n 'financial-balance-sheet',\n 'financial-cash-flow',\n]);\n\nconst ENTITY_LABELS: Record<string, string> = {\n 'financial-cap-table': 'Cap Table',\n 'financial-kpis': 'Financial KPIs',\n 'financial-pnl': 'Profit & Loss',\n 'financial-balance-sheet': 'Balance Sheets',\n 'financial-cash-flow': 'Cash Flow',\n 'blog-posts': 'Blog Posts',\n 'product-releases': 'Product Releases',\n 'case-studies': 'Case Studies',\n webinars: 'Webinars',\n events: 'Events',\n podcasts: 'Podcasts',\n};\n\nexport function mapDocSearchResults(docs: DocSearchResult[]): SearchResult[] {\n const entityGroups = new Map<string, DocSearchResult[]>();\n // Track insertion order — groups appear where the FIRST row of that\n // repo appeared in the response.\n const order: Array<{ type: 'entity'; repo: string } | { type: 'doc'; doc: DocSearchResult }> = [];\n const seenRepos = new Set<string>();\n\n for (const doc of docs) {\n if (doc.sourceRepo && SEARCH_GROUP_REPOS.has(doc.sourceRepo)) {\n const group = entityGroups.get(doc.sourceRepo) || [];\n group.push(doc);\n entityGroups.set(doc.sourceRepo, group);\n if (!seenRepos.has(doc.sourceRepo)) {\n seenRepos.add(doc.sourceRepo);\n order.push({ type: 'entity', repo: doc.sourceRepo });\n }\n } else {\n order.push({ type: 'doc', doc });\n }\n }\n\n const results: SearchResult[] = [];\n for (const entry of order) {\n if (entry.type === 'entity') {\n // An 'entity' entry is only pushed AFTER its group lands in the map, so\n // a miss is unreachable — but dropping one group beats throwing away the\n // whole search dropdown if that ever stops holding.\n const rows = entityGroups.get(entry.repo);\n if (!rows || rows.length === 0) continue;\n const label = ENTITY_LABELS[entry.repo] || entry.repo;\n results.push({\n id: `group-${entry.repo}`,\n title: `${label} (${rows.length} ${rows.length === 1 ? 'record' : 'records'})`,\n path: rows[0].path,\n type: 'file',\n metadata: {\n documentType: rows[0].documentType,\n externalUrl: rows[0].externalUrl,\n sourceRepo: entry.repo,\n id: rows[0].entityId,\n isGroup: true,\n items: rows.map(r => ({\n name: r.name,\n externalUrl: r.externalUrl,\n id: r.entityId,\n sourceRepo: r.sourceRepo,\n documentType: r.documentType,\n })),\n },\n });\n } else {\n const doc = entry.doc;\n const isNonMarkdown = doc.documentType && doc.documentType !== 'markdown';\n results.push({\n id: doc.path,\n title: doc.name,\n description: isNonMarkdown ? doc.name : doc.snippet,\n path: doc.path,\n type: doc.type,\n metadata: {\n matchType: doc.matchType,\n ...(doc.documentType ? { documentType: doc.documentType } : {}),\n ...(doc.externalUrl ? { externalUrl: doc.externalUrl } : {}),\n ...(doc.targetPlatform != null ? { targetPlatform: doc.targetPlatform } : {}),\n ...(doc.sourceRepo ? { sourceRepo: doc.sourceRepo } : {}),\n ...(doc.entityId ? { id: doc.entityId } : {}),\n },\n });\n }\n }\n\n return results;\n}\n","/**\n * Resolve what should happen when the user picks a search result.\n * Returns one of five typed actions so the caller is a single switch.\n *\n * Resolution order:\n * 1. `externalUrl` present → resolve the href through the host's\n * `composeContentUrl` seam (when wired), then use `decideNewTab` to\n * choose same-tab vs new-tab against the resolved `targetPlatform`.\n * 2. Row has `id` + `sourceRepo` + `documentType` → synth an Ask-AI\n * action (entity drill-in via primary key, no URL).\n * 3. Row has only `path` → legacy navigation fallback.\n * 4. Nothing actionable → noop.\n *\n * Lifted from the hub's `hooks/use-docs.ts:resolveSearchResultAction`.\n * Pure — no React, no telemetry.\n */\n\nimport type { ComposeContentUrl } from '../../../utils/content-href';\nimport type { ChatRef } from '../../chat/chat-ref.types';\nimport { decideNewTab } from '../../chat/utils/decide-new-tab';\nimport type { SearchResult } from '../../ui/search-input';\n\n/** True for `https://…` and protocol-relative `//…`. A composed href that is\n * NOT absolute is an in-app route the host claimed via `hostedTypes` /\n * `overrides`. */\nfunction isAbsoluteHref(href: string): boolean {\n return /^(?:[a-z][a-z0-9+.-]*:)?\\/\\//i.test(href);\n}\n\nexport type SearchResultAction =\n | { kind: 'navigate-same-tab'; href: string }\n | { kind: 'navigate-new-tab'; href: string }\n | { kind: 'ask-ai'; detail: { source: string; ref: ChatRef } }\n | { kind: 'route'; path: string }\n | { kind: 'noop' };\n\nexport function resolveSearchResultAction(\n result: SearchResult,\n source: string,\n runtimeMode?: 'host' | 'embed',\n /** The host's unified content-href seam (`runtime.composeContentUrl`).\n * When wired, an entity row's RAG `externalUrl` is re-resolved through it,\n * so a type the host serves in-app (`hostedTypes`) or deep-links itself\n * (`overrides`) lands on the SAME href the catalog cards and chat cards\n * use. Omitted → the legacy verbatim-`externalUrl` behavior. */\n composeContentUrl?: ComposeContentUrl,\n): SearchResultAction {\n const meta = result.metadata ?? {};\n const externalUrl = meta.externalUrl as string | undefined;\n const rowId = meta.id as string | undefined;\n const sourceRepo = meta.sourceRepo as string | undefined;\n const documentType = meta.documentType as string | undefined;\n if (externalUrl) {\n const targetPlatform = meta.targetPlatform as string | null | undefined;\n // Unified content-href seam — the same call `resolveSourceRowCTA` makes\n // for chat rows. Hosted types relativize the slug out of `externalUrl`;\n // everything else comes back verbatim, so an unwired host (or a row with\n // no `documentType`) keeps today's behavior byte-for-byte.\n const composed =\n composeContentUrl && documentType\n ? composeContentUrl({\n type: documentType,\n identifier: rowId ?? '',\n externalUrl,\n targetPlatform: targetPlatform ?? null,\n })\n : null;\n const href = composed?.href || externalUrl;\n // A RELATIVE composed href means the host claimed this row as in-app.\n // Short-circuit BEFORE `decideNewTab`: in embed mode that helper forces\n // new-tab unconditionally, which would fire `window.open()` on a path\n // that only resolves inside this app.\n if (composed && !isAbsoluteHref(href)) {\n return { kind: 'navigate-same-tab', href };\n }\n // Same pure helper `useNavLink` and `useUnifiedNav` call — single\n // decision rule across cards, chips, and autocomplete rows. Thread\n // the caller's `source` as `currentSource` so the platform-vs-\n // platform comparison matches the hub's pre-migration behavior.\n const isNewTab = decideNewTab({\n href,\n targetPlatform: composed ? composed.targetPlatform : targetPlatform,\n surface: 'useUnifiedNav',\n runtimeMode,\n currentSource: source,\n });\n return isNewTab ? { kind: 'navigate-new-tab', href } : { kind: 'navigate-same-tab', href };\n }\n if (rowId && sourceRepo && documentType) {\n return {\n kind: 'ask-ai',\n detail: {\n source,\n ref: { type: documentType, id: rowId, title: result.title, url: null },\n },\n };\n }\n if (result.path) {\n return { kind: 'route', path: result.path };\n }\n return { kind: 'noop' };\n}\n","'use client';\n\n/**\n * `useDocSearch` — debounced RAG-search hook against `/api/docs/search`.\n *\n * Pure fetch + navigation glue. Embedders can mount this directly\n * (any host with a reverse-proxy that exposes `/api/docs/search` will\n * work). Hub callers wire it into the lib `<DocSearchBar>` for the\n * canonical typeahead dropdown.\n *\n * ## What moved from hub to lib\n *\n * Lifted from `multi-platform-hub/hooks/use-docs.ts:useDocSearch`. Two\n * hub-only concerns are now optional injection points instead of\n * direct imports:\n *\n * - `useDocNavigation()` (hub's in-page doc-tree swap) → optional\n * `onInPageSwap?: (path: string) => boolean` config callback. When\n * present and returns true, the hook treats a same-origin result\n * click as \"handled in-page\"; when absent or returns false, the\n * hook falls back to `onNavigate(path)` (`router.push` on hub,\n * `window.location.assign` on bare embedders).\n * - `traceCompose` (hub-only telemetry) → dropped. The lib has no\n * equivalent runtime-context yet; bring it back when there is one.\n *\n * Everything else (debounce, `useChatRuntime` for embed-mode short-\n * circuit, embed-shim router, the action-resolver + result-mapper) is\n * now lib-resident.\n *\n * ## Result hrefs go through the content-href seam\n *\n * A row's href is NOT the RAG `externalUrl` verbatim any more: when the\n * host wires `runtime.composeContentUrl` (or passes `composeContentUrl`\n * here), the row is re-resolved through it — the SAME seam the catalog\n * cards and the chat entity cards use. So a host that serves onboarding\n * guides at `/help-center/onboarding-guides/<slug>` gets that path from\n * the dropdown too, instead of bouncing the user to the canonical hub\n * URL. Unwired hosts keep the verbatim-`externalUrl` behavior.\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { useChatRuntime } from '../../../contexts/chat-runtime-context';\nimport { useRouter } from '../../../embed-shims';\nimport { useDebounce } from '../../../hooks/ui/use-debounce';\nimport type { ComposeContentUrl } from '../../../utils/content-href';\nimport { contentFetch } from '../../../utils/embed-content-fetch';\nimport {\n resolveExternalNavigation,\n stripSameOriginToPath,\n NEW_TAB_FEATURES,\n} from '../../chat/utils/chat-nav-resolution';\nimport type { SearchResult } from '../../ui/search-input';\nimport { mapDocSearchResults } from './map-doc-search-results';\nimport { resolveSearchResultAction } from './resolve-search-result-action';\nimport type { DocSearchResult } from './types';\n\nexport interface UseDocSearchConfig {\n /** Discriminator passed to `/api/docs/search?source=` (e.g.\n * `'openframe'`). Embedders set it to whatever discriminator their\n * reverse-proxy expects. */\n source: string;\n /** Base route prefix this search lives under (e.g. `'/onboarding-guides'`).\n * When a result's href starts with `${baseRoute}/`, the hook\n * attempts the optional in-page swap path before falling through\n * to a full nav. */\n baseRoute: string;\n /** Imperative navigation fallback. Called when no override\n * (in-page swap, new-tab) applies. Hub callers pass\n * `(path) => router.push(path)`; embedders pass an equivalent. */\n onNavigate: (path: string) => void;\n /** Optional `RagTableConfig.id` list to narrow the search to specific\n * tables (e.g. `['onboarding-guides']`). Forwarded to\n * `/api/docs/search?tableIds=…` which intersects with the source's\n * standing set. */\n tableIds?: string[];\n /** Optional in-page swap callback. When the result's href is under\n * `baseRoute` AND this callback returns true, the hook treats the\n * click as handled in-page (no router push). Hub's\n * `<DocumentationSection>` wires this to\n * `useDocNavigation().navigate(path)`. */\n onInPageSwap?: (path: string) => boolean;\n /** Optional endpoint override. Defaults to `'/api/docs/search'`\n * (the hub's reverse-proxy route). Embedders with a different\n * path can override. */\n searchEndpoint?: string;\n /** Optional content-href seam override. Same fall-back chain as\n * `searchEndpoint`: prop → `runtime.composeContentUrl` → none. Wired,\n * it makes a result row honor the host's `hostedTypes` / `overrides`\n * instead of navigating to the RAG's canonical hub URL — so a row\n * lands where the catalog card for the same entity lands. */\n composeContentUrl?: ComposeContentUrl;\n}\n\n/** One shared empty array, so \"no matches\" keeps a stable identity. */\nconst NO_RESULTS: SearchResult[] = [];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/** `Array.isArray` narrows `unknown` to `any[]`; this keeps the elements `unknown`. */\nfunction isUnknownArray(value: unknown): value is readonly unknown[] {\n return Array.isArray(value);\n}\n\nfunction readString(source: Record<string, unknown>, key: string): string | undefined {\n const value = source[key];\n return typeof value === 'string' ? value : undefined;\n}\n\n/**\n * Decode `/api/docs/search`'s `{ success, data }` envelope.\n *\n * `Response.json()` is typed `any`, so the rows used to reach\n * `mapDocSearchResults` through an `as DocSearchResult[]` that asserted a\n * shape nothing had checked. `path` and `name` are the two that bite: the\n * mapper uses `path` as the row's React `id` and `name` as its title, so a row\n * missing either renders as a blank, key-colliding entry in the dropdown.\n * Such rows are dropped; the rest of the result set still lists.\n *\n * Returns null when the envelope itself is not a successful result set.\n */\nfunction toDocSearchResults(payload: unknown): DocSearchResult[] | null {\n if (!isRecord(payload) || !payload.success || !isUnknownArray(payload.data)) return null;\n\n const rows: DocSearchResult[] = [];\n for (const entry of payload.data) {\n if (!isRecord(entry)) continue;\n const path = readString(entry, 'path');\n const name = readString(entry, 'name');\n const type = entry.type;\n if (path === undefined || name === undefined) continue;\n\n const targetPlatform = entry.targetPlatform;\n rows.push({\n path,\n name,\n type: type === 'folder' ? 'folder' : 'file',\n snippet: readString(entry, 'snippet') ?? '',\n matchType: entry.matchType === 'title' ? 'title' : 'content',\n documentType: readString(entry, 'documentType'),\n externalUrl: readString(entry, 'externalUrl'),\n targetPlatform: typeof targetPlatform === 'string' ? targetPlatform : null,\n sourceRepo: readString(entry, 'sourceRepo'),\n entityId: readString(entry, 'entityId'),\n });\n }\n return rows;\n}\n\nexport function useDocSearch(config: UseDocSearchConfig) {\n const { source, baseRoute, onNavigate, tableIds, onInPageSwap, searchEndpoint, composeContentUrl } = config;\n const tableIdsKey = tableIds && tableIds.length > 0 ? tableIds.join(',') : '';\n\n const router = useRouter();\n // Optional chat-runtime read — when present and mode='embed' the\n // search-result row click short-circuits to a new-tab open against\n // the absolutized URL. Null/host preserves today's behavior.\n // Also used as the proxy-prefix fallback for `searchEndpoint`, matching\n // how tickets resolves `findTicketUrl`.\n const runtime = useChatRuntime();\n const resolvedSearchEndpoint = searchEndpoint ?? runtime?.endpoints.docsSearchUrl ?? '/api/docs/search';\n // Content-href seam, same prop → runtime → none chain. Threading it into\n // `resolveSearchResultAction` is what makes the dropdown obey the host's\n // route map; without it the row navigates to the RAG's canonical hub URL\n // even when the host serves that content in-app under a different path.\n const resolvedComposeContentUrl = composeContentUrl ?? runtime?.composeContentUrl;\n\n const [query, setQuery] = useState('');\n const [results, setResults] = useState<SearchResult[]>(NO_RESULTS);\n const [isFetching, setIsFetching] = useState(false);\n const debouncedQuery = useDebounce(query, 300);\n\n // Falling below the two-character floor empties the dropdown. Adjusted while\n // rendering (React's documented pattern for a reset the render already has\n // the answer to) rather than from an effect, which committed one more frame\n // of the previous matches under a query that no longer searches for them.\n const searchable = debouncedQuery.trim().length >= 2;\n const [wasSearchable, setWasSearchable] = useState(searchable);\n if (wasSearchable !== searchable) {\n setWasSearchable(searchable);\n if (!searchable) {\n setResults(NO_RESULTS);\n setIsFetching(false);\n }\n }\n\n useEffect(() => {\n if (!searchable) return undefined;\n\n let cancelled = false;\n\n async function fetchResults() {\n setIsFetching(true);\n try {\n const params = new URLSearchParams({\n q: debouncedQuery,\n source,\n limit: '10',\n });\n if (tableIdsKey) params.set('tableIds', tableIdsKey);\n\n const response = await contentFetch(`${resolvedSearchEndpoint}?${params.toString()}`);\n if (!response.ok) {\n throw new Error(`Search request failed: ${response.status}`);\n }\n\n const json: unknown = await response.json();\n\n if (!cancelled) {\n // An envelope that is not a successful result set now clears the\n // dropdown instead of leaving it untouched. Previously a 200 that\n // carried `success: false` (or a non-array `data`) fell through\n // every branch, so the PREVIOUS query's matches stayed on screen\n // under the new query — the throw path already reset them.\n const rows = toDocSearchResults(json);\n setResults(rows ? mapDocSearchResults(rows) : NO_RESULTS);\n }\n } catch (error) {\n console.error('Doc search error:', error);\n if (!cancelled) {\n setResults(NO_RESULTS);\n }\n } finally {\n if (!cancelled) {\n setIsFetching(false);\n }\n }\n }\n\n // Never rejects — try/catch/finally, every write gated on `cancelled`.\n void fetchResults();\n\n return () => {\n cancelled = true;\n };\n }, [debouncedQuery, searchable, source, tableIdsKey, resolvedSearchEndpoint]);\n\n // Derived loading state — single source of truth for \"should the\n // dropdown show 'Loading...' instead of 'No results found'\":\n const isLoading = query.trim().length >= 2 && (query !== debouncedQuery || isFetching);\n\n // Track whether dropdown should stay open (external link opened in new tab).\n const [keepOpen, setKeepOpen] = useState(false);\n\n const handleResultSelect = useCallback(\n (\n result: SearchResult,\n modifiers?: {\n metaKey?: boolean;\n ctrlKey?: boolean;\n shiftKey?: boolean;\n altKey?: boolean;\n button?: number;\n },\n ) => {\n const action = resolveSearchResultAction(result, source, runtime?.navigation.mode, resolvedComposeContentUrl);\n // Modifier / non-primary mouse click → force new tab regardless of\n // same-tab/new-tab decision. The dropdown row is a `<div>`, not an\n // `<a target=\"_blank\">`, so the browser doesn't background-tab\n // natively on cmd-click. Honor it explicitly here for parity with\n // the anchor-based surfaces (cards, chips, related-content). Plain\n // Enter from the keyboard passes `modifiers === undefined`.\n const wantsNewTab =\n modifiers &&\n (modifiers.metaKey ||\n modifiers.ctrlKey ||\n modifiers.shiftKey ||\n modifiers.altKey ||\n (typeof modifiers.button === 'number' && modifiers.button !== 0));\n switch (action.kind) {\n case 'navigate-same-tab': {\n if (wantsNewTab) {\n setKeepOpen(true);\n window.open(action.href, '_blank', NEW_TAB_FEATURES);\n return;\n }\n // Same-origin click:\n // 1. If the href is under the current doc-tree's baseRoute AND\n // an `onInPageSwap` callback is wired AND returns true →\n // consider in-page swap handled.\n // 2. Otherwise → embed-shim `router.push()` (soft RSC nav on\n // Next.js hosts, window.location.assign on bare hosts).\n setKeepOpen(false);\n const path =\n baseRoute && action.href.startsWith(`${baseRoute}/`) ? action.href.slice(baseRoute.length + 1) : null;\n if (path && onInPageSwap?.(path)) return;\n router.push(stripSameOriginToPath(action.href));\n return;\n }\n case 'navigate-new-tab':\n // Cross-origin (e.g. clicking a flamingo.run release from\n // product-hub) — open in a new tab. Keep dropdown open so the\n // user can pick another result without re-searching.\n setKeepOpen(true);\n // Embed-mode short-circuit — the row was clicked while the panel is\n // hosted inside an embedding app, so absolutize against the\n // embedder-supplied content origin and honor its `openExternal`\n // override. Not the only path an embed host takes: a composed\n // RELATIVE href resolves to `navigate-same-tab` above and never\n // reaches `decideNewTab`, which is what lets an embedder keep its\n // own in-app routes in-app.\n if (runtime?.navigation.mode === 'embed') {\n const targetPlatform = (result.metadata?.targetPlatform as string | null | undefined) ?? null;\n resolveExternalNavigation({\n href: action.href,\n targetPlatform,\n runtime,\n }).open();\n return;\n }\n window.open(action.href, '_blank', NEW_TAB_FEATURES);\n return;\n case 'ask-ai':\n // Row is searchable-but-not-openable (cap_table positions,\n // financial-kpi snapshots, anything backed by\n // `resolveUrl: () => null`). Dispatch a CustomEvent that\n // GlobalAskAI listens for — opens chat + drills via\n // `entityIdFilter` (primary-key only, same as inline-card Ask).\n setKeepOpen(false);\n window.dispatchEvent(new CustomEvent('ask-ai:open-with-ref', { detail: action.detail }));\n return;\n case 'route':\n // Final fallback: legacy navigation by path. Hits when a row\n // has neither URL nor pk metadata — a mapper/API regression.\n setKeepOpen(false);\n onNavigate(action.path);\n return;\n case 'noop':\n return;\n }\n },\n [onNavigate, source, baseRoute, router, onInPageSwap, runtime, resolvedComposeContentUrl],\n );\n\n // Reset keepOpen when query changes. Adjusted while rendering, not from an\n // effect: the dropdown's open state is read from this in the same render the\n // keystroke produced, so an effect held it pinned open for one extra frame.\n const [keepOpenForQuery, setKeepOpenForQuery] = useState(query);\n if (keepOpenForQuery !== query) {\n setKeepOpenForQuery(query);\n setKeepOpen(false);\n }\n\n return {\n query,\n setQuery,\n results,\n isLoading,\n handleResultSelect,\n keepDropdownOpen: keepOpen,\n };\n}\n"]}
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
var
|
|
7
|
+
var _chunkCOY57SZPcjs = require('./chunk-COY57SZP.cjs');
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
var
|
|
10
|
+
var _chunkFKXDDJEQcjs = require('./chunk-FKXDDJEQ.cjs');
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
var _chunkY7ITKISQcjs = require('./chunk-Y7ITKISQ.cjs');
|
|
@@ -123,7 +123,7 @@ CveLink.displayName = "CveLink";
|
|
|
123
123
|
// src/components/platform/ToolBadge.tsx
|
|
124
124
|
|
|
125
125
|
var ToolBadge = ({ toolType, className, iconClassName }) => {
|
|
126
|
-
const label =
|
|
126
|
+
const label = _chunkCOY57SZPcjs.getToolLabel.call(void 0, toolType);
|
|
127
127
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunk23V5BSHDcjs.cn.call(void 0, "flex items-center gap-1 text-ods-text-secondary", className), children: [
|
|
128
128
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkYXWGSDK4cjs.ToolIcon, { toolType, className: iconClassName, size: 16 }),
|
|
129
129
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-ods-text-primary text-h4", children: label })
|
|
@@ -134,31 +134,31 @@ ToolBadge.displayName = "ToolBadge";
|
|
|
134
134
|
// src/components/platform/ShellTypeBadge.tsx
|
|
135
135
|
|
|
136
136
|
var shellIconMap = {
|
|
137
|
-
[
|
|
137
|
+
[_chunkCOY57SZPcjs.ShellTypeValues.POWERSHELL]: {
|
|
138
138
|
icon: _chunkGSQM4PDRcjs.PowershellLogoGreyIcon,
|
|
139
139
|
props: { size: 16, className: "text-ods-text-secondary" }
|
|
140
140
|
},
|
|
141
|
-
[
|
|
141
|
+
[_chunkCOY57SZPcjs.ShellTypeValues.CMD]: {
|
|
142
142
|
icon: _chunk5EM6NGBKcjs.CmdIcon,
|
|
143
143
|
props: { size: 16, color: "var(--color-text-secondary)" }
|
|
144
144
|
},
|
|
145
|
-
[
|
|
145
|
+
[_chunkCOY57SZPcjs.ShellTypeValues.BASH]: {
|
|
146
146
|
icon: _chunk5EM6NGBKcjs.BashIcon,
|
|
147
147
|
props: { size: 16, color: "var(--color-text-secondary)" }
|
|
148
148
|
},
|
|
149
|
-
[
|
|
149
|
+
[_chunkCOY57SZPcjs.ShellTypeValues.PYTHON]: {
|
|
150
150
|
icon: _chunkGSQM4PDRcjs.PythonLogoIcon,
|
|
151
151
|
props: { size: 16, className: "text-ods-text-secondary" }
|
|
152
152
|
},
|
|
153
|
-
[
|
|
153
|
+
[_chunkCOY57SZPcjs.ShellTypeValues.NUSHELL]: {
|
|
154
154
|
icon: _chunk5EM6NGBKcjs.NushellIcon,
|
|
155
155
|
props: { size: 16, color: "var(--color-text-secondary)" }
|
|
156
156
|
},
|
|
157
|
-
[
|
|
157
|
+
[_chunkCOY57SZPcjs.ShellTypeValues.DENO]: {
|
|
158
158
|
icon: _chunk5EM6NGBKcjs.DenoIcon,
|
|
159
159
|
props: { size: 16, color: "var(--color-text-secondary)" }
|
|
160
160
|
},
|
|
161
|
-
[
|
|
161
|
+
[_chunkCOY57SZPcjs.ShellTypeValues.SHELL]: {
|
|
162
162
|
icon: _chunk5EM6NGBKcjs.ShellIcon,
|
|
163
163
|
props: { size: 16, color: "var(--color-text-secondary)" }
|
|
164
164
|
}
|
|
@@ -169,7 +169,7 @@ var defaultIconConfig = {
|
|
|
169
169
|
};
|
|
170
170
|
var ShellTypeBadge = ({ shellType, className, iconClassName }) => {
|
|
171
171
|
const normalizedType = _optionalChain([shellType, 'optionalAccess', _ => _.toUpperCase, 'call', _2 => _2()]);
|
|
172
|
-
const label =
|
|
172
|
+
const label = _chunkCOY57SZPcjs.getShellLabel.call(void 0, normalizedType);
|
|
173
173
|
const { icon: IconComponent, props: iconProps } = _nullishCoalesce(shellIconMap[normalizedType], () => ( defaultIconConfig));
|
|
174
174
|
const defaultIconClassName = "className" in iconProps ? iconProps.className : void 0;
|
|
175
175
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunk23V5BSHDcjs.cn.call(void 0, "flex items-center gap-1", className), children: [
|
|
@@ -185,7 +185,7 @@ function formatSupportedPlatforms(platforms) {
|
|
|
185
185
|
if (!platforms || platforms.length === 0) {
|
|
186
186
|
return "All Platforms";
|
|
187
187
|
}
|
|
188
|
-
return platforms.map((platform) =>
|
|
188
|
+
return platforms.map((platform) => _chunkCOY57SZPcjs.getOSLabel.call(void 0, platform)).join(", ");
|
|
189
189
|
}
|
|
190
190
|
function InfoCell({ label, value, avatar, className }) {
|
|
191
191
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunk23V5BSHDcjs.cn.call(void 0, "flex min-w-0 items-center gap-2", className), children: [
|
|
@@ -205,7 +205,7 @@ var ScriptInfoSection = ({
|
|
|
205
205
|
author,
|
|
206
206
|
className
|
|
207
207
|
}) => {
|
|
208
|
-
const shellLabel =
|
|
208
|
+
const shellLabel = _chunkCOY57SZPcjs.getShellLabel.call(void 0, shellType);
|
|
209
209
|
const platformsLabel = formatSupportedPlatforms(supportedPlatforms);
|
|
210
210
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunk23V5BSHDcjs.cn.call(void 0, "overflow-hidden rounded-[6px] border border-ods-border bg-ods-card", className), children: [
|
|
211
211
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "border-b border-ods-border p-4", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col gap-1", children: [
|
|
@@ -416,7 +416,7 @@ function OnboardingWalkthrough({
|
|
|
416
416
|
isStepSkipped,
|
|
417
417
|
allStepsComplete,
|
|
418
418
|
markMultipleComplete
|
|
419
|
-
} =
|
|
419
|
+
} = _chunkFKXDDJEQcjs.useOnboardingState.call(void 0, storageKey);
|
|
420
420
|
const hasAutoMarkedRef = _react.useRef.call(void 0, false);
|
|
421
421
|
const autoMarkingInProgressRef = _react.useRef.call(void 0, false);
|
|
422
422
|
const lastCompletionStatusRef = _react.useRef.call(void 0, null);
|
|
@@ -524,4 +524,4 @@ function OnboardingWalkthrough({
|
|
|
524
524
|
|
|
525
525
|
|
|
526
526
|
exports.SoftwareInfo = SoftwareInfo; exports.SoftwareSourceBadge = SoftwareSourceBadge; exports.CveLink = CveLink; exports.ToolBadge = ToolBadge; exports.ShellTypeBadge = ShellTypeBadge; exports.ScriptInfoSection = ScriptInfoSection; exports.ScriptArguments = ScriptArguments; exports.OnboardingStepCard = OnboardingStepCard; exports.OnboardingWalkthrough = OnboardingWalkthrough;
|
|
527
|
-
//# sourceMappingURL=chunk-
|
|
527
|
+
//# sourceMappingURL=chunk-V7RUX5CX.cjs.map
|