Skip to content

Commit 8d87d3e

Browse files
fix: unfocus UI inputs on viewport click, refuse duplicate id in ID input (#860)
* fix: unfocus UI inputs on viewport click, refuse duplicate id in ID input raycaster.js preventDefault'ed every mousedown on the viewport, which canceled the browser's default focus transfer, so clicking the viewport never unfocused the currently focused panel input. Only preventDefault non-left buttons now (still needed so middle-click zoom doesn't trigger autoscroll) and add user-select none on the canvas to keep drag-orbits from starting a text selection, which the preventDefault was incidentally providing. changeId now refuses an id already used by another element (a duplicate id breaks entity lookups done with getElementById/querySelector) and shows a message below the input explaining the previous id was restored. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: remove dead a-canvas rules The .aframe-inspector-opened a-scene .a-canvas and .a-canvas.state-dragging rules are nested under #aframeInspector, and the canvas is not a descendant of it, so they never matched. Nothing in src adds the state-dragging class anyway. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 11a6ee8 commit 8d87d3e

4 files changed

Lines changed: 59 additions & 18 deletions

File tree

src/components/components/CommonComponents.js

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,44 @@ import Events from '../../lib/Events';
1212
import { saveBlob } from '../../lib/utils';
1313
import GLTFIcon from '../../../assets/gltf.svg';
1414

15-
// @todo Take this out and use updateEntity?
16-
function changeId(componentName, value) {
17-
var entity = AFRAME.INSPECTOR.selectedEntity;
18-
if (entity.id !== value) {
19-
entity.id = value;
20-
Events.emit('entityidchange', entity);
21-
}
22-
}
23-
2415
export default class CommonComponents extends React.Component {
2516
static propTypes = {
2617
entity: PropTypes.object
2718
};
2819

20+
state = {
21+
duplicateId: null,
22+
idInputKey: 0
23+
};
24+
25+
changeId = (componentName, value) => {
26+
var entity = AFRAME.INSPECTOR.selectedEntity;
27+
if (entity.id !== value) {
28+
// Ids must be unique; committing a duplicate id breaks entity lookups
29+
// done with getElementById or querySelector in components. Refuse the
30+
// value, show a message and remount the input so it displays the
31+
// entity's current id again.
32+
if (value && document.getElementById(value)) {
33+
this.setState((state) => ({
34+
duplicateId: value,
35+
idInputKey: state.idInputKey + 1
36+
}));
37+
return;
38+
}
39+
this.setState({ duplicateId: null });
40+
entity.id = value;
41+
Events.emit('entityidchange', entity);
42+
} else {
43+
this.setState({ duplicateId: null });
44+
}
45+
};
46+
47+
componentDidUpdate(prevProps) {
48+
if (prevProps.entity !== this.props.entity && this.state.duplicateId) {
49+
this.setState({ duplicateId: null });
50+
}
51+
}
52+
2953
onEntityUpdate = (detail) => {
3054
if (detail.entity !== this.props.entity) {
3155
return;
@@ -125,12 +149,19 @@ export default class CommonComponents extends React.Component {
125149
ID
126150
</label>
127151
<InputWidget
128-
onBlur={changeId}
152+
key={this.state.idInputKey}
153+
onBlur={this.changeId}
129154
entity={entity}
130155
name="id"
131156
value={entity.id}
132157
/>
133158
</div>
159+
{this.state.duplicateId !== null && (
160+
<div className="propertyRowError">
161+
The id &quot;{this.state.duplicateId}&quot; is already used by
162+
another element, the previous id was restored.
163+
</div>
164+
)}
134165
<div className="propertyRow">
135166
<label className="text">class</label>
136167
<span>{entity.getAttribute('class')}</span>

src/lib/raycaster.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,14 @@ export function initRaycaster(inspector) {
7373
if (event instanceof CustomEvent) {
7474
return;
7575
}
76-
event.preventDefault();
76+
// Don't preventDefault on left click: it would suppress the browser's
77+
// default focus transfer, leaving a focused panel input (e.g. the id
78+
// field) focused while the selection changes underneath it, so its blur
79+
// handler would later commit a stale value to the newly selected entity.
80+
if (event.button !== 0) {
81+
// Middle click: prevent autoscroll so it can be used for zoom.
82+
event.preventDefault();
83+
}
7784
const array = getMousePosition(
7885
inspector.container,
7986
event.clientX,

src/style/components.styl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@
137137
.text
138138
width 90px
139139

140+
.propertyRowError
141+
color var(--color-error)
142+
font-size 13px
143+
padding 2px 30px 2px 15px
144+
140145
.propertyRowDefined .text
141146
color var(--color-property-defined)
142147
font-weight 600

src/style/index.styl

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ body.aframe-inspector-opened
1212
margin 0
1313
overflow hidden
1414

15+
a-scene .a-canvas
16+
// mousedown default action is no longer prevented (see raycaster.js), so
17+
// block drag-started text selection from anchoring on the canvas
18+
user-select none
19+
1520
/* :where(:root) has zero specificity compared to :root, so any user defined :root will override the below rule */
1621
:where(:root) {
1722
color-scheme: dark;
@@ -301,9 +306,6 @@ body.aframe-inspector-opened
301306
.hide
302307
display none
303308

304-
.a-canvas.state-dragging
305-
cursor grabbing
306-
307309
#rightPanel
308310
align-items stretch
309311
display flex
@@ -325,10 +327,6 @@ body.aframe-inspector-opened
325327
#rightPanel
326328
pointer-events all
327329

328-
.aframe-inspector-opened a-scene .a-canvas
329-
background-color #191919
330-
z-index 9998
331-
332330
.toggle-sidebar
333331
align-items center
334332
display flex

0 commit comments

Comments
 (0)