Angular 工具库 | ng-hub-ui-utils
Angular 工具库,包含 pipes、焦点管理、overlay、过渡和翻译助手,供可复用 UI 组件共享使用。
最后更新 2026年9月20日
概览
团队为何搜索这个库
使用这个 Angular 工具库,避免重写 overlay、焦点陷阱、翻译助手和常见模板工具背后的底层部件。
安装
npm install ng-hub-ui-utils跳转到
适用于
- 共享 UI 基础设施
- overlay 系统
- 焦点管理
- 模板助手
关于 utils
ng-hub-ui-utils 是众多可复用 UI 模式背后的共享基础。当 Angular 团队希望集中管理那些让 overlay 系统、焦点管理、pipes 和翻译助手在各项目间保持一致的底层部件时,它会非常有用。
功能指南
焦点管理
在元素内捕获并管理键盘焦点
示例:
焦点捕获
使用 hubFocusTrap() 将焦点限定在 modal 或对话框内。
Focus Trap Demo
Click "Enable Focus Trap" and try to Tab outside the blue box. Focus will stay trapped inside.
Focus Trap Area
Status: 🔓 Free
Focusable Elements
The selector FOCUSABLE_ELEMENTS_SELECTOR finds all focusable elements.
getFocusableBoundaryElements(container)→ [first, last] elements可聚焦元素
使用 getFocusableBoundaryElements() 查询边界处的可聚焦元素。
Focus Trap Demo
Click "Enable Focus Trap" and try to Tab outside the blue box. Focus will stay trapped inside.
Focus Trap Area
Status: 🔓 Free
Focusable Elements
The selector FOCUSABLE_ELEMENTS_SELECTOR finds all focusable elements.
getFocusableBoundaryElements(container)→ [first, last] elements国际化(i18n)
用于多语言支持的翻译服务和 pipes
示例:
翻译服务
HubTranslationService 用于管理翻译
TranslatePipe - Basic usage
'welcome' | translate→ Welcome to ng-hub-uiTranslatePipe - With parameters
'greeting' | translate:{ name: 'Carlos' }→ Hello, Carlos!TranslatePipe - Nested keys
'buttons.save' | translate→ Save'buttons.cancel' | translate→ CancelProgrammatic access
translationService.getTranslation('buttons.delete')→ Delete翻译 pipe
TranslatePipe 用于模板中的翻译
TranslatePipe - Basic usage
'welcome' | translate→ Welcome to ng-hub-uiTranslatePipe - With parameters
'greeting' | translate:{ name: 'Carlos' }→ Hello, Carlos!TranslatePipe - Nested keys
'buttons.save' | translate→ Save'buttons.cancel' | translate→ CancelProgrammatic access
translationService.getTranslation('buttons.delete')→ Delete翻译 provider
provideHubTranslations() 用于应用配置
TranslatePipe - Basic usage
'welcome' | translate→ Welcome to ng-hub-uiTranslatePipe - With parameters
'greeting' | translate:{ name: 'Carlos' }→ Hello, Carlos!TranslatePipe - Nested keys
'buttons.save' | translate→ Save'buttons.cancel' | translate→ CancelProgrammatic access
translationService.getTranslation('buttons.delete')→ Delete外部 i18n 适配器
在 app.config.ts 中配置一个响应式翻译桥接器,供所有 Hub UI 库使用。
在 app.config.ts 中配置一个响应式翻译桥接器,供所有 Hub UI 库使用。
代码
导入:
模板:
组件:
覆盖层系统
创建定位的 overlays 和浮动元素
示例:
Overlay 服务
HubOverlayService 用于以编程方式创建 overlay
A connected overlay positioned below the trigger, with a backdrop that closes it on outside click.
Overlay 引用
OverlayRef 用于管理 overlay 的生命周期
A connected overlay positioned below the trigger, with a backdrop that closes it on outside click.
定位
ConnectionPositionPair 用于灵活定位
A connected overlay positioned below the trigger, with a backdrop that closes it on outside click.
Popup 服务
简化的 popup 管理
示例:
Popup 创建
HubPopupService 用于快速创建 popup
Nothing open.
Pipes
用于模板的工具 pipes
示例:
类型检查 pipes
IsStringPipe、IsObjectPipe、IsObservablePipe
GetPipe - Access nested properties
user | get:'profile.name'→ John Doeuser | get:'profile.address.city'→ New YorkIsStringPipe - Type checking
'Hello' | isString→ true123 | isString→ falseIsObjectPipe - Object detection
{ a: 1 } | isObject→ true'string' | isObject→ falseUcfirstPipe - Capitalize first letter
'hello world' | ucfirst→ Hello worldUnwrapAsyncPipe - Observable or plain value, same template
status$ | unwrapAsync→ nothing emitted yet'not an observable' | unwrapAsync→ not an observableGetPipe
使用点号表示法访问嵌套对象属性
GetPipe - Access nested properties
user | get:'profile.name'→ John Doeuser | get:'profile.address.city'→ New YorkIsStringPipe - Type checking
'Hello' | isString→ true123 | isString→ falseIsObjectPipe - Object detection
{ a: 1 } | isObject→ true'string' | isObject→ falseUcfirstPipe - Capitalize first letter
'hello world' | ucfirst→ Hello worldUnwrapAsyncPipe - Observable or plain value, same template
status$ | unwrapAsync→ nothing emitted yet'not an observable' | unwrapAsync→ not an observableUcfirstPipe
将字符串的首字母大写
GetPipe - Access nested properties
user | get:'profile.name'→ John Doeuser | get:'profile.address.city'→ New YorkIsStringPipe - Type checking
'Hello' | isString→ true123 | isString→ falseIsObjectPipe - Object detection
{ a: 1 } | isObject→ true'string' | isObject→ falseUcfirstPipe - Capitalize first letter
'hello world' | ucfirst→ Hello worldUnwrapAsyncPipe - Observable or plain value, same template
status$ | unwrapAsync→ nothing emitted yet'not an observable' | unwrapAsync→ not an observableUnwrapAsyncPipe
在模板中解包 observables 和 promises
GetPipe - Access nested properties
user | get:'profile.name'→ John Doeuser | get:'profile.address.city'→ New YorkIsStringPipe - Type checking
'Hello' | isString→ true123 | isString→ falseIsObjectPipe - Object detection
{ a: 1 } | isObject→ true'string' | isObject→ falseUcfirstPipe - Capitalize first letter
'hello world' | ucfirst→ Hello worldUnwrapAsyncPipe - Observable or plain value, same template
status$ | unwrapAsync→ nothing emitted yet'not an observable' | unwrapAsync→ not an observable工具函数
通用的 helper 函数
示例:
Type guards
isString()、isNumber()、isDefined()、isPromise()
Type Guards
isString('hello')→ trueisNumber(42)→ trueisDefined(null)→ falseisDefined('value')→ trueDeep Equality
equals({a:1}, {a:1})→ trueequals([1,2], [1,2])→ trueObject Access
getValue(user, 'profile.name')→ John DoeString Interpolation
interpolateString('Hello {{name}}', {name: 'World'})→ Hello WorldString Utilities
removeAccents('Ñoño café')→ Nono cafepadNumber(5)→ 05字符串工具
removeAccents()、interpolateString()、regExpEscape()
Type Guards
isString('hello')→ trueisNumber(42)→ trueisDefined(null)→ falseisDefined('value')→ trueDeep Equality
equals({a:1}, {a:1})→ trueequals([1,2], [1,2])→ trueObject Access
getValue(user, 'profile.name')→ John DoeString Interpolation
interpolateString('Hello {{name}}', {name: 'World'})→ Hello WorldString Utilities
removeAccents('Ñoño café')→ Nono cafepadNumber(5)→ 05对象工具
equals() 用于深度比较,getValue() 用于点分表示法
Type Guards
isString('hello')→ trueisNumber(42)→ trueisDefined(null)→ falseisDefined('value')→ trueDeep Equality
equals({a:1}, {a:1})→ trueequals([1,2], [1,2])→ trueObject Access
getValue(user, 'profile.name')→ John DoeString Interpolation
interpolateString('Hello {{name}}', {name: 'World'})→ Hello WorldString Utilities
removeAccents('Ñoño café')→ Nono cafepadNumber(5)→ 05DOM 工具
closest()、reflow()、getActiveElement()
closest()
Click anywhere inside; the helper walks up to the nearest panel.
closest(target, '[data-panel]') → —
reflow()
Replaying an animation means removing the class and adding it back. Without a forced reflow the browser coalesces both into no change at all.
getActiveElement()
Focus either control. The second one lives inside a shadow root.
document.activeElement → —
getActiveElement() → —
runInZone()
Both streams are created with runOutsideAngular; only one is piped through the operator.
| plain subscriber | isInAngularZone() → — |
|---|---|
| piped through runInZone(zone) | isInAngularZone() → — |
| ticks | 0 |
RxJS 工具
runInZone() 操作符,用于 NgZone 集成
closest()
Click anywhere inside; the helper walks up to the nearest panel.
closest(target, '[data-panel]') → —
reflow()
Replaying an animation means removing the class and adding it back. Without a forced reflow the browser coalesces both into no change at all.
getActiveElement()
Focus either control. The second one lives inside a shadow root.
document.activeElement → —
getActiveElement() → —
runInZone()
Both streams are created with runOutsideAngular; only one is piped through the operator.
| plain subscriber | isInAngularZone() → — |
|---|---|
| piped through runInZone(zone) | isInAngularZone() → — |
| ticks | 0 |
Scrollbar 工具
测量并补偿 scrollbar 宽度
示例:
Scrollbar 宽度
scrollbarWidth() 和 scrollbarPadding() 函数
ScrollBar Service
The ScrollBar service helps manage scrollbar visibility and compensate for layout shifts when hiding scrollbars (e.g., when opening modals).
Use Case: Modal Body Lock
When opening a modal, you typically hide the body scrollbar. The ScrollBar.hide() method handles this automatically and returns a reverter function.
Status: 🔓 Normal scrolling
Code Example
import { inject } from '@angular/core';
import { ScrollBar } from 'ng-hub-ui-utils';
export class ModalService {
private scrollBar = inject(ScrollBar);
private revertScrollbar: (() => void) | null = null;
openModal() {
// Hide scrollbar and get reverter function
this.revertScrollbar = this.scrollBar.hide();
}
closeModal() {
// Restore scrollbar
if (this.revertScrollbar) {
this.revertScrollbar();
this.revertScrollbar = null;
}
}
}过渡
CSS 过渡辅助函数
示例:
运行过渡
hubRunTransition() 用于以编程方式实现 CSS 过渡
- Run a transition to see when the observable completes.
提示
通过 [hubTooltip] 指令为任意元素提供轻量、可主题化的提示。
示例:
提示指令
为元素应用 [hubTooltip] 并通过 hubTooltipPlacement 指定位置,即可在悬停或聚焦时显示定位的标签,并可通过 --hub-tooltip-* 变量设置主题。旧的 [tooltip] 仍然可用,但已弃用:它未加前缀的输入名会与同一元素上的其他指令冲突。
--hub-tooltip-* variables颜色
无需 DOM 即可解析、测量和转换 CSS 颜色
示例:
颜色解析
parseColor()、toHex() 和 isValidColor() 可解析 hex、rgb()、hsl()、oklch()、oklab() 以及 148 种具名颜色,服务端渲染同样适用
Try one:
| parseColor() | { r: 11, g: 110, b: 255, a: 1 } |
|---|---|
| toHex() | #0b6eff |
| rgbToOklch() | { l: 0.58, c: 0.23, h: 260 } |
| isValidColor() | true |
| toRgb() | already parsed — same object back |
| HUB_NAMED_COLORS | not a bareword — 148 in the table |
对比度与可读性
contrastRatio()(WCAG 2)、contrastAPCA() 以及按 --hub-sys-color-*-on 令牌选择墨色的 readableOn()
A highlighted border marks a chip whose ink differs from what --hub-sys-color-*-on paints in CSS.
Translucent ink has to be composited before it is measured
relativeLuminance() ignores alpha, because a translucent colour has no luminance of its own until something is behind it. Measure the ink as written and you are scoring solid black; compositeOver() blends it onto the surface first, which is what the eye is reading.
| relativeLuminance(surface) | 0.1833 |
|---|---|
| contrastRatio(ink, surface) | 4.67:1 |
| …with compositeOver(ink, surface) | 2.75:1 |
OKLCh 转换
rgbToOklch()、oklchToRgb()、maxSrgbChroma() 和 clampToSrgbGamut(),在设计系统混色所用的色彩空间中处理调色板
clampToSrgbGamut()
Asking for chroma 0.35 on the amber hue at the current lightness.
false0.118调色板推导
harmoniseSemantics() 将 success、warning、danger 与 info 向品牌色相旋转,最多 15°;tintNeutrals() 以同样的方向倾斜灰阶,彩度上限为 0.015。明度始终不变,因此每个角色当初被选中的对比度得以保留。
Hue rotation capped at 15°; neutral chroma capped at 0.015. Success and danger stay 173° apart.
核心特性
最近更改
Version 22.15.2 - 9/20/26, 12:00 AM
changed: The package manifest carries a description and a keyword list. It was the only one of the 26 with neither, while seventeen libraries depend on it, and both fields are what npm ranks a search on. Metadata only: no code, types or styles change.
Version 22.15.1 - 9/16/26, 12:00 AM
changed: Repository, issue and README links follow the move to the hub-env organization. Issues for every Hub UI package are now gathered in hub-env/hub-ui, and the repository and bugs fields of the manifest point at the new addresses. No code, types or styles change.
Version 22.15.0 - 9/8/26, 12:00 AM
added: harmoniseSemantics() and tintNeutrals(), so a brand colour derives the whole palette in one place instead of once per product. The first rotates success, warning, danger and info towards the brand's hue; the second leans the grey ramp the same way. Both leave lightness exactly where the anchor had it, because lightness is what carries the contrast each role was chosen for. Two exported numbers decide the result: HUB_MAX_HUE_SHIFT (15°), which is what keeps success and danger more than 90° apart for a reader with deuteranopia — they start 135.8° apart and a brand hue between them closes the gap by up to twice the cap — and HUB_MAX_NEUTRAL_CHROMA (0.015), which sits between the design system's own gray-500 (0.0145) and gray-600 (0.0165), so a tinted ramp is never more colourful than the grey people already accept as grey.
fixed: The DOM helpers no longer reach for globals a server render does not have. ScrollBar.hide(), hubRunTransition(), getTransitionDurationMs(), reflow(), getActiveElement() and OverlayRef each read window or document straight out of scope, which is a ReferenceError on the way to a prerendered page. They now resolve the document and the view from what they already hold — the injected DOCUMENT, the application's injector, or the element's own ownerDocument.defaultView — which also fixes the same helpers inside an iframe, where the global was the wrong document all along.
changed: reflow() returns DOMRect | null and no longer falls back to document.body, and getActiveElement() accepts null as its root. Both are announced in BREAKING_CHANGES.md; no call that passes an element changes behaviour.
Version 22.14.0 - 9/7/26, 12:00 AM
removed: TooltipDirective and its bare [tooltip] attribute, deprecated since 22.9.0. An unprefixed selector is a name in the application's namespace rather than the library's: Angular hands one attribute to every directive on the element that declares an input of that name, so nobody else could own a tooltip — not a consumer writing their own, not <hub-badge>, which declares a tooltip input and ended up drawing two, and not [hubDropdown], whose own placement is typed over eight values where a tooltip understands four. [hubTooltip] is the replacement, attribute for attribute: tooltip → hubTooltip, placement → hubTooltipPlacement, delay → hubTooltipDelay, offset → hubTooltipOffset. Breaking, and note that a template still writing tooltip="…" keeps compiling and silently shows nothing — see BREAKING_CHANGES.md.
added: A guard over the whole entry point: no directive of this package may claim a bare attribute. It reads Angular's own compiled definitions rather than a hand-kept list, so the rule covers the directive somebody adds next and not only the one just removed.
Version 22.13.0 - 9/7/26, 12:00 AM
added: OverlayPosition.origin, the element the strategy is connected to. It was known to the strategy and to nobody else, and an overlay that has to keep up with its anchor needs to be able to ask which element that is.
fixed: A floating panel stayed behind the moment its trigger moved. The overlay recomputed its position on scroll and on resize, and both of those describe the page moving under an origin that stays put. Nothing covered the opposite, the origin moving inside a page nobody scrolled and no window resized: a section collapsing above it, an image landing late, a sidebar accordion animating shut. The panel then hung at the height the trigger used to have, cut loose from the thing it belongs to, and every connected overlay in the family opens through this service, so all of them had it. The origin is now watched for as long as the panel is open and the panel is re-placed whenever its box actually changes, every frame of an animated collapse rather than once at the start of one. The box is read per frame and nothing is written unless it moved, so an overlay whose trigger sits still never touches the DOM, and the loop runs outside Angular zone so a zone-based application does not run change detection while a menu is open.
Version 22.12.1 - 9/6/26, 12:00 AM
changed: Every row of FUNCTIONALITIES.md now points at a runnable demo. Fifteen features (the popup service, the transition helpers, UnwrapAsyncPipe, the DOM and RxJS helpers and five of the colour functions) were listed as having no example, which left the unit tests as the only executable use of them. Each of those now has a demo on the documentation site, so the table reports coverage instead of a wishlist. No API changed.
fixed: The tooltip and overlay stylesheets resolve at the path the documentation gives. The manifest declared no exports map, so ng-packagr synthesised one for the published package, and a synthesised map lists only "." and "./package.json". A package that declares exports closes every subpath outside that map, so @use "ng-hub-ui-utils/styles/tooltip"; resolved to nothing even though the sheet shipped in styles/. Both sheets are now named in the manifest, extensionless and with the .scss suffix, the way every sibling package in the family already does, and ng-packagr merges those entries into the map it generates instead of replacing them.
fixed: The tooltip is announced to assistive technology and can be dismissed without a mouse. The bubble was a bare span with no id and no role, and the host was never pointed at it, so on an icon-only button, the case the tooltip is written for, a screen reader had nothing to read and the only workaround was an aria-label repeating the same text. The bubble is now role="tooltip" with an id the host is aria-describedby while it is on screen, and the attribute is put back exactly as it was found, so a description a consumer wrote is neither replaced nor left behind. The same change closes WCAG 1.4.13 for it: Escape dismisses the label without moving the pointer or the focus, and the label waits out a short grace period and stays put once the pointer lands on it, which is the only way to read one longer than its box. styles/tooltip.scss therefore ships pointer-events: auto instead of none, and the controller disables them again the moment it starts fading, so an invisible bubble never catches a click meant for what is under it. It is reached through HubTooltipController, so it arrives at all four entry points at once: [hubTooltip], the deprecated [tooltip], [hubOverflowTooltip] and hubTooltipAdapter.
Version 22.12.0 - 9/3/26, 12:00 AM
added: Colour utilities — parseColor(), toHex(), isValidColor(), relativeLuminance(), contrastRatio(), contrastAPCA(), readableOn(), compositeOver() and the OKLCh helpers rgbToOklch(), oklchToRgb(), maxSrgbChroma(), isInSrgbGamut() and clampToSrgbGamut(). The parser resolves hex (3/4/6/8 digits), rgb(), hsl(), oklch(), oklab() and the 148 CSS named colours in both modern and legacy syntax, with no DOM involved, so it runs under server-side rendering. It returns null rather than throwing on anything it cannot resolve.
added: readableOn() picks black or white by OKLCh perceptual lightness, the same decision the --hub-sys-color-*-on token computes in CSS, so a component that resolves its ink in TypeScript cannot disagree with the stylesheet. The alternative metrics are available: maximising the WCAG 2 ratio puts black text on the design system's own blue, green and red accents, which is why it is not the default.
Version 22.11.1 - 9/1/26, 12:00 AM
changed: The homepage in the manifest points at this library's own documentation page rather than at the site root, so the link a registry shows beside the package lands on the reference for the package the reader was already looking at. Metadata only.
Version 22.11.0 - 8/26/26, 12:00 AM
added: The overlay follows its origin. While attached it listens for scroll and resize in the capture phase and recomputes its position, coalesced into an animation frame. Before this it computed coordinates once and never again: a panel opened and then scrolled sat 122px away from the field it belonged to.
added: start and end are logical. They resolved to left and right whatever the direction, so an overlay opened from a field inside an RTL container hung off the wrong edge. The direction is read from the origin element; OverlayPosition.withDirection() overrides it.
added: OverlayRef.onKeydown(), and with it a document-level dispatcher that tells only the topmost open overlay. An overlay rarely holds focus, so a component listening on its own host never heard Escape and the panel that took over the screen could not be dismissed with the key everyone reaches for.
added: HUB_DROPDOWN_POSITIONS — the four-position fallback chain a dropdown wants, below the origin and flipping above when there is no room, expressed logically so one list serves both text directions.
fixed: Tearing an overlay down twice no longer throws. dispose() and detach() called removeChild on nodes something else may already have removed — a test teardown, a router navigation — and the DOMException took the whole destroy path with it.
Version 22.10.0 - 8/22/26, 12:00 AM
added: --hub-tooltip-white-space and --hub-tooltip-text-align, so how a label breaks and sits can be asked for per tooltip instead of through a global rule that changes every tooltip in the product. Both default to what was hard-coded, so nothing moves for anyone who says nothing.
Version 22.9.3 - 8/19/26, 12:00 AM
fixed: A tooltip whose stylesheet was never imported no longer moves the page. The element now takes position: absolute inline at creation — the same value the sheet ships — so it stops landing in normal flow at the end of the document and growing the page a scrollbar that appeared and vanished as the pointer crossed a label.
Version 22.9.2 - 8/17/26, 12:00 AM
fixed: The stylesheets are published under styles/, so @use 'ng-hub-ui-utils/styles/tooltip' names a real path instead of reaching through the package's internal folder layout.
Version 22.9.1 - 8/17/26, 12:00 AM
fixed: The published package declares its licence. An absent license field is not neutral — a registry reports it as unlicensed, which legally reads as all rights reserved. The intent was always MIT.
Version 22.9.0 - 8/17/26, 12:00 AM
added: [hubTooltip], a tooltip directive that can share an element. Its inputs are hubTooltip, hubTooltipPlacement, hubTooltipDelay and hubTooltipOffset: an attribute named for its owner cannot be claimed by anyone else, which is what the bare names could not promise next to [hubDropdown] (its own placement is typed over eight values) or <hub-badge> (which declares a tooltip input and drew two).
deprecated: TooltipDirective / [tooltip]. Kept working, unchanged — both directives are thin shells over the same HubTooltipController. Migration is attribute for attribute: tooltip → hubTooltip, placement → hubTooltipPlacement, delay → hubTooltipDelay, offset → hubTooltipOffset.
Version 22.8.1 - 8/15/26, 12:00 AM
fixed: A content-sized overlay no longer clips its own content into invisibility. Created with no intrinsic size, it computed to a 0×0 box whenever its content was absolutely positioned — which is exactly what a connected-position dropdown is — and the stylesheet's overflow: auto then hid what the overlay existed to display. An overlay created without an explicit width or height now opts out of clipping.
Version 22.8.0 - 8/14/26, 12:00 AM
added: provideHubTranslationAdapter() — the application-wide reactive bridge from an external translation service (transloco, ngx-translate, i18next…) into HubTranslationService. Register it once at bootstrap and every ng-hub-ui library picks up the host dictionary, re-emitting on every language change. Supports optional namespacing and deliberate per-label reactive overrides.
added: HUB_TRANSLATION_PREFIX — injection token that scopes the lookups of a library to a collision-safe HUBUI.<LIBRARY>.* namespace. TranslatePipe resolves the prefixed key first and falls back to the bare key, so existing flat dictionaries keep working untouched.
Version 22.7.2 - 8/8/26, 12:00 AM
fixed: Documentation links now point at the canonical localized URLs. The README linked to https://hubui.dev/<path> with no locale prefix and no trailing slash, and both forms are 301-redirected, so every reader arriving from npm or GitHub landed on a redirect instead of the canonical page.
Version 22.7.1 - 7/27/26, 12:00 AM
fixed: --hub-overlay-zindex / --hub-overlay-backdrop-zindex actually work now: OverlayRef resolves its inline z-index through var(--hub-overlay-zindex, 1000) / var(--hub-overlay-backdrop-zindex, 999) instead of literal values, so re-stacking an overlay no longer requires !important. Defaults are unchanged.
added: OverlayConfig.zIndex — optional explicit layer for a single overlay instance; when set it takes precedence over the token.
Version 22.7.0 - 7/7/26, 12:00 AM
added: resolveHubAccent(value) — the canonical "any colour" accent resolver shared across the ng-hub-ui family: barewords map to var(--hub-sys-color-<name>, <name>), literal #hex / rgb() / oklch() / var() values pass through unchanged, empty values yield null.
Version 22.6.1 - 7/2/26, 12:00 AM
fixed: CSS variable fallbacks realigned to the ds light defaults (e.g. --hub-ref-font-family-base falls back to the system-ui stack instead of inherit); fallbacks only apply when ng-hub-ui-ds is not loaded.
Version 22.6.0 - 6/30/26, 12:00 AM
added: HubOverflowTooltipDirective ([hubOverflowTooltip]) — shows a tooltip only while the label is actually truncated, with live tracking via ResizeObserver + MutationObserver. hubOverflowTooltipMeasure splits the two questions a chip asks separately: the tooltip covers the whole control, while a CSS selector resolved inside it names the inner box whose truncation decides whether it speaks.
added: Agnostic tooltip token — HUB_TOOLTIP_ADAPTER plus provideHubTooltip(adapter) let any tooltip implementation back [hubOverflowTooltip], app-wide or per subtree; defaults to the built-in hubTooltipAdapter.
相关库
常见问题
ng-hub-ui-utils 是什么,必须装吗?
它是整个家族其余部分赖以构建的共享工具箱,本身不提供任何可视组件。有十七个库把它声明为必需的 peer 依赖——board、modal、paginable、panels、calendar、forms 以及其余大部分——所以只要你用了其中任何一个,它就已经在依赖树里了,而且从 7 版起 npm 会自行安装 peer 依赖。单独安装它也没问题:ng-hub-ui-ds 是它唯一的可选 peer 依赖。
ng-hub-ui-utils 里到底有什么?
board、sortable 和 calendar 共用的那套原生拖放内核——moveItemInArray、transferArrayItem、resolveDropPosition,以及一套覆盖触摸的指针会话——再加上 hubFocusTrap 和 getFocusableBoundaryElements 这类焦点辅助、过渡辅助、带连接定位的 overlay 服务、一套能解析任意 CSS 颜色并转换到 OKLCh、还能测量对比度的颜色工具、一个 tooltip 指令、七个管道,以及常见的类型守卫和对象辅助函数。
能在自己的应用里直接用它吗?
可以,而且大部分东西不需要注册:直接导入 isString、HubGetPipe 或 hubFocusTrap 用就是了。有两个子系统确实需要接线。HubTranslationService 没有 providedIn,所以要先调用 provideHubTranslation(),否则注入会失败;tooltip 指令需要 provideHubTooltip(hubTooltipAdapter)。它们的样式表也没有打包进去:请自己加上 @use 'ng-hub-ui-utils/styles/tooltip' 或对应的 overlay 样式。
它是 Angular CDK 的替代品吗?
不是,它也没打算做替代品。这里没有 portal 抽象,没有滚动策略,没有 LiveAnnouncer 或 FocusMonitor,没有虚拟滚动,也没有断点观察器,拖放只有底层原语——handle、placeholder 和 preview 这些指令留在各自需要它们的库里。翻译层是一个带点路径查找和双花括号插值的字典:没有复数规则,没有 ICU,没有懒加载,另有一个适配器令牌,用来把活交给真正的 i18n 库。它是这个家族立足的地基,而不是通用工具集。