@kouji-ui/core 0.6.0 → 0.6.2

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.
@@ -3613,6 +3613,18 @@ class KjRovingTabindexItemDirective {
3613
3613
  /** @internal */
3614
3614
  active = signal(false, /* @ts-ignore */
3615
3615
  ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
3616
+ constructor() {
3617
+ // Registration is by DI, NOT by a content query on the container: a styled
3618
+ // wrapper (`<kj-tab>`) renders its item inside its OWN view, and a content
3619
+ // query cannot cross that boundary. Querying left every item at
3620
+ // tabindex="-1" whenever the wrappers were used — i.e. in every app — so
3621
+ // the tab strip could not be reached by keyboard at all.
3622
+ const parent = inject(KJ_ROVING_TABINDEX, { optional: true });
3623
+ if (!parent)
3624
+ return;
3625
+ parent.register(this);
3626
+ inject(DestroyRef).onDestroy(() => parent.unregister(this));
3627
+ }
3616
3628
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjRovingTabindexItemDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3617
3629
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.5", type: KjRovingTabindexItemDirective, isStandalone: true, selector: "[kjRovingTabindexItem]", host: { properties: { "attr.tabindex": "active() ? \"0\" : \"-1\"" } }, ngImport: i0 });
3618
3630
  }
@@ -3625,7 +3637,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
3625
3637
  '[attr.tabindex]': 'active() ? "0" : "-1"',
3626
3638
  },
3627
3639
  }]
3628
- }] });
3640
+ }], ctorParameters: () => [] });
3629
3641
  /**
3630
3642
  * Implements the roving tabindex pattern for composite widgets such as toolbars and tab lists.
3631
3643
  * Only one item has `tabindex="0"` at a time; arrow keys move focus between items.
@@ -3643,9 +3655,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
3643
3655
  */
3644
3656
  class KjRovingTabindex {
3645
3657
  host = inject((ElementRef));
3646
- items = contentChildren(KjRovingTabindexItemDirective, { ...(ngDevMode ? { debugName: "items" } : /* istanbul ignore next */ {}), descendants: true });
3658
+ registered = signal([], /* @ts-ignore */
3659
+ ...(ngDevMode ? [{ debugName: "registered" }] : /* istanbul ignore next */ []));
3660
+ /** Items in DOM order — registration order follows creation, which a
3661
+ * re-order (`@for` with a changing track) does not preserve. */
3662
+ items = computed(() => [...this.registered()].sort((a, b) => a.el.nativeElement.compareDocumentPosition(b.el.nativeElement) &
3663
+ Node.DOCUMENT_POSITION_FOLLOWING
3664
+ ? -1
3665
+ : 1), /* @ts-ignore */
3666
+ ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
3647
3667
  activeIndex = signal(0, /* @ts-ignore */
3648
3668
  ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
3669
+ /** @internal */
3670
+ register(item) {
3671
+ this.registered.update((all) => [...all, item]);
3672
+ }
3673
+ /** @internal */
3674
+ unregister(item) {
3675
+ this.registered.update((all) => all.filter((i) => i !== item));
3676
+ }
3649
3677
  /**
3650
3678
  * Restricts arrow-key navigation to a single axis.
3651
3679
  * - `'horizontal'`: only ArrowLeft / ArrowRight move focus.
@@ -3726,7 +3754,7 @@ class KjRovingTabindex {
3726
3754
  all[next].el.nativeElement.focus();
3727
3755
  }
3728
3756
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjRovingTabindex, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3729
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.0.5", type: KjRovingTabindex, isStandalone: true, selector: "[kjRovingTabindex]", inputs: { kjRovingOrientation: { classPropertyName: "kjRovingOrientation", publicName: "kjRovingOrientation", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "keydown": "onKeydown($event)", "focusin": "onFocusIn($event)" } }, providers: [{ provide: KJ_ROVING_TABINDEX, useExisting: forwardRef(() => KjRovingTabindex) }], queries: [{ propertyName: "items", predicate: KjRovingTabindexItemDirective, descendants: true, isSignal: true }], ngImport: i0 });
3757
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.5", type: KjRovingTabindex, isStandalone: true, selector: "[kjRovingTabindex]", inputs: { kjRovingOrientation: { classPropertyName: "kjRovingOrientation", publicName: "kjRovingOrientation", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "keydown": "onKeydown($event)", "focusin": "onFocusIn($event)" } }, providers: [{ provide: KJ_ROVING_TABINDEX, useExisting: forwardRef(() => KjRovingTabindex) }], ngImport: i0 });
3730
3758
  }
3731
3759
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjRovingTabindex, decorators: [{
3732
3760
  type: Directive,
@@ -3739,9 +3767,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
3739
3767
  '(focusin)': 'onFocusIn($event)',
3740
3768
  },
3741
3769
  }]
3742
- }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => KjRovingTabindexItemDirective), { ...{
3743
- descendants: true,
3744
- }, isSignal: true }] }], kjRovingOrientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjRovingOrientation", required: false }] }] } });
3770
+ }], ctorParameters: () => [], propDecorators: { kjRovingOrientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjRovingOrientation", required: false }] }] } });
3745
3771
 
3746
3772
  /**
3747
3773
  * Visually hides an element while keeping it accessible to screen readers.
@@ -13496,8 +13522,23 @@ function nextConfirmPopupMessageId() {
13496
13522
  * @doc-category Core/Overlay
13497
13523
  */
13498
13524
  class KjConfirmPopup {
13499
- /** The overlay controller provided by the trigger directive on the same element. */
13500
- controller = inject(KjOverlayController, { optional: true });
13525
+ /** The overlay controller, when the trigger sits on this very element. */
13526
+ selfController = inject(KjOverlayController, { optional: true });
13527
+ /**
13528
+ * The controller of a nested `[kjConfirmPopupTrigger]`. The documented
13529
+ * composition puts the trigger on a CHILD of `[kjConfirmPopup]` (see every
13530
+ * example), so the controller is NOT in this element's injector — without
13531
+ * this registration `close()` bailed out and `(kjConfirmed)` never fired.
13532
+ */
13533
+ registered = signal(null, /* @ts-ignore */
13534
+ ...(ngDevMode ? [{ debugName: "registered" }] : /* istanbul ignore next */ []));
13535
+ /** @internal — called by a nested `[kjConfirmPopupTrigger]`. */
13536
+ _registerController(c) {
13537
+ this.registered.set(c);
13538
+ }
13539
+ controllerOf() {
13540
+ return this.selfController ?? this.registered();
13541
+ }
13501
13542
  /** When `true`, the confirm action renders with destructive intent. */
13502
13543
  kjDestructive = input(false, { ...(ngDevMode ? { debugName: "kjDestructive" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
13503
13544
  /** Where to place initial focus. Default `'cancel'`. */
@@ -13511,10 +13552,8 @@ class KjConfirmPopup {
13511
13552
  kjResult = output();
13512
13553
  // ── KjConfirmPopupContext ──────────────────────────────────────────
13513
13554
  /** Open state forwarded from the underlying controller. */
13514
- open = (() => {
13515
- const ctrl = this.controller;
13516
- return ctrl ? ctrl.isOpen : (() => false);
13517
- })();
13555
+ open = computed(() => this.controllerOf()?.isOpen() ?? false, /* @ts-ignore */
13556
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
13518
13557
  /** Whether the confirm action should render destructively. */
13519
13558
  destructive = this.kjDestructive;
13520
13559
  /** Resolved default focus target. */
@@ -13526,7 +13565,7 @@ class KjConfirmPopup {
13526
13565
  constructor() {
13527
13566
  // Bridge the controller's open signal into the resolution contract.
13528
13567
  effect(() => {
13529
- const isOpen = this.controller?.isOpen() ?? false;
13568
+ const isOpen = this.controllerOf()?.isOpen() ?? false;
13530
13569
  if (isOpen && !this.wasOpen) {
13531
13570
  this.resolved = false;
13532
13571
  }
@@ -13542,7 +13581,8 @@ class KjConfirmPopup {
13542
13581
  * Close the popup with the given result. `true` confirms, `false` cancels.
13543
13582
  */
13544
13583
  close(result) {
13545
- if (!this.controller || !this.controller.isOpen())
13584
+ const controller = this.controllerOf();
13585
+ if (!controller || !controller.isOpen())
13546
13586
  return;
13547
13587
  this.resolved = true;
13548
13588
  if (result)
@@ -13550,7 +13590,7 @@ class KjConfirmPopup {
13550
13590
  else
13551
13591
  this.kjCancelled.emit();
13552
13592
  this.kjResult.emit(result);
13553
- this.controller.close('programmatic');
13593
+ controller.close('programmatic');
13554
13594
  }
13555
13595
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjConfirmPopup, deps: [], target: i0.ɵɵFactoryTarget.Directive });
13556
13596
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.5", type: KjConfirmPopup, isStandalone: true, selector: "[kjConfirmPopup]", inputs: { kjDestructive: { classPropertyName: "kjDestructive", publicName: "kjDestructive", isSignal: true, isRequired: false, transformFunction: null }, kjDefaultFocus: { classPropertyName: "kjDefaultFocus", publicName: "kjDefaultFocus", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { kjConfirmed: "kjConfirmed", kjCancelled: "kjCancelled", kjResult: "kjResult" }, providers: [
@@ -13578,6 +13618,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
13578
13618
  */
13579
13619
  class KjConfirmPopupTrigger {
13580
13620
  _overlayTrigger = inject(KjOverlayTrigger, { self: true });
13621
+ constructor() {
13622
+ // The trigger normally sits on a CHILD of `[kjConfirmPopup]`, so the root
13623
+ // cannot see this controller through its own injector. Hand it over, or
13624
+ // the confirm/cancel slots resolve nothing.
13625
+ inject(KJ_CONFIRM_POPUP, { optional: true })?._registerController?.(this._overlayTrigger.controller);
13626
+ }
13581
13627
  /** Forwarded controller for sibling `[kjFor]` panels. */
13582
13628
  get controller() {
13583
13629
  return this._overlayTrigger.controller;
@@ -13596,7 +13642,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
13596
13642
  exportAs: 'kjConfirmPopupTrigger',
13597
13643
  hostDirectives: [KjPopoverTrigger],
13598
13644
  }]
13599
- }] });
13645
+ }], ctorParameters: () => [] });
13600
13646
 
13601
13647
  /**
13602
13648
  * The floating confirm popup panel. Composes `kj-popover-content` (a
@@ -15550,12 +15596,20 @@ class KjCommandPalette {
15550
15596
  }
15551
15597
  });
15552
15598
  });
15553
- // When items register asynchronously (same query), pick first if nothing highlighted.
15599
+ // Re-seed when the visible set changes under the active item: items may
15600
+ // register asynchronously (nothing highlighted yet), and with
15601
+ // `[kjShouldFilter]="false"` the consumer re-renders the list AFTER the
15602
+ // query effect above ran — so that effect highlighted an item that is
15603
+ // already gone. Without the `some()` check the palette kept a stale value,
15604
+ // no row was active, and Enter did nothing (reproducible by typing fast
15605
+ // or pasting a query).
15554
15606
  effect(() => {
15555
15607
  const visible = this.visibleItems();
15556
15608
  const active = this.kjValue();
15557
15609
  const autoFirst = this.kjAutoActivateFirst();
15558
- if (!autoFirst || visible.length === 0 || active !== null)
15610
+ if (!autoFirst || visible.length === 0)
15611
+ return;
15612
+ if (active !== null && visible.some(i => i.value() === active))
15559
15613
  return;
15560
15614
  untracked(() => {
15561
15615
  const nav = this._nav();