@@ -19,16 +19,15 @@ import type { Opt } from '@/util/opt'
19
19
import { allRanges } from ' @/util/range'
20
20
import { Vec2 } from ' @/util/vec2'
21
21
import type { SuggestionId } from ' shared/languageServerTypes/suggestions'
22
- import type { ContentRange , ExprId } from ' shared/yjsModel.ts'
23
22
import { computed , nextTick , onMounted , ref , watch , type ComputedRef , type Ref } from ' vue'
24
- import { useComponentBrowserInput } from ' ./ComponentBrowser/input'
23
+ import { useComponentBrowserInput , type Usage } from ' ./ComponentBrowser/input'
25
24
import GraphVisualization from ' ./GraphEditor/GraphVisualization.vue'
26
25
27
26
const ITEM_SIZE = 32
28
27
const TOP_BAR_HEIGHT = 32
29
28
// Difference in position between the component browser and a node for the input of the component browser to
30
29
// be placed at the same position as the node.
31
- const COMPONENT_BROWSER_TO_NODE_OFFSET = new Vec2 (20 , 35 )
30
+ const COMPONENT_BROWSER_TO_NODE_OFFSET = new Vec2 (- 4 , - 4 )
32
31
33
32
const projectStore = useProjectStore ()
34
33
const suggestionDbStore = useSuggestionDbStore ()
@@ -37,42 +36,24 @@ const graphStore = useGraphStore()
37
36
const props = defineProps <{
38
37
nodePosition: Vec2
39
38
navigator: ReturnType <typeof useNavigator >
40
- initialContent: string
41
- initialCaretPosition: ContentRange
42
- sourcePort: Opt <ExprId >
39
+ usage: Usage
43
40
}>()
44
41
45
42
const emit = defineEmits <{
46
43
accepted: [searcherExpression : string , requiredImports : RequiredImport []]
47
- closed: [searcherExpression : string ]
44
+ closed: [searcherExpression : string , requiredImports : RequiredImport [] ]
48
45
canceled: []
49
46
}>()
50
47
51
- function getInitialContent(): string {
52
- if (props .sourcePort == null ) return props .initialContent
53
- const sourceNodeName = graphStore .db .getOutputPortIdentifier (props .sourcePort )
54
- const sourceNodeNameWithDot = sourceNodeName ? sourceNodeName + ' .' : ' '
55
- return sourceNodeNameWithDot + props .initialContent
56
- }
57
-
58
- function getInitialCaret(): ContentRange {
59
- if (props .sourcePort == null ) return props .initialCaretPosition
60
- const sourceNodeName = graphStore .db .getOutputPortIdentifier (props .sourcePort )
61
- const sourceNodeNameWithDot = sourceNodeName ? sourceNodeName + ' .' : ' '
62
- return [
63
- props .initialCaretPosition [0 ] + sourceNodeNameWithDot .length ,
64
- props .initialCaretPosition [1 ] + sourceNodeNameWithDot .length ,
65
- ]
66
- }
67
-
68
48
onMounted (() => {
69
49
nextTick (() => {
70
- input .code .value = getInitialContent ()
71
- const caret = getInitialCaret ()
50
+ input .reset (props .usage )
72
51
if (inputField .value != null ) {
73
52
inputField .value .focus ({ preventScroll: true })
74
- input .selection .value = { start: caret [0 ], end: caret [1 ] }
75
- selectLastAfterRefresh ()
53
+ } else {
54
+ console .warn (
55
+ ' Component Browser input element was not mounted. This is not expected and may break the Component Browser' ,
56
+ )
76
57
}
77
58
})
78
59
})
@@ -108,7 +89,15 @@ const currentFiltering = computed(() => {
108
89
)
109
90
})
110
91
111
- watch (currentFiltering , selectLastAfterRefresh )
92
+ watch (currentFiltering , () => {
93
+ selected .value = input .autoSelectFirstComponent .value ? 0 : null
94
+ nextTick (() => {
95
+ scrollToBottom ()
96
+ animatedScrollPosition .skip ()
97
+ animatedHighlightPosition .skip ()
98
+ animatedHighlightHeight .skip ()
99
+ })
100
+ })
112
101
113
102
function readInputFieldSelection() {
114
103
if (
@@ -159,9 +148,10 @@ useEvent(
159
148
window ,
160
149
' pointerdown' ,
161
150
(event ) => {
151
+ if (event .button !== 0 ) return
162
152
if (! (event .target instanceof Element )) return
163
153
if (! cbRoot .value ?.contains (event .target )) {
164
- emit (' closed' , input .code .value )
154
+ emit (' closed' , input .code .value , input . importsToAdd () )
165
155
}
166
156
},
167
157
{ capture: true },
@@ -263,21 +253,6 @@ const highlightClipPath = computed(() => {
263
253
return ` inset(${top }px 0px ${bottom }px 0px round 16px) `
264
254
})
265
255
266
- /**
267
- * Select the last element after updating component list.
268
- *
269
- * As the list changes the scroller's content, we need to wait a frame so the scroller
270
- * recalculates its height and setting scrollTop will work properly.
271
- */
272
- function selectLastAfterRefresh() {
273
- selected .value = 0
274
- nextTick (() => {
275
- scrollToSelected ()
276
- animatedScrollPosition .skip ()
277
- animatedHighlightPosition .skip ()
278
- })
279
- }
280
-
281
256
// === Scrolling ===
282
257
283
258
const scroller = ref <HTMLElement >()
@@ -297,6 +272,10 @@ function scrollToSelected() {
297
272
scrollPosition .value = Math .max (selectedPosition .value - scrollerSize .value .y + ITEM_SIZE , 0 )
298
273
}
299
274
275
+ function scrollToBottom() {
276
+ scrollPosition .value = listContentHeight .value - scrollerSize .value .y
277
+ }
278
+
300
279
function updateScroll() {
301
280
if (scroller .value && Math .abs (scroller .value .scrollTop - animatedScrollPosition .value ) > 1.0 ) {
302
281
scrollPosition .value = scroller .value .scrollTop
@@ -385,6 +364,9 @@ const handler = componentBrowserBindings.handler({
385
364
@focusout =" handleDefocus"
386
365
@keydown =" handler"
387
366
@pointerdown.stop
367
+ @keydown.enter.stop
368
+ @keydown.backspace.stop
369
+ @keydown.delete.stop
388
370
>
389
371
<div class =" panels" >
390
372
<div class =" panel components" >
@@ -490,8 +472,7 @@ const handler = componentBrowserBindings.handler({
490
472
v-model =" input.code.value"
491
473
name =" cb-input"
492
474
autocomplete =" off"
493
- @keydown.backspace.stop
494
- @keyup =" readInputFieldSelection"
475
+ @input =" readInputFieldSelection"
495
476
/>
496
477
</div >
497
478
</div >
0 commit comments