Skip to content
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
4 changes: 3 additions & 1 deletion examples/get-started/pure-js/widgets/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
CompassWidget,
ZoomWidget,
FullscreenWidget,
_GeocoderWidget as GeocoderWidget,
DarkGlassTheme,
LightGlassTheme
} from '@deck.gl/widgets';
Expand Down Expand Up @@ -77,6 +78,7 @@ new Deck({
widgets: [
new ZoomWidget({style: widgetTheme}),
new CompassWidget({style: widgetTheme}),
new FullscreenWidget({style: widgetTheme})
new FullscreenWidget({style: widgetTheme}),
new GeocoderWidget({style: widgetTheme, _geolocation: true})
]
});
32 changes: 6 additions & 26 deletions modules/widgets/src/geocoder-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,41 +87,19 @@ export class GeocoderWidget extends Widget<GeocoderWidgetProps> {
? [CURRENT_LOCATION, ...this.geocodeHistory.addressHistory]
: [...this.geocodeHistory.addressHistory];
render(
<div
className="deck-widget-geocoder"
style={{
pointerEvents: 'auto',
display: 'flex',
alignItems: 'center',
flexWrap: 'wrap' // Allows wrapping on smaller screens
}}
>
<div className="deck-widget-geocoder">
<input
className="deck-widget-geocoder-input"
type="text"
placeholder={this.geocoder.placeholderLocation ?? 'Enter address or location'}
value={this.geocodeHistory.addressText}
// @ts-expect-error event type
onInput={e => this.setInput(e.target?.value || '')}
onKeyPress={this.handleKeyPress}
style={{
flex: '1 1 auto',
minWidth: '200px',
margin: 0,
padding: '8px',
boxSizing: 'border-box'
}}
/>
<DropdownMenu
menuItems={menuItems}
onSelect={this.handleSelect}
style={{
margin: 2,
padding: '4px 2px',
boxSizing: 'border-box'
}}
/>
<DropdownMenu menuItems={menuItems} onSelect={this.handleSelect} />
{this.geocodeHistory.errorText && (
<div className="error">{this.geocodeHistory.errorText}</div>
<div className="deck-widget-geocoder-error">{this.geocodeHistory.errorText}</div>
)}
</div>,
rootElement
Expand Down Expand Up @@ -158,6 +136,8 @@ export class GeocoderWidget extends Widget<GeocoderWidgetProps> {
this.addressText,
this.props.apiKey
);
// Re-render to show updated history or error
this.updateHTML();
if (coordinates) {
this.setViewState(coordinates);
}
Expand Down
59 changes: 10 additions & 49 deletions modules/widgets/src/lib/components/dropdown-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,59 +35,20 @@ export const DropdownMenu = (props: DropdownMenuProps) => {
setIsOpen(false);
};

// Don't render anything if there are no menu items
if (props.menuItems.length === 0) {
return null;
}

return (
<div
className="dropdown-container"
ref={dropdownRef}
style={{
position: 'relative',
display: 'inline-block',
...props.style
}}
>
<button
onClick={toggleDropdown}
style={{
width: '30px',
height: '30px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
border: '1px solid #ccc',
borderRadius: '4px',
background: '#fff',
cursor: 'pointer',
padding: 0
}}
>
<div className="deck-widget-dropdown-container" ref={dropdownRef} style={props.style}>
<button className="deck-widget-dropdown-button" onClick={toggleDropdown}>
<span className={`deck-widget-dropdown-icon ${isOpen ? 'open' : ''}`} />
</button>
{isOpen && (
<ul
style={{
position: 'absolute',
top: '100%',
right: '100%',
background: '#fff',
border: '1px solid #ccc',
borderRadius: '4px',
listStyle: 'none',
padding: '4px 0',
margin: 0,
zIndex: 1000,
minWidth: '200px'
}}
>
<ul className="deck-widget-dropdown-menu">
{props.menuItems.map(item => (
<li
key={item}
onClick={() => handleSelect(item)}
style={{
padding: '4px 8px',
cursor: 'pointer',
whiteSpace: 'nowrap'
}}
>
<li className="deck-widget-dropdown-item" key={item} onClick={() => handleSelect(item)}>
{item}
</li>
))}
Expand Down
156 changes: 140 additions & 16 deletions modules/widgets/src/stylesheet.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
.deck-widget-button-group {
width: calc(var(--button-size, 28px) - 2);
height: calc((var(--button-size, 28px) * 2) - 1);

flex-direction: row;
padding: 1px;
gap: 1px;
Expand Down Expand Up @@ -66,7 +66,6 @@
border-top-right-radius: 0;
}


.deck-widget-button-group.horizontal > *:not(:last-child),
.deck-widget-button-group.horizontal > *:not(:last-child) > button {
border-top-right-radius: 0;
Expand Down Expand Up @@ -192,14 +191,14 @@
.deck-widget.deck-widget-reset-view button.deck-widget-reset-focus .deck-widget-icon {
mask-image: var(
--icon-camera,
url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="black"><path d="M480-320v-100q0-25 17.5-42.5T540-480h100v60H540v100h-60Zm60 240q-25 0-42.5-17.5T480-140v-100h60v100h100v60H540Zm280-240v-100H720v-60h100q25 0 42.5 17.5T880-420v100h-60ZM720-80v-60h100v-100h60v100q0 25-17.5 42.5T820-80H720Zm111-480h-83q-26-88-99-144t-169-56q-117 0-198.5 81.5T200-480q0 72 32.5 132t87.5 98v-110h80v240H160v-80h94q-62-50-98-122.5T120-480q0-75 28.5-140.5t77-114q48.5-48.5 114-77T480-840q129 0 226.5 79.5T831-560Z"/></svg>')
url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="black"><path d="M480-320v-100q0-25 17.5-42.5T540-480h100v60H540v100h-60Zm60 240q-25 0-42.5-17.5T480-140v-100h60v100h100v60H540Zm280-240v-100H720v-60h100q25 0 42.5 17.5T880-420v100h-60ZM720-80v-60h100v-100h60v100q0 25-17.5 42.5T820-80H720Zm111-480h-83q-26-88-99-144t-169-56q-117 0-198.5 81.5T200-480q0 72 32.5 132t87.5 98v-110h80v240H160v-80h94q-62-50-98-122.5T120-480q0-75 28.5-140.5t77-114q48.5-48.5 114-77T480-840q129 0 226.5 79.5T831-560Z"/></svg>')
);
-webkit-mask-image: var(
--icon-camera,
url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="black"><path d="M480-320v-100q0-25 17.5-42.5T540-480h100v60H540v100h-60Zm60 240q-25 0-42.5-17.5T480-140v-100h60v100h100v60H540Zm280-240v-100H720v-60h100q25 0 42.5 17.5T880-420v100h-60ZM720-80v-60h100v-100h60v100q0 25-17.5 42.5T820-80H720Zm111-480h-83q-26-88-99-144t-169-56q-117 0-198.5 81.5T200-480q0 72 32.5 132t87.5 98v-110h80v240H160v-80h94q-62-50-98-122.5T120-480q0-75 28.5-140.5t77-114q48.5-48.5 114-77T480-840q129 0 226.5 79.5T831-560Z"/></svg>')
url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="black"><path d="M480-320v-100q0-25 17.5-42.5T540-480h100v60H540v100h-60Zm60 240q-25 0-42.5-17.5T480-140v-100h60v100h100v60H540Zm280-240v-100H720v-60h100q25 0 42.5 17.5T880-420v100h-60ZM720-80v-60h100v-100h60v100q0 25-17.5 42.5T820-80H720Zm111-480h-83q-26-88-99-144t-169-56q-117 0-198.5 81.5T200-480q0 72 32.5 132t87.5 98v-110h80v240H160v-80h94q-62-50-98-122.5T120-480q0-75 28.5-140.5t77-114q48.5-48.5 114-77T480-840q129 0 226.5 79.5T831-560Z"/></svg>')
);
}

/* Timeline widget styles */
.deck-widget.deck-widget-timeline {
display: flex;
Expand Down Expand Up @@ -248,7 +247,7 @@

.deck-widget.deck-widget-loading button.deck-widget-spinner .deck-widget-icon {
mask-image: var(
--loading-icon,
--loading-icon,
url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" ><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>')
);
-webkit-mask-image: var(
Expand All @@ -267,7 +266,137 @@
/* Geocoder styles */
.deck-widget .deck-widget-geocoder {
pointer-events: auto;
position: relative;
display: flex;
align-items: center;
flex-wrap: wrap;
background: var(--button-stroke, rgba(255, 255, 255, 0.3));
border-radius: var(--button-corner-radius, 8px);
box-shadow: var(--button-shadow, 0px 0px 8px 0px rgba(0, 0, 0, 0.25));
padding: 1px;
gap: 1px;
}

.deck-widget .deck-widget-geocoder-input {
flex: 1 1 auto;
min-width: 200px;
height: calc(var(--button-size, 28px) - 2px);
margin: 0;
padding: 0 8px;
box-sizing: border-box;
background: var(--button-background, #fff);
backdrop-filter: var(--button-backdrop-filter, unset);
color: var(--button-text, rgb(24, 24, 26));
border: var(--button-inner-stroke, unset);
border-radius: calc(var(--button-corner-radius, 8px) - 1px);
border-top-right-radius: 0;
border-bottom-right-radius: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
font-size: 13px;
}

.deck-widget .deck-widget-geocoder-input:focus {
outline: none;
}

.deck-widget .deck-widget-geocoder-input::placeholder {
color: var(--button-icon-idle, rgba(97, 97, 102, 1));
}

.deck-widget .deck-widget-geocoder-error {
position: absolute;
top: calc(100% + var(--menu-gap, 4px));
left: 1px;
right: 1px;
padding: 6px 10px;
background: var(--button-background, #fff);
backdrop-filter: var(--button-backdrop-filter, unset);
border: var(--button-inner-stroke, unset);
border-radius: var(--button-corner-radius, 8px);
box-shadow: var(--button-shadow, 0px 0px 8px 0px rgba(0, 0, 0, 0.25));
color: rgb(220, 80, 80);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
font-size: 12px;
text-align: center;
}

/* When dropdown is not present, input should have full rounded corners */
.deck-widget .deck-widget-geocoder-input:last-child {
border-top-right-radius: calc(var(--button-corner-radius, 8px) - 1px);
border-bottom-right-radius: calc(var(--button-corner-radius, 8px) - 1px);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Input has incorrect corners when error shown without dropdown

Low Severity

The CSS selector .deck-widget-geocoder-input:last-child is intended to restore rounded right corners when the dropdown is absent. However, when there's an error message but no dropdown (empty history + geolocation disabled + failed geocode), the error div becomes the last child, not the input. This causes the input to retain flat right corners despite having no adjacent dropdown button — the error div wraps to the next row due to width: 100% and flex-wrap.

Fix in Cursor Fix in Web


/* Dropdown menu styles */
.deck-widget .deck-widget-dropdown-container {
position: static;
display: inline-block;
}

.deck-widget .deck-widget-dropdown-button {
width: calc(var(--button-size, 28px) - 2px);
height: calc(var(--button-size, 28px) - 2px);
display: flex;
align-items: center;
justify-content: center;
padding: 0;
cursor: pointer;
background: var(--button-background, #fff);
backdrop-filter: var(--button-backdrop-filter, unset);
border: var(--button-inner-stroke, unset);
border-radius: calc(var(--button-corner-radius, 8px) - 1px);
border-top-left-radius: 0;
border-bottom-left-radius: 0;
outline: none;
}

.deck-widget .deck-widget-dropdown-icon {
width: 12px;
height: 12px;
background-color: var(--button-icon-idle, rgba(97, 97, 102, 1));
mask-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="black"><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/></svg>');
-webkit-mask-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="black"><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/></svg>');
mask-size: contain;
-webkit-mask-size: contain;
transition: transform 0.2s ease;
}

.deck-widget .deck-widget-dropdown-icon.open {
transform: rotate(180deg);
}

.deck-widget .deck-widget-dropdown-button:hover .deck-widget-dropdown-icon {
background-color: var(--button-icon-hover, rgba(24, 24, 26, 1));
}

.deck-widget .deck-widget-dropdown-menu {
position: absolute;
top: calc(100% + var(--menu-gap, 4px));
left: 1px;
right: 1px;
margin: 0;
padding: 4px 0;
list-style: none;
z-index: 1000;
background: var(--button-background, #fff);
backdrop-filter: var(--button-backdrop-filter, unset);
border: var(--button-inner-stroke, unset);
border-radius: var(--button-corner-radius, 8px);
box-shadow: var(--button-shadow, 0px 0px 8px 0px rgba(0, 0, 0, 0.25));
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
font-size: 13px;
}

.deck-widget .deck-widget-dropdown-item {
padding: 8px 12px;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--button-text, rgb(24, 24, 26));
}

.deck-widget .deck-widget-dropdown-item:hover {
background: var(--button-stroke, rgba(255, 255, 255, 0.3));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropdown item hover effect invisible in all themes

Medium Severity

The dropdown item hover style uses var(--button-stroke) for the background color, but --button-stroke is designed to match the theme's base color (light stroke on light themes, dark stroke on dark themes). Since the dropdown menu uses --button-background which has the same hue, the hover effect is imperceptible. In LightGlassTheme, it's semi-transparent white on white. In DarkGlassTheme, it's semi-transparent dark on dark. Users won't see any visual feedback when hovering over dropdown items.

Fix in Cursor Fix in Web

}

/* Stats styles */
Expand All @@ -281,16 +410,11 @@
line-height: 1.6;
font-family:
'SF Mono',
/* macOS */
'Menlo',
/* macOS */
'Consolas',
/* Windows */
'DejaVu Sans Mono',
/* Linux */
'Liberation Mono',
/* Linux */
monospace;
/* macOS */ 'Menlo',
/* macOS */ 'Consolas',
/* Windows */ 'DejaVu Sans Mono',
/* Linux */ 'Liberation Mono',
/* Linux */ monospace;
background-color: var(--button-background, #fff);
color: var(--button-text, rgb(24, 24, 26));
}
Loading