UNPKG

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
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