Skip to content

Commit

Permalink
tooltip combined positions, tooltip offset
Browse files Browse the repository at this point in the history
  • Loading branch information
matthew44-mappable committed Apr 15, 2024
1 parent 5dd7675 commit e96e628
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 26 deletions.
2 changes: 1 addition & 1 deletion example/default-tooltip/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import type {MMapLocationRequest, LngLat} from '@mappable-world/mappable-types';

export const CENTER: LngLat = [55.442795, 25.24107];
export const LOCATION: MMapLocationRequest = {center: CENTER, zoom: 14};
export const TOOLTIP_TEXT = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse dictum';
export const TOOLTIP_TEXT = 'Default tooltip';
8 changes: 8 additions & 0 deletions example/default-tooltip/react/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@ async function main() {
const [position, setPosition] = useState<MMapTooltipMarkerProps['position']>(undefined);

const positionLeft = useCallback(() => setPosition('left'), []);
const positionLeftTop = useCallback(() => setPosition('left top'), []);
const positionLeftBottom = useCallback(() => setPosition('left bottom'), []);
const positionBottom = useCallback(() => setPosition('bottom'), []);
const positionTop = useCallback(() => setPosition('top'), []);
const positionRightTop = useCallback(() => setPosition('right top'), []);
const positionRightBottom = useCallback(() => setPosition('right bottom'), []);
const positionRight = useCallback(() => setPosition('right'), []);

return (
Expand All @@ -37,8 +41,12 @@ async function main() {
<MMapDefaultFeaturesLayer />
<MMapControls position="top right">
<MMapControlButton text="Left" onClick={positionLeft} />
<MMapControlButton text="Left Top" onClick={positionLeftTop} />
<MMapControlButton text="Left Bottom" onClick={positionLeftBottom} />
<MMapControlButton text="Bottom" onClick={positionBottom} />
<MMapControlButton text="Top" onClick={positionTop} />
<MMapControlButton text="Right Top" onClick={positionRightTop} />
<MMapControlButton text="Right Bottom" onClick={positionRightBottom} />
<MMapControlButton text="Right" onClick={positionRight} />
</MMapControls>
<MMapTooltipMarker coordinates={CENTER} draggable text={TOOLTIP_TEXT} position={position} />
Expand Down
4 changes: 4 additions & 0 deletions example/default-tooltip/vanilla/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ async function main() {
map.addChild(
new MMapControls({position: 'top right'}, [
new MMapControlButton({text: 'Left', onClick: () => tooltip.update({position: 'left'})}),
new MMapControlButton({text: 'Left Top', onClick: () => tooltip.update({position: 'left top'})}),
new MMapControlButton({text: 'Left Bottom', onClick: () => tooltip.update({position: 'left bottom'})}),
new MMapControlButton({text: 'Bottom', onClick: () => tooltip.update({position: 'bottom'})}),
new MMapControlButton({text: 'Top', onClick: () => tooltip.update({position: 'top'})}),
new MMapControlButton({text: 'Right Top', onClick: () => tooltip.update({position: 'right top'})}),
new MMapControlButton({text: 'Right Bottom', onClick: () => tooltip.update({position: 'right bottom'})}),
new MMapControlButton({text: 'Right', onClick: () => tooltip.update({position: 'right'})})
])
);
Expand Down
12 changes: 12 additions & 0 deletions example/default-tooltip/vue/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,12 @@ async function main() {
const position = Vue.ref<MMapTooltipMarkerProps['position']>(undefined);

const positionLeft = () => (position.value = 'left');
const positionLeftTop = () => (position.value = 'left top');
const positionLeftBottom = () => (position.value = 'left bottom');
const positionBottom = () => (position.value = 'bottom');
const positionTop = () => (position.value = 'top');
const positionRightTop = () => (position.value = 'right top');
const positionRightBottom = () => (position.value = 'right bottom');
const positionRight = () => (position.value = 'right');

return {
Expand All @@ -40,8 +44,12 @@ async function main() {
position,
refMap,
positionLeft,
positionLeftTop,
positionLeftBottom,
positionBottom,
positionTop,
positionRightTop,
positionRightBottom,
positionRight
};
},
Expand All @@ -51,8 +59,12 @@ async function main() {
<MMapDefaultFeaturesLayer />
<MMapControls position="top right">
<MMapControlButton text="Left" :onClick="positionLeft" />
<MMapControlButton text="Left Top" :onClick="positionLeftTop" />
<MMapControlButton text="Left Bottom" :onClick="positionLeftBottom" />
<MMapControlButton text="Bottom" :onClick="positionBottom" />
<MMapControlButton text="Top" :onClick="positionTop" />
<MMapControlButton text="Right Top" :onClick="positionRightTop" />
<MMapControlButton text="Right Bottom" :onClick="positionRightBottom" />
<MMapControlButton text="Right" :onClick="positionRight" />
</MMapControls>
<MMapTooltipMarker :coordinates="CENTER" :draggable="true" :text="TOOLTIP_TEXT" :position="position" />
Expand Down
92 changes: 80 additions & 12 deletions src/markers/MMapTooltipMarker/index.css
Original file line number Diff line number Diff line change
@@ -1,62 +1,130 @@
.mappable--tooltip-marker {
--tail-height: 12px;
--tail-width: 16px;
--border-radius: 12px;

--tail-height-and-offset: calc(var(--tail-height) + var(--offset));

--tooltip-tail-transform-top: translate(-50%, calc(-100% - var(--offset))) rotate(180deg);
--tooltip-tail-transform-bottom: translate(-50%, var(--offset));
}

.mappable--tooltip-marker svg {
display: block;
}

.mappable--tooltip-marker_container {
box-sizing: border-box;
width: max-content;
max-width: 500px;
max-height: 600px;
display: block;
position: absolute;
padding: 8px 12px;
border-radius: 12px;
border-radius: var(--border-radius);
background-color: #fff;
color: #34374a;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
box-shadow: 0px 4px 12px 0px #5f69831a;
box-shadow:
0px 4px 12px 0px #5f69831a,
0px 4px 24px 0px #5f69830a;
overflow: hidden;
text-overflow: ellipsis;
}

.mappable--tooltip-marker_tail {
display: block;
position: absolute;
color: #fff;
box-shadow: 0px 4px 24px 0px #5f69830a;

svg {
filter: drop-shadow(0px 4px 24px rgba(95, 105, 131, 0.04)) drop-shadow(0px 4px 12px rgba(95, 105, 131, 0.1));
}
}

/* positions */
.position-top {
.mappable--tooltip-marker_container {
transform: translate(-50%, calc(-100% - var(--tail-height)));
transform: translate(-50%, calc(-100% - var(--tail-height-and-offset)));
}
.mappable--tooltip-marker_tail {
transform: translate(-50%, -100%) rotate(180deg);
transform: var(--tooltip-tail-transform-top);
}
/* top left */
&.position-left {
.mappable--tooltip-marker_container {
transform: translate(
calc(-100% + var(--border-radius) + var(--tail-width) / 2),
calc(-100% - var(--tail-height-and-offset))
);
}
.mappable--tooltip-marker_tail {
transform: var(--tooltip-tail-transform-top);
}
}
/* top right */
&.position-right {
.mappable--tooltip-marker_container {
transform: translate(
calc(-1 * var(--border-radius) - var(--tail-width) / 2),
calc(-100% - var(--tail-height-and-offset))
);
}
.mappable--tooltip-marker_tail {
transform: var(--tooltip-tail-transform-top);
}
}
}

.position-bottom {
.mappable--tooltip-marker_container {
transform: translate(-50%, var(--tail-height));
transform: translate(-50%, var(--tail-height-and-offset));
}
.mappable--tooltip-marker_tail {
transform: translate(-50%, 0);
transform: var(--tooltip-tail-transform-bottom);
}
/* bottom left */
&.position-left {
.mappable--tooltip-marker_container {
transform: translate(
calc(-100% + var(--border-radius) + var(--tail-width) / 2),
var(--tail-height-and-offset)
);
}
.mappable--tooltip-marker_tail {
transform: var(--tooltip-tail-transform-bottom);
}
}
/* bottom right */
&.position-right {
.mappable--tooltip-marker_container {
transform: translate(
calc(-1 * var(--border-radius) - var(--tail-width) / 2),
var(--tail-height-and-offset)
);
}
.mappable--tooltip-marker_tail {
transform: var(--tooltip-tail-transform-bottom);
}
}
}

.position-left {
.mappable--tooltip-marker_container {
transform: translate(calc(-100% - var(--tail-height)), -50%);
transform: translate(calc(-100% - var(--tail-height-and-offset)), -50%);
}
.mappable--tooltip-marker_tail {
transform: translate(-100%, -50%) rotate(90deg);
transform: translate(calc(-100% - var(--offset)), -50%) rotate(90deg);
}
}

.position-right {
.mappable--tooltip-marker_container {
transform: translate(var(--tail-height), -50%);
transform: translate(var(--tail-height-and-offset), -50%);
}
.mappable--tooltip-marker_tail {
transform: translate(0, -50%) rotate(-90deg);
transform: translate(var(--offset), -50%) rotate(-90deg);
}
}
59 changes: 48 additions & 11 deletions src/markers/MMapTooltipMarker/index.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
import {MMapMarker, MMapMarkerProps} from '@mappable-world/mappable-types';
import {MMapMarkerVuefyOptions} from './vue';
import {MMapTooltipMarkerVuefyOptions} from './vue';

import './index.css';
import tailSVG from './tail.svg';

type VerticalPosition = 'top' | 'bottom';
type HorizontalPosition = 'left' | 'right';
export type MMapTooltipPositionProps = VerticalPosition | HorizontalPosition;
export type MMapTooltipPositionProps =
| VerticalPosition
| HorizontalPosition
| `${VerticalPosition} ${HorizontalPosition}`
| `${HorizontalPosition} ${VerticalPosition}`;

export type MMapTooltipMarkerProps = MMapMarkerProps & {
text: string;
position?: MMapTooltipPositionProps;
offset?: number;
};

const defaultProps = Object.freeze({position: 'top'});
const defaultProps = Object.freeze({position: 'top', offset: 0});
type DefaultProps = typeof defaultProps;

export class MMapTooltipMarker extends mappable.MMapComplexEntity<MMapTooltipMarkerProps, DefaultProps> {
static defaultProps = defaultProps;
static [mappable.optionsKeyVuefy] = MMapMarkerVuefyOptions;
static [mappable.optionsKeyVuefy] = MMapTooltipMarkerVuefyOptions;

private _markerElement: HTMLElement;
private _tooltipContainer: HTMLElement;
Expand All @@ -41,7 +46,8 @@ export class MMapTooltipMarker extends mappable.MMapComplexEntity<MMapTooltipMar
this._tooltipTail.classList.add('mappable--tooltip-marker_tail');
this._tooltipTail.innerHTML = tailSVG;

this._updateElementPosition();
this._updatePosition();
this._updateOffset();

this._markerElement.appendChild(this._tooltipContainer);
this._markerElement.appendChild(this._tooltipTail);
Expand All @@ -52,7 +58,10 @@ export class MMapTooltipMarker extends mappable.MMapComplexEntity<MMapTooltipMar

protected _onUpdate(propsDiff: Partial<MMapTooltipMarkerProps>): void {
if (propsDiff.position !== undefined) {
this._updateElementPosition();
this._updatePosition();
}
if (propsDiff.offset !== undefined) {
this._updateOffset();
}

if (propsDiff.text !== undefined) {
Expand All @@ -62,19 +71,47 @@ export class MMapTooltipMarker extends mappable.MMapComplexEntity<MMapTooltipMar
this._marker.update(this._props);
}

private _updateElementPosition() {
private _updateOffset(): void {
this._markerElement.style.setProperty('--offset', `${this._props.offset}px`);
}

private _updatePosition(): void {
const {position} = this._props;
let verticalPosition: VerticalPosition;
let horizontalPosition: HorizontalPosition;

const positionTypeHash: Record<HorizontalPosition | VerticalPosition, 'horizontal' | 'vertical'> = {
top: 'vertical',
left: 'horizontal',
bottom: 'vertical',
right: 'horizontal'
};

if (position === 'top' || position === 'bottom') {
verticalPosition = position;
} else if (position === 'left' || position === 'right') {
horizontalPosition = position;
} else {
const [first, second] = position.split(' ') as (HorizontalPosition | VerticalPosition)[];
if (positionTypeHash[first] === 'vertical' && positionTypeHash[second] === 'horizontal') {
verticalPosition = first as VerticalPosition;
horizontalPosition = second as HorizontalPosition;
} else if (positionTypeHash[first] === 'horizontal' && positionTypeHash[second] === 'vertical') {
verticalPosition = second as VerticalPosition;
horizontalPosition = first as HorizontalPosition;
}
}

// check top position
this._markerElement.classList.toggle('position-top', position === 'top');
this._markerElement.classList.toggle('position-top', verticalPosition === 'top');

// check bottom position
this._markerElement.classList.toggle('position-bottom', position === 'bottom');
this._markerElement.classList.toggle('position-bottom', verticalPosition === 'bottom');

// check left position
this._markerElement.classList.toggle('position-left', position === 'left');
this._markerElement.classList.toggle('position-left', horizontalPosition === 'left');

// check right position
this._markerElement.classList.toggle('position-right', position === 'right');
this._markerElement.classList.toggle('position-right', horizontalPosition === 'right');
}
}
5 changes: 3 additions & 2 deletions src/markers/MMapTooltipMarker/vue/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {CustomVuefyOptions} from '@mappable-world/mappable-types/modules/vuefy';
import type TVue from '@vue/runtime-core';
import {MMapTooltipMarker, MMapTooltipPositionProps} from '../';

export const MMapMarkerVuefyOptions: CustomVuefyOptions<MMapTooltipMarker> = {
export const MMapTooltipMarkerVuefyOptions: CustomVuefyOptions<MMapTooltipMarker> = {
props: {
coordinates: {type: Object, required: true},
source: String,
Expand All @@ -23,6 +23,7 @@ export const MMapMarkerVuefyOptions: CustomVuefyOptions<MMapTooltipMarker> = {
onClick: Function as TVue.PropType<MMapFeatureProps['onClick']>,
onFastClick: Function as TVue.PropType<MMapFeatureProps['onFastClick']>,
text: {type: String, required: true},
position: {type: String as TVue.PropType<MMapTooltipPositionProps>}
position: {type: String as TVue.PropType<MMapTooltipPositionProps>},
offset: {type: Number, default: 0}
}
};

0 comments on commit e96e628

Please sign in to comment.