Skip to content

Commit 8facdd8

Browse files
committed
Improve sidebar
1 parent 45db7a3 commit 8facdd8

8 files changed

Lines changed: 363 additions & 30 deletions

File tree

src/app/components/sidebar/ModuleEditor.vue

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@
8787
:max="item.max"
8888
:step="item.step"
8989
:default-val="item.defaultVal"
90+
:focus-name="item.id === paramFocusNameId"
91+
@focus-name-done="paramFocusNameId = null"
9092
@update:name="(v) => onParamNameUpdate(index, v)"
9193
@update:min="(v) => onParamMinUpdate(index, v)"
9294
@update:max="(v) => onParamMaxUpdate(index, v)"
@@ -125,6 +127,8 @@
125127
:module-name="moduleName"
126128
:name="item.name"
127129
:expression="item.expression"
130+
:focus-name="item.id === variableFocusNameId"
131+
@focus-name-done="variableFocusNameId = null"
128132
@update:name="(v) => onVarNameUpdate(index, v)"
129133
@update:expression="(v) => onVarExprUpdate(index, v)"
130134
@commit="commitVariableDefs"
@@ -427,7 +431,7 @@ function serializeVarDef(name, expression) {
427431
return e
428432
}
429433
if (!e) {
430-
return n
434+
return `${n} =`
431435
}
432436
return `${n} = ${e}`
433437
}
@@ -481,10 +485,14 @@ export default {
481485
})
482486
const paramItems = ref([])
483487
const paramActiveId = ref(null)
488+
/** Row id whose name field should receive focus after create (cleared by child emit). */
489+
const paramFocusNameId = ref(null)
484490
/** True only after an explicit param row click; cleared on row mouse-leave so hover never re-highlights the slider. */
485491
const paramSliderHighlightActive = ref(false)
486492
const variableItems = ref([])
487493
const variableActiveId = ref(null)
494+
/** Row id whose name field should receive focus after create (cleared by child emit). */
495+
const variableFocusNameId = ref(null)
488496
const maxLoopLengthInput = ref(String(DEFAULT_MODULE_MAX_LOOP_LENGTH))
489497
const maxLoopLengthCommittedSnapshot = ref(maxLoopLengthInput.value)
490498

@@ -1135,6 +1143,7 @@ export default {
11351143
if (last) {
11361144
clearOtherModuleSidebarLists(MODULE_EDITOR_LIST.PARAMS)
11371145
paramActiveId.value = last.id
1146+
paramFocusNameId.value = last.id
11381147
paramSliderHighlightActive.value = true
11391148
applyModuleParamHighlight(last.name)
11401149
}
@@ -1196,6 +1205,7 @@ export default {
11961205
if (last) {
11971206
clearOtherModuleSidebarLists(MODULE_EDITOR_LIST.VARIABLES)
11981207
variableActiveId.value = last.id
1208+
variableFocusNameId.value = last.id
11991209
}
12001210
}
12011211

@@ -1583,8 +1593,10 @@ export default {
15831593
handleControlPointSelect,
15841594
paramItems,
15851595
paramActiveId,
1596+
paramFocusNameId,
15861597
variableItems,
15871598
variableActiveId,
1599+
variableFocusNameId,
15881600
commitParamDefs,
15891601
onParamNameUpdate,
15901602
onParamMinUpdate,

src/app/components/sidebar/ModuleParamListItem.vue

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
@mouseleave="onItemMouseLeave"
2424
>
2525
<input
26+
ref="nameRef"
2627
class="module-param-input module-param-input--name"
2728
:value="name"
2829
:style="{ width: Math.max(name.length, 1) + 'ch' }"
@@ -85,7 +86,7 @@
8586
</template>
8687

8788
<script>
88-
import { ref, computed, nextTick, onMounted, onBeforeUnmount } from 'vue'
89+
import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'
8990
import * as bootstrap from 'bootstrap'
9091
import { useSceneStore } from '../../store/scene'
9192
import { app } from '../../services/app'
@@ -99,10 +100,12 @@ export default {
99100
min: { type: String, default: '0' },
100101
step: { type: String, default: '1' },
101102
max: { type: String, default: '0' },
102-
defaultVal: { type: String, default: '0' }
103+
defaultVal: { type: String, default: '0' },
104+
focusName: { type: Boolean, default: false }
103105
},
104-
emits: ['update:name', 'update:min', 'update:step', 'update:max', 'update:defaultVal', 'commit'],
106+
emits: ['update:name', 'update:min', 'update:step', 'update:max', 'update:defaultVal', 'commit', 'focus-name-done'],
105107
setup(props, { emit }) {
108+
const nameRef = ref(null)
106109
const tooltipHostRef = ref(null)
107110
const sceneStore = useSceneStore()
108111
const editorSelectedObjIndex = ref(-1)
@@ -217,6 +220,27 @@ export default {
217220
})
218221
}
219222
223+
const focusNameInput = () => {
224+
const el = nameRef.value
225+
if (!el) {
226+
return
227+
}
228+
el.focus()
229+
el.select()
230+
emit('focus-name-done')
231+
}
232+
233+
watch(
234+
() => props.focusName,
235+
(shouldFocus) => {
236+
if (!shouldFocus) {
237+
return
238+
}
239+
nextTick(focusNameInput)
240+
},
241+
{ immediate: true, flush: 'post' }
242+
)
243+
220244
const syncEditorSelection = () => {
221245
editorSelectedObjIndex.value = app.editor?.selectedObjIndex ?? -1
222246
}
@@ -246,6 +270,7 @@ export default {
246270
})
247271
248272
return {
273+
nameRef,
249274
tooltipHostRef,
250275
onRowFocusOut,
251276
onEnterCommit,

src/app/components/sidebar/ModuleVariableListItem.vue

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
:value="name"
2424
rows="1"
2525
spellcheck="false"
26-
@keydown.stop
26+
@keydown="onNameKeydown"
2727
@keydown.enter.prevent="onEnterCommit"
2828
@input="onNameInput"
2929
@focus="onNameFocus"
@@ -81,9 +81,10 @@ export default {
8181
props: {
8282
moduleName: { type: String, default: '' },
8383
name: { type: String, default: '' },
84-
expression: { type: String, default: '' }
84+
expression: { type: String, default: '' },
85+
focusName: { type: Boolean, default: false }
8586
},
86-
emits: ['update:name', 'update:expression', 'commit'],
87+
emits: ['update:name', 'update:expression', 'commit', 'focus-name-done'],
8788
setup(props, { emit }) {
8889
const nameRef = ref(null)
8990
const exprRef = ref(null)
@@ -218,6 +219,30 @@ export default {
218219
reshowTooltipIfFocused()
219220
}
220221
222+
const focusNameInput = () => {
223+
const el = nameRef.value
224+
if (!el) {
225+
return
226+
}
227+
el.focus()
228+
el.select()
229+
emit('focus-name-done')
230+
}
231+
232+
watch(
233+
() => props.focusName,
234+
(shouldFocus) => {
235+
if (!shouldFocus) {
236+
return
237+
}
238+
nextTick(() => {
239+
focusNameInput()
240+
autoResize()
241+
})
242+
},
243+
{ immediate: true, flush: 'post' }
244+
)
245+
221246
const autoResize = () => {
222247
for (const el of [nameRef.value, exprRef.value]) {
223248
applyTextareaAutoResize(el)
@@ -234,6 +259,20 @@ export default {
234259
nextTick(autoResize)
235260
}
236261
262+
const onNameKeydown = (e) => {
263+
e.stopPropagation()
264+
if (e.key !== '=') {
265+
return
266+
}
267+
e.preventDefault()
268+
const el = exprRef.value
269+
if (!el) {
270+
return
271+
}
272+
el.focus()
273+
el.select()
274+
}
275+
237276
const onExprFocus = (e) => {
238277
exprFocused.value = true
239278
autoResize()
@@ -324,6 +363,7 @@ export default {
324363
onEnterCommit,
325364
onNameFocus,
326365
onNameBlur,
366+
onNameKeydown,
327367
onExprFocus,
328368
onExprBlur,
329369
onModuleInstancesMouseLeave,

src/app/components/sidebar/VisualTab.vue

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,27 @@ export default {
199199
document.dispatchEvent(new CustomEvent('clearVisualEditorSelection'))
200200
}
201201
202+
// Firefox may scroll a visible overflow panel on arrow keys even when focus is on the
203+
// canvas/body (Chromium does not). Block that default unless focus is inside the panel.
204+
const onVisualSubtabArrowScrollKey = (event) => {
205+
if (event.keyCode < 37 || event.keyCode > 40) {
206+
return
207+
}
208+
const panel =
209+
activeTabId.value === 'scene'
210+
? scenePanelEl.value
211+
: activeModuleName.value != null
212+
? modulePanelEl.value
213+
: null
214+
if (!panel?.offsetParent) {
215+
return
216+
}
217+
const active = document.activeElement
218+
if (!active || !panel.contains(active)) {
219+
event.preventDefault()
220+
}
221+
}
222+
202223
// If the selected module disappears, fall back to Scene.
203224
watch(moduleNames, (names) => {
204225
if (activeTabId.value === 'scene') return
@@ -212,12 +233,14 @@ export default {
212233
document.addEventListener('selectVisualModuleTab', handleSelectModuleTab)
213234
document.addEventListener('selectVisualSceneTab', handleSelectSceneTab)
214235
document.addEventListener('applyVisualNewModule', handleApplyVisualNewModule)
236+
document.addEventListener('keydown', onVisualSubtabArrowScrollKey, true)
215237
})
216238
217239
onUnmounted(() => {
218240
document.removeEventListener('selectVisualModuleTab', handleSelectModuleTab)
219241
document.removeEventListener('selectVisualSceneTab', handleSelectSceneTab)
220242
document.removeEventListener('applyVisualNewModule', handleApplyVisualNewModule)
243+
document.removeEventListener('keydown', onVisualSubtabArrowScrollKey, true)
221244
})
222245
223246
return {

src/app/components/sidebar/controls/FormulaInput.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ export default {
309309
autoResize()
310310
}
311311
312-
if (parentUnchanged && draft !== synced) {
312+
if (rejectedNonEmptyEdit) {
313313
return
314314
}
315315
const el = textareaRef.value

0 commit comments

Comments
 (0)