@@ -105,7 +105,11 @@ export type SingleNodeTarget =
105
105
| ElementInBodyContext
106
106
| ElementInTagContext
107
107
| AttributeInKeyContext
108
- | AttributeInValueContext ;
108
+ | AttributeInValueContext
109
+ | ComponentInBodyContext
110
+ | ComponentInTagContext
111
+ | DirectiveInNameContext
112
+ | DirectiveInBodyContext ;
109
113
110
114
/**
111
115
* Contexts which logically target multiple nodes in the template AST, which cannot be
@@ -127,6 +131,10 @@ export enum TargetNodeKind {
127
131
AttributeInKeyContext ,
128
132
AttributeInValueContext ,
129
133
TwoWayBindingContext ,
134
+ ComponentInTagContext ,
135
+ ComponentInBodyContext ,
136
+ DirectiveInNameContext ,
137
+ DirectiveInBodyContext ,
130
138
}
131
139
132
140
/**
@@ -177,6 +185,42 @@ export interface ElementInBodyContext {
177
185
node : TmplAstElement | TmplAstTemplate ;
178
186
}
179
187
188
+ /**
189
+ * A `TmplAstComponent` element node that's targeted, where the given position is within the tag,
190
+ * e.g. `MyComp` in `<MyComp foo="bar"/>`.
191
+ */
192
+ export interface ComponentInTagContext {
193
+ kind : TargetNodeKind . ComponentInTagContext ;
194
+ node : TmplAstComponent ;
195
+ }
196
+
197
+ /**
198
+ * A `TmplAstComponent` element node that's targeted, where the given position is within the body,
199
+ * e.g. `foo="bar"/>` in `<MyComp foo="bar"/>`.
200
+ */
201
+ export interface ComponentInBodyContext {
202
+ kind : TargetNodeKind . ComponentInBodyContext ;
203
+ node : TmplAstComponent ;
204
+ }
205
+
206
+ /**
207
+ * A `TmplAstDirective` element node that's targeted, where the given position is within the
208
+ * directive's name (e.g. `MyDir` in `@MyDir`).
209
+ */
210
+ export interface DirectiveInNameContext {
211
+ kind : TargetNodeKind . DirectiveInNameContext ;
212
+ node : TmplAstDirective ;
213
+ }
214
+
215
+ /**
216
+ * A `TmplAstDirective` element node that's targeted, where the given position is within the body,
217
+ * e.g. `(foo="bar")` in `@MyDir(foo="bar")`.
218
+ */
219
+ export interface DirectiveInBodyContext {
220
+ kind : TargetNodeKind . DirectiveInBodyContext ;
221
+ node : TmplAstDirective ;
222
+ }
<
341A
td data-grid-cell-id="diff-ec57a8e261b398b22d55d2e3acae12aaa615432863a8c138168391a0ea8da575-179-223-0" data-selected="false" role="gridcell" style="background-color:var(--diffBlob-additionNum-bgColor, var(--diffBlob-addition-bgColor-num));text-align:center" tabindex="-1" valign="top" class="focusable-grid-cell diff-line-number position-relative left-side">
223
+
180
224
export interface AttributeInKeyContext {
181
225
kind : TargetNodeKind . AttributeInKeyContext ;
182
226
node : TmplAstTextAttribute | TmplAstBoundAttribute | TmplAstBoundEvent ;
@@ -267,22 +311,32 @@ export function getTargetAtPosition(
267
311
} else if ( candidate instanceof TmplAstElement ) {
268
312
// Elements have two contexts: the tag context (position is within the element tag) or the
269
313
// element body context (position is outside of the tag name, but still in the element).
314
+ nodeInContext = {
315
+ kind : isWithinTagBody ( position , candidate )
316
+ ? TargetNodeKind . ElementInBodyContext
317
+ : TargetNodeKind . ElementInTagContext ,
318
+ node : candidate ,
319
+ } ;
320
+ } else if ( candidate instanceof TmplAstComponent ) {
321
+ nodeInContext = {
322
+ kind : isWithinTagBody ( position , candidate )
323
+ ? TargetNodeKind . ComponentInBodyContext
324
+ : TargetNodeKind . ComponentInTagContext ,
325
+ node : candidate ,
326
+ } ;
327
+ } else if ( candidate instanceof TmplAstDirective ) {
328
+ const startSpan = candidate . startSourceSpan ;
270
329
271
- // Calculate the end of the element tag name. Any position beyond this is in the element body.
272
- const tagEndPos =
273
- candidate . sourceSpan . start . offset + 1 /* '<' element open */ + candidate . name . length ;
274
- if ( position > tagEndPos ) {
275
- // Position is within the element body
276
- nodeInContext = {
277
- kind : TargetNodeKind . ElementInBodyContext ,
278
- node : candidate ,
279
- } ;
280
- } else {
281
- nodeInContext = {
282
- kind : TargetNodeKind . ElementInTagContext ,
283
- node : candidate ,
284
- } ;
285
- }
330
+ // The start span includes the opening paren, if there is one which we have to account for.
331
+ const endOffset = startSpan . end . offset - ( startSpan . toString ( ) . endsWith ( '(' ) ? 1 : 0 ) ;
332
+
333
+ nodeInContext = {
334
+ kind :
335
+ position >= startSpan . start . offset && position <= endOffset
336
+ ? TargetNodeKind . DirectiveInNameContext
337
+ : TargetNodeKind . DirectiveInBodyContext ,
338
+ node : candidate ,
339
+ } ;
286
340
} else if (
287
341
( candidate instanceof TmplAstBoundAttribute ||
288
342
candidate instanceof TmplAstBoundEvent ||
@@ -474,48 +528,61 @@ class TemplateTargetVisitor implements TmplAstVisitor {
474
528
}
475
529
476
530
visitElement ( element : TmplAstElement ) {
477
- this . visitElementOrTemplate ( element ) ;
531
+ this . visitDirectiveHost ( element ) ;
478
532
}
479
533
480
534
visitTemplate ( template : TmplAstTemplate ) {
481
- this . visitElementOrTemplate ( template ) ;
535
+ this . visitDirectiveHost ( template ) ;
536
+ }
537
+
538
+ visitComponent ( component : TmplAstComponent ) {
539
+ this . visitDirectiveHost ( component ) ;
482
540
}
483
541
484
- visitElementOrTemplate ( element : TmplAstTemplate | TmplAstElement ) {
485
- const isTemplate = element instanceof TmplAstTemplate ;
486
- this . visitAll ( element . attributes ) ;
487
- if ( ! isTemplate ) {
488
- this . visitAll ( element . directives ) ;
542
+ visitDirective ( directive : TmplAstDirective ) {
543
+ this . visitDirectiveHost ( directive ) ;
544
+ }
545
+
546
+ private visitDirectiveHost (
547
+ node : TmplAstTemplate | TmplAstElement | TmplAstComponent | TmplAstDirective ,
548
+ ) {
549
+ const isTemplate = node instanceof TmplAstTemplate ;
550
+ const isDirective = node instanceof TmplAstDirective ;
551
+ this . visitAll ( node . attributes ) ;
552
+ if ( ! isDirective ) {
553
+ this . visitAll ( node . directives ) ;
489
554
}
490
- this . visitAll ( element . inputs ) ;
555
+ this . visitAll ( node . inputs ) ;
491
556
// We allow the path to contain both the `TmplAstBoundAttribute` and `TmplAstBoundEvent` for
492
557
// two-way bindings but do not want the path to contain both the `TmplAstBoundAttribute` with
493
558
// its children when the position is in the value span because we would then logically create a
494
559
// path that also contains the `PropertyWrite` from the `TmplAstBoundEvent`. This early return
495
560
// condition ensures we target just `TmplAstBoundAttribute` for this case and exclude
496
561
// `TmplAstBoundEvent` children.
497
562
if (
498
- this . path [ this . path . length - 1 ] !== element &&
563
+ this . path [ this . path . length - 1 ] !== node &&
499
564
! ( this . path [ this . path . length - 1 ] instanceof TmplAstBoundAttribute )
500
565
) {
501
566
return ;
502
567
}
503
- this . visitAll ( element . outputs ) ;
568
+ this . visitAll ( node . outputs ) ;
504
569
if ( isTemplate ) {
505
- this . visitAll ( element . templateAttrs ) ;
570
+ this . visitAll ( node . templateAttrs ) ;
506
571
}
507
- this . visitAll ( element . references ) ;
572
+ this . visitAll ( node . references ) ;
508
573
if ( isTemplate ) {
509
- this . visitAll ( element . variables ) ;
574
+ this . visitAll ( node . variables ) ;
510
575
}
511
576
512
577
// If we get here and have not found a candidate node on the element itself, proceed with
513
578
// looking for a more specific node on the element children.
514
- if ( this . path [ this . path . length - 1 ] !== element ) {
579
+ if ( this . path [ this . path . length - 1 ] !== node ) {
515
580
return ;
516
581
}
517
582
518
- this . visitAll ( element . children ) ;
583
+ if ( ! isDirective ) {
584
+ this . visitAll ( node . children ) ;
585
+ }
519
586
}
520
587
521
588
visitContent ( content : TmplAstContent ) {
@@ -626,14 +693,6 @@ class TemplateTargetVisitor implements TmplAstVisitor {
626
693
this . visitBinding ( decl . value ) ;
627
694
}
628
695
629
- visitComponent ( component : TmplAstComponent ) {
630
- // TODO(crisbeto): integrate selectorless
631
- }
632
-
633
- visitDirective ( directive : TmplAstDirective ) {
634
- // TODO(crisbeto): integrate selectorless
635
- }
636
-
637
696
visitAll ( nodes : TmplAstNode [ ] ) {
638
697
for ( const node of nodes ) {
639
698
this . visit ( node ) ;
@@ -708,3 +767,12 @@ function isWithinNode(position: number, node: TmplAstNode): boolean {
708
767
node . listeners . some ( ( listener ) => isWithin ( position , listener . sourceSpan ) ) )
709
768
) ;
710
769
}
770
+
771
+ /** Checks whether a position is within the body or the start syntax of a node. */
772
+ function isWithinTagBody ( position : number , node : TmplAstElement | TmplAstComponent ) : boolean {
773
+ // Calculate the end of the element tag name. Any position beyond this is in the body.
774
+ const name = node instanceof TmplAstComponent ? node . fullName : node . name ;
775
+ const tagEndPos =
776
+ node . sourceSpan . start . offset + 1 /* '<' is the opening character */ + name . length ;
777
+ return position > tagEndPos ;
778
+ }
0 commit comments