Compare commits

..

No commits in common. "bad465406d209aade87ece2fc399f6664422aeaa" and "cc44a68e6637f59d91bd6c6270ba92ebb32996b4" have entirely different histories.

7 changed files with 209 additions and 264 deletions

View file

@ -58,4 +58,4 @@ tabs:
label: Position sur la carte
type: map-editor
mode: single
help: Déplacez le marqueur
help: Déplacez le marqueur ou recherchez une adresse

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -36,7 +36,7 @@
</template>
<script>
import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from 'vue';
import { ref, computed, watch, onMounted, nextTick } from 'vue';
import MapPreview from '../map/MapPreview.vue';
import MarkerList from '../map/MarkerList.vue';
import { useMarkersApi } from '../../composables/useMarkersApi.js';
@ -191,7 +191,6 @@ export default {
// Watch for changes in the form fields
const form = document.querySelector('.k-form');
if (form) {
// Listen to input events
form.addEventListener('input', (e) => {
if (e.target.name && (e.target.name.includes('latitude') || e.target.name.includes('longitude'))) {
const newCoords = getCoordinatesFromForm();
@ -199,39 +198,6 @@ export default {
singleLon.value = newCoords.lon;
}
});
// Also use MutationObserver to detect value changes (e.g., from "Supprimer" button)
const latInput = form.querySelector('input[name*="latitude"]');
const lonInput = form.querySelector('input[name*="longitude"]');
if (latInput && lonInput) {
const observer = new MutationObserver(() => {
const newCoords = getCoordinatesFromForm();
if (newCoords.lat !== singleLat.value || newCoords.lon !== singleLon.value) {
singleLat.value = newCoords.lat;
singleLon.value = newCoords.lon;
}
});
// Observe attribute changes (value attribute)
observer.observe(latInput, { attributes: true, attributeFilter: ['value'] });
observer.observe(lonInput, { attributes: true, attributeFilter: ['value'] });
// Also poll periodically as a fallback
const pollInterval = setInterval(() => {
const newCoords = getCoordinatesFromForm();
if (newCoords.lat !== singleLat.value || newCoords.lon !== singleLon.value) {
singleLat.value = newCoords.lat;
singleLon.value = newCoords.lon;
}
}, 500);
// Cleanup on unmount
onBeforeUnmount(() => {
observer.disconnect();
clearInterval(pollInterval);
});
}
}
}
@ -262,23 +228,9 @@ export default {
if (props.mode === 'single') {
// Center map on new position if valid
if (!isNaN(lat) && !isNaN(lon) && lat !== null && lon !== null && lat !== 0 && lon !== 0) {
// Force immediate reactivity
nextTick(() => {
if (mapPreview.value && mapPreview.value.centerOnPosition) {
mapPreview.value.centerOnPosition(lat, lon);
}
});
} else {
// Coordinates are invalid/cleared - reset to default center
center.value = {
lat: props.defaultCenter[0],
lon: props.defaultCenter[1]
};
nextTick(() => {
if (mapPreview.value && mapPreview.value.centerOnPosition) {
mapPreview.value.centerOnPosition(center.value.lat, center.value.lon);
}
});
if (mapPreview.value && mapPreview.value.centerOnPosition) {
mapPreview.value.centerOnPosition(lat, lon);
}
}
}
}

View file

@ -215,7 +215,6 @@ export default {
background: var(--color-white);
transition: border-color 0.2s;
color: #000;
background: var(--input-color-back);
}
.search-input:focus {

View file

@ -138,11 +138,10 @@ export default {
const isMarkerClick = target.closest('.custom-marker');
if (!isMarkerClick) {
const clickPos = {
emit("map-click", {
lat: e.lngLat.lat,
lng: e.lngLat.lng
};
emit("map-click", clickPos);
});
}
});
} catch (error) {
@ -177,27 +176,20 @@ export default {
return;
}
// Create marker element (outer wrapper for MapLibre positioning)
// Create marker element
const el = document.createElement("div");
el.className = "custom-marker";
// Create inner wrapper for visual transforms (isolates from MapLibre transforms)
const inner = document.createElement("div");
inner.className = "marker-inner";
if (props.selectedMarkerId === markerData.id) {
inner.classList.add("selected");
el.classList.add("selected");
}
// Add marker number
const numberEl = document.createElement("div");
numberEl.className = "marker-number";
numberEl.textContent = index + 1;
inner.appendChild(numberEl);
el.appendChild(inner);
el.appendChild(numberEl);
try {
const coords = [markerData.position.lon, markerData.position.lat];
// Create MapLibre marker
// Anchor at bottom-center (where the pin tip is)
const marker = new maplibregl.Marker({
@ -205,7 +197,7 @@ export default {
draggable: true,
anchor: 'bottom'
})
.setLngLat(coords)
.setLngLat([markerData.position.lon, markerData.position.lat])
.addTo(map.value);
// Handle marker drag
@ -252,13 +244,10 @@ export default {
function updateMarkerSelection(selectedId) {
markerElements.value.forEach(({ element }, markerId) => {
if (element) {
const inner = element.querySelector('.marker-inner');
if (inner) {
if (markerId === selectedId) {
inner.classList.add("selected");
} else {
inner.classList.remove("selected");
}
if (markerId === selectedId) {
element.classList.add("selected");
} else {
element.classList.remove("selected");
}
}
});
@ -343,27 +332,22 @@ export default {
}
}
/* Custom marker outer wrapper - NO transforms here, MapLibre handles positioning */
/* Custom marker styles */
.custom-marker {
/* MapLibre will position this element via transform: translate3d() */
cursor: grab;
}
.custom-marker:active {
cursor: grabbing;
}
/* Inner wrapper for visual styling - transforms are isolated here */
.marker-inner {
width: 40px;
height: 40px;
cursor: grab;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.marker-inner::before {
.custom-marker:active {
cursor: grabbing;
}
.custom-marker::before {
content: "";
position: absolute;
width: 40px;
@ -376,12 +360,12 @@ export default {
transition: all 0.2s;
}
.marker-inner:hover::before {
.custom-marker:hover::before {
background: #c0392b;
transform: rotate(-45deg) scale(1.1);
}
.marker-inner.selected::before {
.custom-marker.selected::before {
background: #3498db;
border-color: #2980b9;
box-shadow: 0 4px 12px rgba(52, 152, 219, 0.6);
@ -394,7 +378,7 @@ export default {
font-weight: 700;
font-size: 14px;
line-height: 1;
/* transform removed - was causing positioning issues */
transform: translateY(-4px);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
pointer-events: none;
}

View file

@ -1,65 +1,64 @@
<template>
<aside class="k-map-markers-sidebar">
<!-- Header with counter and add button -->
<header class="k-section-header">
<k-headline>
Marqueurs
<k-counter>{{ markers.length }}/{{ maxMarkers }}</k-counter>
</k-headline>
<k-button
icon="add"
size="xs"
variant="filled"
<div class="marker-list-sidebar">
<!-- Header with add button -->
<div class="marker-list-header">
<h3>Marqueurs ({{ markers.length }}/{{ maxMarkers }})</h3>
<button
type="button"
class="k-button k-button-icon"
@click="$emit('add-marker')"
:disabled="!canAddMarker"
title="Ajouter un marqueur"
>
Ajouter
</k-button>
</header>
<k-icon type="add" />
</button>
</div>
<!-- Geocode search -->
<div class="k-map-markers-search">
<div class="geocode-search-container">
<GeocodeSearch @select-location="$emit('select-location', $event)" />
</div>
<!-- Marker list -->
<div class="k-map-markers-list">
<!-- Marker list items -->
<div class="marker-list-items">
<div
v-for="(marker, index) in markers"
:key="marker.id"
class="k-map-marker-item"
:class="{ 'is-selected': selectedMarkerId === marker.id }"
class="marker-item"
:class="{ active: selectedMarkerId === marker.id }"
@click="$emit('select-marker', marker.id)"
>
<span class="k-map-marker-icon">
{{ index + 1 }}
</span>
<span class="k-map-marker-text">
{{ marker.title || `Marqueur ${index + 1}` }}
</span>
<span class="k-map-marker-options">
<k-button
icon="open"
size="xs"
<div class="marker-item-content">
<span class="marker-number">{{ index + 1 }}</span>
<span class="marker-title">
{{ marker.title || `Marqueur ${index + 1}` }}
</span>
</div>
<div class="marker-item-actions">
<button
type="button"
class="k-button k-button-small"
@click.stop="$emit('edit-marker', marker.id)"
/>
<k-button
icon="trash"
size="xs"
title="Modifier le marqueur"
>
<k-icon type="edit" />
</button>
<button
type="button"
class="k-button k-button-small"
@click.stop="$emit('delete-marker', marker.id)"
/>
</span>
title="Supprimer le marqueur"
>
<k-icon type="trash" />
</button>
</div>
</div>
<div v-if="markers.length === 0" class="k-map-markers-empty">
<k-icon type="map-pin" />
<p class="k-map-markers-empty-text">Aucun marqueur</p>
<p class="k-map-markers-empty-info">
Cliquez sur la carte ou sur "Ajouter" pour créer un marqueur
</p>
<div v-if="markers.length === 0" class="marker-list-empty">
Cliquez sur la carte pour ajouter des marqueurs
</div>
</div>
</aside>
</div>
</template>
<script>
@ -95,9 +94,7 @@ export default {
],
setup(props) {
const canAddMarker = computed(
() => props.markers.length < props.maxMarkers
);
const canAddMarker = computed(() => props.markers.length < props.maxMarkers);
return {
canAddMarker,
@ -107,130 +104,143 @@ export default {
</script>
<style scoped>
/* Sidebar container - uses Kirby's layout system */
.k-map-markers-sidebar {
width: var(--marker-list-width, 280px);
.marker-list-sidebar {
width: var(--marker-list-width, 250px);
flex-shrink: 0;
border-right: 1px solid var(--color-border);
display: flex;
flex-direction: column;
border-right: 1px solid var(--color-border);
background: var(--color-white);
background: var(--color-background);
}
/* Header - minimal override of k-section-header */
.k-section-header {
.marker-list-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-2);
padding: var(--spacing-3);
gap: 0.5rem;
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--color-border);
background: var(--panel-color-back);
margin-bottom: 0;
}
.k-section-header .k-headline {
display: flex;
align-items: center;
gap: var(--spacing-2);
}
/* Search container */
.k-map-markers-search {
padding: var(--spacing-3);
border-bottom: 1px solid var(--color-border);
background: var(--color-background);
}
/* List container */
.k-map-markers-list {
flex: 1;
overflow-y: auto;
padding: var(--spacing-2);
background: var(--color-background);
}
/* Marker item - styled like Kirby's k-item */
.k-map-marker-item {
display: flex;
align-items: center;
gap: var(--spacing-2);
padding: var(--spacing-2);
margin-bottom: var(--spacing-1);
background: var(--color-white);
border-radius: var(--rounded);
cursor: pointer;
transition: all 0.2s;
}
.k-map-marker-item:hover {
background: var(--color-gray-200);
.marker-list-header h3 {
margin: 0;
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
color: var(--color-text-light);
flex: 1;
}
.k-map-marker-item.is-selected {
background: var(--color-blue-300);
color: var(--color-white);
}
.k-map-marker-icon {
.marker-list-header .k-button-icon {
width: 2rem;
height: 2rem;
min-width: 2rem;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
border-radius: 50%;
background: var(--color-gray-300);
color: var(--color-black);
}
.marker-list-header .k-button-icon .k-icon {
color: var(--color-black);
}
.marker-list-header .k-button-icon:hover {
background: var(--color-background);
}
.marker-list-header .k-button-icon:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.geocode-search-container {
padding: 0.75rem;
border-bottom: 1px solid var(--color-border);
background: var(--color-white);
}
.marker-list-items {
flex: 1;
overflow-y: auto;
padding: 0.5rem;
}
.marker-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem;
margin-bottom: 0.5rem;
background: var(--color-white);
border: 1px solid var(--color-border);
border-radius: var(--rounded-sm);
cursor: pointer;
transition: all 0.2s;
color: #000;
}
.marker-item:hover {
background: var(--color-background);
border-color: var(--color-focus);
color: #fff;
}
.marker-item.active {
background: var(--color-focus-outline);
border-color: var(--color-focus);
}
.marker-item-content {
display: flex;
align-items: center;
gap: 0.5rem;
flex: 1;
min-width: 0;
}
.marker-number {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
background: var(--color-gray-400);
color: var(--color-white);
border-radius: 50%;
font-size: 0.75rem;
font-weight: 600;
flex-shrink: 0;
}
.k-map-marker-item.is-selected .k-map-marker-icon {
background: var(--color-blue-600);
.marker-item.active .marker-number {
background: var(--color-focus);
}
.k-map-marker-text {
flex: 1;
min-width: 0;
.marker-title {
font-size: 0.875rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: var(--text-sm);
}
.k-map-marker-options {
.marker-item-actions {
display: flex;
gap: var(--spacing-1);
gap: 0.25rem;
flex-shrink: 0;
}
/* Empty state - styled like Kirby's k-empty */
.k-map-markers-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: var(--spacing-12) var(--spacing-6);
.k-button-small {
padding: 0.25rem 0.5rem;
min-height: auto;
}
.marker-list-empty {
padding: 2rem 1rem;
text-align: center;
color: var(--color-gray-600);
}
.k-map-markers-empty .k-icon {
width: 3rem;
height: 3rem;
margin-bottom: var(--spacing-3);
opacity: 0.25;
}
.k-map-markers-empty-text {
margin: 0 0 var(--spacing-2);
font-size: var(--text-base);
font-weight: 500;
color: var(--color-gray-800);
}
.k-map-markers-empty-info {
margin: 0;
font-size: var(--text-sm);
color: var(--color-gray-600);
color: var(--color-text-light);
font-size: 0.875rem;
}
</style>