ng-hub-ui-utils 22.11.1 → 22.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -116,8 +116,8 @@ export class ExampleComponent {
116
116
  overlayY: 'top'
117
117
  }]);
118
118
 
119
- // Attach component to overlay
120
- const componentRef = overlayRef.attach(MyComponent);
119
+ // Render a component (or a TemplateRef) into the overlay; you get the host element back
120
+ const overlayElement = overlayRef.attach(MyComponent);
121
121
  }
122
122
  }
123
123
  ```
@@ -128,9 +128,11 @@ export class ExampleComponent {
128
128
  this.overlayService.create({ zIndex: 1100 }); // OverlayConfig.zIndex: number | string — takes precedence over the token
129
129
  ```
130
130
 
131
- ### 🎯 Popup Service (Base Class)
131
+ ### 🎯 Popup Service
132
132
 
133
- Base service for creating custom popup implementations.
133
+ `PopupService<T>` hosts a dynamically created popup component and runs its show/hide
134
+ transition. It is a concrete class — subclass it when the popup needs its own API, as
135
+ below, or provide it through a factory.
134
136
 
135
137
  ```typescript
136
138
  import { PopupService } from 'ng-hub-ui-utils';
@@ -356,16 +358,17 @@ import { GetPipe, UcfirstPipe } from 'ng-hub-ui-utils';
356
358
 
357
359
  ### 🏷️ Tooltip Directive
358
360
 
359
- Add a lightweight, themeable tooltip to any element with the `[tooltip]` directive.
360
- The tooltip is appended to `<body>` (never clipped) and shows on hover/focus.
361
+ Add a lightweight, themeable tooltip to any element with the `HubTooltipDirective`
362
+ (`[hubTooltip]`). The tooltip is appended to `<body>` (never clipped) and shows on
363
+ hover/focus.
361
364
 
362
365
  ```typescript
363
- import { TooltipDirective } from 'ng-hub-ui-utils';
366
+ import { HubTooltipDirective } from 'ng-hub-ui-utils';
364
367
 
365
368
  @Component({
366
369
  standalone: true,
367
- imports: [TooltipDirective],
368
- template: `<button tooltip="Save changes" placement="top">Save</button>`
370
+ imports: [HubTooltipDirective],
371
+ template: `<button hubTooltip="Save changes" hubTooltipPlacement="top">Save</button>`
369
372
  })
370
373
  export class ExampleComponent {}
371
374
  ```
@@ -378,8 +381,24 @@ export class ExampleComponent {}
378
381
  > @use 'ng-hub-ui-utils/styles/tooltip';
379
382
  > ```
380
383
 
381
- Inputs: `tooltip` (text), `placement` (`top` | `bottom` | `left` | `right`, default `top`),
382
- `delay` (fade ms, default `150`), `offset` (px, default `8`).
384
+ Inputs: `hubTooltip` (text), `hubTooltipPlacement` (`top` | `bottom` | `left` | `right`,
385
+ default `top`), `hubTooltipDelay` (fade ms, default `150`), `hubTooltipOffset` (px, default `8`).
386
+
387
+ > **`TooltipDirective` (`[tooltip]`) is deprecated since 22.9.0.** It still works,
388
+ > unchanged — both directives are thin shells over the same `HubTooltipController` — but its
389
+ > bare input names (`tooltip`, `placement`, `delay`, `offset`) belong to every directive on
390
+ > the element that declares them, which is how it collided with `[hubDropdown]`'s own
391
+ > `placement` and with the `tooltip` input of `<hub-badge>`. Migration is attribute for
392
+ > attribute: `tooltip` → `hubTooltip`, `placement` → `hubTooltipPlacement`,
393
+ > `delay` → `hubTooltipDelay`, `offset` → `hubTooltipOffset`.
394
+
395
+ Show the label **only while the host is truncated** with `HubOverflowTooltipDirective`
396
+ (`[hubOverflowTooltip]`), which tracks truncation live with a `ResizeObserver` and a
397
+ `MutationObserver` and resolves its tooltip through `HUB_TOOLTIP_ADAPTER`:
398
+
399
+ ```html
400
+ <span class="label" [hubOverflowTooltip]="item.label">{{ item.label }}</span>
401
+ ```
383
402
 
384
403
  Theme it from any scope with `--hub-tooltip-*` variables:
385
404
 
@@ -394,8 +413,14 @@ Theme it from any scope with `--hub-tooltip-*` variables:
394
413
 
395
414
  Available tokens: `--hub-tooltip-bg`, `--hub-tooltip-color`, `--hub-tooltip-opacity`,
396
415
  `--hub-tooltip-padding-x`, `--hub-tooltip-padding-y`, `--hub-tooltip-border-radius`,
397
- `--hub-tooltip-font-size`, `--hub-tooltip-max-width`, `--hub-tooltip-zindex`,
398
- `--hub-tooltip-transition-duration`, `--hub-tooltip-shadow`, `--hub-tooltip-font-family`.
416
+ `--hub-tooltip-font-size`, `--hub-tooltip-font-weight`, `--hub-tooltip-line-height`,
417
+ `--hub-tooltip-max-width`, `--hub-tooltip-zindex`, `--hub-tooltip-transition-duration`,
418
+ `--hub-tooltip-shadow`, `--hub-tooltip-font-family`, `--hub-tooltip-white-space`,
419
+ `--hub-tooltip-text-align`.
420
+
421
+ The last two arrived in 22.10.0, for the tooltip that carries a sentence rather than a
422
+ name: they are set on the **host**, which is the only element you can reach, because the
423
+ bubble itself lives on `<body>`, outside every component's styles.
399
424
 
400
425
  #### Tooltip adapter for other libraries (`hubTooltipAdapter`)
401
426
 
@@ -414,10 +439,26 @@ providers: [
414
439
  ];
415
440
  ```
416
441
 
442
+ Inside this package the same token works the other way round: `[hubOverflowTooltip]`
443
+ resolves its tooltip through `HUB_TOOLTIP_ADAPTER`, which defaults to `hubTooltipAdapter`.
444
+ Swap it app-wide, or for one subtree, with `provideHubTooltip()`:
445
+
446
+ ```ts
447
+ import { provideHubTooltip, HubTooltipAdapter } from 'ng-hub-ui-utils';
448
+
449
+ const myTooltip: HubTooltipAdapter = {
450
+ attach(host, text, options) {
451
+ /* … returns a HubTooltipHandle with update(text) and destroy() */
452
+ }
453
+ };
454
+
455
+ providers: [provideHubTooltip(myTooltip)];
456
+ ```
457
+
417
458
  Also available: the imperative `HubTooltipController` (engine) and the
418
- `HubTooltipAdapter` / `HubTooltipHandle` / `HubTooltipOptions` types. See the
419
- ecosystem-wide [Synergies & agnosticism](../../README.md#synergies--agnosticism)
420
- section.
459
+ `HubTooltipAdapter` / `HubTooltipHandle` / `HubTooltipOptions` / `HubTooltipPlacement`
460
+ types. See the ecosystem-wide
461
+ [Synergies & agnosticism](../../README.md#synergies--agnosticism) section.
421
462
 
422
463
  ## 🚀 Installation
423
464
 
@@ -505,6 +546,20 @@ interface HubTranslationConfig {
505
546
 
506
547
  The configuration is also exposed through the `HUB_TRANSLATION_CONFIG` injection token for advanced scenarios.
507
548
 
549
+ ### `HUB_TRANSLATION_PREFIX`
550
+
551
+ Injection token that scopes a library's lookups to a collision-safe `HUBUI.<LIBRARY>.*`
552
+ namespace. `TranslatePipe` resolves the prefixed key first and falls back to the bare key,
553
+ so a flat dictionary that predates the token keeps working untouched.
554
+
555
+ ```typescript
556
+ providers: [{ provide: HUB_TRANSLATION_PREFIX, useValue: 'HUBUI.TABLE' }];
557
+ ```
558
+
559
+ The adapter types are exported alongside it: `HubTranslationSource`,
560
+ `HubTranslationOverrides`, `HubTranslationAdapterConfig`, `HubTranslationAdapterFactory`
561
+ and the `HUB_TRANSLATION_SOURCE` token `provideHubTranslationAdapter()` registers.
562
+
508
563
  ### `HubTranslationService`
509
564
 
510
565
  Injectable service that holds the active translations and notifies subscribers when they change.
@@ -584,6 +639,19 @@ These functions back the i18n system and are exported for direct use:
584
639
 
585
640
  - `regExpEscape(text: string): string` - Escapes special characters for RegExp
586
641
  - `removeAccents(str: string): string` - Removes accents from text
642
+ - `interpolateString(expr?: string, params?: any, templateMatcher?: RegExp): string` - Replaces `{{ token }}` placeholders
643
+ - `generateUniqueId(length: number): string` - Random alphanumeric id, for a DOM node that needs one
644
+
645
+ ### Object Functions
646
+
647
+ - `equals(o1: any, o2: any): boolean` - Deep equality
648
+ - `getValue(target: any, key: string): any` - Reads a nested value by dot-notation key
649
+ - `isObject(item: any): boolean` - Whether the value is a non-array object
650
+ - `mergeDeep(target: any, source: any): any` - Recursive merge; the only deep object helper in the package
651
+
652
+ ### Signal Utilities
653
+
654
+ - `debouncedSignal<T>(source: Signal<T>, delay?: number | Signal<number>): Signal<T>` - Mirrors a signal, delaying each change; the delay can itself be a signal
587
655
 
588
656
  ### DOM Functions
589
657
 
@@ -591,12 +659,61 @@ These functions back the i18n system and are exported for direct use:
591
659
  - `reflow(element: HTMLElement): DOMRect` - Forces browser reflow
592
660
  - `getActiveElement(root?: Document | ShadowRoot): Element | null` - Gets active element including Shadow DOM
593
661
 
662
+ ### Accent Resolution
663
+
664
+ - `resolveHubAccent(value: string | null | undefined): string | null` - The "any colour" accent resolver shared across the family: a bareword becomes `var(--hub-sys-color-<name>, <name>)`, a literal `#hex` / `rgb()` / `oklch()` / `var()` passes through unchanged, and an empty value yields `null`
665
+
666
+ ### Colour Functions
667
+
668
+ - `parseColor(value): HubRgb | null` - Parses hex (3/4/6/8), `rgb()`, `hsl()`, `oklch()`, `oklab()`, the 148 CSS named colours and `transparent`, in modern and legacy syntax. No DOM, so it runs under SSR. Returns `null` — never throws — for anything it cannot resolve, `var()` and `currentColor` included
669
+ - `toRgb(color): HubRgb | null` - Normalises a string or parsed colour to channels
670
+ - `toHex(color): string | null` - Renders as `#rrggbb`, or `#rrggbbaa` when translucent
671
+ - `isValidColor(value): boolean` - Whether the parser can resolve the string
672
+ - `HUB_NAMED_COLORS: Readonly<Record<string, string>>` - The 148 CSS named colours
673
+
674
+ ### Contrast Functions
675
+
676
+ - `relativeLuminance(color): number | null` - WCAG 2 relative luminance, 0 to 1
677
+ - `contrastRatio(a, b): number | null` - WCAG 2 contrast ratio, 1 to 21
678
+ - `contrastAPCA(text, background): number | null` - APCA lightness contrast, polarity-aware
679
+ - `compositeOver(foreground, background): HubColor` - Blends translucent over opaque
680
+ - `readableOn(background, metric?): string` - Black or white, whichever reads better. Defaults to `'lightness'`, the same decision `--hub-sys-color-*-on` makes in CSS; `'apca'` and `'wcag'` are also available
681
+ - `HUB_INK_LIGHTNESS_THRESHOLD: number` - The OKLCh lightness above which a surface takes dark ink
682
+
683
+ ### OKLCh Functions
684
+
685
+ - `rgbToOklch(color): HubOklch` / `oklchToRgb(color): HubRgb` - Conversions in the space the design system mixes in
686
+ - `maxSrgbChroma(l, h): number` - Highest in-gamut chroma for a hue at a lightness. The sRGB gamut is not a cylinder — at L 0.578 blue reaches 0.232 and amber only 0.119 — so a palette cannot give every hue the same absolute chroma
687
+ - `isInSrgbGamut(color): boolean` - Whether the colour survives the trip to sRGB
688
+ - `clampToSrgbGamut(color): HubOklch` - Reduces chroma until it fits, preserving lightness and hue
689
+
594
690
  ### Focus Functions
595
691
 
596
692
  - `getFocusableBoundaryElements(element: HTMLElement): HTMLElement[]` - Gets first and last focusable elements
597
693
  - `hubFocusTrap(zone, element, stopFocusTrap$, refocusOnClick?)` - Creates focus trap for modals/overlays
598
694
  - `FOCUSABLE_ELEMENTS_SELECTOR: string` - CSS selector for focusable elements
599
695
 
696
+ ### Drag and Drop
697
+
698
+ The engine-agnostic half of native HTML5 drag and drop, shared by the libraries that
699
+ implement it. The UI primitives — handle, placeholder and preview directives — stay in each
700
+ library, because their selectors and data models differ.
701
+
702
+ - `HubDragDropService` - Root-provided coordinator. A drag spans two component instances and the native `dataTransfer` payload is unreadable during `dragover`, so a shared service is the only reliable channel for what is being dragged and from where. Owners `register()` / `unregister()`; `begin()`, `setTarget()` and the readonly `active` / `target` / `isDragging` signals report the drag in progress. It coordinates state only — it never mutates your collections
703
+ - `moveItemInArray<T>(array, fromIndex, toIndex): void` / `transferArrayItem<T>(source, target, fromIndex, toIndex): void` / `copyArrayItem<T>(source, target, fromIndex, toIndex): void` - In-place array moves, mirroring the `@angular/cdk` helpers of the same names
704
+ - `clamp(value, max)`, `computeTargetIndex(...)`, `toAbsoluteIndex(...)`, `containsNode(...)` - Index arithmetic for sliced and nested lists
705
+ - `resolveDropPosition(...)` with `DropRect` and `DragAxis` - Where a pointer sits relative to an item: `'before'` or `'after'`, on a vertical, horizontal or grid axis
706
+ - `createNativeDragImage(...)` returning `DragImageResult` - Renders the drag preview the browser shows
707
+ - `createPointerDragSession(config: PointerDragSessionConfig): PointerDragSession` - Pointer Events fallback for touch, where native drag events are not delivered
708
+ - Types: `DropPosition`, `DragPointerMode`, `DragContainerRef<T>`, `ActiveDrag<T>`, `DragTarget<T>`, `DragRegistration`
709
+
710
+ ### Directives
711
+
712
+ - `HubTooltipDirective` (`[hubTooltip]`) - Tooltip on hover/focus. Inputs: `hubTooltip`, `hubTooltipPlacement`, `hubTooltipDelay`, `hubTooltipOffset`
713
+ - `HubOverflowTooltipDirective` (`[hubOverflowTooltip]`) - Tooltip shown only while the host label is truncated. Inputs: `hubOverflowTooltip`, `placement`
714
+ - `TooltipDirective` (`[tooltip]`) - **Deprecated since 22.9.0**, kept working. Inputs: `tooltip`, `placement`, `delay`, `offset`
715
+ - `provideHubTooltip(adapter: HubTooltipAdapter)` and `HUB_TOOLTIP_ADAPTER` - Swap the implementation behind `[hubOverflowTooltip]`, app-wide or per subtree; defaults to `hubTooltipAdapter`
716
+
600
717
  ### Pipes
601
718
 
602
719
  #### GetPipe
@@ -657,18 +774,37 @@ class OverlayService {
657
774
  }
658
775
 
659
776
  class OverlayRef {
660
- attach<T>(component: ComponentType<T>): ComponentRef<T>;
777
+ // Renders a template or a component into the overlay and returns the host element,
778
+ // not a ComponentRef: the overlay owns the view it created and tears it down itself.
779
+ attach(content: TemplateRef<unknown> | Type<unknown>, viewContainerRef?: ViewContainerRef): HTMLElement;
661
780
  detach(): void;
662
781
  dispose(): void;
782
+ hasAttached(): boolean;
663
783
  updatePosition(): void;
784
+ onBackdropClick(callback: () => void): void;
785
+ // Only the topmost open overlay is told, so a dropdown inside a dialog takes Escape
786
+ // for itself and leaves the dialog open.
787
+ onKeydown(callback: (event: KeyboardEvent) => void): void;
664
788
  }
665
789
 
666
790
  class OverlayPosition {
667
- flexibleConnectedTo(element: ElementRef | HTMLElement): this;
791
+ flexibleConnectedTo(origin: ElementRef | HTMLElement): this;
668
792
  withPositions(positions: ConnectionPosition[]): this;
793
+ // `start` / `end` are logical and read from the origin element; this overrides that.
794
+ withDirection(direction: 'ltr' | 'rtl' | null): this;
669
795
  }
670
796
  ```
671
797
 
798
+ `HUB_DROPDOWN_POSITIONS` is the ready-made fallback chain for a dropdown — below the
799
+ origin, flipping above when there is no room — expressed logically so one list serves
800
+ both text directions:
801
+
802
+ ```typescript
803
+ import { HUB_DROPDOWN_POSITIONS } from 'ng-hub-ui-utils';
804
+
805
+ overlayService.position().flexibleConnectedTo(origin).withPositions([...HUB_DROPDOWN_POSITIONS]);
806
+ ```
807
+
672
808
  #### ScrollBar Service
673
809
 
674
810
  ```typescript
@@ -678,14 +814,26 @@ class ScrollBar {
678
814
  }
679
815
  ```
680
816
 
681
- #### PopupService<T> (Base Class)
817
+ #### PopupService&lt;T&gt;
818
+
819
+ A concrete generic class, not an abstract one: it takes the popup component type in its
820
+ constructor, and it reads its collaborators with `inject()`, so it has to be created inside
821
+ an injection context — as an `@Injectable()` subclass, or from a factory provider.
682
822
 
683
823
  ```typescript
684
- abstract class PopupService<T> {
685
- // Base system for creating dynamic popups
686
- // Extend this class to create specific popup services
687
- open(content?, templateContext?, animation?): { windowRef: ComponentRef<T>; transition$: Observable<void> };
688
- close(animation?): Observable<void>;
824
+ class PopupService<T> {
825
+ constructor(componentType: Type<T>);
826
+ open(
827
+ content?: string | TemplateRef<any>,
828
+ templateContext?: any,
829
+ animation?: boolean
830
+ ): { windowRef: ComponentRef<T>; transition$: Observable<void> };
831
+ close(animation?: boolean): Observable<void>;
832
+ }
833
+
834
+ // The nodes and view a popup projects, returned internally by the content resolver.
835
+ class ContentRef {
836
+ constructor(nodes: Node[][], viewRef?: ViewRef, componentRef?: ComponentRef<any>);
689
837
  }
690
838
  ```
691
839
 
@@ -705,7 +853,7 @@ This library doesn't include visual components, but support utilities used by ot
705
853
  | Overlay Service | Flexible overlay positioning system | ng-hub-ui-modal, ng-hub-ui-portal |
706
854
  | Focus Trap | Focus management in modals/overlays | ng-hub-ui-modal, ng-hub-ui-portal |
707
855
  | Scrollbar | Scrollbar compensation | ng-hub-ui-modal, ng-hub-ui-portal |
708
- | Popup Service | Base class for popup components | ng-hub-ui-modal, ng-hub-ui-portal |
856
+ | Popup Service | Host for dynamically created popups | ng-hub-ui-modal, ng-hub-ui-portal |
709
857
  | Transitions | Smooth animations | ng-hub-ui-accordion, ng-hub-ui-modal |
710
858
  | Type Guards | Type validation functions | ng-hub-ui-stepper |
711
859
  | Pipes | Template utilities | All Hub UI components |