@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.
Files changed (50) hide show
  1. package/README.md +214 -0
  2. package/cluster-layer/index.d.ts +2 -0
  3. package/cluster-layer/index.js +2 -0
  4. package/cluster-layer/map-cluster-layer.d.ts +53 -0
  5. package/cluster-layer/map-cluster-layer.js +337 -0
  6. package/controls/index.d.ts +2 -0
  7. package/controls/index.js +2 -0
  8. package/controls/map-controls.d.ts +37 -0
  9. package/controls/map-controls.js +344 -0
  10. package/heatmap/index.d.ts +2 -0
  11. package/heatmap/index.js +2 -0
  12. package/heatmap/map-heatmap.d.ts +39 -0
  13. package/heatmap/map-heatmap.js +330 -0
  14. package/index.d.ts +10 -0
  15. package/index.js +9 -0
  16. package/map/index.d.ts +5 -0
  17. package/map/index.js +3 -0
  18. package/map/map-context.d.ts +8 -0
  19. package/map/map-context.js +2 -0
  20. package/map/map.d.ts +120 -0
  21. package/map/map.js +628 -0
  22. package/marker/index.d.ts +9 -0
  23. package/marker/index.js +7 -0
  24. package/marker/map-marker-content.d.ts +20 -0
  25. package/marker/map-marker-content.js +135 -0
  26. package/marker/map-marker-label.d.ts +21 -0
  27. package/marker/map-marker-label.js +126 -0
  28. package/marker/map-marker-popup.d.ts +25 -0
  29. package/marker/map-marker-popup.js +158 -0
  30. package/marker/map-marker-tooltip.d.ts +22 -0
  31. package/marker/map-marker-tooltip.js +159 -0
  32. package/marker/map-marker.d.ts +48 -0
  33. package/marker/map-marker.js +220 -0
  34. package/marker/marker-context.d.ts +8 -0
  35. package/marker/marker-context.js +2 -0
  36. package/package.json +70 -0
  37. package/popup/index.d.ts +2 -0
  38. package/popup/index.js +2 -0
  39. package/popup/map-popup.d.ts +29 -0
  40. package/popup/map-popup.js +184 -0
  41. package/region/index.d.ts +2 -0
  42. package/region/index.js +2 -0
  43. package/region/map-region.d.ts +41 -0
  44. package/region/map-region.js +276 -0
  45. package/route/index.d.ts +2 -0
  46. package/route/index.js +2 -0
  47. package/route/map-route.d.ts +33 -0
  48. package/route/map-route.js +245 -0
  49. package/theme/map-theme.d.ts +16 -0
  50. package/theme/map-theme.js +233 -0
package/README.md ADDED
@@ -0,0 +1,214 @@
1
+ # DUI
2
+
3
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
4
+ [![npm](https://img.shields.io/npm/v/@deepfuture/dui-components.svg)](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,2 @@
1
+ import { DuiMapClusterLayer, pointClickEvent, clusterClickEvent } from "./map-cluster-layer.js";
2
+ export { DuiMapClusterLayer, pointClickEvent, clusterClickEvent };
@@ -0,0 +1,2 @@
1
+ import { DuiMapClusterLayer, pointClickEvent, clusterClickEvent } from "./map-cluster-layer.js";
2
+ export { DuiMapClusterLayer, pointClickEvent, clusterClickEvent };
@@ -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,2 @@
1
+ import { DuiMapControls, locateEvent } from "./map-controls.js";
2
+ export { DuiMapControls, locateEvent };
@@ -0,0 +1,2 @@
1
+ import { DuiMapControls, locateEvent } from "./map-controls.js";
2
+ export { DuiMapControls, locateEvent };
@@ -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
+ }