@kouji-ui/core 0.13.0 → 0.13.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.
|
@@ -1141,7 +1141,7 @@ class KjOverlayStack {
|
|
|
1141
1141
|
pressEntry = null;
|
|
1142
1142
|
register(id, opts) {
|
|
1143
1143
|
if (!this.isBrowser) {
|
|
1144
|
-
return { unregister: () => { }, isTopmost: computed(() => false), zIndex: this.configuredBase };
|
|
1144
|
+
return { unregister: () => { }, release: () => { }, isTopmost: computed(() => false), zIndex: this.configuredBase };
|
|
1145
1145
|
}
|
|
1146
1146
|
const entry = {
|
|
1147
1147
|
id,
|
|
@@ -1168,6 +1168,12 @@ class KjOverlayStack {
|
|
|
1168
1168
|
}
|
|
1169
1169
|
this.maybeRemoveListeners();
|
|
1170
1170
|
},
|
|
1171
|
+
release: () => {
|
|
1172
|
+
if (entry.opts.passive || !this._stack().includes(entry))
|
|
1173
|
+
return;
|
|
1174
|
+
entry.opts.passive = true;
|
|
1175
|
+
this._stack.update(s => [...s]);
|
|
1176
|
+
},
|
|
1171
1177
|
isTopmost,
|
|
1172
1178
|
zIndex: entry.zIndex,
|
|
1173
1179
|
};
|
|
@@ -1254,9 +1260,13 @@ class KjOverlayStack {
|
|
|
1254
1260
|
if (e.key !== 'Escape')
|
|
1255
1261
|
return;
|
|
1256
1262
|
const top = this.topmost();
|
|
1257
|
-
if (!top
|
|
1263
|
+
if (!top)
|
|
1258
1264
|
return;
|
|
1259
|
-
|
|
1265
|
+
// Handled (closed, or swallowed by an overlay that must be answered):
|
|
1266
|
+
// app-level Escape listeners must not also close what sits behind it.
|
|
1267
|
+
e.preventDefault();
|
|
1268
|
+
if (top.opts.closeOnEsc)
|
|
1269
|
+
top.opts.onClose('escape');
|
|
1260
1270
|
}
|
|
1261
1271
|
/** Is `target` a press outside `entry`'s content — a candidate dismissal? */
|
|
1262
1272
|
isOutside(entry, target) {
|
|
@@ -2281,6 +2291,13 @@ class KjOverlayController {
|
|
|
2281
2291
|
beginClose() {
|
|
2282
2292
|
if (!this.strategies)
|
|
2283
2293
|
return;
|
|
2294
|
+
// A closing overlay no longer owns Escape / outside presses: one made
|
|
2295
|
+
// during its close animation belongs to what is beneath it. Released
|
|
2296
|
+
// after the current event, so the press that closed it does not also
|
|
2297
|
+
// reach the overlay beneath (its backdrop, its own Escape handler).
|
|
2298
|
+
const handle = this.stackHandle;
|
|
2299
|
+
if (handle)
|
|
2300
|
+
setTimeout(() => handle.release());
|
|
2284
2301
|
this.runTransition('close', () => this.finishClose());
|
|
2285
2302
|
}
|
|
2286
2303
|
/**
|
|
@@ -26999,6 +27016,15 @@ class KjCombobox {
|
|
|
26999
27016
|
/** Filter-aware visible items, exposed for KjListNavigatorConfig. */
|
|
27000
27017
|
visibleItems = computed(() => this.filter.visibleItems(), /* @ts-ignore */
|
|
27001
27018
|
...(ngDevMode ? [{ debugName: "visibleItems" }] : /* istanbul ignore next */ []));
|
|
27019
|
+
/**
|
|
27020
|
+
* Text the user typed since the list last closed or a value was picked —
|
|
27021
|
+
* the only text that filters the list. `kjQuery` also carries the picked
|
|
27022
|
+
* row's name (so the field reads as a picker), and filtering by it would
|
|
27023
|
+
* list nothing but the picked row on every later open.
|
|
27024
|
+
*/
|
|
27025
|
+
_typed = signal('', /* @ts-ignore */
|
|
27026
|
+
...(ngDevMode ? [{ debugName: "_typed" }] : /* istanbul ignore next */ []));
|
|
27027
|
+
filterQuery = this._typed.asReadonly();
|
|
27002
27028
|
/** Public surface read by children. */
|
|
27003
27029
|
query = this.kjQuery.asReadonly();
|
|
27004
27030
|
open = this.controller.isOpen;
|
|
@@ -27053,7 +27079,7 @@ class KjCombobox {
|
|
|
27053
27079
|
constructor() {
|
|
27054
27080
|
this.filter.bind({
|
|
27055
27081
|
items: this.items,
|
|
27056
|
-
query: this.
|
|
27082
|
+
query: this._typed,
|
|
27057
27083
|
filterFn: this.adaptedFilter,
|
|
27058
27084
|
// A windowed wrapper has already filtered the DATA; every rendered
|
|
27059
27085
|
// row is a match by construction.
|
|
@@ -27067,55 +27093,69 @@ class KjCombobox {
|
|
|
27067
27093
|
compareBy: this.compareBy,
|
|
27068
27094
|
});
|
|
27069
27095
|
effect(() => this.kjQueryChange.emit(this.kjQuery()));
|
|
27070
|
-
//
|
|
27071
|
-
// commit it without an explicit ArrowDown press. APG combobox 1.2
|
|
27072
|
-
// discoverability pattern.
|
|
27096
|
+
// Typed text lasts until the list closes (a pick closes it too).
|
|
27073
27097
|
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
|
-
});
|
|
27098
|
+
if (!this.open())
|
|
27099
|
+
untracked(() => this._typed.set(''));
|
|
27083
27100
|
});
|
|
27084
|
-
//
|
|
27101
|
+
// Place the cursor on open, on typing and once the navigator attaches.
|
|
27102
|
+
// Typed text: the first match, so Enter commits it without an ArrowDown
|
|
27103
|
+
// (APG combobox 1.2). No typed text: the picked row when it is listed,
|
|
27104
|
+
// else the first — never the first row while another one is picked, or
|
|
27105
|
+
// two rows read as chosen.
|
|
27085
27106
|
effect(() => {
|
|
27107
|
+
const typed = this._typed();
|
|
27108
|
+
this.open();
|
|
27086
27109
|
const nav = this._nav();
|
|
27087
|
-
if (!
|
|
27088
|
-
return;
|
|
27089
|
-
if (nav.activeId() !== null)
|
|
27110
|
+
if (!this.kjAutoActivateFirst() || !nav || this._virtual() !== null)
|
|
27090
27111
|
return;
|
|
27091
27112
|
untracked(() => {
|
|
27092
27113
|
const visible = this.filter.visibleItems();
|
|
27093
|
-
|
|
27094
|
-
|
|
27114
|
+
const picked = typed === '' ? visible.find(i => this.isPicked(i)) : undefined;
|
|
27115
|
+
nav.setActive((picked ?? visible[0])?.id ?? null);
|
|
27095
27116
|
});
|
|
27096
27117
|
});
|
|
27097
|
-
//
|
|
27098
|
-
//
|
|
27099
|
-
//
|
|
27100
|
-
//
|
|
27101
|
-
// value whose row is scrolled out would
|
|
27118
|
+
// Write the picked row's name into the field when the VALUE changes, or
|
|
27119
|
+
// when that row's text first renders (`label()` falls back to the row's
|
|
27120
|
+
// text after its first render). Never because the user emptied the
|
|
27121
|
+
// field: an empty field lists every option. A windowed wrapper owns this
|
|
27122
|
+
// too: `items()` is a window, so a value whose row is scrolled out would
|
|
27123
|
+
// never be found here.
|
|
27124
|
+
let shown = null;
|
|
27125
|
+
let seeded = false;
|
|
27102
27126
|
effect(() => {
|
|
27103
|
-
const items = this.items();
|
|
27104
27127
|
const v = this.kjValue();
|
|
27105
|
-
if (v === null || v === undefined
|
|
27128
|
+
if (v === null || v === undefined) {
|
|
27129
|
+
shown = null;
|
|
27130
|
+
return;
|
|
27131
|
+
}
|
|
27132
|
+
if (this._virtual() !== null)
|
|
27106
27133
|
return;
|
|
27107
|
-
|
|
27134
|
+
const label = this.items().find(i => i.value() === v)?.label();
|
|
27135
|
+
if (!label)
|
|
27108
27136
|
return;
|
|
27137
|
+
if (shown && Object.is(shown.value, v) && shown.label === label)
|
|
27138
|
+
return;
|
|
27139
|
+
shown = { value: v, label };
|
|
27140
|
+
const first = !seeded;
|
|
27141
|
+
seeded = true;
|
|
27109
27142
|
untracked(() => {
|
|
27110
|
-
|
|
27111
|
-
if (
|
|
27112
|
-
|
|
27143
|
+
// A consumer-bound `kjQuery` beats the preset value's name on first render.
|
|
27144
|
+
if (first && this.kjQuery() !== '')
|
|
27145
|
+
return;
|
|
27146
|
+
this.kjQuery.set(label);
|
|
27113
27147
|
});
|
|
27114
27148
|
});
|
|
27115
27149
|
}
|
|
27150
|
+
isPicked(item) {
|
|
27151
|
+
const v = item.value();
|
|
27152
|
+
return v !== undefined && this._selection.isSelected(v);
|
|
27153
|
+
}
|
|
27154
|
+
/** Text typed into the input: sets the query, filters the list by it and opens it. */
|
|
27116
27155
|
setQuery(value) {
|
|
27117
27156
|
if (this.kjQuery() !== value)
|
|
27118
27157
|
this.kjQuery.set(value);
|
|
27158
|
+
this._typed.set(value);
|
|
27119
27159
|
this.controller.open();
|
|
27120
27160
|
}
|
|
27121
27161
|
/**
|
|
@@ -27349,6 +27389,17 @@ class KjComboboxListbox {
|
|
|
27349
27389
|
offset: this.kjOffset,
|
|
27350
27390
|
matchTriggerWidth: 'min',
|
|
27351
27391
|
});
|
|
27392
|
+
// Keep the cursor's row in view: `aria-activedescendant` never moves DOM
|
|
27393
|
+
// focus, so nothing else scrolls the list to it (a picked row far down the
|
|
27394
|
+
// list on open, ArrowDown past the last visible row).
|
|
27395
|
+
const doc = inject(DOCUMENT);
|
|
27396
|
+
const injector = inject(Injector);
|
|
27397
|
+
effect(() => {
|
|
27398
|
+
const id = this.ctx.activeId();
|
|
27399
|
+
if (!id || !this.ctx.open())
|
|
27400
|
+
return;
|
|
27401
|
+
afterNextRender(() => doc.getElementById(id)?.scrollIntoView?.({ block: 'nearest' }), { injector });
|
|
27402
|
+
});
|
|
27352
27403
|
}
|
|
27353
27404
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: KjComboboxListbox, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
27354
27405
|
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: [
|