@kouji-ui/core 0.11.0 → 0.12.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.
@@ -5330,6 +5330,12 @@ class KjListNavigator {
5330
5330
  ...(ngDevMode ? [{ debugName: "kjPageSize" }] : /* istanbul ignore next */ []));
5331
5331
  /** Activate item on pointer hover. Default: `false`. */
5332
5332
  kjActivateOnHover = input(false, { ...(ngDevMode ? { debugName: "kjActivateOnHover" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
5333
+ /**
5334
+ * Publish `aria-activedescendant` on this host in `'activedescendant'`
5335
+ * mode. Turn off when another element keeps DOM focus and publishes
5336
+ * `activeId` itself (a search field above the list). Default: `true`.
5337
+ */
5338
+ kjHostActiveDescendant = input(true, { ...(ngDevMode ? { debugName: "kjHostActiveDescendant" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
5333
5339
  /**
5334
5340
  * Focus model. `'activedescendant'` (default) keeps DOM focus on the
5335
5341
  * navigator host and signals the active option via
@@ -5705,7 +5711,7 @@ class KjListNavigator {
5705
5711
  }
5706
5712
  }
5707
5713
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjListNavigator, deps: [], target: i0.ɵɵFactoryTarget.Directive });
5708
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.5", type: KjListNavigator, isStandalone: true, selector: "[kjListNavigator]", inputs: { kjOrientation: { classPropertyName: "kjOrientation", publicName: "kjOrientation", isSignal: true, isRequired: false, transformFunction: null }, kjWrap: { classPropertyName: "kjWrap", publicName: "kjWrap", isSignal: true, isRequired: false, transformFunction: null }, kjPageSize: { classPropertyName: "kjPageSize", publicName: "kjPageSize", isSignal: true, isRequired: false, transformFunction: null }, kjActivateOnHover: { classPropertyName: "kjActivateOnHover", publicName: "kjActivateOnHover", isSignal: true, isRequired: false, transformFunction: null }, kjFocusMode: { classPropertyName: "kjFocusMode", publicName: "kjFocusMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { kjActiveChange: "kjActiveChange" }, host: { listeners: { "keydown": "_onKeydown($event)", "focusin": "_onFocusin($event)" }, properties: { "attr.aria-activedescendant": "kjFocusMode() === \"activedescendant\" ? activeId() : null" } }, providers: [
5714
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.5", type: KjListNavigator, isStandalone: true, selector: "[kjListNavigator]", inputs: { kjOrientation: { classPropertyName: "kjOrientation", publicName: "kjOrientation", isSignal: true, isRequired: false, transformFunction: null }, kjWrap: { classPropertyName: "kjWrap", publicName: "kjWrap", isSignal: true, isRequired: false, transformFunction: null }, kjPageSize: { classPropertyName: "kjPageSize", publicName: "kjPageSize", isSignal: true, isRequired: false, transformFunction: null }, kjActivateOnHover: { classPropertyName: "kjActivateOnHover", publicName: "kjActivateOnHover", isSignal: true, isRequired: false, transformFunction: null }, kjHostActiveDescendant: { classPropertyName: "kjHostActiveDescendant", publicName: "kjHostActiveDescendant", isSignal: true, isRequired: false, transformFunction: null }, kjFocusMode: { classPropertyName: "kjFocusMode", publicName: "kjFocusMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { kjActiveChange: "kjActiveChange" }, host: { listeners: { "keydown": "_onKeydown($event)", "focusin": "_onFocusin($event)" }, properties: { "attr.aria-activedescendant": "kjFocusMode() === \"activedescendant\" && kjHostActiveDescendant() ? activeId() : null" } }, providers: [
5709
5715
  {
5710
5716
  provide: KJ_LIST_FOCUS_MODE,
5711
5717
  // The factory closes over the directive instance via `inject`,
@@ -5734,12 +5740,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
5734
5740
  // really holds focus — must not publish it as well. A container that pins
5735
5741
  // `KJ_LIST_FOCUS_MODE_DEFAULT` to `'roving'` (menu, menubar, tree-select)
5736
5742
  // therefore drops the attribute entirely.
5737
- '[attr.aria-activedescendant]': 'kjFocusMode() === "activedescendant" ? activeId() : null',
5743
+ '[attr.aria-activedescendant]': 'kjFocusMode() === "activedescendant" && kjHostActiveDescendant() ? activeId() : null',
5738
5744
  '(keydown)': '_onKeydown($event)',
5739
5745
  '(focusin)': '_onFocusin($event)',
5740
5746
  },
5741
5747
  }]
5742
- }], 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"] }] } });
5748
+ }], 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 }] }], kjHostActiveDescendant: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjHostActiveDescendant", required: false }] }], kjFocusMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjFocusMode", required: false }] }], kjActiveChange: [{ type: i0.Output, args: ["kjActiveChange"] }] } });
5743
5749
  /** Types of `<input>` that hold text the user types (Space is a character). */
5744
5750
  const NON_TEXT_INPUT_TYPES = new Set([
5745
5751
  'button',
@@ -8807,6 +8813,10 @@ function bindPresets(configToken) {
8807
8813
  provide: KJ_VARIANT_PRESET,
8808
8814
  useFactory: () => {
8809
8815
  const c = inject(configToken);
8816
+ // A config without a variant list (e.g. one that only sizes) keeps the
8817
+ // enclosing preset, so it never hands descendants `values: undefined`.
8818
+ if (!c.variants)
8819
+ return inject(KJ_VARIANT_PRESET, { skipSelf: true });
8810
8820
  return { values: c.variants, default: c.defaults.variant };
8811
8821
  },
8812
8822
  },
@@ -8814,6 +8824,10 @@ function bindPresets(configToken) {
8814
8824
  provide: KJ_SIZE_PRESET,
8815
8825
  useFactory: () => {
8816
8826
  const c = inject(configToken);
8827
+ // Same for sizes: `KjTabs` binds a variant-only config, and a `KjSize`
8828
+ // descendant (a `kj-kbd` in a tab panel) must see the enclosing preset.
8829
+ if (!c.sizes)
8830
+ return inject(KJ_SIZE_PRESET, { skipSelf: true });
8817
8831
  return { values: c.sizes, default: c.defaults.size };
8818
8832
  },
8819
8833
  },
@@ -26401,6 +26415,9 @@ class KjSelect {
26401
26415
  ...(ngDevMode ? [{ debugName: "_multiple" }] : /* istanbul ignore next */ []));
26402
26416
  /** Whether multi-select is enabled. */
26403
26417
  multiple = this._multiple.asReadonly();
26418
+ /** @internal — written by `KjSelectContent`'s `kjSearchable` input. */
26419
+ _searchable = signal(false, /* @ts-ignore */
26420
+ ...(ngDevMode ? [{ debugName: "_searchable" }] : /* istanbul ignore next */ []));
26404
26421
  /** @internal — set by `KjSelectTrigger` at construction time. */
26405
26422
  _triggerEl = signal(null, /* @ts-ignore */
26406
26423
  ...(ngDevMode ? [{ debugName: "_triggerEl" }] : /* istanbul ignore next */ []));
@@ -26429,6 +26446,13 @@ class KjSelect {
26429
26446
  * not to this one.
26430
26447
  */
26431
26448
  items = ownListItems(this, this.allItems);
26449
+ /**
26450
+ * Implements `KjListNavigatorConfig.visibleItems`: the items a wrapper has
26451
+ * not hidden through `KjListItem.setVisible` (a search filter, a folded
26452
+ * group). The navigator and type-ahead walk only these.
26453
+ */
26454
+ visibleItems = computed(() => this.items().filter(i => i.visible()), /* @ts-ignore */
26455
+ ...(ngDevMode ? [{ debugName: "visibleItems" }] : /* istanbul ignore next */ []));
26432
26456
  /** Implements `KjListNavigatorConfig.mode`. */
26433
26457
  mode = computed(() => this._multiple() ? 'multi' : 'single', /* @ts-ignore */
26434
26458
  ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
@@ -26556,7 +26580,7 @@ class KjSelectTrigger {
26556
26580
  this._overlayTrigger.attachPanel(panel);
26557
26581
  }
26558
26582
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjSelectTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive });
26559
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.5", type: KjSelectTrigger, isStandalone: true, selector: "[kjSelectTrigger]", inputs: { kjMultiple: { classPropertyName: "kjMultiple", publicName: "kjMultiple", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "keydown": "onKeydown($event)" } }, providers: [
26583
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.5", type: KjSelectTrigger, isStandalone: true, selector: "[kjSelectTrigger]", inputs: { kjMultiple: { classPropertyName: "kjMultiple", publicName: "kjMultiple", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "keydown": "onKeydown($event)" }, properties: { "attr.aria-haspopup": "select?._searchable() ? \"dialog\" : \"listbox\"" } }, providers: [
26560
26584
  {
26561
26585
  provide: KJ_OVERLAY_TRIGGER_EVENT_STRATEGY,
26562
26586
  useFactory: () => listboxClickTrigger(),
@@ -26596,6 +26620,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
26596
26620
  ],
26597
26621
  host: {
26598
26622
  '(keydown)': 'onKeydown($event)',
26623
+ // Runs after the composed `KjOverlayTrigger`'s binding, so it wins: a
26624
+ // searchable panel is a dialog (search field + listbox), not a listbox.
26625
+ '[attr.aria-haspopup]': 'select?._searchable() ? "dialog" : "listbox"',
26599
26626
  },
26600
26627
  }]
26601
26628
  }], ctorParameters: () => [], propDecorators: { kjMultiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjMultiple", required: false }] }] } });
@@ -26752,7 +26779,15 @@ class KjSelectContent {
26752
26779
  ...(ngDevMode ? [{ debugName: "kjAlign" }] : /* istanbul ignore next */ []));
26753
26780
  /** Pixel offset between trigger and panel. */
26754
26781
  kjOffset = input(4, { ...(ngDevMode ? { debugName: "kjOffset" } : /* istanbul ignore next */ {}), transform: pxOffset(4) });
26782
+ /**
26783
+ * The panel holds a search field above an inner listbox the consumer
26784
+ * renders: the panel becomes `role="dialog"` and the trigger advertises
26785
+ * `aria-haspopup="dialog"`. Defaults to `false`.
26786
+ */
26787
+ kjSearchable = input(false, { ...(ngDevMode ? { debugName: "kjSearchable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
26755
26788
  constructor() {
26789
+ const select = inject(KJ_SELECT, { optional: true });
26790
+ effect(() => select?._searchable.set(this.kjSearchable()));
26756
26791
  injectAnchoredPosition({
26757
26792
  side: this.kjSide,
26758
26793
  align: this.kjAlign,
@@ -26761,7 +26796,7 @@ class KjSelectContent {
26761
26796
  });
26762
26797
  }
26763
26798
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjSelectContent, deps: [], target: i0.ɵɵFactoryTarget.Component });
26764
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.5", type: KjSelectContent, isStandalone: true, selector: "kj-select-content", inputs: { kjSide: { classPropertyName: "kjSide", publicName: "kjSide", isSignal: true, isRequired: false, transformFunction: null }, kjAlign: { classPropertyName: "kjAlign", publicName: "kjAlign", isSignal: true, isRequired: false, transformFunction: null }, kjOffset: { classPropertyName: "kjOffset", publicName: "kjOffset", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.aria-multiselectable": "selection.mode() === \"multi\" ? \"true\" : null" } }, providers: [
26799
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.5", type: KjSelectContent, isStandalone: true, selector: "kj-select-content", inputs: { kjSide: { classPropertyName: "kjSide", publicName: "kjSide", isSignal: true, isRequired: false, transformFunction: null }, kjAlign: { classPropertyName: "kjAlign", publicName: "kjAlign", isSignal: true, isRequired: false, transformFunction: null }, kjOffset: { classPropertyName: "kjOffset", publicName: "kjOffset", isSignal: true, isRequired: false, transformFunction: null }, kjSearchable: { classPropertyName: "kjSearchable", publicName: "kjSearchable", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.role": "kjSearchable() ? \"dialog\" : \"listbox\"", "attr.aria-multiselectable": "!kjSearchable() && selection.mode() === \"multi\" ? \"true\" : null" } }, providers: [
26765
26800
  { provide: KJ_OVERLAY_PANEL_ROLE, useValue: 'listbox' },
26766
26801
  { provide: KJ_OVERLAY_MOUNT_STRATEGY, useFactory: () => bodyPortal() },
26767
26802
  { provide: KJ_OVERLAY_POSITION_STRATEGY, useFactory: () => anchoredTo() },
@@ -26769,7 +26804,7 @@ class KjSelectContent {
26769
26804
  // what screen readers announce, so no `aria-activedescendant` is needed
26770
26805
  // on a panel that never holds focus itself.
26771
26806
  { provide: KJ_LIST_FOCUS_MODE_DEFAULT, useValue: 'roving' },
26772
- ], hostDirectives: [{ directive: KjOverlayPanel, inputs: ["kjFor", "kjFor"] }, { directive: KjListNavigator }, { directive: KjListPanelFocus }], ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
26807
+ ], hostDirectives: [{ directive: KjOverlayPanel, inputs: ["kjFor", "kjFor"] }, { directive: KjListNavigator, inputs: ["kjFocusMode", "kjFocusMode", "kjHostActiveDescendant", "kjHostActiveDescendant"] }, { directive: KjListPanelFocus }], ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
26773
26808
  }
26774
26809
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjSelectContent, decorators: [{
26775
26810
  type: Component,
@@ -26778,7 +26813,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
26778
26813
  standalone: true,
26779
26814
  hostDirectives: [
26780
26815
  { directive: KjOverlayPanel, inputs: ['kjFor'] },
26781
- KjListNavigator,
26816
+ // `kjFocusMode` is forwarded so a wrapper that keeps DOM focus in a
26817
+ // search field can switch the panel to `aria-activedescendant`.
26818
+ { directive: KjListNavigator, inputs: ['kjFocusMode', 'kjHostActiveDescendant'] },
26782
26819
  KjListPanelFocus,
26783
26820
  ],
26784
26821
  providers: [
@@ -26791,13 +26828,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
26791
26828
  { provide: KJ_LIST_FOCUS_MODE_DEFAULT, useValue: 'roving' },
26792
26829
  ],
26793
26830
  host: {
26794
- '[attr.aria-multiselectable]': 'selection.mode() === "multi" ? "true" : null',
26831
+ // With `kjSearchable` the panel is a `dialog` holding a search combobox
26832
+ // and an inner `listbox` the wrapper renders, so the listbox-only
26833
+ // attributes move to that inner element. These run after the composed
26834
+ // panel's / navigator's own bindings, so they win.
26835
+ '[attr.role]': 'kjSearchable() ? "dialog" : "listbox"',
26836
+ '[attr.aria-multiselectable]': '!kjSearchable() && selection.mode() === "multi" ? "true" : null',
26795
26837
  },
26796
26838
  changeDetection: ChangeDetectionStrategy.OnPush,
26797
26839
  encapsulation: ViewEncapsulation.None,
26798
26840
  template: `<ng-content />`,
26799
26841
  }]
26800
- }], ctorParameters: () => [], propDecorators: { kjSide: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjSide", required: false }] }], kjAlign: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjAlign", required: false }] }], kjOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjOffset", required: false }] }] } });
26842
+ }], ctorParameters: () => [], propDecorators: { kjSide: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjSide", required: false }] }], kjAlign: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjAlign", required: false }] }], kjOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjOffset", required: false }] }], kjSearchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjSearchable", required: false }] }] } });
26801
26843
 
26802
26844
  /**
26803
26845
  * Individual option inside a `<kj-select-content>`. Public inputs