@leavepulse/ui 0.16.2 → 0.17.1
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/dist/chunks/LpAddressInput-ChT1JQg0.js +366 -0
- package/dist/chunks/{LpAppShell-CV194os6.js → LpAppShell-C6Mj82LS.js} +2 -2
- package/dist/chunks/{LpConfirmDialog-DnReh5pF.js → LpConfirmDialog-3_Qu4hnA.js} +1 -1
- package/dist/chunks/{LpDrawer-BHzCy-Aa.js → LpDrawer-Fxk7ViQ-.js} +1 -1
- package/dist/chunks/{LpFileTree-YOJCuIjJ.js → LpFileTree-CXg3KDth.js} +1 -1
- package/dist/chunks/{LpLogViewer-BvzRZPjs.js → LpLogViewer-D7DNEYaG.js} +1 -1
- package/dist/chunks/LpMapPicker-DFKrd1Ne.js +509 -0
- package/dist/chunks/{LpModal-CezA8eJ3.js → LpModal-J-KRPO7F.js} +1 -1
- package/dist/chunks/{LpNotificationBell-BtfRoL2i.js → LpNotificationBell-D8sdQLgu.js} +26 -13
- package/dist/chunks/{LpScrollArea-De9DLpa1.js → LpScrollArea-CKhOx0_l.js} +2 -2
- package/dist/chunks/{LpSidebar-B11IDRQi.js → LpSidebar-Bk7SrINq.js} +2 -2
- package/dist/chunks/{LpSidebarNav-TIUNHZX-.js → LpSidebarNav-Cpd-yHXb.js} +1 -1
- package/dist/chunks/{LpTable-CKGaUbEM.js → LpTable-X3ieZmbr.js} +1 -1
- package/dist/chunks/{LpThemeSwitcher-BgWwfkxs.js → LpThemeSwitcher-DC6QygnU.js} +11 -3
- package/dist/component-names.d.ts +1 -1
- package/dist/components/LpAddressInput.vue.d.ts +53 -0
- package/dist/components/LpAddressInput.vue.js +4 -0
- package/dist/components/LpAlert.vue.d.ts +1 -1
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.js +1 -1
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpFileTree.vue.d.ts +2 -2
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpLightbox.vue.d.ts +1 -1
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMapPicker.vue.d.ts +44 -0
- package/dist/components/LpMapPicker.vue.js +4 -0
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.d.ts +22 -0
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpScrollArea.vue.js +1 -1
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpTable.vue.js +1 -1
- package/dist/components/LpThemeSwitcher.vue.d.ts +7 -0
- package/dist/components/LpThemeSwitcher.vue.js +1 -1
- package/dist/components/address.d.ts +107 -0
- package/dist/components/address.js +96 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +129 -117
- package/package.json +1 -1
- package/src/component-names.ts +2 -0
- package/src/components/LpAddressInput.vue +377 -0
- package/src/components/LpMapPicker.vue +665 -0
- package/src/components/LpNotificationBell.vue +47 -9
- package/src/components/LpScrollArea.vue +8 -2
- package/src/components/LpThemeSwitcher.vue +24 -2
- package/src/components/address.ts +250 -0
- package/src/index.ts +194 -169
- package/src/theme/presets.ts +8 -2
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
import { defineComponent, computed, ref, watch, onMounted, onBeforeUnmount, openBlock, createElementBlock, normalizeStyle, createElementVNode, normalizeClass, Fragment, renderList, createVNode, createCommentVNode, createBlock, toDisplayString } from "vue";
|
|
2
|
+
import { OSM_TILES, project, tileUrl, unproject } from "../components/address.js";
|
|
3
|
+
import { _ as _sfc_main$1 } from "./LpIcon-CCnX5_2j.js";
|
|
4
|
+
const _hoisted_1 = ["tabindex", "aria-label"];
|
|
5
|
+
const _hoisted_2 = ["src", "onLoad"];
|
|
6
|
+
const _hoisted_3 = { class: "absolute right-2 top-2 flex flex-col gap-1.5" };
|
|
7
|
+
const _hoisted_4 = ["disabled", "title", "aria-label"];
|
|
8
|
+
const _hoisted_5 = ["disabled", "title", "aria-label"];
|
|
9
|
+
const _hoisted_6 = ["title", "aria-label"];
|
|
10
|
+
const _hoisted_7 = ["disabled", "title", "aria-label"];
|
|
11
|
+
const _hoisted_8 = {
|
|
12
|
+
key: 1,
|
|
13
|
+
class: "pointer-events-none absolute bottom-0 right-0 rounded-tl-control bg-black/45 px-1.5 py-0.5 text-[10px] text-white/75 backdrop-blur-sm"
|
|
14
|
+
};
|
|
15
|
+
const PAN_STEP = 80;
|
|
16
|
+
const ZOOM_MS = 220;
|
|
17
|
+
const CONTROL = "flex size-8 items-center justify-center rounded-control bg-black/45 text-white/90 outline-none backdrop-blur-sm transition-[background-color,scale] duration-[var(--duration-fast)] hover:bg-black/65 hover:scale-105 focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40";
|
|
18
|
+
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
19
|
+
__name: "LpMapPicker",
|
|
20
|
+
props: {
|
|
21
|
+
modelValue: {},
|
|
22
|
+
tiles: { default: () => OSM_TILES },
|
|
23
|
+
zoom: { default: 13 },
|
|
24
|
+
center: { default: () => ({ lat: 50.11, lon: 14.44 }) },
|
|
25
|
+
disabled: { type: Boolean },
|
|
26
|
+
height: { default: "20rem" },
|
|
27
|
+
locatable: { type: Boolean, default: true },
|
|
28
|
+
zoomInLabel: { default: "Zoom in" },
|
|
29
|
+
zoomOutLabel: { default: "Zoom out" },
|
|
30
|
+
locateLabel: { default: "Use my location" },
|
|
31
|
+
recentreLabel: { default: "Back to the pin" }
|
|
32
|
+
},
|
|
33
|
+
emits: ["update:modelValue"],
|
|
34
|
+
setup(__props, { expose: __expose, emit: __emit }) {
|
|
35
|
+
const props = __props;
|
|
36
|
+
const emit = __emit;
|
|
37
|
+
const seenTiles = /* @__PURE__ */ new Set();
|
|
38
|
+
const tileSize = computed(() => props.tiles.tileSize ?? 256);
|
|
39
|
+
const minZoom = computed(() => props.tiles.minZoom ?? 1);
|
|
40
|
+
const maxZoom = computed(() => props.tiles.maxZoom ?? 19);
|
|
41
|
+
const viewport = ref(null);
|
|
42
|
+
const viewWidth = ref(0);
|
|
43
|
+
const viewHeight = ref(0);
|
|
44
|
+
const level = ref(clampZoom(props.zoom));
|
|
45
|
+
const mapCenter = ref(props.modelValue ?? props.center);
|
|
46
|
+
const zooming = ref(false);
|
|
47
|
+
let zoomTimer;
|
|
48
|
+
let pruneTimer;
|
|
49
|
+
function clampZoom(value) {
|
|
50
|
+
return Math.round(Math.min(maxZoom.value, Math.max(minZoom.value, value)));
|
|
51
|
+
}
|
|
52
|
+
const origin = computed(() => {
|
|
53
|
+
const point = project(mapCenter.value.lat, mapCenter.value.lon, level.value, tileSize.value);
|
|
54
|
+
return { x: point.x - viewWidth.value / 2, y: point.y - viewHeight.value / 2 };
|
|
55
|
+
});
|
|
56
|
+
const layers = ref([]);
|
|
57
|
+
let nextLayerId = 1;
|
|
58
|
+
const active = computed(() => layers.value[layers.value.length - 1]);
|
|
59
|
+
function anchorFor(atLevel) {
|
|
60
|
+
const size = tileSize.value;
|
|
61
|
+
const factor = 2 ** (atLevel - level.value);
|
|
62
|
+
return {
|
|
63
|
+
x: Math.floor(origin.value.x * factor / size) * size,
|
|
64
|
+
y: Math.floor(origin.value.y * factor / size) * size
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function pushLayer() {
|
|
68
|
+
layers.value = [
|
|
69
|
+
...layers.value,
|
|
70
|
+
{
|
|
71
|
+
id: nextLayerId++,
|
|
72
|
+
level: level.value,
|
|
73
|
+
anchor: anchorFor(level.value),
|
|
74
|
+
tiles: [],
|
|
75
|
+
range: { firstX: 0, firstY: 0, lastX: -1, lastY: -1 }
|
|
76
|
+
}
|
|
77
|
+
];
|
|
78
|
+
refreshTiles();
|
|
79
|
+
}
|
|
80
|
+
function pruneLayers() {
|
|
81
|
+
if (layers.value.length < 2) return;
|
|
82
|
+
layers.value = [layers.value[layers.value.length - 1]];
|
|
83
|
+
}
|
|
84
|
+
function reanchor() {
|
|
85
|
+
const layer = active.value;
|
|
86
|
+
if (!layer) return;
|
|
87
|
+
const next = anchorFor(layer.level);
|
|
88
|
+
const dx = layer.anchor.x - next.x;
|
|
89
|
+
const dy = layer.anchor.y - next.y;
|
|
90
|
+
if (!dx && !dy) return;
|
|
91
|
+
layer.anchor = next;
|
|
92
|
+
for (const tile of layer.tiles) {
|
|
93
|
+
tile.left += dx;
|
|
94
|
+
tile.top += dy;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function refreshTiles() {
|
|
98
|
+
const layer = active.value;
|
|
99
|
+
if (!layer || !viewWidth.value || !viewHeight.value) return;
|
|
100
|
+
const size = tileSize.value;
|
|
101
|
+
const count = 2 ** layer.level;
|
|
102
|
+
const firstX = Math.floor(origin.value.x / size) - 1;
|
|
103
|
+
const firstY = Math.floor(origin.value.y / size) - 1;
|
|
104
|
+
const lastX = Math.floor((origin.value.x + viewWidth.value) / size) + 1;
|
|
105
|
+
const lastY = Math.floor((origin.value.y + viewHeight.value) / size) + 1;
|
|
106
|
+
const seen = layer.range;
|
|
107
|
+
if (seen.firstX === firstX && seen.firstY === firstY && seen.lastX === lastX && seen.lastY === lastY) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
layer.range = { firstX, firstY, lastX, lastY };
|
|
111
|
+
const previous = new Map(layer.tiles.map((tile) => [tile.key, tile]));
|
|
112
|
+
const next = [];
|
|
113
|
+
for (let y = firstY; y <= lastY; y++) {
|
|
114
|
+
if (y < 0 || y >= count) continue;
|
|
115
|
+
for (let x = firstX; x <= lastX; x++) {
|
|
116
|
+
const wrapped = (x % count + count) % count;
|
|
117
|
+
const key = `${layer.level}/${x}/${y}`;
|
|
118
|
+
const existing = previous.get(key);
|
|
119
|
+
if (existing) {
|
|
120
|
+
next.push(existing);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const url = tileUrl(props.tiles, layer.level, wrapped, y);
|
|
124
|
+
next.push({
|
|
125
|
+
key,
|
|
126
|
+
url,
|
|
127
|
+
left: x * size - layer.anchor.x,
|
|
128
|
+
top: y * size - layer.anchor.y,
|
|
129
|
+
// Anything fetched before paints at once: replaying the fade on a
|
|
130
|
+
// cached tile is what makes a zoom look like a reload.
|
|
131
|
+
loaded: seenTiles.has(url)
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
layer.tiles = next;
|
|
136
|
+
}
|
|
137
|
+
const loadingTiles = computed(() => active.value?.tiles.some((tile) => !tile.loaded) ?? false);
|
|
138
|
+
function onTileLoad(tile) {
|
|
139
|
+
tile.loaded = true;
|
|
140
|
+
seenTiles.add(tile.url);
|
|
141
|
+
if (!loadingTiles.value) pruneLayers();
|
|
142
|
+
}
|
|
143
|
+
function layerStyle(layer) {
|
|
144
|
+
const factor = 2 ** (layer.level - level.value);
|
|
145
|
+
const originX = origin.value.x * factor;
|
|
146
|
+
const originY = origin.value.y * factor;
|
|
147
|
+
return {
|
|
148
|
+
transform: `scale(${1 / factor}) translate3d(${layer.anchor.x - originX}px, ${layer.anchor.y - originY}px, 0)`,
|
|
149
|
+
transformOrigin: "0 0",
|
|
150
|
+
transition: zooming.value ? "transform var(--duration-medium) var(--ease-emphasized)" : void 0
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const pin = computed(() => {
|
|
154
|
+
if (!props.modelValue) return null;
|
|
155
|
+
const point = project(props.modelValue.lat, props.modelValue.lon, level.value, tileSize.value);
|
|
156
|
+
return { left: point.x - origin.value.x, top: point.y - origin.value.y };
|
|
157
|
+
});
|
|
158
|
+
function viewportPoint(e) {
|
|
159
|
+
const rect = viewport.value?.getBoundingClientRect();
|
|
160
|
+
return rect ? { x: e.clientX - rect.left, y: e.clientY - rect.top } : { x: 0, y: 0 };
|
|
161
|
+
}
|
|
162
|
+
function toLatLon(at) {
|
|
163
|
+
return unproject(origin.value.x + at.x, origin.value.y + at.y, level.value, tileSize.value);
|
|
164
|
+
}
|
|
165
|
+
const panning = ref(false);
|
|
166
|
+
let moved = false;
|
|
167
|
+
let last = { x: 0, y: 0 };
|
|
168
|
+
let velocity = { x: 0, y: 0 };
|
|
169
|
+
let lastMove = 0;
|
|
170
|
+
let glideFrame;
|
|
171
|
+
function stopGlide() {
|
|
172
|
+
if (glideFrame !== void 0) cancelAnimationFrame(glideFrame);
|
|
173
|
+
glideFrame = void 0;
|
|
174
|
+
velocity = { x: 0, y: 0 };
|
|
175
|
+
}
|
|
176
|
+
function onPointerDown(e) {
|
|
177
|
+
if (props.disabled || e.button !== 0 || zooming.value) return;
|
|
178
|
+
stopGlide();
|
|
179
|
+
panning.value = true;
|
|
180
|
+
moved = false;
|
|
181
|
+
last = { x: e.clientX, y: e.clientY };
|
|
182
|
+
lastMove = performance.now();
|
|
183
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
184
|
+
}
|
|
185
|
+
function onPointerMove(e) {
|
|
186
|
+
if (!panning.value) return;
|
|
187
|
+
const dx = e.clientX - last.x;
|
|
188
|
+
const dy = e.clientY - last.y;
|
|
189
|
+
if (Math.abs(dx) > 2 || Math.abs(dy) > 2) moved = true;
|
|
190
|
+
const now = performance.now();
|
|
191
|
+
const dt = now - lastMove;
|
|
192
|
+
if (dt > 0) {
|
|
193
|
+
velocity = {
|
|
194
|
+
x: velocity.x * 0.7 + dx / dt * 0.3,
|
|
195
|
+
y: velocity.y * 0.7 + dy / dt * 0.3
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
lastMove = now;
|
|
199
|
+
last = { x: e.clientX, y: e.clientY };
|
|
200
|
+
panBy(-dx, -dy);
|
|
201
|
+
}
|
|
202
|
+
function onPointerUp(e) {
|
|
203
|
+
if (!panning.value) return;
|
|
204
|
+
panning.value = false;
|
|
205
|
+
e.currentTarget.releasePointerCapture?.(e.pointerId);
|
|
206
|
+
if (!moved && !props.disabled) {
|
|
207
|
+
emit("update:modelValue", toLatLon(viewportPoint(e)));
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (performance.now() - lastMove > 90) velocity = { x: 0, y: 0 };
|
|
211
|
+
glide();
|
|
212
|
+
}
|
|
213
|
+
function glide() {
|
|
214
|
+
if (reducedMotion() || Math.hypot(velocity.x, velocity.y) < 0.05) {
|
|
215
|
+
velocity = { x: 0, y: 0 };
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
let previous = performance.now();
|
|
219
|
+
const step = (now) => {
|
|
220
|
+
const dt = Math.min(now - previous, 32);
|
|
221
|
+
previous = now;
|
|
222
|
+
const friction = Math.exp(-dt / 240);
|
|
223
|
+
velocity = { x: velocity.x * friction, y: velocity.y * friction };
|
|
224
|
+
panBy(-velocity.x * dt, -velocity.y * dt);
|
|
225
|
+
if (Math.hypot(velocity.x, velocity.y) > 0.015) {
|
|
226
|
+
glideFrame = requestAnimationFrame(step);
|
|
227
|
+
} else {
|
|
228
|
+
stopGlide();
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
glideFrame = requestAnimationFrame(step);
|
|
232
|
+
}
|
|
233
|
+
function reducedMotion() {
|
|
234
|
+
return typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
235
|
+
}
|
|
236
|
+
function panBy(dx, dy) {
|
|
237
|
+
mapCenter.value = unproject(
|
|
238
|
+
origin.value.x + dx + viewWidth.value / 2,
|
|
239
|
+
origin.value.y + dy + viewHeight.value / 2,
|
|
240
|
+
level.value,
|
|
241
|
+
tileSize.value
|
|
242
|
+
);
|
|
243
|
+
refreshTiles();
|
|
244
|
+
}
|
|
245
|
+
function zoomBy(delta, at) {
|
|
246
|
+
if (props.disabled) return;
|
|
247
|
+
const target = clampZoom(level.value + delta);
|
|
248
|
+
if (target === level.value) return;
|
|
249
|
+
stopGlide();
|
|
250
|
+
const point = at ?? { x: viewWidth.value / 2, y: viewHeight.value / 2 };
|
|
251
|
+
const ground = toLatLon(point);
|
|
252
|
+
const world = project(ground.lat, ground.lon, target, tileSize.value);
|
|
253
|
+
level.value = target;
|
|
254
|
+
mapCenter.value = unproject(
|
|
255
|
+
world.x - point.x + viewWidth.value / 2,
|
|
256
|
+
world.y - point.y + viewHeight.value / 2,
|
|
257
|
+
target,
|
|
258
|
+
tileSize.value
|
|
259
|
+
);
|
|
260
|
+
zooming.value = true;
|
|
261
|
+
pushLayer();
|
|
262
|
+
clearTimeout(zoomTimer);
|
|
263
|
+
zoomTimer = setTimeout(() => zooming.value = false, ZOOM_MS);
|
|
264
|
+
clearTimeout(pruneTimer);
|
|
265
|
+
pruneTimer = setTimeout(pruneLayers, 1200);
|
|
266
|
+
}
|
|
267
|
+
function onWheel(e) {
|
|
268
|
+
if (props.disabled) return;
|
|
269
|
+
e.preventDefault();
|
|
270
|
+
zoomBy(e.deltaY < 0 ? 1 : -1, viewportPoint(e));
|
|
271
|
+
}
|
|
272
|
+
function onDoubleClick(e) {
|
|
273
|
+
zoomBy(1, viewportPoint(e));
|
|
274
|
+
}
|
|
275
|
+
function onKeydown(e) {
|
|
276
|
+
if (props.disabled) return;
|
|
277
|
+
switch (e.key) {
|
|
278
|
+
case "ArrowUp":
|
|
279
|
+
e.preventDefault();
|
|
280
|
+
panBy(0, -PAN_STEP);
|
|
281
|
+
break;
|
|
282
|
+
case "ArrowDown":
|
|
283
|
+
e.preventDefault();
|
|
284
|
+
panBy(0, PAN_STEP);
|
|
285
|
+
break;
|
|
286
|
+
case "ArrowLeft":
|
|
287
|
+
e.preventDefault();
|
|
288
|
+
panBy(-PAN_STEP, 0);
|
|
289
|
+
break;
|
|
290
|
+
case "ArrowRight":
|
|
291
|
+
e.preventDefault();
|
|
292
|
+
panBy(PAN_STEP, 0);
|
|
293
|
+
break;
|
|
294
|
+
case "+":
|
|
295
|
+
case "=":
|
|
296
|
+
e.preventDefault();
|
|
297
|
+
zoomBy(1);
|
|
298
|
+
break;
|
|
299
|
+
case "-":
|
|
300
|
+
e.preventDefault();
|
|
301
|
+
zoomBy(-1);
|
|
302
|
+
break;
|
|
303
|
+
case "0":
|
|
304
|
+
e.preventDefault();
|
|
305
|
+
recentre();
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const canRecentre = computed(() => {
|
|
310
|
+
if (!pin.value) return false;
|
|
311
|
+
const { left, top } = pin.value;
|
|
312
|
+
return left < 0 || top < 0 || left > viewWidth.value || top > viewHeight.value;
|
|
313
|
+
});
|
|
314
|
+
function recentre() {
|
|
315
|
+
if (!props.modelValue) return;
|
|
316
|
+
stopGlide();
|
|
317
|
+
mapCenter.value = props.modelValue;
|
|
318
|
+
reanchor();
|
|
319
|
+
refreshTiles();
|
|
320
|
+
}
|
|
321
|
+
const locating = ref(false);
|
|
322
|
+
function locate() {
|
|
323
|
+
if (!navigator.geolocation || locating.value) return;
|
|
324
|
+
locating.value = true;
|
|
325
|
+
navigator.geolocation.getCurrentPosition(
|
|
326
|
+
(position) => {
|
|
327
|
+
locating.value = false;
|
|
328
|
+
const next = { lat: position.coords.latitude, lon: position.coords.longitude };
|
|
329
|
+
emit("update:modelValue", next);
|
|
330
|
+
mapCenter.value = next;
|
|
331
|
+
reanchor();
|
|
332
|
+
refreshTiles();
|
|
333
|
+
},
|
|
334
|
+
() => {
|
|
335
|
+
locating.value = false;
|
|
336
|
+
},
|
|
337
|
+
{ enableHighAccuracy: true, timeout: 1e4 }
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
watch(
|
|
341
|
+
() => props.modelValue,
|
|
342
|
+
(value) => {
|
|
343
|
+
if (!value || panning.value) return;
|
|
344
|
+
mapCenter.value = value;
|
|
345
|
+
if (level.value < props.zoom) {
|
|
346
|
+
level.value = clampZoom(props.zoom);
|
|
347
|
+
pushLayer();
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
reanchor();
|
|
351
|
+
refreshTiles();
|
|
352
|
+
}
|
|
353
|
+
);
|
|
354
|
+
watch(
|
|
355
|
+
() => props.tiles,
|
|
356
|
+
() => {
|
|
357
|
+
layers.value = [];
|
|
358
|
+
pushLayer();
|
|
359
|
+
}
|
|
360
|
+
);
|
|
361
|
+
let observer;
|
|
362
|
+
onMounted(() => {
|
|
363
|
+
const el = viewport.value;
|
|
364
|
+
if (!el) return;
|
|
365
|
+
observer = new ResizeObserver(([entry]) => {
|
|
366
|
+
viewWidth.value = entry.contentRect.width;
|
|
367
|
+
viewHeight.value = entry.contentRect.height;
|
|
368
|
+
if (!layers.value.length) {
|
|
369
|
+
pushLayer();
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
reanchor();
|
|
373
|
+
refreshTiles();
|
|
374
|
+
});
|
|
375
|
+
observer.observe(el);
|
|
376
|
+
});
|
|
377
|
+
onBeforeUnmount(() => {
|
|
378
|
+
observer?.disconnect();
|
|
379
|
+
clearTimeout(zoomTimer);
|
|
380
|
+
clearTimeout(pruneTimer);
|
|
381
|
+
stopGlide();
|
|
382
|
+
});
|
|
383
|
+
__expose({ zoomIn: () => zoomBy(1), zoomOut: () => zoomBy(-1), recentre, locate });
|
|
384
|
+
return (_ctx, _cache) => {
|
|
385
|
+
return openBlock(), createElementBlock("div", {
|
|
386
|
+
class: "relative overflow-hidden rounded-control border border-line bg-surface-soft",
|
|
387
|
+
style: normalizeStyle({ height: __props.height })
|
|
388
|
+
}, [
|
|
389
|
+
createElementVNode("div", {
|
|
390
|
+
ref_key: "viewport",
|
|
391
|
+
ref: viewport,
|
|
392
|
+
class: normalizeClass(["absolute inset-0 touch-none select-none outline-none focus-visible:ring-2 focus-visible:ring-ring", __props.disabled ? "cursor-not-allowed opacity-55" : panning.value ? "cursor-grabbing" : "cursor-grab"]),
|
|
393
|
+
tabindex: __props.disabled ? -1 : 0,
|
|
394
|
+
role: "application",
|
|
395
|
+
"aria-label": __props.recentreLabel,
|
|
396
|
+
onPointerdown: onPointerDown,
|
|
397
|
+
onPointermove: onPointerMove,
|
|
398
|
+
onPointerup: onPointerUp,
|
|
399
|
+
onPointercancel: onPointerUp,
|
|
400
|
+
onWheel,
|
|
401
|
+
onDblclick: onDoubleClick,
|
|
402
|
+
onKeydown
|
|
403
|
+
}, [
|
|
404
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(layers.value, (layer) => {
|
|
405
|
+
return openBlock(), createElementBlock("div", {
|
|
406
|
+
key: layer.id,
|
|
407
|
+
class: "absolute left-0 top-0 will-change-transform motion-reduce:transition-none",
|
|
408
|
+
style: normalizeStyle(layerStyle(layer))
|
|
409
|
+
}, [
|
|
410
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(layer.tiles, (tile) => {
|
|
411
|
+
return openBlock(), createElementBlock("img", {
|
|
412
|
+
key: tile.key,
|
|
413
|
+
src: tile.url,
|
|
414
|
+
alt: "",
|
|
415
|
+
draggable: "false",
|
|
416
|
+
class: normalizeClass(["absolute max-w-none", tile.loaded ? "" : "animate-[map-tile-in_var(--duration-medium)_ease] motion-reduce:animate-none"]),
|
|
417
|
+
style: normalizeStyle({
|
|
418
|
+
left: `${tile.left}px`,
|
|
419
|
+
top: `${tile.top}px`,
|
|
420
|
+
width: `${tileSize.value}px`,
|
|
421
|
+
height: `${tileSize.value}px`
|
|
422
|
+
}),
|
|
423
|
+
onLoad: ($event) => onTileLoad(tile)
|
|
424
|
+
}, null, 46, _hoisted_2);
|
|
425
|
+
}), 128))
|
|
426
|
+
], 4);
|
|
427
|
+
}), 128)),
|
|
428
|
+
pin.value ? (openBlock(), createElementBlock("span", {
|
|
429
|
+
key: 0,
|
|
430
|
+
class: "pointer-events-none absolute",
|
|
431
|
+
style: normalizeStyle({ left: `${pin.value.left}px`, top: `${pin.value.top}px`, transform: "translate(-50%, -100%)" })
|
|
432
|
+
}, [
|
|
433
|
+
createVNode(_sfc_main$1, {
|
|
434
|
+
name: "lucide:map-pin",
|
|
435
|
+
size: 30,
|
|
436
|
+
class: "block animate-[map-pin-in_var(--duration-medium)_var(--ease-emphasized)] text-danger drop-shadow-[0_2px_4px_rgba(0,0,0,0.45)] motion-reduce:animate-none"
|
|
437
|
+
})
|
|
438
|
+
], 4)) : createCommentVNode("", true)
|
|
439
|
+
], 42, _hoisted_1),
|
|
440
|
+
createElementVNode("div", _hoisted_3, [
|
|
441
|
+
createElementVNode("button", {
|
|
442
|
+
class: normalizeClass(CONTROL),
|
|
443
|
+
type: "button",
|
|
444
|
+
disabled: __props.disabled || level.value >= maxZoom.value,
|
|
445
|
+
title: __props.zoomInLabel,
|
|
446
|
+
"aria-label": __props.zoomInLabel,
|
|
447
|
+
onClick: _cache[0] || (_cache[0] = ($event) => zoomBy(1))
|
|
448
|
+
}, [
|
|
449
|
+
createVNode(_sfc_main$1, {
|
|
450
|
+
name: "lucide:plus",
|
|
451
|
+
size: 16
|
|
452
|
+
})
|
|
453
|
+
], 8, _hoisted_4),
|
|
454
|
+
createElementVNode("button", {
|
|
455
|
+
class: normalizeClass(CONTROL),
|
|
456
|
+
type: "button",
|
|
457
|
+
disabled: __props.disabled || level.value <= minZoom.value,
|
|
458
|
+
title: __props.zoomOutLabel,
|
|
459
|
+
"aria-label": __props.zoomOutLabel,
|
|
460
|
+
onClick: _cache[1] || (_cache[1] = ($event) => zoomBy(-1))
|
|
461
|
+
}, [
|
|
462
|
+
createVNode(_sfc_main$1, {
|
|
463
|
+
name: "lucide:minus",
|
|
464
|
+
size: 16
|
|
465
|
+
})
|
|
466
|
+
], 8, _hoisted_5),
|
|
467
|
+
canRecentre.value ? (openBlock(), createElementBlock("button", {
|
|
468
|
+
key: 0,
|
|
469
|
+
class: normalizeClass(CONTROL),
|
|
470
|
+
type: "button",
|
|
471
|
+
title: __props.recentreLabel,
|
|
472
|
+
"aria-label": __props.recentreLabel,
|
|
473
|
+
onClick: recentre
|
|
474
|
+
}, [
|
|
475
|
+
createVNode(_sfc_main$1, {
|
|
476
|
+
name: "lucide:locate-fixed",
|
|
477
|
+
size: 16
|
|
478
|
+
})
|
|
479
|
+
], 8, _hoisted_6)) : createCommentVNode("", true),
|
|
480
|
+
__props.locatable ? (openBlock(), createElementBlock("button", {
|
|
481
|
+
key: 1,
|
|
482
|
+
class: normalizeClass(CONTROL),
|
|
483
|
+
type: "button",
|
|
484
|
+
disabled: __props.disabled || locating.value,
|
|
485
|
+
title: __props.locateLabel,
|
|
486
|
+
"aria-label": __props.locateLabel,
|
|
487
|
+
onClick: locate
|
|
488
|
+
}, [
|
|
489
|
+
createVNode(_sfc_main$1, {
|
|
490
|
+
name: locating.value ? "lucide:loader-circle" : "lucide:crosshair",
|
|
491
|
+
size: 16,
|
|
492
|
+
class: normalizeClass(locating.value ? "animate-spin" : "")
|
|
493
|
+
}, null, 8, ["name", "class"])
|
|
494
|
+
], 8, _hoisted_7)) : createCommentVNode("", true)
|
|
495
|
+
]),
|
|
496
|
+
loadingTiles.value ? (openBlock(), createBlock(_sfc_main$1, {
|
|
497
|
+
key: 0,
|
|
498
|
+
name: "lucide:loader-circle",
|
|
499
|
+
size: 16,
|
|
500
|
+
class: "pointer-events-none absolute left-2 top-2 animate-spin text-white/70 drop-shadow-[0_1px_2px_rgba(0,0,0,0.6)]"
|
|
501
|
+
})) : createCommentVNode("", true),
|
|
502
|
+
props.tiles.attribution ? (openBlock(), createElementBlock("p", _hoisted_8, toDisplayString(props.tiles.attribution), 1)) : createCommentVNode("", true)
|
|
503
|
+
], 4);
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
export {
|
|
508
|
+
_sfc_main as _
|
|
509
|
+
};
|
|
@@ -2,7 +2,7 @@ import { defineComponent, computed, useSlots, openBlock, createBlock, unref, wit
|
|
|
2
2
|
import { DialogRoot, DialogPortal, DialogOverlay, DialogContent, DialogTitle, DialogDescription, DialogClose } from "reka-ui";
|
|
3
3
|
import { CLOSE_ICON } from "../components/dropdown.js";
|
|
4
4
|
import { _ as _sfc_main$1 } from "./LpIcon-CCnX5_2j.js";
|
|
5
|
-
import { _ as _sfc_main$2 } from "./LpScrollArea-
|
|
5
|
+
import { _ as _sfc_main$2 } from "./LpScrollArea-CKhOx0_l.js";
|
|
6
6
|
const _hoisted_1 = { class: "fixed inset-0 z-(--z-modal) flex items-center justify-center pointer-events-none" };
|
|
7
7
|
const _hoisted_2 = {
|
|
8
8
|
key: 0,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { defineComponent,
|
|
1
|
+
import { defineComponent, computed, useModel, openBlock, createBlock, withCtx, createElementVNode, toDisplayString, createElementBlock, createCommentVNode, Fragment, renderList, resolveDynamicComponent, normalizeClass, createVNode, createTextVNode, mergeModels } from "vue";
|
|
2
2
|
import { _ as _sfc_main$7 } from "./LpBadge-CXzBPnwO.js";
|
|
3
3
|
import { _ as _sfc_main$6 } from "./LpButton-5VXXCjwF.js";
|
|
4
4
|
import { _ as _sfc_main$4 } from "./LpContextMenu-D0mzXqA7.js";
|
|
5
5
|
import { _ as _sfc_main$2 } from "./LpEmptyState-CrfyRJUG.js";
|
|
6
6
|
import { _ as _sfc_main$5 } from "./LpIcon-CCnX5_2j.js";
|
|
7
7
|
import { _ as _sfc_main$1 } from "./LpPopover-8268UmlC.js";
|
|
8
|
-
import { _ as _sfc_main$3 } from "./LpScrollArea-
|
|
8
|
+
import { _ as _sfc_main$3 } from "./LpScrollArea-CKhOx0_l.js";
|
|
9
9
|
const _hoisted_1 = { class: "flex items-center justify-between gap-2 px-3 py-2.5" };
|
|
10
10
|
const _hoisted_2 = { class: "text-sm font-semibold" };
|
|
11
11
|
const _hoisted_3 = {
|
|
@@ -35,7 +35,9 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
35
35
|
emptyLabel: {},
|
|
36
36
|
title: {},
|
|
37
37
|
maxBadge: { default: 9 },
|
|
38
|
-
menuItems: {}
|
|
38
|
+
menuItems: {},
|
|
39
|
+
labels: {},
|
|
40
|
+
formatTime: {}
|
|
39
41
|
}, {
|
|
40
42
|
"open": { type: Boolean, ...{ default: false } },
|
|
41
43
|
"openModifiers": {}
|
|
@@ -43,6 +45,16 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
43
45
|
emits: /* @__PURE__ */ mergeModels(["markRead", "markUnread", "markAllRead", "dismiss", "select"], ["update:open"]),
|
|
44
46
|
setup(__props, { emit: __emit }) {
|
|
45
47
|
const props = __props;
|
|
48
|
+
const l = computed(() => ({
|
|
49
|
+
title: props.title ?? props.labels?.title ?? "Notifications",
|
|
50
|
+
markAllRead: props.labels?.markAllRead ?? "Mark all read",
|
|
51
|
+
markAsRead: props.labels?.markAsRead ?? "Mark as read",
|
|
52
|
+
markAsUnread: props.labels?.markAsUnread ?? "Mark as unread",
|
|
53
|
+
open: props.labels?.open ?? "Open",
|
|
54
|
+
dismiss: props.labels?.dismiss ?? "Dismiss",
|
|
55
|
+
empty: props.emptyLabel ?? props.labels?.empty ?? "No notifications",
|
|
56
|
+
ariaLabel: props.labels?.ariaLabel ?? props.labels?.title ?? "Notifications"
|
|
57
|
+
}));
|
|
46
58
|
const open = useModel(__props, "open");
|
|
47
59
|
const emit = __emit;
|
|
48
60
|
const unread = computed(
|
|
@@ -55,7 +67,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
55
67
|
const contextMenu = computed(
|
|
56
68
|
() => props.menuItems ?? [
|
|
57
69
|
{
|
|
58
|
-
label:
|
|
70
|
+
label: l.value.markAllRead,
|
|
59
71
|
icon: "lucide:check-check",
|
|
60
72
|
disabled: !hasUnread.value,
|
|
61
73
|
onSelect: () => emit("markAllRead")
|
|
@@ -70,26 +82,26 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
70
82
|
const actions = [];
|
|
71
83
|
if (item.read) {
|
|
72
84
|
actions.push({
|
|
73
|
-
label:
|
|
85
|
+
label: l.value.markAsUnread,
|
|
74
86
|
icon: "lucide:dot",
|
|
75
87
|
onSelect: () => emit("markUnread", item.id)
|
|
76
88
|
});
|
|
77
89
|
} else {
|
|
78
90
|
actions.push({
|
|
79
|
-
label:
|
|
91
|
+
label: l.value.markAsRead,
|
|
80
92
|
icon: "lucide:check",
|
|
81
93
|
onSelect: () => emit("markRead", item.id)
|
|
82
94
|
});
|
|
83
95
|
}
|
|
84
96
|
if (item.link) {
|
|
85
97
|
actions.push({
|
|
86
|
-
label:
|
|
98
|
+
label: l.value.open,
|
|
87
99
|
icon: "lucide:external-link",
|
|
88
100
|
onSelect: () => emit("select", item)
|
|
89
101
|
});
|
|
90
102
|
}
|
|
91
103
|
actions.push({
|
|
92
|
-
label:
|
|
104
|
+
label: l.value.dismiss,
|
|
93
105
|
icon: "lucide:x",
|
|
94
106
|
danger: true,
|
|
95
107
|
separatorBefore: true,
|
|
@@ -99,6 +111,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
99
111
|
}
|
|
100
112
|
function timeAgo(iso) {
|
|
101
113
|
if (!iso) return "";
|
|
114
|
+
if (props.formatTime) return props.formatTime(iso);
|
|
102
115
|
const then = new Date(iso).getTime();
|
|
103
116
|
if (Number.isNaN(then)) return "";
|
|
104
117
|
const secs = Math.max(0, Math.round((Date.now() - then) / 1e3));
|
|
@@ -127,7 +140,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
127
140
|
variant: "ghost",
|
|
128
141
|
size: "sm",
|
|
129
142
|
class: "relative",
|
|
130
|
-
"aria-label":
|
|
143
|
+
"aria-label": l.value.ariaLabel,
|
|
131
144
|
onClick: _cache[0] || (_cache[0] = ($event) => open.value = !open.value)
|
|
132
145
|
}, {
|
|
133
146
|
default: withCtx(() => [
|
|
@@ -147,20 +160,20 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
147
160
|
})) : createCommentVNode("", true)
|
|
148
161
|
]),
|
|
149
162
|
_: 1
|
|
150
|
-
})
|
|
163
|
+
}, 8, ["aria-label"])
|
|
151
164
|
]),
|
|
152
165
|
_: 1
|
|
153
166
|
}, 8, ["items"])
|
|
154
167
|
]),
|
|
155
168
|
default: withCtx(() => [
|
|
156
169
|
createElementVNode("header", _hoisted_1, [
|
|
157
|
-
createElementVNode("span", _hoisted_2, toDisplayString(
|
|
170
|
+
createElementVNode("span", _hoisted_2, toDisplayString(l.value.title), 1),
|
|
158
171
|
hasUnread.value ? (openBlock(), createElementBlock("button", {
|
|
159
172
|
key: 0,
|
|
160
173
|
type: "button",
|
|
161
174
|
class: "rounded-md px-1.5 py-0.5 text-xs font-medium text-brand outline-none transition-colors hover:bg-brand-soft focus-visible:bg-brand-soft",
|
|
162
175
|
onClick: _cache[1] || (_cache[1] = ($event) => emit("markAllRead"))
|
|
163
|
-
},
|
|
176
|
+
}, toDisplayString(l.value.markAllRead), 1)) : createCommentVNode("", true)
|
|
164
177
|
]),
|
|
165
178
|
__props.loading ? (openBlock(), createElementBlock("div", _hoisted_3, [
|
|
166
179
|
(openBlock(), createElementBlock(Fragment, null, renderList(4, (i) => {
|
|
@@ -172,7 +185,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
172
185
|
])) : !__props.items.length ? (openBlock(), createBlock(_sfc_main$2, {
|
|
173
186
|
key: 1,
|
|
174
187
|
icon: "lucide:bell-off",
|
|
175
|
-
title:
|
|
188
|
+
title: l.value.empty,
|
|
176
189
|
class: "px-3 py-10"
|
|
177
190
|
}, null, 8, ["title"])) : (openBlock(), createBlock(_sfc_main$3, {
|
|
178
191
|
key: 2,
|
|
@@ -18,7 +18,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
18
18
|
__expose({ viewportEl });
|
|
19
19
|
return (_ctx, _cache) => {
|
|
20
20
|
return openBlock(), createBlock(unref(ScrollAreaRoot), {
|
|
21
|
-
class: "relative overflow-hidden",
|
|
21
|
+
class: "relative flex flex-col overflow-hidden",
|
|
22
22
|
type: "hover",
|
|
23
23
|
"scroll-hide-delay": 500
|
|
24
24
|
}, {
|
|
@@ -46,7 +46,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
46
46
|
createVNode(unref(ScrollAreaViewport), {
|
|
47
47
|
ref_key: "viewportRef",
|
|
48
48
|
ref: viewportRef,
|
|
49
|
-
class: normalizeClass(["
|
|
49
|
+
class: normalizeClass(["w-full min-h-0 min-w-0 flex-1 [&>div]:!block [&>div]:!min-w-0", [
|
|
50
50
|
__props.instant ? "" : "scroll-smooth motion-reduce:scroll-auto",
|
|
51
51
|
__props.fade ? "[mask-image:linear-gradient(to_bottom,transparent_0,black_14px,black_calc(100%-14px),transparent_100%)]" : ""
|
|
52
52
|
]]),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineComponent, computed, useId, openBlock, createElementBlock, Fragment, createElementVNode, mergeProps, normalizeClass, renderSlot, createCommentVNode, createVNode, createSlots, withCtx, normalizeProps, guardReactiveProps, createBlock } from "vue";
|
|
2
|
-
import { _ as _sfc_main$2 } from "./LpDrawer-
|
|
3
|
-
import { _ as _sfc_main$1 } from "./LpSidebarNav-
|
|
2
|
+
import { _ as _sfc_main$2 } from "./LpDrawer-Fxk7ViQ-.js";
|
|
3
|
+
import { _ as _sfc_main$1 } from "./LpSidebarNav-Cpd-yHXb.js";
|
|
4
4
|
const _hoisted_1 = {
|
|
5
5
|
key: 1,
|
|
6
6
|
class: "mt-2 flex shrink-0 flex-col gap-2"
|
|
@@ -4,7 +4,7 @@ import { usePillTransition } from "../composables/usePillTransition.js";
|
|
|
4
4
|
import { _ as _sfc_main$4 } from "./LpBadge-CXzBPnwO.js";
|
|
5
5
|
import { _ as _sfc_main$2 } from "./LpContextMenu-D0mzXqA7.js";
|
|
6
6
|
import { _ as _sfc_main$3 } from "./LpIcon-CCnX5_2j.js";
|
|
7
|
-
import { _ as _sfc_main$1 } from "./LpScrollArea-
|
|
7
|
+
import { _ as _sfc_main$1 } from "./LpScrollArea-CKhOx0_l.js";
|
|
8
8
|
const _hoisted_1 = {
|
|
9
9
|
key: 0,
|
|
10
10
|
class: "flex min-h-0 flex-1 flex-col gap-4"
|