,
7 | idField = 'id'
8 | ) {
9 | const subscribe = createSubscriber((update) => {
10 | const handleChange = () => update();
11 |
12 | collection.on('add', handleChange);
13 | collection.on('remove', handleChange);
14 |
15 | return () => {
16 | collection.un('add', handleChange);
17 | collection.un('remove', handleChange);
18 | };
19 | });
20 |
21 | return {
22 | get collection() {
23 | subscribe();
24 | return collection;
25 | },
26 | get length() {
27 | subscribe();
28 | return collection.getLength();
29 | },
30 | get array() {
31 | subscribe();
32 | return collection.getArray();
33 | },
34 | has(feature: T) {
35 | subscribe();
36 | return collection.getArray().includes(feature);
37 | },
38 | hasId(id: string) {
39 | subscribe();
40 | return collection.getArray().some((f) => f.get(idField) === id);
41 | }
42 | };
43 | }
44 |
--------------------------------------------------------------------------------
/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | export { Map, MapRoot, MapView } from './components/map/index.js';
2 | export {
3 | Layer,
4 | LayerTile,
5 | LayerVector,
6 | LayerWebGL,
7 | LayerStatic
8 | } from './components/layers/index.js';
9 | export {
10 | Feature,
11 | FeaturePoint,
12 | FeatureLineString,
13 | FeaturePolygon
14 | } from './components/features/index.js';
15 | export {
16 | Interaction,
17 | InteractionSelect,
18 | InteractionHover
19 | } from './components/interactions/index.js';
20 | export { Overlay, OverlayTooltip, TooltipManager } from './components/overlays/index.js';
21 |
22 | export {
23 | MAP_CONTEXT_KEY,
24 | LAYER_CONTEXT_KEY,
25 | type MapContext,
26 | type ViewProps,
27 | type MapProps,
28 | type LayerContext,
29 | // Component Props Types
30 | type MapRootProps,
31 | type MapViewProps,
32 | type LayerTileProps,
33 | type LayerStaticProps,
34 | type LayerVectorProps,
35 | type LayerWebGLProps,
36 | type FeaturePointProps,
37 | type FeatureLineStringProps,
38 | type FeaturePolygonProps,
39 | type InteractionSelectProps,
40 | type InteractionHoverProps,
41 | type OverlayTooltipProps,
42 | type TooltipManagerProps
43 | } from './types.js';
44 |
--------------------------------------------------------------------------------
/site/src/lib/components/ui/popover/popover-content.svelte:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
29 |
30 |
--------------------------------------------------------------------------------
/site/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte:
--------------------------------------------------------------------------------
1 |
16 |
17 |
27 | {@render children?.()}
28 |
29 |
30 |
--------------------------------------------------------------------------------
/site/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
23 | {#snippet children({ checked })}
24 |
25 | {#if checked}
26 |
27 | {/if}
28 |
29 | {@render childrenProp?.({ checked })}
30 | {/snippet}
31 |
32 |
--------------------------------------------------------------------------------
/site/src/lib/examples/tooltip-select.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
9 | {props.name || 'Feature'}
10 | {props.type || ''}
11 |
12 |
13 | {#each Object.entries(props).filter(([key]) => key !== 'geometry') as [key, value]}
14 |
15 | {key}:
16 | {value}
17 |
18 | {/each}
19 |
20 |
21 | alert(`Clicked on ${props.name}`)}
23 | class="pointer-events-auto w-full cursor-pointer rounded-md border-none bg-indigo-600 px-4 py-2 text-sm font-medium text-white transition-colors duration-200 hover:bg-indigo-700"
24 | >
25 | View Details
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/site/src/lib/components/ui/switch/switch.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
23 |
29 |
30 |
--------------------------------------------------------------------------------
/site/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
27 |
28 |
--------------------------------------------------------------------------------
/site/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte:
--------------------------------------------------------------------------------
1 |
16 |
17 |
28 |
--------------------------------------------------------------------------------
/site/svelte.config.js:
--------------------------------------------------------------------------------
1 | import { mdsvex } from 'mdsvex';
2 | import adapter from '@sveltejs/adapter-static';
3 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
4 | import { createHighlighter } from 'shiki';
5 | import rehypeSlug from 'rehype-slug';
6 | import rehypeAutolinkHeadings from 'rehype-autolink-headings';
7 | import { remarkTocHeadings } from './remark-toc-headings.js';
8 |
9 | const highlighter = await createHighlighter({
10 | themes: ['github-dark', 'github-light'],
11 | langs: ['typescript', 'javascript', 'svelte', 'html', 'css', 'bash', 'json']
12 | });
13 |
14 | const mdsvexOptions = {
15 | extensions: ['.svx', '.md'],
16 | highlight: {
17 | highlighter: (code, lang) => {
18 | const html = highlighter.codeToHtml(code, {
19 | lang: lang || 'text',
20 | themes: {
21 | light: 'github-light',
22 | dark: 'github-dark'
23 | },
24 | defaultColor: false
25 | });
26 | return `{@html \`${html}\`}`;
27 | }
28 | },
29 | remarkPlugins: [remarkTocHeadings],
30 | rehypePlugins: [rehypeSlug, [rehypeAutolinkHeadings, { behavior: 'wrap' }]]
31 | };
32 |
33 | const config = {
34 | preprocess: [vitePreprocess(), mdsvex(mdsvexOptions)],
35 | kit: {
36 | adapter: adapter({
37 | fallback: '404.html'
38 | }),
39 | paths: {
40 | base: process.argv.includes('dev') ? '' : process.env.BASE_PATH
41 | }
42 | },
43 | extensions: ['.svelte', '.svx', '.md']
44 | };
45 |
46 | export default config;
47 |
--------------------------------------------------------------------------------
/site/remark-toc-headings.js:
--------------------------------------------------------------------------------
1 | import { visit } from 'unist-util-visit';
2 |
3 | /**
4 | * Remark plugin to add data-toc attribute to headings marked with {.toc}
5 | *
6 | * Usage in markdown:
7 | * ## My Heading {.toc}
8 | * ### Another Section {.toc}
9 | * ## Regular Heading (will not appear in TOC)
10 | */
11 | export function remarkTocHeadings() {
12 | return function transformer(tree) {
13 | visit(tree, 'heading', (node) => {
14 | // Only process h2, h3, h4
15 | if (node.depth < 2 || node.depth > 4) return;
16 |
17 | // Check for {.toc} marker in any text node
18 | let hasTocMarker = false;
19 | const tocMarkerRegex = /\s*\{[#.]toc\}\s*$/;
20 |
21 | // Process all children to find and remove the marker
22 | for (let i = 0; i < node.children.length; i++) {
23 | const child = node.children[i];
24 | if (child.type === 'text') {
25 | const text = child.value;
26 | if (tocMarkerRegex.test(text)) {
27 | hasTocMarker = true;
28 | // Remove the marker from the text
29 | child.value = text.replace(tocMarkerRegex, '').trim();
30 | break; // Only process the first match
31 | }
32 | }
33 | }
34 |
35 | // If we found the marker, add the data-toc attribute
36 | if (hasTocMarker) {
37 | node.data = node.data || {};
38 | node.data.hProperties = node.data.hProperties || {};
39 | node.data.hProperties['data-toc'] = true;
40 | }
41 | });
42 |
43 | return tree;
44 | };
45 | }
46 |
--------------------------------------------------------------------------------
/site/src/lib/components/ui/select/select-item.svelte:
--------------------------------------------------------------------------------
1 |
15 |
16 |
26 | {#snippet children({ selected, highlighted })}
27 |
28 | {#if selected}
29 |
30 | {/if}
31 |
32 | {#if childrenProp}
33 | {@render childrenProp({ selected, highlighted })}
34 | {:else}
35 | {label || value}
36 | {/if}
37 | {/snippet}
38 |
39 |
--------------------------------------------------------------------------------
/site/src/lib/components/ui/scroll-area/scroll-area.svelte:
--------------------------------------------------------------------------------
1 |
20 |
21 |
27 |
31 | {@render children?.()}
32 |
33 | {#if orientation === 'vertical' || orientation === 'both'}
34 |
35 | {/if}
36 | {#if orientation === 'horizontal' || orientation === 'both'}
37 |
38 | {/if}
39 |
40 |
41 |
--------------------------------------------------------------------------------
/site/src/lib/components/docs/map-example.svelte:
--------------------------------------------------------------------------------
1 |
20 |
21 | {#if Component && source}
22 |
23 | {#if title}
24 |
{title}
25 | {/if}
26 |
27 | {#if description}
28 |
{description}
29 | {/if}
30 |
31 |
32 | {#snippet preview()}
33 | {#if customPreview}
34 | {@render customPreview()}
35 | {:else}
36 |
37 | {/if}
38 | {/snippet}
39 |
40 | {#snippet code()}
41 |
42 | {/snippet}
43 |
44 |
45 | {:else}
46 |
47 | Example "{name}" not found in registry
48 |
49 | {/if}
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Svelte OpenLayers
2 |
3 | A composable, component-based OpenLayers integration for Svelte 5. Build interactive maps using declarative components while maintaining full access to OpenLayers' powerful functionality.
4 |
5 | 
6 | 
7 | 
8 |
9 | ## Installation
10 |
11 | ```bash
12 | npm install svelte-openlayers ol
13 | # or
14 | bun add svelte-openlayers ol
15 | ```
16 |
17 | ## Quick Start
18 |
19 | ```svelte
20 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
38 | ```
39 |
40 | ## Documentation
41 |
42 | For complete API documentation, examples, and guides, visit:
43 |
44 | **[svelte-openlayers.com](https://svelte-openlayers.com)**
45 |
46 | ## Contributing
47 |
48 | Contributions are welcome! Please read our [contributing guidelines](CONTRIBUTING.md) for details on our code of conduct and development process.
49 |
50 | ## License
51 |
52 | MIT © O
53 |
54 | ---
55 |
56 | Built with [Svelte 5](https://svelte.dev/) and [OpenLayers](https://openlayers.org/)
57 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to GitHub Pages
2 |
3 | on:
4 | push:
5 | branches: [main]
6 |
7 | jobs:
8 | build_site:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v4
13 |
14 | - name: Setup Node.js
15 | uses: actions/setup-node@v4
16 | with:
17 | node-version: 20
18 |
19 | - name: Clean workspace
20 | run: |
21 | rm -rf node_modules package-lock.json
22 | rm -rf site/node_modules site/package-lock.json site/bun.lockb
23 |
24 | - name: Install root dependencies
25 | run: npm install --legacy-peer-deps
26 |
27 | - name: Build library (root)
28 | run: npm run build
29 |
30 | - name: Install site dependencies
31 | run: npm install --legacy-peer-deps
32 | working-directory: site
33 |
34 | - name: Build site
35 | env:
36 | BASE_PATH: '/${{ github.event.repository.name }}'
37 | run: npm run build
38 | working-directory: site
39 |
40 | - name: Upload Artifacts
41 | uses: actions/upload-pages-artifact@v3
42 | with:
43 | path: 'site/build/'
44 |
45 | deploy:
46 | needs: build_site
47 | runs-on: ubuntu-latest
48 | permissions:
49 | pages: write
50 | id-token: write
51 | environment:
52 | name: github-pages
53 | url: ${{ steps.deployment.outputs.page_url }}
54 | steps:
55 | - name: Deploy
56 | id: deployment
57 | uses: actions/deploy-pages@v4
58 |
--------------------------------------------------------------------------------
/src/lib/components/layers/LayerStatic.svelte:
--------------------------------------------------------------------------------
1 |
65 |
--------------------------------------------------------------------------------
/site/src/routes/examples/bidirectional-selection/+page.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 | {meta?.title} - Example
12 |
13 |
14 |
15 |
16 |
Bidirectional Selection
17 |
18 | This example demonstrates how to synchronize selection between a table and map features.
19 | Selecting an item in the table highlights it on the map and vice versa.
20 |
21 |
22 |
23 |
Key Features:
24 |
25 | Click any row in the table to select and pan to that location on the map
26 | Click any marker on the map to select it in the table
27 | Hover over table rows to see visual feedback on the map
28 | Use the Clear Selection button to deselect all features
29 |
30 |
31 |
32 |
Interactive Demo
33 |
34 | {#snippet customPreview()}
35 |
36 | {/snippet}
37 |
38 |
39 |
--------------------------------------------------------------------------------
/site/src/lib/components/ui/select/select-trigger.svelte:
--------------------------------------------------------------------------------
1 |
16 |
17 |
27 | {@render children?.()}
28 |
29 |
30 |
--------------------------------------------------------------------------------
/site/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte:
--------------------------------------------------------------------------------
1 |
19 |
20 |
31 | {#snippet children({ checked, indeterminate })}
32 |
33 | {#if indeterminate}
34 |
35 | {:else}
36 |
37 | {/if}
38 |
39 | {@render childrenProp?.()}
40 | {/snippet}
41 |
42 |
--------------------------------------------------------------------------------
/site/src/lib/components/docs/api-table.svelte:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
{title}
22 |
23 |
24 |
25 |
26 |
27 | Property
28 | Type
29 | Default
30 | Description
31 |
32 |
33 |
34 | {#each items as item}
35 |
36 |
37 | {item.name}
38 | {#if item.required}
39 | Required
40 | {/if}
41 |
42 |
43 | {item.type}
44 |
45 |
46 | {#if item.default}
47 | {item.default}
48 | {:else}
49 | —
50 | {/if}
51 |
52 | {item.description}
53 |
54 | {/each}
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/site/src/routes/examples/features/+page.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 | {meta?.title} - Example
16 |
17 |
18 |
19 |
20 |
21 |
Features
22 |
23 | Demonstration of different geometry types: Points, LineStrings, and Polygons with custom
24 | styling and interactions.
25 |
26 |
27 |
28 |
29 |
Basic Marker Styling
30 |
31 |
32 |
33 |
34 |
35 |
Scoped Styling
36 |
37 |
38 |
39 |
40 |
41 |
Linestrings & Polygons
42 |
43 |
44 |
45 |
46 |
47 |
SVG Icon Markers
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/site/src/lib/components/icons/svelte-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
9 |
12 |
20 |
21 |
--------------------------------------------------------------------------------
/site/src/lib/components/ui/dropdown-menu/index.ts:
--------------------------------------------------------------------------------
1 | import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
2 | import CheckboxItem from './dropdown-menu-checkbox-item.svelte';
3 | import Content from './dropdown-menu-content.svelte';
4 | import Group from './dropdown-menu-group.svelte';
5 | import Item from './dropdown-menu-item.svelte';
6 | import Label from './dropdown-menu-label.svelte';
7 | import RadioGroup from './dropdown-menu-radio-group.svelte';
8 | import RadioItem from './dropdown-menu-radio-item.svelte';
9 | import Separator from './dropdown-menu-separator.svelte';
10 | import Shortcut from './dropdown-menu-shortcut.svelte';
11 | import Trigger from './dropdown-menu-trigger.svelte';
12 | import SubContent from './dropdown-menu-sub-content.svelte';
13 | import SubTrigger from './dropdown-menu-sub-trigger.svelte';
14 | import GroupHeading from './dropdown-menu-group-heading.svelte';
15 | const Sub = DropdownMenuPrimitive.Sub;
16 | const Root = DropdownMenuPrimitive.Root;
17 |
18 | export {
19 | CheckboxItem,
20 | Content,
21 | Root as DropdownMenu,
22 | CheckboxItem as DropdownMenuCheckboxItem,
23 | Content as DropdownMenuContent,
24 | Group as DropdownMenuGroup,
25 | Item as DropdownMenuItem,
26 | Label as DropdownMenuLabel,
27 | RadioGroup as DropdownMenuRadioGroup,
28 | RadioItem as DropdownMenuRadioItem,
29 | Separator as DropdownMenuSeparator,
30 | Shortcut as DropdownMenuShortcut,
31 | Sub as DropdownMenuSub,
32 | SubContent as DropdownMenuSubContent,
33 | SubTrigger as DropdownMenuSubTrigger,
34 | Trigger as DropdownMenuTrigger,
35 | GroupHeading as DropdownMenuGroupHeading,
36 | Group,
37 | GroupHeading,
38 | Item,
39 | Label,
40 | RadioGroup,
41 | RadioItem,
42 | Root,
43 | Separator,
44 | Shortcut,
45 | Sub,
46 | SubContent,
47 | SubTrigger,
48 | Trigger
49 | };
50 |
--------------------------------------------------------------------------------
/site/src/lib/components/ui/tooltip/tooltip-content.svelte:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
30 | {@render children?.()}
31 |
32 | {#snippet child({ props })}
33 |
44 | {/snippet}
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/site/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "site",
3 | "private": true,
4 | "version": "0.0.1",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite dev",
8 | "build": "vite build",
9 | "preview": "vite preview",
10 | "prepare": "svelte-kit sync || echo ''",
11 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
12 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
13 | "format": "prettier --write .",
14 | "lint": "prettier --check . && eslint ."
15 | },
16 | "dependencies": {
17 | "bits-ui": "^2.8.6",
18 | "lucide-svelte": "^0.542.0",
19 | "ol": "^10.6.1",
20 | "svelte-openlayers": "file:.."
21 | },
22 | "devDependencies": {
23 | "@eslint/compat": "^1.2.5",
24 | "@eslint/js": "^9.18.0",
25 | "@internationalized/date": "^3.8.1",
26 | "@lucide/svelte": "^0.515.0",
27 | "@sveltejs/adapter-auto": "^6.0.0",
28 | "@sveltejs/adapter-static": "^3.0.9",
29 | "@sveltejs/kit": "^2.16.0",
30 | "@sveltejs/vite-plugin-svelte": "^5.0.0",
31 | "@tailwindcss/vite": "^4.0.0",
32 | "@types/node": "^22.0.0",
33 | "clsx": "^2.1.1",
34 | "eslint": "^9.18.0",
35 | "eslint-config-prettier": "^10.0.1",
36 | "eslint-plugin-svelte": "^3.0.0",
37 | "globals": "^16.0.0",
38 | "mdsvex": "^0.12.3",
39 | "prettier": "^3.4.2",
40 | "prettier-plugin-svelte": "^3.3.3",
41 | "prettier-plugin-tailwindcss": "^0.6.11",
42 | "rehype-autolink-headings": "^7.1.0",
43 | "rehype-slug": "^6.0.0",
44 | "shiki": "^3.12.2",
45 | "svelte": "^5.0.0",
46 | "svelte-check": "^4.0.0",
47 | "tailwind-merge": "^3.3.1",
48 | "tailwind-variants": "^1.0.0",
49 | "tailwindcss": "^4.0.0",
50 | "tw-animate-css": "^1.3.8",
51 | "typescript": "^5.0.0",
52 | "typescript-eslint": "^8.20.0",
53 | "unist-util-visit": "^5.0.0",
54 | "vite": "^6.2.6"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/site/src/lib/components/ui/select/select-content.svelte:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
30 |
31 |
36 | {@render children?.()}
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/site/src/routes/docs/getting-started/+page.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | Welcome to Svelte OpenLayers! This guide will help you get up and running with interactive maps in your Svelte application.
4 |
5 | ## Installation {.toc}
6 |
7 | Install Svelte OpenLayers using your preferred package manager:
8 |
9 | ```bash
10 | # Using npm
11 | npm install svelte-openlayers
12 |
13 | # Using pnpm
14 | pnpm add svelte-openlayers
15 |
16 | # Using bun
17 | bun add svelte-openlayers
18 | ```
19 |
20 | ## Quick Start {.toc}
21 |
22 | Here's a simple example to get you started:
23 |
24 | ```svelte
25 |
28 |
29 |
30 |
31 |
32 |
33 | ```
34 |
35 | This creates a basic map centered on New York City with an OpenStreetMap tile layer.
36 |
37 | ## Core Concepts {.toc}
38 |
39 | - **Composable Components**: Build maps using nested Svelte components
40 | - **Reactive Properties**: Map properties automatically sync with Svelte state
41 | - **Context-Based**: Components communicate through Svelte's context API
42 | - **Type-Safe**: Full TypeScript support with proper type inference
43 |
44 | ## What's Currently Available {.toc}
45 |
46 | Svelte OpenLayers is in active development. Here's what's currently implemented:
47 |
48 | ### Components
49 | - **Map**: `Map.Root`, `Map.View`
50 | - **Layers**: `Layer.Tile`, `Layer.Vector`
51 | - **Features**: `Feature.Point`, `Feature.LineString`, `Feature.Polygon`
52 | - **Interactions**: `Interaction.Select`, `Interaction.Hover`
53 | - **Overlays**: `Overlay.Tooltip`, `TooltipManager`
54 |
55 | ### Coming Soon
56 | - Additional layer types (VectorTile, Image, WMS)
57 | - Drawing and editing interactions
58 | - Popup overlays and custom markers
59 | - Control components
60 | - Animation utilities
61 |
--------------------------------------------------------------------------------
/site/src/routes/docs/architecture/+page.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Architecture
3 | ---
4 |
5 | # Architecture
6 |
7 | Svelte OpenLayers follows an **event-driven architecture** where OpenLayers events are the source of truth, ensuring predictable behavior and preventing circular dependencies.
8 |
9 | ## Core Principles {.toc}
10 |
11 | ### 1. Composable Design {.toc}
12 |
13 | Components follow consistent patterns with independently importable, tree-shakeable modules:
14 |
15 | ```svelte
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | ```
27 |
28 | ### 2. Event-Driven Updates {.toc}
29 |
30 | OpenLayers events drive Svelte state updates, not the other way around.
31 |
32 | ### 3. Context-Based Communication {.toc}
33 |
34 | Components communicate through Svelte's context API:
35 |
36 | - `MapContext` - Shared by all map components
37 | - Auto-registration with parent contexts
38 | - Consistent interfaces across component types
39 |
40 | ## Component Hierarchy {.toc}
41 |
42 | ```
43 | Map.Root # Main container, provides MapContext
44 | ├── Map.View # Controls view (center, zoom, rotation)
45 | ├── Layer.Tile # Tile layers (OSM, XYZ sources)
46 | ├── Layer.Vector # Vector layers container
47 | │ ├── Feature.Point # Point geometries
48 | │ ├── Feature.LineString # Line geometries
49 | │ └── Feature.Polygon # Polygon geometries
50 | ├── Interaction.Select # Feature selection
51 | ├── Interaction.Hover # Feature hover
52 | └── Overlay.TooltipManager # Helper component that manages selection and hover interactions automatically
53 | ```
54 |
--------------------------------------------------------------------------------
/site/src/lib/components/ui/badge/badge.svelte:
--------------------------------------------------------------------------------
1 |
23 |
24 |
39 |
40 |
48 | {@render children?.()}
49 |
50 |
--------------------------------------------------------------------------------
/site/src/lib/components/docs/example-preview.svelte:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 | {#if title || description}
20 |
21 | {#if title}
22 |
{title}
23 | {/if}
24 | {#if description}
25 |
{description}
26 | {/if}
27 |
28 | {/if}
29 |
30 |
31 |
32 |
33 | (activeTab = 'preview')}
41 | >
42 | Preview
43 |
44 | (activeTab = 'code')}
52 | >
53 | Code
54 |
55 |
56 |
57 |
58 |
59 | {#if activeTab === 'preview'}
60 |
61 | {@render preview()}
62 |
63 | {:else}
64 |
65 | {@render code()}
66 |
67 | {/if}
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/site/src/lib/components/ui/input/input.svelte:
--------------------------------------------------------------------------------
1 |
21 |
22 | {#if type === "file"}
23 |
37 | {:else}
38 |
51 | {/if}
52 |
--------------------------------------------------------------------------------
/src/lib/components/overlays/OverlayTooltip.svelte:
--------------------------------------------------------------------------------
1 |
71 |
72 |
77 | {#if children}
78 | {@render children()}
79 | {:else if content}
80 | {@html content}
81 | {/if}
82 |
83 |
--------------------------------------------------------------------------------
/src/lib/components/layers/LayerTile.svelte:
--------------------------------------------------------------------------------
1 |
87 |
--------------------------------------------------------------------------------
/site/src/routes/examples/+layout.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
18 |
19 |
20 |
(mobileNavOpen = !mobileNavOpen)}
25 | >
26 |
27 | Toggle Examples Navigation
28 |
29 |
30 |
31 | {#if mobileNavOpen}
32 |
33 |
(mobileNavOpen = false)}
37 | aria-label="Close mobile navigation"
38 | >
39 |
48 |
49 | {/if}
50 |
51 |
52 |
53 |
54 |
55 |
56 | {@render children()}
57 |
58 |
59 |
60 |
61 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/lib/components/features/FeaturePoint.svelte:
--------------------------------------------------------------------------------
1 |
83 |
--------------------------------------------------------------------------------
/src/lib/components/interactions/InteractionHover.svelte:
--------------------------------------------------------------------------------
1 |
89 |
--------------------------------------------------------------------------------
/site/src/routes/examples/webgl-points/+page.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 | {meta?.title} - Example
12 |
13 |
14 |
15 |
16 |
17 |
WebGL Rendering
18 |
19 | High-performance rendering of large point datasets using WebGL with dynamic, expression-based
20 | styling. Render thousands of points efficiently with GPU acceleration.
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
Key Features:
30 |
31 | GPU-accelerated rendering for thousands of points
32 | Expression-based styling with interpolation and conditionals
33 | Dynamic style variables that can be updated at runtime
34 | Support for circles, shapes, and icons
35 | Hover interactions with style changes
36 | Time-based animations for rotating elements
37 |
38 |
39 |
40 |
41 |
Style Expressions:
42 |
43 | WebGL styles use expressions to dynamically compute visual properties based on feature
44 | attributes, zoom level, or time:
45 |
46 |
47 |
48 | ['get', 'property'] - Access feature properties
49 |
50 |
51 | ['interpolate', ['linear'], ...] - Smooth value transitions
52 |
53 |
54 | ['match', condition, value1, value2] - Conditional styling
55 |
56 |
57 | ['zoom'] - Current zoom level for scale-dependent rendering
58 |
59 |
60 | ['time'] - Animation variable for rotating elements
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/src/lib/components/features/FeatureLineString.svelte:
--------------------------------------------------------------------------------
1 |
83 |
--------------------------------------------------------------------------------
/site/src/lib/components/docs-nav.svelte:
--------------------------------------------------------------------------------
1 |
51 |
52 |
53 |
54 | {#each navigation as section}
55 |
56 |
{section.title}
57 | {#if section.items}
58 |
59 | {#each section.items as item}
60 |
69 | {item.title}
70 |
71 | {/each}
72 |
73 | {/if}
74 |
75 | {/each}
76 |
77 |
78 |
--------------------------------------------------------------------------------
/site/src/lib/components/ui/sheet/sheet-content.svelte:
--------------------------------------------------------------------------------
1 |
22 |
23 |
43 |
44 |
45 |
46 |
52 | {@render children?.()}
53 |
56 |
57 | Close
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/site/src/lib/components/examples-nav.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
33 |
34 |
35 | {#each categoriesWithExamples as category}
36 | {#if category.examples.length > 0}
37 |
63 | {/if}
64 | {/each}
65 |
66 |
67 |
--------------------------------------------------------------------------------
/src/lib/components/features/FeaturePolygon.svelte:
--------------------------------------------------------------------------------
1 |
88 |
--------------------------------------------------------------------------------
/site/src/routes/examples/layers/+page.svelte:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 | {meta?.title} - Example
22 |
23 |
24 |
25 |
26 |
Layer Management
27 |
28 | Managing multiple map layers with different data sources, base maps, and layer visibility
29 | controls.
30 |
31 |
32 |
33 |
34 |
35 |
Base Layer
36 |
37 | {#each mapSources as source}
38 | (activeBaseLayer = source.id)}
42 | >
43 | {source.name}
44 |
45 | {/each}
46 |
47 |
48 |
49 |
50 |
51 |
Data Layers
52 |
53 |
54 | Airports
55 |
56 |
57 |
58 | Railways
59 |
60 |
61 |
62 | Regions
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | {#snippet customPreview()}
71 |
72 | {/snippet}
73 |
74 |
75 |
--------------------------------------------------------------------------------
/site/src/lib/examples/tooltips-basic-demo.svelte:
--------------------------------------------------------------------------------
1 |
62 |
63 |
64 |
65 |
66 | s.id === 'carto-voyager')?.url}
69 | attributions={mapSources.find((s) => s.id === 'carto-voyager')?.attributions}
70 | />
71 |
72 |
73 | {#each landmarks as landmark}
74 | {@const { id, coordinates, ...rest } = landmark}
75 |
76 | {/each}
77 |
78 |
79 |
80 |
81 |
82 |
83 | {#if tooltipMode === 'hover'}
84 | Hover over landmarks to see their information
85 | {:else}
86 | Click on landmarks to toggle their information
87 | {/if}
88 |
89 |
--------------------------------------------------------------------------------
/src/lib/components/interactions/InteractionSelect.svelte:
--------------------------------------------------------------------------------
1 |
85 |
--------------------------------------------------------------------------------
/site/src/lib/components/ui/button/button.svelte:
--------------------------------------------------------------------------------
1 |
41 |
42 |
55 |
56 | {#if href}
57 |
67 | {@render children?.()}
68 |
69 | {:else}
70 |
78 | {@render children?.()}
79 |
80 | {/if}
81 |
--------------------------------------------------------------------------------
/src/lib/components/map/MapView.svelte:
--------------------------------------------------------------------------------
1 |
122 |
--------------------------------------------------------------------------------
/site/src/lib/examples/tooltips-component-demo.svelte:
--------------------------------------------------------------------------------
1 |
64 |
65 |
66 |
67 |
68 | s.id === 'carto-voyager')?.url}
71 | attributions={mapSources.find((s) => s.id === 'carto-voyager')?.attributions}
72 | />
73 |
74 |
75 | {#each landmarks as landmark}
76 | {@const { id, coordinates, ...rest } = landmark}
77 |
78 | {/each}
79 |
80 |
81 |
88 | {#snippet hoverSnippet(feature)}
89 | {@const props = feature.getProperties()}
90 |
91 | {/snippet}
92 | {#snippet selectSnippet(feature)}
93 | {@const props = feature.getProperties()}
94 |
95 | {/snippet}
96 |
97 |
98 |
99 |
100 | {#if tooltipMode === 'hover'}
101 | Hover over landmarks to see their information
102 | {:else}
103 | Click on landmarks to toggle their information
104 | {/if}
105 |
106 |
--------------------------------------------------------------------------------
/site/src/routes/docs/+page.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
Documentation
11 |
12 | Learn how to use Svelte OpenLayers to build powerful, interactive maps with composable
13 | components.
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Getting Started
23 |
24 |
25 |
26 |
27 | Get up and running with Svelte OpenLayers in minutes. Learn how to install the library and
28 | create your first map.
29 |
30 |
31 | Read Guide
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Architecture
42 |
43 |
44 |
45 |
46 | Understand the core principles and architecture that power Svelte OpenLayers.
47 |
48 |
49 | Learn More
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | Components
60 |
61 |
62 |
63 |
64 | Explore the complete set of components available for building your maps.
65 |
66 |
67 | View Components
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | Examples
78 |
79 |
80 |
81 |
82 | See Svelte OpenLayers in action with live, interactive examples.
83 |
84 |
85 | Browse Examples
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/site/src/routes/examples/+page.svelte:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 | Examples - svelte-openlayers
25 |
29 |
30 |
31 |
32 |
33 |
Interactive Examples
34 |
35 | Explore practical examples of svelte-openlayers components with live code demos and detailed
36 | explanations.
37 |
38 |
39 |
40 |
41 | {#each allExamples as example}
42 | {@const Icon = example.icon}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
{example.title}
52 |
53 | {#each example.tags as tag}
54 |
55 | {tag}
56 |
57 | {/each}
58 |
59 |
60 |
61 |
62 |
63 | {example.description}
64 |
65 |
66 |
67 |
68 |
69 |
Key Concepts:
70 |
71 | {#each example.concepts as concept}
72 |
73 | {concept}
74 |
75 | {/each}
76 |
77 |
78 |
79 | View Example
80 |
81 |
82 |
83 |
84 | {/each}
85 |
86 |
87 |
--------------------------------------------------------------------------------
/site/src/routes/examples/tooltips/+page.svelte:
--------------------------------------------------------------------------------
1 |
69 |
70 |
71 | Tooltips - Examples
72 |
76 |
77 |
78 |
79 |
Interactive Tooltips
80 |
81 | Different approaches to displaying contextual information when interacting with map features.
82 |
83 |
84 |
Basic Tooltips
85 |
86 | {#snippet customPreview()}
87 |
88 | {/snippet}
89 |
90 |
91 |
HTML Tooltips
92 |
93 | {#snippet customPreview()}
94 |
95 | {/snippet}
96 |
97 |
98 |
Component Tooltips
99 |
100 | {#snippet customPreview()}
101 |
102 | {/snippet}
103 |
104 |
105 |
--------------------------------------------------------------------------------
/site/src/lib/components/site-header.svelte:
--------------------------------------------------------------------------------
1 |
37 |
38 |
41 |
42 |
58 |
59 |
(mobileMenuOpen = !mobileMenuOpen)}
64 | >
65 |
66 | Toggle Menu
67 |
68 |
69 |
70 |
71 |
72 |
73 | {#if isDarkMode}
74 |
75 | {:else}
76 |
77 | {/if}
78 | Toggle theme
79 |
80 |
86 | {@html GitHub}
87 |
88 |
89 |
90 |
91 |
92 | {#if mobileMenuOpen}
93 |
106 | {/if}
107 |
108 |
--------------------------------------------------------------------------------
/src/lib/components/layers/LayerWebGL.svelte:
--------------------------------------------------------------------------------
1 |
122 |
123 | {#if children && vectorSource}
124 | {@render children()}
125 | {/if}
126 |
--------------------------------------------------------------------------------
/src/lib/components/layers/LayerVector.svelte:
--------------------------------------------------------------------------------
1 |
128 |
129 | {#if children && vectorSource}
130 | {@render children()}
131 | {/if}
132 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svelte-openlayers",
3 | "version": "0.0.1",
4 | "private": false,
5 | "workspaces": [
6 | "site"
7 | ],
8 | "scripts": {
9 | "dev": "vite dev",
10 | "dev:site": "cd site && bun run dev",
11 | "build": "svelte-kit sync && svelte-package",
12 | "build:watch": "svelte-kit sync && svelte-package --watch",
13 | "preview": "vite preview",
14 | "prepare": "svelte-kit sync || echo ''",
15 | "prepack": "npm run build",
16 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
17 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
18 | "format": "prettier --write .",
19 | "lint": "prettier --check . && eslint .",
20 | "test:unit": "vitest",
21 | "test": "npm run test:unit -- --run"
22 | },
23 | "files": [
24 | "dist",
25 | "!dist/**/*.test.*",
26 | "!dist/**/*.spec.*"
27 | ],
28 | "sideEffects": [
29 | "**/*.css"
30 | ],
31 | "svelte": "./dist/index.js",
32 | "types": "./dist/index.d.ts",
33 | "type": "module",
34 | "exports": {
35 | ".": {
36 | "types": "./dist/index.d.ts",
37 | "svelte": "./dist/index.js",
38 | "default": "./dist/index.js"
39 | },
40 | "./utils": {
41 | "types": "./dist/utils/index.d.ts",
42 | "default": "./dist/utils/index.js"
43 | },
44 | "./map": {
45 | "types": "./dist/components/map/index.d.ts",
46 | "svelte": "./dist/components/map/index.js",
47 | "default": "./dist/components/map/index.js"
48 | },
49 | "./layers": {
50 | "types": "./dist/components/layers/index.d.ts",
51 | "svelte": "./dist/components/layers/index.js",
52 | "default": "./dist/components/layers/index.js"
53 | },
54 | "./features": {
55 | "types": "./dist/components/features/index.d.ts",
56 | "svelte": "./dist/components/features/index.js",
57 | "default": "./dist/components/features/index.js"
58 | },
59 | "./interactions": {
60 | "types": "./dist/components/interactions/index.d.ts",
61 | "svelte": "./dist/components/interactions/index.js",
62 | "default": "./dist/components/interactions/index.js"
63 | },
64 | "./overlays": {
65 | "types": "./dist/components/overlays/index.d.ts",
66 | "svelte": "./dist/components/overlays/index.js",
67 | "default": "./dist/components/overlays/index.js"
68 | },
69 | "./styles.css": "./dist/styles/index.css",
70 | "./styles/variables.css": "./dist/styles/variables.css",
71 | "./styles/variables-only.css": "./dist/styles/variables-only.css",
72 | "./styles/components.css": "./dist/styles/components.css"
73 | },
74 | "peerDependencies": {
75 | "svelte": "^5.0.0"
76 | },
77 | "devDependencies": {
78 | "@eslint/compat": "^1.2.5",
79 | "@eslint/js": "^9.18.0",
80 | "@sveltejs/adapter-auto": "^6.0.0",
81 | "@sveltejs/kit": "^2.16.0",
82 | "@sveltejs/package": "^2.0.0",
83 | "@sveltejs/vite-plugin-svelte": "^5.0.0",
84 | "@testing-library/jest-dom": "^6.6.3",
85 | "@testing-library/svelte": "^5.2.4",
86 | "eslint": "^9.18.0",
87 | "eslint-config-prettier": "^10.0.1",
88 | "eslint-plugin-svelte": "^3.0.0",
89 | "globals": "^16.0.0",
90 | "jsdom": "^26.0.0",
91 | "prettier": "^3.4.2",
92 | "prettier-plugin-svelte": "^3.3.3",
93 | "publint": "^0.3.2",
94 | "rollup-plugin-analyzer": "^4.0.0",
95 | "semantic-release": "^24.2.7",
96 | "svelte": "^5.0.0",
97 | "svelte-check": "^4.0.0",
98 | "typescript": "^5.0.0",
99 | "typescript-eslint": "^8.20.0",
100 | "vite": "^6.2.6",
101 | "vitest": "^3.0.0"
102 | },
103 | "license": "MIT",
104 | "homepage": "https://svelte-openlayers.com",
105 | "repository": {
106 | "type": "git",
107 | "url": "https://github.com/oMaN-Rod/svelte-openlayers.git"
108 | },
109 | "keywords": [
110 | "svelte",
111 | "openlayers"
112 | ],
113 | "dependencies": {
114 | "ol": "^10.6.1"
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/site/src/lib/examples/layers-demo.svelte:
--------------------------------------------------------------------------------
1 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | {#if activeBaseLayer === 'osm'}
87 |
88 | {:else}
89 | s.id === activeBaseLayer)?.url}
92 | attributions={mapSources.find((s) => s.id === activeBaseLayer)?.attributions}
93 | bind:layer={tileLayer}
94 | zIndex={0}
95 | />
96 | {/if}
97 |
98 |
99 | {#if layersVisible.airports}
100 |
101 | {#each sampleData.airports as airport}
102 |
103 | {/each}
104 |
105 | {/if}
106 |
107 |
108 | {#if layersVisible.railways}
109 |
110 | {#each sampleData.railways as railway}
111 |
112 | {/each}
113 |
114 | {/if}
115 |
116 |
117 | {#if layersVisible.regions}
118 |
119 | {#each sampleData.regions as region}
120 |
121 | {/each}
122 |
123 | {/if}
124 |
125 |
126 |
127 | Active layers: {Object.entries(layersVisible)
128 | .filter(([_, visible]) => visible)
129 | .map(([name]) => name)
130 | .join(', ')}
131 |
132 |
--------------------------------------------------------------------------------
/site/src/routes/docs/components/+page.md:
--------------------------------------------------------------------------------
1 | # Components Overview
2 |
3 | Svelte OpenLayers provides a comprehensive set of components for building interactive maps. All components follow consistent patterns and are fully composable.
4 |
5 | ## Core Components {.toc}
6 |
7 | ### Map Components {.toc}
8 |
9 | The foundation of every map application:
10 |
11 | - **Map.Root** - Main map container and context provider
12 | - **Map.View** - Controls viewport (center, zoom, rotation)
13 | - **Map.Controls** - Built-in map controls (zoom, scale, etc.)
14 |
15 | ### Layer Components {.toc}
16 |
17 | Display different types of data on your map:
18 |
19 | - **Layer.Tile** - Raster tile layers (OSM, XYZ, WMS)
20 | - **Layer.Vector** - Vector data layers with Canvas rendering
21 | - **Layer.WebGL** - High-performance WebGL vector layers for large datasets
22 | - **Layer.VectorTile** - Vector tile layers for performance
23 | - **Layer.Image** - Static image layers
24 |
25 | ### Feature Components {.toc}
26 |
27 | Vector geometries that can be displayed and interacted with:
28 |
29 | - **Feature.Point** - Point locations
30 | - **Feature.LineString** - Lines and paths
31 | - **Feature.Polygon** - Areas and boundaries
32 | - **Feature.Circle** - Circular areas
33 | - **Feature.MultiPoint** - Multiple points as one feature
34 |
35 | ### Interaction Components {.toc}
36 |
37 | Handle user interactions with the map:
38 |
39 | - **Interaction.Select** - Feature selection
40 | - **Interaction.Draw** - Drawing new features
41 | - **Interaction.Modify** - Editing existing features
42 | - **Interaction.Translate** - Moving features
43 |
44 | ### Overlay Components {.toc}
45 |
46 | Display HTML content positioned on the map:
47 |
48 | - **Overlay.Popup** - Information popups
49 | - **Overlay.Tooltip** - Hover tooltips
50 |
51 | ## Component Patterns {.toc}
52 |
53 | ### Composable Structure {.toc}
54 |
55 | All components follow a consistent, nestable structure:
56 |
57 | ```svelte
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | ```
69 |
70 | ### Reactive Properties {.toc}
71 |
72 | Component properties are reactive and bindable:
73 |
74 | ```svelte
75 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | ```
86 |
87 | ### Event Handling {.toc}
88 |
89 | Components emit standard events with OpenLayers data:
90 |
91 | ```svelte
92 | console.log('Selected:', event.detail.features)} />
93 | ```
94 |
95 | ### Styling {.toc}
96 |
97 | Multiple ways to style map features:
98 |
99 | ```svelte
100 |
101 |
102 |
103 |
104 | getStyleForFeature(feature)} />
105 |
106 |
107 |
108 | ```
109 |
110 | ## TypeScript Support {.toc}
111 |
112 | All components are fully typed with TypeScript:
113 |
114 | ```typescript
115 | import type { MapRootProps, FeaturePointProps } from 'svelte-openlayers';
116 |
117 | // Props are properly typed
118 | const mapProps: MapRootProps = {
119 | center: [0, 0],
120 | zoom: 2,
121 | projection: 'EPSG:4326'
122 | };
123 | ```
124 |
--------------------------------------------------------------------------------
/site/src/lib/components/table-of-contents.svelte:
--------------------------------------------------------------------------------
1 |
97 |
98 | {#if tocItems.length > 0}
99 |
100 |
101 |
On This Page
102 |
103 | {#each tocItems as item}
104 |
105 | scrollToSection(item.id)}
107 | class="hover:bg-muted/50 block w-full rounded-md px-3 py-1.5 text-left transition-colors {activeId ===
108 | item.id
109 | ? 'text-primary bg-muted/50 font-medium'
110 | : 'text-muted-foreground hover:text-foreground'}"
111 | >
112 | {#if activeId === item.id}
113 |
116 | {/if}
117 | {item.text}
118 |
119 |
120 | {/each}
121 |
122 |
123 |
124 | {/if}
125 |
126 |
145 |
--------------------------------------------------------------------------------
/site/src/lib/examples/tooltips-html-demo.svelte:
--------------------------------------------------------------------------------
1 |
98 |
99 |
100 |
101 |
102 | s.id === 'carto-voyager')?.url}
105 | attributions={mapSources.find((s) => s.id === 'carto-voyager')?.attributions}
106 | />
107 |
108 |
109 | {#each landmarks as landmark}
110 | {@const { id, coordinates, ...rest } = landmark}
111 |
112 | {/each}
113 |
114 |
115 | {
120 | const props = feature.getProperties();
121 | return hoverContent(props.name, props.type || 'Feature');
122 | }}
123 | selectContent={(feature) => selectContent(feature)}
124 | hoverClass="!bg-white"
125 | selectClass="!bg-white"
126 | />
127 |
128 |
129 |
130 | {#if tooltipMode === 'hover'}
131 | Hover over landmarks to see their information
132 | {:else}
133 | Click on landmarks to toggle their information
134 | {/if}
135 |
136 |
--------------------------------------------------------------------------------
/site/src/lib/examples/features-demo.svelte:
--------------------------------------------------------------------------------
1 |
75 |
76 |
77 |
78 |
79 | s.id === 'carto-voyager')?.url}
82 | attributions={mapSources.find((s) => s.id === 'carto-voyager')?.attributions}
83 | />
84 |
85 |
86 |
87 |
96 |
97 |
106 |
107 |
108 |
109 | {#each locations as location}
110 |
120 | {/each}
121 |
122 |
129 | {#snippet hoverSnippet(feature)}
130 | {@const props = feature.getProperties()}
131 |
132 | {/snippet}
133 | {#snippet selectSnippet(feature)}
134 | {@const props = feature.getProperties()}
135 |
136 | {/snippet}
137 |
138 |
139 |
140 | {#if selectedFeature}
141 |
142 | Selected:
143 | {selectedFeature}
144 |
145 | {/if}
146 |
--------------------------------------------------------------------------------
/site/src/app.css:
--------------------------------------------------------------------------------
1 | @import 'tailwindcss';
2 |
3 | @import 'tw-animate-css';
4 |
5 | @custom-variant dark (&:is(.dark *));
6 |
7 | :root {
8 | --radius: 0.625rem;
9 | --background: oklch(1 0 0);
10 | --foreground: oklch(0.145 0 0);
11 | --card: oklch(1 0 0);
12 | --card-foreground: oklch(0.145 0 0);
13 | --popover: oklch(1 0 0);
14 | --popover-foreground: oklch(0.145 0 0);
15 | --primary: oklch(0.205 0 0);
16 | --primary-foreground: oklch(0.985 0 0);
17 | --secondary: oklch(0.97 0 0);
18 | --secondary-foreground: oklch(0.205 0 0);
19 | --muted: oklch(0.97 0 0);
20 | --muted-foreground: oklch(0.556 0 0);
21 | --accent: oklch(0.97 0 0);
22 | --accent-foreground: oklch(0.205 0 0);
23 | --destructive: oklch(0.577 0.245 27.325);
24 | --border: oklch(0.922 0 0);
25 | --input: oklch(0.922 0 0);
26 | --ring: oklch(0.708 0 0);
27 | --chart-1: oklch(0.646 0.222 41.116);
28 | --chart-2: oklch(0.6 0.118 184.704);
29 | --chart-3: oklch(0.398 0.07 227.392);
30 | --chart-4: oklch(0.828 0.189 84.429);
31 | --chart-5: oklch(0.769 0.188 70.08);
32 | --sidebar: oklch(0.985 0 0);
33 | --sidebar-foreground: oklch(0.145 0 0);
34 | --sidebar-primary: oklch(0.205 0 0);
35 | --sidebar-primary-foreground: oklch(0.985 0 0);
36 | --sidebar-accent: oklch(0.97 0 0);
37 | --sidebar-accent-foreground: oklch(0.205 0 0);
38 | --sidebar-border: oklch(0.922 0 0);
39 | --sidebar-ring: oklch(0.708 0 0);
40 | }
41 |
42 | .dark {
43 | --background: oklch(0.145 0 0);
44 | --foreground: oklch(0.985 0 0);
45 | --card: oklch(0.205 0 0);
46 | --card-foreground: oklch(0.985 0 0);
47 | --popover: oklch(0.205 0 0);
48 | --popover-foreground: oklch(0.985 0 0);
49 | --primary: oklch(0.922 0 0);
50 | --primary-foreground: oklch(0.205 0 0);
51 | --secondary: oklch(0.269 0 0);
52 | --secondary-foreground: oklch(0.985 0 0);
53 | --muted: oklch(0.269 0 0);
54 | --muted-foreground: oklch(0.708 0 0);
55 | --accent: oklch(0.269 0 0);
56 | --accent-foreground: oklch(0.985 0 0);
57 | --destructive: oklch(0.704 0.191 22.216);
58 | --border: oklch(1 0 0 / 10%);
59 | --input: oklch(1 0 0 / 15%);
60 | --ring: oklch(0.556 0 0);
61 | --chart-1: oklch(0.488 0.243 264.376);
62 | --chart-2: oklch(0.696 0.17 162.48);
63 | --chart-3: oklch(0.769 0.188 70.08);
64 | --chart-4: oklch(0.627 0.265 303.9);
65 | --chart-5: oklch(0.645 0.246 16.439);
66 | --sidebar: oklch(0.205 0 0);
67 | --sidebar-foreground: oklch(0.985 0 0);
68 | --sidebar-primary: oklch(0.488 0.243 264.376);
69 | --sidebar-primary-foreground: oklch(0.985 0 0);
70 | --sidebar-accent: oklch(0.269 0 0);
71 | --sidebar-accent-foreground: oklch(0.985 0 0);
72 | --sidebar-border: oklch(1 0 0 / 10%);
73 | --sidebar-ring: oklch(0.556 0 0);
74 | }
75 |
76 | @theme inline {
77 | --radius-sm: calc(var(--radius) - 4px);
78 | --radius-md: calc(var(--radius) - 2px);
79 | --radius-lg: var(--radius);
80 | --radius-xl: calc(var(--radius) + 4px);
81 | --color-background: var(--background);
82 | --color-foreground: var(--foreground);
83 | --color-card: var(--card);
84 | --color-card-foreground: var(--card-foreground);
85 | --color-popover: var(--popover);
86 | --color-popover-foreground: var(--popover-foreground);
87 | --color-primary: var(--primary);
88 | --color-primary-foreground: var(--primary-foreground);
89 | --color-secondary: var(--secondary);
90 | --color-secondary-foreground: var(--secondary-foreground);
91 | --color-muted: var(--muted);
92 | --color-muted-foreground: var(--muted-foreground);
93 | --color-accent: var(--accent);
94 | --color-accent-foreground: var(--accent-foreground);
95 | --color-destructive: var(--destructive);
96 | --color-border: var(--border);
97 | --color-input: var(--input);
98 | --color-ring: var(--ring);
99 | --color-chart-1: var(--chart-1);
100 | --color-chart-2: var(--chart-2);
101 | --color-chart-3: var(--chart-3);
102 | --color-chart-4: var(--chart-4);
103 | --color-chart-5: var(--chart-5);
104 | --color-sidebar: var(--sidebar);
105 | --color-sidebar-foreground: var(--sidebar-foreground);
106 | --color-sidebar-primary: var(--sidebar-primary);
107 | --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
108 | --color-sidebar-accent: var(--sidebar-accent);
109 | --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
110 | --color-sidebar-border: var(--sidebar-border);
111 | --color-sidebar-ring: var(--sidebar-ring);
112 | }
113 |
114 | @layer base {
115 | * {
116 | @apply border-border outline-ring/50;
117 | }
118 | body {
119 | @apply bg-background text-foreground;
120 | }
121 | }
122 |
123 | @layer components {
124 | .container {
125 | @apply mx-auto w-full max-w-screen-xl px-4 sm:px-6 lg:px-8;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------