@deepfuture/dui-map 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +214 -0
- package/cluster-layer/index.d.ts +2 -0
- package/cluster-layer/index.js +2 -0
- package/cluster-layer/map-cluster-layer.d.ts +53 -0
- package/cluster-layer/map-cluster-layer.js +337 -0
- package/controls/index.d.ts +2 -0
- package/controls/index.js +2 -0
- package/controls/map-controls.d.ts +37 -0
- package/controls/map-controls.js +344 -0
- package/heatmap/index.d.ts +2 -0
- package/heatmap/index.js +2 -0
- package/heatmap/map-heatmap.d.ts +39 -0
- package/heatmap/map-heatmap.js +330 -0
- package/index.d.ts +10 -0
- package/index.js +9 -0
- package/map/index.d.ts +5 -0
- package/map/index.js +3 -0
- package/map/map-context.d.ts +8 -0
- package/map/map-context.js +2 -0
- package/map/map.d.ts +120 -0
- package/map/map.js +628 -0
- package/marker/index.d.ts +9 -0
- package/marker/index.js +7 -0
- package/marker/map-marker-content.d.ts +20 -0
- package/marker/map-marker-content.js +135 -0
- package/marker/map-marker-label.d.ts +21 -0
- package/marker/map-marker-label.js +126 -0
- package/marker/map-marker-popup.d.ts +25 -0
- package/marker/map-marker-popup.js +158 -0
- package/marker/map-marker-tooltip.d.ts +22 -0
- package/marker/map-marker-tooltip.js +159 -0
- package/marker/map-marker.d.ts +48 -0
- package/marker/map-marker.js +220 -0
- package/marker/marker-context.d.ts +8 -0
- package/marker/marker-context.js +2 -0
- package/package.json +70 -0
- package/popup/index.d.ts +2 -0
- package/popup/index.js +2 -0
- package/popup/map-popup.d.ts +29 -0
- package/popup/map-popup.js +184 -0
- package/region/index.d.ts +2 -0
- package/region/index.js +2 -0
- package/region/map-region.d.ts +41 -0
- package/region/map-region.js +276 -0
- package/route/index.d.ts +2 -0
- package/route/index.js +2 -0
- package/route/map-route.d.ts +33 -0
- package/route/map-route.js +245 -0
- package/theme/map-theme.d.ts +16 -0
- package/theme/map-theme.js +233 -0
package/README.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# DUI
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
[](https://www.npmjs.com/package/@deepfuture/dui-components)
|
|
5
|
+
|
|
6
|
+
Styled [Lit](https://lit.dev) web component library built on [dui-primitives](https://github.com/deepfuturenow/dui-primitives).
|
|
7
|
+
|
|
8
|
+
DUI extends unstyled, accessible primitive components with a complete design system — design tokens, variant systems, and aesthetic CSS. Import a component and it's ready to use. No setup, no configuration.
|
|
9
|
+
|
|
10
|
+
**[Live Docs & Demos →](https://deepfuturenow.github.io/dui/)**
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
**npm / pnpm / yarn:**
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @deepfuture/dui-components
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**CDN (zero setup):**
|
|
21
|
+
|
|
22
|
+
```html
|
|
23
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/@deepfuture/dui-cdn/dui.min.js"></script>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
Import the components you need — they self-register on import:
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import "@deepfuture/dui-components/button";
|
|
32
|
+
import "@deepfuture/dui-components/dialog";
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```html
|
|
36
|
+
<dui-dialog>
|
|
37
|
+
<dui-dialog-trigger>
|
|
38
|
+
<dui-button>Open Dialog</dui-button>
|
|
39
|
+
</dui-dialog-trigger>
|
|
40
|
+
<dui-dialog-popup>
|
|
41
|
+
<h2>Hello</h2>
|
|
42
|
+
<p>This is a dialog.</p>
|
|
43
|
+
<dui-dialog-close>
|
|
44
|
+
<dui-button appearance="outline">Close</dui-button>
|
|
45
|
+
</dui-dialog-close>
|
|
46
|
+
</dui-dialog-popup>
|
|
47
|
+
</dui-dialog>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or register everything at once:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import "@deepfuture/dui-components";
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## How It Works
|
|
57
|
+
|
|
58
|
+
DUI uses a two-layer inheritance model:
|
|
59
|
+
|
|
60
|
+
1. **Primitives** (`@dui/primitives`) — unstyled base classes with accessibility, keyboard behavior, and ARIA built in
|
|
61
|
+
2. **Components** (`@dui/components`) — extend primitives with design tokens, variant systems, and aesthetic CSS
|
|
62
|
+
|
|
63
|
+
Each component self-registers via `customElements.define()` when imported. Design tokens are automatically injected into `document.adoptedStyleSheets` on first import.
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
DuiButtonPrimitive (structure, ARIA, keyboard)
|
|
67
|
+
↓ extends
|
|
68
|
+
DuiButton (tokens, variants, aesthetic CSS, customElements.define())
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
No build step, no setup function, no configuration — just import and use.
|
|
72
|
+
|
|
73
|
+
## Components
|
|
74
|
+
|
|
75
|
+
43 component families, 85+ elements total.
|
|
76
|
+
|
|
77
|
+
| Category | Components |
|
|
78
|
+
|----------|-----------|
|
|
79
|
+
| **Actions** | Button, Toggle, Toggle Group, Toolbar, Split Button |
|
|
80
|
+
| **Forms** | Input, Textarea, Select, Combobox, Checkbox, Radio, Switch, Slider, Number Field, Dropzone, Field, Fieldset |
|
|
81
|
+
| **Data Display** | Badge, Avatar, Calendar, Data Table, Progress, Spinner, Separator, Trunc |
|
|
82
|
+
| **Overlays** | Dialog, Alert Dialog, Popover, Tooltip, Menu, Menubar, Preview Card, Command |
|
|
83
|
+
| **Disclosure** | Accordion, Collapsible, Tabs |
|
|
84
|
+
| **Navigation** | Breadcrumb, Sidebar (with 12 sub-components), Stepper |
|
|
85
|
+
| **Layout** | Card, Card Grid, Scroll Area, Portal |
|
|
86
|
+
| **Utility** | Icon |
|
|
87
|
+
|
|
88
|
+
## Styling
|
|
89
|
+
|
|
90
|
+
DUI uses a two-layer approach to styling:
|
|
91
|
+
|
|
92
|
+
### CSS Variables — for the variant system
|
|
93
|
+
|
|
94
|
+
Variables control values that variants, sizes, and states toggle:
|
|
95
|
+
|
|
96
|
+
```css
|
|
97
|
+
/* Override variant colors */
|
|
98
|
+
dui-button {
|
|
99
|
+
--button-bg: linear-gradient(135deg, pink, purple);
|
|
100
|
+
--button-radius: var(--radius-full);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### `::part(root)` — for everything else
|
|
105
|
+
|
|
106
|
+
Every component exposes a `root` CSS part for full CSS expressiveness:
|
|
107
|
+
|
|
108
|
+
```css
|
|
109
|
+
/* Frosted glass effect */
|
|
110
|
+
dui-dialog-popup::part(root) {
|
|
111
|
+
backdrop-filter: blur(12px);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Custom hover animation */
|
|
115
|
+
dui-button::part(root):hover {
|
|
116
|
+
filter: brightness(1.25);
|
|
117
|
+
transform: translateY(-1px);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* Glow shadow */
|
|
121
|
+
dui-badge::part(root) {
|
|
122
|
+
box-shadow: 0 0 16px oklch(0.7 0.2 280 / 0.4);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
No need for the library to anticipate every CSS property — `::part()` gives you direct access.
|
|
127
|
+
|
|
128
|
+
## Dark Mode
|
|
129
|
+
|
|
130
|
+
Toggle dark mode by setting `data-theme="dark"` on the `<html>` element:
|
|
131
|
+
|
|
132
|
+
```html
|
|
133
|
+
<html data-theme="dark">
|
|
134
|
+
<!-- All DUI components render in dark mode -->
|
|
135
|
+
</html>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Templates
|
|
139
|
+
|
|
140
|
+
Pre-composed UI patterns built from DUI components — ready-to-use cards, feed items, and other building blocks. Templates adapt automatically to dark mode and token overrides.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npm install @deepfuture/dui-templates
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import "@deepfuture/dui-templates/feed";
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
```html
|
|
151
|
+
<dui-feed-item
|
|
152
|
+
title="Earthquake detected"
|
|
153
|
+
subtitle="USGS Pacific Northwest"
|
|
154
|
+
timestamp="2 min ago"
|
|
155
|
+
category="Seismic"
|
|
156
|
+
severity="high"
|
|
157
|
+
description="Magnitude 4.2 recorded near Portland, OR."
|
|
158
|
+
></dui-feed-item>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Templates self-register on import, just like components.
|
|
162
|
+
|
|
163
|
+
## Packages
|
|
164
|
+
|
|
165
|
+
| Package | Purpose |
|
|
166
|
+
|---------|---------|
|
|
167
|
+
| [`@deepfuture/dui-components`](https://www.npmjs.com/package/@deepfuture/dui-components) | Styled components (extends dui-primitives) |
|
|
168
|
+
| [`@deepfuture/dui-templates`](https://www.npmjs.com/package/@deepfuture/dui-templates) | Pre-composed UI patterns |
|
|
169
|
+
| [`@deepfuture/dui-cdn`](https://www.npmjs.com/package/@deepfuture/dui-cdn) | Pre-bundled CDN build (all deps inlined) |
|
|
170
|
+
| [`@deepfuture/dui-inspector`](https://www.npmjs.com/package/@deepfuture/dui-inspector) | Runtime inspector & mutation API |
|
|
171
|
+
|
|
172
|
+
**Foundation (separate repo):**
|
|
173
|
+
|
|
174
|
+
| Package | Purpose |
|
|
175
|
+
|---------|---------|
|
|
176
|
+
| `@deepfuture/dui-primitives/core` | Base reset, event factory, floating UI utilities (part of dui-primitives) |
|
|
177
|
+
| [`@deepfuture/dui-primitives`](https://www.npmjs.com/package/@deepfuture/dui-primitives) | Unstyled accessible component classes |
|
|
178
|
+
|
|
179
|
+
## Dev Tools
|
|
180
|
+
|
|
181
|
+
### Theme Editor
|
|
182
|
+
|
|
183
|
+
A visual editor for design tokens. Edit colors with OKLCH sliders, tweak spacing and typography, and export your customized `tokens.css`.
|
|
184
|
+
|
|
185
|
+
### Inspector
|
|
186
|
+
|
|
187
|
+
A runtime inspector and mutation API for DUI components ([separate package](https://github.com/deepfuturenow/dui-inspector)). Two interfaces:
|
|
188
|
+
|
|
189
|
+
- **Visual UI** (Ctrl+Shift+I) — hover-highlight components, inspect properties/tokens/styles, edit theme CSS and design tokens live
|
|
190
|
+
- **Console API** — `window.__dui_inspect()`, `window.__dui_mutate.*`, `window.__dui_export()` for programmatic access by agents or scripts
|
|
191
|
+
|
|
192
|
+
## Documentation
|
|
193
|
+
|
|
194
|
+
- **[Live Docs](https://deepfuturenow.github.io/dui/)** — interactive demos for every component
|
|
195
|
+
- [Architecture](docs/architecture.md) — two-layer inheritance model, package responsibilities
|
|
196
|
+
- [Creating Components](docs/creating-components.md) — extending primitives into styled components
|
|
197
|
+
- [Creating Templates](docs/creating-templates.md) — building pre-composed UI patterns
|
|
198
|
+
- [Theming](docs/theming.md) — color system, design tokens, variant CSS
|
|
199
|
+
- [Styling](docs/styling.md) — customizing components with variables and `::part()`
|
|
200
|
+
- [Consuming](docs/consuming.md) — integrating DUI into an app
|
|
201
|
+
|
|
202
|
+
## Building Your Own Component Set
|
|
203
|
+
|
|
204
|
+
DUI itself is an example of extending [dui-primitives](https://github.com/deepfuturenow/dui-primitives). You can build your own styled component library the same way:
|
|
205
|
+
|
|
206
|
+
1. Install `@deepfuture/dui-primitives`
|
|
207
|
+
2. Extend primitives with your own aesthetic CSS
|
|
208
|
+
3. Call `customElements.define()` to self-register
|
|
209
|
+
|
|
210
|
+
See [Creating Components](docs/creating-components.md) for the full pattern.
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { LitElement, type PropertyValues, type TemplateResult } from "lit";
|
|
2
|
+
import { type MapContext } from "../map/map-context.js";
|
|
3
|
+
/** Fired when an unclustered point is clicked. */
|
|
4
|
+
export declare const pointClickEvent: (detail: {
|
|
5
|
+
feature: GeoJSON.Feature<GeoJSON.Point>;
|
|
6
|
+
coordinates: [number, number];
|
|
7
|
+
}) => CustomEvent<{
|
|
8
|
+
feature: GeoJSON.Feature<GeoJSON.Point>;
|
|
9
|
+
coordinates: [number, number];
|
|
10
|
+
}>;
|
|
11
|
+
/** Fired when a cluster is clicked. */
|
|
12
|
+
export declare const clusterClickEvent: (detail: {
|
|
13
|
+
clusterId: number;
|
|
14
|
+
coordinates: [number, number];
|
|
15
|
+
pointCount: number;
|
|
16
|
+
}) => CustomEvent<{
|
|
17
|
+
clusterId: number;
|
|
18
|
+
coordinates: [number, number];
|
|
19
|
+
pointCount: number;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* `<dui-map-cluster-layer>` — Clustered point visualization on the map.
|
|
23
|
+
*
|
|
24
|
+
* Accepts a GeoJSON FeatureCollection (or URL string) and renders clustered circles
|
|
25
|
+
* with automatic zoom-to-cluster on click.
|
|
26
|
+
*
|
|
27
|
+
* @fires dui-cluster-point-click - Fired when an unclustered point is clicked.
|
|
28
|
+
* @fires dui-cluster-click - Fired when a cluster is clicked.
|
|
29
|
+
*/
|
|
30
|
+
export declare class DuiMapClusterLayer extends LitElement {
|
|
31
|
+
#private;
|
|
32
|
+
static tagName: "dui-map-cluster-layer";
|
|
33
|
+
static styles: import("lit").CSSResult[];
|
|
34
|
+
/** GeoJSON data URL or inline FeatureCollection. */
|
|
35
|
+
accessor data: string | GeoJSON.FeatureCollection<GeoJSON.Point>;
|
|
36
|
+
/** Maximum zoom level to cluster points on. */
|
|
37
|
+
accessor clusterMaxZoom: number;
|
|
38
|
+
/** Radius of each cluster when clustering points in pixels. */
|
|
39
|
+
accessor clusterRadius: number;
|
|
40
|
+
/** Colors for cluster circles: `[small, medium, large]`. */
|
|
41
|
+
accessor clusterColors: [string, string, string];
|
|
42
|
+
/** Point count thresholds for color/size steps: `[medium, large]`. */
|
|
43
|
+
accessor clusterThresholds: [number, number];
|
|
44
|
+
/** Color for unclustered individual points. */
|
|
45
|
+
accessor pointColor: string;
|
|
46
|
+
/** Unique identifier. Auto-generated if not set. */
|
|
47
|
+
accessor clusterId: string;
|
|
48
|
+
accessor _mapCtx: MapContext;
|
|
49
|
+
connectedCallback(): void;
|
|
50
|
+
disconnectedCallback(): void;
|
|
51
|
+
updated(changed: PropertyValues): void;
|
|
52
|
+
render(): TemplateResult;
|
|
53
|
+
}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
2
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
3
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
4
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
5
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
6
|
+
var _, done = false;
|
|
7
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
8
|
+
var context = {};
|
|
9
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
10
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
11
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
12
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
13
|
+
if (kind === "accessor") {
|
|
14
|
+
if (result === void 0) continue;
|
|
15
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
16
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
17
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
18
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
19
|
+
}
|
|
20
|
+
else if (_ = accept(result)) {
|
|
21
|
+
if (kind === "field") initializers.unshift(_);
|
|
22
|
+
else descriptor[key] = _;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
26
|
+
done = true;
|
|
27
|
+
};
|
|
28
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
29
|
+
var useValue = arguments.length > 2;
|
|
30
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
31
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
32
|
+
}
|
|
33
|
+
return useValue ? value : void 0;
|
|
34
|
+
};
|
|
35
|
+
import { css, LitElement, html } from "lit";
|
|
36
|
+
import { property } from "lit/decorators.js";
|
|
37
|
+
import { consume } from "@lit/context";
|
|
38
|
+
import { base } from "@deepfuture/dui-primitives/core/base";
|
|
39
|
+
import { customEvent } from "@deepfuture/dui-primitives/core/event";
|
|
40
|
+
import { mapContext } from "../map/map-context.js";
|
|
41
|
+
/** Fired when an unclustered point is clicked. */
|
|
42
|
+
export const pointClickEvent = customEvent("dui-cluster-point-click", { bubbles: true, composed: true });
|
|
43
|
+
/** Fired when a cluster is clicked. */
|
|
44
|
+
export const clusterClickEvent = customEvent("dui-cluster-click", { bubbles: true, composed: true });
|
|
45
|
+
const styles = css `
|
|
46
|
+
:host {
|
|
47
|
+
display: contents;
|
|
48
|
+
}
|
|
49
|
+
`;
|
|
50
|
+
/**
|
|
51
|
+
* `<dui-map-cluster-layer>` — Clustered point visualization on the map.
|
|
52
|
+
*
|
|
53
|
+
* Accepts a GeoJSON FeatureCollection (or URL string) and renders clustered circles
|
|
54
|
+
* with automatic zoom-to-cluster on click.
|
|
55
|
+
*
|
|
56
|
+
* @fires dui-cluster-point-click - Fired when an unclustered point is clicked.
|
|
57
|
+
* @fires dui-cluster-click - Fired when a cluster is clicked.
|
|
58
|
+
*/
|
|
59
|
+
let DuiMapClusterLayer = (() => {
|
|
60
|
+
let _classSuper = LitElement;
|
|
61
|
+
let _data_decorators;
|
|
62
|
+
let _data_initializers = [];
|
|
63
|
+
let _data_extraInitializers = [];
|
|
64
|
+
let _clusterMaxZoom_decorators;
|
|
65
|
+
let _clusterMaxZoom_initializers = [];
|
|
66
|
+
let _clusterMaxZoom_extraInitializers = [];
|
|
67
|
+
let _clusterRadius_decorators;
|
|
68
|
+
let _clusterRadius_initializers = [];
|
|
69
|
+
let _clusterRadius_extraInitializers = [];
|
|
70
|
+
let _clusterColors_decorators;
|
|
71
|
+
let _clusterColors_initializers = [];
|
|
72
|
+
let _clusterColors_extraInitializers = [];
|
|
73
|
+
let _clusterThresholds_decorators;
|
|
74
|
+
let _clusterThresholds_initializers = [];
|
|
75
|
+
let _clusterThresholds_extraInitializers = [];
|
|
76
|
+
let _pointColor_decorators;
|
|
77
|
+
let _pointColor_initializers = [];
|
|
78
|
+
let _pointColor_extraInitializers = [];
|
|
79
|
+
let _clusterId_decorators;
|
|
80
|
+
let _clusterId_initializers = [];
|
|
81
|
+
let _clusterId_extraInitializers = [];
|
|
82
|
+
let __mapCtx_decorators;
|
|
83
|
+
let __mapCtx_initializers = [];
|
|
84
|
+
let __mapCtx_extraInitializers = [];
|
|
85
|
+
return class DuiMapClusterLayer extends _classSuper {
|
|
86
|
+
static {
|
|
87
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
88
|
+
_data_decorators = [property({ type: String })];
|
|
89
|
+
_clusterMaxZoom_decorators = [property({ type: Number, attribute: "cluster-max-zoom" })];
|
|
90
|
+
_clusterRadius_decorators = [property({ type: Number, attribute: "cluster-radius" })];
|
|
91
|
+
_clusterColors_decorators = [property({ type: Array, attribute: "cluster-colors" })];
|
|
92
|
+
_clusterThresholds_decorators = [property({ type: Array, attribute: "cluster-thresholds" })];
|
|
93
|
+
_pointColor_decorators = [property({ attribute: "point-color" })];
|
|
94
|
+
_clusterId_decorators = [property({ attribute: "cluster-id" })];
|
|
95
|
+
__mapCtx_decorators = [consume({ context: mapContext, subscribe: true })];
|
|
96
|
+
__esDecorate(this, null, _data_decorators, { kind: "accessor", name: "data", static: false, private: false, access: { has: obj => "data" in obj, get: obj => obj.data, set: (obj, value) => { obj.data = value; } }, metadata: _metadata }, _data_initializers, _data_extraInitializers);
|
|
97
|
+
__esDecorate(this, null, _clusterMaxZoom_decorators, { kind: "accessor", name: "clusterMaxZoom", static: false, private: false, access: { has: obj => "clusterMaxZoom" in obj, get: obj => obj.clusterMaxZoom, set: (obj, value) => { obj.clusterMaxZoom = value; } }, metadata: _metadata }, _clusterMaxZoom_initializers, _clusterMaxZoom_extraInitializers);
|
|
98
|
+
__esDecorate(this, null, _clusterRadius_decorators, { kind: "accessor", name: "clusterRadius", static: false, private: false, access: { has: obj => "clusterRadius" in obj, get: obj => obj.clusterRadius, set: (obj, value) => { obj.clusterRadius = value; } }, metadata: _metadata }, _clusterRadius_initializers, _clusterRadius_extraInitializers);
|
|
99
|
+
__esDecorate(this, null, _clusterColors_decorators, { kind: "accessor", name: "clusterColors", static: false, private: false, access: { has: obj => "clusterColors" in obj, get: obj => obj.clusterColors, set: (obj, value) => { obj.clusterColors = value; } }, metadata: _metadata }, _clusterColors_initializers, _clusterColors_extraInitializers);
|
|
100
|
+
__esDecorate(this, null, _clusterThresholds_decorators, { kind: "accessor", name: "clusterThresholds", static: false, private: false, access: { has: obj => "clusterThresholds" in obj, get: obj => obj.clusterThresholds, set: (obj, value) => { obj.clusterThresholds = value; } }, metadata: _metadata }, _clusterThresholds_initializers, _clusterThresholds_extraInitializers);
|
|
101
|
+
__esDecorate(this, null, _pointColor_decorators, { kind: "accessor", name: "pointColor", static: false, private: false, access: { has: obj => "pointColor" in obj, get: obj => obj.pointColor, set: (obj, value) => { obj.pointColor = value; } }, metadata: _metadata }, _pointColor_initializers, _pointColor_extraInitializers);
|
|
102
|
+
__esDecorate(this, null, _clusterId_decorators, { kind: "accessor", name: "clusterId", static: false, private: false, access: { has: obj => "clusterId" in obj, get: obj => obj.clusterId, set: (obj, value) => { obj.clusterId = value; } }, metadata: _metadata }, _clusterId_initializers, _clusterId_extraInitializers);
|
|
103
|
+
__esDecorate(this, null, __mapCtx_decorators, { kind: "accessor", name: "_mapCtx", static: false, private: false, access: { has: obj => "_mapCtx" in obj, get: obj => obj._mapCtx, set: (obj, value) => { obj._mapCtx = value; } }, metadata: _metadata }, __mapCtx_initializers, __mapCtx_extraInitializers);
|
|
104
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
105
|
+
}
|
|
106
|
+
static tagName = "dui-map-cluster-layer";
|
|
107
|
+
static styles = [base, styles];
|
|
108
|
+
#data_accessor_storage = __runInitializers(this, _data_initializers, "");
|
|
109
|
+
/** GeoJSON data URL or inline FeatureCollection. */
|
|
110
|
+
get data() { return this.#data_accessor_storage; }
|
|
111
|
+
set data(value) { this.#data_accessor_storage = value; }
|
|
112
|
+
#clusterMaxZoom_accessor_storage = (__runInitializers(this, _data_extraInitializers), __runInitializers(this, _clusterMaxZoom_initializers, 14));
|
|
113
|
+
/** Maximum zoom level to cluster points on. */
|
|
114
|
+
get clusterMaxZoom() { return this.#clusterMaxZoom_accessor_storage; }
|
|
115
|
+
set clusterMaxZoom(value) { this.#clusterMaxZoom_accessor_storage = value; }
|
|
116
|
+
#clusterRadius_accessor_storage = (__runInitializers(this, _clusterMaxZoom_extraInitializers), __runInitializers(this, _clusterRadius_initializers, 50));
|
|
117
|
+
/** Radius of each cluster when clustering points in pixels. */
|
|
118
|
+
get clusterRadius() { return this.#clusterRadius_accessor_storage; }
|
|
119
|
+
set clusterRadius(value) { this.#clusterRadius_accessor_storage = value; }
|
|
120
|
+
#clusterColors_accessor_storage = (__runInitializers(this, _clusterRadius_extraInitializers), __runInitializers(this, _clusterColors_initializers, ["#22c55e", "#eab308", "#ef4444"]));
|
|
121
|
+
/** Colors for cluster circles: `[small, medium, large]`. */
|
|
122
|
+
get clusterColors() { return this.#clusterColors_accessor_storage; }
|
|
123
|
+
set clusterColors(value) { this.#clusterColors_accessor_storage = value; }
|
|
124
|
+
#clusterThresholds_accessor_storage = (__runInitializers(this, _clusterColors_extraInitializers), __runInitializers(this, _clusterThresholds_initializers, [100, 750]));
|
|
125
|
+
/** Point count thresholds for color/size steps: `[medium, large]`. */
|
|
126
|
+
get clusterThresholds() { return this.#clusterThresholds_accessor_storage; }
|
|
127
|
+
set clusterThresholds(value) { this.#clusterThresholds_accessor_storage = value; }
|
|
128
|
+
#pointColor_accessor_storage = (__runInitializers(this, _clusterThresholds_extraInitializers), __runInitializers(this, _pointColor_initializers, "#3b82f6"));
|
|
129
|
+
/** Color for unclustered individual points. */
|
|
130
|
+
get pointColor() { return this.#pointColor_accessor_storage; }
|
|
131
|
+
set pointColor(value) { this.#pointColor_accessor_storage = value; }
|
|
132
|
+
#clusterId_accessor_storage = (__runInitializers(this, _pointColor_extraInitializers), __runInitializers(this, _clusterId_initializers, `cluster-${Math.random().toString(36).slice(2, 9)}`));
|
|
133
|
+
/** Unique identifier. Auto-generated if not set. */
|
|
134
|
+
get clusterId() { return this.#clusterId_accessor_storage; }
|
|
135
|
+
set clusterId(value) { this.#clusterId_accessor_storage = value; }
|
|
136
|
+
#_mapCtx_accessor_storage = (__runInitializers(this, _clusterId_extraInitializers), __runInitializers(this, __mapCtx_initializers, void 0));
|
|
137
|
+
get _mapCtx() { return this.#_mapCtx_accessor_storage; }
|
|
138
|
+
set _mapCtx(value) { this.#_mapCtx_accessor_storage = value; }
|
|
139
|
+
#sourceId = (__runInitializers(this, __mapCtx_extraInitializers), "");
|
|
140
|
+
#clusterLayerId = "";
|
|
141
|
+
#clusterCountLayerId = "";
|
|
142
|
+
#unclusteredLayerId = "";
|
|
143
|
+
#layersAdded = false;
|
|
144
|
+
connectedCallback() {
|
|
145
|
+
super.connectedCallback();
|
|
146
|
+
this.#sourceId = `cluster-source-${this.clusterId}`;
|
|
147
|
+
this.#clusterLayerId = `clusters-${this.clusterId}`;
|
|
148
|
+
this.#clusterCountLayerId = `cluster-count-${this.clusterId}`;
|
|
149
|
+
this.#unclusteredLayerId = `unclustered-point-${this.clusterId}`;
|
|
150
|
+
}
|
|
151
|
+
disconnectedCallback() {
|
|
152
|
+
super.disconnectedCallback();
|
|
153
|
+
this.#removeLayers();
|
|
154
|
+
}
|
|
155
|
+
updated(changed) {
|
|
156
|
+
const map = this._mapCtx?.map;
|
|
157
|
+
const isLoaded = this._mapCtx?.isLoaded;
|
|
158
|
+
if (!map || !isLoaded)
|
|
159
|
+
return;
|
|
160
|
+
if (!this.#layersAdded) {
|
|
161
|
+
this.#addLayers(map);
|
|
162
|
+
}
|
|
163
|
+
// Update source data when data changes (for non-URL data)
|
|
164
|
+
if (changed.has("data") && typeof this.data !== "string") {
|
|
165
|
+
const source = map.getSource(this.#sourceId);
|
|
166
|
+
if (source) {
|
|
167
|
+
source.setData(this.data);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Update layer styles
|
|
171
|
+
if (changed.has("clusterColors") || changed.has("clusterThresholds")) {
|
|
172
|
+
if (map.getLayer(this.#clusterLayerId)) {
|
|
173
|
+
map.setPaintProperty(this.#clusterLayerId, "circle-color", [
|
|
174
|
+
"step",
|
|
175
|
+
["get", "point_count"],
|
|
176
|
+
this.clusterColors[0],
|
|
177
|
+
this.clusterThresholds[0],
|
|
178
|
+
this.clusterColors[1],
|
|
179
|
+
this.clusterThresholds[1],
|
|
180
|
+
this.clusterColors[2],
|
|
181
|
+
]);
|
|
182
|
+
map.setPaintProperty(this.#clusterLayerId, "circle-radius", [
|
|
183
|
+
"step",
|
|
184
|
+
["get", "point_count"],
|
|
185
|
+
20,
|
|
186
|
+
this.clusterThresholds[0],
|
|
187
|
+
30,
|
|
188
|
+
this.clusterThresholds[1],
|
|
189
|
+
40,
|
|
190
|
+
]);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (changed.has("pointColor")) {
|
|
194
|
+
if (map.getLayer(this.#unclusteredLayerId)) {
|
|
195
|
+
map.setPaintProperty(this.#unclusteredLayerId, "circle-color", this.pointColor);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
#addLayers(map) {
|
|
200
|
+
if (map.getSource(this.#sourceId))
|
|
201
|
+
return;
|
|
202
|
+
map.addSource(this.#sourceId, {
|
|
203
|
+
type: "geojson",
|
|
204
|
+
data: this.data,
|
|
205
|
+
cluster: true,
|
|
206
|
+
clusterMaxZoom: this.clusterMaxZoom,
|
|
207
|
+
clusterRadius: this.clusterRadius,
|
|
208
|
+
});
|
|
209
|
+
// Cluster circles
|
|
210
|
+
map.addLayer({
|
|
211
|
+
id: this.#clusterLayerId,
|
|
212
|
+
type: "circle",
|
|
213
|
+
source: this.#sourceId,
|
|
214
|
+
filter: ["has", "point_count"],
|
|
215
|
+
paint: {
|
|
216
|
+
"circle-color": [
|
|
217
|
+
"step",
|
|
218
|
+
["get", "point_count"],
|
|
219
|
+
this.clusterColors[0],
|
|
220
|
+
this.clusterThresholds[0],
|
|
221
|
+
this.clusterColors[1],
|
|
222
|
+
this.clusterThresholds[1],
|
|
223
|
+
this.clusterColors[2],
|
|
224
|
+
],
|
|
225
|
+
"circle-radius": [
|
|
226
|
+
"step",
|
|
227
|
+
["get", "point_count"],
|
|
228
|
+
20,
|
|
229
|
+
this.clusterThresholds[0],
|
|
230
|
+
30,
|
|
231
|
+
this.clusterThresholds[1],
|
|
232
|
+
40,
|
|
233
|
+
],
|
|
234
|
+
"circle-stroke-width": 1,
|
|
235
|
+
"circle-stroke-color": "#fff",
|
|
236
|
+
"circle-opacity": 0.85,
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
// Cluster count text
|
|
240
|
+
map.addLayer({
|
|
241
|
+
id: this.#clusterCountLayerId,
|
|
242
|
+
type: "symbol",
|
|
243
|
+
source: this.#sourceId,
|
|
244
|
+
filter: ["has", "point_count"],
|
|
245
|
+
layout: {
|
|
246
|
+
"text-field": "{point_count_abbreviated}",
|
|
247
|
+
"text-size": 12,
|
|
248
|
+
},
|
|
249
|
+
paint: {
|
|
250
|
+
"text-color": "#fff",
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
// Unclustered points
|
|
254
|
+
map.addLayer({
|
|
255
|
+
id: this.#unclusteredLayerId,
|
|
256
|
+
type: "circle",
|
|
257
|
+
source: this.#sourceId,
|
|
258
|
+
filter: ["!", ["has", "point_count"]],
|
|
259
|
+
paint: {
|
|
260
|
+
"circle-color": this.pointColor,
|
|
261
|
+
"circle-radius": 5,
|
|
262
|
+
"circle-stroke-width": 2,
|
|
263
|
+
"circle-stroke-color": "#fff",
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
// Click handlers
|
|
267
|
+
map.on("click", this.#clusterLayerId, async (e) => {
|
|
268
|
+
const features = map.queryRenderedFeatures(e.point, {
|
|
269
|
+
layers: [this.#clusterLayerId],
|
|
270
|
+
});
|
|
271
|
+
if (!features.length)
|
|
272
|
+
return;
|
|
273
|
+
const feature = features[0];
|
|
274
|
+
const cid = feature.properties?.cluster_id;
|
|
275
|
+
const pointCount = feature.properties?.point_count;
|
|
276
|
+
const coordinates = feature.geometry.coordinates;
|
|
277
|
+
this.dispatchEvent(clusterClickEvent({ clusterId: cid, coordinates, pointCount }));
|
|
278
|
+
// Default: zoom to cluster expansion zoom
|
|
279
|
+
const source = map.getSource(this.#sourceId);
|
|
280
|
+
const zoom = await source.getClusterExpansionZoom(cid);
|
|
281
|
+
map.easeTo({ center: coordinates, zoom });
|
|
282
|
+
});
|
|
283
|
+
map.on("click", this.#unclusteredLayerId, (e) => {
|
|
284
|
+
if (!e.features?.length)
|
|
285
|
+
return;
|
|
286
|
+
const feature = e.features[0];
|
|
287
|
+
const coordinates = feature.geometry.coordinates.slice();
|
|
288
|
+
// Handle world copies
|
|
289
|
+
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
|
|
290
|
+
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
|
|
291
|
+
}
|
|
292
|
+
this.dispatchEvent(pointClickEvent({
|
|
293
|
+
feature: feature,
|
|
294
|
+
coordinates,
|
|
295
|
+
}));
|
|
296
|
+
});
|
|
297
|
+
// Cursor handlers
|
|
298
|
+
map.on("mouseenter", this.#clusterLayerId, () => {
|
|
299
|
+
map.getCanvas().style.cursor = "pointer";
|
|
300
|
+
});
|
|
301
|
+
map.on("mouseleave", this.#clusterLayerId, () => {
|
|
302
|
+
map.getCanvas().style.cursor = "";
|
|
303
|
+
});
|
|
304
|
+
map.on("mouseenter", this.#unclusteredLayerId, () => {
|
|
305
|
+
map.getCanvas().style.cursor = "pointer";
|
|
306
|
+
});
|
|
307
|
+
map.on("mouseleave", this.#unclusteredLayerId, () => {
|
|
308
|
+
map.getCanvas().style.cursor = "";
|
|
309
|
+
});
|
|
310
|
+
this.#layersAdded = true;
|
|
311
|
+
}
|
|
312
|
+
#removeLayers() {
|
|
313
|
+
const map = this._mapCtx?.map;
|
|
314
|
+
if (!map)
|
|
315
|
+
return;
|
|
316
|
+
try {
|
|
317
|
+
if (map.getLayer(this.#clusterCountLayerId))
|
|
318
|
+
map.removeLayer(this.#clusterCountLayerId);
|
|
319
|
+
if (map.getLayer(this.#unclusteredLayerId))
|
|
320
|
+
map.removeLayer(this.#unclusteredLayerId);
|
|
321
|
+
if (map.getLayer(this.#clusterLayerId))
|
|
322
|
+
map.removeLayer(this.#clusterLayerId);
|
|
323
|
+
if (map.getSource(this.#sourceId))
|
|
324
|
+
map.removeSource(this.#sourceId);
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
// Map may already be destroyed
|
|
328
|
+
}
|
|
329
|
+
this.#layersAdded = false;
|
|
330
|
+
}
|
|
331
|
+
render() {
|
|
332
|
+
return html ``;
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
})();
|
|
336
|
+
export { DuiMapClusterLayer };
|
|
337
|
+
customElements.define(DuiMapClusterLayer.tagName, DuiMapClusterLayer);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { LitElement, type TemplateResult } from "lit";
|
|
2
|
+
import { type MapContext } from "../map/map-context.js";
|
|
3
|
+
/** Fired when the user's geolocation is found. */
|
|
4
|
+
export declare const locateEvent: (detail: {
|
|
5
|
+
longitude: number;
|
|
6
|
+
latitude: number;
|
|
7
|
+
}) => CustomEvent<{
|
|
8
|
+
longitude: number;
|
|
9
|
+
latitude: number;
|
|
10
|
+
}>;
|
|
11
|
+
/**
|
|
12
|
+
* `<dui-map-controls>` — Zoom, compass, locate, and fullscreen controls.
|
|
13
|
+
*
|
|
14
|
+
* @csspart root - The outer controls wrapper.
|
|
15
|
+
* @csspart group - A control group container.
|
|
16
|
+
* @csspart control-button - Individual control buttons.
|
|
17
|
+
* @fires dui-map-locate - Fired with user coordinates when located.
|
|
18
|
+
*/
|
|
19
|
+
export declare class DuiMapControls extends LitElement {
|
|
20
|
+
#private;
|
|
21
|
+
static tagName: "dui-map-controls";
|
|
22
|
+
static styles: import("lit").CSSResult[];
|
|
23
|
+
/** Position on the map. */
|
|
24
|
+
accessor position: "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
|
25
|
+
/** Show zoom in/out buttons. */
|
|
26
|
+
accessor showZoom: boolean;
|
|
27
|
+
/** Show compass button to reset bearing. */
|
|
28
|
+
accessor showCompass: boolean;
|
|
29
|
+
/** Show locate button to find user's location. */
|
|
30
|
+
accessor showLocate: boolean;
|
|
31
|
+
/** Show fullscreen toggle button. */
|
|
32
|
+
accessor showFullscreen: boolean;
|
|
33
|
+
accessor _mapCtx: MapContext;
|
|
34
|
+
disconnectedCallback(): void;
|
|
35
|
+
updated(): void;
|
|
36
|
+
render(): TemplateResult;
|
|
37
|
+
}
|