@skyux/core
Version:
This library was generated with [Nx](https://nx.dev).
1 lines • 328 kB
Source Map (JSON)
{"version":3,"file":"skyux-core.mjs","sources":["../../../../../libs/components/core/src/lib/modules/adapter-service/adapter.module.ts","../../../../../libs/components/core/src/lib/modules/breakpoint-observer/breakpoint.ts","../../../../../libs/components/core/src/lib/modules/media-query/media-breakpoints.ts","../../../../../libs/components/core/src/lib/modules/breakpoint-observer/breakpoint-utils.ts","../../../../../libs/components/core/src/lib/modules/adapter-service/adapter.service.ts","../../../../../libs/components/core/src/lib/modules/affix/affix-auto-fit-context.ts","../../../../../libs/components/core/src/lib/modules/affix/affix-utils.ts","../../../../../libs/components/core/src/lib/modules/affix/dom-utils.ts","../../../../../libs/components/core/src/lib/modules/affix/affixer.ts","../../../../../libs/components/core/src/lib/modules/affix/affix.service.ts","../../../../../libs/components/core/src/lib/modules/affix/affix.directive.ts","../../../../../libs/components/core/src/lib/modules/affix/affix.module.ts","../../../../../libs/components/core/src/lib/modules/content-info-provider/content-info-provider.ts","../../../../../libs/components/core/src/lib/modules/default-input-provider/default-input-provider.ts","../../../../../libs/components/core/src/lib/modules/dock/dock-item.ts","../../../../../libs/components/core/src/lib/modules/dock/dock-location.ts","../../../../../libs/components/core/src/lib/modules/dock/dock.module.ts","../../../../../libs/components/core/src/lib/modules/dynamic-component/dynamic-component-location.ts","../../../../../libs/components/core/src/lib/modules/window/window-ref.ts","../../../../../libs/components/core/src/lib/modules/dynamic-component/dynamic-component.service.ts","../../../../../libs/components/core/src/lib/modules/mutation/mutation-observer-service.ts","../../../../../libs/components/core/src/lib/modules/dock/dock-dom-adapter.service.ts","../../../../../libs/components/core/src/lib/modules/dock/sort-by-stack-order.ts","../../../../../libs/components/core/src/lib/modules/dock/dock.component.ts","../../../../../libs/components/core/src/lib/modules/dock/dock.component.html","../../../../../libs/components/core/src/lib/modules/dock/dock.service.ts","../../../../../libs/components/core/src/lib/modules/dynamic-component/dynamic-component.module.ts","../../../../../libs/components/core/src/lib/modules/file-reader/file-reader.service.ts","../../../../../libs/components/core/src/lib/modules/format/app-format.ts","../../../../../libs/components/core/src/lib/modules/help/help-global-options-token.ts","../../../../../libs/components/core/src/lib/modules/help/help.service.ts","../../../../../libs/components/core/src/lib/modules/id/id.service.ts","../../../../../libs/components/core/src/lib/modules/id/id.directive.ts","../../../../../libs/components/core/src/lib/modules/id/id.module.ts","../../../../../libs/components/core/src/lib/modules/layout-host/layout-host.service.ts","../../../../../libs/components/core/src/lib/modules/layout-host/layout-host.directive.ts","../../../../../libs/components/core/src/lib/modules/live-announcer/live-announcer.service.ts","../../../../../libs/components/core/src/lib/modules/log/log.module.ts","../../../../../libs/components/core/src/lib/modules/log/types/log-level.ts","../../../../../libs/components/core/src/lib/modules/log/types/log-level-token.ts","../../../../../libs/components/core/src/lib/modules/log/log.service.ts","../../../../../libs/components/core/src/lib/modules/breakpoint-observer/breakpoint-observer.token.ts","../../../../../libs/components/core/src/lib/modules/resize-observer/resize-observer.service.ts","../../../../../libs/components/core/src/lib/modules/breakpoint-observer/container-breakpoint-observer.ts","../../../../../libs/components/core/src/lib/modules/breakpoint-observer/media-breakpoint-observer.ts","../../../../../libs/components/core/src/lib/modules/media-query/media-query.service.ts","../../../../../libs/components/core/src/lib/modules/breakpoint-observer/provide-breakpoint-observer.ts","../../../../../libs/components/core/src/lib/modules/breakpoint-observer/responsive-host.directive.ts","../../../../../libs/components/core/src/lib/modules/media-query/media-query.module.ts","../../../../../libs/components/core/src/lib/modules/shared/sky-core-resources.module.ts","../../../../../libs/components/core/src/lib/modules/numeric/numeric.options.ts","../../../../../libs/components/core/src/lib/modules/shared/number-format/number-format-utility.ts","../../../../../libs/components/core/src/lib/modules/numeric/numeric.service.ts","../../../../../libs/components/core/src/lib/modules/numeric/numeric.pipe.ts","../../../../../libs/components/core/src/lib/modules/numeric/numeric.module.ts","../../../../../libs/components/core/src/lib/modules/overlay/overlay-instance.ts","../../../../../libs/components/core/src/lib/modules/overlay/overlay.module.ts","../../../../../libs/components/core/src/lib/modules/overlay/overlay-adapter.service.ts","../../../../../libs/components/core/src/lib/modules/overlay/overlay-context.ts","../../../../../libs/components/core/src/lib/modules/stacking-context/stacking-context-token.ts","../../../../../libs/components/core/src/lib/modules/overlay/overlay.component.ts","../../../../../libs/components/core/src/lib/modules/overlay/overlay.component.html","../../../../../libs/components/core/src/lib/modules/overlay/overlay.service.ts","../../../../../libs/components/core/src/lib/modules/percent-pipe/percent.pipe.ts","../../../../../libs/components/core/src/lib/modules/percent-pipe/percent-pipe.module.ts","../../../../../libs/components/core/src/lib/modules/resize-observer/resize-observer-media-query.service.ts","../../../../../libs/components/core/src/lib/modules/screen-reader-label/screen-reader-label.directive.ts","../../../../../libs/components/core/src/lib/modules/scroll-shadow/scroll-shadow.directive.ts","../../../../../libs/components/core/src/lib/modules/scrollable-host/scrollable-host.service.ts","../../../../../libs/components/core/src/lib/modules/title/title.service.ts","../../../../../libs/components/core/src/lib/modules/trim/trim.directive.ts","../../../../../libs/components/core/src/lib/modules/trim/trim.module.ts","../../../../../libs/components/core/src/lib/modules/ui-config/ui-config.service.ts","../../../../../libs/components/core/src/lib/modules/viewkeeper/viewkeeper.ts","../../../../../libs/components/core/src/lib/modules/viewkeeper/viewkeeper-host-options.ts","../../../../../libs/components/core/src/lib/modules/viewkeeper/viewkeeper.service.ts","../../../../../libs/components/core/src/lib/modules/viewkeeper/viewkeeper.directive.ts","../../../../../libs/components/core/src/lib/modules/viewkeeper/viewkeeper.module.ts","../../../../../libs/components/core/src/version.ts","../../../../../libs/components/core/src/skyux-core.ts"],"sourcesContent":["import { NgModule } from '@angular/core';\n\n/**\n * @deprecated The `SkyCoreAdapterService` no longer needs the `SkyCoreAdapterModule`.\n * The `SkyCoreAdapterModule` can be removed from your project.\n */\n@NgModule({})\nexport class SkyCoreAdapterModule {}\n","/**\n * A list of all breakpoints.\n * @internal\n */\nexport const SKY_BREAKPOINTS = ['xs', 'sm', 'md', 'lg'] as const;\n\n/**\n * The name of a viewport or container breakpoint.\n */\nexport type SkyBreakpoint = (typeof SKY_BREAKPOINTS)[number];\n","/**\n * Represents all available media breakpoints.\n * @deprecated Use `SkyBreakpoint` instead.\n */\nexport enum SkyMediaBreakpoints {\n /**\n * Screen widths of 767px or less.\n */\n xs = 1,\n\n /**\n * Screen widths of 768px to 991px.\n */\n sm,\n\n /**\n * Screen widths of 992px to 1199px.\n */\n md,\n\n /**\n * Screen widths of 1200px or greater.\n */\n lg,\n}\n","import { SkyMediaBreakpoints } from '../media-query/media-breakpoints';\n\nimport { SKY_BREAKPOINTS, SkyBreakpoint } from './breakpoint';\n\nconst breakpointLookup = new Map<SkyMediaBreakpoints, SkyBreakpoint>([\n [SkyMediaBreakpoints.xs, 'xs'],\n [SkyMediaBreakpoints.sm, 'sm'],\n [SkyMediaBreakpoints.md, 'md'],\n [SkyMediaBreakpoints.lg, 'lg'],\n]);\n\nconst legacyLookup = new Map<SkyBreakpoint, SkyMediaBreakpoints>([\n ['xs', SkyMediaBreakpoints.xs],\n ['sm', SkyMediaBreakpoints.sm],\n ['md', SkyMediaBreakpoints.md],\n ['lg', SkyMediaBreakpoints.lg],\n]);\n\n/**\n * Whether the value is of type `SkyBreakpoint`.\n * @internal\n */\nexport function isSkyBreakpoint(\n value: SkyBreakpoint | SkyMediaBreakpoints | null | undefined,\n): value is SkyBreakpoint {\n return (\n value !== null &&\n value !== undefined &&\n SKY_BREAKPOINTS.includes(value as SkyBreakpoint)\n );\n}\n\n/**\n * Transforms a `SkyMediaBreakpoints` value to `SkyBreakpoint`.\n * @internal\n */\nexport function toSkyBreakpoint(\n breakpoint: SkyMediaBreakpoints,\n): SkyBreakpoint {\n return breakpointLookup.get(breakpoint) as SkyBreakpoint;\n}\n\n/**\n * Transforms a `SkyBreakpoint` value to `SkyMediaBreakpoints`.\n * @internal\n */\nexport function toSkyMediaBreakpoints(\n breakpoint: SkyBreakpoint,\n): SkyMediaBreakpoints {\n return legacyLookup.get(breakpoint) as SkyMediaBreakpoints;\n}\n","import {\n ElementRef,\n Injectable,\n Renderer2,\n RendererFactory2,\n} from '@angular/core';\n\nimport {\n SKY_BREAKPOINTS,\n SkyBreakpoint,\n} from '../breakpoint-observer/breakpoint';\nimport {\n isSkyBreakpoint,\n toSkyBreakpoint,\n} from '../breakpoint-observer/breakpoint-utils';\nimport { SkyMediaBreakpoints } from '../media-query/media-breakpoints';\n\nimport { SkyFocusableChildrenOptions } from './focusable-children-options';\n\nconst SKY_TABBABLE_SELECTOR = [\n 'a[href]',\n 'area[href]',\n 'input:not([disabled])',\n 'button:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n 'iframe',\n 'object',\n 'embed',\n '*[contenteditable=true]:not([disabled])',\n '*[tabindex]:not([disabled])',\n].join(', ');\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SkyCoreAdapterService {\n #renderer: Renderer2;\n\n constructor(rendererFactory: RendererFactory2) {\n this.#renderer = rendererFactory.createRenderer(undefined, null);\n }\n\n /**\n * Set the responsive container CSS class for a given element.\n *\n * @param elementRef - The element that will receive the new CSS class.\n * @param breakpoint - The breakpoint to determine which class gets set.\n * For example a breakpoint of \"xs\" will set a CSS class of \"sky-responsive-container-xs\".\n * @deprecated Use the `SkyResponsiveHostDirective` instead.\n */\n public setResponsiveContainerClass(\n elementRef: ElementRef,\n breakpoint: SkyBreakpoint | SkyMediaBreakpoints,\n ): void {\n const nativeEl = elementRef.nativeElement;\n\n for (const breakpointType of SKY_BREAKPOINTS) {\n this.#renderer.removeClass(\n nativeEl,\n `sky-responsive-container-${breakpointType}`,\n );\n }\n\n if (!isSkyBreakpoint(breakpoint)) {\n breakpoint = toSkyBreakpoint(breakpoint);\n }\n\n this.#renderer.addClass(nativeEl, `sky-responsive-container-${breakpoint}`);\n }\n\n /**\n * This method temporarily enables/disables pointer events.\n * This is helpful to prevent iFrames from interfering with drag events.\n *\n * @param enable - Set to `true` to enable pointer events. Set to `false` to disable.\n */\n public toggleIframePointerEvents(enable: boolean): void {\n const iframes = Array.from<HTMLElement>(\n document.querySelectorAll('iframe'),\n );\n for (const iframe of iframes) {\n this.#renderer.setStyle(iframe, 'pointer-events', enable ? '' : 'none');\n }\n }\n\n /**\n * Focuses on the first element found with an `autofocus` attribute inside the supplied `elementRef`.\n *\n * @param elementRef - The element to search within.\n * @return Returns `true` if a child element with autofocus is found.\n */\n public applyAutoFocus(elementRef?: ElementRef): boolean {\n if (!elementRef) {\n return false;\n }\n\n const elementWithAutoFocus =\n elementRef.nativeElement.querySelector('[autofocus]');\n\n // Child was found with the autofocus property. Set focus and return true.\n if (elementWithAutoFocus) {\n elementWithAutoFocus.focus();\n return true;\n }\n\n // No children were found with autofocus property. Return false.\n return false;\n }\n\n /**\n * Sets focus on the first focusable child of the `elementRef` parameter.\n * If no focusable children are found, and `focusOnContainerIfNoChildrenFound` is `true`,\n * focus will be set on the container element.\n *\n * @param elementRef - The element to search within.\n * @param containerSelector - A CSS selector indicating the container that should\n * receive focus if no focusable children are found.\n * @param focusOnContainerIfNoChildrenFound - It set to `true`, the container will\n * receive focus if no focusable children are found.\n */\n public getFocusableChildrenAndApplyFocus(\n elementRef: ElementRef,\n containerSelector?: string,\n focusOnContainerIfNoChildrenFound = false,\n ): void {\n const containerElement =\n elementRef.nativeElement.querySelector(containerSelector);\n\n if (containerElement) {\n const focusableChildren = this.getFocusableChildren(containerElement);\n\n // Focus first focusable child if available. Otherwise, set focus on container.\n if (\n !this.#focusFirstElement(focusableChildren) &&\n focusOnContainerIfNoChildrenFound\n ) {\n containerElement.focus();\n }\n }\n }\n\n /**\n * Returns an array of all focusable children of provided `element`.\n *\n * @param element - The HTMLElement to search within.\n * @param options - Options for getting focusable children.\n */\n public getFocusableChildren(\n element?: HTMLElement,\n options?: SkyFocusableChildrenOptions,\n ): HTMLElement[] {\n if (!element) {\n return [];\n }\n\n let elements = Array.prototype.slice.call(\n element.querySelectorAll(SKY_TABBABLE_SELECTOR),\n );\n\n // Unless ignoreTabIndex = true, filter out elements with tabindex = -1.\n if (!options || !options.ignoreTabIndex) {\n elements = elements.filter((el: HTMLElement) => {\n return el.tabIndex !== -1;\n });\n }\n\n // Unless ignoreVisibility = true, filter out elements that are not visible.\n if (!options || !options.ignoreVisibility) {\n elements = elements.filter((el: HTMLElement) => {\n return this.#isVisible(el);\n });\n }\n\n return elements;\n }\n\n /**\n * Returns the clientWidth of the provided elementRef.\n * @param elementRef - The element to calculate width from.\n */\n public getWidth(elementRef: ElementRef): number {\n return elementRef.nativeElement.clientWidth;\n }\n\n /**\n * Checks if an event target has a higher z-index than a given element.\n * @param target The event target element.\n * @param element The element to test against. A z-index must be explicitly set for this element.\n */\n public isTargetAboveElement(\n target: EventTarget,\n element: HTMLElement,\n ): boolean {\n const zIndex = getComputedStyle(element).zIndex;\n\n let el = target as HTMLElement;\n\n while (el) {\n // Getting the computed style only works for elements that exist in the DOM.\n // In certain scenarios, an element is removed after a click event; by the time the event\n // bubbles up to other elements, however, the element has been removed and the computed style returns empty.\n // In this case, we'll need to check the z-index directly, via the style property.\n const targetZIndex = getComputedStyle(el).zIndex || el.style.zIndex;\n if (\n targetZIndex !== '' &&\n targetZIndex !== 'auto' &&\n +targetZIndex > +zIndex\n ) {\n return true;\n }\n\n el = el.parentElement as HTMLElement;\n }\n\n return false;\n }\n\n /**\n * Remove inline height styles from the provided elements.\n * @param elementRef - The element to search within.\n * @param selector - The CSS selector to use when finding elements for removing height.\n */\n public resetHeight(elementRef: ElementRef, selector: string): void {\n const children = Array.from<HTMLElement>(\n elementRef.nativeElement.querySelectorAll(selector),\n );\n for (const child of children) {\n this.#renderer.removeStyle(child, 'height');\n }\n }\n\n /**\n * Sets all element heights to match the height of the tallest element.\n * @param elementRef - The element to search within.\n * @param selector - The CSS selector to use when finding elements for syncing height.\n */\n public syncMaxHeight(elementRef: ElementRef, selector: string): void {\n const children = Array.from<HTMLElement>(\n elementRef.nativeElement.querySelectorAll(selector),\n );\n /* istanbul ignore else */\n if (children.length > 0) {\n let maxHeight = 0;\n for (const child of children) {\n maxHeight = Math.max(maxHeight, child.offsetHeight);\n }\n for (const child of children) {\n this.#renderer.setStyle(child, 'height', `${maxHeight}px`);\n }\n }\n }\n\n #focusFirstElement(list: HTMLElement[]): boolean {\n if (list.length > 0) {\n list[0].focus();\n return true;\n }\n return false;\n }\n\n #isVisible(element: HTMLElement): boolean {\n const style = window.getComputedStyle(element);\n const isHidden = style.display === 'none' || style.visibility === 'hidden';\n if (isHidden) {\n return false;\n }\n\n const hasBounds = !!(\n element.offsetWidth ||\n element.offsetHeight ||\n element.getClientRects().length\n );\n return hasBounds;\n }\n}\n","export enum SkyAffixAutoFitContext {\n /**\n * Auto-fit functionality will respect the nearest overflow parent element's dimensions.\n */\n OverflowParent = 0,\n\n /**\n * Auto-fit functionality will respect the browser viewport dimensions.\n */\n Viewport = 1,\n}\n","import { SkyAffixPlacement } from './affix-placement';\n\nexport function getNextPlacement(\n placement: SkyAffixPlacement,\n): SkyAffixPlacement {\n const placements: SkyAffixPlacement[] = ['above', 'right', 'below', 'left'];\n\n let index = placements.indexOf(placement) + 1;\n if (index >= placements.length) {\n index = 0;\n }\n\n return placements[index];\n}\n\nexport function getInversePlacement(\n placement: SkyAffixPlacement,\n): SkyAffixPlacement {\n const pairings: Record<string, SkyAffixPlacement> = {\n above: 'below',\n below: 'above',\n right: 'left',\n left: 'right',\n };\n\n return pairings[placement];\n}\n","import { ViewportRuler } from '@angular/cdk/overlay';\n\nimport { SkyAffixOffset } from './affix-offset';\nimport { AffixRect } from './affix-rect';\n\nfunction useViewportForBounds(element: HTMLElement): boolean {\n return 'BODY' === element.tagName;\n}\n\n/**\n * Returns the offset values of a given element.\n * @param element The HTML element.\n * @param bufferOffset An optional offset to add/subtract to the element's actual offset.\n */\nexport function getElementOffset(\n element: HTMLElement,\n bufferOffset?: SkyAffixOffset,\n): Required<SkyAffixOffset> {\n const bufferOffsetBottom = bufferOffset?.bottom || 0;\n const bufferOffsetLeft = bufferOffset?.left || 0;\n const bufferOffsetRight = bufferOffset?.right || 0;\n const bufferOffsetTop = bufferOffset?.top || 0;\n\n let top: number;\n let left: number;\n let right: number;\n let bottom: number;\n\n const clientRect = element.getBoundingClientRect();\n left = clientRect.left;\n top = clientRect.top;\n right = clientRect.right;\n bottom = clientRect.bottom;\n\n bottom -= bufferOffsetBottom;\n left += bufferOffsetLeft;\n right -= bufferOffsetRight;\n top += bufferOffsetTop;\n\n return {\n bottom,\n left,\n right,\n top,\n };\n}\n\n/**\n * Returns an AffixRect that represents the outer dimensions of a given element.\n */\nexport function getOuterRect(element: HTMLElement): Required<AffixRect> {\n const rect = element.getBoundingClientRect();\n const computedStyle = window.getComputedStyle(element, undefined);\n const marginTop = parseFloat(computedStyle.marginTop);\n const marginLeft = parseFloat(computedStyle.marginLeft);\n const marginRight = parseFloat(computedStyle.marginRight);\n const marginBottom = parseFloat(computedStyle.marginBottom);\n\n return {\n top: rect.top - marginTop,\n left: rect.left - marginLeft,\n bottom: rect.top + rect.height + marginBottom,\n right: rect.left + rect.width + marginLeft + marginRight,\n width: rect.width + marginLeft + marginRight,\n height: rect.height + marginTop + marginBottom,\n };\n}\n\n/**\n * Returns the visible rect for a given element.\n */\nexport function getVisibleRectForElement(\n viewportRuler: ViewportRuler,\n element: HTMLElement,\n): Required<AffixRect> {\n const elementRect = getOuterRect(element);\n const viewportRect = viewportRuler.getViewportRect();\n\n const visibleRect = {\n top: Math.max(elementRect.top, 0),\n left: Math.max(elementRect.left, 0),\n bottom: Math.min(elementRect.bottom, viewportRect.height),\n right: Math.min(elementRect.right, viewportRect.width),\n };\n\n return {\n ...visibleRect,\n width: visibleRect.right - visibleRect.left,\n height: visibleRect.bottom - visibleRect.top,\n };\n}\n\nexport function getOverflowParents(child: HTMLElement): HTMLElement[] {\n const bodyElement = window.document.body;\n const results = [];\n\n let parentElement = child?.parentNode;\n\n while (parentElement !== undefined && parentElement instanceof HTMLElement) {\n if (parentElement.matches('body')) {\n break;\n }\n\n const computedStyle = window.getComputedStyle(parentElement, undefined);\n const overflowY = computedStyle.overflowY.toLowerCase();\n\n const largerThanTheDocumentElement =\n window.document.documentElement.scrollWidth < parentElement.scrollWidth ||\n window.document.documentElement.scrollHeight < parentElement.scrollHeight;\n const hasOverflowRules =\n overflowY === 'auto' || overflowY === 'hidden' || overflowY === 'scroll';\n\n if (largerThanTheDocumentElement || hasOverflowRules) {\n results.push(parentElement);\n }\n if (computedStyle.position === 'fixed') {\n break;\n }\n\n parentElement = parentElement.parentNode;\n }\n\n results.push(bodyElement);\n\n return results;\n}\n\n/**\n * Confirms offset is fully visible within a parent element.\n */\nexport function isOffsetFullyVisibleWithinParent(\n viewportRuler: ViewportRuler,\n parent: HTMLElement,\n offset: Required<SkyAffixOffset>,\n bufferOffset?: SkyAffixOffset,\n): boolean {\n let parentOffset: Required<SkyAffixOffset>;\n\n if (useViewportForBounds(parent)) {\n const viewportRect = viewportRuler.getViewportRect();\n parentOffset = {\n top: 0,\n left: 0,\n right: viewportRect.width,\n bottom: viewportRect.height,\n };\n } else if (bufferOffset) {\n parentOffset = getElementOffset(parent, bufferOffset);\n } else {\n parentOffset = getVisibleRectForElement(viewportRuler, parent);\n }\n\n return (\n parentOffset.top <= offset.top &&\n parentOffset.right >= offset.right &&\n parentOffset.bottom >= offset.bottom &&\n parentOffset.left <= offset.left\n );\n}\n\nexport function isOffsetPartiallyVisibleWithinParent(\n viewportRuler: ViewportRuler,\n parent: HTMLElement,\n offset: Required<SkyAffixOffset>,\n bufferOffset?: SkyAffixOffset,\n): boolean {\n let parentOffset: Required<SkyAffixOffset>;\n if (useViewportForBounds(parent)) {\n const viewportRect = viewportRuler.getViewportRect();\n parentOffset = {\n top: 0,\n left: 0,\n right: viewportRect.width,\n bottom: viewportRect.height,\n };\n } else if (bufferOffset) {\n parentOffset = getElementOffset(parent, bufferOffset);\n } else {\n parentOffset = getVisibleRectForElement(viewportRuler, parent);\n }\n\n return !(\n parentOffset.top >= offset.bottom ||\n parentOffset.right <= offset.left ||\n parentOffset.bottom <= offset.top ||\n parentOffset.left >= offset.right\n );\n}\n","import { ViewportRuler } from '@angular/cdk/overlay';\nimport { NgZone, Renderer2 } from '@angular/core';\n\nimport { Observable, Subject, Subscription } from 'rxjs';\n\nimport { SkyAffixAutoFitContext } from './affix-auto-fit-context';\nimport { SkyAffixConfig } from './affix-config';\nimport { SkyAffixOffset } from './affix-offset';\nimport { SkyAffixOffsetChange } from './affix-offset-change';\nimport { SkyAffixPlacement } from './affix-placement';\nimport { SkyAffixPlacementChange } from './affix-placement-change';\nimport { AffixRect } from './affix-rect';\nimport { getInversePlacement, getNextPlacement } from './affix-utils';\nimport {\n getElementOffset,\n getOuterRect,\n getOverflowParents,\n getVisibleRectForElement,\n isOffsetFullyVisibleWithinParent,\n isOffsetPartiallyVisibleWithinParent,\n} from './dom-utils';\n\n/**\n * Make specific properties required, so that we don't have to\n * do null checks throughout this file.\n */\ntype AffixConfigOrDefaults = SkyAffixConfig &\n Required<\n Pick<\n SkyAffixConfig,\n | 'autoFitContext'\n | 'enableAutoFit'\n | 'horizontalAlignment'\n | 'isSticky'\n | 'placement'\n >\n >;\n\nconst DEFAULT_AFFIX_CONFIG: AffixConfigOrDefaults = {\n autoFitContext: SkyAffixAutoFitContext.OverflowParent,\n enableAutoFit: false,\n horizontalAlignment: 'center',\n isSticky: false,\n placement: 'above',\n};\n\nexport class SkyAffixer {\n /**\n * Fires when the affixed element's offset changes.\n */\n public get offsetChange(): Observable<SkyAffixOffsetChange> {\n return this.#offsetChangeObs;\n }\n\n /**\n * Fires when the base element's nearest overflow parent is scrolling. This is useful if you need\n * to perform an additional action during the scroll event but don't want to generate another\n * event listener.\n */\n public get overflowScroll(): Observable<void> {\n return this.#overflowScrollObs;\n }\n\n /**\n * Fires when the placement value changes. A `null` value indicates that a suitable\n * placement could not be found.\n */\n public get placementChange(): Observable<SkyAffixPlacementChange> {\n return this.#placementChangeObs;\n }\n\n get #config(): AffixConfigOrDefaults {\n return this.#_config;\n }\n\n set #config(value: SkyAffixConfig | undefined) {\n const merged: AffixConfigOrDefaults = {\n ...DEFAULT_AFFIX_CONFIG,\n ...value,\n };\n\n // Make sure none of the values are undefined.\n let key: keyof typeof merged;\n for (key in merged) {\n if (merged[key] === undefined) {\n (merged as any)[key] = DEFAULT_AFFIX_CONFIG[key];\n }\n }\n\n this.#_config = merged;\n }\n\n #affixedElement: HTMLElement;\n\n #baseElement: HTMLElement | undefined;\n\n #currentOffset: SkyAffixOffset | undefined;\n\n #currentPlacement: SkyAffixPlacement | undefined;\n\n #layoutViewport: HTMLElement;\n\n #offsetChange: Subject<SkyAffixOffsetChange>;\n\n #offsetChangeObs: Observable<SkyAffixOffsetChange>;\n\n #overflowParents: HTMLElement[] = [];\n\n #overflowScroll: Subject<void>;\n\n #overflowScrollObs: Observable<void>;\n\n #placementChange: Subject<SkyAffixPlacementChange>;\n\n #placementChangeObs: Observable<SkyAffixPlacementChange>;\n\n #renderer: Renderer2;\n\n #scrollChange = new Subject<void>();\n\n #viewportListeners: Subscription | undefined;\n\n #viewportRuler: ViewportRuler;\n\n #zone: NgZone;\n\n #_config: AffixConfigOrDefaults = DEFAULT_AFFIX_CONFIG;\n\n #scrollChangeListener: () => void = () => this.#scrollChange.next();\n\n constructor(\n affixedElement: HTMLElement,\n renderer: Renderer2,\n viewportRuler: ViewportRuler,\n zone: NgZone,\n layoutViewport: HTMLElement,\n ) {\n this.#affixedElement = affixedElement;\n this.#renderer = renderer;\n this.#layoutViewport = layoutViewport;\n this.#viewportRuler = viewportRuler;\n this.#zone = zone;\n\n this.#offsetChange = new Subject<SkyAffixOffsetChange>();\n this.#overflowScroll = new Subject<void>();\n this.#placementChange = new Subject<SkyAffixPlacementChange>();\n\n this.#offsetChangeObs = this.#offsetChange.asObservable();\n this.#overflowScrollObs = this.#overflowScroll.asObservable();\n this.#placementChangeObs = this.#placementChange.asObservable();\n }\n\n /**\n * Affixes an element to a base element.\n * @param baseElement The base element.\n * @param config Configuration for the affix action.\n */\n public affixTo(baseElement: HTMLElement, config?: SkyAffixConfig): void {\n this.#reset();\n\n this.#config = config;\n this.#baseElement = baseElement;\n this.#overflowParents = getOverflowParents(baseElement);\n\n this.#affix();\n\n if (this.#config.isSticky) {\n this.#addViewportListeners();\n }\n }\n\n public getConfig(): SkyAffixConfig {\n return this.#config;\n }\n\n /**\n * Re-runs the affix calculation.\n */\n public reaffix(): void {\n // Reset current placement to preferred placement.\n this.#currentPlacement = this.#config.placement;\n this.#affix();\n }\n\n /**\n * Destroys the affixer.\n */\n public destroy(): void {\n this.#reset();\n this.#placementChange.complete();\n this.#offsetChange.complete();\n this.#overflowScroll.complete();\n this.#scrollChange.complete();\n }\n\n #affix(): void {\n const offset = this.#getOffset();\n\n const offsetParentRect = this.#getOffsetParentRect();\n offset.top = offset.top - offsetParentRect.top;\n offset.left = offset.left - offsetParentRect.left;\n offset.bottom = offset.bottom - offsetParentRect.top;\n offset.right = offset.right - offsetParentRect.left;\n\n if (this.#isNewOffset(offset)) {\n this.#renderer.setStyle(this.#affixedElement, 'top', `${offset.top}px`);\n this.#renderer.setStyle(this.#affixedElement, 'left', `${offset.left}px`);\n\n this.#offsetChange.next({ offset });\n }\n }\n\n #getOffsetParentRect(): AffixRect {\n // Firefox sets the offsetParent to document.body if the element uses fixed positioning.\n if (\n this.#config.position === 'absolute' &&\n this.#affixedElement.offsetParent\n ) {\n return getOuterRect(this.#affixedElement.offsetParent as HTMLElement);\n } else {\n const layoutRect = getOuterRect(this.#layoutViewport);\n return {\n top: layoutRect.top,\n left: layoutRect.left,\n height: layoutRect.height,\n width: layoutRect.width,\n bottom: layoutRect.top - layoutRect.height,\n right: layoutRect.left - layoutRect.width,\n };\n }\n }\n\n #getOffset(): Required<SkyAffixOffset> {\n const parent = this.#getAutoFitContextParent();\n\n const maxAttempts = 4;\n let attempts = 0;\n\n let isAffixedElementFullyVisible = false;\n let offset: Required<SkyAffixOffset>;\n let placement = this.#config.placement;\n\n do {\n offset = this.#getPreferredOffset(placement);\n isAffixedElementFullyVisible = isOffsetFullyVisibleWithinParent(\n this.#viewportRuler,\n parent,\n offset,\n this.#config.autoFitOverflowOffset,\n );\n\n if (!this.#config.enableAutoFit) {\n break;\n }\n\n if (!isAffixedElementFullyVisible) {\n placement =\n attempts % 2 === 0\n ? getInversePlacement(placement)\n : getNextPlacement(placement);\n }\n\n attempts++;\n } while (!isAffixedElementFullyVisible && attempts < maxAttempts);\n\n if (isAffixedElementFullyVisible) {\n if (this.#isBaseElementVisible()) {\n this.#notifyPlacementChange(placement);\n } else {\n this.#notifyPlacementChange(null);\n }\n\n return offset;\n }\n\n if (this.#config.enableAutoFit) {\n this.#notifyPlacementChange(null);\n }\n\n // No suitable placement was found, so revert to preferred placement.\n return this.#getPreferredOffset(this.#config.placement);\n }\n\n #getPreferredOffset(placement: SkyAffixPlacement): Required<SkyAffixOffset> {\n if (!this.#baseElement) {\n return { top: 0, left: 0, bottom: 0, right: 0 };\n }\n\n const affixedRect = getOuterRect(this.#affixedElement);\n const baseRect = this.#baseElement.getBoundingClientRect();\n\n const horizontalAlignment = this.#config.horizontalAlignment;\n const verticalAlignment = this.#config.verticalAlignment;\n const enableAutoFit = this.#config.enableAutoFit;\n\n let top: number;\n let left: number;\n\n if (placement === 'above' || placement === 'below') {\n if (placement === 'above') {\n top = baseRect.top - affixedRect.height;\n\n switch (verticalAlignment) {\n case 'top':\n top = top + affixedRect.height;\n break;\n case 'middle':\n top = top + affixedRect.height / 2;\n break;\n case 'bottom':\n default:\n break;\n }\n } else {\n top = baseRect.bottom;\n\n switch (verticalAlignment) {\n case 'top':\n default:\n break;\n case 'middle':\n top = top - affixedRect.height / 2;\n break;\n case 'bottom':\n top = top - affixedRect.height;\n break;\n }\n }\n\n switch (horizontalAlignment) {\n case 'left':\n left = baseRect.left;\n break;\n\n case 'center':\n default:\n left = baseRect.left + baseRect.width / 2 - affixedRect.width / 2;\n break;\n\n case 'right':\n left = baseRect.right - affixedRect.width;\n break;\n }\n } else {\n if (placement === 'left') {\n left = baseRect.left - affixedRect.width;\n } else {\n left = baseRect.right;\n }\n\n switch (verticalAlignment) {\n case 'top':\n top = baseRect.top;\n break;\n\n case 'middle':\n default:\n top = baseRect.top + baseRect.height / 2 - affixedRect.height / 2;\n break;\n\n case 'bottom':\n top = baseRect.bottom - affixedRect.height;\n break;\n }\n }\n\n const offset: Required<SkyAffixOffset> = { top, left, bottom: 0, right: 0 };\n\n if (enableAutoFit) {\n const adjustments = this.#adjustOffsetToOverflowParent(\n { top, left },\n placement,\n this.#baseElement,\n );\n offset.top = adjustments.top;\n offset.left = adjustments.left;\n }\n\n offset.bottom = offset.top + affixedRect.height;\n offset.right = offset.left + affixedRect.width;\n\n return offset;\n }\n\n /**\n * Slightly adjust the offset to fit within the scroll parent's boundaries if\n * the affixed element would otherwise be clipped.\n */\n #adjustOffsetToOverflowParent(\n offset: { top: number; left: number },\n placement: SkyAffixPlacement,\n baseElement: HTMLElement,\n ): { top: number; left: number } {\n const affixedRect = getOuterRect(this.#affixedElement);\n const baseRect = baseElement.getBoundingClientRect();\n\n const parent = this.#getAutoFitContextParent();\n let parentOffset: Required<SkyAffixOffset>;\n\n if (this.#config.autoFitOverflowOffset) {\n // When the config contains a specific offset.\n parentOffset = getElementOffset(\n parent,\n this.#config.autoFitOverflowOffset,\n );\n } else if (\n isOffsetFullyVisibleWithinParent(this.#viewportRuler, parent, baseRect)\n ) {\n // When the base element is fully visible within the parent, aim for the visible portion of the parent element.\n parentOffset = getVisibleRectForElement(this.#viewportRuler, parent);\n } else {\n // Anywhere in the parent element.\n parentOffset = getOuterRect(parent);\n }\n\n // A pixel value representing the leeway between the edge of the overflow parent and the edge\n // of the base element before it disappears from view.\n // If the visible portion of the base element is less than this pixel value, the auto-fit\n // functionality attempts to find another placement.\n const defaultPixelTolerance = 40;\n let pixelTolerance: number;\n\n const originalOffsetTop = offset.top;\n const originalOffsetLeft = offset.left;\n\n switch (placement) {\n case 'above':\n case 'below':\n // Keep the affixed element within the overflow parent.\n if (offset.left < parentOffset.left) {\n offset.left = parentOffset.left;\n } else if (offset.left + affixedRect.width > parentOffset.right) {\n offset.left = parentOffset.right - affixedRect.width;\n }\n\n // Use a smaller pixel tolerance if the base element width is less than the default.\n pixelTolerance = Math.min(defaultPixelTolerance, baseRect.width);\n\n // Make sure the affixed element never detaches from the base element.\n if (\n offset.left + pixelTolerance > baseRect.right ||\n offset.left + affixedRect.width - pixelTolerance < baseRect.left\n ) {\n offset.left = originalOffsetLeft;\n }\n\n break;\n\n case 'left':\n case 'right':\n // Keep the affixed element within the overflow parent.\n if (offset.top < parentOffset.top) {\n offset.top = parentOffset.top;\n } else if (offset.top + affixedRect.height > parentOffset.bottom) {\n offset.top = parentOffset.bottom - affixedRect.height;\n }\n\n // Use a smaller pixel tolerance if the base element height is less than the default.\n pixelTolerance = Math.min(defaultPixelTolerance, baseRect.height);\n\n // Make sure the affixed element never detaches from the base element.\n if (\n offset.top + pixelTolerance > baseRect.bottom ||\n offset.top + affixedRect.height - pixelTolerance < baseRect.top\n ) {\n offset.top = originalOffsetTop;\n }\n\n break;\n }\n\n return offset;\n }\n\n #getImmediateOverflowParent(): HTMLElement {\n return this.#overflowParents[0];\n }\n\n #getAutoFitContextParent(): HTMLElement {\n const bodyElement = this.#overflowParents[this.#overflowParents.length - 1];\n\n return this.#config.autoFitContext === SkyAffixAutoFitContext.OverflowParent\n ? this.#getImmediateOverflowParent()\n : bodyElement;\n }\n\n #notifyPlacementChange(placement: SkyAffixPlacement | null): void {\n if (this.#currentPlacement !== placement) {\n this.#currentPlacement = placement ?? undefined;\n this.#placementChange.next({\n placement,\n });\n }\n }\n\n #reset(): void {\n this.#removeViewportListeners();\n\n this.#overflowParents = [];\n\n this.#config =\n this.#baseElement =\n this.#currentPlacement =\n this.#currentOffset =\n undefined;\n }\n\n #isNewOffset(offset: SkyAffixOffset): boolean {\n if (this.#currentOffset === undefined) {\n this.#currentOffset = offset;\n return true;\n }\n\n if (\n this.#currentOffset.top === offset.top &&\n this.#currentOffset.left === offset.left\n ) {\n return false;\n }\n\n this.#currentOffset = offset;\n\n return true;\n }\n\n #isBaseElementVisible(): boolean {\n // Can't get here if the base element is undefined.\n /* istanbul ignore if */\n if (!this.#baseElement) {\n return false;\n }\n\n const baseRect = this.#baseElement.getBoundingClientRect();\n\n return isOffsetPartiallyVisibleWithinParent(\n this.#viewportRuler,\n this.#getImmediateOverflowParent(),\n {\n top: baseRect.top,\n left: baseRect.left,\n right: baseRect.right,\n bottom: baseRect.bottom,\n },\n this.#config.autoFitOverflowOffset,\n );\n }\n\n #addViewportListeners(): void {\n this.#viewportListeners = new Subscription();\n\n // Resize and orientation changes.\n this.#viewportListeners.add(\n this.#viewportRuler.change().subscribe(() => {\n this.#affix();\n }),\n );\n\n this.#viewportListeners.add(\n this.#scrollChange.subscribe(() => {\n this.#affix();\n this.#overflowScroll.next();\n }),\n );\n\n // Listen for scroll events on the window, visual viewport, and any overflow parents.\n // https://developer.chrome.com/blog/visual-viewport-api/#events-only-fire-when-the-visual-viewport-changes\n this.#zone.runOutsideAngular(() => {\n [window, window.visualViewport, ...this.#overflowParents].forEach(\n (parentElement) => {\n parentElement?.addEventListener('scroll', this.#scrollChangeListener);\n },\n );\n });\n }\n\n #removeViewportListeners(): void {\n this.#viewportListeners?.unsubscribe();\n\n this.#zone.runOutsideAngular(() => {\n [window, window.visualViewport, ...this.#overflowParents].forEach(\n (parentElement) => {\n parentElement?.removeEventListener(\n 'scroll',\n this.#scrollChangeListener,\n );\n },\n );\n });\n }\n}\n","import { ViewportRuler } from '@angular/cdk/overlay';\nimport { DOCUMENT } from '@angular/common';\nimport {\n ElementRef,\n Injectable,\n NgZone,\n OnDestroy,\n RendererFactory2,\n inject,\n} from '@angular/core';\n\nimport { SkyAffixer } from './affixer';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SkyAffixService implements OnDestroy {\n readonly #renderer = inject(RendererFactory2).createRenderer(undefined, null);\n\n readonly #viewportRuler = inject(ViewportRuler);\n\n readonly #zone = inject(NgZone);\n\n readonly #layoutViewport = this.#createLayoutViewportShim(inject(DOCUMENT));\n\n public ngOnDestroy(): void {\n this.#renderer.removeChild(\n this.#layoutViewport.parentNode,\n this.#layoutViewport,\n );\n }\n\n /**\n * Creates an instance of [[SkyAffixer]].\n * @param affixed The element to be affixed.\n */\n public createAffixer(affixed: ElementRef): SkyAffixer {\n return new SkyAffixer(\n affixed.nativeElement,\n this.#renderer,\n this.#viewportRuler,\n this.#zone,\n this.#layoutViewport,\n );\n }\n\n /**\n * Create a layout viewport element that can be used to determine the relative position\n * of the visual viewport. Inspired by\n * https://github.com/WICG/visual-viewport/blob/gh-pages/examples/fixed-to-viewport.html\n */\n #createLayoutViewportShim(doc: Document): HTMLElement {\n const layoutViewportElement = this.#renderer.createElement('div');\n this.#renderer.addClass(\n layoutViewportElement,\n 'sky-affix-layout-viewport-shim',\n );\n this.#renderer.setStyle(layoutViewportElement, 'width', '100%');\n this.#renderer.setStyle(layoutViewportElement, 'height', '100%');\n this.#renderer.setStyle(layoutViewportElement, 'position', 'fixed');\n this.#renderer.setStyle(layoutViewportElement, 'top', '0');\n this.#renderer.setStyle(layoutViewportElement, 'left', '0');\n this.#renderer.setStyle(layoutViewportElement, 'visibility', 'hidden');\n this.#renderer.setStyle(layoutViewportElement, 'pointerEvents', 'none');\n this.#renderer.appendChild(doc.body, layoutViewportElement);\n\n return layoutViewportElement;\n }\n}\n","import {\n Directive,\n ElementRef,\n EventEmitter,\n Input,\n OnChanges,\n OnDestroy,\n OnInit,\n Output,\n SimpleChanges,\n} from '@angular/core';\n\nimport { Subject } from 'rxjs';\nimport { takeUntil } from 'rxjs/operators';\n\nimport { SkyAffixAutoFitContext } from './affix-auto-fit-context';\nimport { SkyAffixHorizontalAlignment } from './affix-horizontal-alignment';\nimport { SkyAffixOffset } from './affix-offset';\nimport { SkyAffixOffsetChange } from './affix-offset-change';\nimport { SkyAffixPlacement } from './affix-placement';\nimport { SkyAffixPlacementChange } from './affix-placement-change';\nimport { SkyAffixPosition } from './affix-position';\nimport { SkyAffixVerticalAlignment } from './affix-vertical-alignment';\nimport { SkyAffixService } from './affix.service';\nimport { SkyAffixer } from './affixer';\n\n/**\n * Affixes the host element to a base element.\n */\n@Directive({\n selector: '[skyAffixTo]',\n standalone: false,\n})\nexport class SkyAffixDirective implements OnInit, OnChanges, OnDestroy {\n /**\n * The base element to affix the host element.\n */\n @Input()\n public skyAffixTo: HTMLElement | undefined;\n\n /**\n * Sets the `autoFitContext` property of [[SkyAffixConfig]].\n */\n @Input()\n public affixAutoFitContext: SkyAffixAutoFitContext | undefined;\n\n /**\n * Sets the `autoFitOverflowOffset` property of [[SkyAffixConfig]].\n */\n @Input()\n public affixAutoFitOverflowOffset: SkyAffixOffset | undefined;\n\n /**\n * Sets the `enableAutoFit` property of [[SkyAffixConfig]].\n */\n @Input()\n public affixEnableAutoFit: boolean | undefined;\n\n /**\n * Sets the `horizontalAlignment` property of [[SkyAffixConfig]].\n */\n @Input()\n public affixHorizontalAlignment: SkyAffixHorizontalAlignment | undefined;\n\n /**\n * Sets the `isSticky` property of [[SkyAffixConfig]].\n */\n @Input()\n public affixIsSticky: boolean | undefined;\n\n /**\n * Sets the `placement` property of [[SkyAffixConfig]].\n */\n @Input()\n public affixPlacement: SkyAffixPlacement | undefined;\n\n /**\n * Sets the `position` property of [[SkyAffixConfig]].\n */\n @Input()\n public affixPosition: SkyAffixPosition | undefined;\n\n /**\n * Sets the `verticalAlignment` property of [[SkyAffixConfig]].\n */\n @Input()\n public affixVerticalAlignment: SkyAffixVerticalAlignment | undefined;\n\n /**\n * Fires when the affixed element's offset changes.\n */\n @Output()\n public affixOffsetChange = new EventEmitter<SkyAffixOffsetChange>();\n\n /**\n * Fires when the affixed element's overflow container is scrolled.\n */\n @Output()\n public affixOverflowScroll = new EventEmitter<void>();\n\n /**\n * Fires when the placement value changes.\n */\n @Output()\n public affixPlacementChange = new EventEmitter<SkyAffixPlacementChange>();\n\n #affixer: SkyAffixer | undefined;\n\n #affixService: SkyAffixService;\n\n #elementRef: ElementRef;\n\n #ngUnsubscribe = new Subject<void>();\n\n constructor(elementRef: ElementRef, affixService: SkyAffixService) {\n this.#elementRef = elementRef;\n this.#affixService = affixService;\n }\n\n public ngOnInit(): void {\n this.#affixer = this.#affixService.createAffixer(this.#elementRef);\n\n this.#affixer.offsetChange\n .pipe(takeUntil(this.#ngUnsubscribe))\n .subscribe((change) => this.affixOffsetChange.emit(change));\n\n this.#affixer.overflowScroll\n .pipe(takeUntil(this.#ngUnsubscribe))\n .subscribe((change) => this.affixOverflowScroll.emit(change));\n\n this.#affixer.placementChange\n .pipe(takeUntil(this.#ngUnsubscribe))\n .subscribe((change) => this.affixPlacementChange.emit(change));\n\n this.#updateAlignment();\n }\n\n public ngOnChanges(changes: SimpleChanges): void {\n /* istanbul ignore else */\n if (\n changes['affixAutoFitContext'] ||\n changes['affixAutoFitOverflowOffset'] ||\n changes['affixEnableAutoFit'] ||\n changes['affixHorizontalAlignment'] ||\n changes['affixIsSticky'] ||\n changes['affixPlacement'] ||\n changes['affixPosition'] ||\n changes['affixVerticalAlignment']\n ) {\n this.#updateAlignment();\n }\n }\n\n public ngOnDestroy(): void {\n this.affixOffsetChange.complete();\n this.affixOverflowScroll.complete();\n this.affixPlacementChange.complete();\n this.#ngUnsubscribe.next();\n this.#ngUnsubscribe.complete();\n\n /*istanbul ignore else*/\n if (this.#affixer) {\n this.#affixer.destroy();\n this.#affixer = undefined;\n }\n }\n\n #updateAlignment(): void {\n if (this.skyAffixTo && this.#affixer) {\n this.#affixer.affixTo(this.skyAffixTo, {\n autoFitContext: this.affixAutoFitContext,\n autoFitOverflowOffset: this.affixAutoFitOverflowOffset,\n enableAutoFit: this.affixEnableAutoFit,\n horizontalAlignment: this.affixHorizontalAlignment,\n isSticky: this.affixIsSticky,\n placement: this.affixPlacement,\n position: this.affixPosition,\n verticalAlignment: this.affixVerticalAlignment,\n });\n }\n }\n}\n","import { CommonModule } from '@angular/common';\nimport { NgModule } from '@angular/core';\n\nimport { SkyAffixDirective } from './affix.directive';\n\n@NgModule({\n imports: [CommonModule],\n exports: [SkyAffixDirective],\n declarations: [SkyAffixDirective],\n})\nexport class SkyAffixModule {}\n","import { Injectable } from '@angular/core';\n\nimport { Observable, ReplaySubject } from 'rxjs';\n\nimport { SkyContentInfo } from './content-info';\n\n/**\n * @internal\n * An API to provide information about a parent component's content to child components.\n * For example, toolbar can use this to provide its child components with a list\n * descriptor they can use to construct aria labels, or tree view can provide the node\n * name to its context menus.\n */\n@Injectable()\nexport class SkyContentInfoProvider {\n #contentInfo = new ReplaySubject<SkyContentInfo>(1);\n #currentValue: SkyContentInfo = {};\n\n public patchInfo(value: SkyContentInfo): void {\n const newValue = {\n ...this.#currentValue,\n