@kouji-ui/core 0.6.1 → 0.6.3

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.
@@ -2438,10 +2438,22 @@ class KjListNavigator {
2438
2438
  this.moveBy(-this.kjPageSize());
2439
2439
  return;
2440
2440
  case 'Enter':
2441
- case ' ':
2442
2441
  // Only preventDefault when we actually activate. Lets consumers
2443
- // (e.g. combobox free-text Enter) fall through when nothing is
2444
- // active, and lets Space type a literal space in a combobox input.
2442
+ // (e.g. combobox free-text Enter) fall through when nothing is active.
2443
+ if (this.activeItem()) {
2444
+ e.preventDefault();
2445
+ this.activateCurrent();
2446
+ }
2447
+ return;
2448
+ case ' ':
2449
+ // Space activates a LIST — but in a text field it is a character the
2450
+ // user is typing, and the field owns it. A command palette highlights
2451
+ // a row for every query, so treating Space as activation there made
2452
+ // multi-word queries impossible: the space ran the highlighted command
2453
+ // instead of reaching the input. Enter remains the activation key from
2454
+ // a text field.
2455
+ if (isTextEntry(e.target))
2456
+ return;
2445
2457
  if (this.activeItem()) {
2446
2458
  e.preventDefault();
2447
2459
  this.activateCurrent();
@@ -2488,6 +2500,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
2488
2500
  },
2489
2501
  }]
2490
2502
  }], ctorParameters: () => [], propDecorators: { kjOrientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjOrientation", required: false }] }], kjWrap: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjWrap", required: false }] }], kjPageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjPageSize", required: false }] }], kjActivateOnHover: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjActivateOnHover", required: false }] }], kjFocusMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjFocusMode", required: false }] }], kjActiveChange: [{ type: i0.Output, args: ["kjActiveChange"] }] } });
2503
+ /** Types of `<input>` that hold text the user types (Space is a character). */
2504
+ const NON_TEXT_INPUT_TYPES = new Set([
2505
+ 'button',
2506
+ 'checkbox',
2507
+ 'color',
2508
+ 'file',
2509
+ 'image',
2510
+ 'radio',
2511
+ 'range',
2512
+ 'reset',
2513
+ 'submit',
2514
+ ]);
2515
+ /** Whether a key event landed in a field where Space types a character. */
2516
+ function isTextEntry(target) {
2517
+ const el = target;
2518
+ if (!el || typeof el.tagName !== 'string')
2519
+ return false;
2520
+ if (el.isContentEditable)
2521
+ return true;
2522
+ if (el.tagName === 'TEXTAREA')
2523
+ return true;
2524
+ if (el.tagName !== 'INPUT')
2525
+ return false;
2526
+ const type = el.type?.toLowerCase() ?? 'text';
2527
+ return !NON_TEXT_INPUT_TYPES.has(type);
2528
+ }
2491
2529
 
2492
2530
  // packages/core/src/primitives/list/selection.ts
2493
2531
  /**
@@ -13522,8 +13560,23 @@ function nextConfirmPopupMessageId() {
13522
13560
  * @doc-category Core/Overlay
13523
13561
  */
13524
13562
  class KjConfirmPopup {
13525
- /** The overlay controller provided by the trigger directive on the same element. */
13526
- controller = inject(KjOverlayController, { optional: true });
13563
+ /** The overlay controller, when the trigger sits on this very element. */
13564
+ selfController = inject(KjOverlayController, { optional: true });
13565
+ /**
13566
+ * The controller of a nested `[kjConfirmPopupTrigger]`. The documented
13567
+ * composition puts the trigger on a CHILD of `[kjConfirmPopup]` (see every
13568
+ * example), so the controller is NOT in this element's injector — without
13569
+ * this registration `close()` bailed out and `(kjConfirmed)` never fired.
13570
+ */
13571
+ registered = signal(null, /* @ts-ignore */
13572
+ ...(ngDevMode ? [{ debugName: "registered" }] : /* istanbul ignore next */ []));
13573
+ /** @internal — called by a nested `[kjConfirmPopupTrigger]`. */
13574
+ _registerController(c) {
13575
+ this.registered.set(c);
13576
+ }
13577
+ controllerOf() {
13578
+ return this.selfController ?? this.registered();
13579
+ }
13527
13580
  /** When `true`, the confirm action renders with destructive intent. */
13528
13581
  kjDestructive = input(false, { ...(ngDevMode ? { debugName: "kjDestructive" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
13529
13582
  /** Where to place initial focus. Default `'cancel'`. */
@@ -13537,10 +13590,8 @@ class KjConfirmPopup {
13537
13590
  kjResult = output();
13538
13591
  // ── KjConfirmPopupContext ──────────────────────────────────────────
13539
13592
  /** Open state forwarded from the underlying controller. */
13540
- open = (() => {
13541
- const ctrl = this.controller;
13542
- return ctrl ? ctrl.isOpen : (() => false);
13543
- })();
13593
+ open = computed(() => this.controllerOf()?.isOpen() ?? false, /* @ts-ignore */
13594
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
13544
13595
  /** Whether the confirm action should render destructively. */
13545
13596
  destructive = this.kjDestructive;
13546
13597
  /** Resolved default focus target. */
@@ -13552,7 +13603,7 @@ class KjConfirmPopup {
13552
13603
  constructor() {
13553
13604
  // Bridge the controller's open signal into the resolution contract.
13554
13605
  effect(() => {
13555
- const isOpen = this.controller?.isOpen() ?? false;
13606
+ const isOpen = this.controllerOf()?.isOpen() ?? false;
13556
13607
  if (isOpen && !this.wasOpen) {
13557
13608
  this.resolved = false;
13558
13609
  }
@@ -13568,7 +13619,8 @@ class KjConfirmPopup {
13568
13619
  * Close the popup with the given result. `true` confirms, `false` cancels.
13569
13620
  */
13570
13621
  close(result) {
13571
- if (!this.controller || !this.controller.isOpen())
13622
+ const controller = this.controllerOf();
13623
+ if (!controller || !controller.isOpen())
13572
13624
  return;
13573
13625
  this.resolved = true;
13574
13626
  if (result)
@@ -13576,7 +13628,7 @@ class KjConfirmPopup {
13576
13628
  else
13577
13629
  this.kjCancelled.emit();
13578
13630
  this.kjResult.emit(result);
13579
- this.controller.close('programmatic');
13631
+ controller.close('programmatic');
13580
13632
  }
13581
13633
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjConfirmPopup, deps: [], target: i0.ɵɵFactoryTarget.Directive });
13582
13634
  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: [
@@ -13604,6 +13656,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
13604
13656
  */
13605
13657
  class KjConfirmPopupTrigger {
13606
13658
  _overlayTrigger = inject(KjOverlayTrigger, { self: true });
13659
+ constructor() {
13660
+ // The trigger normally sits on a CHILD of `[kjConfirmPopup]`, so the root
13661
+ // cannot see this controller through its own injector. Hand it over, or
13662
+ // the confirm/cancel slots resolve nothing.
13663
+ inject(KJ_CONFIRM_POPUP, { optional: true })?._registerController?.(this._overlayTrigger.controller);
13664
+ }
13607
13665
  /** Forwarded controller for sibling `[kjFor]` panels. */
13608
13666
  get controller() {
13609
13667
  return this._overlayTrigger.controller;
@@ -13622,7 +13680,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
13622
13680
  exportAs: 'kjConfirmPopupTrigger',
13623
13681
  hostDirectives: [KjPopoverTrigger],
13624
13682
  }]
13625
- }] });
13683
+ }], ctorParameters: () => [] });
13626
13684
 
13627
13685
  /**
13628
13686
  * The floating confirm popup panel. Composes `kj-popover-content` (a
@@ -15576,12 +15634,20 @@ class KjCommandPalette {
15576
15634
  }
15577
15635
  });
15578
15636
  });
15579
- // When items register asynchronously (same query), pick first if nothing highlighted.
15637
+ // Re-seed when the visible set changes under the active item: items may
15638
+ // register asynchronously (nothing highlighted yet), and with
15639
+ // `[kjShouldFilter]="false"` the consumer re-renders the list AFTER the
15640
+ // query effect above ran — so that effect highlighted an item that is
15641
+ // already gone. Without the `some()` check the palette kept a stale value,
15642
+ // no row was active, and Enter did nothing (reproducible by typing fast
15643
+ // or pasting a query).
15580
15644
  effect(() => {
15581
15645
  const visible = this.visibleItems();
15582
15646
  const active = this.kjValue();
15583
15647
  const autoFirst = this.kjAutoActivateFirst();
15584
- if (!autoFirst || visible.length === 0 || active !== null)
15648
+ if (!autoFirst || visible.length === 0)
15649
+ return;
15650
+ if (active !== null && visible.some(i => i.value() === active))
15585
15651
  return;
15586
15652
  untracked(() => {
15587
15653
  const nav = this._nav();