ngx-json-treeview
Version:
A simple Angular component to display object data in an expandable JSON tree view.
501 lines (493 loc) • 33.4 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, input, output, computed, inject, Component } from '@angular/core';
/**
* A handler that checks if a segment's value is a string that looks like an
* HTTP/HTTPS link. If it is, it opens the link in a new tab.
*/
const followLinkHandler = {
canHandle: (segment) => {
if (typeof segment.value === 'string' && segment.value.startsWith('http')) {
try {
const url = new URL(segment.value); // Validate the URL.
return url.protocol === 'http:' || url.protocol === 'https:';
}
catch (e) {
// Invalid URL.
}
}
return false;
},
handler: (segment) => {
window.open(segment.value, '_blank', 'noopener,noreferrer');
},
};
/**
* A collection of built-in value click handlers.
* This array can be used to easily apply all default handlers.
*/
const VALUE_CLICK_HANDLERS = [
followLinkHandler,
];
/**
* A namespace for individual value click handlers.
* This allows for easy discovery and individual import of handlers.
*/
const ValueClickHandlers = {
followLinkHandler,
};
/**
* 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 {
nextId = 0;
next() {
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 }
* ```
*/
const ID_GENERATOR = new InjectionToken('ID_GENERATOR', {
providedIn: 'root',
factory: () => new DefaultIdGenerator(),
});
/**
* Generates a preview string representation of an object.
*
* @param obj The object to preview.
* @param limit The maximum length of the preview string. Defaults to 200.
* @param stringsLimit The maximum length of a string to display before
* truncating. Defaults to 10.
* @returns A preview string representation of the object.
*/
function previewString(obj, limit = 200, stringsLimit = 10) {
let result = '';
if (obj === null) {
result += 'null';
}
else if (obj === undefined) {
result += 'undefined';
}
else if (typeof obj === 'string') {
if (obj.length > stringsLimit) {
result += `"${obj.substring(0, stringsLimit)}…"`;
}
else {
result += `"${obj}"`;
}
}
else if (typeof obj === 'boolean') {
result += `${obj ? 'true' : 'false'}`;
}
else if (typeof obj === 'number') {
result += `${obj}`;
}
else if (typeof obj === 'object') {
if (obj instanceof Date) {
result += `"${obj.toISOString()}"`;
}
else if (Array.isArray(obj)) {
result += `Array[${obj.length}] [`;
for (const key in obj) {
if (result.length >= limit) {
break;
}
result += previewString(obj[key], limit - result.length);
result += ',';
}
if (result.endsWith(',')) {
result = result.slice(0, -1);
}
result += ']';
}
else {
result += 'Object {';
for (const key in obj) {
if (result.length >= limit) {
break;
}
if (obj[key] !== undefined) {
result += `"${key}":`;
result += previewString(obj[key], limit - result.length);
result += ',';
}
}
if (result.endsWith(',')) {
result = result.slice(0, -1);
}
result += '}';
}
}
else if (typeof obj === 'function') {
result += 'Function';
}
if (result.length >= limit) {
return result.substring(0, limit) + '…';
}
return result;
}
/**
* Decycles a JavaScript object by replacing circular references with `$ref`
* properties. This is useful for serializing objects that contain circular
* references, preventing infinite loops.
*
* Original: https://github.com/douglascrockford/JSON-js/blob/master/cycle.js
*
* @param object The object to decycle.
* @returns A decycled version of the object.
*/
function decycle(object) {
const objects = new WeakMap();
return (function derez(value, path) {
let old_path;
let nu;
if (typeof value === 'object' &&
value !== null &&
!(value instanceof Boolean) &&
!(value instanceof Date) &&
!(value instanceof Number) &&
!(value instanceof RegExp) &&
!(value instanceof String)) {
old_path = objects.get(value);
if (old_path !== undefined) {
return { $ref: old_path };
}
objects.set(value, path);
if (Array.isArray(value)) {
nu = [];
value.forEach(function (element, i) {
nu[i] = derez(element, path + '[' + i + ']');
});
}
else {
nu = {};
Object.keys(value).forEach(function (name) {
nu[name] = derez(value[name], path + '[' + JSON.stringify(name) + ']');
});
}
return nu;
}
return value;
})(object, '$');
}
/**
* Renders JSON data in an expandable and collapsible tree structure.
* Allows users to navigate complex data hierarchies visually.
*/
class NgxJsonTreeviewComponent {
/**
* The JSON object or array to display in the tree view.
* @required
*/
json = input.required();
/**
* Controls the default expansion state for all expandable segments
* i.e. objects and arrays.
* - If `true`, nodes are expanded down to the specified `depth`.
* - If `false`, all nodes start collapsed.
* @default true
*/
expanded = input(true);
/**
* Determines the maximum nesting level automatically expanded when `expanded`
* is `true`.
* - `-1`: Infinite depth (all levels expanded).
* - `0`: Only the root node is expanded (if applicable).
* - `n`: Root and nodes down to `n` levels are expanded.
* @default -1
*/
depth = input(-1);
/**
* If `true`, values are clickable when there is a corresponding handler
* in the `valueClickHandlers` array that can process it.
*
* This allows for use cases such as:
* - Following hyperlinks.
* - Copying a value to the clipboard.
* - Triggering custom actions based on the value's content or type.
* @default false
*/
enableClickableValues = input(false);
/**
* @deprecated Use `valueClickHandlers` instead. This input will be removed
* in a future version.
*
* A function that determines if a specific value node should be considered
* clickable. This provides more granular control than the global
* `enableClickableValues` flag.
*
* The function receives the `Segment` object and should return `true` if the
* value is clickable, `false` otherwise. This check is only performed if
* `enableClickableValues` is also `true`.
*
* @param segment - The segment being evaluated.
* @returns `true` if the segment's value should be clickable, `false`
* otherwise.
*/
isClickableValue = input();
/**
* @deprecated Use `valueClickHandlers` instead. This output will be removed
* in a future version.
*
* If `enableClickableValues` is set to `true`, emits a `Segment` object when
* a value node is clicked. The emitted `Segment` contains details about the
* clicked node (key, value, type, path, etc.).
*/
onValueClick = output();
/**
* An array of handler functions to be executed when a value node is clicked.
* Only the first handler in the array for which `isClickable` returns `true`
* will be executed.
*
* If `enableClickableValues` is set to true, but `valueClickHandlers` is
* omitted, the built-in `VALUE_CLICK_HANDLERS` will be used as the default.
*/
valueClickHandlers = input();
/**
* *Internal* input representing the parent segment in the tree hierarchy.
* Primrily used for calculating paths.
* @internal
*/
_parent = input();
/**
* *Internal* input representing the current nesting depth. Used in
* conjunction with the `depth` input to control expansion.
* @internal
*/
_currentDepth = input(0);
internalValueClickHandlers = computed(() => {
const handlers = [];
const legacyIsClickableFn = this.isClickableValue();
if (legacyIsClickableFn) {
handlers.push({
canHandle: legacyIsClickableFn,
handler: (segment) => this.onValueClick.emit(segment),
});
}
handlers.push(...(this.valueClickHandlers() ?? VALUE_CLICK_HANDLERS));
return handlers;
});
rootType = computed(() => {
if (this.json() === null) {
return 'null';
}
else if (Array.isArray(this.json())) {
return 'array';
}
else
return typeof this.json();
});
segments = computed(() => {
const json = decycle(this.json());
if (typeof json === 'object' && json != null) {
return Object.keys(json).map((key) => this.parseKeyValue(key, json[key]));
}
return [];
});
isExpanded = computed(() => this.expanded() &&
!(this.depth() > -1 && this._currentDepth() >= this.depth()));
openingBrace = computed(() => {
if (this.rootType() === 'array') {
return '[';
}
else
return '{';
});
closingBrace = computed(() => {
if (this.rootType() === 'array') {
return ']';
}
else
return '}';
});
asString = computed(() => JSON.stringify(this.json(), null, 2).trim());
primitiveSegmentClass = computed(() => {
const type = this.rootType();
if (['object', 'array'].includes(type)) {
return 'punctuation';
}
return 'segment-type-' + type;
});
primitiveSegment = computed(() => {
if (this.segments().length > 0)
return null;
return {
key: '',
value: this.json(),
type: this.rootType(),
description: this.asString(),
expanded: false,
path: this._parent()?.path ?? '',
};
});
isClickablePrimitive = computed(() => {
const segment = this.primitiveSegment();
return !!segment && this.isClickable(segment);
});
isArrayElement = computed(() => this.rootType() === 'array');
idGenerator = inject(ID_GENERATOR);
id = this.idGenerator.next();
isExpandable(segment) {
return ((segment.type === 'object' && Object.keys(segment.value).length > 0) ||
(segment.type === 'array' && segment.value.length > 0));
}
isEmpty(segment) {
return ((segment.type === 'object' && Object.keys(segment.value).length === 0) ||
(segment.type === 'array' && segment.value.length === 0));
}
isClickable(segment) {
if (!this.enableClickableValues()) {
return false;
}
return this.internalValueClickHandlers().some((handler) => {
try {
return handler.canHandle(segment);
}
catch (e) {
return false;
}
});
}
toggle(segment) {
if (this.isExpandable(segment)) {
segment.expanded = !segment.expanded;
}
}
onPrimitiveClick() {
const segment = this.primitiveSegment();
if (segment) {
this.onValueClickHandler(segment);
}
}
onValueClickHandler(segment) {
for (const handler of this.internalValueClickHandlers()) {
try {
if (handler.canHandle(segment)) {
try {
handler.handler(segment);
}
catch (e) {
console.error('Error executing click handler:', e);
}
return; // Stop after the first handler is executed.
}
}
catch (e) {
// in case of any error, continue to the next handler
}
}
}
openingBraceForSegment(segment) {
if (segment.type === 'array') {
return '[';
}
else if (segment.type === 'object') {
return '{';
}
return undefined;
}
closingBraceForSegment(segment) {
if (segment.type === 'array') {
return ']';
}
else if (segment.type === 'object') {
return '}';
}
return undefined;
}
getPath(key) {
const parent = this._parent();
let path;
if (parent) {
if (parent.type === 'array') {
path = `${parent.path}[${key}]`;
}
else {
path = `${parent.path}.${key}`;
}
}
else {
path = key;
}
return path;
}
parseKeyValue(key, value) {
const segment = {
parent: this._parent(),
path: this.getPath(key),
key: key,
value: value,
type: undefined,
description: '' + value,
expanded: this.isExpanded(),
};
switch (typeof segment.value) {
case 'number':
segment.type = 'number';
break;
case 'boolean':
segment.type = 'boolean';
break;
case 'function':
segment.type = 'function';
break;
case 'string':
segment.type = 'string';
segment.description = JSON.stringify(segment.value);
break;
case 'undefined':
segment.type = 'undefined';
segment.description = 'undefined';
break;
case 'object':
if (segment.value === null) {
segment.type = 'null';
segment.description = 'null';
}
else if (Array.isArray(segment.value)) {
segment.type = 'array';
segment.description = previewString(segment.value);
}
else if (segment.value instanceof Date) {
segment.type = 'date';
segment.description = `"${segment.value.toISOString()}"`;
}
else {
segment.type = 'object';
segment.description = previewString(segment.value);
}
break;
default:
console.error('Unknown type parsing json key/value.');
}
return segment;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.1", ngImport: i0, type: NgxJsonTreeviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.1", type: NgxJsonTreeviewComponent, isStandalone: true, selector: "ngx-json-treeview", inputs: { json: { classPropertyName: "json", publicName: "json", isSignal: true, isRequired: true, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null }, depth: { classPropertyName: "depth", publicName: "depth", isSignal: true, isRequired: false, transformFunction: null }, enableClickableValues: { classPropertyName: "enableClickableValues", publicName: "enableClickableValues", isSignal: true, isRequired: false, transformFunction: null }, isClickableValue: { classPropertyName: "isClickableValue", publicName: "isClickableValue", isSignal: true, isRequired: false, transformFunction: null }, valueClickHandlers: { classPropertyName: "valueClickHandlers", publicName: "valueClickHandlers", isSignal: true, isRequired: false, transformFunction: null }, _parent: { classPropertyName: "_parent", publicName: "_parent", isSignal: true, isRequired: false, transformFunction: null }, _currentDepth: { classPropertyName: "_currentDepth", publicName: "_currentDepth", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onValueClick: "onValueClick" }, ngImport: i0, template: "<section\n class=\"ngx-json-treeview\"\n [attr.role]=\"_currentDepth() === 0 ? 'tree' : null\">\n @if (segments().length === 0) {\n <div [class]=\"primitiveSegmentClass()\">\n <button\n type=\"button\"\n class=\"segment-primitive\"\n [class.clickable]=\"isClickablePrimitive()\"\n (click)=\"onPrimitiveClick()\"\n [disabled]=\"!isClickablePrimitive()\">\n {{ asString() }}\n </button>\n </div>\n } @else {\n @if (_currentDepth() === 0) {\n <span class=\"punctuation\">{{ openingBrace() }}</span>\n }\n @for (\n segment of segments();\n track segment;\n let i = $index;\n let len = $count\n ) {\n @let needsComma = i < len - 1;\n @let expandable = isExpandable(segment);\n @let empty = isEmpty(segment);\n @let openingBrace = openingBraceForSegment(segment);\n @let closingBrace = closingBraceForSegment(segment);\n @let clickableValue = isClickable(segment);\n @let nodeId = id + '-node-label-' + _currentDepth() + '-' + i;\n\n <div\n [class]=\"'segment segment-type-' + segment.type\"\n role=\"treeitem\"\n [attr.aria-expanded]=\"expandable ? segment.expanded : undefined\"\n [attr.aria-level]=\"_currentDepth() + 1\"\n [attr.aria-setsize]=\"len\"\n [attr.aria-posinset]=\"i + 1\"\n [attr.aria-labelledby]=\"nodeId\">\n <div class=\"segment-main\">\n <button\n [id]=\"nodeId\"\n type=\"button\"\n [class.expandable]=\"expandable\"\n [class.expanded]=\"segment.expanded\"\n (click)=\"toggle(segment)\"\n [disabled]=\"!expandable\">\n @if (expandable) {\n <div class=\"toggler\"></div>\n }\n @if (isArrayElement()) {\n <span class=\"segment-label\">{{ segment.key }}</span>\n } @else {\n <span class=\"segment-key\">{{ `\"${segment.key}\"` }}</span>\n }\n </button>\n <span\n [class.segment-label]=\"isArrayElement()\"\n [class.punctuation]=\"!isArrayElement()\"\n >:\n </span>\n @if (empty) {\n <span class=\"punctuation\"\n >{{ openingBrace }}{{ closingBrace\n }}{{ needsComma ? ',' : '' }}</span\n >\n } @else if (!expandable || !segment.expanded) {\n <button\n type=\"button\"\n [class.segment-label]=\"expandable\"\n [class.segment-value]=\"!expandable\"\n [class.clickable]=\"clickableValue\"\n (click)=\"onValueClickHandler(segment)\"\n [disabled]=\"!clickableValue\">\n {{ segment.description }}\n </button>\n <span class=\"punctuation\">{{ needsComma ? ',' : '' }}</span>\n } @else {\n <span class=\"punctuation\">\n {{ openingBrace }}\n </span>\n }\n </div>\n @if (expandable && segment.expanded) {\n <div class=\"children\" role=\"group\">\n <ngx-json-treeview\n [json]=\"segment.value\"\n [expanded]=\"expanded()\"\n [depth]=\"depth()\"\n [enableClickableValues]=\"enableClickableValues()\"\n [valueClickHandlers]=\"valueClickHandlers()\"\n [isClickableValue]=\"isClickableValue()\"\n (onValueClick)=\"onValueClickHandler($event)\"\n [_parent]=\"segment\"\n [_currentDepth]=\"_currentDepth() + 1\" />\n <span class=\"punctuation\">\n {{ closingBrace }}{{ needsComma ? ',' : '' }}\n </span>\n </div>\n }\n </div>\n }\n @if (_currentDepth() === 0) {\n <span class=\"punctuation\">{{ closingBrace() }}</span>\n }\n }\n</section>\n", styles: ["@charset \"UTF-8\";.ngx-json-treeview{font-family:var(--ngx-json-font-family, monospace);font-size:var(--ngx-json-font-size, 1em);overflow:hidden;position:relative}.ngx-json-treeview .segment{margin:1px 1px 1px 12px;padding:2px}.ngx-json-treeview .segment .segment-main{word-wrap:break-word;margin:1px 1px 1px 12px}.ngx-json-treeview .segment .segment-main .toggler{color:var(--ngx-json-toggler, #787878);font-size:.8em;line-height:1.2em;margin-left:-14px;margin-top:3px;position:absolute;vertical-align:middle}.ngx-json-treeview .segment .segment-main .toggler:after{content:\"\\25b6\";display:inline-block;transition:transform .1s ease-in}.ngx-json-treeview .segment .segment-main .segment-key{color:var(--ngx-json-key, #a31515)}.ngx-json-treeview .segment .segment-main .segment-label{color:var(--ngx-json-label, #656e77);font-style:italic}.ngx-json-treeview .segment .segment-main .segment-value{color:var(--ngx-json-value, #000)}.ngx-json-treeview .segment .children{margin-left:12px}.ngx-json-treeview .segment-type-string>.segment-main>.segment-value{color:var(--ngx-json-string, #0451a5)}.ngx-json-treeview .segment-type-number>.segment-main>.segment-value{color:var(--ngx-json-number, #098658)}.ngx-json-treeview .segment-type-boolean>.segment-main>.segment-value{color:var(--ngx-json-boolean, #a31515)}.ngx-json-treeview .segment-type-date>.segment-main>.segment-value{color:var(--ngx-json-date, #05668d)}.ngx-json-treeview .segment-type-function>.segment-main>.segment-value{color:var(--ngx-json-function, #656e77)}.ngx-json-treeview .segment-type-null>.segment-main>.segment-value{color:var(--ngx-json-null, #fff)}.ngx-json-treeview .segment-type-undefined>.segment-main>.segment-value{color:var(--ngx-json-undefined, #fff)}.ngx-json-treeview .segment-type-null>.segment-main>.segment-value{background-color:var(--ngx-json-null-bg, red)}.ngx-json-treeview .segment-type-undefined>.segment-main>.segment-key{color:var(--ngx-json-undefined-key, #a31515)}.ngx-json-treeview .segment-type-undefined>.segment-main>.segment-value{background-color:var(--ngx-json-undefined-bg, #656e77)}.ngx-json-treeview .segment-type-object>.segment-main,.ngx-json-treeview .segment-type-array>.segment-main{white-space:nowrap}.ngx-json-treeview .expanded>.toggler:after{transform:rotate(90deg)}.ngx-json-treeview .expandable,.ngx-json-treeview .expandable>.toggler{cursor:pointer}.ngx-json-treeview .clickable{cursor:pointer;text-decoration:none}.ngx-json-treeview .clickable:hover{text-decoration:underline}.ngx-json-treeview .punctuation{color:var(--ngx-json-punctuation, #000)}.ngx-json-treeview button{background:none;border:none;color:inherit;font:inherit;margin:0;padding:0;text-align:left}.ngx-json-treeview button:disabled{cursor:default}.ngx-json-treeview button:focus-visible{outline:1px solid var(--ngx-json-focus-color, #005fcc);outline-offset:1px}.ngx-json-treeview .segment-type-string>.segment-primitive{color:var(--ngx-json-string, #0451a5)}.ngx-json-treeview .segment-type-number>.segment-primitive{color:var(--ngx-json-number, #098658)}.ngx-json-treeview .segment-type-boolean>.segment-primitive{color:var(--ngx-json-boolean, #a31515)}.ngx-json-treeview .segment-type-date>.segment-primitive{color:var(--ngx-json-date, #05668d)}.ngx-json-treeview .segment-type-function>.segment-primitive{color:var(--ngx-json-function, #656e77)}.ngx-json-treeview .segment-type-null>.segment-primitive{color:var(--ngx-json-null, #fff)}.ngx-json-treeview .segment-type-undefined>.segment-primitive{color:var(--ngx-json-undefined, #fff)}.ngx-json-treeview .segment-primitive{margin:0;padding:0}.ngx-json-treeview .segment-type-null>.segment-primitive{background-color:var(--ngx-json-null-bg, red)}\n"], dependencies: [{ kind: "component", type: NgxJsonTreeviewComponent, selector: "ngx-json-treeview", inputs: ["json", "expanded", "depth", "enableClickableValues", "isClickableValue", "valueClickHandlers", "_parent", "_currentDepth"], outputs: ["onValueClick"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.1", ngImport: i0, type: NgxJsonTreeviewComponent, decorators: [{
type: Component,
args: [{ selector: 'ngx-json-treeview', imports: [], template: "<section\n class=\"ngx-json-treeview\"\n [attr.role]=\"_currentDepth() === 0 ? 'tree' : null\">\n @if (segments().length === 0) {\n <div [class]=\"primitiveSegmentClass()\">\n <button\n type=\"button\"\n class=\"segment-primitive\"\n [class.clickable]=\"isClickablePrimitive()\"\n (click)=\"onPrimitiveClick()\"\n [disabled]=\"!isClickablePrimitive()\">\n {{ asString() }}\n </button>\n </div>\n } @else {\n @if (_currentDepth() === 0) {\n <span class=\"punctuation\">{{ openingBrace() }}</span>\n }\n @for (\n segment of segments();\n track segment;\n let i = $index;\n let len = $count\n ) {\n @let needsComma = i < len - 1;\n @let expandable = isExpandable(segment);\n @let empty = isEmpty(segment);\n @let openingBrace = openingBraceForSegment(segment);\n @let closingBrace = closingBraceForSegment(segment);\n @let clickableValue = isClickable(segment);\n @let nodeId = id + '-node-label-' + _currentDepth() + '-' + i;\n\n <div\n [class]=\"'segment segment-type-' + segment.type\"\n role=\"treeitem\"\n [attr.aria-expanded]=\"expandable ? segment.expanded : undefined\"\n [attr.aria-level]=\"_currentDepth() + 1\"\n [attr.aria-setsize]=\"len\"\n [attr.aria-posinset]=\"i + 1\"\n [attr.aria-labelledby]=\"nodeId\">\n <div class=\"segment-main\">\n <button\n [id]=\"nodeId\"\n type=\"button\"\n [class.expandable]=\"expandable\"\n [class.expanded]=\"segment.expanded\"\n (click)=\"toggle(segment)\"\n [disabled]=\"!expandable\">\n @if (expandable) {\n <div class=\"toggler\"></div>\n }\n @if (isArrayElement()) {\n <span class=\"segment-label\">{{ segment.key }}</span>\n } @else {\n <span class=\"segment-key\">{{ `\"${segment.key}\"` }}</span>\n }\n </button>\n <span\n [class.segment-label]=\"isArrayElement()\"\n [class.punctuation]=\"!isArrayElement()\"\n >:\n </span>\n @if (empty) {\n <span class=\"punctuation\"\n >{{ openingBrace }}{{ closingBrace\n }}{{ needsComma ? ',' : '' }}</span\n >\n } @else if (!expandable || !segment.expanded) {\n <button\n type=\"button\"\n [class.segment-label]=\"expandable\"\n [class.segment-value]=\"!expandable\"\n [class.clickable]=\"clickableValue\"\n (click)=\"onValueClickHandler(segment)\"\n [disabled]=\"!clickableValue\">\n {{ segment.description }}\n </button>\n <span class=\"punctuation\">{{ needsComma ? ',' : '' }}</span>\n } @else {\n <span class=\"punctuation\">\n {{ openingBrace }}\n </span>\n }\n </div>\n @if (expandable && segment.expanded) {\n <div class=\"children\" role=\"group\">\n <ngx-json-treeview\n [json]=\"segment.value\"\n [expanded]=\"expanded()\"\n [depth]=\"depth()\"\n [enableClickableValues]=\"enableClickableValues()\"\n [valueClickHandlers]=\"valueClickHandlers()\"\n [isClickableValue]=\"isClickableValue()\"\n (onValueClick)=\"onValueClickHandler($event)\"\n [_parent]=\"segment\"\n [_currentDepth]=\"_currentDepth() + 1\" />\n <span class=\"punctuation\">\n {{ closingBrace }}{{ needsComma ? ',' : '' }}\n </span>\n </div>\n }\n </div>\n }\n @if (_currentDepth() === 0) {\n <span class=\"punctuation\">{{ closingBrace() }}</span>\n }\n }\n</section>\n", styles: ["@charset \"UTF-8\";.ngx-json-treeview{font-family:var(--ngx-json-font-family, monospace);font-size:var(--ngx-json-font-size, 1em);overflow:hidden;position:relative}.ngx-json-treeview .segment{margin:1px 1px 1px 12px;padding:2px}.ngx-json-treeview .segment .segment-main{word-wrap:break-word;margin:1px 1px 1px 12px}.ngx-json-treeview .segment .segment-main .toggler{color:var(--ngx-json-toggler, #787878);font-size:.8em;line-height:1.2em;margin-left:-14px;margin-top:3px;position:absolute;vertical-align:middle}.ngx-json-treeview .segment .segment-main .toggler:after{content:\"\\25b6\";display:inline-block;transition:transform .1s ease-in}.ngx-json-treeview .segment .segment-main .segment-key{color:var(--ngx-json-key, #a31515)}.ngx-json-treeview .segment .segment-main .segment-label{color:var(--ngx-json-label, #656e77);font-style:italic}.ngx-json-treeview .segment .segment-main .segment-value{color:var(--ngx-json-value, #000)}.ngx-json-treeview .segment .children{margin-left:12px}.ngx-json-treeview .segment-type-string>.segment-main>.segment-value{color:var(--ngx-json-string, #0451a5)}.ngx-json-treeview .segment-type-number>.segment-main>.segment-value{color:var(--ngx-json-number, #098658)}.ngx-json-treeview .segment-type-boolean>.segment-main>.segment-value{color:var(--ngx-json-boolean, #a31515)}.ngx-json-treeview .segment-type-date>.segment-main>.segment-value{color:var(--ngx-json-date, #05668d)}.ngx-json-treeview .segment-type-function>.segment-main>.segment-value{color:var(--ngx-json-function, #656e77)}.ngx-json-treeview .segment-type-null>.segment-main>.segment-value{color:var(--ngx-json-null, #fff)}.ngx-json-treeview .segment-type-undefined>.segment-main>.segment-value{color:var(--ngx-json-undefined, #fff)}.ngx-json-treeview .segment-type-null>.segment-main>.segment-value{background-color:var(--ngx-json-null-bg, red)}.ngx-json-treeview .segment-type-undefined>.segment-main>.segment-key{color:var(--ngx-json-undefined-key, #a31515)}.ngx-json-treeview .segment-type-undefined>.segment-main>.segment-value{background-color:var(--ngx-json-undefined-bg, #656e77)}.ngx-json-treeview .segment-type-object>.segment-main,.ngx-json-treeview .segment-type-array>.segment-main{white-space:nowrap}.ngx-json-treeview .expanded>.toggler:after{transform:rotate(90deg)}.ngx-json-treeview .expandable,.ngx-json-treeview .expandable>.toggler{cursor:pointer}.ngx-json-treeview .clickable{cursor:pointer;text-decoration:none}.ngx-json-treeview .clickable:hover{text-decoration:underline}.ngx-json-treeview .punctuation{color:var(--ngx-json-punctuation, #000)}.ngx-json-treeview button{background:none;border:none;color:inherit;font:inherit;margin:0;padding:0;text-align:left}.ngx-json-treeview button:disabled{cursor:default}.ngx-json-treeview button:focus-visible{outline:1px solid var(--ngx-json-focus-color, #005fcc);outline-offset:1px}.ngx-json-treeview .segment-type-string>.segment-primitive{color:var(--ngx-json-string, #0451a5)}.ngx-json-treeview .segment-type-number>.segment-primitive{color:var(--ngx-json-number, #098658)}.ngx-json-treeview .segment-type-boolean>.segment-primitive{color:var(--ngx-json-boolean, #a31515)}.ngx-json-treeview .segment-type-date>.segment-primitive{color:var(--ngx-json-date, #05668d)}.ngx-json-treeview .segment-type-function>.segment-primitive{color:var(--ngx-json-function, #656e77)}.ngx-json-treeview .segment-type-null>.segment-primitive{color:var(--ngx-json-null, #fff)}.ngx-json-treeview .segment-type-undefined>.segment-primitive{color:var(--ngx-json-undefined, #fff)}.ngx-json-treeview .segment-primitive{margin:0;padding:0}.ngx-json-treeview .segment-type-null>.segment-primitive{background-color:var(--ngx-json-null-bg, red)}\n"] }]
}] });
/*
* Public API Surface of ngx-json-treeview
*/
/**
* Generated bundle index. Do not edit.
*/
export { NgxJsonTreeviewComponent, VALUE_CLICK_HANDLERS, ValueClickHandlers, followLinkHandler };
//# sourceMappingURL=ngx-json-treeview.mjs.map