@truenas/ui-components 0.7.9 → 0.7.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truenas/ui-components",
3
- "version": "0.7.9",
3
+ "version": "0.7.11",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org",
6
6
  "access": "public"
@@ -562,12 +562,34 @@ declare class TnSelectComponent<T = unknown> implements ControlValueAccessor, On
562
562
  * - **Enter / Space** opens the dropdown if closed; if open and an option
563
563
  * is highlighted, selects that option (in single mode) or toggles it
564
564
  * (in multiple mode).
565
- * - **Escape** closes the dropdown without changing the selection.
565
+ * - **Escape** is NOT here: it closes the dropdown without changing the
566
+ * selection, from `onHostKeydown` on this component's host element,
567
+ * because it also has to consume the key.
566
568
  *
567
569
  * All navigation keys call `event.preventDefault()` so the page does not
568
570
  * scroll while the user is moving through options.
569
571
  */
570
572
  onKeydown(event: KeyboardEvent): void;
573
+ /**
574
+ * Escape, which closes the dropdown and CONSUMES the key.
575
+ *
576
+ * Consuming matters because of what is usually underneath: since #322 a
577
+ * `tn-side-panel` is a CDK overlay listening for Escape through
578
+ * `OverlayKeyboardDispatcher`, which delivers to the top-most attached
579
+ * overlay that has subscribers. Closing the dropdown disposes this select's
580
+ * overlay mid-keystroke, taking it out of that stack — so an Escape left to
581
+ * carry on reached `<body>`, found the panel as the new top-most subscriber,
582
+ * and closed the whole form along with the dropdown (#324). A `tn-drawer` in
583
+ * `over` mode has a keydown handler of its own and closed the same way.
584
+ *
585
+ * Why this sits on the HOST rather than beside the other keys on the trigger:
586
+ * `stopPropagation()` stops ANCESTOR listeners, not other listeners on the
587
+ * same element. Called from the trigger it also hid Escape from a `tnTooltip`
588
+ * on this component, which dismisses a visible tooltip from a host listener —
589
+ * so the tooltip stayed up. From here, that directive still sees the key and
590
+ * only the containers ABOVE this select stop seeing it.
591
+ */
592
+ protected onHostKeydown(event: KeyboardEvent): void;
571
593
  private moveFocus;
572
594
  private activateFocusedOption;
573
595
  private scrollFocusedIntoView;
@@ -1146,6 +1168,31 @@ declare class TnAutocompleteComponent<T = unknown> implements ControlValueAccess
1146
1168
  */
1147
1169
  private runAction;
1148
1170
  onKeydown(event: KeyboardEvent): void;
1171
+ /**
1172
+ * Escape, which cancels the draft, dismisses the panel, and — while the panel
1173
+ * is open — CONSUMES the key.
1174
+ *
1175
+ * Consuming matters because of what is usually underneath: since #322 a
1176
+ * `tn-side-panel` is a CDK overlay listening for Escape through
1177
+ * `OverlayKeyboardDispatcher`, which delivers to the top-most attached
1178
+ * overlay that has subscribers. Dismissing disposes this panel's overlay
1179
+ * mid-keystroke, taking it out of that stack — so an Escape left to carry on
1180
+ * reached `<body>`, found the side panel as the new top-most subscriber, and
1181
+ * closed the whole form along with the panel (#324). A `tn-drawer` in `over`
1182
+ * mode has a keydown handler of its own and closed the same way.
1183
+ *
1184
+ * With the panel already closed the key is NOT consumed: there is nothing to
1185
+ * dismiss and it belongs to whatever contains this field. The draft still
1186
+ * reverts, which is what the blur that follows would otherwise commit.
1187
+ *
1188
+ * Why this sits on the HOST rather than beside the other keys on the input:
1189
+ * `stopPropagation()` stops ANCESTOR listeners, not other listeners on the
1190
+ * same element. Called from the input it also hid Escape from a `tnTooltip`
1191
+ * on this component, which dismisses a visible tooltip from a host listener —
1192
+ * so the tooltip stayed up. From here, that directive still sees the key and
1193
+ * only the containers ABOVE this field stop seeing it.
1194
+ */
1195
+ protected onHostKeydown(event: KeyboardEvent): void;
1149
1196
  /**
1150
1197
  * `loadMore` normally fires from a scroll event, which never happens when
1151
1198
  * the current page is too short to overflow the panel — pagination would
@@ -1264,6 +1311,13 @@ declare class TnAutocompleteComponent<T = unknown> implements ControlValueAccess
1264
1311
  * matches its width to the input. Mirrors TnSelectComponent's approach.
1265
1312
  */
1266
1313
  private attachOverlay;
1314
+ /**
1315
+ * Escape's effect on this field: cancel the draft, then close.
1316
+ *
1317
+ * Shared by the overlay subscription above and `onHostKeydown`, which is
1318
+ * where Escape arrives while focus is in the field — open panel or closed.
1319
+ */
1320
+ private dismiss;
1267
1321
  private detachOverlay;
1268
1322
  private scrollToHighlighted;
1269
1323
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnAutocompleteComponent<any>, never>;
@@ -4389,6 +4443,29 @@ declare class TnChipInputComponent<T = string> implements ControlValueAccessor,
4389
4443
  protected onFocus(): void;
4390
4444
  protected onBlur(): void;
4391
4445
  protected onKeydown(event: KeyboardEvent): void;
4446
+ /**
4447
+ * Escape, which dismisses the suggestion panel and CONSUMES the key.
4448
+ *
4449
+ * Consuming matters because of what is usually underneath: since #322 a
4450
+ * `tn-side-panel` is a CDK overlay listening for Escape through
4451
+ * `OverlayKeyboardDispatcher`, which delivers to the top-most attached
4452
+ * overlay that has subscribers. Dismissing disposes this panel's overlay
4453
+ * mid-keystroke, taking it out of that stack — so an Escape left to carry on
4454
+ * reached `<body>`, found the side panel as the new top-most subscriber, and
4455
+ * closed the whole form along with the panel (#324). A `tn-drawer` in `over`
4456
+ * mode has a keydown handler of its own and closed the same way.
4457
+ *
4458
+ * With the panel already closed nothing happens here and the key belongs to
4459
+ * whatever contains this field — which is what it did before #324 too.
4460
+ *
4461
+ * Why this sits on the HOST rather than beside the other keys on the input:
4462
+ * `stopPropagation()` stops ANCESTOR listeners, not other listeners on the
4463
+ * same element. Called from the input it also hid Escape from a `tnTooltip`
4464
+ * on this component, which dismisses a visible tooltip from a host listener —
4465
+ * so the tooltip stayed up. From here, that directive still sees the key and
4466
+ * only the containers ABOVE this field stop seeing it.
4467
+ */
4468
+ protected onHostKeydown(event: KeyboardEvent): void;
4392
4469
  protected onSuggestionClick(option: TnChipInputOption<T>): void;
4393
4470
  /** Prevents the option `mousedown` from blurring the input before the click lands. */
4394
4471
  protected onSuggestionMousedown(event: MouseEvent): void;
@@ -4778,6 +4855,7 @@ declare class TnMenuComponent implements OnDestroy {
4778
4855
  contextMenuTemplate: _angular_core.Signal<TemplateRef<unknown>>;
4779
4856
  private contextOverlayRef?;
4780
4857
  private contextBackdropSub?;
4858
+ private contextKeydownSub?;
4781
4859
  private overlay;
4782
4860
  private viewContainerRef;
4783
4861
  onMenuItemClick(item: TnMenuItem): void;
@@ -12639,6 +12717,25 @@ declare class TnDateRangeInputComponent implements ControlValueAccessor, OnInit,
12639
12717
  private updateDateFromSegments;
12640
12718
  private focusNextSegment;
12641
12719
  private focusPrevSegment;
12720
+ /**
12721
+ * Escape, which closes the calendar and CONSUMES the key. Same reasoning as
12722
+ * `TnDateInputComponent.onHostKeydown`, which this mirrors: opening does not
12723
+ * move focus into the overlay, so the real keystroke starts on a date
12724
+ * segment inside this host, where an ancestor such as a `tn-drawer` in
12725
+ * `over` mode would stop it before CDK's keyboard dispatcher ever ran — and
12726
+ * a calendar waiting on the dispatcher stayed open while the drawer closed
12727
+ * underneath it (#324).
12728
+ *
12729
+ * Consuming it also keeps a `tn-side-panel` from acting on the same key: the
12730
+ * panel is a CDK overlay subscribed through that dispatcher (#322), and
12731
+ * closing this calendar disposes its overlay mid-keystroke, leaving the
12732
+ * panel as the top-most subscriber for an Escape allowed to carry on.
12733
+ *
12734
+ * Closed, the key is not consumed; it belongs to whatever contains this
12735
+ * field. On the HOST rather than the segments so that `stopPropagation()`
12736
+ * still leaves a `tnTooltip` on this component able to see the key.
12737
+ */
12738
+ protected onHostKeydown(event: KeyboardEvent): void;
12642
12739
  openDatepicker(): void;
12643
12740
  close(): void;
12644
12741
  private createOverlay;
@@ -13404,6 +13501,34 @@ declare class TnDateInputComponent implements ControlValueAccessor, OnInit, OnDe
13404
13501
  private updateDateFromSegments;
13405
13502
  private focusNextSegment;
13406
13503
  private focusPrevSegment;
13504
+ /**
13505
+ * Escape, which closes the calendar and CONSUMES the key.
13506
+ *
13507
+ * Opening the calendar does not move focus into it, so the keystroke a user
13508
+ * actually makes starts on a date segment — inside this host, not inside the
13509
+ * overlay. That is the path the `keydownEvents()` subscription in
13510
+ * `createOverlay` cannot serve: CDK's `OverlayKeyboardDispatcher` listens on
13511
+ * `<body>`, so any ancestor that stops the key first means it never runs.
13512
+ * `tn-drawer` in `over` mode is exactly such an ancestor — it calls
13513
+ * `stopPropagation()` on Escape from a handler on its own panel element — so
13514
+ * a calendar left waiting for the dispatcher stayed open while the drawer
13515
+ * closed underneath it (#324).
13516
+ *
13517
+ * Consuming it also keeps a `tn-side-panel` from acting on the same key:
13518
+ * since #322 the panel is a CDK overlay subscribed through that dispatcher,
13519
+ * and closing this calendar disposes its overlay mid-keystroke, leaving the
13520
+ * panel as the top-most subscriber for an Escape allowed to carry on.
13521
+ *
13522
+ * With the calendar closed the key is NOT consumed: there is nothing to
13523
+ * close and it belongs to whatever contains this field.
13524
+ *
13525
+ * Why the HOST rather than the segment inputs: `stopPropagation()` stops
13526
+ * ANCESTOR listeners, not other listeners on the same element. From here a
13527
+ * `tnTooltip` on this component still sees Escape and dismisses itself, and
13528
+ * only the containers ABOVE this field stop seeing it — the same placement
13529
+ * `tn-select` and `tn-autocomplete` use.
13530
+ */
13531
+ protected onHostKeydown(event: KeyboardEvent): void;
13407
13532
  openDatepicker(): void;
13408
13533
  close(): void;
13409
13534
  private createOverlay;
@@ -14831,15 +14956,89 @@ declare class TnSidePanelHeaderActionDirective {
14831
14956
  * own, focus it yourself once the panel is open; the component leaves focus
14832
14957
  * alone as soon as it is inside the panel. `lib/a11y/initial-focus.ts` holds
14833
14958
  * the reasoning for capturing the container rather than a control.
14959
+ *
14960
+ * WHERE IT RENDERS, AND WHY THAT DECIDES WHAT IT STACKS AGAINST
14961
+ * ------------------------------------------------------------
14962
+ * The panel renders through a CDK `OverlayRef` created WHEN IT OPENS (#322),
14963
+ * which is what makes it stack with `TnDialog`, `tn-menu` and tooltips by open
14964
+ * order: whatever attached last paints on top, in both directions. It used to
14965
+ * append its overlay to `<body>` at construction time with a fixed `z-index`,
14966
+ * and the CDK overlay container is a `<body>` child with the same one — so the
14967
+ * winner was whichever element `<body>` happened to receive last, which follows
14968
+ * nothing a caller can see. A panel opened FROM a dialog landed under that
14969
+ * dialog's backdrop; on a browser with the popover API, where CDK puts its
14970
+ * overlays in the top layer, no `z-index` on a `<body>` child could have won at
14971
+ * all.
14972
+ *
14973
+ * Three things follow from being a CDK overlay, and each replaces something
14974
+ * this component used to do for itself:
14975
+ *
14976
+ * - **Escape reaches the topmost overlay only**, through CDK's
14977
+ * `OverlayKeyboardDispatcher`, rather than through a `keydown` handler on the
14978
+ * panel that stopped propagation to keep a dialog underneath from closing too.
14979
+ * The panel therefore also closes on Escape pressed outside it, which is what
14980
+ * `TnDialog` does — `tn-drawer` still binds `keydown` on its own panel and
14981
+ * does not.
14982
+ * - **A CDK dialog opened over an open panel hides the panel from assistive
14983
+ * technology.** CDK does this by sweeping the overlay container's SIBLINGS,
14984
+ * which no longer includes the panel, so the component tracks it — see
14985
+ * `dialogsAbove`.
14986
+ * - **A consumer no longer re-homes the overlay or overrides its `z-index` and
14987
+ * `pointer-events`.** There is no `z-index` on it any more; the CDK pane it
14988
+ * lives in carries the stacking.
14834
14989
  */
14835
14990
  declare class TnSidePanelComponent implements OnDestroy {
14836
14991
  private iconRegistry;
14837
14992
  private document;
14838
14993
  private destroyRef;
14994
+ private injector;
14995
+ private dialog;
14839
14996
  private overlayRef;
14840
14997
  private panelRef;
14841
14998
  private contentRef;
14842
14999
  protected initialized: _angular_core.WritableSignal<boolean>;
15000
+ /**
15001
+ * The CDK overlay currently hosting `overlayRef`'s element, or `null` while
15002
+ * the panel is closed (#322).
15003
+ *
15004
+ * **Created on open and disposed once the close has settled**, not once for
15005
+ * the component's lifetime. That is the whole mechanism: CDK decides stacking
15006
+ * by the order overlays ATTACH — `showPopover()` order in the top layer, and
15007
+ * `_updateStackingOrder()`'s move to the end of the container where the
15008
+ * popover API is missing — so an overlay created when the component was built
15009
+ * would stack by construction order, which for a panel rendered inside a
15010
+ * dialog is exactly backwards.
15011
+ *
15012
+ * Non-null is therefore also the answer to "is this panel currently one of
15013
+ * the overlays on screen", which is what `dialogsAbove` keys off.
15014
+ */
15015
+ private cdkOverlay;
15016
+ /** Escape handling for the overlay above, dropped with it. */
15017
+ private keydowns;
15018
+ /**
15019
+ * The CDK dialogs currently covering this panel (#322).
15020
+ *
15021
+ * The panel is hidden from assistive technology while this is non-empty,
15022
+ * which is what CDK's own `Dialog` does for everything outside the overlay
15023
+ * container — it sweeps the container's SIBLINGS, and a panel that now lives
15024
+ * INSIDE the container is not one. A dialog already open when the panel opens
15025
+ * is underneath it and never joins this; only dialogs that arrive afterwards
15026
+ * are above.
15027
+ *
15028
+ * A SET RATHER THAN A COUNT, and each dialog removes ITSELF. Nothing
15029
+ * unsubscribes a dialog's `closed` when the panel closes underneath it — the
15030
+ * panel cannot outlive its own release to do that — so with a count, a
15031
+ * dialog opened during one open and closed during the NEXT one decremented a
15032
+ * tally it had never contributed to, and put the panel back in the
15033
+ * accessibility tree with a live modal still stacked over it. Removing a
15034
+ * member that is not there is a no-op, which is the property a count does
15035
+ * not have.
15036
+ *
15037
+ * Emptied only when the last one goes, on the same reasoning as CDK's
15038
+ * `_removeOpenDialog`: two stacked dialogs closing one at a time must not
15039
+ * un-hide the panel while one is still covering it.
15040
+ */
15041
+ private dialogsAbove;
14843
15042
  /**
14844
15043
  * Whether the content region carries the tab stop, its role and its name
14845
15044
  * (#248) — which is NOT the same question as whether it currently overflows.
@@ -14852,10 +15051,14 @@ declare class TnSidePanelComponent implements OnDestroy {
14852
15051
  * answer is held true while the region has focus, and why `role` and
14853
15052
  * `aria-label` are gated on the same signal as `tabindex` rather than left on.
14854
15053
  *
14855
- * Read in `afterNextRender`, which is where the helper takes its first
14856
- * measurement — before the overlay is portaled to `<body>` below, which does
14857
- * not affect it: `.tn-side-panel__overlay` is `position: fixed; inset: 0`, so
14858
- * its size comes from the viewport rather than from its parent.
15054
+ * The helper's FIRST measurement reads a closed panel, which is
15055
+ * `display: none` since #322 and so reads as not overflowing. That is the
15056
+ * right answer for a panel nobody can reach yet, and it does not stick: the
15057
+ * helper's `ResizeObserver` fires when the overlay attaches and the region
15058
+ * gets a box, which is the same instrument that already answered a viewport
15059
+ * resize. Once open, `.tn-side-panel__overlay` is `position: fixed;
15060
+ * inset: 0`, so the region's size comes from the viewport rather than from
15061
+ * whichever CDK pane is hosting it.
14859
15062
  */
14860
15063
  protected contentKeyboardReachable: _angular_core.Signal<boolean>;
14861
15064
  open: _angular_core.ModelSignal<boolean>;
@@ -14957,19 +15160,75 @@ declare class TnSidePanelComponent implements OnDestroy {
14957
15160
  * fallback and raises no warning.
14958
15161
  */
14959
15162
  protected resolvedAriaLabel: _angular_core.Signal<string | null>;
15163
+ /**
15164
+ * Whether the overlay is out of the accessibility tree: closed, or covered by
15165
+ * a CDK dialog that opened over it (#322).
15166
+ *
15167
+ * The two are one attribute because they are one question, and rendering them
15168
+ * from separate bindings would mean the second could clear the first.
15169
+ */
15170
+ protected hiddenFromAssistiveTech: _angular_core.Signal<boolean>;
14960
15171
  private previouslyFocusedElement;
14961
15172
  /**
14962
15173
  * Decides when an open or a close counts as finished, so that the outputs
14963
15174
  * above fire exactly once per change whether or not a transition ran. A field
14964
15175
  * initializer rather than the constructor, because it registers an `effect`
14965
15176
  * and so needs an injection context.
15177
+ *
15178
+ * It is also what times the overlay's release (#322). The CDK overlay has to
15179
+ * outlive the CLOSE — disposing it puts the element back in this component's
15180
+ * view, where it is `display: none`, so doing that the moment `open` goes
15181
+ * false replaces the slide-out with a disappearance. "The close has settled"
15182
+ * is exactly the question this helper already answers, for a transition that
15183
+ * may never fire.
14966
15184
  */
14967
15185
  private lifecycle;
14968
15186
  constructor();
15187
+ /**
15188
+ * Put the overlay on screen, in a CDK overlay created now (#322).
15189
+ *
15190
+ * The three steps are ordered, and the order is the reason this is not two
15191
+ * bindings:
15192
+ *
15193
+ * 1. **Attach**, which is what fixes where the panel sits in the stack.
15194
+ * 2. **Read layout**, which forces the browser to compute the panel's closed
15195
+ * position in its new home. Without it the attach and the open class land
15196
+ * in one style update, the browser has no "before" to transition from, and
15197
+ * the panel appears rather than slides. A deliberate synchronous reflow,
15198
+ * and the only one: it happens once per open.
15199
+ * 3. **Open**, which is the change the transition runs on.
15200
+ *
15201
+ * Re-entrant on purpose. A panel reopened while its close is still animating
15202
+ * keeps the overlay it already has — the stack has not changed under it, and
15203
+ * `tnTransitionLifecycle` has already cancelled the close that would have
15204
+ * released it.
15205
+ */
15206
+ private showOverlay;
15207
+ /**
15208
+ * Give the CDK overlay back, once the close has finished animating.
15209
+ *
15210
+ * Disposing rather than detaching, so that the next open builds a new overlay
15211
+ * and takes a new place in the stack — see `cdkOverlay`. It restores the
15212
+ * element to this component's own view, where `display: none` keeps it out of
15213
+ * the way until then.
15214
+ */
15215
+ private releaseOverlay;
14969
15216
  ngOnDestroy(): void;
14970
15217
  protected dismiss(): void;
14971
15218
  protected onBackdropClick(): void;
14972
- protected onKeydown(event: KeyboardEvent): void;
15219
+ /**
15220
+ * Escape, delivered by CDK's `OverlayKeyboardDispatcher` (#322).
15221
+ *
15222
+ * It arrives only while this panel is the topmost attached overlay, so
15223
+ * nothing here has to decide whether the key was meant for something above or
15224
+ * below — which is what the `stopPropagation` this replaced was standing in
15225
+ * for, and it could only ever guard the direction it knew about.
15226
+ *
15227
+ * `preventDefault` rather than `stopPropagation`, matching CDK's own
15228
+ * `DialogRef`: the key has already been routed, and swallowing it would hide
15229
+ * it from a consumer listening at the document for their own reasons.
15230
+ */
15231
+ private onOverlayKeydown;
14973
15232
  protected onTransitionEnd(event: TransitionEvent): void;
14974
15233
  private restoreFocus;
14975
15234
  private registerMdiIcons;
@@ -14996,7 +15255,8 @@ declare class TnSidePanelHarness extends ComponentHarness {
14996
15255
  static hostSelector: string;
14997
15256
  static with(options?: SidePanelHarnessFilters): HarnessPredicate<TnSidePanelHarness>;
14998
15257
  /**
14999
- * Locate the overlay wrapper, which may be portaled to document.body.
15258
+ * Locate the overlay wrapper, which is portaled into a CDK overlay while the
15259
+ * panel is open and sits inside the host element while it is not.
15000
15260
  * Uses the data-tn-panel attribute to correlate the host with its overlay.
15001
15261
  */
15002
15262
  private getOverlay;
@@ -15333,6 +15593,35 @@ declare class TnFilePickerComponent implements ControlValueAccessor, OnInit, OnD
15333
15593
  onPathCommit(event: Event): void;
15334
15594
  /** `openOnClick` entry point. */
15335
15595
  onInputClick(): void;
15596
+ /**
15597
+ * Escape, which closes the popup and CONSUMES the key.
15598
+ *
15599
+ * Opening the popup does not move focus into it, so the keystroke a user
15600
+ * actually makes starts on the path input — inside this host, not inside the
15601
+ * overlay. That is the path the `keydownEvents()` subscription in
15602
+ * `createOverlay` cannot serve: CDK's `OverlayKeyboardDispatcher` listens on
15603
+ * `<body>`, so any ancestor that stops the key first means it never runs.
15604
+ * `tn-drawer` in `over` mode is exactly such an ancestor — it calls
15605
+ * `stopPropagation()` on Escape from a handler on its own panel element — so
15606
+ * a picker left waiting for the dispatcher stayed open while the drawer
15607
+ * closed underneath it (#324).
15608
+ *
15609
+ * Consuming it also keeps a `tn-side-panel` from acting on the same key:
15610
+ * since #322 the panel is a CDK overlay subscribed through that dispatcher,
15611
+ * and closing this popup disposes its overlay mid-keystroke, leaving the
15612
+ * panel as the top-most subscriber for an Escape allowed to carry on.
15613
+ *
15614
+ * With the popup closed the key is NOT consumed: there is nothing to close
15615
+ * and it belongs to whatever contains this field. The inline-creation row is
15616
+ * unaffected either way — it lives in the popup, whose keydowns never reach
15617
+ * this host.
15618
+ *
15619
+ * Why the HOST rather than the input: `stopPropagation()` stops ANCESTOR
15620
+ * listeners, not other listeners on the same element. From here a `tnTooltip`
15621
+ * on this component still sees Escape and dismisses itself, and only the
15622
+ * containers ABOVE this field stop seeing it.
15623
+ */
15624
+ protected onHostKeydown(event: KeyboardEvent): void;
15336
15625
  openFilePicker(): void;
15337
15626
  close(): void;
15338
15627
  onItemClick(item: FileSystemItem): void;