@kouji-ui/core 0.12.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) {
|
|
@@ -26445,7 +26449,19 @@ class KjSelect {
|
|
|
26445
26449
|
* inside a palette, a menu inside a select) answer to that composite,
|
|
26446
26450
|
* not to this one.
|
|
26447
26451
|
*/
|
|
26448
|
-
|
|
26452
|
+
_viewItems = signal(null, /* @ts-ignore */
|
|
26453
|
+
...(ngDevMode ? [{ debugName: "_viewItems" }] : /* istanbul ignore next */ []));
|
|
26454
|
+
viewItems = computed(() => this._viewItems()?.() ?? null, /* @ts-ignore */
|
|
26455
|
+
...(ngDevMode ? [{ debugName: "viewItems" }] : /* istanbul ignore next */ []));
|
|
26456
|
+
/**
|
|
26457
|
+
* @internal Register rows a wrapper stamps in its OWN view
|
|
26458
|
+
* (`<kj-select [kjOptions]>`). A content query never crosses into a
|
|
26459
|
+
* component's view, so those rows join `items()` only through here.
|
|
26460
|
+
*/
|
|
26461
|
+
_setViewItems(items) {
|
|
26462
|
+
this._viewItems.set(items);
|
|
26463
|
+
}
|
|
26464
|
+
items = ownListItems(this, this.allItems, this.viewItems);
|
|
26449
26465
|
/**
|
|
26450
26466
|
* Implements `KjListNavigatorConfig.visibleItems`: the items a wrapper has
|
|
26451
26467
|
* not hidden through `KjListItem.setVisible` (a search filter, a folded
|
|
@@ -26987,6 +27003,15 @@ class KjCombobox {
|
|
|
26987
27003
|
/** Filter-aware visible items, exposed for KjListNavigatorConfig. */
|
|
26988
27004
|
visibleItems = computed(() => this.filter.visibleItems(), /* @ts-ignore */
|
|
26989
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();
|
|
26990
27015
|
/** Public surface read by children. */
|
|
26991
27016
|
query = this.kjQuery.asReadonly();
|
|
26992
27017
|
open = this.controller.isOpen;
|
|
@@ -27041,7 +27066,7 @@ class KjCombobox {
|
|
|
27041
27066
|
constructor() {
|
|
27042
27067
|
this.filter.bind({
|
|
27043
27068
|
items: this.items,
|
|
27044
|
-
query: this.
|
|
27069
|
+
query: this._typed,
|
|
27045
27070
|
filterFn: this.adaptedFilter,
|
|
27046
27071
|
// A windowed wrapper has already filtered the DATA; every rendered
|
|
27047
27072
|
// row is a match by construction.
|
|
@@ -27055,55 +27080,69 @@ class KjCombobox {
|
|
|
27055
27080
|
compareBy: this.compareBy,
|
|
27056
27081
|
});
|
|
27057
27082
|
effect(() => this.kjQueryChange.emit(this.kjQuery()));
|
|
27058
|
-
//
|
|
27059
|
-
// commit it without an explicit ArrowDown press. APG combobox 1.2
|
|
27060
|
-
// discoverability pattern.
|
|
27083
|
+
// Typed text lasts until the list closes (a pick closes it too).
|
|
27061
27084
|
effect(() => {
|
|
27062
|
-
this.
|
|
27063
|
-
|
|
27064
|
-
const nav = this._nav();
|
|
27065
|
-
if (!autoFirst || !nav || this._virtual() !== null)
|
|
27066
|
-
return;
|
|
27067
|
-
untracked(() => {
|
|
27068
|
-
const visible = this.filter.visibleItems();
|
|
27069
|
-
nav.setActive(visible.length ? visible[0].id : null);
|
|
27070
|
-
});
|
|
27085
|
+
if (!this.open())
|
|
27086
|
+
untracked(() => this._typed.set(''));
|
|
27071
27087
|
});
|
|
27072
|
-
//
|
|
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.
|
|
27073
27093
|
effect(() => {
|
|
27094
|
+
const typed = this._typed();
|
|
27095
|
+
this.open();
|
|
27074
27096
|
const nav = this._nav();
|
|
27075
|
-
if (!
|
|
27076
|
-
return;
|
|
27077
|
-
if (nav.activeId() !== null)
|
|
27097
|
+
if (!this.kjAutoActivateFirst() || !nav || this._virtual() !== null)
|
|
27078
27098
|
return;
|
|
27079
27099
|
untracked(() => {
|
|
27080
27100
|
const visible = this.filter.visibleItems();
|
|
27081
|
-
|
|
27082
|
-
|
|
27101
|
+
const picked = typed === '' ? visible.find(i => this.isPicked(i)) : undefined;
|
|
27102
|
+
nav.setActive((picked ?? visible[0])?.id ?? null);
|
|
27083
27103
|
});
|
|
27084
27104
|
});
|
|
27085
|
-
//
|
|
27086
|
-
//
|
|
27087
|
-
//
|
|
27088
|
-
//
|
|
27089
|
-
// 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;
|
|
27090
27113
|
effect(() => {
|
|
27091
|
-
const items = this.items();
|
|
27092
27114
|
const v = this.kjValue();
|
|
27093
|
-
if (v === null || v === undefined
|
|
27115
|
+
if (v === null || v === undefined) {
|
|
27116
|
+
shown = null;
|
|
27094
27117
|
return;
|
|
27095
|
-
|
|
27118
|
+
}
|
|
27119
|
+
if (this._virtual() !== null)
|
|
27120
|
+
return;
|
|
27121
|
+
const label = this.items().find(i => i.value() === v)?.label();
|
|
27122
|
+
if (!label)
|
|
27096
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;
|
|
27097
27129
|
untracked(() => {
|
|
27098
|
-
|
|
27099
|
-
if (
|
|
27100
|
-
|
|
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);
|
|
27101
27134
|
});
|
|
27102
27135
|
});
|
|
27103
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. */
|
|
27104
27142
|
setQuery(value) {
|
|
27105
27143
|
if (this.kjQuery() !== value)
|
|
27106
27144
|
this.kjQuery.set(value);
|
|
27145
|
+
this._typed.set(value);
|
|
27107
27146
|
this.controller.open();
|
|
27108
27147
|
}
|
|
27109
27148
|
/**
|
|
@@ -27337,6 +27376,17 @@ class KjComboboxListbox {
|
|
|
27337
27376
|
offset: this.kjOffset,
|
|
27338
27377
|
matchTriggerWidth: 'min',
|
|
27339
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
|
+
});
|
|
27340
27390
|
}
|
|
27341
27391
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjComboboxListbox, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
27342
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: [
|