Skip to content

Commit dd783de

Browse files
committed
evaluate visibility expression
maplibre/maplibre-gl-js#6495
1 parent e7f3ae0 commit dd783de

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-0
lines changed

src/expression/visibility.test.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import createVisibilityExpression from './visibility';
2+
import {describe, test, expect, vi} from 'vitest';
3+
4+
describe('evaluate visibility expression', () => {
5+
test('literal values', () => {
6+
vi.spyOn(console, 'warn').mockImplementation(() => {});
7+
8+
expect(createVisibilityExpression('none', {}).evaluate()).toBe('none');
9+
expect(console.warn).not.toHaveBeenCalled();
10+
11+
expect(createVisibilityExpression('visible', {}).evaluate()).toBe('visible');
12+
expect(console.warn).not.toHaveBeenCalled();
13+
});
14+
15+
test('global state property as visibility expression', () => {
16+
const globalState = {};
17+
const value = createVisibilityExpression(['global-state', 'x'], globalState);
18+
19+
vi.spyOn(console, 'warn').mockImplementation(() => {});
20+
21+
globalState.x = 'none';
22+
expect(value.evaluate()).toBe('none');
23+
expect(console.warn).not.toHaveBeenCalled();
24+
25+
globalState.x = 'visible';
26+
expect(value.evaluate()).toBe('visible');
27+
expect(console.warn).not.toHaveBeenCalled();
28+
});
29+
30+
test('global state flag as visibility expression', () => {
31+
const globalState = {};
32+
const value = createVisibilityExpression(['case', ['global-state', 'x'], 'visible', 'none'], globalState);
33+
34+
vi.spyOn(console, 'warn').mockImplementation(() => {});
35+
36+
globalState.x = false;
37+
expect(value.evaluate()).toBe('none');
38+
expect(console.warn).not.toHaveBeenCalled();
39+
40+
globalState.x = true;
41+
expect(value.evaluate()).toBe('visible');
42+
expect(console.warn).not.toHaveBeenCalled();
43+
});
44+
45+
test('warns and falls back to default for invalid expression', () => {
46+
const value = createVisibilityExpression(['get', 'x'], {});
47+
48+
vi.spyOn(console, 'warn').mockImplementation(() => {});
49+
50+
expect(value.evaluate()).toBe('visible');
51+
expect(console.warn).toHaveBeenCalledWith('Expected value to be of type string, but found null instead.');
52+
});
53+
54+
test('warns and falls back to default for missing global property', () => {
55+
const value = createVisibilityExpression(['global-state', 'x'], {});
56+
57+
vi.spyOn(console, 'warn').mockImplementation(() => {});
58+
59+
expect(value.evaluate()).toBe('visible');
60+
expect(console.warn).toHaveBeenCalledWith('Expected value to be of type string, but found null instead.');
61+
});
62+
63+
test('warns and falls back to default for invalid global property', () => {
64+
const value = createVisibilityExpression(['global-state', 'x'], {x: 'invalid'});
65+
66+
vi.spyOn(console, 'warn').mockImplementation(() => {});
67+
68+
expect(value.evaluate()).toBe('visible');
69+
expect(console.warn).toHaveBeenCalledWith('Expected value to be one of "visible", "none", but found "invalid" instead.');
70+
});
71+
});

src/expression/visibility.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {createExpression, findGlobalStateRefs} from '.';
2+
import {type GlobalProperties, type StylePropertySpecification} from '..';
3+
import {type VisibilitySpecification} from '../types.g';
4+
5+
const visibilitySpec: StylePropertySpecification = {
6+
type: 'enum',
7+
'property-type': 'data-constant',
8+
expression: {
9+
interpolated: false,
10+
parameters: ['global-state']
11+
},
12+
values: {visible: {}, none: {}},
13+
transition: false,
14+
default: 'visible'
15+
};
16+
17+
export default function createVisibility(visibility: VisibilitySpecification, globalState: Record<string, any>) {
18+
const expression = {
19+
setValue,
20+
evaluate: null
21+
};
22+
setValue(visibility);
23+
return expression;
24+
25+
function setValue(visibility: VisibilitySpecification) {
26+
if (visibility === null || visibility === undefined || visibility === 'visible' || visibility === 'none') {
27+
expression.evaluate = visibility === 'none' ? () => 'none' : () => 'visible';
28+
addGlobalStateRefs(expression);
29+
return;
30+
}
31+
32+
const compiled = createExpression(visibility, visibilitySpec, globalState);
33+
if (compiled.result === 'error') {
34+
throw new Error(compiled.value.map(err => `${err.key}: ${err.message}`).join(', '));
35+
}
36+
expression.evaluate = () => compiled.value.evaluate({} as GlobalProperties);
37+
addGlobalStateRefs(expression, () => findGlobalStateRefs(compiled.value.expression));
38+
}
39+
}
40+
41+
function addGlobalStateRefs(visibility, getGlobalStateRefs: () => Set<string> = () => new Set<string>()) {
42+
visibility.getGlobalStateRefs = getGlobalStateRefs;
43+
}

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {validate} from './validate/validate';
4242
import {migrate} from './migrate';
4343
import {classifyRings} from './util/classify_rings';
4444
import {ProjectionDefinition} from './expression/types/projection_definition';
45+
import createVisibilityExpression from './expression/visibility';
4546

4647
type ExpressionType = 'data-driven' | 'cross-faded' | 'cross-faded-data-driven' | 'color-ramp' | 'data-constant' | 'constant';
4748
type ExpressionParameters = Array<'zoom' | 'feature' | 'feature-state' | 'heatmap-density' | 'elevation' | 'line-progress' | 'global-state'>;
@@ -203,6 +204,7 @@ export {
203204
createExpression,
204205
isFunction, createFunction,
205206
createPropertyExpression,
207+
createVisibilityExpression,
206208
convertFilter,
207209
featureFilter,
208210
typeOf,

0 commit comments

Comments
 (0)