feat(devtools): improve value highlighting in object previews

Introduce a more fine-grained value preview highlighting based on the property type in the `ng-object-tree-explorer`.
This commit is contained in:
hawkgs 2026-05-05 16:34:13 +03:00 committed by Alex Rickabaugh
parent f8045272b5
commit 1cb2e6bf5b
11 changed files with 118 additions and 7 deletions

View file

@ -159,7 +159,8 @@ export class PropActionsMenuComponent {
}
private getSignalNode(node: FlatNode): DevtoolsSignalGraphNode | null {
if (node.prop.descriptor.containerType?.includes('Signal')) {
const containerType = node.prop.descriptor.containerType;
if (containerType === 'WritableSignal' || containerType === 'ReadonlySignal') {
return this.signalGraph.graph()?.nodes.find((sn) => sn.label === node.prop.name) ?? null;
}
return null;

View file

@ -0,0 +1,20 @@
load("//devtools/tools:defaults.bzl", "ng_project", "sass_binary")
package(default_visibility = ["//devtools:__subpackages__"])
sass_binary(
name = "styles",
src = "prop-value-highlighter.scss",
)
ng_project(
name = "prop-value-highlighter",
srcs = [
"prop-value-highlighter.directive.ts",
],
deps = [
"//:node_modules/@angular/core",
"//devtools/projects/ng-devtools/src/lib/shared/object-tree-explorer:types",
"//devtools/projects/protocol",
],
)

View file

@ -0,0 +1,53 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {computed, Directive, input} from '@angular/core';
import {Property} from '../object-tree-types';
import {PropType} from '../../../../../../protocol';
/** Should be used in conjunction with prop-value-highlighter.scss */
@Directive({
selector: '[ngPropValueHighlighter]',
host: {
'[class]': 'typeClass()',
},
})
export class PropValueHighlighterDirective {
protected readonly ngPropValueHighlighter = input.required<Property>();
protected readonly typeClass = computed<string>(() => {
const prop = this.ngPropValueHighlighter();
// Containers and class getters can have types.
// Since their preview differs, we don't want to
// use the specific value type highlighting.
if (prop.descriptor.containerType !== null || prop.descriptor.preview === '(...)') {
return 'type-default';
}
switch (prop.descriptor.type) {
case PropType.Number:
case PropType.Boolean:
return 'type-scalar';
case PropType.String:
return 'type-string';
case PropType.Null:
case PropType.Undefined:
return 'type-nullish';
case PropType.BigInt:
case PropType.Date:
case PropType.Set:
case PropType.Map:
case PropType.Symbol:
case PropType.HTMLNode:
return 'type-object-based';
default:
return 'type-default';
}
});
}

View file

@ -0,0 +1,15 @@
.type-scalar {
color: var(--color-prop-value-scalar);
}
.type-string {
color: var(--color-prop-value-string);
}
.type-nullish {
color: var(--color-prop-value-nullish);
}
.type-object-based {
color: var(--color-prop-value-object-based);
}

View file

@ -18,10 +18,12 @@ ng_project(
angular_assets = [
"property-editor.component.html",
":property-editor_styles",
"//devtools/projects/ng-devtools/src/lib/shared/object-tree-explorer/prop-value-highlighter:styles",
],
deps = [
"//:node_modules/@angular/core",
"//:node_modules/@angular/forms",
"//devtools/projects/ng-devtools/src/lib/shared/object-tree-explorer:types",
"//devtools/projects/ng-devtools/src/lib/shared/object-tree-explorer/prop-value-highlighter",
],
)

View file

@ -1,6 +1,8 @@
@switch (currentPropertyState()) {
@case (readState) {
<span class="editable">{{ property().descriptor.preview }}</span>
<span class="editable" [ngPropValueHighlighter]="property()">{{
property().descriptor.preview
}}</span>
}
@case (writeState) {
<div class="value-input">

View file

@ -19,6 +19,7 @@ import {
} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {Property} from '../object-tree-types';
import {PropValueHighlighterDirective} from '../prop-value-highlighter/prop-value-highlighter.directive';
type EditorType = string | number | boolean;
type EditorResult = EditorType | Array<EditorType>;
@ -39,8 +40,11 @@ const parseValue = (value: EditorResult): EditorResult => {
@Component({
templateUrl: './property-editor.component.html',
selector: 'ng-property-editor',
styleUrls: ['./property-editor.component.scss'],
imports: [FormsModule],
styleUrls: [
'./property-editor.component.scss',
'../prop-value-highlighter/prop-value-highlighter.scss',
],
imports: [FormsModule, PropValueHighlighterDirective],
host: {
'(click)': 'onClick()',
},

View file

@ -18,10 +18,12 @@ ng_project(
angular_assets = [
"property-preview.component.html",
":property-preview_styles",
"//devtools/projects/ng-devtools/src/lib/shared/object-tree-explorer/prop-value-highlighter:styles",
],
deps = [
"//:node_modules/@angular/core",
"//devtools/projects/ng-devtools/src/lib/shared/object-tree-explorer:types",
"//devtools/projects/ng-devtools/src/lib/shared/object-tree-explorer/prop-value-highlighter",
"//devtools/projects/protocol",
],
)

View file

@ -1,6 +1,7 @@
<span
class="value"
[class.function]="isClickableProp()"
[ngPropValueHighlighter]="node().prop"
(click)="isClickableProp() && inspect.emit()"
>{{ node().prop.descriptor.preview }}</span
>

View file

@ -9,11 +9,16 @@
import {Component, computed, input, output} from '@angular/core';
import {PropType} from '../../../../../../protocol';
import {FlatNode} from '../object-tree-types';
import {PropValueHighlighterDirective} from '../prop-value-highlighter/prop-value-highlighter.directive';
@Component({
selector: 'ng-property-preview',
templateUrl: './property-preview.component.html',
styleUrls: ['./property-preview.component.scss'],
styleUrls: [
'./property-preview.component.scss',
'../prop-value-highlighter/prop-value-highlighter.scss',
],
imports: [PropValueHighlighterDirective],
})
export class PropertyPreviewComponent {
readonly node = input.required<FlatNode>();

View file

@ -102,7 +102,10 @@ $_colors: (
--color-tree-node-highlighted: #cddffd;
--color-tree-node-matched: #f3ce49;
--color-property-list-name: #71a2f7;
--color-prop-value-scalar: #0842a0;
--color-prop-value-string: #db362e;
--color-prop-value-nullish: #aaaaaa;
--color-prop-value-object-based: #747474;
}
@mixin _dark-colors {
@ -148,7 +151,10 @@ $_colors: (
--color-tree-node-highlighted: #00416c;
--color-tree-node-matched: #906921;
--color-property-list-name: #0c3a96;
--color-prop-value-scalar: #9980ff;
--color-prop-value-string: #5cd5fb;
--color-prop-value-nullish: #6f6f6f;
--color-prop-value-object-based: #ababab;
}
/* Utilities */