ng-hub-ui-utils 22.19.0 → 22.21.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 CHANGED
@@ -385,7 +385,8 @@ export class ExampleComponent {}
385
385
  > ```
386
386
 
387
387
  Inputs: `hubTooltip` (text), `hubTooltipPlacement` (`top` | `bottom` | `left` | `right`,
388
- default `top`), `hubTooltipDelay` (fade ms, default `150`), `hubTooltipOffset` (px, default `8`).
388
+ default `top`), `hubTooltipDelay` (fade ms, default `150`), `hubTooltipShowDelay` (ms a hover has to last before
389
+ the tooltip opens, default `0`; keyboard focus always opens it at once), `hubTooltipOffset` (px, default `8`).
389
390
 
390
391
  **The placement is a preference, not an instruction (since 22.16.0).** A tooltip that would open
391
392
  off the edge of the window opens on the opposite side instead, at all four edges, and one centred
@@ -814,9 +815,14 @@ library, because their selectors and data models differ.
814
815
  - `createPointerDragSession(config: PointerDragSessionConfig): PointerDragSession` - Pointer Events fallback for touch, where native drag events are not delivered
815
816
  - Types: `DropPosition`, `DragPointerMode`, `DragContainerRef<T>`, `ActiveDrag<T>`, `DragTarget<T>`, `DragRegistration`
816
817
 
818
+ ### Form Field Feedback
819
+
820
+ - `HUB_FIELD_FEEDBACK: InjectionToken<Signal<HubFieldFeedbackPlacement>>` - Lets a container decide where the form fields inside it show their validation message: `'bottom'`, under the control as in a form, or `'tooltip'`, where the field is ringed in the danger colour and the message opens on hover. A container that lays fields out as cells provides it, because a message under the control makes the row taller. It lives here so the paginable table and the spreadsheet can ask for it without depending on `ng-hub-ui-forms`, which is the library that honours it
821
+ - Types: `HubFieldFeedbackPlacement` (`'bottom' | 'tooltip'`)
822
+
817
823
  ### Directives
818
824
 
819
- - `HubTooltipDirective` (`[hubTooltip]`) - Tooltip on hover/focus, flipped away from the window edges. Inputs: `hubTooltip`, `hubTooltipPlacement`, `hubTooltipDelay`, `hubTooltipOffset`
825
+ - `HubTooltipDirective` (`[hubTooltip]`) - Tooltip on hover/focus, flipped away from the window edges. Inputs: `hubTooltip`, `hubTooltipPlacement`, `hubTooltipDelay`, `hubTooltipShowDelay`, `hubTooltipOffset`
820
826
  - `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)
821
827
  - `provideHubTooltip(adapter: HubTooltipAdapter)` and `HUB_TOOLTIP_ADAPTER` - Swap the implementation behind `[hubOverflowTooltip]`, app-wide or per subtree; defaults to `hubTooltipAdapter`
822
828
 
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { signal, computed, Injectable, effect, InjectionToken, inject, makeEnvironmentProviders, provideAppInitializer, DestroyRef, ElementRef, TemplateRef, createComponent, NgZone, ApplicationRef, Pipe, ChangeDetectorRef, Injector, ViewContainerRef, input, Directive, afterNextRender } from '@angular/core';
2
+ import { signal, computed, Injectable, InjectionToken, effect, inject, makeEnvironmentProviders, provideAppInitializer, DestroyRef, ElementRef, TemplateRef, createComponent, NgZone, ApplicationRef, Pipe, ChangeDetectorRef, Injector, ViewContainerRef, input, Directive, afterNextRender } from '@angular/core';
3
3
  import { fromEvent, Observable, Subject, isObservable, EMPTY, of, timer, race } from 'rxjs';
4
4
  import { takeUntil, map, filter, withLatestFrom, endWith, take, mergeMap, tap } from 'rxjs/operators';
5
5
  import { DOCUMENT } from '@angular/common';
@@ -1361,6 +1361,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1361
1361
  * selectors and data models differ.
1362
1362
  */
1363
1363
 
1364
+ /**
1365
+ * Lets a container decide where the form fields inside it show their validation messages.
1366
+ *
1367
+ * Under the control is right in a form and wrong in a grid: in a table cell the message makes
1368
+ * the row taller and pushes every row below it. A container that lays fields out as cells
1369
+ * provides `'tooltip'`, and each field then marks itself in the danger colour and keeps the
1370
+ * message for a tooltip on hover.
1371
+ *
1372
+ * It lives here, and not in `ng-hub-ui-forms`, so a library that hosts fields (the paginable
1373
+ * table) can ask for it without depending on the forms library. A signal, because the
1374
+ * container's own setting can change at runtime.
1375
+ */
1376
+ const HUB_FIELD_FEEDBACK = new InjectionToken('HUB_FIELD_FEEDBACK');
1377
+
1364
1378
  const FOCUSABLE_ELEMENTS_SELECTOR = [
1365
1379
  'a[href]',
1366
1380
  'button:not([disabled])',
@@ -4068,11 +4082,13 @@ let nextTooltipId = 0;
4068
4082
  class HubTooltipController {
4069
4083
  host;
4070
4084
  tooltipEl = null;
4085
+ showTimeout = null;
4071
4086
  hideTimeout = null;
4072
4087
  leaveTimeout = null;
4073
4088
  text = '';
4074
4089
  placement = 'top';
4075
4090
  delay = 150;
4091
+ showDelay = 0;
4076
4092
  offset = 8;
4077
4093
  /**
4078
4094
  * Side the bubble is actually on, which is the requested placement until the window says
@@ -4085,6 +4101,7 @@ class HubTooltipController {
4085
4101
  /** Id of this controller's bubble, minted once so the host can be described by it. */
4086
4102
  tooltipId = `hub-tooltip-${++nextTooltipId}`;
4087
4103
  onShow = () => this.show();
4104
+ onPointerEnter = () => this.scheduleShow();
4088
4105
  onHide = () => this.hide();
4089
4106
  onPointerLeave = () => this.scheduleHide();
4090
4107
  onTooltipEnter = () => this.retain();
@@ -4115,7 +4132,7 @@ class HubTooltipController {
4115
4132
  this.doc = host.ownerDocument;
4116
4133
  this.view = this.doc.defaultView;
4117
4134
  this.setOptions(options);
4118
- this.host.addEventListener('mouseenter', this.onShow);
4135
+ this.host.addEventListener('mouseenter', this.onPointerEnter);
4119
4136
  this.host.addEventListener('focus', this.onShow);
4120
4137
  this.host.addEventListener('mouseleave', this.onPointerLeave);
4121
4138
  this.host.addEventListener('blur', this.onHide);
@@ -4151,21 +4168,34 @@ class HubTooltipController {
4151
4168
  if (options.delay != null) {
4152
4169
  this.delay = options.delay;
4153
4170
  }
4171
+ if (options.showDelay != null) {
4172
+ this.showDelay = Math.max(0, options.showDelay);
4173
+ }
4154
4174
  if (options.offset != null) {
4155
4175
  this.offset = options.offset;
4156
4176
  }
4157
4177
  }
4158
4178
  /** Detaches listeners and removes any live tooltip element. */
4159
4179
  destroy() {
4160
- this.host.removeEventListener('mouseenter', this.onShow);
4180
+ this.host.removeEventListener('mouseenter', this.onPointerEnter);
4161
4181
  this.host.removeEventListener('focus', this.onShow);
4162
4182
  this.host.removeEventListener('mouseleave', this.onPointerLeave);
4163
4183
  this.host.removeEventListener('blur', this.onHide);
4164
4184
  this.host.removeEventListener('click', this.onHide);
4165
4185
  this.removeElement();
4166
4186
  }
4187
+ /** Waits for a sustained hover before opening, while retaining an already visible bubble. */
4188
+ scheduleShow() {
4189
+ this.clearShowTimeout();
4190
+ if (this.tooltipEl || this.showDelay === 0) {
4191
+ this.show();
4192
+ return;
4193
+ }
4194
+ this.showTimeout = setTimeout(() => this.show(), this.showDelay);
4195
+ }
4167
4196
  /** Creates, positions and reveals the tooltip element. */
4168
4197
  show() {
4198
+ this.clearShowTimeout();
4169
4199
  if (!this.text) {
4170
4200
  return;
4171
4201
  }
@@ -4218,6 +4248,7 @@ class HubTooltipController {
4218
4248
  * offset opens between host and bubble without the label vanishing on the way.
4219
4249
  */
4220
4250
  scheduleHide() {
4251
+ this.clearShowTimeout();
4221
4252
  if (!this.tooltipEl) {
4222
4253
  return;
4223
4254
  }
@@ -4235,6 +4266,7 @@ class HubTooltipController {
4235
4266
  }
4236
4267
  /** Fades the tooltip out and removes it after the fade completes. */
4237
4268
  hide() {
4269
+ this.clearShowTimeout();
4238
4270
  this.clearLeaveTimeout();
4239
4271
  if (!this.tooltipEl) {
4240
4272
  return;
@@ -4248,6 +4280,7 @@ class HubTooltipController {
4248
4280
  }
4249
4281
  /** Removes the tooltip element immediately. */
4250
4282
  removeElement() {
4283
+ this.clearShowTimeout();
4251
4284
  this.clearHideTimeout();
4252
4285
  this.clearLeaveTimeout();
4253
4286
  if (this.tooltipEl) {
@@ -4304,6 +4337,13 @@ class HubTooltipController {
4304
4337
  }
4305
4338
  }
4306
4339
  }
4340
+ /** Cancels hover work when the pointer leaves or the host is destroyed. */
4341
+ clearShowTimeout() {
4342
+ if (this.showTimeout !== null) {
4343
+ clearTimeout(this.showTimeout);
4344
+ this.showTimeout = null;
4345
+ }
4346
+ }
4307
4347
  clearHideTimeout() {
4308
4348
  if (this.hideTimeout !== null) {
4309
4349
  clearTimeout(this.hideTimeout);
@@ -4471,6 +4511,8 @@ class HubTooltipDirective {
4471
4511
  placement = input('top', { ...(ngDevMode ? { debugName: "placement" } : /* istanbul ignore next */ {}), alias: 'hubTooltipPlacement' });
4472
4512
  /** Fade duration in milliseconds, also used as the removal delay on hide. */
4473
4513
  delay = input(150, { ...(ngDevMode ? { debugName: "delay" } : /* istanbul ignore next */ {}), alias: 'hubTooltipDelay' });
4514
+ /** Pointer hover wait in milliseconds; keyboard focus remains immediate. */
4515
+ showDelay = input(0, { ...(ngDevMode ? { debugName: "showDelay" } : /* istanbul ignore next */ {}), alias: 'hubTooltipShowDelay' });
4474
4516
  /** Gap in pixels between the host and the tooltip. */
4475
4517
  offset = input(8, { ...(ngDevMode ? { debugName: "offset" } : /* istanbul ignore next */ {}), alias: 'hubTooltipOffset' });
4476
4518
  host = inject(ElementRef);
@@ -4480,6 +4522,7 @@ class HubTooltipDirective {
4480
4522
  this.controller.setOptions({
4481
4523
  placement: this.placement(),
4482
4524
  delay: this.delay(),
4525
+ showDelay: this.showDelay(),
4483
4526
  offset: this.offset()
4484
4527
  });
4485
4528
  this.controller.setText(this.text());
@@ -4490,14 +4533,14 @@ class HubTooltipDirective {
4490
4533
  this.controller.destroy();
4491
4534
  }
4492
4535
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubTooltipDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4493
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.8", type: HubTooltipDirective, isStandalone: true, selector: "[hubTooltip]", inputs: { text: { classPropertyName: "text", publicName: "hubTooltip", isSignal: true, isRequired: true, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "hubTooltipPlacement", isSignal: true, isRequired: false, transformFunction: null }, delay: { classPropertyName: "delay", publicName: "hubTooltipDelay", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "hubTooltipOffset", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
4536
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.8", type: HubTooltipDirective, isStandalone: true, selector: "[hubTooltip]", inputs: { text: { classPropertyName: "text", publicName: "hubTooltip", isSignal: true, isRequired: true, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "hubTooltipPlacement", isSignal: true, isRequired: false, transformFunction: null }, delay: { classPropertyName: "delay", publicName: "hubTooltipDelay", isSignal: true, isRequired: false, transformFunction: null }, showDelay: { classPropertyName: "showDelay", publicName: "hubTooltipShowDelay", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "hubTooltipOffset", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
4494
4537
  }
4495
4538
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubTooltipDirective, decorators: [{
4496
4539
  type: Directive,
4497
4540
  args: [{
4498
4541
  selector: '[hubTooltip]'
4499
4542
  }]
4500
- }], ctorParameters: () => [], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "hubTooltip", required: true }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "hubTooltipPlacement", required: false }] }], delay: [{ type: i0.Input, args: [{ isSignal: true, alias: "hubTooltipDelay", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "hubTooltipOffset", required: false }] }] } });
4543
+ }], ctorParameters: () => [], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "hubTooltip", required: true }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "hubTooltipPlacement", required: false }] }], delay: [{ type: i0.Input, args: [{ isSignal: true, alias: "hubTooltipDelay", required: false }] }], showDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "hubTooltipShowDelay", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "hubTooltipOffset", required: false }] }] } });
4501
4544
 
4502
4545
  /**
4503
4546
  * Shows a tooltip with the given text **only while the host element is truncated**
@@ -4651,5 +4694,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
4651
4694
  * Generated bundle index. Do not edit.
4652
4695
  */
4653
4696
 
4654
- export { ContentRef, FOCUSABLE_ELEMENTS_SELECTOR, GetPipe, HUB_DROPDOWN_POSITIONS, HUB_INK_LIGHTNESS_THRESHOLD, HUB_MAX_HUE_SHIFT, HUB_MAX_NEUTRAL_CHROMA, HUB_NAMED_COLORS, HUB_NEUTRAL_ANCHORS, HUB_SEMANTIC_ANCHORS, HUB_TOOLTIP_ADAPTER, HUB_TRANSLATION_CONFIG, HUB_TRANSLATION_PREFIX, HUB_TRANSLATION_SOURCE, HubDragDropService, HubGetPipe, HubIsObjectPipe, HubIsObservablePipe, HubIsStringPipe, HubOverflowTooltipDirective, HubTooltipController, HubTooltipDirective, HubTranslatePipe, HubTranslationService, HubUnwrapAsyncPipe, IsObjectPipe, IsObservablePipe, IsStringPipe, OverlayPosition, OverlayRef, OverlayService, PopupService, ScrollBar, TranslatePipe, UcfirstPipe, UnwrapAsyncPipe, addGridRange, anchorOf, buildSpanMap, clamp$1 as clamp, clampGridRange, clampGridSelection, clampToSrgbGamut, closest, compositeOver, computeTargetIndex, containsNode, contrastAPCA, contrastRatio, copyArrayItem, coversRange, createNativeDragImage, createPointerDragSession, debouncedSignal, equals, generateUniqueId, getActiveElement, getFocusableBoundaryElements, getValue, getValueInRange, gridCellTabIndex, gridRangeBetween, gridRangeCells, gridRangeSize, gridSelectionBounds, gridSelectionCells, gridSelectionSize, gridSelectionTable, gridWindow, growWindowToSpans, harmoniseSemantics, hubAnchorToViewport, hubClampToViewport, hubCompleteTransition, hubFitsInViewport, hubFocusTrap, hubOppositeSide, hubRunTransition, hubToAnchorSide, hubToPhysicalSide, hubTooltipAdapter, hubViewportOf, interpolateString, isCovered, isDefined, isInSrgbGamut, isInteger, isNumber, isObject, isPromise, isString, isValidColor, isWithinGridRange, isWithinGridSelection, maxSrgbChroma, mergeDeep, moveGridFocus, moveItemInArray, oklchToRgb, padNumber, parseColor, provideHubTooltip, provideHubTranslation, provideHubTranslationAdapter, readableOn, reflow, regExpEscape, registerOverlayKeydown, relativeLuminance, removeAccents, resolveDropPosition, resolveGridEdge, resolveGridIntent, resolveHubAccent, rgbToOklch, runInZone, spanAt, tintNeutrals, toAbsoluteIndex, toHex, toInteger, toRgb, toString, transferArrayItem };
4697
+ export { ContentRef, FOCUSABLE_ELEMENTS_SELECTOR, GetPipe, HUB_DROPDOWN_POSITIONS, HUB_FIELD_FEEDBACK, HUB_INK_LIGHTNESS_THRESHOLD, HUB_MAX_HUE_SHIFT, HUB_MAX_NEUTRAL_CHROMA, HUB_NAMED_COLORS, HUB_NEUTRAL_ANCHORS, HUB_SEMANTIC_ANCHORS, HUB_TOOLTIP_ADAPTER, HUB_TRANSLATION_CONFIG, HUB_TRANSLATION_PREFIX, HUB_TRANSLATION_SOURCE, HubDragDropService, HubGetPipe, HubIsObjectPipe, HubIsObservablePipe, HubIsStringPipe, HubOverflowTooltipDirective, HubTooltipController, HubTooltipDirective, HubTranslatePipe, HubTranslationService, HubUnwrapAsyncPipe, IsObjectPipe, IsObservablePipe, IsStringPipe, OverlayPosition, OverlayRef, OverlayService, PopupService, ScrollBar, TranslatePipe, UcfirstPipe, UnwrapAsyncPipe, addGridRange, anchorOf, buildSpanMap, clamp$1 as clamp, clampGridRange, clampGridSelection, clampToSrgbGamut, closest, compositeOver, computeTargetIndex, containsNode, contrastAPCA, contrastRatio, copyArrayItem, coversRange, createNativeDragImage, createPointerDragSession, debouncedSignal, equals, generateUniqueId, getActiveElement, getFocusableBoundaryElements, getValue, getValueInRange, gridCellTabIndex, gridRangeBetween, gridRangeCells, gridRangeSize, gridSelectionBounds, gridSelectionCells, gridSelectionSize, gridSelectionTable, gridWindow, growWindowToSpans, harmoniseSemantics, hubAnchorToViewport, hubClampToViewport, hubCompleteTransition, hubFitsInViewport, hubFocusTrap, hubOppositeSide, hubRunTransition, hubToAnchorSide, hubToPhysicalSide, hubTooltipAdapter, hubViewportOf, interpolateString, isCovered, isDefined, isInSrgbGamut, isInteger, isNumber, isObject, isPromise, isString, isValidColor, isWithinGridRange, isWithinGridSelection, maxSrgbChroma, mergeDeep, moveGridFocus, moveItemInArray, oklchToRgb, padNumber, parseColor, provideHubTooltip, provideHubTranslation, provideHubTranslationAdapter, readableOn, reflow, regExpEscape, registerOverlayKeydown, relativeLuminance, removeAccents, resolveDropPosition, resolveGridEdge, resolveGridIntent, resolveHubAccent, rgbToOklch, runInZone, spanAt, tintNeutrals, toAbsoluteIndex, toHex, toInteger, toRgb, toString, transferArrayItem };
4655
4698
  //# sourceMappingURL=ng-hub-ui-utils.mjs.map