@kouji-ui/core 0.9.0 → 0.9.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.
- package/fesm2022/kouji-ui-core.mjs +102 -35
- package/fesm2022/kouji-ui-core.mjs.map +1 -1
- package/package.json +1 -1
- package/types/kouji-ui-core.d.ts +12 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, input, booleanAttribute, Directive, PLATFORM_ID, signal, DestroyRef, Injectable, ElementRef, afterNextRender, forwardRef, InjectionToken, Injector, computed, effect,
|
|
2
|
+
import { inject, input, booleanAttribute, Directive, PLATFORM_ID, signal, DestroyRef, Injectable, ElementRef, afterNextRender, untracked, forwardRef, InjectionToken, Injector, computed, effect, viewChild, ViewContainerRef, ViewEncapsulation, ChangeDetectionStrategy, Component, ApplicationRef, EnvironmentInjector, createComponent, model, isSignal, DOCUMENT as DOCUMENT$1, output, numberAttribute, afterRenderEffect, contentChildren, makeEnvironmentProviders, LOCALE_ID, provideEnvironmentInitializer, contentChild, linkedSignal, PendingTasks, TemplateRef, runInInjectionContext, resource } from '@angular/core';
|
|
3
3
|
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
|
4
4
|
import { NG_VALUE_ACCESSOR, NG_VALIDATORS, NgForm, FormGroupDirective, FormGroup, FormArray } from '@angular/forms';
|
|
5
5
|
import { Subject } from 'rxjs';
|
|
@@ -282,10 +282,13 @@ class KjFocusRing {
|
|
|
282
282
|
afterNextRender(() => {
|
|
283
283
|
if (!isPlatformBrowser(this.platformId))
|
|
284
284
|
return;
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
285
|
+
// `focus` / `blur` can fire synchronously while Angular renders — a
|
|
286
|
+
// focused element that a binding disables or removes blurs at once in
|
|
287
|
+
// Chromium — and a raw listener inherits the template's reactive
|
|
288
|
+
// context, where a signal write throws NG0600. `untracked` runs the
|
|
289
|
+
// handlers outside it (Angular's own listeners do the same).
|
|
290
|
+
const onFocus = () => untracked(() => this._focusVisible.set(this.inputModality.modality() === 'keyboard'));
|
|
291
|
+
const onBlur = () => untracked(() => this._focusVisible.set(false));
|
|
289
292
|
const releaseModality = this.inputModality.retain();
|
|
290
293
|
this.el.nativeElement.addEventListener('focus', onFocus);
|
|
291
294
|
this.el.nativeElement.addEventListener('blur', onBlur);
|
|
@@ -3105,7 +3108,9 @@ function releaseInert(el) {
|
|
|
3105
3108
|
* Makes everything behind a modal `panel` inert: every child of `<body>`
|
|
3106
3109
|
* outside the overlay container (except script/style nodes and live
|
|
3107
3110
|
* regions), plus every overlay wrapper opened before the panel's own — a
|
|
3108
|
-
* dialog stacked on a dialog freezes the one below.
|
|
3111
|
+
* dialog stacked on a dialog freezes the one below. The container itself is
|
|
3112
|
+
* never inerted, even for a panel mounted outside it, so overlays the modal
|
|
3113
|
+
* opens afterwards (dropdowns, pickers) stay usable. A body child that
|
|
3109
3114
|
* contains the panel (an inline-mounted modal) is left alone, because
|
|
3110
3115
|
* inerting it would inert the panel itself.
|
|
3111
3116
|
*
|
|
@@ -3117,10 +3122,19 @@ function inertSiblingsOf(panel) {
|
|
|
3117
3122
|
const body = panel.ownerDocument?.body;
|
|
3118
3123
|
if (!body)
|
|
3119
3124
|
return () => { };
|
|
3120
|
-
|
|
3125
|
+
// Overlay roots are never inerted, whether or not the panel lives in one.
|
|
3126
|
+
// An inline-mounted (`inPlace`) modal sits outside the root, yet the
|
|
3127
|
+
// overlays it opens — a select, combobox or date picker dropdown — mount
|
|
3128
|
+
// into it after the modal opened: inerting the root would freeze them. The
|
|
3129
|
+
// wrappers open at this point (below the modal) are frozen one by one
|
|
3130
|
+
// instead; later ones stay live.
|
|
3131
|
+
const containers = Array.from(body.children).filter(isOverlayContainer);
|
|
3132
|
+
const own = panel.closest('.kj-overlay-container');
|
|
3133
|
+
if (own && !containers.includes(own))
|
|
3134
|
+
containers.push(own);
|
|
3121
3135
|
const targets = [];
|
|
3122
3136
|
for (const child of Array.from(body.children)) {
|
|
3123
|
-
if (child
|
|
3137
|
+
if (isOverlayContainer(child))
|
|
3124
3138
|
continue;
|
|
3125
3139
|
if (child.contains(panel))
|
|
3126
3140
|
continue;
|
|
@@ -3130,12 +3144,13 @@ function inertSiblingsOf(panel) {
|
|
|
3130
3144
|
continue;
|
|
3131
3145
|
targets.push(child);
|
|
3132
3146
|
}
|
|
3133
|
-
|
|
3134
|
-
const
|
|
3147
|
+
for (const container of containers) {
|
|
3148
|
+
const ownWrapper = Array.from(container.children).find((w) => w.contains(panel));
|
|
3135
3149
|
for (const wrapper of Array.from(container.children)) {
|
|
3136
|
-
if (wrapper ===
|
|
3150
|
+
if (wrapper === ownWrapper)
|
|
3137
3151
|
continue;
|
|
3138
|
-
const below = !
|
|
3152
|
+
const below = !ownWrapper ||
|
|
3153
|
+
!!(wrapper.compareDocumentPosition(ownWrapper) & Node.DOCUMENT_POSITION_FOLLOWING);
|
|
3139
3154
|
if (below)
|
|
3140
3155
|
targets.push(wrapper);
|
|
3141
3156
|
}
|
|
@@ -3151,6 +3166,10 @@ function inertSiblingsOf(panel) {
|
|
|
3151
3166
|
releaseInert(el);
|
|
3152
3167
|
};
|
|
3153
3168
|
}
|
|
3169
|
+
/** Whether `el` is an overlay root (`getOverlayContainer()`'s element, or a look-alike). */
|
|
3170
|
+
function isOverlayContainer(el) {
|
|
3171
|
+
return (el.hasAttribute('data-kj-overlay-container') || el.classList.contains('kj-overlay-container'));
|
|
3172
|
+
}
|
|
3154
3173
|
|
|
3155
3174
|
/**
|
|
3156
3175
|
* Mount strategy for declarative overlays whose panel is rendered inline
|
|
@@ -4515,6 +4534,9 @@ const read$2 = (v, fallback) => {
|
|
|
4515
4534
|
* do not bubble, and elements with `display: contents` (e.g. `<kj-button>`)
|
|
4516
4535
|
* never receive these events because they have no rendered box. Walk to the
|
|
4517
4536
|
* first descendant with a layout box.
|
|
4537
|
+
*
|
|
4538
|
+
* Measures, so it forces a layout: call it only on a hover, never on attach —
|
|
4539
|
+
* a table with a hover trigger per row would otherwise lay out once per row.
|
|
4518
4540
|
*/
|
|
4519
4541
|
const effectiveHoverTarget = (el) => {
|
|
4520
4542
|
if (!el.ownerDocument?.defaultView)
|
|
@@ -4542,7 +4564,9 @@ function onHover(initialOpts = {}) {
|
|
|
4542
4564
|
let openTimer = 0, closeTimer = 0;
|
|
4543
4565
|
let onEnter = null;
|
|
4544
4566
|
let onLeave = null;
|
|
4545
|
-
let
|
|
4567
|
+
let onFirstOver = null;
|
|
4568
|
+
let triggerTarget = null;
|
|
4569
|
+
let boxTarget = null;
|
|
4546
4570
|
let panelTarget = null;
|
|
4547
4571
|
const cancelClose = () => {
|
|
4548
4572
|
if (closeTimer) {
|
|
@@ -4584,11 +4608,12 @@ function onHover(initialOpts = {}) {
|
|
|
4584
4608
|
const trigger = ctx.triggerEl();
|
|
4585
4609
|
if (!trigger || onEnter)
|
|
4586
4610
|
return;
|
|
4587
|
-
listenTarget = effectiveHoverTarget(trigger);
|
|
4588
4611
|
onEnter = () => {
|
|
4589
4612
|
cancelClose();
|
|
4590
4613
|
wirePanel();
|
|
4591
|
-
|
|
4614
|
+
// The first hover can reach here twice (the bubbled `pointerover` that
|
|
4615
|
+
// resolves the box target, then that target's own `pointerenter`).
|
|
4616
|
+
if (ctx?.isOpen() || openTimer)
|
|
4592
4617
|
return;
|
|
4593
4618
|
openTimer = setTimeout(() => {
|
|
4594
4619
|
openTimer = 0;
|
|
@@ -4602,8 +4627,27 @@ function onHover(initialOpts = {}) {
|
|
|
4602
4627
|
wirePanel();
|
|
4603
4628
|
scheduleClose();
|
|
4604
4629
|
};
|
|
4605
|
-
|
|
4606
|
-
|
|
4630
|
+
// Nothing is measured here. The trigger listens directly (enough when it
|
|
4631
|
+
// has a box); a `display: contents` trigger only sees the bubbled
|
|
4632
|
+
// `pointerover` of a descendant, so the first one resolves — and measures,
|
|
4633
|
+
// once — the descendant that actually receives enter / leave.
|
|
4634
|
+
onFirstOver = () => {
|
|
4635
|
+
if (!onEnter || !onLeave || !triggerTarget)
|
|
4636
|
+
return;
|
|
4637
|
+
triggerTarget.removeEventListener('pointerover', onFirstOver);
|
|
4638
|
+
onFirstOver = null;
|
|
4639
|
+
const target = effectiveHoverTarget(triggerTarget);
|
|
4640
|
+
if (target !== triggerTarget) {
|
|
4641
|
+
boxTarget = target;
|
|
4642
|
+
target.addEventListener('pointerenter', onEnter);
|
|
4643
|
+
target.addEventListener('pointerleave', onLeave);
|
|
4644
|
+
}
|
|
4645
|
+
onEnter();
|
|
4646
|
+
};
|
|
4647
|
+
triggerTarget = trigger;
|
|
4648
|
+
trigger.addEventListener('pointerenter', onEnter);
|
|
4649
|
+
trigger.addEventListener('pointerleave', onLeave);
|
|
4650
|
+
trigger.addEventListener('pointerover', onFirstOver);
|
|
4607
4651
|
};
|
|
4608
4652
|
return {
|
|
4609
4653
|
ariaHasPopup: null,
|
|
@@ -4618,10 +4662,16 @@ function onHover(initialOpts = {}) {
|
|
|
4618
4662
|
onOpen() { },
|
|
4619
4663
|
onClose() { },
|
|
4620
4664
|
detach() {
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4665
|
+
for (const el of [triggerTarget, boxTarget]) {
|
|
4666
|
+
if (!el)
|
|
4667
|
+
continue;
|
|
4668
|
+
if (onEnter)
|
|
4669
|
+
el.removeEventListener('pointerenter', onEnter);
|
|
4670
|
+
if (onLeave)
|
|
4671
|
+
el.removeEventListener('pointerleave', onLeave);
|
|
4672
|
+
}
|
|
4673
|
+
if (triggerTarget && onFirstOver)
|
|
4674
|
+
triggerTarget.removeEventListener('pointerover', onFirstOver);
|
|
4625
4675
|
if (panelTarget) {
|
|
4626
4676
|
panelTarget.removeEventListener('pointerenter', cancelClose);
|
|
4627
4677
|
panelTarget.removeEventListener('pointerleave', scheduleClose);
|
|
@@ -4630,8 +4680,8 @@ function onHover(initialOpts = {}) {
|
|
|
4630
4680
|
clearTimeout(openTimer);
|
|
4631
4681
|
if (closeTimer)
|
|
4632
4682
|
clearTimeout(closeTimer);
|
|
4633
|
-
onEnter = onLeave = null;
|
|
4634
|
-
|
|
4683
|
+
onEnter = onLeave = onFirstOver = null;
|
|
4684
|
+
triggerTarget = boxTarget = null;
|
|
4635
4685
|
panelTarget = null;
|
|
4636
4686
|
openTimer = closeTimer = 0;
|
|
4637
4687
|
toggle = null;
|
|
@@ -22495,7 +22545,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
|
|
|
22495
22545
|
}]
|
|
22496
22546
|
}], propDecorators: { kjCell: [{ type: i0.Input, args: [{ isSignal: true, alias: "kjCell", required: true }] }] } });
|
|
22497
22547
|
|
|
22498
|
-
/** `Node.DOCUMENT_POSITION_PRECEDING
|
|
22548
|
+
/** `Node.DOCUMENT_POSITION_PRECEDING`, inlined so the module never touches the `Node` global. */
|
|
22499
22549
|
const DOCUMENT_POSITION_PRECEDING = 2;
|
|
22500
22550
|
/** Text-entry controls whose own arrow / Home / End handling must win over grid navigation. */
|
|
22501
22551
|
const TEXT_ENTRY_SELECTOR = 'input:not([type="button"]):not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="reset"]), textarea, select, [contenteditable]:not([contenteditable="false"])';
|
|
@@ -22533,23 +22583,32 @@ class KjTableKeyboardNav {
|
|
|
22533
22583
|
/** Id of the cell the user focused last; survives the cell leaving the DOM. */
|
|
22534
22584
|
activeId = signal(null, /* @ts-ignore */
|
|
22535
22585
|
...(ngDevMode ? [{ debugName: "activeId" }] : /* istanbul ignore next */ []));
|
|
22586
|
+
/** Tab stop resolved after the last render (see {@link tabStopId}). */
|
|
22587
|
+
resolvedStopId = signal(null, /* @ts-ignore */
|
|
22588
|
+
...(ngDevMode ? [{ debugName: "resolvedStopId" }] : /* istanbul ignore next */ []));
|
|
22536
22589
|
/**
|
|
22537
22590
|
* Id of the cell that owns `tabindex="0"`: the last focused cell while it
|
|
22538
22591
|
* is rendered, else the first rendered cell in DOM order.
|
|
22592
|
+
*
|
|
22593
|
+
* Every cell reads this, so it must not touch the `cells` query: each
|
|
22594
|
+
* stamped cell view dirties the query, and a per-cell read would re-collect
|
|
22595
|
+
* it and re-sort the DOM once per cell — O(n²) for an n-cell body. It is
|
|
22596
|
+
* resolved once per render instead, after the cells are in the DOM. Until
|
|
22597
|
+
* then (and on the server) no cell is a Tab stop.
|
|
22539
22598
|
*/
|
|
22540
|
-
tabStopId =
|
|
22541
|
-
|
|
22542
|
-
|
|
22543
|
-
|
|
22544
|
-
|
|
22545
|
-
|
|
22546
|
-
|
|
22547
|
-
|
|
22548
|
-
}, /* @ts-ignore */
|
|
22549
|
-
...(ngDevMode ? [{ debugName: "tabStopId" }] : /* istanbul ignore next */ []));
|
|
22599
|
+
tabStopId = this.resolvedStopId.asReadonly();
|
|
22600
|
+
constructor() {
|
|
22601
|
+
afterRenderEffect(() => {
|
|
22602
|
+
const cells = this.cells();
|
|
22603
|
+
const active = this.activeId();
|
|
22604
|
+
this.resolvedStopId.set(resolveTabStop(cells, active));
|
|
22605
|
+
});
|
|
22606
|
+
}
|
|
22550
22607
|
/** Record `cellId` as the last focused cell. @param cellId TanStack `cell.id`. */
|
|
22551
22608
|
setActive(cellId) {
|
|
22552
22609
|
this.activeId.set(cellId);
|
|
22610
|
+
// A cell that just took focus is rendered: it is the Tab stop right away.
|
|
22611
|
+
this.resolvedStopId.set(cellId);
|
|
22553
22612
|
}
|
|
22554
22613
|
onKeyDown(event) {
|
|
22555
22614
|
if (event.defaultPrevented)
|
|
@@ -22651,7 +22710,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
|
|
|
22651
22710
|
providers: [{ provide: KJ_TABLE_KEYBOARD_NAV, useExisting: KjTableKeyboardNav }],
|
|
22652
22711
|
host: { '(keydown)': 'onKeyDown($event)' },
|
|
22653
22712
|
}]
|
|
22654
|
-
}], propDecorators: { cells: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => KjTableCell), { ...{ descendants: true }, isSignal: true }] }] } });
|
|
22713
|
+
}], ctorParameters: () => [], propDecorators: { cells: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => KjTableCell), { ...{ descendants: true }, isSignal: true }] }] } });
|
|
22714
|
+
/** The active cell while it is rendered, else the first rendered cell in DOM order. */
|
|
22715
|
+
function resolveTabStop(cells, active) {
|
|
22716
|
+
if (cells.length === 0)
|
|
22717
|
+
return null;
|
|
22718
|
+
if (active !== null && cells.some((c) => c.cellId() === active))
|
|
22719
|
+
return active;
|
|
22720
|
+
return firstInDomOrder(cells).cellId();
|
|
22721
|
+
}
|
|
22655
22722
|
/**
|
|
22656
22723
|
* The first cell in DOM order. Query results follow declaration order, which
|
|
22657
22724
|
* a row template stamped into several bodies (pinned top, centre, pinned
|