Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle class name change #2483

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion modules/react-mapbox/src/components/marker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {MarkerEvent, MarkerDragEvent} from '../types/events';

import {MapContext} from './map';
import {arePointsEqual} from '../utils/deep-equal';
import {compareClassNames} from '../utils/compare-class-names';

export type MarkerProps = MarkerOptions & {
/** Longitude of the anchor location */
Expand All @@ -32,7 +33,6 @@ export const Marker = memo(
forwardRef((props: MarkerProps, ref: React.Ref<MarkerInstance>) => {
const {map, mapLib} = useContext(MapContext);
const thisRef = useRef({props});
thisRef.current.props = props;

const marker: MarkerInstance = useMemo(() => {
let hasChildren = false;
Expand Down Expand Up @@ -102,6 +102,7 @@ export const Marker = memo(

useImperativeHandle(ref, () => marker, []);

const oldProps = thisRef.current.props;
if (marker.getLngLat().lng !== longitude || marker.getLngLat().lat !== latitude) {
marker.setLngLat([longitude, latitude]);
}
Expand All @@ -123,7 +124,14 @@ export const Marker = memo(
if (marker.getPopup() !== popup) {
marker.setPopup(popup);
}
const classNameDiff = compareClassNames(oldProps.className, props.className);
if (classNameDiff) {
for (const c of classNameDiff) {
marker.toggleClassName(c);
}
}

thisRef.current.props = props;
return createPortal(props.children, marker.getElement());
})
);
33 changes: 10 additions & 23 deletions modules/react-mapbox/src/components/popup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {PopupEvent} from '../types/events';

import {MapContext} from './map';
import {deepEqual} from '../utils/deep-equal';
import {compareClassNames} from '../utils/compare-class-names';

export type PopupProps = PopupOptions & {
/** Longitude of the anchor location */
Expand All @@ -24,11 +25,6 @@ export type PopupProps = PopupOptions & {
children?: React.ReactNode;
};

// Adapted from https://github.com/mapbox/mapbox-gl-js/blob/v1.13.0/src/ui/popup.js
function getClassList(className: string) {
return new Set(className ? className.trim().split(/\s+/) : []);
}

/* eslint-disable complexity,max-statements */
export const Popup = memo(
forwardRef((props: PopupProps, ref: React.Ref<PopupInstance>) => {
Expand All @@ -37,7 +33,6 @@ export const Popup = memo(
return document.createElement('div');
}, []);
const thisRef = useRef({props});
thisRef.current.props = props;

const popup: PopupInstance = useMemo(() => {
const options = {...props};
Expand Down Expand Up @@ -75,32 +70,24 @@ export const Popup = memo(
useImperativeHandle(ref, () => popup, []);

if (popup.isOpen()) {
const oldProps = thisRef.current.props;
if (popup.getLngLat().lng !== props.longitude || popup.getLngLat().lat !== props.latitude) {
popup.setLngLat([props.longitude, props.latitude]);
}
if (props.offset && !deepEqual(popup.options.offset, props.offset)) {
if (props.offset && !deepEqual(oldProps.offset, props.offset)) {
popup.options.anchor = props.anchor;
popup.setOffset(props.offset);
}
if (popup.options.anchor !== props.anchor || popup.options.maxWidth !== props.maxWidth) {
popup.options.anchor = props.anchor;
if (oldProps.anchor !== props.anchor || oldProps.maxWidth !== props.maxWidth) {
popup.setMaxWidth(props.maxWidth);
}
if (popup.options.className !== props.className) {
const prevClassList = getClassList(popup.options.className);
const nextClassList = getClassList(props.className);

for (const c of prevClassList) {
if (!nextClassList.has(c)) {
popup.removeClassName(c);
}
}
for (const c of nextClassList) {
if (!prevClassList.has(c)) {
popup.addClassName(c);
}
const classNameDiff = compareClassNames(oldProps.className, props.className);
if (classNameDiff) {
for (const c of classNameDiff) {
popup.toggleClassName(c);
}
popup.options.className = props.className;
}
thisRef.current.props = props;
}

return createPortal(props.children, container);
Expand Down
29 changes: 29 additions & 0 deletions modules/react-mapbox/src/utils/compare-class-names.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/** Compare two classNames string and return the difference */
export function compareClassNames(
prevClassName: string | undefined,
nextClassName: string | undefined
): string[] | null {
if (prevClassName === nextClassName) {
return null;
}

const prevClassList = getClassList(prevClassName);
const nextClassList = getClassList(nextClassName);
const diff: string[] = [];

for (const c of nextClassList) {
if (!prevClassList.has(c)) {
diff.push(c);
}
}
for (const c of prevClassList) {
if (!nextClassList.has(c)) {
diff.push(c);
}
}
return diff.length === 0 ? null : diff;
}

function getClassList(className: string | undefined) {
return new Set(className ? className.trim().split(/\s+/) : []);
}
2 changes: 2 additions & 0 deletions modules/react-mapbox/test/components/marker.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ test('Marker', async t => {
offset={[0, 1]}
rotation={30}
draggable
className="classA"
pitchAlignment="map"
rotationAlignment="map"
onDragStart={() => (callbackType = 'dragstart')}
Expand All @@ -64,6 +65,7 @@ test('Marker', async t => {
t.not(rotation, marker.getRotation(), 'rotation is updated');
t.not(pitchAlignment, marker.getPitchAlignment(), 'pitchAlignment is updated');
t.not(rotationAlignment, marker.getRotationAlignment(), 'rotationAlignment is updated');
t.ok(marker._element.classList.contains('classA'), 'className is updated');

marker.fire('dragstart');
t.is(callbackType, 'dragstart', 'onDragStart called');
Expand Down
3 changes: 1 addition & 2 deletions modules/react-mapbox/test/components/popup.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ test('Popup', async t => {
</Map>
);
await sleep(1);

t.is(popup.options.className, 'classA', 'className is updated');
t.ok(popup._container.classList.contains('classA'), 'className is updated');

root.unmount();
t.end();
Expand Down
52 changes: 52 additions & 0 deletions modules/react-mapbox/test/utils/compare-class-names.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import test from 'tape-promise/tape';
import {compareClassNames} from '@vis.gl/react-mapbox/utils/compare-class-names';

test('compareClassNames', t => {
const TEST_CASES = [
{
title: 'Empty class names',
prevClassName: '',
nextClassName: '',
output: null
},
{
title: 'Identical class names',
prevClassName: 'marker active',
nextClassName: 'active marker ',
output: null
},
{
title: 'Addition',
prevClassName: undefined,
nextClassName: 'marker',
output: ['marker']
},
{
title: 'Addition',
prevClassName: 'marker',
nextClassName: 'marker active',
output: ['active']
},
{
title: 'Removal',
prevClassName: 'marker active',
nextClassName: 'marker',
output: ['active']
},
{
title: 'Multiple addition & removal',
prevClassName: 'marker active',
nextClassName: 'marker hovered hidden',
output: ['hovered', 'hidden', 'active']
}
];

for (const testCase of TEST_CASES) {
t.deepEqual(
compareClassNames(testCase.prevClassName, testCase.nextClassName),
testCase.output,
testCase.title
);
}
t.end();
});
1 change: 1 addition & 0 deletions modules/react-mapbox/test/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ import './deep-equal.spec';
import './transform.spec';
import './style-utils.spec';
import './apply-react-style.spec';
import './compare-class-names.spec';
10 changes: 9 additions & 1 deletion modules/react-maplibre/src/components/marker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {MarkerEvent, MarkerDragEvent} from '../types/events';

import {MapContext} from './map';
import {arePointsEqual} from '../utils/deep-equal';
import {compareClassNames} from '../utils/compare-class-names';

export type MarkerProps = MarkerOptions & {
/** Longitude of the anchor location */
Expand All @@ -32,7 +33,6 @@ export const Marker = memo(
forwardRef((props: MarkerProps, ref: React.Ref<MarkerInstance>) => {
const {map, mapLib} = useContext(MapContext);
const thisRef = useRef({props});
thisRef.current.props = props;

const marker: MarkerInstance = useMemo(() => {
let hasChildren = false;
Expand Down Expand Up @@ -102,6 +102,7 @@ export const Marker = memo(

useImperativeHandle(ref, () => marker, []);

const oldProps = thisRef.current.props;
if (marker.getLngLat().lng !== longitude || marker.getLngLat().lat !== latitude) {
marker.setLngLat([longitude, latitude]);
}
Expand All @@ -123,7 +124,14 @@ export const Marker = memo(
if (marker.getPopup() !== popup) {
marker.setPopup(popup);
}
const classNameDiff = compareClassNames(oldProps.className, props.className);
if (classNameDiff) {
for (const c of classNameDiff) {
marker.toggleClassName(c);
}
}

thisRef.current.props = props;
return createPortal(props.children, marker.getElement());
})
);
31 changes: 9 additions & 22 deletions modules/react-maplibre/src/components/popup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import {MapContext} from './map';
import {deepEqual} from '../utils/deep-equal';
import {compareClassNames} from '../utils/compare-class-names';

export type PopupProps = PopupOptions & {
/** Longitude of the anchor location */
Expand All @@ -24,11 +25,6 @@
children?: React.ReactNode;
};

// Adapted from https://github.com/mapbox/mapbox-gl-js/blob/v1.13.0/src/ui/popup.js
function getClassList(className: string) {
return new Set(className ? className.trim().split(/\s+/) : []);
}

/* eslint-disable complexity,max-statements */
export const Popup = memo(
forwardRef((props: PopupProps, ref: React.Ref<PopupInstance>) => {
Expand All @@ -37,13 +33,12 @@
return document.createElement('div');
}, []);
const thisRef = useRef({props});
thisRef.current.props = props;

const popup: PopupInstance = useMemo(() => {
const options = {...props};
const pp = new mapLib.Popup(options);
pp.setLngLat([props.longitude, props.latitude]);
pp.once('open', e => {

Check warning on line 41 in modules/react-maplibre/src/components/popup.ts

View workflow job for this annotation

GitHub Actions / test-node

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
thisRef.current.props.onOpen?.(e as PopupEvent);
});
return pp;
Expand Down Expand Up @@ -75,32 +70,24 @@
useImperativeHandle(ref, () => popup, []);

if (popup.isOpen()) {
const oldProps = thisRef.current.props;
if (popup.getLngLat().lng !== props.longitude || popup.getLngLat().lat !== props.latitude) {
popup.setLngLat([props.longitude, props.latitude]);
}
if (props.offset && !deepEqual(popup.options.offset, props.offset)) {
if (props.offset && !deepEqual(oldProps.offset, props.offset)) {
popup.setOffset(props.offset);
}
if (popup.options.anchor !== props.anchor || popup.options.maxWidth !== props.maxWidth) {
if (oldProps.anchor !== props.anchor || oldProps.maxWidth !== props.maxWidth) {
popup.options.anchor = props.anchor;
popup.setMaxWidth(props.maxWidth);
}
if (popup.options.className !== props.className) {
const prevClassList = getClassList(popup.options.className);
const nextClassList = getClassList(props.className);

for (const c of prevClassList) {
if (!nextClassList.has(c)) {
popup.removeClassName(c);
}
}
for (const c of nextClassList) {
if (!prevClassList.has(c)) {
popup.addClassName(c);
}
const classNameDiff = compareClassNames(oldProps.className, props.className);
if (classNameDiff) {
for (const c of classNameDiff) {
popup.toggleClassName(c);
}
popup.options.className = props.className;
}
thisRef.current.props = props;
}

return createPortal(props.children, container);
Expand Down
29 changes: 29 additions & 0 deletions modules/react-maplibre/src/utils/compare-class-names.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/** Compare two classNames string and return the difference */
export function compareClassNames(
prevClassName: string | undefined,
nextClassName: string | undefined
): string[] | null {
if (prevClassName === nextClassName) {
return null;
}

const prevClassList = getClassList(prevClassName);
const nextClassList = getClassList(nextClassName);
const diff: string[] = [];

for (const c of nextClassList) {
if (!prevClassList.has(c)) {
diff.push(c);
}
}
for (const c of prevClassList) {
if (!nextClassList.has(c)) {
diff.push(c);
}
}
return diff.length === 0 ? null : diff;
}

function getClassList(className: string | undefined) {
return new Set(className ? className.trim().split(/\s+/) : []);
}
2 changes: 2 additions & 0 deletions modules/react-maplibre/test/components/marker.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ test('Marker', async t => {
offset={[0, 1]}
rotation={30}
draggable
className="classA"
pitchAlignment="viewport"
rotationAlignment="viewport"
onDragStart={() => (callbackType = 'dragstart')}
Expand All @@ -63,6 +64,7 @@ test('Marker', async t => {
t.not(rotation, marker.getRotation(), 'rotation is updated');
t.not(pitchAlignment, marker.getPitchAlignment(), 'pitchAlignment is updated');
t.not(rotationAlignment, marker.getRotationAlignment(), 'rotationAlignment is updated');
t.ok(marker._element.classList.contains('classA'), 'className is updated');

marker.fire('dragstart');
t.is(callbackType, 'dragstart', 'onDragStart called');
Expand Down
3 changes: 1 addition & 2 deletions modules/react-maplibre/test/components/popup.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ test('Popup', async t => {
</Map>
);
await sleep(1);

t.is(popup.options.className, 'classA', 'className is updated');
t.ok(popup._container.classList.contains('classA'), 'className is updated');

root.unmount();
t.end();
Expand Down
Loading
Loading