ng-hub-ui-utils 22.15.2 β 22.17.0
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 +216 -155
- package/fesm2022/ng-hub-ui-utils.mjs +351 -42
- package/fesm2022/ng-hub-ui-utils.mjs.map +1 -1
- package/package.json +4 -4
- package/styles/overlay.scss +5 -0
- package/types/ng-hub-ui-utils.d.ts +234 -4
package/README.md
CHANGED
|
@@ -93,31 +93,34 @@ Advanced system for creating overlays and floating components with flexible posi
|
|
|
93
93
|
import { OverlayService, OverlayConfig } from 'ng-hub-ui-utils';
|
|
94
94
|
|
|
95
95
|
@Component({
|
|
96
|
-
|
|
96
|
+
selector: 'app-example'
|
|
97
97
|
})
|
|
98
98
|
export class ExampleComponent {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
99
|
+
constructor(private overlayService: OverlayService) {}
|
|
100
|
+
|
|
101
|
+
openOverlay(elementRef: ElementRef) {
|
|
102
|
+
// Create overlay with configuration
|
|
103
|
+
const overlayRef = this.overlayService.create({
|
|
104
|
+
hasBackdrop: true,
|
|
105
|
+
backdropClass: 'custom-backdrop'
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Configure position strategy
|
|
109
|
+
const positionStrategy = this.overlayService
|
|
110
|
+
.position()
|
|
111
|
+
.flexibleConnectedTo(elementRef)
|
|
112
|
+
.withPositions([
|
|
113
|
+
{
|
|
114
|
+
originX: 'start',
|
|
115
|
+
originY: 'bottom',
|
|
116
|
+
overlayX: 'start',
|
|
117
|
+
overlayY: 'top'
|
|
118
|
+
}
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
// Render a component (or a TemplateRef) into the overlay; you get the host element back
|
|
122
|
+
const overlayElement = overlayRef.attach(MyComponent);
|
|
123
|
+
}
|
|
121
124
|
}
|
|
122
125
|
```
|
|
123
126
|
|
|
@@ -138,14 +141,14 @@ import { PopupService } from 'ng-hub-ui-utils';
|
|
|
138
141
|
|
|
139
142
|
@Injectable()
|
|
140
143
|
export class MyPopupService extends PopupService<MyPopupComponent> {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
144
|
+
constructor() {
|
|
145
|
+
super(MyPopupComponent);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
openPopup(content?: string | TemplateRef<any>) {
|
|
149
|
+
const { windowRef, transition$ } = super.open(content, {}, true);
|
|
150
|
+
return { windowRef, transition$ };
|
|
151
|
+
}
|
|
149
152
|
}
|
|
150
153
|
```
|
|
151
154
|
|
|
@@ -248,14 +251,14 @@ import { GetPipe, IsStringPipe, IsObjectPipe, IsObservablePipe, UcfirstPipe, Unw
|
|
|
248
251
|
imports: [GetPipe, IsStringPipe, UcfirstPipe, UnwrapAsyncPipe],
|
|
249
252
|
template: `
|
|
250
253
|
<!-- Safe nested property access -->
|
|
251
|
-
<p>{{ user | get
|
|
254
|
+
<p>{{ user | get: 'address.city' : 'Unknown' }}</p>
|
|
252
255
|
|
|
253
256
|
<!-- Capitalize first letter -->
|
|
254
257
|
<h1>{{ title | ucfirst }}</h1>
|
|
255
258
|
|
|
256
259
|
<!-- Type checking in templates -->
|
|
257
260
|
@if (value | isString) {
|
|
258
|
-
|
|
261
|
+
<span>It's a string: {{ value }}</span>
|
|
259
262
|
}
|
|
260
263
|
|
|
261
264
|
<!-- Unwrap Observable or direct value -->
|
|
@@ -272,12 +275,12 @@ export class ExampleComponent {
|
|
|
272
275
|
|
|
273
276
|
**Available Pipes:**
|
|
274
277
|
|
|
275
|
-
-
|
|
276
|
-
-
|
|
277
|
-
-
|
|
278
|
-
-
|
|
279
|
-
-
|
|
280
|
-
-
|
|
278
|
+
- **GetPipe** (`get`): Safe nested property access with default values
|
|
279
|
+
- **IsStringPipe** (`isString`): Check if value is a string
|
|
280
|
+
- **IsObjectPipe** (`isObject`): Check if value is an object
|
|
281
|
+
- **IsObservablePipe** (`isObservable`): Check if value is an Observable
|
|
282
|
+
- **UcfirstPipe** (`ucfirst`): Capitalize first letter of a string
|
|
283
|
+
- **UnwrapAsyncPipe** (`unwrapAsync`): Unwrap Observable or return direct value
|
|
281
284
|
|
|
282
285
|
### π οΈ General Utility Functions
|
|
283
286
|
|
|
@@ -383,6 +386,19 @@ export class ExampleComponent {}
|
|
|
383
386
|
Inputs: `hubTooltip` (text), `hubTooltipPlacement` (`top` | `bottom` | `left` | `right`,
|
|
384
387
|
default `top`), `hubTooltipDelay` (fade ms, default `150`), `hubTooltipOffset` (px, default `8`).
|
|
385
388
|
|
|
389
|
+
**The placement is a preference, not an instruction (since 22.16.0).** A tooltip that would open
|
|
390
|
+
off the edge of the window opens on the opposite side instead, at all four edges, and one centred
|
|
391
|
+
on a host near the inline edge is slid back inside rather than flipped β flipping does nothing for
|
|
392
|
+
an overflow on the cross axis. It gives way only when it genuinely has no room, so a tooltip that
|
|
393
|
+
did not need to move does not, and the fit is re-run while the label is open, on window resize and
|
|
394
|
+
on a scroll anywhere above it. Under RTL the inline edge is the other one and the flip follows it,
|
|
395
|
+
while `hubTooltipPlacement="left"` still means the host's left edge.
|
|
396
|
+
|
|
397
|
+
If you have been writing `hubTooltipPlacement="bottom"` by hand on every hint in a header, you can
|
|
398
|
+
stop: the attribute is still honoured wherever it fits, but it is no longer what keeps the label
|
|
399
|
+
on screen. The class on the bubble (`hub-tooltip--top`, `--bottom`, `--left`, `--right`) names the
|
|
400
|
+
side it ended up on, so an arrow styled off it follows the flip.
|
|
401
|
+
|
|
386
402
|
> **`TooltipDirective` (`[tooltip]`) was removed in 22.14.0**, having been deprecated since
|
|
387
403
|
> 22.9.0. Its bare input names (`tooltip`, `placement`, `delay`, `offset`) belonged to every
|
|
388
404
|
> directive on the element that declared them, which is how it collided with `[hubDropdown]`'s
|
|
@@ -449,10 +465,7 @@ import { hubTooltipAdapter } from 'ng-hub-ui-utils';
|
|
|
449
465
|
import { provideHubBadgeTooltip } from 'ng-hub-ui-badges';
|
|
450
466
|
import { provideHubBreadcrumbTooltip } from 'ng-hub-ui-breadcrumbs';
|
|
451
467
|
|
|
452
|
-
providers: [
|
|
453
|
-
provideHubBadgeTooltip(hubTooltipAdapter),
|
|
454
|
-
provideHubBreadcrumbTooltip(hubTooltipAdapter)
|
|
455
|
-
];
|
|
468
|
+
providers: [provideHubBadgeTooltip(hubTooltipAdapter), provideHubBreadcrumbTooltip(hubTooltipAdapter)];
|
|
456
469
|
```
|
|
457
470
|
|
|
458
471
|
Inside this package the same token works the other way round: `[hubOverflowTooltip]`
|
|
@@ -497,7 +510,7 @@ import { toInteger, isString, ScrollBar, getFocusableBoundaryElements, GetPipe,
|
|
|
497
510
|
template: `
|
|
498
511
|
<div #container>
|
|
499
512
|
<h1>{{ title | ucfirst }}</h1>
|
|
500
|
-
<p>{{ user | get
|
|
513
|
+
<p>{{ user | get: 'name' : 'Anonymous' }}</p>
|
|
501
514
|
</div>
|
|
502
515
|
`
|
|
503
516
|
})
|
|
@@ -598,7 +611,7 @@ class HubTranslationService {
|
|
|
598
611
|
```typescript
|
|
599
612
|
import { HubTranslationService } from 'ng-hub-ui-utils';
|
|
600
613
|
|
|
601
|
-
@Component({
|
|
614
|
+
@Component({/* ... */})
|
|
602
615
|
export class LanguageSwitcherComponent {
|
|
603
616
|
private translationSvc = inject(HubTranslationService);
|
|
604
617
|
|
|
@@ -638,81 +651,81 @@ These functions back the i18n system and are exported for direct use:
|
|
|
638
651
|
|
|
639
652
|
### Conversion Functions
|
|
640
653
|
|
|
641
|
-
-
|
|
642
|
-
-
|
|
643
|
-
-
|
|
644
|
-
-
|
|
654
|
+
- `toInteger(value: any): number` - Safely converts to integer
|
|
655
|
+
- `toString(value: any): string` - Converts to string handling null/undefined
|
|
656
|
+
- `getValueInRange(value: number, max: number, min?: number): number` - Limits value to range
|
|
657
|
+
- `padNumber(value: number): string` - Adds leading zero to numbers
|
|
645
658
|
|
|
646
659
|
### Validation Functions
|
|
647
660
|
|
|
648
|
-
-
|
|
649
|
-
-
|
|
650
|
-
-
|
|
651
|
-
-
|
|
652
|
-
-
|
|
661
|
+
- `isString(value: any): value is string` - Checks if value is a string
|
|
662
|
+
- `isNumber(value: any): value is number` - Checks if value is a valid number
|
|
663
|
+
- `isInteger(value: any): value is number` - Checks if value is an integer
|
|
664
|
+
- `isDefined(value: any): boolean` - Checks if not null/undefined
|
|
665
|
+
- `isPromise<T>(v: any): v is Promise<T>` - Checks if value is a Promise
|
|
653
666
|
|
|
654
667
|
### String Functions
|
|
655
668
|
|
|
656
|
-
-
|
|
657
|
-
-
|
|
658
|
-
-
|
|
659
|
-
-
|
|
669
|
+
- `regExpEscape(text: string): string` - Escapes special characters for RegExp
|
|
670
|
+
- `removeAccents(str: string): string` - Removes accents from text
|
|
671
|
+
- `interpolateString(expr?: string, params?: any, templateMatcher?: RegExp): string` - Replaces `{{ token }}` placeholders
|
|
672
|
+
- `generateUniqueId(length: number): string` - Random alphanumeric id, for a DOM node that needs one
|
|
660
673
|
|
|
661
674
|
### Object Functions
|
|
662
675
|
|
|
663
|
-
-
|
|
664
|
-
-
|
|
665
|
-
-
|
|
666
|
-
-
|
|
676
|
+
- `equals(o1: any, o2: any): boolean` - Deep equality
|
|
677
|
+
- `getValue(target: any, key: string): any` - Reads a nested value by dot-notation key
|
|
678
|
+
- `isObject(item: any): boolean` - Whether the value is a non-array object
|
|
679
|
+
- `mergeDeep(target: any, source: any): any` - Recursive merge; the only deep object helper in the package
|
|
667
680
|
|
|
668
681
|
### Signal Utilities
|
|
669
682
|
|
|
670
|
-
-
|
|
683
|
+
- `debouncedSignal<T>(source: Signal<T>, delay?: number | Signal<number>): Signal<T>` - Mirrors a signal, delaying each change; the delay can itself be a signal
|
|
671
684
|
|
|
672
685
|
### DOM Functions
|
|
673
686
|
|
|
674
|
-
-
|
|
675
|
-
-
|
|
676
|
-
-
|
|
687
|
+
- `closest(element: HTMLElement, selector?: string): HTMLElement | null` - Finds parent element by selector
|
|
688
|
+
- `reflow(element: HTMLElement): DOMRect` - Forces browser reflow
|
|
689
|
+
- `getActiveElement(root?: Document | ShadowRoot): Element | null` - Gets active element including Shadow DOM
|
|
677
690
|
|
|
678
691
|
### Accent Resolution
|
|
679
692
|
|
|
680
|
-
-
|
|
693
|
+
- `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`
|
|
681
694
|
|
|
682
695
|
### Colour Functions
|
|
683
696
|
|
|
684
|
-
-
|
|
685
|
-
-
|
|
686
|
-
-
|
|
687
|
-
-
|
|
688
|
-
-
|
|
697
|
+
- `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
|
|
698
|
+
- `toRgb(color): HubRgb | null` - Normalises a string or parsed colour to channels
|
|
699
|
+
- `toHex(color): string | null` - Renders as `#rrggbb`, or `#rrggbbaa` when translucent
|
|
700
|
+
- `isValidColor(value): boolean` - Whether the parser can resolve the string
|
|
701
|
+
- `HUB_NAMED_COLORS: Readonly<Record<string, string>>` - The 148 CSS named colours
|
|
689
702
|
|
|
690
703
|
### Contrast Functions
|
|
691
704
|
|
|
692
|
-
-
|
|
693
|
-
-
|
|
694
|
-
-
|
|
695
|
-
-
|
|
696
|
-
-
|
|
697
|
-
-
|
|
705
|
+
- `relativeLuminance(color): number | null` - WCAG 2 relative luminance, 0 to 1
|
|
706
|
+
- `contrastRatio(a, b): number | null` - WCAG 2 contrast ratio, 1 to 21
|
|
707
|
+
- `contrastAPCA(text, background): number | null` - APCA lightness contrast, polarity-aware
|
|
708
|
+
- `compositeOver(foreground, background): HubColor` - Blends translucent over opaque
|
|
709
|
+
- `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
|
|
710
|
+
- `HUB_INK_LIGHTNESS_THRESHOLD: number` - The OKLCh lightness above which a surface takes dark ink
|
|
698
711
|
|
|
699
712
|
### OKLCh Functions
|
|
700
713
|
|
|
701
|
-
-
|
|
702
|
-
-
|
|
703
|
-
-
|
|
704
|
-
-
|
|
714
|
+
- `rgbToOklch(color): HubOklch` / `oklchToRgb(color): HubRgb` - Conversions in the space the design system mixes in
|
|
715
|
+
- `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
|
|
716
|
+
- `isInSrgbGamut(color): boolean` - Whether the colour survives the trip to sRGB
|
|
717
|
+
- `clampToSrgbGamut(color): HubOklch` - Reduces chroma until it fits, preserving lightness and hue
|
|
705
718
|
|
|
706
719
|
### Palette Derivation
|
|
707
720
|
|
|
708
721
|
One brand colour, the whole palette β and the two numbers that keep it honest.
|
|
709
722
|
|
|
710
|
-
-
|
|
711
|
-
-
|
|
712
|
-
-
|
|
713
|
-
-
|
|
714
|
-
-
|
|
715
|
-
-
|
|
723
|
+
- `harmoniseSemantics(primary, options?): HubSemanticPalette | null` - Rotates `success`, `warning`, `danger` and `info` towards the brand's hue and returns them as hex. Lightness is left exactly where the anchor had it, because it is what carries the contrast each role was chosen for; chroma is reduced only when the new hue cannot hold it inside sRGB
|
|
724
|
+
- `tintNeutrals(primary, options?): HubNeutralRamp | null` - Leans the grey ramp (`100` β¦ `900`) the same way, keeping each step's lightness
|
|
725
|
+
- `HUB_MAX_HUE_SHIFT: number` (15) - How far a role may rotate, in degrees. Not taste: success and danger sit 135.8Β° apart in OKLCh, and a viewer with deuteranopia separates them by hue alone. A brand hue between the two pulls both inwards, so the gap closes by up to twice the cap; at 22.9Β° it would reach the 90Β° floor. 15Β° leaves the worst case at 105.8Β°
|
|
726
|
+
- `HUB_MAX_NEUTRAL_CHROMA: number` (0.015) - The most chroma a tinted neutral may carry. Anchored on the ramp the design system already ships β `gray-600` measures 0.0165 and `gray-500` 0.0145 β so a tinted ramp is never more colourful than the grey people already accept as grey
|
|
727
|
+
- `HUB_SEMANTIC_ANCHORS` / `HUB_NEUTRAL_ANCHORS` - The untinted starting points, so a product that harmonises nothing still gets the palette the stylesheet ships
|
|
728
|
+
- Types: `HubSemanticRole`, `HubSemanticPalette`, `HubNeutralStep`, `HubNeutralRamp`, `HubHarmoniseOptions`, `HubTintNeutralsOptions`
|
|
716
729
|
|
|
717
730
|
```typescript
|
|
718
731
|
import { harmoniseSemantics, tintNeutrals } from 'ng-hub-ui-utils';
|
|
@@ -728,9 +741,9 @@ is rounding noise, and harmonising towards it would rotate every role in a direc
|
|
|
728
741
|
|
|
729
742
|
### Focus Functions
|
|
730
743
|
|
|
731
|
-
-
|
|
732
|
-
-
|
|
733
|
-
-
|
|
744
|
+
- `getFocusableBoundaryElements(element: HTMLElement): HTMLElement[]` - Gets first and last focusable elements
|
|
745
|
+
- `hubFocusTrap(zone, element, stopFocusTrap$, refocusOnClick?)` - Creates focus trap for modals/overlays
|
|
746
|
+
- `FOCUSABLE_ELEMENTS_SELECTOR: string` - CSS selector for focusable elements
|
|
734
747
|
|
|
735
748
|
### Drag and Drop
|
|
736
749
|
|
|
@@ -738,19 +751,19 @@ The engine-agnostic half of native HTML5 drag and drop, shared by the libraries
|
|
|
738
751
|
implement it. The UI primitives β handle, placeholder and preview directives β stay in each
|
|
739
752
|
library, because their selectors and data models differ.
|
|
740
753
|
|
|
741
|
-
-
|
|
742
|
-
-
|
|
743
|
-
-
|
|
744
|
-
-
|
|
745
|
-
-
|
|
746
|
-
-
|
|
747
|
-
-
|
|
754
|
+
- `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
|
|
755
|
+
- `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
|
|
756
|
+
- `clamp(value, max)`, `computeTargetIndex(...)`, `toAbsoluteIndex(...)`, `containsNode(...)` - Index arithmetic for sliced and nested lists
|
|
757
|
+
- `resolveDropPosition(...)` with `DropRect` and `DragAxis` - Where a pointer sits relative to an item: `'before'` or `'after'`, on a vertical, horizontal or grid axis
|
|
758
|
+
- `createNativeDragImage(...)` returning `DragImageResult` - Renders the drag preview the browser shows
|
|
759
|
+
- `createPointerDragSession(config: PointerDragSessionConfig): PointerDragSession` - Pointer Events fallback for touch, where native drag events are not delivered
|
|
760
|
+
- Types: `DropPosition`, `DragPointerMode`, `DragContainerRef<T>`, `ActiveDrag<T>`, `DragTarget<T>`, `DragRegistration`
|
|
748
761
|
|
|
749
762
|
### Directives
|
|
750
763
|
|
|
751
|
-
-
|
|
752
|
-
-
|
|
753
|
-
-
|
|
764
|
+
- `HubTooltipDirective` (`[hubTooltip]`) - Tooltip on hover/focus, flipped away from the window edges. Inputs: `hubTooltip`, `hubTooltipPlacement`, `hubTooltipDelay`, `hubTooltipOffset`
|
|
765
|
+
- `HubOverflowTooltipDirective` (`[hubOverflowTooltip]`) - Tooltip shown only while the label is truncated. Inputs: `hubOverflowTooltip`, `placement`, `hubOverflowTooltipMeasure` (CSS selector, resolved inside the host, naming the box whose truncation decides it; defaults to the host)
|
|
766
|
+
- `provideHubTooltip(adapter: HubTooltipAdapter)` and `HUB_TOOLTIP_ADAPTER` - Swap the implementation behind `[hubOverflowTooltip]`, app-wide or per subtree; defaults to `hubTooltipAdapter`
|
|
754
767
|
|
|
755
768
|
### Pipes
|
|
756
769
|
|
|
@@ -807,31 +820,31 @@ library, because their selectors and data models differ.
|
|
|
807
820
|
```typescript
|
|
808
821
|
@Injectable({ providedIn: 'root' })
|
|
809
822
|
class OverlayService {
|
|
810
|
-
|
|
811
|
-
|
|
823
|
+
create(config?: OverlayConfig): OverlayRef;
|
|
824
|
+
position(): OverlayPosition;
|
|
812
825
|
}
|
|
813
826
|
|
|
814
827
|
class OverlayRef {
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
828
|
+
// Renders a template or a component into the overlay and returns the host element,
|
|
829
|
+
// not a ComponentRef: the overlay owns the view it created and tears it down itself.
|
|
830
|
+
attach(content: TemplateRef<unknown> | Type<unknown>, viewContainerRef?: ViewContainerRef): HTMLElement;
|
|
831
|
+
detach(): void;
|
|
832
|
+
dispose(): void;
|
|
833
|
+
hasAttached(): boolean;
|
|
834
|
+
updatePosition(): void;
|
|
835
|
+
onBackdropClick(callback: () => void): void;
|
|
836
|
+
// Only the topmost open overlay is told, so a dropdown inside a dialog takes Escape
|
|
837
|
+
// for itself and leaves the dialog open.
|
|
838
|
+
onKeydown(callback: (event: KeyboardEvent) => void): void;
|
|
826
839
|
}
|
|
827
840
|
|
|
828
841
|
class OverlayPosition {
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
842
|
+
// The element the panel is anchored to. The overlay watches it and follows it when it moves.
|
|
843
|
+
readonly origin: HTMLElement | null;
|
|
844
|
+
flexibleConnectedTo(origin: ElementRef | HTMLElement): this;
|
|
845
|
+
withPositions(positions: ConnectionPosition[]): this;
|
|
846
|
+
// `start` / `end` are logical and read from the origin element; this overrides that.
|
|
847
|
+
withDirection(direction: 'ltr' | 'rtl' | null): this;
|
|
835
848
|
}
|
|
836
849
|
```
|
|
837
850
|
|
|
@@ -842,15 +855,55 @@ both text directions:
|
|
|
842
855
|
```typescript
|
|
843
856
|
import { HUB_DROPDOWN_POSITIONS } from 'ng-hub-ui-utils';
|
|
844
857
|
|
|
845
|
-
overlayService
|
|
858
|
+
overlayService
|
|
859
|
+
.position()
|
|
860
|
+
.flexibleConnectedTo(origin)
|
|
861
|
+
.withPositions([...HUB_DROPDOWN_POSITIONS]);
|
|
846
862
|
```
|
|
847
863
|
|
|
864
|
+
#### Viewport fitting (`viewport-fit`)
|
|
865
|
+
|
|
866
|
+
The arithmetic behind both the overlay strategy and the tooltip, exported on its own so a floating
|
|
867
|
+
element that is neither does not have to write a third copy. It takes plain numbers, touches no DOM
|
|
868
|
+
and runs unchanged on a server render.
|
|
869
|
+
|
|
870
|
+
```typescript
|
|
871
|
+
import { hubAnchorToViewport, hubToAnchorSide, hubToPhysicalSide, hubViewportOf } from 'ng-hub-ui-utils';
|
|
872
|
+
|
|
873
|
+
const rtl = getComputedStyle(host).direction === 'rtl';
|
|
874
|
+
|
|
875
|
+
const placed = hubAnchorToViewport({
|
|
876
|
+
anchor: host.getBoundingClientRect(),
|
|
877
|
+
box: { width: panel.offsetWidth, height: panel.offsetHeight },
|
|
878
|
+
viewport: hubViewportOf(panel), // null on a server render: nothing flips
|
|
879
|
+
side: hubToAnchorSide('bottom', rtl), // 'block-start' | 'block-end' | 'inline-start' | 'inline-end'
|
|
880
|
+
align: 'start', // logical: mirrors under RTL
|
|
881
|
+
offset: 8,
|
|
882
|
+
margin: 0,
|
|
883
|
+
rtl
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
// placed.x / placed.y are viewport coordinates β add scrollX / scrollY for an absolutely
|
|
887
|
+
// positioned element. placed.side is where it ended up, placed.flipped whether that was a flip.
|
|
888
|
+
panel.dataset['side'] = hubToPhysicalSide(placed.side, rtl);
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
The requested side is kept unless it genuinely cannot hold the box **and** the opposite side has
|
|
892
|
+
more room, so nothing flips for free; the cross axis is clamped rather than flipped, since a box
|
|
893
|
+
centred on an anchor near the inline edge overflows there whichever side it opens on. Sides are
|
|
894
|
+
logical so a single fallback rule serves both text directions β `hubToAnchorSide` and
|
|
895
|
+
`hubToPhysicalSide` round-trip a physical edge through that axis without changing what the caller
|
|
896
|
+
asked for.
|
|
897
|
+
|
|
898
|
+
Also exported: `hubFitsInViewport(point, size, viewport, margin?)`,
|
|
899
|
+
`hubClampToViewport(point, size, viewport, margin?)` and `hubOppositeSide(side)`.
|
|
900
|
+
|
|
848
901
|
#### ScrollBar Service
|
|
849
902
|
|
|
850
903
|
```typescript
|
|
851
904
|
@Injectable({ providedIn: 'root' })
|
|
852
905
|
class ScrollBar {
|
|
853
|
-
|
|
906
|
+
hide(): ScrollbarReverter; // Hides scrollbar with compensation
|
|
854
907
|
}
|
|
855
908
|
```
|
|
856
909
|
|
|
@@ -862,48 +915,48 @@ an injection context β as an `@Injectable()` subclass, or from a factory provi
|
|
|
862
915
|
|
|
863
916
|
```typescript
|
|
864
917
|
class PopupService<T> {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
918
|
+
constructor(componentType: Type<T>);
|
|
919
|
+
open(
|
|
920
|
+
content?: string | TemplateRef<any>,
|
|
921
|
+
templateContext?: any,
|
|
922
|
+
animation?: boolean
|
|
923
|
+
): { windowRef: ComponentRef<T>; transition$: Observable<void> };
|
|
924
|
+
close(animation?: boolean): Observable<void>;
|
|
872
925
|
}
|
|
873
926
|
|
|
874
927
|
// The nodes and view a popup projects, returned internally by the content resolver.
|
|
875
928
|
class ContentRef {
|
|
876
|
-
|
|
929
|
+
constructor(nodes: Node[][], viewRef?: ViewRef, componentRef?: ComponentRef<any>);
|
|
877
930
|
}
|
|
878
931
|
```
|
|
879
932
|
|
|
880
933
|
### Transition Utilities
|
|
881
934
|
|
|
882
|
-
-
|
|
883
|
-
-
|
|
884
|
-
-
|
|
885
|
-
-
|
|
935
|
+
- `hubRunTransition<T>(zone, element, startFn, options)` - Advanced transition system with Observable
|
|
936
|
+
- `hubCompleteTransition(element)` - Completes a running transition on an element
|
|
937
|
+
- `getTransitionDurationMs(element)` - Gets CSS transition duration in milliseconds
|
|
938
|
+
- `runInZone<T>(zone)` - RxJS operator to execute observables inside NgZone
|
|
886
939
|
|
|
887
940
|
## π¨ Support Components
|
|
888
941
|
|
|
889
942
|
This library doesn't include visual components, but support utilities used by other components in the Hub UI ecosystem:
|
|
890
943
|
|
|
891
|
-
| Utility | Description | Used by
|
|
892
|
-
| --------------- | ----------------------------------- |
|
|
893
|
-
| Overlay Service | Flexible overlay positioning system | ng-hub-ui-modal, ng-hub-ui-portal
|
|
894
|
-
| Focus Trap | Focus management in modals/overlays | ng-hub-ui-modal, ng-hub-ui-portal
|
|
895
|
-
| Scrollbar | Scrollbar compensation | ng-hub-ui-modal, ng-hub-ui-portal
|
|
896
|
-
| Popup Service | Host for dynamically created popups | ng-hub-ui-modal, ng-hub-ui-portal
|
|
897
|
-
| Transitions | Smooth animations | ng-hub-ui-accordion, ng-hub-ui-modal
|
|
898
|
-
| Type Guards | Type validation functions | ng-hub-ui-stepper
|
|
899
|
-
| Pipes | Template utilities | All Hub UI components
|
|
944
|
+
| Utility | Description | Used by |
|
|
945
|
+
| --------------- | ----------------------------------- | ------------------------------------ |
|
|
946
|
+
| Overlay Service | Flexible overlay positioning system | ng-hub-ui-modal, ng-hub-ui-portal |
|
|
947
|
+
| Focus Trap | Focus management in modals/overlays | ng-hub-ui-modal, ng-hub-ui-portal |
|
|
948
|
+
| Scrollbar | Scrollbar compensation | ng-hub-ui-modal, ng-hub-ui-portal |
|
|
949
|
+
| Popup Service | Host for dynamically created popups | ng-hub-ui-modal, ng-hub-ui-portal |
|
|
950
|
+
| Transitions | Smooth animations | ng-hub-ui-accordion, ng-hub-ui-modal |
|
|
951
|
+
| Type Guards | Type validation functions | ng-hub-ui-stepper |
|
|
952
|
+
| Pipes | Template utilities | All Hub UI components |
|
|
900
953
|
|
|
901
954
|
## π€ Compatibility
|
|
902
955
|
|
|
903
|
-
-
|
|
904
|
-
-
|
|
905
|
-
-
|
|
906
|
-
-
|
|
956
|
+
- Angular 16+
|
|
957
|
+
- TypeScript 4.8+
|
|
958
|
+
- Node.js 16+
|
|
959
|
+
- Browsers: Chrome 90+, Firefox 88+, Safari 14+, Edge 90+
|
|
907
960
|
|
|
908
961
|
## π οΈ Development
|
|
909
962
|
|
|
@@ -977,10 +1030,10 @@ Recent highlights:
|
|
|
977
1030
|
|
|
978
1031
|
## π Issues and Support
|
|
979
1032
|
|
|
980
|
-
-
|
|
981
|
-
-
|
|
982
|
-
-
|
|
983
|
-
-
|
|
1033
|
+
- [Report a bug](https://github.com/hub-env/hub-ui/issues)
|
|
1034
|
+
- [Request a feature](https://github.com/hub-env/hub-ui/issues/new?template=feature_request.yml)
|
|
1035
|
+
- [Repository](https://github.com/hub-env/ng-hub-ui-utils)
|
|
1036
|
+
- **Author**: [Carlos Morcillo](https://www.carlosmorcillo.com)
|
|
984
1037
|
|
|
985
1038
|
## β Support the Project
|
|
986
1039
|
|
|
@@ -991,10 +1044,10 @@ If Hub UI has been useful to you, consider supporting its development:
|
|
|
991
1044
|
|
|
992
1045
|
Your support helps to:
|
|
993
1046
|
|
|
994
|
-
-
|
|
995
|
-
-
|
|
996
|
-
-
|
|
997
|
-
-
|
|
1047
|
+
- π Keep the project active
|
|
1048
|
+
- π Fix bugs faster
|
|
1049
|
+
- β¨ Develop new features
|
|
1050
|
+
- π Improve documentation
|
|
998
1051
|
|
|
999
1052
|
## π€ Contributions
|
|
1000
1053
|
|
|
@@ -1008,6 +1061,14 @@ Contributions are welcome! Please:
|
|
|
1008
1061
|
|
|
1009
1062
|
Check our [contribution guidelines](CONTRIBUTING.md) for more details.
|
|
1010
1063
|
|
|
1064
|
+
## πΌ Commercial support
|
|
1065
|
+
|
|
1066
|
+
These libraries are maintained by [Carlos Morcillo FernΓ‘ndez](https://www.carlosmorcillo.com), a freelance frontend architect working with teams that build and maintain Angular applications.
|
|
1067
|
+
|
|
1068
|
+
If your team depends on Hub-UI and needs more than an issue thread can solve, that is my day job: architecture audits, design systems, Angular migrations and team mentoring. For projects that also need design and a full team, I run them through [Frog Hub](https://froghub.es), my development studio.
|
|
1069
|
+
|
|
1070
|
+
Have a look at [the services](https://www.carlosmorcillo.com/en/services/) or [tell me about your project](https://www.carlosmorcillo.com/en/contact/).
|
|
1071
|
+
|
|
1011
1072
|
## π License
|
|
1012
1073
|
|
|
1013
1074
|
MIT Β© Hub UI contributors
|