Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CustomControl components #19

Merged
merged 17 commits into from
Nov 21, 2024
Merged

Add CustomControl components #19

merged 17 commits into from
Nov 21, 2024

Conversation

Kanahiro
Copy link
Member

@Kanahiro Kanahiro commented Nov 20, 2024

Close #6

What I did

Notes

None

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a new "Custom Control" component for enhanced map interactions, allowing users to toggle terrain and hillshade layers.
    • Added a link to the "Custom Control" example in the examples list.
  • Documentation

    • Created a new markdown file detailing the "Custom Control" component, including usage instructions and code snippets.
  • Version Update

    • Incremented project version from 0.0.5 to 0.0.6.

@Kanahiro Kanahiro requested a review from ciscorn November 20, 2024 18:26
Copy link

cloudflare-workers-and-pages bot commented Nov 20, 2024

Deploying svelte-maplibre-gl with  Cloudflare Pages  Cloudflare Pages

Latest commit: a870266
Status: ✅  Deploy successful!
Preview URL: https://cd5cc156.svelte-maplibre-gl.pages.dev
Branch Preview URL: https://feat-custom-control.svelte-maplibre-gl.pages.dev

View logs

Copy link

coderabbitai bot commented Nov 20, 2024

Walkthrough

This pull request introduces a new CustomControl component for a MapLibre map interface using Svelte. It enhances the functionality by allowing users to toggle terrain and hillshade layers and manage dark mode. Additionally, a new list item linking to this component is added in the examples index. Documentation for the CustomControl component is also created, and minor updates are made to the README and package configuration.

Changes

File Path Change Summary
src/content/examples/Index.svelte Added a new list item linking to the "Custom Control" example.
src/content/examples/custom-control/CustomControl.svelte Introduced a customizable control component for MapLibre, managing various states and rendering controls.
src/content/examples/custom-control/MyControl.ts Added MyControl class and MyControlConstructorOptions interface for managing control functionalities.
src/content/examples/custom-control/content.svelte.md Created documentation for the CustomControl component, including a description and code examples.
src/lib/maplibre/index.ts Added export for the new CustomControl component.
src/lib/maplibre/controls/CustomControl.svelte Defined the CustomControl component with TypeScript props and lifecycle management for map controls.
README.md Updated contributing and authorship sections; removed support note for "MIERUNE Inc."
package.json Incremented version from 0.0.5 to 0.0.6; listed maplibre-gl in devDependencies again.

Possibly related PRs

  • Add 'Limit Map Interactions' example #18: The changes in this PR involve adding a new list item in src/content/examples/Index.svelte, which is directly related to the main PR that also modifies the same file by adding a new list item.

🐰 In the meadow, a new control,
With toggles and layers, it plays a role.
Dark mode or light, the choice is clear,
Custom controls bring us cheer!
So hop along, let’s take a look,
At the magic in our codebook! 🌼

Warning

Rate limit exceeded

@ciscorn has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 25 minutes and 16 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between e245f2b and a870266.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (9)
src/lib/maplibre/controls/CustomControl/Group.svelte (2)

6-8: Consider adding error handling for child rendering.

While the current implementation is functional, it could benefit from error boundaries to gracefully handle rendering failures of child components.

Consider wrapping the render in a try-catch or implementing an error boundary:

 <div class="maplibregl-ctrl-group">
-	{@render children?.()}
+	{#try}
+		{@render children?.()}
+	{:catch error}
+		<div class="maplibregl-ctrl-group-error">
+			<!-- Consider logging the error or showing a fallback UI -->
+		</div>
+	{/try}
 </div>

1-8: Well-structured building block for custom controls.

This component follows good architectural principles:

  • Single responsibility: focuses solely on grouping controls
  • Composability: can be used with any custom control content
  • Framework alignment: follows MapLibre's control patterns

Consider documenting usage examples in the component's JSDoc comments to help other developers understand its purpose and integration patterns.

src/lib/maplibre/controls/CustomControl/Icon.svelte (1)

1-9: Consider enhancing type safety and documentation

The props implementation could benefit from some improvements:

  1. Extract the props interface for reusability
  2. Add more specific types for event handlers
  3. Include JSDoc documentation for props

Consider applying these changes:

 <script lang="ts">
 	import type { Snippet } from 'svelte';
+
+	/** Props interface for Icon component */
+	interface IconProps {
+		/** Optional content to render inside the button */
+		children?: Snippet;
+		/** Required CSS class name for additional styling */
+		className: string;
+		/** Optional click event handler */
+		onclick?: (event: MouseEvent) => void;
+		/** Optional context menu event handler */
+		oncontextmenu?: (event: MouseEvent) => void;
+	}
+
 	let {
 		children,
 		onclick,
 		oncontextmenu,
 		className
-	}: { children?: Snippet; className: string; onclick?: () => void; oncontextmenu?: () => void } = $props();
+	}: IconProps = $props();
 </script>
src/lib/maplibre/controls/CustomControl/Control.svelte (2)

14-20: Consider enhancing error messages for better debugging.

The initialization and validation logic is solid, but the error messages could be more descriptive.

Consider this enhancement:

-if (!mapCtx.map) throw new Error('Map instance is not initialized.');
+if (!mapCtx.map) throw new Error('MapLibre GL map instance is not initialized. Ensure the Control component is used within a Map component.');

-if (!control && !children) throw new Error('You must provide either control or children.');
+if (!control && !children) throw new Error('CustomControl requires either a control object (IControl) or children elements. Please provide at least one of these props.');

47-49: Consider adding ARIA attributes for accessibility.

The template structure is good, but could benefit from accessibility improvements.

Consider this enhancement:

-<div bind:this={el} class="maplibregl-ctrl">
+<div bind:this={el} class="maplibregl-ctrl" role="group" aria-label="Map custom control">
   {@render children?.()}
 </div>
src/content/examples/custom-control/MyControl.ts (2)

6-14: Consider adding readonly modifiers to private fields.

Since these fields are only set in the constructor and never modified afterwards, they should be marked as readonly for better type safety.

 class MyControl implements maplibregl.IControl {
-  private _container: HTMLElement | undefined;
-  private _toggleTerrain: () => boolean;
-  private _toggleHillshade: () => boolean;
+  private readonly _container: HTMLElement | undefined;
+  private readonly _toggleTerrain: () => boolean;
+  private readonly _toggleHillshade: () => boolean;

49-53: Simplify container removal with optional chaining.

The current null check can be simplified using optional chaining.

  onRemove() {
-   if (this._container && this._container.parentNode) {
-     this._container.parentNode.removeChild(this._container);
-   }
+   this._container?.parentNode?.removeChild(this._container);
  }
🧰 Tools
🪛 Biome

[error] 50-50: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

src/content/examples/custom-control/CustomControl.svelte (1)

28-35: Consider making map configuration more flexible

The map configuration has hardcoded values for zoom, pitch, center, etc. Consider:

  1. Making these configurable through props
  2. Adding validation for the maxPitch value
 <MapLibre
 	class="h-[60vh] min-h-[300px]"
 	style={mapStyle}
-	zoom={12}
-	pitch={40}
-	maxPitch={85}
-	center={{ lng: 11.09085, lat: 47.3 }}
+	zoom={initialZoom}
+	pitch={initialPitch}
+	maxPitch={Math.min(maxPitch, 85)}
+	center={initialCenter}
 >
src/lib/maplibre/index.ts (1)

46-46: Consider using .ts extension for TypeScript imports

The export follows the established pattern and is well-placed in the controls section. However, since this is a TypeScript file, consider using the .ts extension in the import path for consistency.

-export { default as CustomControl } from './controls/CustomControl/index.js';
+export { default as CustomControl } from './controls/CustomControl/index.ts';
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 7d6c413 and 8e2ef67.

📒 Files selected for processing (11)
  • src/content/examples/Index.svelte (1 hunks)
  • src/content/examples/custom-control/CustomControl.svelte (1 hunks)
  • src/content/examples/custom-control/IconMoon.svelte (1 hunks)
  • src/content/examples/custom-control/IconSun.svelte (1 hunks)
  • src/content/examples/custom-control/MyControl.ts (1 hunks)
  • src/content/examples/custom-control/content.svelte.md (1 hunks)
  • src/lib/maplibre/controls/CustomControl/Control.svelte (1 hunks)
  • src/lib/maplibre/controls/CustomControl/Group.svelte (1 hunks)
  • src/lib/maplibre/controls/CustomControl/Icon.svelte (1 hunks)
  • src/lib/maplibre/controls/CustomControl/index.ts (1 hunks)
  • src/lib/maplibre/index.ts (1 hunks)
✅ Files skipped from review due to trivial changes (5)
  • src/content/examples/Index.svelte
  • src/content/examples/custom-control/IconMoon.svelte
  • src/content/examples/custom-control/IconSun.svelte
  • src/content/examples/custom-control/content.svelte.md
  • src/lib/maplibre/controls/CustomControl/index.ts
🧰 Additional context used
🪛 Biome
src/content/examples/custom-control/MyControl.ts

[error] 50-50: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🪛 eslint
src/lib/maplibre/controls/CustomControl/Control.svelte

[error] 24-24: '_' is defined but never used.

(@typescript-eslint/no-unused-vars)

🔇 Additional comments (11)
src/lib/maplibre/controls/CustomControl/Group.svelte (2)

1-4: LGTM! Modern Svelte 5 syntax with proper typing.

The component correctly uses TypeScript with proper typing for dynamic content rendering. The use of Svelte 5's new $props() syntax is forward-looking.


6-8: LGTM! Follows MapLibre control conventions.

The component correctly uses MapLibre's control group class and properly handles optional children rendering.

src/lib/maplibre/controls/CustomControl/Icon.svelte (1)

1-20: Verify MapLibre control integration patterns

Let's ensure this component follows MapLibre's control implementation patterns and styling conventions.

✅ Verification successful

MapLibre control integration is correctly implemented

The Icon component follows MapLibre's control patterns correctly:

  • It's part of a well-structured CustomControl implementation that properly implements MapLibre's IControl interface
  • Uses the standard maplibregl-ctrl-icon class as expected
  • Works as part of a composable control system with Control/Group/Icon components
  • The example usage shows correct integration with other MapLibre controls
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other MapLibre control implementations for consistency
rg -t svelte "maplibregl-ctrl-icon" --glob '!src/lib/maplibre/controls/CustomControl/Icon.svelte'

# Check for any existing control button implementations
ast-grep --pattern 'class:maplibregl-ctrl-icon={$_}'

Length of output: 183


Script:

#!/bin/bash
# Search for other MapLibre control implementations without file type restriction
rg "maplibregl-ctrl-icon" --glob '!src/lib/maplibre/controls/CustomControl/Icon.svelte'

# Look for any MapLibre control related files
fd "control" --glob "*.svelte"

# Check for any MapLibre style related files or documentation
fd -e css -e scss | xargs rg "maplibregl-ctrl"

# Look for any MapLibre initialization or control setup
rg "new maplibregl.Control" -A 5

Length of output: 296


Script:

#!/bin/bash
# Look for MapLibre control related files
fd -t f "control" -E "Icon.svelte"

# Check the CustomControl directory structure and files
ls -R src/lib/maplibre/controls/

# Look for any MapLibre style imports or CSS files
fd -t f -e css -e scss

# Check the parent component that might be using this Icon
cat src/lib/maplibre/controls/CustomControl/CustomControl.svelte 2>/dev/null || true

Length of output: 1085


Script:

#!/bin/bash
# Check the Control.svelte implementation which is likely the parent component
cat src/lib/maplibre/controls/CustomControl/Control.svelte

# Check NavigationControl implementation for reference patterns
cat src/lib/maplibre/controls/NavigationControl.svelte

# Check the CustomControl example implementation
cat src/content/examples/custom-control/CustomControl.svelte

# Look for any MapLibre control type definitions or interfaces
rg "interface.*Control" -A 5

Length of output: 8456

src/lib/maplibre/controls/CustomControl/Control.svelte (2)

1-13: LGTM! Well-structured imports and type definitions.

The Props interface is well-defined with proper TypeScript types, and the imports are appropriate for the functionality needed.


33-44: Verify control cleanup behavior across different scenarios.

The lifecycle management looks good, with proper cleanup in both the effect and onDestroy hooks.

Let's verify the cleanup behavior:

✅ Verification successful

Control cleanup behavior is consistently implemented across the codebase

The verification shows that all control components in the codebase follow the same robust cleanup pattern:

  • Removing controls in the $effect before adding new ones to prevent duplicates
  • Properly cleaning up controls in onDestroy hooks

This is consistently implemented across all control components:

  • CustomControl/Control.svelte
  • GlobeControl.svelte
  • TerrainControl.svelte
  • NavigationControl.svelte
  • FullScreenControl.svelte
  • LogoControl.svelte
  • AttributionControl.svelte
  • ScaleControl.svelte
  • GeolocateControl.svelte
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other control implementations to ensure consistent cleanup patterns
ast-grep --pattern 'onDestroy(() => {
  $$$
  map?.removeControl($$$)
  $$$
})'

Length of output: 81


Script:

#!/bin/bash
# Let's try a broader search for control cleanup patterns
# First, let's find all files that might contain control-related code
fd -e svelte -e ts -e js | xargs rg -l "removeControl|addControl"

# Then, let's look for cleanup patterns in these files
rg "onDestroy|cleanup|dispose" -A 5 -B 5

Length of output: 29448

src/content/examples/custom-control/MyControl.ts (2)

1-4: LGTM! Clear and well-defined interface.

The interface is well-structured with clear method signatures for toggle functionality.


56-56: LGTM! Clear and explicit export.

The named export is appropriate for this module.

src/content/examples/custom-control/CustomControl.svelte (3)

16-25: LGTM! Clean control initialization with proper callbacks

The control initialization follows best practices with atomic state updates and proper return values.


7-14: Verify map style URLs availability and accessibility

The map style URLs are hardcoded to use CartoDB's basemaps. Consider:

  1. Adding error handling for failed style loading
  2. Making the URLs configurable through props
✅ Verification successful

Map style URLs are accessible and properly configured

Both CartoDB basemap style URLs are accessible and return valid responses with proper CORS headers (Access-Control-Allow-Origin: *) and content type (application/json). The URLs are being served through a reliable CDN with good caching, indicating they are production-ready endpoints.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify the map style URLs are accessible
curl -I "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json"
curl -I "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"

Length of output: 2145


68-73: Review conditional rendering performance impact

The conditional rendering of Terrain and HillshadeLayer components might cause performance issues when frequently toggled. Consider:

  1. Using opacity instead of removing/adding components
  2. Adding transition animations for smoother toggling
src/lib/maplibre/index.ts (1)

46-46: Verify the CustomControl component implementation

Let's ensure the referenced CustomControl component exists and is properly implemented.

✅ Verification successful

Let me gather more information about the CustomControl implementation.


Let me check the implementation of the Svelte components that make up the CustomControl.


CustomControl component is properly implemented and follows MapLibre control patterns

The CustomControl is a well-structured Svelte component that:

  • Implements the required MapLibre IControl interface
  • Provides three subcomponents (Control, Group, Icon) for flexible control creation
  • Properly handles lifecycle with map control addition/removal
  • Includes proper TypeScript typing and error handling
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the existence and implementation of CustomControl component

# Check if the file exists
fd "CustomControl" "src/lib/maplibre/controls"

# Check the implementation
ast-grep --pattern 'export default class CustomControl {
  $$$
}'

Length of output: 153


Script:

#!/bin/bash
# Check the content of the index file in CustomControl directory
cat src/lib/maplibre/controls/CustomControl/index.ts

# Also check for any test files to understand the expected behavior
fd "CustomControl" "src" -e test.ts -e spec.ts

Length of output: 288


Script:

#!/bin/bash
# Check the implementation of the Control, Group, and Icon components
cat src/lib/maplibre/controls/CustomControl/Control.svelte
cat src/lib/maplibre/controls/CustomControl/Group.svelte
cat src/lib/maplibre/controls/CustomControl/Icon.svelte

Length of output: 1940

src/lib/maplibre/controls/CustomControl/Icon.svelte Outdated Show resolved Hide resolved
src/lib/maplibre/controls/CustomControl/Control.svelte Outdated Show resolved Hide resolved
src/content/examples/custom-control/MyControl.ts Outdated Show resolved Hide resolved
src/content/examples/custom-control/CustomControl.svelte Outdated Show resolved Hide resolved
Copy link
Member

@ciscorn ciscorn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!
I’ve made a few comments suggesting improvements.

src/lib/maplibre/controls/CustomControl/Icon.svelte Outdated Show resolved Hide resolved
src/lib/maplibre/controls/CustomControl/Icon.svelte Outdated Show resolved Hide resolved
src/content/examples/custom-control/CustomControl.svelte Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (1)
src/content/examples/custom-control/CustomControl.svelte (1)

10-14: Consider making map style URLs configurable

The map style URLs are currently hardcoded. Consider making them configurable through component props to improve reusability and maintainability.

+export let darkModeStyle = 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json';
+export let lightModeStyle = 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json';

 const mapStyle = $derived(
 	isDarkMode
-		? 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json'
-		: 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json'
+		? darkModeStyle
+		: lightModeStyle
 );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4462d25 and 5f68f52.

📒 Files selected for processing (2)
  • src/content/examples/custom-control/CustomControl.svelte (1 hunks)
  • src/lib/maplibre/controls/CustomControl/Icon.svelte (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/maplibre/controls/CustomControl/Icon.svelte
🔇 Additional comments (4)
src/content/examples/custom-control/CustomControl.svelte (4)

5-5: Update import path from .js to .ts

The import statement needs to be updated to match the correct file extension.


16-25: LGTM! Clean implementation of control callbacks

The control initialization is well-structured with clear toggle functions that maintain single responsibility.


36-59: LGTM! Well-organized control layout

The control layout is well-structured with clear positioning and good use of CustomControl components. The implementation allows for both IControl injection and custom Svelte elements.


61-67: Consider error handling for terrain tiles

The terrain tiles configuration could benefit from improved error handling and configuration.

@Kanahiro
Copy link
Member Author

@ciscorn Thank you for comments! I revised as you advised.

@Kanahiro Kanahiro requested a review from ciscorn November 21, 2024 03:16
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Outside diff range and nitpick comments (11)
src/content/examples/animate-images/AnimateImages.svelte (2)

7-13: Consider adding performance optimizations and user controls.

The current implementation continuously updates frames regardless of tab visibility or user preference.

Consider these improvements:

  1. Add visibility detection:
 $effect(() => {
 	let animationFrameId: number;
+	const isVisible = document.visibilityState === 'visible';
+	
+	function handleVisibilityChange() {
+		if (document.visibilityState === 'hidden') {
+			cancelAnimationFrame(animationFrameId);
+		} else {
+			animationFrameId = requestAnimationFrame(update);
+		}
+	}
+	
+	document.addEventListener('visibilitychange', handleVisibilityChange);
 	function update() {
 		frame = Math.round((performance.now() / 1000) * 5) % FRAME_COUNT;
 		animationFrameId = requestAnimationFrame(update);
 	}
-	animationFrameId = requestAnimationFrame(update);
+	if (isVisible) {
+		animationFrameId = requestAnimationFrame(update);
+	}
 	
 	return () => {
 		cancelAnimationFrame(animationFrameId);
+		document.removeEventListener('visibilitychange', handleVisibilityChange);
 	};
 });
  1. Add user controls:
+let isPlaying = $state(true);
+let speed = $state(5);
+
 $effect(() => {
 	let animationFrameId: number;
+	if (!isPlaying) return;
+	
 	function update() {
-		frame = Math.round((performance.now() / 1000) * 5) % FRAME_COUNT;
+		frame = Math.round((performance.now() / 1000) * speed) % FRAME_COUNT;
 		animationFrameId = requestAnimationFrame(update);
 	}
 	animationFrameId = requestAnimationFrame(update);
 	
 	return () => {
 		cancelAnimationFrame(animationFrameId);
 	};
 });

7-13: Consider adding error boundaries for the animation loop.

The animation loop could potentially throw errors if the performance API is not available or if frame calculations fail.

Add error handling:

 $effect(() => {
 	let animationFrameId: number;
 	function update() {
+		try {
 			frame = Math.round((performance.now() / 1000) * 5) % FRAME_COUNT;
 			animationFrameId = requestAnimationFrame(update);
+		} catch (error) {
+			console.error('Animation update failed:', error);
+			cancelAnimationFrame(animationFrameId);
+		}
 	}
 	animationFrameId = requestAnimationFrame(update);
 	
 	return () => {
 		cancelAnimationFrame(animationFrameId);
 	};
 });
src/lib/maplibre/controls/CustomControl.svelte (3)

15-16: Enhance props validation and error messaging.

While the basic validation is present, consider making it more robust and user-friendly.

Consider this improvement:

-if (!givenControl && !children) throw new Error('You must provide either control or children.');
+if (!givenControl && !children) {
+  throw new Error(
+    'CustomControl requires either a control prop (IControl instance) or children prop (Svelte components). ' +
+    'Please provide one of these props to render the control.'
+  );
+}

18-19: Improve error message for map initialization.

The current error message could be more helpful for debugging.

Consider this improvement:

-if (!mapCtx.map) throw new Error('Map instance is not initialized.');
+if (!mapCtx.map) {
+  throw new Error(
+    'Map instance is not initialized. Ensure CustomControl is used within a Map component context.'
+  );
+}

1-52: Consider adding JSDoc documentation for better developer experience.

The component is well-architected, but adding comprehensive documentation would improve its usability.

Consider adding JSDoc comments at the component level:

/**
 * CustomControl component for MapLibre GL JS.
 * Allows integration of both pre-defined MapLibre controls and custom Svelte components.
 *
 * @component
 * @example
 * ```svelte
 * <CustomControl position="top-right">
 *   <button>Custom Button</button>
 * </CustomControl>
 * ```
 *
 * @example
 * ```svelte
 * <CustomControl position="top-left" control={new NavigationControl()} />
 * ```
 */
🧰 Tools
🪛 eslint

[error] 43-43: Expected an assignment or function call and instead saw an expression.

(@typescript-eslint/no-unused-expressions)

src/content/examples/custom-control/MyControl.ts (2)

1-4: Add JSDoc documentation to interface methods.

Consider adding documentation to explain the purpose and behavior of each toggle method.

 interface MyControlConstructorOptions {
+  /** Toggles the terrain layer. Returns true if terrain is enabled after toggle. */
   toggleTerrain: () => boolean;
+  /** Toggles the hillshade layer. Returns true if hillshade is enabled after toggle. */
   toggleHillshade: () => boolean;
 }

6-14: Add parameter validation in constructor.

Consider adding checks for required callbacks to prevent runtime errors.

   constructor(options: MyControlConstructorOptions) {
+    if (!options?.toggleTerrain || !options?.toggleHillshade) {
+      throw new Error('Both toggleTerrain and toggleHillshade callbacks are required');
+    }
     this._toggleTerrain = options.toggleTerrain;
     this._toggleHillshade = options.toggleHillshade;
   }
src/content/examples/limit-interaction/LimitInteraction.svelte (3)

8-17: Add TypeScript type definitions for state variables.

Consider adding explicit type definitions for better type safety and code maintainability.

-let zoomRange = $state([5, 11]);
-let pitchRange = $state([0, 60]);
-let boxZoom = $state(true);
+let zoomRange = $state<[number, number]>([5, 11]);
+let pitchRange = $state<[number, number]>([0, 60]);
+let boxZoom = $state<boolean>(true);

23-24: Consider adjusting initial zoom for better UX.

The initial zoom (5) matches the lower bound of zoomRange [5, 11]. If a user decreases the minimum zoom below 5, the map won't automatically adjust to show this new range. Consider setting the initial zoom to a value well within the range.

-	zoom={5}
+	zoom={8}

Also applies to: 33-34


38-39: Enhance control panel accessibility and usability.

Consider the following improvements:

  1. Add aria-label to the control panel for better accessibility
  2. Increase the background opacity for better readability on dark maps
  3. Group related controls (e.g., zoom/pitch controls, touch controls) using fieldset or similar
-	<div class="absolute right-3 top-3 z-10 flex flex-col gap-1 rounded bg-background/60 p-3 text-sm backdrop-blur">
+	<div 
+		class="absolute right-3 top-3 z-10 flex flex-col gap-1 rounded bg-background/80 p-3 text-sm backdrop-blur"
+		aria-label="Map interaction controls"
+		role="region"
+	>
src/content/examples/custom-control/CustomControl.svelte (1)

1-88: Consider externalizing configuration

The component has several hardcoded values that could be made configurable:

  • Map style URLs
  • Initial center coordinates
  • Terrain tiles URL
  • Zoom levels

Consider extracting these into a configuration object that can be passed as props.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 5f68f52 and e245f2b.

📒 Files selected for processing (15)
  • README.md (0 hunks)
  • package.json (1 hunks)
  • src/content/examples/Index.svelte (1 hunks)
  • src/content/examples/animate-images/AnimateImages.svelte (1 hunks)
  • src/content/examples/custom-control/CustomControl.svelte (1 hunks)
  • src/content/examples/custom-control/MyControl.ts (1 hunks)
  • src/content/examples/custom-control/content.svelte.md (1 hunks)
  • src/content/examples/hover-styles/HoverStyles.svelte (0 hunks)
  • src/content/examples/limit-interaction/LimitInteraction.svelte (1 hunks)
  • src/content/examples/limit-interaction/content.svelte.md (1 hunks)
  • src/lib/maplibre/controls/CustomControl.svelte (1 hunks)
  • src/lib/maplibre/index.ts (1 hunks)
  • src/lib/maplibre/map/MapLibre.svelte (3 hunks)
  • src/routes/components/+page.ts (1 hunks)
  • src/routes/examples/+page.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • README.md
  • src/content/examples/hover-styles/HoverStyles.svelte
✅ Files skipped from review due to trivial changes (2)
  • package.json
  • src/content/examples/limit-interaction/content.svelte.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/content/examples/Index.svelte
  • src/content/examples/custom-control/content.svelte.md
  • src/lib/maplibre/index.ts
🧰 Additional context used
🪛 Biome
src/content/examples/custom-control/MyControl.ts

[error] 52-52: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🪛 eslint
src/lib/maplibre/controls/CustomControl.svelte

[error] 43-43: Expected an assignment or function call and instead saw an expression.

(@typescript-eslint/no-unused-expressions)

🔇 Additional comments (9)
src/routes/examples/+page.ts (1)

4-4: 🛠️ Refactor suggestion

Consider using more specific redirect status codes.

The change from 303 to 302 might lead to inconsistent behavior across browsers. Consider using:

  • 303 (See Other) if you want to force a GET request
  • 307 (Temporary Redirect) if you want to preserve the original HTTP method

The 302 status code has historically led to inconsistent handling of the HTTP method across different browsers.

Let's verify the redirect behavior across the application:

src/routes/components/+page.ts (1)

4-4: Consider using 303 instead of 302 for better browser consistency.

While 302 works for this static redirect, 303 is generally preferred in modern web applications as it ensures consistent GET requests across all browsers. However, if this is intentionally aligned with the change in src/routes/examples/+page.ts, the current implementation is acceptable.

Let's verify if this route handles any form submissions that might be affected by this change:

✅ Verification successful

Let me gather more information about the form handling by using different file patterns:


Let me check the content and purpose of the markdown component this redirects to:


The 302 redirect is appropriate for this use case

This is a simple static redirect from /components to /components/markdown without any form handling or POST operations. The change from 303 to 302 is safe in this context as:

  1. It's a GET-to-GET redirect (no form submissions involved)
  2. The redirect is temporary and straightforward
  3. There are no form handlers or POST operations in this route that could be affected
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for form handling in the components route
# Look for form submissions or POST handlers that might be affected by the redirect

# Search for form elements or POST handlers in related component files
rg -t svelte 'method=(["'\''](POST|post)["'\'']|POST|post)' src/routes/components/
rg -t svelte '<form' -A 5 src/routes/components/

# Check for server-side POST handlers
rg -t ts 'POST|post' src/routes/components/

Length of output: 503


Script:

#!/bin/bash
# Search for form-related patterns without file type restrictions
rg '<form' src/routes/components/
rg 'method=' src/routes/components/

# Check the actual component file being redirected to
rg -l 'markdown' src/routes/components/

Length of output: 142


Script:

#!/bin/bash
# Check the markdown component and its related files
cat src/routes/components/+page.ts
fd markdown src/routes/components/ --type f

Length of output: 192

src/lib/maplibre/controls/CustomControl.svelte (2)

1-14: LGTM! Well-structured type definitions and imports.

The Props interface is well-defined with appropriate types, and the documentation reference is helpful.


48-52: LGTM! Clean and efficient template implementation.

The template follows Svelte best practices with proper class handling and safe children rendering.

src/content/examples/custom-control/CustomControl.svelte (3)

10-10: Update the file extension in the import statement

The import statement uses .js extension but the file is actually a TypeScript file.

- import { MyControl } from './MyControl.js';
+ import { MyControl } from './MyControl.ts';

23-32: LGTM: Control initialization is well implemented

The MyControl instance is properly initialized with callbacks that handle state updates and return the new state.


74-80: Add error handling for terrain tiles

The terrain tiles URL is hardcoded and lacks error handling.

Let's verify the terrain tiles URL availability:

#!/bin/bash
# Check if the terrain tiles endpoint is accessible
curl -I https://demotiles.maplibre.org/terrain-tiles/0/0/0.png
src/lib/maplibre/map/MapLibre.svelte (2)

544-548: LGTM! Clean and safe template structure.

The template changes improve component stability by:

  1. Ensuring children are only rendered after map initialization
  2. Using proper optional chaining for children rendering
  3. Double-checking both map existence and loaded state

429-432: LGTM! Verify initialization behavior.

The addition of the !firstRun check to maxPitch/minPitch updates is consistent with other map options and prevents potential race conditions during initialization.

Let's verify that this change doesn't affect the initial map state:

Also applies to: 441-444

✅ Verification successful

Verified: !firstRun check for pitch updates is correct and safe

The code analysis shows that pitch handling is well-structured:

  • Initial pitch value is properly bound via pitch = $bindable(undefined)
  • Pitch updates are handled in a comprehensive transform update block that includes other related properties
  • The component properly sets up pitch-related event listeners (pitch, pitchstart, pitchend)

The !firstRun check is safe because:

  • The map's initial pitch is set through the map initialization options
  • Post-initialization updates are handled through the transform update logic
  • This matches the pattern used for other transform-related properties
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any initialization-related issues with pitch settings
# Look for patterns where pitch settings are crucial during map initialization

# Search for pitch-related initialization code
rg -A 5 'pitch.*init|init.*pitch'

# Search for any pitch-related issues or bugs reported
rg -A 5 'pitch.*bug|bug.*pitch|pitch.*issue|issue.*pitch'

# Look for test files related to pitch functionality
fd -e test.ts -e test.js -e spec.ts -e spec.js | xargs rg 'pitch'

Length of output: 161


Script:

#!/bin/bash
# Let's try a different approach to analyze pitch-related functionality

# Look for pitch-related code in MapLibre.svelte
rg -A 10 "pitch" "src/lib/maplibre/map/MapLibre.svelte"

# Search for map initialization code to understand the setup flow
ast-grep --pattern 'onMount($$$)'

# Look for any pitch-related props or options in the component
ast-grep --pattern 'export let $_ = $_'

Length of output: 2607

src/content/examples/animate-images/AnimateImages.svelte Outdated Show resolved Hide resolved
Comment on lines +38 to +45
$effect(() => {
if (control) {
mapCtx.map?.addControl(control, position);
}
return () => {
control && mapCtx.map?.removeControl(control);
};
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix linting error and improve cleanup logic.

The effect hook has a linting error and could benefit from more explicit cleanup.

Apply this improvement:

 $effect(() => {
   if (control) {
     mapCtx.map?.addControl(control, position);
   }
   return () => {
-    control && mapCtx.map?.removeControl(control);
+    if (control && mapCtx.map) {
+      mapCtx.map.removeControl(control);
+    }
   };
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$effect(() => {
if (control) {
mapCtx.map?.addControl(control, position);
}
return () => {
control && mapCtx.map?.removeControl(control);
};
});
$effect(() => {
if (control) {
mapCtx.map?.addControl(control, position);
}
return () => {
if (control && mapCtx.map) {
mapCtx.map.removeControl(control);
}
};
});
🧰 Tools
🪛 eslint

[error] 43-43: Expected an assignment or function call and instead saw an expression.

(@typescript-eslint/no-unused-expressions)

@ciscorn ciscorn force-pushed the feat/custom-control branch from 3ece5ce to a870266 Compare November 21, 2024 06:31
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (6)
src/lib/maplibre/controls/CustomControl.svelte (2)

15-16: Enhance error message for better debugging.

While the validation is correct, the error message could be more descriptive to help developers understand the requirements better.

-if (!givenControl && !children) throw new Error('You must provide either control or children.');
+if (!givenControl && !children) throw new Error('CustomControl requires either a control prop (IControl instance) or children prop (Svelte component/snippet) to render.');

48-52: Consider adding ARIA attributes for accessibility.

The control might benefit from appropriate ARIA attributes to improve accessibility.

-<div bind:this={el} class={`maplibregl-ctrl ${className}`} class:maplibregl-ctrl-group={group}>
+<div 
+  bind:this={el} 
+  class={`maplibregl-ctrl ${className}`} 
+  class:maplibregl-ctrl-group={group}
+  role="group"
+  aria-label="Map custom control">
   {@render children?.()}
 </div>
src/content/examples/custom-control/MyControl.ts (3)

1-4: Add JSDoc documentation for better maintainability.

Consider adding JSDoc comments to document the interface and its methods, including parameter descriptions and return value meanings.

+/**
+ * Options for initializing MyControl
+ */
 interface MyControlConstructorOptions {
+  /** Toggle terrain layer visibility. Returns true if terrain is enabled. */
   toggleTerrain: () => boolean;
+  /** Toggle hillshade layer visibility. Returns true if hillshade is enabled. */
   toggleHillshade: () => boolean;
 }

6-14: Improve type safety for container field.

Consider using a more specific type for the container field to better handle its lifecycle.

 class MyControl implements maplibregl.IControl {
-  private _container: HTMLElement | undefined;
+  private _container: HTMLDivElement | null = null;
   private _toggleTerrain: () => boolean;
   private _toggleHillshade: () => boolean;

16-49: Consider implementing keyboard navigation support.

While previous comments covered styling and accessibility improvements, the control should also support keyboard navigation between buttons.

   onAdd() {
     this._container = document.createElement('div');
-    this._container.className = 'maplibregl-ctrl maplibregl-ctrl-group p-2 rounded flex w-[240px] gap-x-2';
+    this._container.className = 'maplibregl-ctrl maplibregl-ctrl-group p-2 rounded flex w-[240px] gap-x-2';
+    this._container.setAttribute('role', 'toolbar');
+    this._container.setAttribute('aria-label', 'Map layer controls');

     const toggleTerrain = document.createElement('button');
     toggleTerrain.textContent = 'Disable Terrain';
     toggleTerrain.type = 'button';
+    toggleTerrain.tabIndex = 0;
     // ... rest of the button configuration ...

     const toggleHillshade = document.createElement('button');
     toggleHillshade.textContent = 'Disable Hillshade';
     toggleHillshade.type = 'button';
+    toggleHillshade.tabIndex = 0;
     // ... rest of the button configuration ...

+    // Add keyboard navigation
+    this._container.addEventListener('keydown', (e) => {
+      if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
+        const buttons = [toggleTerrain, toggleHillshade];
+        const currentIdx = document.activeElement === toggleTerrain ? 0 : 1;
+        const nextIdx = e.key === 'ArrowRight' ? 
+          (currentIdx + 1) % buttons.length : 
+          (currentIdx - 1 + buttons.length) % buttons.length;
+        buttons[nextIdx].focus();
+      }
+    });
src/content/examples/custom-control/CustomControl.svelte (1)

35-35: Consider making map height more responsive

The current height setting h-[50vh] min-h-[200px] might not provide optimal responsiveness in all scenarios. Consider using CSS custom properties or Svelte props to make the height configurable.

- <MapLibre class="h-[50vh] min-h-[200px]" style={mapStyle} zoom={12} pitch={40} maxPitch={85} bind:center>
+ <MapLibre 
+   class="h-[var(--map-height,50vh)] min-h-[var(--map-min-height,200px)]" 
+   style={mapStyle} 
+   zoom={12} 
+   pitch={40} 
+   maxPitch={85} 
+   bind:center
+ >
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between e245f2b and a870266.

📒 Files selected for processing (8)
  • README.md (0 hunks)
  • package.json (1 hunks)
  • src/content/examples/Index.svelte (1 hunks)
  • src/content/examples/custom-control/CustomControl.svelte (1 hunks)
  • src/content/examples/custom-control/MyControl.ts (1 hunks)
  • src/content/examples/custom-control/content.svelte.md (1 hunks)
  • src/lib/maplibre/controls/CustomControl.svelte (1 hunks)
  • src/lib/maplibre/index.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • README.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • package.json
  • src/content/examples/Index.svelte
  • src/content/examples/custom-control/content.svelte.md
  • src/lib/maplibre/index.ts
🧰 Additional context used
🪛 Biome
src/content/examples/custom-control/MyControl.ts

[error] 52-52: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🪛 eslint
src/lib/maplibre/controls/CustomControl.svelte

[error] 43-43: Expected an assignment or function call and instead saw an expression.

(@typescript-eslint/no-unused-expressions)

🔇 Additional comments (10)
src/lib/maplibre/controls/CustomControl.svelte (4)

1-14: LGTM! Well-structured type definitions with helpful documentation link.

The Props interface is well-defined with appropriate types, and the documentation reference is valuable for maintainers.


21-36: Previous type safety concerns are still valid.

The concerns about type safety in the control implementation raised in the previous review still need to be addressed.


38-45: Previous effect hook concerns are still valid.

The linting error and cleanup logic improvements suggested in the previous review still need to be addressed.

🧰 Tools
🪛 eslint

[error] 43-43: Expected an assignment or function call and instead saw an expression.

(@typescript-eslint/no-unused-expressions)


1-52: Verify component exports and usage.

Let's ensure the component is properly exported and used consistently across the application.

✅ Verification successful

Component is properly exported and used correctly

The component is correctly exported in the library's index file and the usage examples demonstrate proper implementation with all supported props:

  • Default export in src/lib/maplibre/index.ts
  • Examples show correct usage of position, control, class, and children props
  • Both control object and direct children approaches are demonstrated
  • Component is being used in documentation examples
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the component is properly exported
rg -l "CustomControl" src/lib/maplibre/index.ts

# Find all usage instances to verify proper implementation
rg -l "CustomControl" src/content/examples/

Length of output: 228


Script:

#!/bin/bash
# Check how the component is exported in index.ts
rg "CustomControl" src/lib/maplibre/index.ts

# Check usage examples
rg -A 5 "CustomControl" src/content/examples/custom-control/content.svelte.md
rg -A 5 "CustomControl" src/content/examples/custom-control/CustomControl.svelte

Length of output: 1980

🧰 Tools
🪛 eslint

[error] 43-43: Expected an assignment or function call and instead saw an expression.

(@typescript-eslint/no-unused-expressions)

src/content/examples/custom-control/CustomControl.svelte (6)

10-10: Import extension needs to be updated

A previous review already identified this issue.


12-22: Well-structured state management

The state management is clean, properly typed, and follows Svelte best practices.


23-32: Clean control initialization with proper callback handling

The MyControl initialization is well-structured with clear toggle callbacks that properly manage state.


41-47: Accessibility improvements needed

A previous review already identified accessibility issues in these control buttons.

Also applies to: 52-63


70-70: Fix coordinate display

A previous review already identified the coordinate display bug.


74-80: Error handling needed for terrain tiles

A previous review already identified the need for error handling in terrain tiles.

Copy link
Member

@ciscorn ciscorn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kanahiro Thank you!

I've made some simplifications:

  • Removed CustomControl.Group as it can now be represented as <CustomControl group={true}>
  • Also removed CustomControl.Group since it can be expressed with a standard HTML <button>.

Let me know if you have any thoughts or suggestions!

@ciscorn ciscorn merged commit 25075c5 into main Nov 21, 2024
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Custom control buttons
2 participants