UNPKG

@ux-aspects/ux-aspects

Version:

Open source user interface framework for building modern, responsive, mobile big data applications

830 lines 130 kB
/* eslint-disable @typescript-eslint/no-empty-object-type */ import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { DOWN_ARROW, ENTER, LEFT_ARROW, RIGHT_ARROW, UP_ARROW } from '@angular/cdk/keycodes'; import { DomPortalOutlet, TemplatePortal } from '@angular/cdk/portal'; import { ApplicationRef, ChangeDetectionStrategy, Component, ComponentFactoryResolver, ContentChild, ElementRef, EventEmitter, Injector, Input, NgZone, Output, Renderer2, ViewChild, ViewContainerRef, inject, } from '@angular/core'; import { hierarchy, interpolate, linkVertical, select, transition, tree, zoom, zoomTransform, } from 'd3'; import { Subject } from 'rxjs'; import { debounceTime, takeUntil } from 'rxjs/operators'; import { FocusIndicatorService } from '../../directives/accessibility/index'; import { ResizeService } from '../../directives/resize/index'; import * as i0 from "@angular/core"; import * as i1 from "../../directives/accessibility/focus-indicator/focus-indicator-origin/focus-indicator-origin.directive"; import * as i2 from "@angular/common"; import * as i3 from "../icon/icon.component"; export class OrganizationChartComponent { constructor() { this._resizeService = inject(ResizeService); this._componentFactoryResolver = inject(ComponentFactoryResolver); this._injector = inject(Injector); this._elementRef = inject(ElementRef); this._appRef = inject(ApplicationRef); this._viewContainerRef = inject(ViewContainerRef); this._renderer = inject(Renderer2); this._focusIndicator = inject(FocusIndicatorService); this._ngZone = inject(NgZone); /** Define the presentation of the connectors */ this.connector = 'elbow'; /** Define the duration of the transition animations */ this.duration = 750; /** Define whether or not we can reveal additional parents */ this.showReveal = false; /** Define the aria label for the reveal button */ this.revealAriaLabel = 'Reveal More'; /** Emit whenever a node is selected */ this.selectedChange = new EventEmitter(true); /** Emit whenever the reveal button is pressed */ this.reveal = new EventEmitter(); /** Emit when the transition ends */ this.transitionEnd = new EventEmitter(); this._toggleNodesOnClick = true; /** Store a flattened array of nodes */ this._nodeLayout = []; /** Store a flattened array of links */ this._linkLayout = []; /** Store the portal/outlets associated with some data */ this._portals = new Map(); /** Store the focus indicators associated with nodes */ this._indicators = new Map(); /** Store whether or not a transition is in progress */ this._isTransitioning = false; /** Store whether or not a camera pan is in progress */ this._isPanning = false; /** Determine if the component is initialised */ this._isInitialised = false; /** Determine if the connector type has changed since the last render */ this._hasConnectorChanged = false; /** Automatically unsubscribe from all subscriptions on destroy */ this._onDestroy = new Subject(); } /** Defines whether nodes can be toggled or not */ set toggleNodesOnClick(toggleNodesOnClick) { this._toggleNodesOnClick = coerceBooleanProperty(toggleNodesOnClick); } get toggleNodesOnClick() { return this._toggleNodesOnClick; } /** Programmatically select an item */ set selected(selected) { if (this.selected === selected || !selected) { return; } if (this._isInitialised) { this.select(selected); this.centerNode(selected); } else { this._pendingSelection = selected; } } ngAfterViewInit() { // before we do anything ensure they have provided a template if (!this.nodeTemplate) { throw new Error('Organization Chart - You must provide a node template!'); } if (!this.nodeWidth || !this.nodeHeight) { throw new Error('Organization Chart - You must specify a nodeWidth and nodeHeight'); } // create the zoom drag listener this._zoom = zoom() .scaleExtent([1, 1]) .interpolate(interpolate) .on('zoom', this.applyCameraPosition.bind(this)) .on('end', () => { if (!this._isPanning) { this.ensureNodesAreVisible(); } }); // set up the selections this._linksContainer = select(this.linksContainer.nativeElement); this._nodesContainer = select(this.nodesContainer.nativeElement); // setup the zoom on the node layer this._ngZone.runOutsideAngular(() => this._nodesContainer.call(this._zoom)); // perform the initial render this.render(); // ensure we set the initial chart size this._width = this._elementRef.nativeElement.offsetWidth; this._height = this._elementRef.nativeElement.offsetHeight; // watch for any resizing of the chart const resize$ = this._resizeService.addResizeListener(this._elementRef.nativeElement); // on size change immediate update the width and height measurements resize$.pipe(takeUntil(this._onDestroy)).subscribe(this.onResize.bind(this)); // after a debounce ensure nodes are visible resize$ .pipe(takeUntil(this._onDestroy), debounceTime(this.duration)) .subscribe(this.ensureNodesAreVisible.bind(this)); // initially horizontally center the root node this.centerNode(this.dataset, OrganizationChartAxis.Horizontal, false); // initally move the camera down slightly so the root node does not appear at the very top of the chart this.moveCamera(0, 150, false); // mark this component as initialised this._isInitialised = true; } ngOnChanges(changes) { if (changes.connector && !changes.connector.firstChange) { this._hasConnectorChanged = true; } // if only the selected property has changed then don't re-render as this is handled by the setter if (Object.keys(changes).length === 1 && changes.selected) { return; } if (this._isInitialised) { this.render(); } } ngOnDestroy() { this._onDestroy.next(); this._onDestroy.complete(); // correctly dispose all portals and outlets this._portals.forEach(node => { node.portal.detach(); node.outlet.dispose(); }); } /** Perform the actual rendering of the chart */ render() { // perform the layout algorithm on the current dataset this.updateLayout(); // select all the existing links and nodes this.updateSelections(); // create a d3 transition based in the specified transition time const defaultTransition = transition('organizationChartDefaultTransition') .duration(this.duration) .on('start', () => (this._isTransitioning = true)) .on('end', () => { this._isTransitioning = false; this.transitionEnd.emit(); }); // render the links when they are first added to the DOM this._links .enter() .insert('path') .attr('class', 'ux-organization-chart-link') .attr('d', link => this.getLinkPath(link)) .attr('opacity', -2) .transition(defaultTransition) .attr('d', link => this.getLinkPath(link)) .attr('opacity', 1); // define the standard transition while the link is 'alive' this._links .transition() .duration(this._hasConnectorChanged ? 0 : this.duration) .attr('d', link => this.getLinkPath(link)); // apply transitions when removing nodes this._links .exit() .transition(defaultTransition) .attr('d', (link) => this.getCollapsedLinkPath(link)) .attr('opacity', 0) .remove(); // when a node is first added to the DOM position it this._nodes .enter() .append('div') .attr('class', 'ux-organization-chart-node') .style('width', this.nodeWidth + 'px') .style('height', this.nodeHeight + 'px') .style('left', node => (node.parent ? node.parent.x : node.x) + 'px') .style('top', node => (node.parent ? node.parent.y : node.y) + 'px') .style('opacity', 0) .on('keydown', (event, node) => this.onKeydown(event, node)) .on('focus', (event, node) => this.onFocus(node)) .on('click', (event, node) => this.onClick(node)) .each(this.renderNodeTemplate.bind(this)) .each((node, index, group) => this.monitorFocus(group[index], node)) .transition(defaultTransition) .style('left', node => node.x + 'px') .style('top', node => node.y + 'px') .style('opacity', 1); // apply any movements while nodes are 'alive' this._nodes .transition(defaultTransition) .style('left', node => node.x + 'px') .style('top', node => node.y + 'px'); // apply transitions when removing nodes this._nodes .exit() .transition(defaultTransition) .style('left', (node) => (node.parent ? node.parent.x : node.x) + 'px') .style('opacity', 0) .remove() .on('end', (node) => this.destroyNode(node)); // update the position of the reveal button select(this.revealElement.nativeElement) .style('left', this.nodeWidth / 2 - this.revealElement.nativeElement.offsetWidth / 2 + 'px') .style('top', -(this.nodeHeight / 2 + this.revealElement.nativeElement.offsetHeight / 2) + 'px'); // after any new links and nodes have been created or removed we should update the selections this.updateSelections(); // update the selected classes - ensure there is always a selected node if (!this._selected) { this.select(this._pendingSelection || this.dataset); this._pendingSelection = null; } // set the tab indexes and aria labels for any newly added items this.setNodeAttributes(); // apply the current camera position to any new nodes/links this.applyCameraPosition(); // reset the connector changed status this._hasConnectorChanged = false; } /** Select a specified node */ select(node) { // get the node in the desired format node = this.coerceDataNode(node); // check if the node is already selected if (this._selected === node) { return; } // ensure all parents are expanded this.expandParents(node); // deselect any current node this.deselect(false); // if the selected item has changed then store the latest selection this._selected = node; // emit the latest selection this.selectedChange.emit(this._selected); // show reveal any nodes that may previously have been hidden but are now visible due to selection if (this._isInitialised) { this.render(); } // add the styling to the selected node this._renderer.addClass(this.getNodeElement(this._selected), 'ux-organization-chart-node-selected'); // update the styling and tabindexes this.setNodeAttributes(); } /** Deselect the currently selected node */ deselect(emit = true) { if (this._nodes) { this._nodes .nodes() .forEach(element => this._renderer.removeClass(element, 'ux-organization-chart-node-selected')); } if (emit && !!this._selected) { this._selected = null; this.selectedChange.next(null); // update the tab indexes and aria labels this.setNodeAttributes(); } } /** Toggle the collapsed state of a node */ toggle(node) { if (this._isTransitioning) { return; } // get the node in the desired format node = this.coercePointNode(node); // ensure the clicked node is selected this.select(node); // apply the appropriate action this.isExpanded(node) ? this.collapse(node) : this.expand(node); } /** Expand a node */ expand(node) { if (this._isTransitioning || !this.toggleNodesOnClick) { return; } // get the node in the desired format node = this.coercePointNode(node); // ensure this node and all parent nodes are expanded node.ancestors().forEach(_node => (_node.data.expanded = true)); // re-render the nodes this.render(); // if the node has children then we want to move the camera to a child node if (Array.isArray(node.data.children) && node.data.children.length > 0) { // center on the middle child this.centerNode(node.data.children[Math.floor(node.data.children.length / 2)]); } else { this.centerNode(node); } } /** Collapse a node */ collapse(node) { // do nothing if a transition is currently in progress if (this._isTransitioning) { return; } // get the node in the desired format node = this.coercePointNode(node); // ensure this node and all child nodes are collapse node.descendants().forEach(_node => (_node.data.expanded = false)); // re-render the nodes this.render(); // center the node that has just been collapsed this.centerNode(node); } /** Move a specific node to the center of the screen */ centerNode(node, axis = OrganizationChartAxis.Both, animate = true) { // get the node in the desired format node = this.coercePointNode(node); // get the current camera position const camera = this.getCameraPosition(); const x = axis === OrganizationChartAxis.Vertical ? camera.x : this._width / 2 - (node.x + this.nodeWidth / 2); const y = axis === OrganizationChartAxis.Horizontal ? camera.y : this._height / 2 - (node.y + this.nodeHeight / 2); // update the camera position this.setCameraPosition(x, y, animate); } /** Explicity set the position of the camera */ setCameraPosition(x, y, animate = true) { // get the current transform let camera = zoomTransform(this._nodesContainer.node()); // do nothing if the co-orindates have not changed if (camera.x === x && camera.y === y) { return; } // update the camera position camera = camera.translate(x - camera.x, y - camera.y); // indicate that the camera is panning programmatically this._isPanning = true; if (animate) { this._nodesContainer .transition() .duration(this.duration) .call(this._zoom.transform, camera) .on('end interrupt cancel', () => (this._isPanning = false)); } else { this._nodesContainer.call(this._zoom.transform, camera); this._isPanning = false; } } /** Move the camera an amount from its current position */ moveCamera(x, y, animate = true) { // get the current camera position const camera = this.getCameraPosition(); this.setCameraPosition(camera.x + x, camera.y + y, animate); } /** Focus a given node */ focus(node) { this.focusNode(this.coercePointNode(node)); } /** Focus the root node */ _focusRootNode() { this.focusNode(this.coercePointNode(this.dataset)); } /** Destroy the outlet and portal associated with a node */ destroyNode(node) { // get the node in a consistent format node = this.coercePointNode(node); // remove focus monitoring if (this._indicators.has(node.data)) { // remove the focus monitoring this._indicators.get(node.data).destroy(); // remove the indicator from the list of indicators this._indicators.delete(node.data); } // if there is not portal/outlets associated with this node then do nothing if (!this._portals.has(node.data)) { return; } // get the portal and outlet from the map const portalRef = this._portals.get(node.data); // perform the cleanup portalRef.portal.detach(); portalRef.outlet.dispose(); // remove this entry from the map this._portals.delete(node.data); } // update the data structure for the node and link layouts updateLayout() { this._layout = this.getLayout(); this._nodeLayout = this._layout.descendants(); this._linkLayout = this._layout.links(); } /** Ensure the selections stay in sync with the view */ updateSelections() { // select all the newly added dom nodes and associate the dataset this._nodes = this._nodesContainer .selectAll('.ux-organization-chart-node') .data(this._nodeLayout, (node) => node.data.id.toString()); // select all the newly added path nodes this._links = this._linksContainer .selectAll('.ux-organization-chart-link') .data(this._linkLayout, (link) => { return `${link.source.data.id}-${link.target.data.id}`; }); } /** Render the content of the node based on the template provided */ renderNodeTemplate(node, index, group) { // create the context for the node const context = { data: node.data.data, node: node.data, focused: false, }; // the focused state should be a getter Object.defineProperty(context, 'focused', { get: () => this._focused === node.data, }); // create the outlet to insert the Template and the portal from the TemplateRef const outlet = this.createPortalOutlet(group[index]); const portal = new TemplatePortal(this.nodeTemplate, this._viewContainerRef, context); // insert the TemplateRef into the specified region portal.attach(outlet); // store the portal and outlet so we can correctly dispose of the nodes this._portals.set(node.data, { portal, outlet }); } /** Handle any zoom events (we use zoom for panning behaviour) */ applyCameraPosition() { // get the new x and y position let { x, y } = zoomTransform(this._nodesContainer.node()); // round the precision to integers to prevent any anti-aliasing x = Math.round(x); y = Math.round(y); // transform the position of the reveal button this._renderer.setStyle(this.revealElement.nativeElement, 'transform', `translate(${x}px, ${y}px)`); // transform the position of the nodes this._nodesContainer .selectAll('.ux-organization-chart-node') .style('transform', `translate(${x}px, ${y}px)`); // transform the position of the links this._linksContainer .selectAll('.ux-organization-chart-link') .attr('transform', `translate(${x} ${y})`); } /** Get the data in with the required layout information */ getLayout() { // create a hierarchical representation of the data - don't include collapsed nodes const treeHierarchy = hierarchy(this.dataset, node => Array.isArray(node.children) && node.expanded ? node.children : []); // create our layout const layout = tree() .nodeSize([this.nodeWidth, this.nodeHeight]) .separation(this.getNodeSpacing.bind(this)); // process the data with the layout const treeLayout = layout(treeHierarchy); // calculate the vertical spacing const verticalSpacing = this.verticalSpacing === undefined ? this.nodeHeight : this.verticalSpacing; // set the vertical spacing treeLayout.each(data => (data.y = data.depth * (this.nodeHeight + verticalSpacing))); return treeLayout; } /** Determine how much horizontal spacing should be between nodes */ getNodeSpacing(nodeOne, nodeTwo) { // if the nodes are not siblings then space further apart if (nodeOne.parent !== nodeTwo.parent) { return 2; } // if they are siblings they should be closer together return 1.5; } /** Ensure we consistently use the HierarchyPoint data structure */ coercePointNode(node) { // determine if this is a raw data node or a hierarchy point // eslint-disable-next-line no-prototype-builtins if (node.hasOwnProperty('depth') && node.hasOwnProperty('x') && node.hasOwnProperty('y')) { return node; } // otherwise find the matching node const match = this._nodeLayout.find(_node => _node.data === node); // if the data does not exist in the hierarchy throw an exception if (!match) { throw new Error('The node does not exist in the hierarchy'); } return match; } coerceDataNode(node) { // eslint-disable-next-line no-prototype-builtins if (node.hasOwnProperty('depth') && node.hasOwnProperty('x') && node.hasOwnProperty('y')) { return node.data; } return node; } /** Handle chart resize events */ onResize({ width, height }) { this._width = width; this._height = height; } /** Deteremine if a node is expanded or collapsed */ isExpanded(node) { return !!node.data.expanded; } /** Get the current position of the camera */ getCameraPosition() { return zoomTransform(this._nodesContainer.node()); } /** Get the SVG line definition for each link */ getLinkPath(pointLink) { if (this.connector === 'elbow') { const source = { x: pointLink.source.x + this.nodeWidth / 2, y: pointLink.source.y + this.nodeHeight, }; const target = { x: pointLink.target.x + this.nodeWidth / 2, y: pointLink.target.y }; return ('M' + source.x + ',' + source.y + 'v' + (target.y - source.y) / 2 + 'h' + (target.x - source.x) + 'v' + (target.y - source.y) / 2); } else { const source = { x: pointLink.source.x + this.nodeWidth / 2, y: pointLink.source.y + this.nodeHeight / 2, }; const target = { x: pointLink.target.x + this.nodeWidth / 2, y: pointLink.target.y + this.nodeHeight / 2, }; return linkVertical()({ source: [source.x, source.y], target: [target.x, target.y] }); } } /** Get the link path line defintion when the link is collapsing */ getCollapsedLinkPath(pointLink) { return this.getLinkPath({ source: pointLink.source, target: pointLink.source }); } /** Create a dynamic region that Angular can insert into */ createPortalOutlet(element) { return new DomPortalOutlet(element, this._componentFactoryResolver, this._appRef, this._injector); } /** Make the appropriate node tabbable and update aria attributes */ setNodeAttributes() { for (const element of this._nodes.nodes()) { // intially the tab index of all items to -1 this._renderer.setAttribute(element, 'tabindex', '-1'); // set the expanded aria attribute this._renderer.setAttribute(element, 'aria-expanded', this.getNodeData(element).data.expanded ? 'true' : 'false'); } // if there is a selected item then it should be tabbable otherwise make the root tabbable if (this._selected) { this._renderer.setAttribute(this.getNodeElement(this._selected), 'tabindex', '0'); } } /** Get the element that represents a given node */ getNodeElement(node) { node = this.coercePointNode(node); // find the element that matches the node data const index = this._nodes.data().indexOf(node); return this._nodes.nodes()[index]; } /** Get the element that represents a given node */ getNodeData(node) { // find the element that matches the node element const index = this._nodes.nodes().indexOf(node); return this._nodes.data()[index]; } /** Handle click events */ onClick(node) { if (!this.toggleNodesOnClick) { return; } this.toggle(node); } /** Handle keyboard events */ onKeydown(event, node) { if (!this.toggleNodesOnClick) { return; } switch (event.keyCode) { case DOWN_ARROW: event.preventDefault(); // if the node is collapsed and has children expand if (!node.data.expanded && Array.isArray(node.data.children) && node.data.children.length > 0) { return this.expand(node); } return this.focusChild(node); case RIGHT_ARROW: event.preventDefault(); return this.focusNextSibling(node); case UP_ARROW: event.preventDefault(); return this.focusParent(node); case LEFT_ARROW: event.preventDefault(); return this.focusPreviousSibling(node); case ENTER: return this.toggle(node); } } /** When a node receives focus */ onFocus(node) { if (!this.isNodeInViewport(node, this._width * 0.1, this._height * 0.1)) { this.centerNode(node); } } /** Move focus to the parent node */ focusParent(node) { if (node.parent) { this.focusNode(node.parent); } else if (this.revealElement) { this.revealElement.nativeElement.focus(); // center the root node to ensure the reveal button is in view this.centerNode(this.dataset); } } /** Move focus to the child node */ focusChild(node) { if (Array.isArray(node.children) && node.children.length > 0) { this.focusNode(node.children[Math.floor(node.children.length / 2)]); } } /** Move focus to the sibling on the left */ focusPreviousSibling(node) { if (node.parent) { this.focusNode(node.parent.children[node.parent.children.indexOf(node) - 1]); } } /** Move focus to the sibling on the right */ focusNextSibling(node) { if (node.parent) { this.focusNode(node.parent.children[node.parent.children.indexOf(node) + 1]); } } /** Focus a given node */ focusNode(node) { if (node) { this.getNodeElement(node).focus({ preventScroll: true }); // ensure we don't perform scrolling if the node is not in view (we rely on preventScroll as IE doesn't support it) this.nodesContainer.nativeElement.scrollTop = 0; this.nodesContainer.nativeElement.scrollLeft = 0; } } /** Determine if a node is fully visible within the viewport */ isNodeInViewport(node, insetX = 0, insetY = 0) { const { x, y } = this.getCameraPosition(); const left = node.x + x; const top = node.y + y; const right = node.x + x + this.nodeWidth; const bottom = node.y + y + this.nodeHeight; return (left >= insetX && top >= insetY && right <= this._width - insetX && bottom <= this._height - insetY); } /** Determine if a node is fully outside of the viewport */ isNodeOutsideViewport(node, insetX = 0, insetY = 0) { const { x, y } = this.getCameraPosition(); const left = node.x + x + this.nodeWidth; const top = node.y + y + this.nodeHeight; const right = node.x + x; const bottom = node.y + y; return (left < insetX || top < insetY || right > this._width - insetX || bottom > this._height - insetY); } /** Determine how far a node is from being within the viewport */ getDistanceFromViewport(node, insetX = 0, insetY = 0) { // if the node is in the viewport then it will always be 0, 0 if (!this.isNodeOutsideViewport(node, insetX, insetY)) { return [0, 0]; } const { x, y } = this.getCameraPosition(); const left = insetX - (node.x + x + this.nodeWidth); const top = insetY - (node.y + y + this.nodeHeight); const right = node.x + x - (this._width - insetX); const bottom = node.y + y - (this._height - insetY); let horizontal = 0; let vertical = 0; if (left > 0 && left > right) { horizontal = left; } if (right > 0 && left < right) { horizontal = -right; } if (top > 0 && top > bottom) { vertical = top; } if (bottom > 0 && top < bottom) { vertical = -bottom; } // calculate the distances on both axis return [horizontal, vertical]; } /** Begin monitoring the element focus so we only show styling when navigated by keyboard */ monitorFocus(element, node) { // create the focus indicator const indicator = this._focusIndicator.monitor(element, { checkChildren: false, programmaticFocusIndicator: true, }); // store the currently selected node as an instance variable indicator.isFocused$.pipe(takeUntil(this._onDestroy)).subscribe(isFocused => { // by default the CDK runs this outside of NgZone however we need it to run inside NgZone to update the node template this._ngZone.run(() => { if (isFocused) { this._focused = node.data; } else if (node.data === this._focused) { this._focused = null; } }); }); // store the focus indicator reference this._indicators.set(node.data, indicator); } // ensure that there are at least some nodes visible ensureNodesAreVisible() { // determine how many nodes are currently visible const visibleCount = this._nodes.filter(node => !this.isNodeOutsideViewport(node)).size(); if (visibleCount > 0) { return; } // get the distance each node is from being within the viewport const distances = this._nodes .data() .map(node => this.getDistanceFromViewport(node, this.nodeWidth * 1.25, this.nodeHeight * 1.5)); // find the closest node const [x, y] = distances.reduce((previous, current) => { const [previousX, previousY] = previous; const [currentX, currentY] = current; return Math.abs(previousX) + Math.abs(previousY) < Math.abs(currentX) + Math.abs(currentY) ? previous : current; }); // move the camera by the required amount this.moveCamera(x, y); } /** Expand all parent nodes */ expandParents(node) { // get the parent node let parent = this.getParent(node); while (parent) { parent.expanded = true; parent = this.getParent(parent); } } /** Get the parent of a given node */ getParent(node) { return [this.coerceDataNode(this.dataset), ...this.getAllChildren(this.dataset)].find(_node => { if (!Array.isArray(_node.children)) { return false; } return _node.children.find((child) => child.id === node.id); }); } /** Get a flat array of all the nodes childrent */ getAllChildren(node) { const children = node.children || []; // check for any children on the children return [ ...children, ...children.reduce((accumulation, child) => [...accumulation, ...this.getAllChildren(child)], []), ].map(child => this.coerceDataNode(child)); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: OrganizationChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.12", type: OrganizationChartComponent, selector: "ux-organization-chart", inputs: { dataset: "dataset", connector: "connector", nodeWidth: "nodeWidth", nodeHeight: "nodeHeight", duration: "duration", verticalSpacing: "verticalSpacing", showReveal: "showReveal", revealAriaLabel: "revealAriaLabel", toggleNodesOnClick: "toggleNodesOnClick", selected: "selected" }, outputs: { selectedChange: "selectedChange", reveal: "reveal", transitionEnd: "transitionEnd" }, queries: [{ propertyName: "revealTemplate", first: true, predicate: ["revealTemplate"], descendants: true }, { propertyName: "nodeTemplate", first: true, predicate: ["nodeTemplate"], descendants: true }], viewQueries: [{ propertyName: "revealElement", first: true, predicate: ["revealElement"], descendants: true, static: true }, { propertyName: "linksContainer", first: true, predicate: ["links"], descendants: true, static: true }, { propertyName: "nodesContainer", first: true, predicate: ["nodes"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<!-- Add a button above the root node to load additional parent items -->\n<button #revealElement\n uxFocusIndicatorOrigin\n class=\"ux-organization-chart-reveal\"\n tabindex=\"-1\"\n [attr.aria-label]=\"revealAriaLabel\"\n [hidden]=\"!showReveal\"\n (click)=\"reveal.emit(); _focusRootNode()\"\n (keydown.ArrowDown)=\"_focusRootNode(); $event.preventDefault()\">\n\n <!-- Display Reveal Template -->\n <ng-container [ngTemplateOutlet]=\"revealTemplate || defaultRevealTemplate\"></ng-container>\n</button>\n\n<!-- Show the links connecting each node -->\n<svg #links class=\"ux-organization-chart-links\"></svg>\n\n<!-- Show the nodes containing information about each item -->\n<div #nodes class=\"ux-organization-chart-nodes\"></div>\n\n<!-- Provide a default reveal template -->\n<ng-template #defaultRevealTemplate>\n <ux-icon name=\"tab-up\"></ux-icon>\n</ng-template>", dependencies: [{ kind: "directive", type: i1.FocusIndicatorOriginDirective, selector: "[uxFocusIndicatorOrigin]" }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i3.IconComponent, selector: "ux-icon", inputs: ["name", "size", "rotate", "flipHorizontal", "flipVertical"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: OrganizationChartComponent, decorators: [{ type: Component, args: [{ selector: 'ux-organization-chart', changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Add a button above the root node to load additional parent items -->\n<button #revealElement\n uxFocusIndicatorOrigin\n class=\"ux-organization-chart-reveal\"\n tabindex=\"-1\"\n [attr.aria-label]=\"revealAriaLabel\"\n [hidden]=\"!showReveal\"\n (click)=\"reveal.emit(); _focusRootNode()\"\n (keydown.ArrowDown)=\"_focusRootNode(); $event.preventDefault()\">\n\n <!-- Display Reveal Template -->\n <ng-container [ngTemplateOutlet]=\"revealTemplate || defaultRevealTemplate\"></ng-container>\n</button>\n\n<!-- Show the links connecting each node -->\n<svg #links class=\"ux-organization-chart-links\"></svg>\n\n<!-- Show the nodes containing information about each item -->\n<div #nodes class=\"ux-organization-chart-nodes\"></div>\n\n<!-- Provide a default reveal template -->\n<ng-template #defaultRevealTemplate>\n <ux-icon name=\"tab-up\"></ux-icon>\n</ng-template>" }] }], propDecorators: { dataset: [{ type: Input }], connector: [{ type: Input }], nodeWidth: [{ type: Input }], nodeHeight: [{ type: Input }], duration: [{ type: Input }], verticalSpacing: [{ type: Input }], showReveal: [{ type: Input }], revealAriaLabel: [{ type: Input }], toggleNodesOnClick: [{ type: Input }], selected: [{ type: Input }], selectedChange: [{ type: Output }], reveal: [{ type: Output }], transitionEnd: [{ type: Output }], revealTemplate: [{ type: ContentChild, args: ['revealTemplate', { static: false }] }], nodeTemplate: [{ type: ContentChild, args: ['nodeTemplate', { static: false }] }], revealElement: [{ type: ViewChild, args: ['revealElement', { static: true }] }], linksContainer: [{ type: ViewChild, args: ['links', { static: true }] }], nodesContainer: [{ type: ViewChild, args: ['nodes', { static: true }] }] } }); export var OrganizationChartAxis; (function (OrganizationChartAxis) { OrganizationChartAxis[OrganizationChartAxis["Horizontal"] = 0] = "Horizontal"; OrganizationChartAxis[OrganizationChartAxis["Vertical"] = 1] = "Vertical"; OrganizationChartAxis[OrganizationChartAxis["Both"] = 2] = "Both"; })(OrganizationChartAxis || (OrganizationChartAxis = {})); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3JnYW5pemF0aW9uLWNoYXJ0LmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9jb21wb25lbnRzL29yZ2FuaXphdGlvbi1jaGFydC9vcmdhbml6YXRpb24tY2hhcnQuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vLi4vLi4vc3JjL2NvbXBvbmVudHMvb3JnYW5pemF0aW9uLWNoYXJ0L29yZ2FuaXphdGlvbi1jaGFydC5jb21wb25lbnQuaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSw0REFBNEQ7QUFDNUQsT0FBTyxFQUFnQixxQkFBcUIsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBQzVFLE9BQU8sRUFBRSxVQUFVLEVBQUUsS0FBSyxFQUFFLFVBQVUsRUFBRSxXQUFXLEVBQUUsUUFBUSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDN0YsT0FBTyxFQUFFLGVBQWUsRUFBRSxjQUFjLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUN0RSxPQUFPLEVBRUwsY0FBYyxFQUNkLHVCQUF1QixFQUN2QixTQUFTLEVBQ1Qsd0JBQXdCLEVBQ3hCLFlBQVksRUFDWixVQUFVLEVBQ1YsWUFBWSxFQUNaLFFBQVEsRUFDUixLQUFLLEVBQ0wsTUFBTSxFQUdOLE1BQU0sRUFDTixTQUFTLEVBR1QsU0FBUyxFQUNULGdCQUFnQixFQUNoQixNQUFNLEdBQ1AsTUFBTSxlQUFlLENBQUM7QUFDdkIsT0FBTyxFQU1MLFNBQVMsRUFDVCxXQUFXLEVBQ1gsWUFBWSxFQUNaLE1BQU0sRUFDTixVQUFVLEVBQ1YsSUFBSSxFQUNKLElBQUksRUFDSixhQUFhLEdBQ2QsTUFBTSxJQUFJLENBQUM7QUFDWixPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQy9CLE9BQU8sRUFBRSxZQUFZLEVBQUUsU0FBUyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDekQsT0FBTyxFQUFrQixxQkFBcUIsRUFBRSxNQUFNLHNDQUFzQyxDQUFDO0FBQzdGLE9BQU8sRUFBb0IsYUFBYSxFQUFFLE1BQU0sK0JBQStCLENBQUM7Ozs7O0FBT2hGLE1BQU0sT0FBTywwQkFBMEI7SUFMdkM7UUFNbUIsbUJBQWMsR0FBRyxNQUFNLENBQUMsYUFBYSxDQUFDLENBQUM7UUFFdkMsOEJBQXlCLEdBQUcsTUFBTSxDQUFDLHdCQUF3QixDQUFDLENBQUM7UUFFN0QsY0FBUyxHQUFHLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUU3QixnQkFBVyxHQUFHLE1BQU0sQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUVqQyxZQUFPLEdBQUcsTUFBTSxDQUFDLGNBQWMsQ0FBQyxDQUFDO1FBRWpDLHNCQUFpQixHQUFHLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO1FBRTdDLGNBQVMsR0FBRyxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUM7UUFFOUIsb0JBQWUsR0FBRyxNQUFNLENBQUMscUJBQXFCLENBQUMsQ0FBQztRQUVoRCxZQUFPLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBSzFDLGdEQUFnRDtRQUN2QyxjQUFTLEdBQStCLE9BQU8sQ0FBQztRQVF6RCx1REFBdUQ7UUFDOUMsYUFBUSxHQUFXLEdBQUcsQ0FBQztRQUtoQyw2REFBNkQ7UUFDcEQsZUFBVSxHQUFZLEtBQUssQ0FBQztRQUVyQyxrREFBa0Q7UUFDekMsb0JBQWUsR0FBVyxhQUFhLENBQUM7UUF5QmpELHVDQUF1QztRQUM3QixtQkFBYyxHQUFHLElBQUksWUFBWSxDQUEyQixJQUFJLENBQUMsQ0FBQztRQUU1RSxpREFBaUQ7UUFDdkMsV0FBTSxHQUFHLElBQUksWUFBWSxFQUFRLENBQUM7UUFFNUMsb0NBQW9DO1FBQzFCLGtCQUFhLEdBQUcsSUFBSSxZQUFZLEVBQVEsQ0FBQztRQW1CM0Msd0JBQW1CLEdBQVksSUFBSSxDQUFDO1FBSzVDLHVDQUF1QztRQUMvQixnQkFBVyxHQUFtRCxFQUFFLENBQUM7UUFFekUsdUNBQXVDO1FBQy9CLGdCQUFXLEdBQW1ELEVBQUUsQ0FBQztRQW9DekUseURBQXlEO1FBQ3hDLGFBQVEsR0FBRyxJQUFJLEdBQUcsRUFBd0QsQ0FBQztRQUU1Rix1REFBdUQ7UUFDdEMsZ0JBQVcsR0FBRyxJQUFJLEdBQUcsRUFBNEMsQ0FBQztRQUVuRix1REFBdUQ7UUFDL0MscUJBQWdCLEdBQVksS0FBSyxDQUFDO1FBRTFDLHVEQUF1RDtRQUMvQyxlQUFVLEdBQVksS0FBSyxDQUFDO1FBRXBDLGdEQUFnRDtRQUN4QyxtQkFBYyxHQUFZLEtBQUssQ0FBQztRQUV4Qyx3RUFBd0U7UUFDaEUseUJBQW9CLEdBQVksS0FBSyxDQUFDO1FBUTlDLGtFQUFrRTtRQUNqRCxlQUFVLEdBQUcsSUFBSSxPQUFPLEVBQVEsQ0FBQztLQSs3Qm5EO0lBdGpDQyxrREFBa0Q7SUFDbEQsSUFBYSxrQkFBa0IsQ0FBQyxrQkFBMkI7UUFDekQsSUFBSSxDQUFDLG1CQUFtQixHQUFHLHFCQUFxQixDQUFDLGtCQUFrQixDQUFDLENBQUM7SUFDdkUsQ0FBQztJQUVELElBQUksa0JBQWtCO1FBQ3BCLE9BQU8sSUFBSSxDQUFDLG1CQUFtQixDQUFDO0lBQ2xDLENBQUM7SUFFRCxzQ0FBc0M7SUFDdEMsSUFBYSxRQUFRLENBQUMsUUFBa0M7UUFDdEQsSUFBSSxJQUFJLENBQUMsUUFBUSxLQUFLLFFBQVEsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQzVDLE9BQU87UUFDVCxDQUFDO1FBRUQsSUFBSSxJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7WUFDeEIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQztZQUN0QixJQUFJLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQzVCLENBQUM7YUFBTSxDQUFDO1lBQ04sSUFBSSxDQUFDLGlCQUFpQixHQUFHLFFBQVEsQ0FBQztRQUNwQyxDQUFDO0lBQ0gsQ0FBQztJQW9HRCxlQUFlO1FBQ2IsNkRBQTZEO1FBQzdELElBQUksQ0FBQyxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7WUFDdkIsTUFBTSxJQUFJLEtBQUssQ0FBQyx3REFBd0QsQ0FBQyxDQUFDO1FBQzVFLENBQUM7UUFFRCxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQztZQUN4QyxNQUFNLElBQUksS0FBSyxDQUFDLGtFQUFrRSxDQUFDLENBQUM7UUFDdEYsQ0FBQztRQUVELGdDQUFnQztRQUNoQyxJQUFJLENBQUMsS0FBSyxHQUFHLElBQUksRUFBRTthQUNoQixXQUFXLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUM7YUFDbkIsV0FBVyxDQUFDLFdBQVcsQ0FBQzthQUN4QixFQUFFLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7YUFDL0MsRUFBRSxDQUFDLEtBQUssRUFBRSxHQUFHLEVBQUU7WUFDZCxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO2dCQUNyQixJQUFJLENBQUMscUJBQXFCLEVBQUUsQ0FBQztZQUMvQixDQUFDO1FBQ0gsQ0FBQyxDQUFDLENBQUM7UUFFTCx3QkFBd0I7UUFDeEIsSUFBSSxDQUFDLGVBQWUsR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQyxhQUFhLENBQUMsQ0FBQztRQUNqRSxJQUFJLENBQUMsZUFBZSxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsY0FBYyxDQUFDLGFBQWEsQ0FBQyxDQUFDO1FBRWpFLG1DQUFtQztRQUNuQyxJQUFJLENBQUMsT0FBTyxDQUFDLGlCQUFpQixDQUFDLEdBQUcsRUFBRSxDQUFDLElBQUksQ0FBQyxlQUFlLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO1FBRTVFLDZCQUE2QjtRQUM3QixJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7UUFFZCx1Q0FBdUM7UUFDdkMsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLGFBQWEsQ0FBQyxXQUFXLENBQUM7UUFDekQsSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLGFBQWEsQ0FBQyxZQUFZLENBQUM7UUFFM0Qsc0NBQXNDO1FBQ3RDLE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxjQUFjLENBQUMsaUJBQWlCLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxhQUFhLENBQUMsQ0FBQztRQUV0RixvRUFBb0U7UUFDcEUsT0FBTyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7UUFFN0UsNENBQTRDO1FBQzVDLE9BQU87YUFDSixJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsRUFBRSxZQUFZLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO2FBQzdELFNBQVMsQ0FBQyxJQUFJLENBQUMscUJBQXFCLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7UUFFcEQsOENBQThDO1FBQzlDLElBQUksQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxxQkFBcUIsQ0FBQyxVQUFVLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFFdkUsdUdBQXVHO1FBQ3ZHLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQyxFQUFFLEdBQUcsRUFBRSxLQUFLLENBQUMsQ0FBQztRQUUvQixxQ0FBcUM7UUFDckMsSUFBSSxDQUFDLGNBQWMsR0FBRyxJQUFJLENBQUM7SUFDN0IsQ0FBQztJQUVELFdBQVcsQ0FBQyxPQUFzQjtRQUNoQyxJQUFJLE9BQU8sQ0FBQyxTQUFTLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLFdBQVcsRUFBRSxDQUFDO1lBQ3hELElBQUksQ0FBQyxvQkFBb0IsR0FBRyxJQUFJLENBQUM7UUFDbkMsQ0FBQztRQUVELGtHQUFrRztRQUNsRyxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUMsTUFBTSxLQUFLLENBQUMsSUFBSSxPQUFPLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDMUQsT0FBTztRQUNULENBQUM7UUFFRCxJQUFJLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztZQUN4QixJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7UUFDaEIsQ0FBQztJQUNILENBQUM7SUFFRCxXQUFXO1FBQ1QsSUFBSSxDQUFDLFVBQVUsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUN2QixJQUFJLENBQUMsVUFBVSxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBRTNCLDRDQUE0QztRQUM1QyxJQUFJLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsRUFBRTtZQUMzQixJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ3JCLElBQUksQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUFFLENBQUM7UUFDeEIsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQsZ0RBQWdEO0lBQ2hELE1BQU07UUFDSixzREFBc0Q7UUFDdEQsSUFBSSxDQUFDLFlBQVksRUFBRSxDQUFDO1FBRXBCLDBDQUEwQztRQUMxQyxJQUFJLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQztRQUV4QixnRUFBZ0U7UUFDaEUsTUFBTSxpQkFBaUIsR0FBRyxVQUFVLENBQUMsb0NBQW9DLENBQUM7YUFDdkUsUUFBUSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUM7YUFDdkIsRUFBRSxDQUFDLE9BQU8sRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFDLElBQUksQ0FBQyxnQkFBZ0IsR0FBRyxJQUFJLENBQUMsQ0FBQzthQUNqRCxFQUFFLENBQUMsS0FBSyxFQUFFLEdBQUcsRUFBRTtZQUNkLElBQUksQ0FBQyxnQkFBZ0IsR0FBRyxLQUFLLENBQUM7WUFDOUIsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUM1QixDQUFDLENBQUMsQ0FBQztRQUVMLHdEQUF3RDtRQUN4RCxJQUFJLENBQUMsTUFBTTthQUNSLEtBQUssRUFBRTthQUNQLE1BQU0sQ0FBQyxNQUFNLENBQUM7YUFDZCxJQUFJLENBQUMsT0FBTyxFQUFFLDRCQUE0QixDQUFDO2FBQzNDLElBQUksQ0FBQyxHQUFHLEVBQUUsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDO2FBQ3pDLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQyxDQUFDLENBQUM7YUFDbkIsVUFBVSxDQUFDLGlCQUFpQixDQUFDO2FBQzdCLElBQUksQ0FBQyxHQUFHLEVBQUUsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDO2FBQ3pDLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQyxDQUFDLENBQUM7UUFFdEIsMkRBQTJEO1FBQzNELElBQUksQ0FBQyxNQUFNO2FBQ1IsVUFBVSxFQUFFO2FBQ1osUUFBUSxDQUFDLElBQUksQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDO2FBQ3ZELElBQUksQ0FBQyxHQUFHLEVBQUUsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7UUFFN0Msd0NBQXdDO1FBQ3hDLElBQUksQ0FBQyxNQUFNO2FBQ1IsSUFBSSxFQUFFO2FBQ04sVUFBVSxDQUFDLGlCQUFpQixDQUFDO2FBQzdCLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQyxJQUFrRCxFQUFFLEVBQUUsQ0FDaEUsSUFBSSxDQUFDLG9CQUFvQixDQUFDLElBQUksQ0FBQyxDQUNoQzthQUNBLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQyxDQUFDO2FBQ2xCLE1BQU0sRUFBRSxDQUFDO1FBRVosb0RBQW9EO1FBQ3BELElBQUksQ0FBQyxNQUFNO2FBQ1IsS0FBSyxFQUFFO2FBQ1AsTUFBTSxDQUFDLEtBQUssQ0FBQzthQUNiLElBQUksQ0FBQyxPQUFPLEVBQUUsNEJBQTRCLENBQUM7YUFDM0MsS0FBSyxDQUFDLE9BQU8sRUFBRSxJQUFJLENBQUMsU0FBUyxHQUFHLElBQUksQ0FBQzthQUNyQyxLQUFLLENBQUMsUUFBUSxFQUFFLElBQUksQ0FBQyxVQUFVLEdBQUcsSUFBSSxDQUFDO2FBQ3ZDLEtBQUssQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEdBQUcsSUFBSSxDQUFDO2FBQ3BFLEtBQUssQ0FBQyxLQUFLLEVBQUUsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEdBQUcsSUFBSSxDQUFDO2FBQ25FLEtBQUssQ0FBQyxTQUFTLEVBQUUsQ0FBQyxDQUFDO2FBQ25CLEVBQUUsQ0FBQyxTQUFTLEVBQUUsQ0FBQyxLQUFLLEVBQUUsSUFBSSxFQUFFLEVBQUUsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssRUFBRSxJQUFJLENBQUMsQ0FBQzthQUMzRCxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxFQUFFLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQzthQUNoRCxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxFQUFFLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQzthQUNoRCxJQUFJLENBQUMsSUFBSSxDQUFDLGtCQUFrQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQzthQUN4QyxJQUFJLENBQUMsQ0FBQyxJQUFJLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxFQUFFLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLEVBQUUsSUFBSSxDQUFDLENBQUM7YUFDbkUsVUFBVSxDQUFDLGlCQUFpQixDQUFDO2FBQzdCLEtBQUssQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsQ0FBQyxHQUFHLElBQUksQ0FBQzthQUNwQyxLQUFLLENBQUMsS0FBSyxFQUFFLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLENBQUMsR0FBRyxJQUFJLENBQUM7YUFDbkMsS0FBSyxDQUFDLFNBQVMsRUFBRSxDQUFDLENBQUMsQ0FBQztRQUV2Qiw4Q0FBOEM7UUFDOUMsSUFBSSxDQUFDLE1BQU07YUFDUixVQUFVLENBQUMsaUJBQWlCLENBQUM7YUFDN0IsS0FBSyxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxDQUFDLEdBQUcsSUFBSSxDQUFDO2FBQ3BDLEtBQUssQ0FBQyxLQUFLLEVBQUUsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsQ0FBQyxHQUFHLElBQUksQ0FBQyxDQUFDO1FBRXZDLHdDQUF3QztRQUN4QyxJQUFJLENBQUMsTUFBTTthQUNSLElBQUksRUFBRTthQUNOLFVBQVUsQ0FBQyxpQkFBaUIsQ0FBQzthQUM3QixLQUFLLENBQ0osTUFBTSxFQUNOLENBQUMsSUFBa0QsRUFBRSxFQUFFLENBQ3JELENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsR0FBRyxJQUFJLENBQ2hEO2FBQ0EsS0FBSyxDQUFDLFNBQVMsRUFBRSxDQUFDLENBQUM7YUFDbkIsTUFBTSxFQUFFO2FBQ1IsRUFBRSxDQUFDLEtBQUssRUFBRSxDQUFDLElBQWtELEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztRQUU3RiwyQ0FBMkM7UUFDM0MsTUFBTSxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsYUFBYSxDQUFDO2FBQ3JDLEtBQUssQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLFNBQVMsR0FBRyxDQUFDLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxhQUFhLENBQUMsV0FBVyxHQUFHLENBQUMsR0FBRyxJQUFJLENBQUM7YUFDM0YsS0FBSyxDQUNKLEtBQUssRUFDTCxDQUFDLENBQUMsSUFBSSxDQUFDLFVBQVUsR0FBRyxDQUFDLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxhQUFhLENBQUMsWUFBWSxHQUFHLENBQUMsQ0FBQyxHQUFHLElBQUksQ0FDbEYsQ0FBQztRQUVKLDZGQUE2RjtRQUM3RixJQUFJLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQztRQUV4Qix1RUFBdUU7UU