@kouji-ui/core 0.13.0 → 0.13.1
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.
|
@@ -1254,9 +1254,13 @@ class KjOverlayStack {
|
|
|
1254
1254
|
if (e.key !== 'Escape')
|
|
1255
1255
|
return;
|
|
1256
1256
|
const top = this.topmost();
|
|
1257
|
-
if (!top
|
|
1257
|
+
if (!top)
|
|
1258
1258
|
return;
|
|
1259
|
-
|
|
1259
|
+
// Handled (closed, or swallowed by an overlay that must be answered):
|
|
1260
|
+
// app-level Escape listeners must not also close what sits behind it.
|
|
1261
|
+
e.preventDefault();
|
|
1262
|
+
if (top.opts.closeOnEsc)
|
|
1263
|
+
top.opts.onClose('escape');
|
|
1260
1264
|
}
|
|
1261
1265
|
/** Is `target` a press outside `entry`'s content — a candidate dismissal? */
|
|
1262
1266
|
isOutside(entry, target) {
|
|
@@ -26999,6 +27003,15 @@ class KjCombobox {
|
|
|
26999
27003
|
/** Filter-aware visible items, exposed for KjListNavigatorConfig. */
|
|
27000
27004
|
visibleItems = computed(() => this.filter.visibleItems(), /* @ts-ignore */
|
|
27001
27005
|
...(ngDevMode ? [{ debugName: "visibleItems" }] : /* istanbul ignore next */ []));
|
|
27006
|
+
/**
|
|
27007
|
+
* Text the user typed since the list last closed or a value was picked —
|
|
27008
|
+
* the only text that filters the list. `kjQuery` also carries the picked
|
|
27009
|
+
* row's name (so the field reads as a picker), and filtering by it would
|
|
27010
|
+
* list nothing but the picked row on every later open.
|
|
27011
|
+
*/
|
|
27012
|
+
_typed = signal('', /* @ts-ignore */
|
|
27013
|
+
...(ngDevMode ? [{ debugName: "_typed" }] : /* istanbul ignore next */ []));
|
|
27014
|
+
filterQuery = this._typed.asReadonly();
|
|
27002
27015
|
/** Public surface read by children. */
|
|
27003
27016
|
query = this.kjQuery.asReadonly();
|
|
27004
27017
|
open = this.controller.isOpen;
|
|
@@ -27053,7 +27066,7 @@ class KjCombobox {
|
|
|
27053
27066
|
constructor() {
|
|
27054
27067
|
this.filter.bind({
|
|
27055
27068
|
items: this.items,
|
|
27056
|
-
query: this.
|
|
27069
|
+
query: this._typed,
|
|
27057
27070
|
filterFn: this.adaptedFilter,
|
|
27058
27071
|
// A windowed wrapper has already filtered the DATA; every rendered
|
|
27059
27072
|
// row is a match by construction.
|
|
@@ -27067,55 +27080,69 @@ class KjCombobox {
|
|
|
27067
27080
|
compareBy: this.compareBy,
|
|
27068
27081
|
});
|
|
27069
27082
|
effect(() => this.kjQueryChange.emit(this.kjQuery()));
|
|
27070
|
-
//
|
|
27071
|
-
// commit it without an explicit ArrowDown press. APG combobox 1.2
|
|
27072
|
-
// discoverability pattern.
|
|
27083
|
+
// Typed text lasts until the list closes (a pick closes it too).
|
|
27073
27084
|
effect(() => {
|
|
27074
|
-
this.
|
|
27075
|
-
|
|
27076
|
-
const nav = this._nav();
|
|
27077
|
-
if (!autoFirst || !nav || this._virtual() !== null)
|
|
27078
|
-
return;
|
|
27079
|
-
untracked(() => {
|
|
27080
|
-
const visible = this.filter.visibleItems();
|
|
27081
|
-
nav.setActive(visible.length ? visible[0].id : null);
|
|
27082
|
-
});
|
|
27085
|
+
if (!this.open())
|
|
27086
|
+
untracked(() => this._typed.set(''));
|
|
27083
27087
|
});
|
|
27084
|
-
//
|
|
27088
|
+
// Place the cursor on open, on typing and once the navigator attaches.
|
|
27089
|
+
// Typed text: the first match, so Enter commits it without an ArrowDown
|
|
27090
|
+
// (APG combobox 1.2). No typed text: the picked row when it is listed,
|
|
27091
|
+
// else the first — never the first row while another one is picked, or
|
|
27092
|
+
// two rows read as chosen.
|
|
27085
27093
|
effect(() => {
|
|
27094
|
+
const typed = this._typed();
|
|
27095
|
+
this.open();
|
|
27086
27096
|
const nav = this._nav();
|
|
27087
|
-
if (!
|
|
27088
|
-
return;
|
|
27089
|
-
if (nav.activeId() !== null)
|
|
27097
|
+
if (!this.kjAutoActivateFirst() || !nav || this._virtual() !== null)
|
|
27090
27098
|
return;
|
|
27091
27099
|
untracked(() => {
|
|
27092
27100
|
const visible = this.filter.visibleItems();
|
|
27093
|
-
|
|
27094
|
-
|
|
27101
|
+
const picked = typed === '' ? visible.find(i => this.isPicked(i)) : undefined;
|
|
27102
|
+
nav.setActive((picked ?? visible[0])?.id ?? null);
|
|
27095
27103
|
});
|
|
27096
27104
|
});
|
|
27097
|
-
//
|
|
27098
|
-
//
|
|
27099
|
-
//
|
|
27100
|
-
//
|
|
27101
|
-
// value whose row is scrolled out would
|
|
27105
|
+
// Write the picked row's name into the field when the VALUE changes, or
|
|
27106
|
+
// when that row's text first renders (`label()` falls back to the row's
|
|
27107
|
+
// text after its first render). Never because the user emptied the
|
|
27108
|
+
// field: an empty field lists every option. A windowed wrapper owns this
|
|
27109
|
+
// too: `items()` is a window, so a value whose row is scrolled out would
|
|
27110
|
+
// never be found here.
|
|
27111
|
+
let shown = null;
|
|
27112
|
+
let seeded = false;
|
|
27102
27113
|
effect(() => {
|
|
27103
|
-
const items = this.items();
|
|
27104
27114
|
const v = this.kjValue();
|
|
27105
|
-
if (v === null || v === undefined
|
|
27115
|
+
if (v === null || v === undefined) {
|
|
27116
|
+
shown = null;
|
|
27117
|
+
return;
|
|
27118
|
+
}
|
|
27119
|
+
if (this._virtual() !== null)
|
|
27106
27120
|
return;
|
|
27107
|
-
|
|
27121
|
+
const label = this.items().find(i => i.value() === v)?.label();
|
|
27122
|
+
if (!label)
|
|
27108
27123
|
return;
|
|
27124
|
+
if (shown && Object.is(shown.value, v) && shown.label === label)
|
|
27125
|
+
return;
|
|
27126
|
+
shown = { value: v, label };
|
|
27127
|
+
const first = !seeded;
|
|
27128
|
+
seeded = true;
|
|
27109
27129
|
untracked(() => {
|
|
27110
|
-
|
|
27111
|
-
if (
|
|
27112
|
-
|
|
27130
|
+
// A consumer-bound `kjQuery` beats the preset value's name on first render.
|
|
27131
|
+
if (first && this.kjQuery() !== '')
|
|
27132
|
+
return;
|
|
27133
|
+
this.kjQuery.set(label);
|
|
27113
27134
|
});
|
|
27114
27135
|
});
|
|
27115
27136
|
}
|
|
27137
|
+
isPicked(item) {
|
|
27138
|
+
const v = item.value();
|
|
27139
|
+
return v !== undefined && this._selection.isSelected(v);
|
|
27140
|
+
}
|
|
27141
|
+
/** Text typed into the input: sets the query, filters the list by it and opens it. */
|
|
27116
27142
|
setQuery(value) {
|
|
27117
27143
|
if (this.kjQuery() !== value)
|
|
27118
27144
|
this.kjQuery.set(value);
|
|
27145
|
+
this._typed.set(value);
|
|
27119
27146
|
this.controller.open();
|
|
27120
27147
|
}
|
|
27121
27148
|
/**
|
|
@@ -27349,6 +27376,17 @@ class KjComboboxListbox {
|
|
|
27349
27376
|
offset: this.kjOffset,
|
|
27350
27377
|
matchTriggerWidth: 'min',
|
|
27351
27378
|
});
|
|
27379
|
+
// Keep the cursor's row in view: `aria-activedescendant` never moves DOM
|
|
27380
|
+
// focus, so nothing else scrolls the list to it (a picked row far down the
|
|
27381
|
+
// list on open, ArrowDown past the last visible row).
|
|
27382
|
+
const doc = inject(DOCUMENT);
|
|
27383
|
+
const injector = inject(Injector);
|
|
27384
|
+
effect(() => {
|
|
27385
|
+
const id = this.ctx.activeId();
|
|
27386
|
+
if (!id || !this.ctx.open())
|
|
27387
|
+
return;
|
|
27388
|
+
afterNextRender(() => doc.getElementById(id)?.scrollIntoView?.({ block: 'nearest' }), { injector });
|
|
27389
|
+
});
|
|
27352
27390
|
}
|
|
27353
27391
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjComboboxListbox, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
27354
27392
|
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.5", type: KjComboboxListbox, isStandalone: true, selector: "[kjComboboxListbox]", 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: { listeners: { "mousedown": "$event.preventDefault()" } }, providers: [
|