diff --git a/README.md b/README.md index 2ba6304..d4440e8 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,8 @@ A security-first Blazor iframe component with automatic resizing, cross-frame me - **Navigation Tracking** - Capture iframe navigation events with URL and query parameters - **Sandbox Support** - Multiple security levels from permissive to paranoid isolation - **Environment-Aware** - Different configurations for development vs production -- **Automatic Resizing** - Smart height adjustment based on iframe content +- **Automatic Resizing** - Smart height adjustment based on iframe content with configurable options +- **Programmatic Reload** - Refresh iframe content without recreating the entire component ## Documentation @@ -125,6 +126,76 @@ var paymentOptions = new MessageSecurityOptions() .ForPaymentWidget(); ``` +### Auto-Resize Configuration + +Control how the iframe automatically adjusts its height based on content: + +```razor + + +@code { + // Custom resize configuration + private readonly ResizeOptions resizeOptions = new() + { + MinHeight = 200, + MaxHeight = 2000, + PollingInterval = 500, + DebounceMs = 100, + UseResizeObserver = true + }; + + // Or use built-in presets: + // ResizeOptions.Default - Balanced defaults + // ResizeOptions.Performance - Less frequent updates (better performance) + // ResizeOptions.Responsive - More frequent updates (smoother resizing) +} +``` + +| Property | Default | Description | +|----------|---------|-------------| +| `MinHeight` | 100 | Minimum height in pixels | +| `MaxHeight` | 50000 | Maximum height in pixels | +| `PollingInterval` | 500 | Fallback polling interval (ms) when ResizeObserver unavailable | +| `DebounceMs` | 100 | Debounce delay to prevent excessive updates (0 to disable) | +| `UseResizeObserver` | true | Use ResizeObserver API when available | + +### Programmatic Reload + +Refresh iframe content without recreating the entire component - useful for PDFs, dynamic content, or cache-busting: + +```razor + + + + + +@code { + private BlazorFrame? iframeRef; + private string pdfUrl = "https://example.com/document.pdf"; + + // Reload the current content + private async Task RefreshContent() + { + if (iframeRef != null) + { + await iframeRef.ReloadAsync(); + } + } + + // Load a new URL with cache-busting + private async Task LoadNewDocument() + { + if (iframeRef != null) + { + var cacheBustedUrl = $"https://example.com/document.pdf?v={DateTime.UtcNow.Ticks}"; + await iframeRef.ReloadAsync(cacheBustedUrl); + } + } +} +``` + ### Content Security Policy ```razor @@ -168,13 +239,56 @@ All iframe messages are automatically validated for: ### Sandbox Security Levels -| Level | Description | Use Case | +| Level | Permissions | Use Case | |-------|-------------|----------| | **None** | No restrictions | Trusted content only | | **Basic** | Scripts + same-origin | Most trusted widgets | -| **Permissive** | + forms + popups | Interactive widgets | -| **Strict** | Scripts + same-origin only | Display widgets | -| **Paranoid** | Scripts only | Untrusted content | +| **Permissive** | Scripts + same-origin + forms + popups | Interactive widgets | +| **Strict** | Scripts only (no same-origin) | Semi-trusted content | +| **Paranoid** | Empty sandbox (no permissions) | Untrusted content | + +## API Reference + +### Component Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `Src` | `string` | `""` | The URL to load in the iframe | +| `Width` | `string` | `"100%"` | Width of the iframe | +| `Height` | `string` | `"600px"` | Height of the iframe | +| `EnableAutoResize` | `bool` | `true` | Enable automatic height adjustment | +| `ResizeOptions` | `ResizeOptions?` | `null` | Configuration for auto-resize behavior | +| `EnableScroll` | `bool` | `false` | Enable scrolling on the wrapper | +| `EnableNavigationTracking` | `bool` | `false` | Track iframe navigation events | +| `AllowedOrigins` | `List?` | `null` | Allowed origins for postMessage | +| `SecurityOptions` | `MessageSecurityOptions` | `new()` | Security configuration | +| `CspOptions` | `CspOptions?` | `null` | Content Security Policy configuration | + +### Component Methods + +| Method | Returns | Description | +|--------|---------|-------------| +| `ReloadAsync()` | `Task` | Reloads the iframe content | +| `ReloadAsync(string newSrc)` | `Task` | Reloads with a new source URL | +| `SendMessageAsync(object data, string? targetOrigin)` | `Task` | Sends a message to the iframe | +| `SendTypedMessageAsync(string type, object? data, string? targetOrigin)` | `Task` | Sends a typed message | +| `GetRecommendedCspHeader()` | `CspHeader?` | Gets the recommended CSP header | +| `ValidateCspConfiguration()` | `CspValidationResult?` | Validates the CSP configuration | + +### Events + +| Event | Type | Description | +|-------|------|-------------| +| `OnLoad` | `EventCallback` | Fired when iframe loads | +| `OnMessage` | `EventCallback` | Fired on message (raw JSON) | +| `OnValidatedMessage` | `EventCallback` | Fired on validated message | +| `OnSecurityViolation` | `EventCallback` | Fired on security violation | +| `OnNavigation` | `EventCallback` | Fired on navigation | +| `OnUrlChanged` | `EventCallback` | Fired on URL change | +| `OnMessageSent` | `EventCallback` | Fired when message sent | +| `OnMessageSendFailed` | `EventCallback` | Fired on send failure | +| `OnCspHeaderGenerated` | `EventCallback` | Fired when CSP generated | +| `OnInitializationError` | `EventCallback` | Fired on init failure | ## Demo diff --git a/docs/configuration/display-options.md b/docs/configuration/display-options.md index eded925..a8da9ad 100644 --- a/docs/configuration/display-options.md +++ b/docs/configuration/display-options.md @@ -100,43 +100,63 @@ This guide covers all aspects of configuring how BlazorFrame appears and behaves @code { private readonly ResizeOptions basicResizeOptions = new() { - DebounceDelayMs = 100, // Wait 100ms before resizing - UseResizeObserver = true, // Use modern ResizeObserver API - MaxHeight = 1000, // Maximum height in pixels - MinHeight = 200 // Minimum height in pixels + MinHeight = 200, // Minimum height in pixels + MaxHeight = 1000, // Maximum height in pixels + DebounceMs = 100, // Wait 100ms before resizing + UseResizeObserver = true // Use modern ResizeObserver API }; } ``` +### ResizeOptions Properties + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `MinHeight` | `int` | `100` | Minimum height in pixels | +| `MaxHeight` | `int` | `50000` | Maximum height in pixels | +| `PollingInterval` | `int` | `500` | Fallback polling interval (ms) when ResizeObserver is unavailable | +| `DebounceMs` | `int` | `100` | Debounce delay to prevent excessive updates (set to 0 to disable) | +| `UseResizeObserver` | `bool` | `true` | Use the modern ResizeObserver API when available | + +### Built-in Presets + +BlazorFrame provides built-in presets for common resize configurations: + +```razor +@code { + // Default balanced configuration + private readonly ResizeOptions defaultOptions = ResizeOptions.Default; + + // Optimized for performance (less frequent updates) + private readonly ResizeOptions performanceOptions = ResizeOptions.Performance; + + // Optimized for responsiveness (more frequent updates) + private readonly ResizeOptions responsiveOptions = ResizeOptions.Responsive; +} +``` + +| Preset | PollingInterval | DebounceMs | Best For | +|--------|-----------------|------------|----------| +| `Default` | 500ms | 100ms | Most use cases | +| `Performance` | 1000ms | 250ms | Many iframes, mobile devices | +| `Responsive` | 250ms | 50ms | Dynamic content, smooth animations | + ### Advanced Auto-Resize Configuration ```razor + ResizeOptions="@advancedResizeOptions" /> @code { private readonly ResizeOptions advancedResizeOptions = new() { - DebounceDelayMs = 50, // Fast response for dynamic content - UseResizeObserver = true, // Prefer modern API - FallbackPollingInterval = 1000, // Fallback polling every 1 second - MaxHeight = 1500, // Large max height for rich content - MinHeight = 150, // Small min height for compact widgets - AutoResizeWidth = false, // Only auto-resize height - RespectAspectRatio = true, // Maintain aspect ratio if possible - SmoothResize = true, // Animate resize transitions - ResizeThrottleMs = 16 // Throttle resize events (60fps) + MinHeight = 150, // Small min height for compact widgets + MaxHeight = 1500, // Large max height for rich content + PollingInterval = 1000, // Fallback polling every 1 second + DebounceMs = 50, // Fast response for dynamic content + UseResizeObserver = true // Prefer modern API }; - - private async Task HandleResize(ResizeEventArgs args) - { - Logger.LogDebug("Iframe resized to {Width}x{Height}", args.Width, args.Height); - - // Could trigger layout adjustments - await AdjustSurroundingLayout(args); - } } ``` @@ -169,6 +189,110 @@ This guide covers all aspects of configuring how BlazorFrame appears and behaves } ``` +## Programmatic Reload + +Refresh iframe content without recreating the entire Blazor component. This is particularly useful for: +- Refreshing PDF documents +- Reloading dynamic content +- Cache-busting with updated URLs + +### Basic Reload + +```razor + + + + +@code { + private BlazorFrame? iframeRef; + + private async Task RefreshContent() + { + if (iframeRef != null) + { + await iframeRef.ReloadAsync(); + } + } +} +``` + +### Reload with New URL + +```razor + + + + +@code { + private BlazorFrame? iframeRef; + private string currentUrl = "https://example.com/doc1.pdf"; + + private async Task LoadNewDocument() + { + if (iframeRef != null) + { + await iframeRef.ReloadAsync("https://example.com/doc2.pdf"); + } + } +} +``` + +### Cache-Busting Reload + +```razor + + + + +@code { + private BlazorFrame? iframeRef; + private string pdfUrl = "https://example.com/report.pdf"; + + private async Task RefreshWithCacheBust() + { + if (iframeRef != null) + { + // Add timestamp to bust cache + var cacheBustedUrl = $"{pdfUrl}?v={DateTime.UtcNow.Ticks}"; + await iframeRef.ReloadAsync(cacheBustedUrl); + } + } +} +``` + +### Auto-Refresh on Interval + +```razor + + +@code { + private BlazorFrame? iframeRef; + private string dashboardUrl = "https://example.com/dashboard"; + private Timer? refreshTimer; + + protected override void OnInitialized() + { + // Auto-refresh every 5 minutes + refreshTimer = new Timer(async _ => + { + if (iframeRef != null) + { + await InvokeAsync(async () => + { + await iframeRef.ReloadAsync(); + StateHasChanged(); + }); + } + }, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); + } + + public void Dispose() + { + refreshTimer?.Dispose(); + } +} +``` + ## Scrolling and Overflow ### Scroll Configuration @@ -332,27 +456,16 @@ This guide covers all aspects of configuring how BlazorFrame appears and behaves Loading
Loading Widget

Please wait while we load the content...

-
-
-
} + OnLoad="@(() => contentLoaded = true)" /> @code { private bool contentLoaded = false; - private int loadingProgress = 0; - - private void UpdateLoadingProgress(int progress) - { - loadingProgress = progress; - StateHasChanged(); - } } ``` @@ -386,24 +499,6 @@ This guide covers all aspects of configuring how BlazorFrame appears and behaves Height="300px" /> ``` -### Material Design Integration - -```razor -
-
- -
-
-
- -
-
-
-``` - ### Dark Mode Support ```razor @@ -498,44 +593,6 @@ This guide covers all aspects of configuring how BlazorFrame appears and behaves ``` -### Split Layout - -```razor -
-
- -
-
-
- -
-
- - -``` - ### Grid Layout ```razor @@ -625,31 +682,10 @@ This guide covers all aspects of configuring how BlazorFrame appears and behaves ### Resource Optimization ```razor - - -@code { - private string GetOptimizedUrl() - { - var url = baseContentUrl; - - // Add performance parameters - url += "?optimize=true"; - url += "&quality=medium"; - url += "&cache=1hour"; - - return url; - } - - private readonly ResizeOptions optimizedResizeOptions = new() - { - DebounceDelayMs = 300, // Longer debounce for performance - UseResizeObserver = true, // Use efficient API - ResizeThrottleMs = 33 // 30fps throttling - }; -} + ResizeOptions="@ResizeOptions.Performance" /> ``` ## Accessibility @@ -704,6 +740,9 @@ This guide covers all aspects of configuring how BlazorFrame appears and behaves ### Do - **Use responsive dimensions** - Make iframes work on all screen sizes - **Enable auto-resize** for dynamic content that changes height +- **Configure ResizeOptions** appropriately for your use case +- **Use built-in presets** (`ResizeOptions.Performance`, `ResizeOptions.Responsive`) when applicable +- **Use `ReloadAsync()`** instead of recreating components for content refresh - **Provide loading indicators** - Show users that content is loading - **Set appropriate min/max heights** - Prevent layout issues - **Use semantic HTML** - Include proper titles and ARIA labels @@ -718,5 +757,6 @@ This guide covers all aspects of configuring how BlazorFrame appears and behaves - **Make iframes too small** - Ensure content is readable - **Forget about responsive design** - Test on different screen sizes - **Overuse auto-resize** - Can cause performance issues with many iframes +- **Recreate components** just to refresh content - use `ReloadAsync()` instead --- diff --git a/docs/core-features/parameters.md b/docs/core-features/parameters.md index 8de9af4..885f0ea 100644 --- a/docs/core-features/parameters.md +++ b/docs/core-features/parameters.md @@ -96,6 +96,53 @@ Complete reference for all BlazorFrame component parameters, their types, defaul } ``` +### ResizeOptions +**Type:** `ResizeOptions?` +**Default:** `null` +**Description:** Configuration options for auto-resize behavior including min/max height, polling interval, and debouncing. + +```razor + + + + + + +@code { + // Custom configuration + private readonly ResizeOptions customResizeOptions = new() + { + MinHeight = 200, + MaxHeight = 1500, + PollingInterval = 500, + DebounceMs = 100, + UseResizeObserver = true + }; +} +``` + +**ResizeOptions Properties:** + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `MinHeight` | `int` | `100` | Minimum height in pixels | +| `MaxHeight` | `int` | `50000` | Maximum height in pixels | +| `PollingInterval` | `int` | `500` | Fallback polling interval (ms) when ResizeObserver is unavailable | +| `DebounceMs` | `int` | `100` | Debounce delay to prevent excessive updates (set to 0 to disable) | +| `UseResizeObserver` | `bool` | `true` | Use the modern ResizeObserver API when available | + +**Built-in Presets:** + +| Preset | PollingInterval | DebounceMs | Best For | +|--------|-----------------|------------|----------| +| `ResizeOptions.Default` | 500ms | 100ms | Most use cases | +| `ResizeOptions.Performance` | 1000ms | 250ms | Many iframes, mobile devices | +| `ResizeOptions.Responsive` | 250ms | 50ms | Dynamic content, smooth animations | + ### EnableScroll **Type:** `bool` **Default:** `false` @@ -462,6 +509,135 @@ Complete reference for all BlazorFrame component parameters, their types, defaul } ``` +## Component Methods + +### ReloadAsync() +**Returns:** `Task` +**Description:** Reloads the iframe content by forcing a re-render of the iframe element. + +```razor + + + + +@code { + private BlazorFrame? iframeRef; + + private async Task RefreshContent() + { + if (iframeRef != null) + { + await iframeRef.ReloadAsync(); + } + } +} +``` + +### ReloadAsync(string newSrc) +**Returns:** `Task` +**Description:** Reloads the iframe with a new source URL. + +```razor + + +@code { + private BlazorFrame? iframeRef; + private string currentUrl = "https://example.com/doc1.pdf"; + + private async Task LoadDifferentDocument() + { + if (iframeRef != null) + { + // Load a new URL with cache-busting + var newUrl = $"https://example.com/doc2.pdf?v={DateTime.UtcNow.Ticks}"; + await iframeRef.ReloadAsync(newUrl); + } + } +} +``` + +### SendMessageAsync(object data, string? targetOrigin) +**Returns:** `Task` +**Description:** Sends a message to the iframe content. + +```razor + + +@code { + private BlazorFrame? iframeRef; + + private async Task SendData() + { + if (iframeRef != null) + { + var success = await iframeRef.SendMessageAsync(new { action = "update", value = 42 }); + if (!success) + { + Console.WriteLine("Failed to send message"); + } + } + } +} +``` + +### SendTypedMessageAsync(string messageType, object? data, string? targetOrigin) +**Returns:** `Task` +**Description:** Sends a typed message to the iframe with automatic timestamp. + +```razor + + +@code { + private BlazorFrame? iframeRef; + + private async Task SendTypedData() + { + if (iframeRef != null) + { + await iframeRef.SendTypedMessageAsync("user-update", new { userId = 123, name = "John" }); + } + } +} +``` + +### GetRecommendedCspHeader() +**Returns:** `CspHeader?` +**Description:** Gets the recommended CSP header for the current configuration. + +```razor +@code { + private void GetCspHeader() + { + var cspHeader = iframeRef?.GetRecommendedCspHeader(); + if (cspHeader != null) + { + Console.WriteLine($"Header: {cspHeader.HeaderName}"); + Console.WriteLine($"Value: {cspHeader.HeaderValue}"); + } + } +} +``` + +### ValidateCspConfiguration() +**Returns:** `CspValidationResult?` +**Description:** Validates the current CSP configuration. + +```razor +@code { + private void ValidateCsp() + { + var result = iframeRef?.ValidateCspConfiguration(); + if (result != null) + { + foreach (var warning in result.Warnings) + { + Console.WriteLine($"Warning: {warning}"); + } + } + } +} +``` + ## Styling Parameters ### AdditionalAttributes @@ -507,6 +683,7 @@ Complete reference for all BlazorFrame component parameters, their types, defaul Width="100%" Height="@GetResponsiveHeight()" EnableAutoResize="@(!isMobile)" + ResizeOptions="@(isMobile ? ResizeOptions.Performance : ResizeOptions.Default)" EnableScroll="@isMobile" SecurityOptions="@GetSecurityOptions()" class="@GetCssClasses()" /> @@ -562,11 +739,57 @@ Complete reference for all BlazorFrame component parameters, their types, defaul } ``` +### PDF Viewer with Refresh +```razor + + + + + +@code { + private BlazorFrame? pdfViewer; + private string pdfUrl = "https://example.com/document.pdf"; + private int currentPage = 1; + + private async Task RefreshPdf() + { + if (pdfViewer != null) + { + // Reload with cache-busting + var cacheBustedUrl = $"{pdfUrl}?v={DateTime.UtcNow.Ticks}"; + await pdfViewer.ReloadAsync(cacheBustedUrl); + } + } + + private async Task LoadNextPage() + { + currentPage++; + if (pdfViewer != null) + { + await pdfViewer.ReloadAsync($"https://example.com/document-{currentPage}.pdf"); + } + } + + private Task HandlePdfLoad() + { + Console.WriteLine("PDF loaded successfully"); + return Task.CompletedTask; + } +} +``` + ### Development-Friendly Configuration ```razor