Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 26 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ A simple Angular component to display object data in an expandable JSON tree vie

## Key Features

- Expandable/collapsible nodes.
- Configurable initial expansion state and depth.
- Optional click handling for value nodes.
- Display JSON objects and arrays in a collapsible tree structure.
- Customize styling with CSS variables.
- Clickable value nodes for custom interactions.
- Control over initial expansion depth.
- Keyboard navigable

## Demo
Expand Down Expand Up @@ -50,27 +51,28 @@ the desired behavior.

## Theming

Theming can be done with CSS variables

```css
--ngx-json-string /* color of string values */
--ngx-json-number /* color of number values */
--ngx-json-boolean /* color of boolean values */
--ngx-json-date /* color of date values */
--ngx-json-function /* color of function values */
--ngx-json-null /* color of null values */
--ngx-json-null-bg /* background color of null values */
--ngx-json-undefined /* color of undefined values */
--ngx-json-undefined-bg /* background color of undefined values */
--ngx-json-toggler /* color of toggler */
--ngx-json-key /* color of keys */
--ngx-json-punctuation /* color of punctuation (':', '{', '}', etc) */
--ngx-json-value /* color of values */
--ngx-json-undefined-key /* color for key of undefined values */
--ngx-json-label /* color of preview labels */
--ngx-json-font-family /* font-family */
--ngx-json-font-size /* font-size */
```
You can customize the appearance of the tree view using these CSS variables:

| Variable | Description |
| -------------------------- | -------------------------------------- |
| `--ngx-json-font-family` | Font family for the tree view. |
| `--ngx-json-font-size` | Font size for the tree view. |
| `--ngx-json-focus-color` | Outline color for focused elements. |
| `--ngx-json-toggler` | Color of the expand/collapse toggler. |
| `--ngx-json-key` | Color of object keys. |
| `--ngx-json-label` | Color of array indices. |
| `--ngx-json-value` | Default color for primitive values. |
| `--ngx-json-string` | Color for string values. |
| `--ngx-json-number` | Color for number values. |
| `--ngx-json-boolean` | Color for boolean values. |
| `--ngx-json-date` | Color for date values. |
| `--ngx-json-function` | Color for function values. |
| `--ngx-json-null` | Text color for null values. |
| `--ngx-json-null-bg` | Background color for null values. |
| `--ngx-json-undefined` | Text color for undefined values. |
| `--ngx-json-undefined-key` | Key color for undefined values. |
| `--ngx-json-undefined-bg` | Background color for undefined values. |
| `--ngx-json-punctuation` | Color of braces, brackets, and commas. |

## Thanks

Expand Down
36 changes: 24 additions & 12 deletions projects/ngx-json-treeview/src/lib/ngx-json-treeview.component.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<section class="ngx-json-treeview">
<section
class="ngx-json-treeview"
[attr.role]="_currentDepth() === 0 ? 'tree' : null">
@if (segments().length === 0) {
<div [class]="primativeSegmentClass()">
<span class="segment-primitive">
Expand All @@ -21,14 +23,24 @@
@let openingBrace = openingBraceForSegment(segment);
@let closingBrace = closingBraceForSegment(segment);
@let clickableValue = isClickable(segment);
<div [class]="'segment segment-type-' + segment.type">
@let nodeId = id + '-node-label-' + _currentDepth() + '-' + i;

<div
[class]="'segment segment-type-' + segment.type"
role="treeitem"
[attr.aria-expanded]="expandable ? segment.expanded : undefined"
[attr.aria-level]="_currentDepth() + 1"
[attr.aria-setsize]="len"
[attr.aria-posinset]="i + 1"
[attr.aria-labelledby]="nodeId">
<div class="segment-main">
<span
[tabindex]="expandable ? 0 : -1"
<button
[id]="nodeId"
type="button"
[class.expandable]="expandable"
[class.expanded]="segment.expanded"
(click)="toggle(segment)"
(keydown.enter)="toggle(segment)">
[disabled]="!expandable">
@if (expandable) {
<div class="toggler"></div>
}
Expand All @@ -37,7 +49,7 @@
} @else {
<span class="segment-key">{{ `"${segment.key}"` }}</span>
}
</span>
</button>
<span
[class.segment-label]="isArrayElement()"
[class.punctuation]="!isArrayElement()"
Expand All @@ -49,15 +61,15 @@
}}{{ needsComma ? ',' : '' }}</span
>
} @else if (!expandable || !segment.expanded) {
<span
[tabindex]="clickableValue ? 0 : -1"
<button
type="button"
[class.segment-label]="expandable"
[class.segment-value]="!expandable"
[class.clickable]="clickableValue"
(click)="onValueClickHandler(segment)"
(keydown.enter)="onValueClickHandler(segment)"
>{{ segment.description }}</span
>
[disabled]="!clickableValue">
{{ segment.description }}
</button>
<span class="punctuation">{{ needsComma ? ',' : '' }}</span>
} @else {
<span class="punctuation">
Expand All @@ -66,7 +78,7 @@
}
</div>
@if (expandable && segment.expanded) {
<div class="children">
<div class="children" role="group">
<ngx-json-treeview
[json]="segment.value"
[expanded]="expanded()"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,25 @@ $type-colors: (
word-wrap: break-word;
margin: 1px 1px 1px 12px;

> button {
background: none;
border: none;
color: inherit;
font: inherit;
margin: 0;
padding: 0;
text-align: left;

&:disabled {
cursor: default;
}

&:focus-visible {
outline: 1px solid var(--ngx-json-focus-color, #005fcc);
outline-offset: 1px;
}
}

.toggler {
color: var(--ngx-json-toggler, #787878);
font-size: 0.8em;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Component, computed, input, output } from '@angular/core';
import { Component, computed, inject, input, output } from '@angular/core';
import { ID_GENERATOR } from './services/id-generator';
import { decycle, previewString } from './util';

/**
Expand Down Expand Up @@ -153,6 +154,9 @@ export class NgxJsonTreeviewComponent {
});
isArrayElement = computed<boolean>(() => this.rootType() === 'array');

private readonly idGenerator = inject(ID_GENERATOR);
public readonly id = this.idGenerator.next();

isExpandable(segment: Segment) {
return (
(segment.type === 'object' && Object.keys(segment.value).length > 0) ||
Expand Down
42 changes: 42 additions & 0 deletions projects/ngx-json-treeview/src/lib/services/id-generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { InjectionToken } from '@angular/core';

/**
* Interface for a service that generates unique IDs.
* @docs-private
*/
export interface IdGenerator {
next(): string;
}

/**
* Default implementation of `IdGenerator` for client-side applications.
* This implementation is not safe for server-side rendering, as the counter
* is shared across all requests.
*/
class DefaultIdGenerator implements IdGenerator {
private nextId = 0;

next(): string {
return `ngx-json-treeview-${this.nextId++}`;
}
}

/**
* Injection token for the `IdGenerator` service.
*
* For server-side rendering, it's crucial to provide this token at a request level
* to ensure that IDs are unique per request. Failure to do so will result in
* ID mismatches between the server-rendered HTML and the client-side application,
* breaking hydration.
*
* Example for server-side provider in `server.ts` or equivalent:
*
* ```typescript
* // In your server-side providers:
* { provide: ID_GENERATOR, useClass: ServerIdGenerator }
* ```
*/
export const ID_GENERATOR = new InjectionToken<IdGenerator>('ID_GENERATOR', {
providedIn: 'root',
factory: () => new DefaultIdGenerator(),
});