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 label: Position sur la carte
type: map-editor type: map-editor
mode: single 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> </template>
<script> <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 MapPreview from '../map/MapPreview.vue';
import MarkerList from '../map/MarkerList.vue'; import MarkerList from '../map/MarkerList.vue';
import { useMarkersApi } from '../../composables/useMarkersApi.js'; import { useMarkersApi } from '../../composables/useMarkersApi.js';
@ -191,7 +191,6 @@ export default {
// Watch for changes in the form fields // Watch for changes in the form fields
const form = document.querySelector('.k-form'); const form = document.querySelector('.k-form');
if (form) { if (form) {
// Listen to input events
form.addEventListener('input', (e) => { form.addEventListener('input', (e) => {
if (e.target.name && (e.target.name.includes('latitude') || e.target.name.includes('longitude'))) { if (e.target.name && (e.target.name.includes('latitude') || e.target.name.includes('longitude'))) {
const newCoords = getCoordinatesFromForm(); const newCoords = getCoordinatesFromForm();
@ -199,39 +198,6 @@ export default {
singleLon.value = newCoords.lon; 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') { if (props.mode === 'single') {
// Center map on new position if valid // Center map on new position if valid
if (!isNaN(lat) && !isNaN(lon) && lat !== null && lon !== null && lat !== 0 && lon !== 0) { if (!isNaN(lat) && !isNaN(lon) && lat !== null && lon !== null && lat !== 0 && lon !== 0) {
// Force immediate reactivity if (mapPreview.value && mapPreview.value.centerOnPosition) {
nextTick(() => { mapPreview.value.centerOnPosition(lat, lon);
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);
}
});
} }
} }
} }

View file

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

View file

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

View file

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