├── site ├── static │ ├── .nojekyll │ └── favicon.png ├── .npmrc ├── src │ ├── routes │ │ ├── +layout.ts │ │ ├── examples │ │ │ ├── styling-customization │ │ │ │ └── +page.svelte │ │ │ ├── basic-map │ │ │ │ └── +page.svelte │ │ │ ├── static-layer │ │ │ │ └── +page.svelte │ │ │ ├── custom-source-geojson │ │ │ │ └── +page.svelte │ │ │ ├── bidirectional-selection │ │ │ │ └── +page.svelte │ │ │ ├── features │ │ │ │ └── +page.svelte │ │ │ ├── +layout.svelte │ │ │ ├── webgl-points │ │ │ │ └── +page.svelte │ │ │ ├── layers │ │ │ │ └── +page.svelte │ │ │ ├── +page.svelte │ │ │ └── tooltips │ │ │ │ └── +page.svelte │ │ ├── +layout.svelte │ │ └── docs │ │ │ ├── getting-started │ │ │ └── +page.md │ │ │ ├── architecture │ │ │ └── +page.md │ │ │ ├── +page.svelte │ │ │ └── components │ │ │ └── +page.md │ ├── lib │ │ ├── components │ │ │ ├── icons │ │ │ │ ├── map-pin.png │ │ │ │ ├── map-pin-minus-inside.svg │ │ │ │ ├── map-pin.svg │ │ │ │ ├── map-pin-check-inside.svg │ │ │ │ ├── map-pin-plus-inside.svg │ │ │ │ ├── map-pin-x-inside.svg │ │ │ │ ├── house.svg │ │ │ │ ├── map-pin-minus.svg │ │ │ │ ├── map-pin-check.svg │ │ │ │ ├── map-pin-plus.svg │ │ │ │ ├── map-pin-x.svg │ │ │ │ ├── map-pin-pen.svg │ │ │ │ ├── hospital.svg │ │ │ │ ├── map-pin-house.svg │ │ │ │ ├── map-pin-off.svg │ │ │ │ ├── school.svg │ │ │ │ ├── building.svg │ │ │ │ ├── github.svg │ │ │ │ └── svelte-logo.svg │ │ │ ├── ui │ │ │ │ ├── input │ │ │ │ │ ├── index.ts │ │ │ │ │ └── input.svelte │ │ │ │ ├── switch │ │ │ │ │ ├── index.ts │ │ │ │ │ └── switch.svelte │ │ │ │ ├── separator │ │ │ │ │ ├── index.ts │ │ │ │ │ └── separator.svelte │ │ │ │ ├── badge │ │ │ │ │ ├── index.ts │ │ │ │ │ └── badge.svelte │ │ │ │ ├── scroll-area │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── scroll-area-scrollbar.svelte │ │ │ │ │ └── scroll-area.svelte │ │ │ │ ├── select │ │ │ │ │ ├── select-group.svelte │ │ │ │ │ ├── select-separator.svelte │ │ │ │ │ ├── select-label.svelte │ │ │ │ │ ├── select-group-heading.svelte │ │ │ │ │ ├── select-scroll-up-button.svelte │ │ │ │ │ ├── select-scroll-down-button.svelte │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── select-item.svelte │ │ │ │ │ ├── select-trigger.svelte │ │ │ │ │ └── select-content.svelte │ │ │ │ ├── sheet │ │ │ │ │ ├── sheet-close.svelte │ │ │ │ │ ├── sheet-trigger.svelte │ │ │ │ │ ├── sheet-title.svelte │ │ │ │ │ ├── sheet-description.svelte │ │ │ │ │ ├── sheet-header.svelte │ │ │ │ │ ├── sheet-footer.svelte │ │ │ │ │ ├── sheet-overlay.svelte │ │ │ │ │ ├── index.ts │ │ │ │ │ └── sheet-content.svelte │ │ │ │ ├── tooltip │ │ │ │ │ ├── tooltip-trigger.svelte │ │ │ │ │ ├── index.ts │ │ │ │ │ └── tooltip-content.svelte │ │ │ │ ├── dropdown-menu │ │ │ │ │ ├── dropdown-menu-group.svelte │ │ │ │ │ ├── dropdown-menu-trigger.svelte │ │ │ │ │ ├── dropdown-menu-radio-group.svelte │ │ │ │ │ ├── dropdown-menu-separator.svelte │ │ │ │ │ ├── dropdown-menu-shortcut.svelte │ │ │ │ │ ├── dropdown-menu-label.svelte │ │ │ │ │ ├── dropdown-menu-group-heading.svelte │ │ │ │ │ ├── dropdown-menu-sub-content.svelte │ │ │ │ │ ├── dropdown-menu-sub-trigger.svelte │ │ │ │ │ ├── dropdown-menu-radio-item.svelte │ │ │ │ │ ├── dropdown-menu-content.svelte │ │ │ │ │ ├── dropdown-menu-item.svelte │ │ │ │ │ ├── dropdown-menu-checkbox-item.svelte │ │ │ │ │ └── index.ts │ │ │ │ ├── button │ │ │ │ │ ├── index.ts │ │ │ │ │ └── button.svelte │ │ │ │ ├── tabs │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── tabs-content.svelte │ │ │ │ │ ├── tabs.svelte │ │ │ │ │ ├── tabs-list.svelte │ │ │ │ │ └── tabs-trigger.svelte │ │ │ │ ├── popover │ │ │ │ │ ├── popover-trigger.svelte │ │ │ │ │ ├── index.ts │ │ │ │ │ └── popover-content.svelte │ │ │ │ └── card │ │ │ │ │ ├── card-content.svelte │ │ │ │ │ ├── card-title.svelte │ │ │ │ │ ├── card-description.svelte │ │ │ │ │ ├── card-footer.svelte │ │ │ │ │ ├── card-action.svelte │ │ │ │ │ ├── card.svelte │ │ │ │ │ ├── index.ts │ │ │ │ │ └── card-header.svelte │ │ │ ├── docs │ │ │ │ ├── index.ts │ │ │ │ ├── map-example.svelte │ │ │ │ ├── api-table.svelte │ │ │ │ └── example-preview.svelte │ │ │ ├── docs-nav.svelte │ │ │ ├── examples-nav.svelte │ │ │ ├── site-header.svelte │ │ │ └── table-of-contents.svelte │ │ ├── index.ts │ │ ├── utils.ts │ │ └── examples │ │ │ ├── basic-map-demo.svelte │ │ │ ├── custom-source-geojson-demo.svelte │ │ │ ├── static-layer-demo.svelte │ │ │ ├── tooltip-hover.svelte │ │ │ ├── styling-basic-demo.svelte │ │ │ ├── tooltip-select.svelte │ │ │ ├── tooltips-basic-demo.svelte │ │ │ ├── tooltips-component-demo.svelte │ │ │ ├── layers-demo.svelte │ │ │ ├── tooltips-html-demo.svelte │ │ │ └── features-demo.svelte │ ├── app.d.ts │ ├── app.html │ └── app.css ├── bun.lockb ├── .prettierignore ├── eslint.config.js ├── vite.config.ts ├── .gitignore ├── .prettierrc ├── components.json ├── tsconfig.json ├── README.md ├── svelte.config.js ├── remark-toc-headings.js └── package.json ├── .npmrc ├── static └── favicon.png ├── .prettierignore ├── src ├── demo.spec.ts ├── routes │ └── +page.svelte ├── lib │ ├── components │ │ ├── map │ │ │ ├── index.ts │ │ │ └── MapView.svelte │ │ ├── overlays │ │ │ ├── index.ts │ │ │ └── OverlayTooltip.svelte │ │ ├── interactions │ │ │ ├── index.ts │ │ │ ├── InteractionHover.svelte │ │ │ └── InteractionSelect.svelte │ │ ├── features │ │ │ ├── index.ts │ │ │ ├── FeaturePoint.svelte │ │ │ ├── FeatureLineString.svelte │ │ │ └── FeaturePolygon.svelte │ │ └── layers │ │ │ ├── index.ts │ │ │ ├── LayerStatic.svelte │ │ │ ├── LayerTile.svelte │ │ │ ├── LayerWebGL.svelte │ │ │ └── LayerVector.svelte │ ├── styles │ │ ├── variables-only.css │ │ └── index.css │ ├── utils │ │ ├── index.ts │ │ ├── context.ts │ │ └── collections.ts │ └── index.ts ├── app.d.ts └── app.html ├── .prettierrc ├── .gitignore ├── tsconfig.json ├── vitest-setup-client.ts ├── svelte.config.js ├── vite.config.ts ├── .github └── workflows │ ├── release.yml │ └── deploy.yml ├── eslint.config.js ├── README.md └── package.json /site/static/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /site/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /site/src/routes/+layout.ts: -------------------------------------------------------------------------------- 1 | export const prerender = true; 2 | -------------------------------------------------------------------------------- /site/bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oMaN-Rod/svelte-openlayers/HEAD/site/bun.lockb -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oMaN-Rod/svelte-openlayers/HEAD/static/favicon.png -------------------------------------------------------------------------------- /site/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oMaN-Rod/svelte-openlayers/HEAD/site/static/favicon.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Package Managers 2 | package-lock.json 3 | pnpm-lock.yaml 4 | yarn.lock 5 | bun.lock 6 | bun.lockb 7 | -------------------------------------------------------------------------------- /site/.prettierignore: -------------------------------------------------------------------------------- 1 | # Package Managers 2 | package-lock.json 3 | pnpm-lock.yaml 4 | yarn.lock 5 | bun.lock 6 | bun.lockb 7 | -------------------------------------------------------------------------------- /site/src/lib/components/icons/map-pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oMaN-Rod/svelte-openlayers/HEAD/site/src/lib/components/icons/map-pin.png -------------------------------------------------------------------------------- /site/src/lib/components/ui/input/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./input.svelte"; 2 | 3 | export { 4 | Root, 5 | // 6 | Root as Input, 7 | }; 8 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/switch/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './switch.svelte'; 2 | 3 | export { 4 | Root, 5 | // 6 | Root as Switch 7 | }; 8 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/separator/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./separator.svelte"; 2 | 3 | export { 4 | Root, 5 | // 6 | Root as Separator, 7 | }; 8 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/badge/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Badge } from './badge.svelte'; 2 | export { badgeVariants, type BadgeVariant } from './badge.svelte'; 3 | -------------------------------------------------------------------------------- /site/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | // Re-export svelte-openlayers components for convenience 2 | export { Map, Layer, Feature, Interaction, Overlay } from 'svelte-openlayers'; 3 | -------------------------------------------------------------------------------- /site/eslint.config.js: -------------------------------------------------------------------------------- 1 | import prettier from 'eslint-config-prettier'; 2 | import svelte from 'eslint-plugin-svelte'; 3 | 4 | export default [prettier, ...svelte.configs.prettier]; 5 | -------------------------------------------------------------------------------- /src/demo.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | describe('sum test', () => { 4 | it('adds 1 + 2 to equal 3', () => { 5 | expect(1 + 2).toBe(3); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Svelte OpenLayers

5 |

A composable, component-based OpenLayers integration for Svelte 5

6 |
7 |
8 |
9 | -------------------------------------------------------------------------------- /src/lib/components/map/index.ts: -------------------------------------------------------------------------------- 1 | import MapRoot from './MapRoot.svelte'; 2 | import MapView from './MapView.svelte'; 3 | 4 | export const Map = { 5 | Root: MapRoot, 6 | View: MapView 7 | }; 8 | 9 | export { MapRoot, MapView }; -------------------------------------------------------------------------------- /site/src/lib/components/docs/index.ts: -------------------------------------------------------------------------------- 1 | export { default as CodeBlock } from './code-block.svelte'; 2 | export { default as ExamplePreview } from './example-preview.svelte'; 3 | export { default as ApiTable } from './api-table.svelte'; 4 | -------------------------------------------------------------------------------- /site/vite.config.ts: -------------------------------------------------------------------------------- 1 | import tailwindcss from '@tailwindcss/vite'; 2 | import { sveltekit } from '@sveltejs/kit/vite'; 3 | import { defineConfig } from 'vite'; 4 | 5 | export default defineConfig({ 6 | plugins: [tailwindcss(), sveltekit()] 7 | }); 8 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/scroll-area/index.ts: -------------------------------------------------------------------------------- 1 | import Scrollbar from './scroll-area-scrollbar.svelte'; 2 | import Root from './scroll-area.svelte'; 3 | 4 | export { 5 | Root, 6 | Scrollbar, 7 | //, 8 | Root as ScrollArea, 9 | Scrollbar as ScrollAreaScrollbar 10 | }; 11 | -------------------------------------------------------------------------------- /src/lib/components/overlays/index.ts: -------------------------------------------------------------------------------- 1 | import OverlayTooltip from './OverlayTooltip.svelte'; 2 | import TooltipManager from './TooltipManager.svelte'; 3 | 4 | export const Overlay = { 5 | Tooltip: OverlayTooltip, 6 | TooltipManager 7 | }; 8 | 9 | export { OverlayTooltip, TooltipManager }; -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "overrides": [ 8 | { 9 | "files": "*.svelte", 10 | "options": { 11 | "parser": "svelte" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/select/select-group.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/sheet/sheet-close.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/sheet/sheet-trigger.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/lib/components/interactions/index.ts: -------------------------------------------------------------------------------- 1 | import InteractionSelect from './InteractionSelect.svelte'; 2 | import InteractionHover from './InteractionHover.svelte'; 3 | 4 | export const Interaction = { 5 | Select: InteractionSelect, 6 | Hover: InteractionHover 7 | }; 8 | 9 | export { InteractionSelect, InteractionHover }; -------------------------------------------------------------------------------- /site/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Output 4 | .output 5 | .vercel 6 | .netlify 7 | .wrangler 8 | /.svelte-kit 9 | /build 10 | 11 | # OS 12 | .DS_Store 13 | Thumbs.db 14 | 15 | # Env 16 | .env 17 | .env.* 18 | !.env.example 19 | !.env.test 20 | 21 | # Vite 22 | vite.config.js.timestamp-* 23 | vite.config.ts.timestamp-* 24 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/tooltip/tooltip-trigger.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Output 4 | .output 5 | .vercel 6 | .netlify 7 | .wrangler 8 | /.svelte-kit 9 | /build 10 | /dist 11 | 12 | # OS 13 | .DS_Store 14 | Thumbs.db 15 | 16 | # Env 17 | .env 18 | .env.* 19 | !.env.example 20 | !.env.test 21 | 22 | # Vite 23 | vite.config.js.timestamp-* 24 | vite.config.ts.timestamp-* 25 | -------------------------------------------------------------------------------- /site/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], 7 | "overrides": [ 8 | { 9 | "files": "*.svelte", 10 | "options": { 11 | "parser": "svelte" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://svelte.dev/docs/kit/types#app.d.ts 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /site/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://svelte.dev/docs/kit/types#app.d.ts 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/button/index.ts: -------------------------------------------------------------------------------- 1 | import Root, { 2 | type ButtonProps, 3 | type ButtonSize, 4 | type ButtonVariant, 5 | buttonVariants 6 | } from './button.svelte'; 7 | 8 | export { 9 | Root, 10 | type ButtonProps as Props, 11 | // 12 | Root as Button, 13 | buttonVariants, 14 | type ButtonProps, 15 | type ButtonSize, 16 | type ButtonVariant 17 | }; 18 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/tabs/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './tabs.svelte'; 2 | import Content from './tabs-content.svelte'; 3 | import List from './tabs-list.svelte'; 4 | import Trigger from './tabs-trigger.svelte'; 5 | 6 | export { 7 | Root, 8 | Content, 9 | List, 10 | Trigger, 11 | // 12 | Root as Tabs, 13 | Content as TabsContent, 14 | List as TabsList, 15 | Trigger as TabsTrigger 16 | }; 17 | -------------------------------------------------------------------------------- /src/lib/components/features/index.ts: -------------------------------------------------------------------------------- 1 | import FeaturePoint from './FeaturePoint.svelte'; 2 | import FeatureLineString from './FeatureLineString.svelte'; 3 | import FeaturePolygon from './FeaturePolygon.svelte'; 4 | 5 | export const Feature = { 6 | Point: FeaturePoint, 7 | LineString: FeatureLineString, 8 | Polygon: FeaturePolygon 9 | }; 10 | 11 | export { FeaturePoint, FeatureLineString, FeaturePolygon }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "module": "NodeNext", 13 | "moduleResolution": "NodeNext" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /site/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /site/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://shadcn-svelte.com/schema.json", 3 | "tailwind": { 4 | "css": "src/app.css", 5 | "baseColor": "neutral" 6 | }, 7 | "aliases": { 8 | "components": "$lib/components", 9 | "utils": "$lib/utils", 10 | "ui": "$lib/components/ui", 11 | "hooks": "$lib/hooks", 12 | "lib": "$lib" 13 | }, 14 | "typescript": true, 15 | "registry": "https://shadcn-svelte.com/registry" 16 | } 17 | -------------------------------------------------------------------------------- /site/src/lib/components/icons/map-pin-minus-inside.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/src/lib/components/icons/map-pin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /site/src/lib/components/icons/map-pin-check-inside.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/lib/components/layers/index.ts: -------------------------------------------------------------------------------- 1 | import LayerTile from './LayerTile.svelte'; 2 | import LayerVector from './LayerVector.svelte'; 3 | import LayerWebGL from './LayerWebGL.svelte'; 4 | import LayerStatic from './LayerStatic.svelte'; 5 | 6 | export const Layer = { 7 | Tile: LayerTile, 8 | Vector: LayerVector, 9 | WebGL: LayerWebGL, 10 | Static: LayerStatic, 11 | }; 12 | 13 | export { LayerTile, LayerVector, LayerWebGL,LayerStatic }; 14 | -------------------------------------------------------------------------------- /site/src/lib/components/icons/map-pin-plus-inside.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/src/lib/components/icons/map-pin-x-inside.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/src/lib/components/icons/house.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /site/src/lib/components/icons/map-pin-minus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 17 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/popover/popover-trigger.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 18 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/tabs/tabs-content.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 18 | -------------------------------------------------------------------------------- /site/src/lib/components/icons/map-pin-check.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/src/lib/components/icons/map-pin-plus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/src/lib/components/icons/map-pin-x.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/popover/index.ts: -------------------------------------------------------------------------------- 1 | import { Popover as PopoverPrimitive } from 'bits-ui'; 2 | import Content from './popover-content.svelte'; 3 | import Trigger from './popover-trigger.svelte'; 4 | const Root = PopoverPrimitive.Root; 5 | const Close = PopoverPrimitive.Close; 6 | 7 | export { 8 | Root, 9 | Content, 10 | Trigger, 11 | Close, 12 | // 13 | Root as Popover, 14 | Content as PopoverContent, 15 | Trigger as PopoverTrigger, 16 | Close as PopoverClose 17 | }; 18 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/sheet/sheet-title.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 18 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/tabs/tabs.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 20 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/sheet/sheet-description.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 18 | -------------------------------------------------------------------------------- /src/lib/styles/variables-only.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Svelte OpenLayers - Variables Only 3 | * 4 | * Import this file to include only the CSS variables without any component styles. 5 | * This is useful if you want to customize all component styles yourself while 6 | * maintaining access to the design system variables. 7 | * 8 | * @example 9 | * // Import variables only 10 | * import 'svelte-openlayers/styles/variables-only.css'; 11 | */ 12 | 13 | /* Core CSS Variables only */ 14 | @import './variables.css'; -------------------------------------------------------------------------------- /site/src/lib/components/icons/map-pin-pen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/card/card-content.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 | {@render children?.()} 15 |
16 | -------------------------------------------------------------------------------- /site/src/lib/components/icons/hospital.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /site/src/lib/components/icons/map-pin-house.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/tabs/tabs-list.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 18 | -------------------------------------------------------------------------------- /vitest-setup-client.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/vitest'; 2 | import { vi } from 'vitest'; 3 | 4 | // required for svelte5 + jsdom as jsdom does not support matchMedia 5 | Object.defineProperty(window, 'matchMedia', { 6 | writable: true, 7 | enumerable: true, 8 | value: vi.fn().mockImplementation((query) => ({ 9 | matches: false, 10 | media: query, 11 | onchange: null, 12 | addEventListener: vi.fn(), 13 | removeEventListener: vi.fn(), 14 | dispatchEvent: vi.fn() 15 | })) 16 | }); 17 | 18 | // add more mocks here if you need them 19 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/card/card-title.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
19 | {@render children?.()} 20 |
21 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/sheet/sheet-header.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
19 | {@render children?.()} 20 |
21 | -------------------------------------------------------------------------------- /site/src/lib/components/icons/map-pin-off.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/src/lib/components/icons/school.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/sheet/sheet-footer.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
19 | {@render children?.()} 20 |
21 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/card/card-description.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |

19 | {@render children?.()} 20 |

21 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/card/card-footer.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
19 | {@render children?.()} 20 |
21 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/select/select-separator.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/select/select-label.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
19 | {@render children?.()} 20 |
21 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/tooltip/index.ts: -------------------------------------------------------------------------------- 1 | import { Tooltip as TooltipPrimitive } from 'bits-ui'; 2 | import Trigger from './tooltip-trigger.svelte'; 3 | import Content from './tooltip-content.svelte'; 4 | 5 | const Root = TooltipPrimitive.Root; 6 | const Provider = TooltipPrimitive.Provider; 7 | const Portal = TooltipPrimitive.Portal; 8 | 9 | export { 10 | Root, 11 | Trigger, 12 | Content, 13 | Provider, 14 | Portal, 15 | // 16 | Root as Tooltip, 17 | Content as TooltipContent, 18 | Trigger as TooltipTrigger, 19 | Provider as TooltipProvider, 20 | Portal as TooltipPortal 21 | }; 22 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/card/card-action.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
19 | {@render children?.()} 20 |
21 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 19 | {@render children?.()} 20 | 21 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/card/card.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
22 | {@render children?.()} 23 |
24 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/sheet/sheet-overlay.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 21 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/separator/separator.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 21 | -------------------------------------------------------------------------------- /site/src/lib/components/icons/building.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/select/select-group-heading.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 20 | {@render children?.()} 21 | 22 | -------------------------------------------------------------------------------- /src/lib/styles/index.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Svelte OpenLayers - Main Stylesheet 3 | * 4 | * Import this file to include all OpenLayers component styles and CSS variables. 5 | * 6 | * @example 7 | * // In your main app file or component 8 | * import 'svelte-openlayers/styles'; 9 | * 10 | * @example 11 | * // Or import specific parts 12 | * import 'svelte-openlayers/styles/variables.css'; 13 | * import 'svelte-openlayers/styles/components.css'; 14 | */ 15 | 16 | /* OpenLayers default styles */ 17 | @import 'ol/ol.css'; 18 | 19 | /* Core CSS Variables */ 20 | @import './variables.css'; 21 | 22 | /* Component-specific styles and variables */ 23 | @import './components.css'; 24 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/card/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './card.svelte'; 2 | import Content from './card-content.svelte'; 3 | import Description from './card-description.svelte'; 4 | import Footer from './card-footer.svelte'; 5 | import Header from './card-header.svelte'; 6 | import Title from './card-title.svelte'; 7 | import Action from './card-action.svelte'; 8 | 9 | export { 10 | Root, 11 | Content, 12 | Description, 13 | Footer, 14 | Header, 15 | Title, 16 | Action, 17 | // 18 | Root as Card, 19 | Content as CardContent, 20 | Description as CardDescription, 21 | Footer as CardFooter, 22 | Header as CardHeader, 23 | Title as CardTitle, 24 | Action as CardAction 25 | }; 26 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 |
23 | {@render children?.()} 24 |
25 | -------------------------------------------------------------------------------- /site/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from 'clsx'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | 8 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 9 | export type WithoutChild = T extends { child?: any } ? Omit : T; 10 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 11 | export type WithoutChildren = T extends { children?: any } ? Omit : T; 12 | export type WithoutChildrenOrChild = WithoutChildren>; 13 | export type WithElementRef = T & { ref?: U | null }; 14 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/card/card-header.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
22 | {@render children?.()} 23 |
24 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | 23 | -------------------------------------------------------------------------------- /site/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "moduleResolution": "bundler" 13 | } 14 | // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias 15 | // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files 16 | // 17 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 18 | // from the referenced tsconfig.json - TypeScript does not merge them in 19 | } 20 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/select/select-scroll-up-button.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /site/src/routes/examples/styling-customization/+page.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | {meta?.title} - Example 11 | 12 | 13 | 14 |
15 |
16 |

{meta?.title}

17 |

{meta?.description}

18 |
19 | 20 | 21 |
22 | -------------------------------------------------------------------------------- /site/src/routes/examples/basic-map/+page.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | {meta?.title} - Example 11 | 12 | 13 | 14 |
15 |

Basic Map

16 |

17 | A simple map showing the most basic OpenLayers integration with Svelte. 18 |

19 | 20 | 21 |
22 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://svelte.dev/docs/kit/integrations 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. 12 | // If your environment is not supported, or you settled on a specific environment, switch out the adapter. 13 | // See https://svelte.dev/docs/kit/adapters for more information about adapters. 14 | adapter: adapter() 15 | } 16 | }; 17 | 18 | export default config; 19 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/select/select-scroll-down-button.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /site/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 |
21 | 22 |
23 | {@render children()} 24 |
25 |
26 | -------------------------------------------------------------------------------- /site/src/lib/examples/basic-map-demo.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 | Center: [{transformedCoordinates[0].toFixed(2)}, {transformedCoordinates[1].toFixed(2)}] 20 |
21 |
Zoom: {mapZoom.toFixed(1)}
22 |
23 | -------------------------------------------------------------------------------- /site/src/lib/components/icons/github.svg: -------------------------------------------------------------------------------- 1 | 3 | GitHub 4 | 5 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { svelteTesting } from '@testing-library/svelte/vite'; 2 | import { sveltekit } from '@sveltejs/kit/vite'; 3 | import { defineConfig } from 'vite'; 4 | 5 | export default defineConfig({ 6 | plugins: [sveltekit()], 7 | test: { 8 | workspace: [ 9 | { 10 | extends: './vite.config.ts', 11 | plugins: [svelteTesting()], 12 | test: { 13 | name: 'client', 14 | environment: 'jsdom', 15 | clearMocks: true, 16 | include: ['src/**/*.svelte.{test,spec}.{js,ts}'], 17 | exclude: ['src/lib/server/**'], 18 | setupFiles: ['./vitest-setup-client.ts'] 19 | } 20 | }, 21 | { 22 | extends: './vite.config.ts', 23 | test: { 24 | name: 'server', 25 | environment: 'node', 26 | include: ['src/**/*.{test,spec}.{js,ts}'], 27 | exclude: ['src/**/*.svelte.{test,spec}.{js,ts}'] 28 | } 29 | } 30 | ] 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /site/src/routes/examples/static-layer/+page.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | {meta?.title} - Example 11 | 12 | 13 | 14 |
15 |

Static Image Layer

16 |

17 | Render a single static image using a custom pixel-based projection and constrained extent. This 18 | is useful for annotated maps, diagrams, or historical imagery that doesn't use standard 19 | geographic coordinates. 20 |

21 | 22 | 23 |
24 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | release: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: write 16 | issues: write 17 | pull-requests: write 18 | id-token: write 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 23 | - uses: actions/setup-node@v4 24 | with: 25 | node-version: 20 26 | - name: Clean and install dependencies 27 | run: | 28 | rm -rf node_modules package-lock.json 29 | npm install --legacy-peer-deps 30 | - run: npm run build 31 | - name: Release 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 35 | run: npx semantic-release 36 | -------------------------------------------------------------------------------- /src/lib/utils/index.ts: -------------------------------------------------------------------------------- 1 | // Explicit exports for better tree-shaking instead of star exports 2 | 3 | // Style utilities 4 | export { 5 | createCircleStyle, 6 | createStrokeStyle, 7 | createFillStyle, 8 | createTextStyle, 9 | createIconStyle, 10 | createStyle, 11 | type CircleStyleOptions, 12 | type StrokeStyleOptions, 13 | type FillStyleOptions, 14 | type TextStyleOptions, 15 | type IconStyleOptions 16 | } from './styles.js'; 17 | 18 | // Context utilities 19 | export { setMapContext, getMapContext, setLayerContext, getLayerContext } from './context.js'; 20 | 21 | // CSS utilities 22 | export { 23 | getCSSVariable, 24 | setCSSVariable, 25 | getCSSVariables, 26 | getThemePrimaryColor, 27 | hasCSSVariable, 28 | getOpenLayersTheme 29 | } from './css.js'; 30 | 31 | // Collection utilities 32 | export { createReactiveCollection } from './collections.js'; 33 | export { ReactiveCollection, type ReactiveCollectionOptions } from './reactive-collection.js'; 34 | -------------------------------------------------------------------------------- /site/src/lib/examples/custom-source-geojson-demo.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | -------------------------------------------------------------------------------- /src/lib/utils/context.ts: -------------------------------------------------------------------------------- 1 | import { getContext, setContext } from 'svelte'; 2 | import { 3 | LAYER_CONTEXT_KEY, 4 | MAP_CONTEXT_KEY, 5 | type LayerContext, 6 | type MapContext 7 | } from '../types.js'; 8 | 9 | export function setMapContext(context: MapContext): void { 10 | setContext(MAP_CONTEXT_KEY, context); 11 | } 12 | 13 | export function getMapContext(): MapContext { 14 | const context = getContext(MAP_CONTEXT_KEY); 15 | if (!context) { 16 | throw new Error('Map context not found. Make sure component is used within Map.Root'); 17 | } 18 | return context; 19 | } 20 | 21 | export function setLayerContext(context: LayerContext): void { 22 | setContext(LAYER_CONTEXT_KEY, context); 23 | } 24 | 25 | export function getLayerContext(feature: string): LayerContext { 26 | const context = getContext(LAYER_CONTEXT_KEY); 27 | 28 | if (!context) { 29 | throw new Error(`Feature ${feature} must be used within LayerVector`); 30 | } 31 | 32 | return context; 33 | } 34 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 21 | -------------------------------------------------------------------------------- /site/src/lib/examples/static-layer-demo.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 |
17 | 18 | 19 | 24 | 25 |
26 |
27 |
28 | Center: [{mapCenter[0].toFixed(2)}, {mapCenter[1].toFixed(2)}] 29 |
30 |
Zoom: {mapZoom.toFixed(1)}
31 |
32 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 26 | {@render children?.()} 27 | 31 | 32 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/sheet/index.ts: -------------------------------------------------------------------------------- 1 | import { Dialog as SheetPrimitive } from 'bits-ui'; 2 | import Trigger from './sheet-trigger.svelte'; 3 | import Close from './sheet-close.svelte'; 4 | import Overlay from './sheet-overlay.svelte'; 5 | import Content from './sheet-content.svelte'; 6 | import Header from './sheet-header.svelte'; 7 | import Footer from './sheet-footer.svelte'; 8 | import Title from './sheet-title.svelte'; 9 | import Description from './sheet-description.svelte'; 10 | 11 | const Root = SheetPrimitive.Root; 12 | const Portal = SheetPrimitive.Portal; 13 | 14 | export { 15 | Root, 16 | Close, 17 | Trigger, 18 | Portal, 19 | Overlay, 20 | Content, 21 | Header, 22 | Footer, 23 | Title, 24 | Description, 25 | // 26 | Root as Sheet, 27 | Close as SheetClose, 28 | Trigger as SheetTrigger, 29 | Portal as SheetPortal, 30 | Overlay as SheetOverlay, 31 | Content as SheetContent, 32 | Header as SheetHeader, 33 | Footer as SheetFooter, 34 | Title as SheetTitle, 35 | Description as SheetDescription 36 | }; 37 | -------------------------------------------------------------------------------- /site/README.md: -------------------------------------------------------------------------------- 1 | # sv 2 | 3 | Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). 4 | 5 | ## Creating a project 6 | 7 | If you're seeing this, you've probably already done this step. Congrats! 8 | 9 | ```bash 10 | # create a new project in the current directory 11 | npx sv create 12 | 13 | # create a new project in my-app 14 | npx sv create my-app 15 | ``` 16 | 17 | ## Developing 18 | 19 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 20 | 21 | ```bash 22 | npm run dev 23 | 24 | # or start the server and open the app in a new browser tab 25 | npm run dev -- --open 26 | ``` 27 | 28 | ## Building 29 | 30 | To create a production version of your app: 31 | 32 | ```bash 33 | npm run build 34 | ``` 35 | 36 | You can preview the production build with `npm run preview`. 37 | 38 | > To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. 39 | -------------------------------------------------------------------------------- /site/src/lib/examples/tooltip-hover.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
📍
7 |
{name || 'Unknown'}
8 |
{type || 'Feature'}
9 |
10 | 11 | 41 | -------------------------------------------------------------------------------- /site/src/routes/examples/custom-source-geojson/+page.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | {meta?.title} - Example 12 | 13 | 14 | 15 |
16 |
17 |

Custom GeoJSON Source

18 |

19 | Learn how to load GeoJSON data from remote URLs or inline objects using a custom VectorSource 20 | with OpenLayers. 21 |

22 |
23 | 24 |
25 |

Basic Example

26 | 27 | 28 |
29 |
30 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import prettier from 'eslint-config-prettier'; 2 | import js from '@eslint/js'; 3 | import { includeIgnoreFile } from '@eslint/compat'; 4 | import svelte from 'eslint-plugin-svelte'; 5 | import globals from 'globals'; 6 | import { fileURLToPath } from 'node:url'; 7 | import ts from 'typescript-eslint'; 8 | import svelteConfig from './svelte.config.js'; 9 | 10 | const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); 11 | 12 | export default ts.config( 13 | includeIgnoreFile(gitignorePath), 14 | js.configs.recommended, 15 | ...ts.configs.recommended, 16 | ...svelte.configs.recommended, 17 | prettier, 18 | ...svelte.configs.prettier, 19 | { 20 | languageOptions: { 21 | globals: { ...globals.browser, ...globals.node } 22 | }, 23 | rules: { 'no-undef': 'off' } 24 | }, 25 | { 26 | files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'], 27 | languageOptions: { 28 | parserOptions: { 29 | projectService: true, 30 | extraFileExtensions: ['.svelte'], 31 | parser: ts.parser, 32 | svelteConfig 33 | } 34 | } 35 | } 36 | ); 37 | -------------------------------------------------------------------------------- /site/src/lib/examples/styling-basic-demo.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
15 | 16 | 17 | s.id === 'carto-voyager')?.url} 20 | attributions={mapSources.find((s) => s.id === 'carto-voyager')?.attributions} 21 | /> 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/tabs/tabs-trigger.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 21 | -------------------------------------------------------------------------------- /site/src/lib/components/ui/select/index.ts: -------------------------------------------------------------------------------- 1 | import { Select as SelectPrimitive } from "bits-ui"; 2 | 3 | import Group from "./select-group.svelte"; 4 | import Label from "./select-label.svelte"; 5 | import Item from "./select-item.svelte"; 6 | import Content from "./select-content.svelte"; 7 | import Trigger from "./select-trigger.svelte"; 8 | import Separator from "./select-separator.svelte"; 9 | import ScrollDownButton from "./select-scroll-down-button.svelte"; 10 | import ScrollUpButton from "./select-scroll-up-button.svelte"; 11 | import GroupHeading from "./select-group-heading.svelte"; 12 | 13 | const Root = SelectPrimitive.Root; 14 | 15 | export { 16 | Root, 17 | Group, 18 | Label, 19 | Item, 20 | Content, 21 | Trigger, 22 | Separator, 23 | ScrollDownButton, 24 | ScrollUpButton, 25 | GroupHeading, 26 | // 27 | Root as Select, 28 | Group as SelectGroup, 29 | Label as SelectLabel, 30 | Item as SelectItem, 31 | Content as SelectContent, 32 | Trigger as SelectTrigger, 33 | Separator as SelectSeparator, 34 | ScrollDownButton as SelectScrollDownButton, 35 | ScrollUpButton as SelectScrollUpButton, 36 | GroupHeading as SelectGroupHeading, 37 | }; 38 | -------------------------------------------------------------------------------- /src/lib/utils/collections.ts: -------------------------------------------------------------------------------- 1 | import type Collection from 'ol/Collection.js'; 2 | import type Feature from 'ol/Feature.js'; 3 | import { createSubscriber } from 'svelte/reactivity'; 4 | 5 | export function createReactiveCollection( 6 | collection: Collection, 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 | 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 | ![Svelte OpenLayers Demo](https://img.shields.io/badge/Svelte-5.0+-orange.svg) 6 | ![OpenLayers](https://img.shields.io/badge/OpenLayers-10.6+-blue.svg) 7 | ![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg) 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 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | {#each items as item} 35 | 36 | 42 | 45 | 52 | 53 | 54 | {/each} 55 | 56 |
PropertyTypeDefaultDescription
37 | {item.name} 38 | {#if item.required} 39 | Required 40 | {/if} 41 | 43 | {item.type} 44 | 46 | {#if item.default} 47 | {item.default} 48 | {:else} 49 | 50 | {/if} 51 | {item.description}
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 | 44 | 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 | 29 | 30 | 31 | {#if mobileNavOpen} 32 |
33 | 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 | 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 |
38 |

{category.title}

39 | 62 |
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 | 45 | {/each} 46 |
47 |
48 | 49 | 50 |
51 |

Data Layers

52 |
53 |
54 | 55 | 56 |
57 |
58 | 59 | 60 |
61 |
62 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 68 | 69 |
70 |
71 | 89 |
90 |
91 | 92 | {#if mobileMenuOpen} 93 |
94 | 105 |
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 | Content here 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 | 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 | --------------------------------------------------------------------------------