@flamingo-stack/openframe-frontend-core 0.0.287 → 0.0.288-snapshot.20260618172839

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/{chunk-6CSW5TMS.cjs → chunk-6NL7TTDR.cjs} +23 -23
  2. package/dist/{chunk-6CSW5TMS.cjs.map → chunk-6NL7TTDR.cjs.map} +1 -1
  3. package/dist/{chunk-TY2EB7VK.js → chunk-7YLTJXMQ.js} +2 -2
  4. package/dist/{chunk-ZYLQMCHW.js → chunk-BZR546EB.js} +2 -2
  5. package/dist/{chunk-JQ4I743L.cjs → chunk-DFAMTCC4.cjs} +5 -5
  6. package/dist/{chunk-JQ4I743L.cjs.map → chunk-DFAMTCC4.cjs.map} +1 -1
  7. package/dist/{chunk-RG6FNZUA.cjs → chunk-HIABEYRE.cjs} +3 -3
  8. package/dist/{chunk-RG6FNZUA.cjs.map → chunk-HIABEYRE.cjs.map} +1 -1
  9. package/dist/{chunk-OXC72UIP.cjs → chunk-KLXCXNLW.cjs} +7 -5
  10. package/dist/chunk-KLXCXNLW.cjs.map +1 -0
  11. package/dist/{chunk-6AW25OS6.cjs → chunk-LZQ4HSOR.cjs} +25 -25
  12. package/dist/{chunk-6AW25OS6.cjs.map → chunk-LZQ4HSOR.cjs.map} +1 -1
  13. package/dist/chunk-OV3ZCU6X.cjs +619 -0
  14. package/dist/chunk-OV3ZCU6X.cjs.map +1 -0
  15. package/dist/{chunk-EFYXPR43.js → chunk-P2SO7ADJ.js} +7 -5
  16. package/dist/{chunk-EFYXPR43.js.map → chunk-P2SO7ADJ.js.map} +1 -1
  17. package/dist/{chunk-GJDXIVEQ.js → chunk-PYHCHGM5.js} +2 -2
  18. package/dist/{chunk-RWCA2ZQK.js → chunk-Q6S6DCVP.js} +2 -2
  19. package/dist/chunk-UBFYGWFP.js +619 -0
  20. package/dist/chunk-UBFYGWFP.js.map +1 -0
  21. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +1 -1
  22. package/dist/components/chat/index.cjs +2 -2
  23. package/dist/components/chat/index.js +1 -1
  24. package/dist/components/chat/types/api.types.d.ts +11 -4
  25. package/dist/components/chat/types/api.types.d.ts.map +1 -1
  26. package/dist/components/chat/utils/history-merge.d.ts +5 -1
  27. package/dist/components/chat/utils/history-merge.d.ts.map +1 -1
  28. package/dist/components/contact/index.cjs +3 -3
  29. package/dist/components/contact/index.js +2 -2
  30. package/dist/components/embeds/index.cjs +7 -3
  31. package/dist/components/embeds/index.cjs.map +1 -1
  32. package/dist/components/embeds/index.d.ts +2 -0
  33. package/dist/components/embeds/index.d.ts.map +1 -1
  34. package/dist/components/embeds/index.js +6 -2
  35. package/dist/components/embeds/og-link-preview.d.ts +114 -0
  36. package/dist/components/embeds/og-link-preview.d.ts.map +1 -0
  37. package/dist/components/faq/index.cjs +3 -3
  38. package/dist/components/faq/index.js +2 -2
  39. package/dist/components/features/index.cjs +2 -2
  40. package/dist/components/features/index.js +1 -1
  41. package/dist/components/index.cjs +50 -46
  42. package/dist/components/index.cjs.map +1 -1
  43. package/dist/components/index.js +9 -5
  44. package/dist/components/index.js.map +1 -1
  45. package/dist/components/navigation/index.cjs +2 -2
  46. package/dist/components/navigation/index.js +1 -1
  47. package/dist/components/related-content/index.cjs +3 -3
  48. package/dist/components/related-content/index.js +2 -2
  49. package/dist/components/tickets/index.cjs +45 -45
  50. package/dist/components/tickets/index.js +3 -3
  51. package/dist/components/ui/index.cjs +2 -2
  52. package/dist/components/ui/index.js +1 -1
  53. package/dist/index.cjs +2 -2
  54. package/dist/index.js +1 -1
  55. package/package.json +1 -1
  56. package/src/components/chat/hooks/use-realtime-chunk-processor.ts +3 -1
  57. package/src/components/chat/types/api.types.ts +9 -6
  58. package/src/components/chat/utils/__tests__/history-merge.test.ts +62 -0
  59. package/src/components/chat/utils/history-merge.ts +5 -1
  60. package/src/components/embeds/index.ts +7 -0
  61. package/src/components/embeds/og-link-preview.tsx +446 -0
  62. package/dist/chunk-MV67MBV3.js +0 -290
  63. package/dist/chunk-MV67MBV3.js.map +0 -1
  64. package/dist/chunk-MWS25U4U.cjs +0 -290
  65. package/dist/chunk-MWS25U4U.cjs.map +0 -1
  66. package/dist/chunk-OXC72UIP.cjs.map +0 -1
  67. /package/dist/{chunk-TY2EB7VK.js.map → chunk-7YLTJXMQ.js.map} +0 -0
  68. /package/dist/{chunk-ZYLQMCHW.js.map → chunk-BZR546EB.js.map} +0 -0
  69. /package/dist/{chunk-GJDXIVEQ.js.map → chunk-PYHCHGM5.js.map} +0 -0
  70. /package/dist/{chunk-RWCA2ZQK.js.map → chunk-Q6S6DCVP.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-MWS25U4U.cjs","../src/components/embeds/embed-iframe.tsx","../src/components/embeds/pdf-viewer.tsx","../src/components/embeds/google-sheets-viewer.tsx","../src/components/embeds/figma-embed.tsx"],"names":["jsx","jsxs"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-MWS25U4U.cjs","sourcesContent":[null,"\"use client\"\n\nimport React, { useState, useCallback, useRef, useEffect } from 'react'\n\n/** Loading skeleton for iframe embeds — matches project skeleton pattern */\nfunction EmbedLoadingSkeleton({ height }: { height?: string }) {\n return (\n <div\n className=\"w-full rounded-lg border border-ods-border overflow-hidden bg-ods-skeleton animate-pulse\"\n style={{ height: height || 'calc(100vh - 250px)' }}\n >\n <div className=\"flex flex-col items-center justify-center h-full gap-4\">\n <div className=\"w-12 h-12 rounded-lg bg-ods-card\" />\n <div className=\"h-4 w-48 rounded bg-ods-card\" />\n <div className=\"h-3 w-32 rounded bg-ods-card\" />\n </div>\n </div>\n )\n}\n\nexport interface EmbedIframeProps {\n /** The URL to embed */\n src: string\n /** Accessible title for the iframe */\n title: string\n /** Additional class names for the outer container */\n className?: string\n /** Container height (CSS value). Defaults to `calc(100vh - 250px)` */\n height?: string\n /** iframe `allow` attribute */\n allow?: string\n /** iframe `referrerPolicy` attribute */\n referrerPolicy?: React.IframeHTMLAttributes<HTMLIFrameElement>['referrerPolicy']\n /** iframe `loading` attribute */\n loading?: 'eager' | 'lazy'\n /** iframe `allowFullScreen` attribute */\n allowFullScreen?: boolean\n}\n\n/**\n * Base iframe wrapper with loading skeleton and proper memory cleanup.\n *\n * Prevents memory leaks by:\n * - Using `key={src}` to force full unmount/remount when src changes\n * - Setting iframe src to about:blank on unmount to release the embedded document\n * - Resetting loaded state when src changes\n */\nexport function EmbedIframe({\n src,\n title,\n className,\n height,\n allow,\n referrerPolicy,\n loading,\n allowFullScreen,\n}: EmbedIframeProps) {\n const [isLoaded, setIsLoaded] = useState(false)\n const iframeRef = useRef<HTMLIFrameElement>(null)\n const handleLoad = useCallback(() => setIsLoaded(true), [])\n\n useEffect(() => {\n setIsLoaded(false)\n }, [src])\n\n useEffect(() => {\n const iframe = iframeRef.current\n return () => {\n if (iframe) {\n try {\n iframe.src = 'about:blank'\n } catch {\n // Cross-origin iframes may throw — safe to ignore\n }\n }\n }\n }, [src])\n\n const resolvedHeight = height || 'calc(100vh - 250px)'\n\n return (\n <>\n {!isLoaded && <EmbedLoadingSkeleton height={resolvedHeight} />}\n <div\n className={`w-full rounded-lg border border-ods-border overflow-hidden ${!isLoaded ? 'h-0 overflow-hidden' : ''} ${className || ''}`}\n style={isLoaded ? { height: resolvedHeight } : undefined}\n >\n <iframe\n key={src}\n ref={iframeRef}\n src={src}\n className=\"w-full h-full border-0\"\n title={title}\n onLoad={handleLoad}\n allow={allow}\n referrerPolicy={referrerPolicy}\n loading={loading}\n allowFullScreen={allow?.includes('fullscreen') ? undefined : allowFullScreen}\n />\n </div>\n </>\n )\n}\n","\"use client\"\n\nimport React from 'react'\nimport { Button } from '../ui'\nimport { Download, Eye } from 'lucide-react'\nimport { AdobePdfIcon } from '../icons-v2-generated'\nimport { EmbedIframe } from './embed-iframe'\n\nexport interface PdfViewerProps {\n src: string\n fileName?: string\n onPreview?: () => void\n onDownload?: () => void\n height?: string\n}\n\nexport function PdfViewer({ src, fileName, onPreview, onDownload, height }: PdfViewerProps) {\n const displayName = fileName || 'PDF Document'\n\n if (!src) {\n return (\n <div className=\"flex flex-col items-center justify-center py-16 text-center\">\n <AdobePdfIcon className=\"w-16 h-16 text-ods-text-secondary mb-4\" />\n <p className=\"text-ods-text-secondary\">PDF file not available</p>\n </div>\n )\n }\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <AdobePdfIcon className=\"w-5 h-5 shrink-0\" />\n <h2 className=\"text-xl font-semibold text-ods-text-primary truncate\">{displayName}</h2>\n </div>\n <div className=\"flex items-center gap-2 w-full sm:w-auto\">\n <Button\n variant=\"outline\"\n size=\"small-legacy\"\n href={onPreview ? undefined : src}\n openInNewTab={!onPreview}\n onClick={onPreview}\n leftIcon={<Eye className=\"w-4 h-4\" />}\n className=\"flex-1 sm:flex-initial\"\n >\n Preview\n </Button>\n <Button\n variant=\"outline\"\n size=\"small-legacy\"\n href={onDownload ? undefined : src}\n openInNewTab={!onDownload}\n onClick={onDownload}\n leftIcon={<Download className=\"w-4 h-4\" />}\n className=\"flex-1 sm:flex-initial\"\n >\n Download\n </Button>\n </div>\n </div>\n <EmbedIframe src={src} title={displayName} height={height} />\n </div>\n )\n}\n","\"use client\"\n\nimport React from 'react'\nimport { Button } from '../ui'\nimport { ExternalLink } from 'lucide-react'\nimport { GoogleSheetsIcon } from '../icons-v2-generated'\nimport { EmbedIframe } from './embed-iframe'\nimport { toGoogleSheetsEmbedUrl, toGoogleSheetsOriginalUrl } from '../../utils/embed-url-converters'\n\nexport interface GoogleSheetsViewerProps {\n externalUrl: string\n fileName?: string\n height?: string\n}\n\nexport function GoogleSheetsViewer({ externalUrl, fileName, height }: GoogleSheetsViewerProps) {\n const displayName = fileName || 'Google Sheet'\n\n if (!externalUrl) {\n return (\n <div className=\"flex flex-col items-center justify-center py-16 text-center\">\n <GoogleSheetsIcon className=\"w-16 h-16 text-ods-text-secondary mb-4\" />\n <p className=\"text-ods-text-secondary\">Google Sheet URL not configured</p>\n </div>\n )\n }\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <GoogleSheetsIcon className=\"w-5 h-5 shrink-0\" />\n <h2 className=\"text-xl font-semibold text-ods-text-primary truncate\">{displayName}</h2>\n </div>\n <Button\n variant=\"outline\"\n size=\"small-legacy\"\n href={toGoogleSheetsOriginalUrl(externalUrl)}\n openInNewTab\n leftIcon={<GoogleSheetsIcon className=\"w-4 h-4\" />}\n rightIcon={<ExternalLink className=\"w-4 h-4\" />}\n className=\"w-full sm:w-auto\"\n >\n Open in Google Sheets\n </Button>\n </div>\n <EmbedIframe\n src={toGoogleSheetsEmbedUrl(externalUrl)}\n title={displayName}\n height={height}\n />\n </div>\n )\n}\n","'use client'\n\nimport { useState } from 'react'\nimport { Button, ToggleGroup, ToggleGroupItem } from '../ui'\nimport { FigmaIcon } from '../icons-v2-generated'\nimport { ExternalLink, Play, LayoutGrid } from 'lucide-react'\nimport { toFigmaEmbedUrl, toFigmaOriginalUrl, isFigmaSlidesUrl } from '../../utils/embed-url-converters'\nimport { EmbedIframe } from './embed-iframe'\n\nexport interface FigmaEmbedProps {\n /** Any Figma URL (design/file/proto/board/slides/deck) or an already-resolved embed URL. */\n url: string\n /** Heading shown above the embed. Defaults to \"Figma Design\". */\n title?: string\n /**\n * iframe height (CSS value). The data-room document viewer omits it (full\n * height, `calc(100vh - 250px)`); inline markdown passes e.g. `\"70vh\"` so the\n * embed sits naturally inside article content.\n */\n height?: string\n /** iframe loading strategy. Defaults to `\"lazy\"`; the data-room viewer passes `\"eager\"`. */\n loading?: 'eager' | 'lazy'\n}\n\ntype SlidesView = 'present' | 'browse'\n\n/**\n * Two-state present/browse toggle for Figma Slides. `present` (default) uses\n * Figma's deck viewer (full-bleed slide + `‹ n/N ›` nav bar + keyboard nav);\n * `browse` uses the thumbnail-rail + zoom viewer.\n */\nfunction SlidesViewToggle({\n view,\n onChange,\n}: {\n view: SlidesView\n onChange: (v: SlidesView) => void\n}) {\n const options: { key: SlidesView; label: string; Icon: typeof Play }[] = [\n { key: 'present', label: 'Present', Icon: Play },\n { key: 'browse', label: 'Browse', Icon: LayoutGrid },\n ]\n return (\n <ToggleGroup\n type=\"single\"\n value={view}\n onValueChange={(v: string) => {\n if (v && v !== view) onChange(v as SlidesView)\n }}\n aria-label=\"Figma slides view mode\"\n className=\"flex shrink-0 items-center gap-0.5 rounded-lg border border-ods-border bg-ods-card p-0.5\"\n >\n {options.map(({ key, label, Icon }) => {\n const active = view === key\n return (\n <ToggleGroupItem\n key={key}\n value={key}\n aria-label={label}\n className={`flex items-center gap-1.5 rounded-md px-2.5 py-1 text-sm font-medium transition-colors ${\n active\n ? 'bg-ods-accent text-ods-text-on-accent'\n : 'text-ods-text-secondary hover:text-ods-text-primary hover:bg-ods-bg-hover'\n }`}\n >\n <Icon className=\"h-4 w-4 shrink-0\" />\n {label}\n </ToggleGroupItem>\n )\n })}\n </ToggleGroup>\n )\n}\n\n/**\n * Single source of truth for every Figma surface — the data-room document viewer\n * and in-article markdown both render this. A header (icon + title + \"Open in\n * Figma\") over an interactive Figma iframe, built from the canonical\n * `toFigmaEmbedUrl` / `toFigmaOriginalUrl` converters + the shared `<EmbedIframe>`.\n * Only height/loading differ per surface.\n *\n * For Slides decks, a present/browse toggle (default = present) lets viewers flip\n * slides with Figma's native nav bar + keyboard, or switch to the thumbnail-rail\n * browse view.\n */\nexport function FigmaEmbed({ url, title, height, loading = 'lazy' }: FigmaEmbedProps) {\n const [view, setView] = useState<SlidesView>('present')\n const isSlides = url ? isFigmaSlidesUrl(url) : false\n const embedSrc = url ? toFigmaEmbedUrl(url, { slidesView: view }) : null\n const originalUrl = (() => {\n if (!url) return null\n try {\n const parsed = new URL(toFigmaOriginalUrl(url))\n const host = parsed.hostname.toLowerCase()\n const okHost = host === 'figma.com' || host.endsWith('.figma.com')\n const okProtocol = parsed.protocol === 'https:' || parsed.protocol === 'http:'\n return okHost && okProtocol ? parsed.toString() : null\n } catch {\n return null\n }\n })()\n const heading = title || 'Figma Design'\n\n return (\n <div className=\"my-6 space-y-3\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <FigmaIcon className=\"w-5 h-5 shrink-0\" />\n <span className=\"font-sans text-base font-semibold text-ods-text-primary truncate\">\n {heading}\n </span>\n </div>\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n {isSlides && embedSrc && <SlidesViewToggle view={view} onChange={setView} />}\n {originalUrl && (\n <Button\n variant=\"outline\"\n size=\"small-legacy\"\n href={originalUrl}\n openInNewTab\n leftIcon={<FigmaIcon className=\"w-4 h-4\" />}\n rightIcon={<ExternalLink className=\"w-4 h-4\" />}\n className=\"w-full sm:w-auto\"\n >\n Open in Figma\n </Button>\n )}\n </div>\n </div>\n {embedSrc ? (\n <EmbedIframe\n src={embedSrc}\n title={heading}\n allow=\"clipboard-write; clipboard-read; fullscreen\"\n loading={loading}\n height={height}\n allowFullScreen\n />\n ) : (\n <div className=\"flex flex-col items-center justify-center py-16 text-center\">\n <FigmaIcon className=\"w-16 h-16 text-ods-text-secondary mb-4\" />\n <p className=\"text-ods-text-secondary\">Figma URL not configured</p>\n </div>\n )}\n </div>\n )\n}\n"]}