@aihu/primitives 0.0.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/LICENSE +21 -0
- package/README.md +129 -0
- package/dist/button-C-8c-A17.js +126 -0
- package/dist/button-C-8c-A17.js.map +1 -0
- package/dist/button.d.ts +2 -0
- package/dist/button.js +2 -0
- package/dist/collection.d.ts +26 -0
- package/dist/collection.d.ts.map +1 -0
- package/dist/collection.js +72 -0
- package/dist/collection.js.map +1 -0
- package/dist/config-provider.d.ts +39 -0
- package/dist/config-provider.d.ts.map +1 -0
- package/dist/config-provider.js +112 -0
- package/dist/config-provider.js.map +1 -0
- package/dist/dialog-y7MHc6vf.js +253 -0
- package/dist/dialog-y7MHc6vf.js.map +1 -0
- package/dist/dialog.d.ts +2 -0
- package/dist/dialog.js +2 -0
- package/dist/dom-context.d.ts +40 -0
- package/dist/dom-context.d.ts.map +1 -0
- package/dist/dom-context.js +64 -0
- package/dist/dom-context.js.map +1 -0
- package/dist/form-control.d.ts +41 -0
- package/dist/form-control.d.ts.map +1 -0
- package/dist/form-control.js +145 -0
- package/dist/form-control.js.map +1 -0
- package/dist/index-D9kf9rVU.d.ts +86 -0
- package/dist/index-D9kf9rVU.d.ts.map +1 -0
- package/dist/index-DPD4L6Nj.d.ts +31 -0
- package/dist/index-DPD4L6Nj.d.ts.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/presence-gate.d.ts +33 -0
- package/dist/presence-gate.d.ts.map +1 -0
- package/dist/presence-gate.js +109 -0
- package/dist/presence-gate.js.map +1 -0
- package/dist/roving-focus.d.ts +33 -0
- package/dist/roving-focus.d.ts.map +1 -0
- package/dist/roving-focus.js +159 -0
- package/dist/roving-focus.js.map +1 -0
- package/dist/tooltip.d.ts +72 -0
- package/dist/tooltip.d.ts.map +1 -0
- package/dist/tooltip.js +195 -0
- package/dist/tooltip.js.map +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { DomContext } from "./dom-context.js";
|
|
2
|
+
import { Read } from "@aihu/signals";
|
|
3
|
+
|
|
4
|
+
//#region src/dialog/focus-trap.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* `createFocusTrap` — a tiny native-DOM focus trap (no library). Queries the
|
|
7
|
+
* focusable descendants of a container, wraps Tab at the edges, moves focus to
|
|
8
|
+
* the first focusable (or the container) on activate, and restores focus to the
|
|
9
|
+
* previously-active element on deactivate. Used by `dialog-content`.
|
|
10
|
+
*/
|
|
11
|
+
interface FocusTrap {
|
|
12
|
+
activate(): void;
|
|
13
|
+
deactivate(): void;
|
|
14
|
+
}
|
|
15
|
+
declare function createFocusTrap(container: Element): FocusTrap;
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/dialog/index.d.ts
|
|
18
|
+
interface DialogContextValue {
|
|
19
|
+
readonly open: Read<boolean>;
|
|
20
|
+
readonly modal: Read<boolean>;
|
|
21
|
+
readonly contentId: Read<string>;
|
|
22
|
+
readonly titleId: Read<string | null>;
|
|
23
|
+
readonly descriptionId: Read<string | null>;
|
|
24
|
+
setTitleId(id: string): void;
|
|
25
|
+
setDescriptionId(id: string): void;
|
|
26
|
+
setOpen(next: boolean): void;
|
|
27
|
+
close(): void;
|
|
28
|
+
toggle(): void;
|
|
29
|
+
}
|
|
30
|
+
declare const dialogContext: DomContext<DialogContextValue>;
|
|
31
|
+
declare class AihuDialogRoot extends HTMLElement {
|
|
32
|
+
static readonly observedAttributes: string[];
|
|
33
|
+
private readonly _open;
|
|
34
|
+
private readonly _modal;
|
|
35
|
+
private readonly _contentId;
|
|
36
|
+
private readonly _titleId;
|
|
37
|
+
private readonly _descriptionId;
|
|
38
|
+
private _disposers;
|
|
39
|
+
private _ctx;
|
|
40
|
+
constructor();
|
|
41
|
+
get open(): Read<boolean>;
|
|
42
|
+
setOpen(next: boolean): void;
|
|
43
|
+
connectedCallback(): void;
|
|
44
|
+
disconnectedCallback(): void;
|
|
45
|
+
attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
|
|
46
|
+
}
|
|
47
|
+
/** Base for pieces that inject the dialog context lazily on connect. */
|
|
48
|
+
declare abstract class DialogPiece extends HTMLElement {
|
|
49
|
+
protected ctx: DialogContextValue;
|
|
50
|
+
protected disposers: Array<() => void>;
|
|
51
|
+
connectedCallback(): void;
|
|
52
|
+
disconnectedCallback(): void;
|
|
53
|
+
protected reflectState(): void;
|
|
54
|
+
protected abstract onConnect(): void;
|
|
55
|
+
}
|
|
56
|
+
declare class AihuDialogTrigger extends DialogPiece {
|
|
57
|
+
protected onConnect(): void;
|
|
58
|
+
private readonly _onClick;
|
|
59
|
+
}
|
|
60
|
+
declare class AihuDialogContent extends DialogPiece {
|
|
61
|
+
private _trap;
|
|
62
|
+
protected onConnect(): void;
|
|
63
|
+
disconnectedCallback(): void;
|
|
64
|
+
private _activateTrap;
|
|
65
|
+
private _deactivateTrap;
|
|
66
|
+
private readonly _onKeydown;
|
|
67
|
+
}
|
|
68
|
+
declare class AihuDialogBackdrop extends DialogPiece {
|
|
69
|
+
protected onConnect(): void;
|
|
70
|
+
private readonly _onClick;
|
|
71
|
+
}
|
|
72
|
+
declare class AihuDialogClose extends DialogPiece {
|
|
73
|
+
protected onConnect(): void;
|
|
74
|
+
private readonly _onClick;
|
|
75
|
+
}
|
|
76
|
+
declare class AihuDialogTitle extends DialogPiece {
|
|
77
|
+
protected onConnect(): void;
|
|
78
|
+
}
|
|
79
|
+
declare class AihuDialogDescription extends DialogPiece {
|
|
80
|
+
protected onConnect(): void;
|
|
81
|
+
}
|
|
82
|
+
/** Register all dialog custom elements (idempotent). */
|
|
83
|
+
declare function defineDialog(): void;
|
|
84
|
+
//#endregion
|
|
85
|
+
export { AihuDialogRoot as a, DialogContextValue as c, FocusTrap as d, createFocusTrap as f, AihuDialogDescription as i, defineDialog as l, AihuDialogClose as n, AihuDialogTitle as o, AihuDialogContent as r, AihuDialogTrigger as s, AihuDialogBackdrop as t, dialogContext as u };
|
|
86
|
+
//# sourceMappingURL=index-D9kf9rVU.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-D9kf9rVU.d.ts","names":[],"sources":["../src/dialog/focus-trap.ts","../src/dialog/index.ts"],"mappings":";;;;;;;;AAYA;;UAAiB,SAAA;EACf,QAAA;EACA,UAAA;AAAA;AAAA,iBAcc,eAAA,CAAgB,SAAA,EAAW,OAAA,GAAU,SAAA;;;UCbpC,kBAAA;EAAA,SACN,IAAA,EAAM,IAAA;EAAA,SACN,KAAA,EAAO,IAAA;EAAA,SACP,SAAA,EAAW,IAAA;EAAA,SACX,OAAA,EAAS,IAAA;EAAA,SACT,aAAA,EAAe,IAAA;EACxB,UAAA,CAAW,EAAA;EACX,gBAAA,CAAiB,EAAA;EACjB,OAAA,CAAQ,IAAA;EACR,KAAA;EACA,MAAA;AAAA;AAAA,cAGW,aAAA,EAAa,UAAA,CAAA,kBAAA;AAAA,cAKb,cAAA,SAAuB,WAAA;EAAA,gBAClB,kBAAA;EAAA,iBAEC,KAAA;EAAA,iBACA,MAAA;EAAA,iBACA,UAAA;EAAA,iBACA,QAAA;EAAA,iBACA,cAAA;EAAA,QACT,UAAA;EAAA,QACA,IAAA;;MAmBJ,IAAA,CAAA,GAAQ,IAAA;EAIZ,OAAA,CAAQ,IAAA;EAOR,iBAAA,CAAA;EAUA,oBAAA,CAAA;EAKA,wBAAA,CAAyB,IAAA,UAAc,IAAA,iBAAqB,KAAA;AAAA;;uBAO/C,WAAA,SAAoB,WAAA;EAAA,UACvB,GAAA,EAAM,kBAAA;EAAA,UACN,SAAA,EAAW,KAAA;EAErB,iBAAA,CAAA;EAKA,oBAAA,CAAA;EAAA,UAKU,YAAA,CAAA;EAAA,mBAQS,SAAA,CAAA;AAAA;AAAA,cAGR,iBAAA,SAA0B,WAAA;EAAA,UAC3B,SAAA,CAAA;EAAA,iBAiBO,QAAA;AAAA;AAAA,cAKN,iBAAA,SAA0B,WAAA;EAAA,QAC7B,KAAA;EAAA,UAEE,SAAA,CAAA;EAmBD,oBAAA,CAAA;EAAA,QAMD,aAAA;EAAA,QAMA,eAAA;EAAA,iBAKS,UAAA;AAAA;AAAA,cAQN,kBAAA,SAA2B,WAAA;EAAA,UAC5B,SAAA,CAAA;EAAA,iBAKO,QAAA;AAAA;AAAA,cAON,eAAA,SAAwB,WAAA;EAAA,UACzB,SAAA,CAAA;EAAA,iBASO,QAAA;AAAA;AAAA,cAKN,eAAA,SAAwB,WAAA;EAAA,UACzB,SAAA,CAAA;AAAA;AAAA,cAMC,qBAAA,SAA8B,WAAA;EAAA,UAC/B,SAAA,CAAA;AAAA;;iBAkBI,YAAA,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Read } from "@aihu/signals";
|
|
2
|
+
|
|
3
|
+
//#region src/button/button.d.ts
|
|
4
|
+
type ButtonType = 'button' | 'submit' | 'reset';
|
|
5
|
+
declare class AihuButton extends HTMLElement {
|
|
6
|
+
static readonly observedAttributes: string[];
|
|
7
|
+
private readonly _disabled;
|
|
8
|
+
private readonly _pressed;
|
|
9
|
+
private _inheritedDisabled;
|
|
10
|
+
private _disposers;
|
|
11
|
+
/** True when this is a toggle button (the `pressed` attribute is present). */
|
|
12
|
+
private _isToggle;
|
|
13
|
+
get disabled(): Read<boolean>;
|
|
14
|
+
get pressed(): Read<boolean | null>;
|
|
15
|
+
private get _isNativeButton();
|
|
16
|
+
connectedCallback(): void;
|
|
17
|
+
disconnectedCallback(): void;
|
|
18
|
+
attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
|
|
19
|
+
/** Toggle a toggle-button's pressed state. */
|
|
20
|
+
toggle(): void;
|
|
21
|
+
private _effectiveDisabled;
|
|
22
|
+
private _stateString;
|
|
23
|
+
private readonly _onKeydown;
|
|
24
|
+
private readonly _onClickCapture;
|
|
25
|
+
private _syncFromAttrs;
|
|
26
|
+
}
|
|
27
|
+
/** Register a concrete button tag backed by `AihuButton` (idempotent). */
|
|
28
|
+
declare function defineButton(tag: string): typeof AihuButton;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { ButtonType as n, defineButton as r, AihuButton as t };
|
|
31
|
+
//# sourceMappingURL=index-DPD4L6Nj.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-DPD4L6Nj.d.ts","names":[],"sources":["../src/button/button.ts"],"mappings":";;;KAqBY,UAAA;AAAA,cAEC,UAAA,SAAmB,WAAA;EAAA,gBACd,kBAAA;EAAA,iBAEC,SAAA;EAAA,iBACA,QAAA;EAAA,QACT,kBAAA;EAAA,QACA,UAAA;EAKQ;EAAA,QAFR,SAAA;EAAA,IAEJ,QAAA,CAAA,GAAY,IAAA;EAAA,IAGZ,OAAA,CAAA,GAAW,IAAA;EAAA,YAIH,eAAA,CAAA;EAIZ,iBAAA,CAAA;EAiCA,oBAAA,CAAA;EAOA,wBAAA,CAAyB,IAAA,UAAc,IAAA,iBAAqB,KAAA;EAArB;EAavC,MAAA,CAAA;EAAA,QAKQ,kBAAA;EAAA,QAKA,YAAA;EAAA,iBAMS,UAAA;EAAA,iBAQA,eAAA;EAAA,QAST,cAAA;AAAA;;iBAOM,YAAA,CAAa,GAAA,kBAAqB,UAAA"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { n as ButtonType, r as defineButton, t as AihuButton } from "./index-DPD4L6Nj.js";
|
|
2
|
+
import { DomContext, MissingContextError, createDomContext, injectContext, provideContext } from "./dom-context.js";
|
|
3
|
+
import { AihuCollection, CollectionContextValue, collectionContext, createCollection, defineCollection } from "./collection.js";
|
|
4
|
+
import { AihuConfigProvider, ColorScheme, ConfigContextValue, Density, Direction, configContext, defineConfigProvider } from "./config-provider.js";
|
|
5
|
+
import { a as AihuDialogRoot, c as DialogContextValue, d as FocusTrap, f as createFocusTrap, i as AihuDialogDescription, l as defineDialog, n as AihuDialogClose, o as AihuDialogTitle, r as AihuDialogContent, s as AihuDialogTrigger, t as AihuDialogBackdrop, u as dialogContext } from "./index-D9kf9rVU.js";
|
|
6
|
+
import { AihuFormControl, FormControlContextValue, defineFormControl, formControlContext } from "./form-control.js";
|
|
7
|
+
import { AihuPresenceGate, definePresenceGate, presenceContext } from "./presence-gate.js";
|
|
8
|
+
import { AihuRovingFocus, Orientation, defineRovingFocus } from "./roving-focus.js";
|
|
9
|
+
import { AihuTooltipContent, AihuTooltipRoot, AihuTooltipTrigger, TooltipContextValue, TooltipCoords, defineTooltip, tooltipContext } from "./tooltip.js";
|
|
10
|
+
export { AihuButton, AihuCollection, AihuConfigProvider, AihuDialogBackdrop, AihuDialogClose, AihuDialogContent, AihuDialogDescription, AihuDialogRoot, AihuDialogTitle, AihuDialogTrigger, AihuFormControl, AihuPresenceGate, AihuRovingFocus, AihuTooltipContent, AihuTooltipRoot, AihuTooltipTrigger, type ButtonType, type CollectionContextValue, type ColorScheme, type ConfigContextValue, type Density, type DialogContextValue, type Direction, type DomContext, type FocusTrap, type FormControlContextValue, MissingContextError, type Orientation, type TooltipContextValue, type TooltipCoords, collectionContext, configContext, createCollection, createDomContext, createFocusTrap, defineButton, defineCollection, defineConfigProvider, defineDialog, defineFormControl, definePresenceGate, defineRovingFocus, defineTooltip, dialogContext, formControlContext, injectContext, presenceContext, provideContext, tooltipContext };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { MissingContextError, createDomContext, injectContext, provideContext } from "./dom-context.js";
|
|
2
|
+
import { AihuFormControl, defineFormControl, formControlContext } from "./form-control.js";
|
|
3
|
+
import { n as defineButton, t as AihuButton } from "./button-C-8c-A17.js";
|
|
4
|
+
import { AihuCollection, collectionContext, createCollection, defineCollection } from "./collection.js";
|
|
5
|
+
import { AihuConfigProvider, configContext, defineConfigProvider } from "./config-provider.js";
|
|
6
|
+
import { a as AihuDialogRoot, c as defineDialog, i as AihuDialogDescription, l as dialogContext, n as AihuDialogClose, o as AihuDialogTitle, r as AihuDialogContent, s as AihuDialogTrigger, t as AihuDialogBackdrop, u as createFocusTrap } from "./dialog-y7MHc6vf.js";
|
|
7
|
+
import { AihuPresenceGate, definePresenceGate, presenceContext } from "./presence-gate.js";
|
|
8
|
+
import { AihuRovingFocus, defineRovingFocus } from "./roving-focus.js";
|
|
9
|
+
import { AihuTooltipContent, AihuTooltipRoot, AihuTooltipTrigger, defineTooltip, tooltipContext } from "./tooltip.js";
|
|
10
|
+
export { AihuButton, AihuCollection, AihuConfigProvider, AihuDialogBackdrop, AihuDialogClose, AihuDialogContent, AihuDialogDescription, AihuDialogRoot, AihuDialogTitle, AihuDialogTrigger, AihuFormControl, AihuPresenceGate, AihuRovingFocus, AihuTooltipContent, AihuTooltipRoot, AihuTooltipTrigger, MissingContextError, collectionContext, configContext, createCollection, createDomContext, createFocusTrap, defineButton, defineCollection, defineConfigProvider, defineDialog, defineFormControl, definePresenceGate, defineRovingFocus, defineTooltip, dialogContext, formControlContext, injectContext, presenceContext, provideContext, tooltipContext };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { DomContext } from "./dom-context.js";
|
|
2
|
+
import { Read } from "@aihu/signals";
|
|
3
|
+
|
|
4
|
+
//#region src/presence-gate/index.d.ts
|
|
5
|
+
/** Context carrying the gate's `present` signal to descendants. */
|
|
6
|
+
declare const presenceContext: DomContext<Read<boolean>>;
|
|
7
|
+
declare class AihuPresenceGate extends HTMLElement {
|
|
8
|
+
static readonly observedAttributes: string[];
|
|
9
|
+
private readonly _present;
|
|
10
|
+
private readonly _closing;
|
|
11
|
+
/** The `present` signal (read), exposed for the provided context + tests. */
|
|
12
|
+
get present(): Read<boolean>;
|
|
13
|
+
/** True while open OR while a closing animation is still running. */
|
|
14
|
+
readonly mounted: Read<boolean>;
|
|
15
|
+
private _disposers;
|
|
16
|
+
private _stash;
|
|
17
|
+
/** Tears down the pending exit listeners; set while a close animation runs. */
|
|
18
|
+
private _cancelExit;
|
|
19
|
+
constructor();
|
|
20
|
+
connectedCallback(): void;
|
|
21
|
+
disconnectedCallback(): void;
|
|
22
|
+
attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
|
|
23
|
+
private _setPresent;
|
|
24
|
+
/** Hold mounted children until the exit animation ends, then unmount. */
|
|
25
|
+
private _beginExit;
|
|
26
|
+
private _unmountChildren;
|
|
27
|
+
private _restoreChildren;
|
|
28
|
+
}
|
|
29
|
+
/** Register `<aihu-presence-gate>` (idempotent — safe to call repeatedly). */
|
|
30
|
+
declare function definePresenceGate(tag?: string): void;
|
|
31
|
+
//#endregion
|
|
32
|
+
export { AihuPresenceGate, definePresenceGate, presenceContext };
|
|
33
|
+
//# sourceMappingURL=presence-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presence-gate.d.ts","names":[],"sources":["../src/presence-gate/index.ts"],"mappings":";;;;;cAoBa,eAAA,EAAe,UAAA,CAAA,IAAA;AAAA,cAEf,gBAAA,SAAyB,WAAA;EAAA,gBACpB,kBAAA;EAAA,iBAEC,QAAA;EAAA,iBACA,QAAA;EAGF;EAAA,IAAX,OAAA,CAAA,GAAW,IAAA;EAKG;EAAA,SAAT,OAAA,EAAS,IAAA;EAAA,QAEV,UAAA;EAAA,QACA,MAAA;;UAEA,WAAA;;EAQR,iBAAA,CAAA;EASA,oBAAA,CAAA;EAKA,wBAAA,CAAyB,IAAA,UAAc,IAAA,iBAAqB,KAAA;EAAA,QAIpD,WAAA;EAAA;EAAA,QAeA,UAAA;EAAA,QAuBA,gBAAA;EAAA,QAMA,gBAAA;AAAA;;iBASM,kBAAA,CAAmB,GAAA"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { createDomContext, provideContext } from "./dom-context.js";
|
|
2
|
+
import { computed, effect, signal } from "@aihu/signals";
|
|
3
|
+
//#region src/presence-gate/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* `<aihu-presence-gate>` — headless mount/unmount gate with exit-animation hold
|
|
6
|
+
* (the Radix `Presence` pattern). When `present` flips false the element keeps
|
|
7
|
+
* its children mounted until any CSS transition/animation on the gate completes
|
|
8
|
+
* (`transitionend`/`animationend`), then unmounts them. Emits NO CSS — the
|
|
9
|
+
* consumer's exit animation drives the timing.
|
|
10
|
+
*
|
|
11
|
+
* Reflected attributes: `present` (boolean, controlled), `data-state`
|
|
12
|
+
* (`"open" | "closed"`).
|
|
13
|
+
* Owned signals: `present` (boolean), `mounted` (computed — true while open OR
|
|
14
|
+
* while a closing transition runs).
|
|
15
|
+
* Context: provides `presenceContext` carrying the `present` signal so
|
|
16
|
+
* descendant pieces (e.g. `dialog-content`) can read presence reactively.
|
|
17
|
+
* ARIA: none — structural gate only.
|
|
18
|
+
*/
|
|
19
|
+
/** Context carrying the gate's `present` signal to descendants. */
|
|
20
|
+
const presenceContext = createDomContext("presence");
|
|
21
|
+
var AihuPresenceGate = class extends HTMLElement {
|
|
22
|
+
static observedAttributes = ["present"];
|
|
23
|
+
_present = signal(false);
|
|
24
|
+
_closing = signal(false);
|
|
25
|
+
/** The `present` signal (read), exposed for the provided context + tests. */
|
|
26
|
+
get present() {
|
|
27
|
+
return this._present[0];
|
|
28
|
+
}
|
|
29
|
+
/** True while open OR while a closing animation is still running. */
|
|
30
|
+
mounted;
|
|
31
|
+
_disposers = [];
|
|
32
|
+
_stash = [];
|
|
33
|
+
/** Tears down the pending exit listeners; set while a close animation runs. */
|
|
34
|
+
_cancelExit = null;
|
|
35
|
+
constructor() {
|
|
36
|
+
super();
|
|
37
|
+
this.mounted = computed(() => this._present[0]() || this._closing[0]());
|
|
38
|
+
provideContext(this, presenceContext, this._present[0]);
|
|
39
|
+
}
|
|
40
|
+
connectedCallback() {
|
|
41
|
+
this._setPresent(this.hasAttribute("present"));
|
|
42
|
+
this._disposers.push(effect(() => {
|
|
43
|
+
this.setAttribute("data-state", this._present[0]() ? "open" : "closed");
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
disconnectedCallback() {
|
|
47
|
+
for (const d of this._disposers) d();
|
|
48
|
+
this._disposers = [];
|
|
49
|
+
}
|
|
50
|
+
attributeChangedCallback(name, _old, value) {
|
|
51
|
+
if (name === "present") this._setPresent(value !== null);
|
|
52
|
+
}
|
|
53
|
+
_setPresent(next) {
|
|
54
|
+
if (next === this._present[0]()) return;
|
|
55
|
+
this._present[1](next);
|
|
56
|
+
if (next) {
|
|
57
|
+
this._cancelExit?.();
|
|
58
|
+
this._closing[1](false);
|
|
59
|
+
this._restoreChildren();
|
|
60
|
+
} else this._beginExit();
|
|
61
|
+
}
|
|
62
|
+
/** Hold mounted children until the exit animation ends, then unmount. */
|
|
63
|
+
_beginExit() {
|
|
64
|
+
this._closing[1](true);
|
|
65
|
+
let done = false;
|
|
66
|
+
const finish = () => {
|
|
67
|
+
if (done) return;
|
|
68
|
+
done = true;
|
|
69
|
+
teardown();
|
|
70
|
+
this._closing[1](false);
|
|
71
|
+
this._unmountChildren();
|
|
72
|
+
};
|
|
73
|
+
const teardown = () => {
|
|
74
|
+
this.removeEventListener("transitionend", finish);
|
|
75
|
+
this.removeEventListener("animationend", finish);
|
|
76
|
+
this._cancelExit = null;
|
|
77
|
+
};
|
|
78
|
+
this._cancelExit = () => {
|
|
79
|
+
done = true;
|
|
80
|
+
teardown();
|
|
81
|
+
};
|
|
82
|
+
this.addEventListener("transitionend", finish);
|
|
83
|
+
this.addEventListener("animationend", finish);
|
|
84
|
+
}
|
|
85
|
+
_unmountChildren() {
|
|
86
|
+
if (this._stash.length > 0) return;
|
|
87
|
+
this._stash = Array.from(this.childNodes);
|
|
88
|
+
for (const n of this._stash) n.remove();
|
|
89
|
+
}
|
|
90
|
+
_restoreChildren() {
|
|
91
|
+
if (this._stash.length === 0) return;
|
|
92
|
+
for (const n of this._stash) this.appendChild(n);
|
|
93
|
+
this._stash = [];
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
let _defined = false;
|
|
97
|
+
/** Register `<aihu-presence-gate>` (idempotent — safe to call repeatedly). */
|
|
98
|
+
function definePresenceGate(tag = "aihu-presence-gate") {
|
|
99
|
+
if (_defined || customElements.get(tag)) {
|
|
100
|
+
_defined = true;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
customElements.define(tag, AihuPresenceGate);
|
|
104
|
+
_defined = true;
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { AihuPresenceGate, definePresenceGate, presenceContext };
|
|
108
|
+
|
|
109
|
+
//# sourceMappingURL=presence-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presence-gate.js","names":[],"sources":["../src/presence-gate/index.ts"],"sourcesContent":["/**\n * `<aihu-presence-gate>` — headless mount/unmount gate with exit-animation hold\n * (the Radix `Presence` pattern). When `present` flips false the element keeps\n * its children mounted until any CSS transition/animation on the gate completes\n * (`transitionend`/`animationend`), then unmounts them. Emits NO CSS — the\n * consumer's exit animation drives the timing.\n *\n * Reflected attributes: `present` (boolean, controlled), `data-state`\n * (`\"open\" | \"closed\"`).\n * Owned signals: `present` (boolean), `mounted` (computed — true while open OR\n * while a closing transition runs).\n * Context: provides `presenceContext` carrying the `present` signal so\n * descendant pieces (e.g. `dialog-content`) can read presence reactively.\n * ARIA: none — structural gate only.\n */\n\nimport { computed, effect, type Read, signal } from '@aihu/signals'\nimport { createDomContext, provideContext } from '../dom-context.ts'\n\n/** Context carrying the gate's `present` signal to descendants. */\nexport const presenceContext = createDomContext<Read<boolean>>('presence')\n\nexport class AihuPresenceGate extends HTMLElement {\n static readonly observedAttributes = ['present']\n\n private readonly _present = signal(false)\n private readonly _closing = signal(false)\n\n /** The `present` signal (read), exposed for the provided context + tests. */\n get present(): Read<boolean> {\n return this._present[0]\n }\n\n /** True while open OR while a closing animation is still running. */\n readonly mounted: Read<boolean>\n\n private _disposers: Array<() => void> = []\n private _stash: ChildNode[] = []\n /** Tears down the pending exit listeners; set while a close animation runs. */\n private _cancelExit: (() => void) | null = null\n\n constructor() {\n super()\n this.mounted = computed(() => this._present[0]() || this._closing[0]())\n provideContext(this, presenceContext, this._present[0])\n }\n\n connectedCallback(): void {\n this._setPresent(this.hasAttribute('present'))\n this._disposers.push(\n effect(() => {\n this.setAttribute('data-state', this._present[0]() ? 'open' : 'closed')\n }),\n )\n }\n\n disconnectedCallback(): void {\n for (const d of this._disposers) d()\n this._disposers = []\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n if (name === 'present') this._setPresent(value !== null)\n }\n\n private _setPresent(next: boolean): void {\n if (next === this._present[0]()) return\n this._present[1](next)\n if (next) {\n // Re-opening — cancel any in-flight exit so a stray transitionend can't\n // unmount the children we're keeping.\n this._cancelExit?.()\n this._closing[1](false)\n this._restoreChildren()\n } else {\n this._beginExit()\n }\n }\n\n /** Hold mounted children until the exit animation ends, then unmount. */\n private _beginExit(): void {\n this._closing[1](true)\n let done = false\n const finish = (): void => {\n if (done) return\n done = true\n teardown()\n this._closing[1](false)\n this._unmountChildren()\n }\n const teardown = (): void => {\n this.removeEventListener('transitionend', finish)\n this.removeEventListener('animationend', finish)\n this._cancelExit = null\n }\n this._cancelExit = (): void => {\n done = true\n teardown()\n }\n this.addEventListener('transitionend', finish)\n this.addEventListener('animationend', finish)\n }\n\n private _unmountChildren(): void {\n if (this._stash.length > 0) return\n this._stash = Array.from(this.childNodes)\n for (const n of this._stash) n.remove()\n }\n\n private _restoreChildren(): void {\n if (this._stash.length === 0) return\n for (const n of this._stash) this.appendChild(n)\n this._stash = []\n }\n}\n\nlet _defined = false\n/** Register `<aihu-presence-gate>` (idempotent — safe to call repeatedly). */\nexport function definePresenceGate(tag = 'aihu-presence-gate'): void {\n if (_defined || customElements.get(tag)) {\n _defined = true\n return\n }\n customElements.define(tag, AihuPresenceGate)\n _defined = true\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAoBA,MAAa,kBAAkB,iBAAgC,WAAW;AAE1E,IAAa,mBAAb,cAAsC,YAAY;CAChD,OAAgB,qBAAqB,CAAC,UAAU;CAEhD,WAA4B,OAAO,MAAM;CACzC,WAA4B,OAAO,MAAM;;CAGzC,IAAI,UAAyB;EAC3B,OAAO,KAAK,SAAS;;;CAIvB;CAEA,aAAwC,EAAE;CAC1C,SAA8B,EAAE;;CAEhC,cAA2C;CAE3C,cAAc;EACZ,OAAO;EACP,KAAK,UAAU,eAAe,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC;EACvE,eAAe,MAAM,iBAAiB,KAAK,SAAS,GAAG;;CAGzD,oBAA0B;EACxB,KAAK,YAAY,KAAK,aAAa,UAAU,CAAC;EAC9C,KAAK,WAAW,KACd,aAAa;GACX,KAAK,aAAa,cAAc,KAAK,SAAS,IAAI,GAAG,SAAS,SAAS;IACvE,CACH;;CAGH,uBAA6B;EAC3B,KAAK,MAAM,KAAK,KAAK,YAAY,GAAG;EACpC,KAAK,aAAa,EAAE;;CAGtB,yBAAyB,MAAc,MAAqB,OAA4B;EACtF,IAAI,SAAS,WAAW,KAAK,YAAY,UAAU,KAAK;;CAG1D,YAAoB,MAAqB;EACvC,IAAI,SAAS,KAAK,SAAS,IAAI,EAAE;EACjC,KAAK,SAAS,GAAG,KAAK;EACtB,IAAI,MAAM;GAGR,KAAK,eAAe;GACpB,KAAK,SAAS,GAAG,MAAM;GACvB,KAAK,kBAAkB;SAEvB,KAAK,YAAY;;;CAKrB,aAA2B;EACzB,KAAK,SAAS,GAAG,KAAK;EACtB,IAAI,OAAO;EACX,MAAM,eAAqB;GACzB,IAAI,MAAM;GACV,OAAO;GACP,UAAU;GACV,KAAK,SAAS,GAAG,MAAM;GACvB,KAAK,kBAAkB;;EAEzB,MAAM,iBAAuB;GAC3B,KAAK,oBAAoB,iBAAiB,OAAO;GACjD,KAAK,oBAAoB,gBAAgB,OAAO;GAChD,KAAK,cAAc;;EAErB,KAAK,oBAA0B;GAC7B,OAAO;GACP,UAAU;;EAEZ,KAAK,iBAAiB,iBAAiB,OAAO;EAC9C,KAAK,iBAAiB,gBAAgB,OAAO;;CAG/C,mBAAiC;EAC/B,IAAI,KAAK,OAAO,SAAS,GAAG;EAC5B,KAAK,SAAS,MAAM,KAAK,KAAK,WAAW;EACzC,KAAK,MAAM,KAAK,KAAK,QAAQ,EAAE,QAAQ;;CAGzC,mBAAiC;EAC/B,IAAI,KAAK,OAAO,WAAW,GAAG;EAC9B,KAAK,MAAM,KAAK,KAAK,QAAQ,KAAK,YAAY,EAAE;EAChD,KAAK,SAAS,EAAE;;;AAIpB,IAAI,WAAW;;AAEf,SAAgB,mBAAmB,MAAM,sBAA4B;CACnE,IAAI,YAAY,eAAe,IAAI,IAAI,EAAE;EACvC,WAAW;EACX;;CAEF,eAAe,OAAO,KAAK,iBAAiB;CAC5C,WAAW"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Read } from "@aihu/signals";
|
|
2
|
+
|
|
3
|
+
//#region src/roving-focus/index.d.ts
|
|
4
|
+
type Orientation = 'horizontal' | 'vertical' | 'both';
|
|
5
|
+
declare class AihuRovingFocus extends HTMLElement {
|
|
6
|
+
static readonly observedAttributes: string[];
|
|
7
|
+
private readonly _collection;
|
|
8
|
+
private readonly _currentIndex;
|
|
9
|
+
private readonly _loop;
|
|
10
|
+
private readonly _orientation;
|
|
11
|
+
private readonly _dirAttr;
|
|
12
|
+
readonly activeId: Read<string | null>;
|
|
13
|
+
private _disposers;
|
|
14
|
+
get currentIndex(): Read<number>;
|
|
15
|
+
get items(): Read<Element[]>;
|
|
16
|
+
constructor();
|
|
17
|
+
connectedCallback(): void;
|
|
18
|
+
disconnectedCallback(): void;
|
|
19
|
+
attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
|
|
20
|
+
/** Register a focusable descendant; returns an unregister disposer. */
|
|
21
|
+
register(el: Element): () => void;
|
|
22
|
+
/** Move the current index to `index` (clamped/looped) and focus the item. */
|
|
23
|
+
setCurrent(index: number): void;
|
|
24
|
+
/** Resolve the effective direction (own attr, else nearest config-provider). */
|
|
25
|
+
private _direction;
|
|
26
|
+
private readonly _onKeydown;
|
|
27
|
+
private _syncFromAttrs;
|
|
28
|
+
}
|
|
29
|
+
/** Register `<aihu-roving-focus>` (idempotent). */
|
|
30
|
+
declare function defineRovingFocus(tag?: string): void;
|
|
31
|
+
//#endregion
|
|
32
|
+
export { AihuRovingFocus, Orientation, defineRovingFocus };
|
|
33
|
+
//# sourceMappingURL=roving-focus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roving-focus.d.ts","names":[],"sources":["../src/roving-focus/index.ts"],"mappings":";;;KAwBY,WAAA;AAAA,cAEC,eAAA,SAAwB,WAAA;EAAA,gBACnB,kBAAA;EAAA,iBAEC,WAAA;EAAA,iBACA,aAAA;EAAA,iBACA,KAAA;EAAA,iBACA,YAAA;EAAA,iBACA,QAAA;EAAA,SAER,QAAA,EAAU,IAAA;EAAA,QAEX,UAAA;EAAA,IAEJ,YAAA,CAAA,GAAgB,IAAA;EAAA,IAGhB,KAAA,CAAA,GAAS,IAAA,CAAK,OAAA;;EAelB,iBAAA,CAAA;EAgBA,oBAAA,CAAA;EAMA,wBAAA,CAAyB,IAAA,UAAc,IAAA,iBAAqB,KAAA;EAtB5D;EAsCA,QAAA,CAAS,EAAA,EAAI,OAAA;EAhBb;EAqBA,UAAA,CAAW,KAAA;EArB4B;EAAA,QAmC/B,UAAA;EAAA,iBAUS,UAAA;EAAA,QAqCT,cAAA;AAAA;;iBAWM,iBAAA,CAAkB,GAAA"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { injectValue, provideContext } from "./dom-context.js";
|
|
2
|
+
import { collectionContext, createCollection } from "./collection.js";
|
|
3
|
+
import { configContext } from "./config-provider.js";
|
|
4
|
+
import { computed, effect, signal } from "@aihu/signals";
|
|
5
|
+
//#region src/roving-focus/index.ts
|
|
6
|
+
/**
|
|
7
|
+
* `<aihu-roving-focus>` — WAI-ARIA roving-tabindex container. Exactly one item
|
|
8
|
+
* carries `tabindex="0"` (the current); all others `tabindex="-1"`. Arrow keys
|
|
9
|
+
* move the current index (respecting `orientation` + `loop` + RTL `dir`),
|
|
10
|
+
* Home/End jump to the first/last item, and `element.focus()` follows. It
|
|
11
|
+
* imposes NO role (the consumer sets `role="toolbar"`/`"menu"`/etc.) and ships
|
|
12
|
+
* NO CSS.
|
|
13
|
+
*
|
|
14
|
+
* Items are enumerated via the `collection` substrate (Task 8): each focusable
|
|
15
|
+
* descendant registers itself; the container reuses `createCollection()` so
|
|
16
|
+
* item order tracks the DOM.
|
|
17
|
+
*
|
|
18
|
+
* Reflected attributes: `orientation` (`"horizontal" | "vertical" | "both"`),
|
|
19
|
+
* `loop` (boolean), `dir` (`"ltr" | "rtl"` — inherited from a `config-provider`
|
|
20
|
+
* ancestor if present).
|
|
21
|
+
* Owned signals: `currentIndex`, `items` (from the collection), `activeId`
|
|
22
|
+
* (computed).
|
|
23
|
+
*/
|
|
24
|
+
var AihuRovingFocus = class extends HTMLElement {
|
|
25
|
+
static observedAttributes = [
|
|
26
|
+
"orientation",
|
|
27
|
+
"loop",
|
|
28
|
+
"dir"
|
|
29
|
+
];
|
|
30
|
+
_collection = createCollection();
|
|
31
|
+
_currentIndex = signal(0);
|
|
32
|
+
_loop = signal(false);
|
|
33
|
+
_orientation = signal("horizontal");
|
|
34
|
+
_dirAttr = signal(null);
|
|
35
|
+
activeId;
|
|
36
|
+
_disposers = [];
|
|
37
|
+
get currentIndex() {
|
|
38
|
+
return this._currentIndex[0];
|
|
39
|
+
}
|
|
40
|
+
get items() {
|
|
41
|
+
return this._collection.items;
|
|
42
|
+
}
|
|
43
|
+
constructor() {
|
|
44
|
+
super();
|
|
45
|
+
provideContext(this, collectionContext, this._collection);
|
|
46
|
+
this.activeId = computed(() => {
|
|
47
|
+
const el = this._collection.items()[this._currentIndex[0]()];
|
|
48
|
+
return el ? el.id || null : null;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
connectedCallback() {
|
|
52
|
+
this._syncFromAttrs();
|
|
53
|
+
this.addEventListener("keydown", this._onKeydown);
|
|
54
|
+
this._disposers.push(effect(() => {
|
|
55
|
+
const items = this._collection.items();
|
|
56
|
+
const current = this._currentIndex[0]();
|
|
57
|
+
items.forEach((el, i) => {
|
|
58
|
+
el.setAttribute("tabindex", i === current ? "0" : "-1");
|
|
59
|
+
});
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
disconnectedCallback() {
|
|
63
|
+
this.removeEventListener("keydown", this._onKeydown);
|
|
64
|
+
for (const d of this._disposers) d();
|
|
65
|
+
this._disposers = [];
|
|
66
|
+
}
|
|
67
|
+
attributeChangedCallback(name, _old, value) {
|
|
68
|
+
switch (name) {
|
|
69
|
+
case "orientation":
|
|
70
|
+
if (value === "horizontal" || value === "vertical" || value === "both") this._orientation[1](value);
|
|
71
|
+
break;
|
|
72
|
+
case "loop":
|
|
73
|
+
this._loop[1](value !== null);
|
|
74
|
+
break;
|
|
75
|
+
case "dir":
|
|
76
|
+
this._dirAttr[1](value === "rtl" ? "rtl" : value === "ltr" ? "ltr" : null);
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Register a focusable descendant; returns an unregister disposer. */
|
|
81
|
+
register(el) {
|
|
82
|
+
return this._collection.register(el);
|
|
83
|
+
}
|
|
84
|
+
/** Move the current index to `index` (clamped/looped) and focus the item. */
|
|
85
|
+
setCurrent(index) {
|
|
86
|
+
const items = this._collection.items();
|
|
87
|
+
if (items.length === 0) return;
|
|
88
|
+
let next = index;
|
|
89
|
+
if (this._loop[0]()) next = (index % items.length + items.length) % items.length;
|
|
90
|
+
else next = Math.max(0, Math.min(items.length - 1, index));
|
|
91
|
+
this._currentIndex[1](next);
|
|
92
|
+
items[next].focus?.();
|
|
93
|
+
}
|
|
94
|
+
/** Resolve the effective direction (own attr, else nearest config-provider). */
|
|
95
|
+
_direction() {
|
|
96
|
+
const own = this._dirAttr[0]();
|
|
97
|
+
if (own) return own;
|
|
98
|
+
try {
|
|
99
|
+
return injectValue(this, configContext).dir();
|
|
100
|
+
} catch {
|
|
101
|
+
return "ltr";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
_onKeydown = (ev) => {
|
|
105
|
+
const orientation = this._orientation[0]();
|
|
106
|
+
const horizontal = orientation === "horizontal" || orientation === "both";
|
|
107
|
+
const vertical = orientation === "vertical" || orientation === "both";
|
|
108
|
+
const rtl = this._direction() === "rtl";
|
|
109
|
+
const current = this._currentIndex[0]();
|
|
110
|
+
let handled = true;
|
|
111
|
+
switch (ev.key) {
|
|
112
|
+
case "ArrowRight":
|
|
113
|
+
if (horizontal) this.setCurrent(current + (rtl ? -1 : 1));
|
|
114
|
+
else handled = false;
|
|
115
|
+
break;
|
|
116
|
+
case "ArrowLeft":
|
|
117
|
+
if (horizontal) this.setCurrent(current + (rtl ? 1 : -1));
|
|
118
|
+
else handled = false;
|
|
119
|
+
break;
|
|
120
|
+
case "ArrowDown":
|
|
121
|
+
if (vertical) this.setCurrent(current + 1);
|
|
122
|
+
else handled = false;
|
|
123
|
+
break;
|
|
124
|
+
case "ArrowUp":
|
|
125
|
+
if (vertical) this.setCurrent(current - 1);
|
|
126
|
+
else handled = false;
|
|
127
|
+
break;
|
|
128
|
+
case "Home":
|
|
129
|
+
this.setCurrent(0);
|
|
130
|
+
break;
|
|
131
|
+
case "End":
|
|
132
|
+
this.setCurrent(this._collection.items().length - 1);
|
|
133
|
+
break;
|
|
134
|
+
default: handled = false;
|
|
135
|
+
}
|
|
136
|
+
if (handled) ev.preventDefault();
|
|
137
|
+
};
|
|
138
|
+
_syncFromAttrs() {
|
|
139
|
+
const o = this.getAttribute("orientation");
|
|
140
|
+
if (o === "horizontal" || o === "vertical" || o === "both") this._orientation[1](o);
|
|
141
|
+
this._loop[1](this.hasAttribute("loop"));
|
|
142
|
+
const dir = this.getAttribute("dir");
|
|
143
|
+
this._dirAttr[1](dir === "rtl" ? "rtl" : dir === "ltr" ? "ltr" : null);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
let _defined = false;
|
|
147
|
+
/** Register `<aihu-roving-focus>` (idempotent). */
|
|
148
|
+
function defineRovingFocus(tag = "aihu-roving-focus") {
|
|
149
|
+
if (_defined || customElements.get(tag)) {
|
|
150
|
+
_defined = true;
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
customElements.define(tag, AihuRovingFocus);
|
|
154
|
+
_defined = true;
|
|
155
|
+
}
|
|
156
|
+
//#endregion
|
|
157
|
+
export { AihuRovingFocus, defineRovingFocus };
|
|
158
|
+
|
|
159
|
+
//# sourceMappingURL=roving-focus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roving-focus.js","names":[],"sources":["../src/roving-focus/index.ts"],"sourcesContent":["/**\n * `<aihu-roving-focus>` — WAI-ARIA roving-tabindex container. Exactly one item\n * carries `tabindex=\"0\"` (the current); all others `tabindex=\"-1\"`. Arrow keys\n * move the current index (respecting `orientation` + `loop` + RTL `dir`),\n * Home/End jump to the first/last item, and `element.focus()` follows. It\n * imposes NO role (the consumer sets `role=\"toolbar\"`/`\"menu\"`/etc.) and ships\n * NO CSS.\n *\n * Items are enumerated via the `collection` substrate (Task 8): each focusable\n * descendant registers itself; the container reuses `createCollection()` so\n * item order tracks the DOM.\n *\n * Reflected attributes: `orientation` (`\"horizontal\" | \"vertical\" | \"both\"`),\n * `loop` (boolean), `dir` (`\"ltr\" | \"rtl\"` — inherited from a `config-provider`\n * ancestor if present).\n * Owned signals: `currentIndex`, `items` (from the collection), `activeId`\n * (computed).\n */\n\nimport { computed, effect, type Read, signal } from '@aihu/signals'\nimport { collectionContext, createCollection } from '../collection/index.ts'\nimport { configContext, type Direction } from '../config-provider/index.ts'\nimport { injectValue, provideContext } from '../dom-context.ts'\n\nexport type Orientation = 'horizontal' | 'vertical' | 'both'\n\nexport class AihuRovingFocus extends HTMLElement {\n static readonly observedAttributes = ['orientation', 'loop', 'dir']\n\n private readonly _collection = createCollection()\n private readonly _currentIndex = signal(0)\n private readonly _loop = signal(false)\n private readonly _orientation = signal<Orientation>('horizontal')\n private readonly _dirAttr = signal<Direction | null>(null)\n\n readonly activeId: Read<string | null>\n\n private _disposers: Array<() => void> = []\n\n get currentIndex(): Read<number> {\n return this._currentIndex[0]\n }\n get items(): Read<Element[]> {\n return this._collection.items\n }\n\n constructor() {\n super()\n // Reuse the collection substrate for descendant enumeration.\n provideContext(this, collectionContext, this._collection)\n this.activeId = computed(() => {\n const items = this._collection.items()\n const el = items[this._currentIndex[0]()]\n return el ? el.id || null : null\n })\n }\n\n connectedCallback(): void {\n this._syncFromAttrs()\n this.addEventListener('keydown', this._onKeydown)\n\n // Stamp roving tabindex whenever items or currentIndex change.\n this._disposers.push(\n effect(() => {\n const items = this._collection.items()\n const current = this._currentIndex[0]()\n items.forEach((el, i) => {\n el.setAttribute('tabindex', i === current ? '0' : '-1')\n })\n }),\n )\n }\n\n disconnectedCallback(): void {\n this.removeEventListener('keydown', this._onKeydown)\n for (const d of this._disposers) d()\n this._disposers = []\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n switch (name) {\n case 'orientation':\n if (value === 'horizontal' || value === 'vertical' || value === 'both')\n this._orientation[1](value)\n break\n case 'loop':\n this._loop[1](value !== null)\n break\n case 'dir':\n this._dirAttr[1](value === 'rtl' ? 'rtl' : value === 'ltr' ? 'ltr' : null)\n break\n }\n }\n\n /** Register a focusable descendant; returns an unregister disposer. */\n register(el: Element): () => void {\n return this._collection.register(el)\n }\n\n /** Move the current index to `index` (clamped/looped) and focus the item. */\n setCurrent(index: number): void {\n const items = this._collection.items()\n if (items.length === 0) return\n let next = index\n if (this._loop[0]()) {\n next = ((index % items.length) + items.length) % items.length\n } else {\n next = Math.max(0, Math.min(items.length - 1, index))\n }\n this._currentIndex[1](next)\n ;(items[next] as HTMLElement).focus?.()\n }\n\n /** Resolve the effective direction (own attr, else nearest config-provider). */\n private _direction(): Direction {\n const own = this._dirAttr[0]()\n if (own) return own\n try {\n return injectValue(this, configContext).dir()\n } catch {\n return 'ltr'\n }\n }\n\n private readonly _onKeydown = (ev: KeyboardEvent): void => {\n const orientation = this._orientation[0]()\n const horizontal = orientation === 'horizontal' || orientation === 'both'\n const vertical = orientation === 'vertical' || orientation === 'both'\n const rtl = this._direction() === 'rtl'\n const current = this._currentIndex[0]()\n\n let handled = true\n switch (ev.key) {\n case 'ArrowRight':\n if (horizontal) this.setCurrent(current + (rtl ? -1 : 1))\n else handled = false\n break\n case 'ArrowLeft':\n if (horizontal) this.setCurrent(current + (rtl ? 1 : -1))\n else handled = false\n break\n case 'ArrowDown':\n if (vertical) this.setCurrent(current + 1)\n else handled = false\n break\n case 'ArrowUp':\n if (vertical) this.setCurrent(current - 1)\n else handled = false\n break\n case 'Home':\n this.setCurrent(0)\n break\n case 'End':\n this.setCurrent(this._collection.items().length - 1)\n break\n default:\n handled = false\n }\n if (handled) ev.preventDefault()\n }\n\n private _syncFromAttrs(): void {\n const o = this.getAttribute('orientation')\n if (o === 'horizontal' || o === 'vertical' || o === 'both') this._orientation[1](o)\n this._loop[1](this.hasAttribute('loop'))\n const dir = this.getAttribute('dir')\n this._dirAttr[1](dir === 'rtl' ? 'rtl' : dir === 'ltr' ? 'ltr' : null)\n }\n}\n\nlet _defined = false\n/** Register `<aihu-roving-focus>` (idempotent). */\nexport function defineRovingFocus(tag = 'aihu-roving-focus'): void {\n if (_defined || customElements.get(tag)) {\n _defined = true\n return\n }\n customElements.define(tag, AihuRovingFocus)\n _defined = true\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { DomContext } from "./dom-context.js";
|
|
2
|
+
import { Read } from "@aihu/signals";
|
|
3
|
+
import { Placement } from "@aihu/css-engine/runtime/progressive";
|
|
4
|
+
|
|
5
|
+
//#region src/tooltip/index.d.ts
|
|
6
|
+
interface TooltipCoords {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
placement: Placement;
|
|
10
|
+
}
|
|
11
|
+
interface TooltipContextValue {
|
|
12
|
+
readonly open: Read<boolean>;
|
|
13
|
+
readonly contentId: Read<string>;
|
|
14
|
+
readonly placement: Read<Placement>;
|
|
15
|
+
readonly openDelay: Read<number>;
|
|
16
|
+
readonly closeDelay: Read<number>;
|
|
17
|
+
setOpen(next: boolean): void;
|
|
18
|
+
/** Schedule open/close honoring the configured delays. */
|
|
19
|
+
scheduleOpen(): void;
|
|
20
|
+
scheduleClose(): void;
|
|
21
|
+
/** Immediate dismiss (Escape). */
|
|
22
|
+
dismiss(): void;
|
|
23
|
+
registerTrigger(el: Element): void;
|
|
24
|
+
triggerEl(): Element | null;
|
|
25
|
+
}
|
|
26
|
+
declare const tooltipContext: DomContext<TooltipContextValue>;
|
|
27
|
+
declare class AihuTooltipRoot extends HTMLElement {
|
|
28
|
+
static readonly observedAttributes: string[];
|
|
29
|
+
private readonly _open;
|
|
30
|
+
private readonly _contentId;
|
|
31
|
+
private readonly _placement;
|
|
32
|
+
private readonly _openDelay;
|
|
33
|
+
private readonly _closeDelay;
|
|
34
|
+
private _trigger;
|
|
35
|
+
private _openTimer;
|
|
36
|
+
private _closeTimer;
|
|
37
|
+
private _disposers;
|
|
38
|
+
private _ctx;
|
|
39
|
+
constructor();
|
|
40
|
+
get open(): Read<boolean>;
|
|
41
|
+
connectedCallback(): void;
|
|
42
|
+
disconnectedCallback(): void;
|
|
43
|
+
attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
|
|
44
|
+
private _setOpen;
|
|
45
|
+
private _scheduleOpen;
|
|
46
|
+
private _scheduleClose;
|
|
47
|
+
private _clearTimers;
|
|
48
|
+
private _syncFromAttrs;
|
|
49
|
+
}
|
|
50
|
+
declare class AihuTooltipTrigger extends HTMLElement {
|
|
51
|
+
private ctx;
|
|
52
|
+
private disposers;
|
|
53
|
+
connectedCallback(): void;
|
|
54
|
+
disconnectedCallback(): void;
|
|
55
|
+
private readonly _onEnter;
|
|
56
|
+
private readonly _onLeave;
|
|
57
|
+
private readonly _onKeydown;
|
|
58
|
+
}
|
|
59
|
+
declare class AihuTooltipContent extends HTMLElement {
|
|
60
|
+
private ctx;
|
|
61
|
+
private disposers;
|
|
62
|
+
connectedCallback(): void;
|
|
63
|
+
disconnectedCallback(): void;
|
|
64
|
+
/** Position against the trigger using the REUSED css-engine shim. */
|
|
65
|
+
private _position;
|
|
66
|
+
private readonly _onKeydown;
|
|
67
|
+
}
|
|
68
|
+
/** Register all tooltip custom elements (idempotent). */
|
|
69
|
+
declare function defineTooltip(): void;
|
|
70
|
+
//#endregion
|
|
71
|
+
export { AihuTooltipContent, AihuTooltipRoot, AihuTooltipTrigger, TooltipContextValue, TooltipCoords, defineTooltip, tooltipContext };
|
|
72
|
+
//# sourceMappingURL=tooltip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tooltip.d.ts","names":[],"sources":["../src/tooltip/index.ts"],"mappings":";;;;;UAoBiB,aAAA;EACf,CAAA;EACA,CAAA;EACA,SAAA,EAAW,SAAA;AAAA;AAAA,UAGI,mBAAA;EAAA,SACN,IAAA,EAAM,IAAA;EAAA,SACN,SAAA,EAAW,IAAA;EAAA,SACX,SAAA,EAAW,IAAA,CAAK,SAAA;EAAA,SAChB,SAAA,EAAW,IAAA;EAAA,SACX,UAAA,EAAY,IAAA;EACrB,OAAA,CAAQ,IAAA;EALO;EAOf,YAAA;EACA,aAAA;EANS;EAQT,OAAA;EACA,eAAA,CAAgB,EAAA,EAAI,OAAA;EACpB,SAAA,IAAa,OAAA;AAAA;AAAA,cAGF,cAAA,EAAc,UAAA,CAAA,mBAAA;AAAA,cAKd,eAAA,SAAwB,WAAA;EAAA,gBACnB,kBAAA;EAAA,iBAEC,KAAA;EAAA,iBACA,UAAA;EAAA,iBACA,UAAA;EAAA,iBACA,UAAA;EAAA,iBACA,WAAA;EAAA,QACT,QAAA;EAAA,QACA,UAAA;EAAA,QACA,WAAA;EAAA,QACA,UAAA;EAAA,QACA,IAAA;;MAsBJ,IAAA,CAAA,GAAQ,IAAA;EAIZ,iBAAA,CAAA;EASA,oBAAA,CAAA;EAMA,wBAAA,CAAyB,IAAA,UAAc,IAAA,iBAAqB,KAAA;EAAA,QAiBpD,QAAA;EAAA,QAQA,aAAA;EAAA,QAKA,cAAA;EAAA,QAKA,YAAA;EAAA,QAOA,cAAA;AAAA;AAAA,cAcG,kBAAA,SAA2B,WAAA;EAAA,QAC9B,GAAA;EAAA,QACA,SAAA;EAER,iBAAA,CAAA;EAiBA,oBAAA,CAAA;EAAA,iBAKiB,QAAA;EAAA,iBAGA,QAAA;EAAA,iBAGA,UAAA;AAAA;AAAA,cAKN,kBAAA,SAA2B,WAAA;EAAA,QAC9B,GAAA;EAAA,QACA,SAAA;EAER,iBAAA,CAAA;EAeA,oBAAA,CAAA;EA/HA;EAAA,QAsIQ,SAAA;EAAA,iBAMS,UAAA;AAAA;;iBAaH,aAAA,CAAA"}
|