@primitiv-ui/web 0.1.54 → 0.1.56
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/README.md +65 -3
- package/dist/cdn/chunks/chunk-PKW6AKXD.js +1 -0
- package/dist/cdn/modal.js +1 -1
- package/dist/cdn/popover.js +1 -0
- package/dist/cdn/tooltip.js +1 -0
- package/dist/custom-elements.json +840 -104
- package/dist/define/index.d.ts +2 -0
- package/dist/define/index.js +2 -0
- package/dist/define/popover.d.ts +1 -0
- package/dist/define/popover.js +11 -0
- package/dist/define/tooltip.d.ts +1 -0
- package/dist/define/tooltip.js +11 -0
- package/dist/frameworks/events.d.ts +8 -0
- package/dist/frameworks/react.d.ts +31 -2
- package/dist/frameworks/svelte.d.ts +26 -1
- package/dist/frameworks/vue.d.ts +26 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/popover/PopoverController.d.ts +55 -0
- package/dist/popover/PopoverController.js +100 -0
- package/dist/popover/elements.d.ts +98 -0
- package/dist/popover/elements.js +230 -0
- package/dist/popover/index.d.ts +2 -0
- package/dist/popover/index.js +2 -0
- package/dist/primitiv-web.js +1 -1
- package/dist/tags.d.ts +14 -0
- package/dist/tooltip/TooltipController.d.ts +66 -0
- package/dist/tooltip/TooltipController.js +115 -0
- package/dist/tooltip/TooltipGroup.d.ts +23 -0
- package/dist/tooltip/TooltipGroup.js +44 -0
- package/dist/tooltip/elements.d.ts +83 -0
- package/dist/tooltip/elements.js +222 -0
- package/dist/tooltip/index.d.ts +3 -0
- package/dist/tooltip/index.js +3 -0
- package/package.json +18 -2
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { defineElements, ElementBase, reportOnce, write } from "../internal/element.js";
|
|
2
|
+
import { PrimitivRootElement } from "../internal/RootElement.js";
|
|
3
|
+
import { PopoverController } from "./PopoverController.js";
|
|
4
|
+
// Stryker disable StringLiteral: static — evaluated once at import, so runtime mutant switching never reaches them; the names are pinned by the definePopover registry test instead.
|
|
5
|
+
const ROOT = "primitiv-popover";
|
|
6
|
+
const TRIGGER = "primitiv-popover-trigger";
|
|
7
|
+
const ANCHOR = "primitiv-popover-anchor";
|
|
8
|
+
const CONTENT = "primitiv-popover-content";
|
|
9
|
+
const TITLE = "primitiv-popover-title";
|
|
10
|
+
const DESCRIPTION = "primitiv-popover-description";
|
|
11
|
+
const CLOSE = "primitiv-popover-close";
|
|
12
|
+
// Stryker restore StringLiteral
|
|
13
|
+
/**
|
|
14
|
+
* The one element child `part` decorates — the `<button>`, heading or anchor
|
|
15
|
+
* element the consumer writes — or `null`, reported once, when it has anything
|
|
16
|
+
* but exactly one (RFC 0033 D24). `what` names the child in the report.
|
|
17
|
+
*/
|
|
18
|
+
function decoratable(part, what) {
|
|
19
|
+
if (part.childElementCount === 1)
|
|
20
|
+
return part.firstElementChild;
|
|
21
|
+
reportOnce(part, `<${part.localName}> needs exactly one element child (${what}) to decorate.`);
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* `<primitiv-popover>` — the state owner of a non-modal popover: a trigger
|
|
26
|
+
* that opens a floating panel, built on the native Popover API. The browser
|
|
27
|
+
* puts the panel in the top layer and light-dismisses it; the element adds the
|
|
28
|
+
* rest of the WAI-ARIA non-modal dialog pattern — focus moves into the panel
|
|
29
|
+
* as it opens, Escape closes it and hands focus back to the trigger, and the
|
|
30
|
+
* trigger's `aria-expanded` follows it.
|
|
31
|
+
*
|
|
32
|
+
* The panel is shown with its trigger (or the anchor) as its source, which
|
|
33
|
+
* makes that element the panel's implicit anchor, so CSS anchor positioning
|
|
34
|
+
* (`position-area`) places it with no `anchor-name` to wire.
|
|
35
|
+
*
|
|
36
|
+
* The root finds its own parts, so a popover nested in a panel keeps its own,
|
|
37
|
+
* and reconciles their ARIA from its reflected state whenever a part arrives,
|
|
38
|
+
* leaves, or changes an attribute the root reads (the lifecycle is
|
|
39
|
+
* `PrimitivRootElement`'s).
|
|
40
|
+
*
|
|
41
|
+
* @customElement primitiv-popover
|
|
42
|
+
* @summary The state owner of a non-modal popover on the native Popover API.
|
|
43
|
+
* @attr {boolean} open - Whether the panel is open. Reflected: it changes as the user opens and dismisses it, so a server re-render or a history snapshot sees the live state. Set it (or the `open` property) to open or close the panel from script.
|
|
44
|
+
* @fires {CustomEvent<{ open: boolean }>} primitiv-popover-change - Bubbles, cancelable, fired before a user-driven change is applied — a trigger or close click, Escape, or a light dismiss — with the open state afterwards. Call `preventDefault()` to veto it.
|
|
45
|
+
*/
|
|
46
|
+
export class PrimitivPopoverElement extends PrimitivRootElement {
|
|
47
|
+
/** The panel the root currently decorates, and the controller over it. */
|
|
48
|
+
#content = null;
|
|
49
|
+
#popover = null;
|
|
50
|
+
/** The decorated trigger and close buttons, and the anchor, as of the last reconcile. */
|
|
51
|
+
#trigger = null;
|
|
52
|
+
#anchor = null;
|
|
53
|
+
// Stryker disable next-line ArrayDeclaration: equivalent — the first reconcile runs as the root connects, before any click can reach it, and replaces it.
|
|
54
|
+
#closers = [];
|
|
55
|
+
/** The root's own click listener, while connected. */
|
|
56
|
+
#listening = null;
|
|
57
|
+
controller = {
|
|
58
|
+
connect: () => this.#connect(),
|
|
59
|
+
disconnect: () => this.#disconnect(),
|
|
60
|
+
};
|
|
61
|
+
reconcileOn = ["open"];
|
|
62
|
+
/** Whether the panel is open, reflected as the boolean `open` attribute. */
|
|
63
|
+
get open() {
|
|
64
|
+
return this.hasAttribute("open");
|
|
65
|
+
}
|
|
66
|
+
set open(next) {
|
|
67
|
+
this.toggleAttribute("open", next);
|
|
68
|
+
}
|
|
69
|
+
/** The controller's view of this element: state lives in the reflected attribute. */
|
|
70
|
+
#host() {
|
|
71
|
+
return {
|
|
72
|
+
state: () => ({ open: this.open }),
|
|
73
|
+
change: (open) => this.#change(open),
|
|
74
|
+
trigger: () => this.#trigger,
|
|
75
|
+
source: () => this.#anchor ?? this.#trigger,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/** A change the user asked for: announced first, so a listener can veto it. */
|
|
79
|
+
#change(open) {
|
|
80
|
+
if (open === this.open)
|
|
81
|
+
return;
|
|
82
|
+
if (this.announce("primitiv-popover-change", { open }))
|
|
83
|
+
this.open = open;
|
|
84
|
+
// A veto can leave the panel out of step — the browser is light-dismissing
|
|
85
|
+
// it — so put it back where the state says, once that dismissal is done.
|
|
86
|
+
else
|
|
87
|
+
queueMicrotask(() => this.#popover?.sync());
|
|
88
|
+
}
|
|
89
|
+
#connect() {
|
|
90
|
+
this.#listening = new AbortController();
|
|
91
|
+
this.addEventListener("click", (event) => this.#click(event), { signal: this.#listening.signal });
|
|
92
|
+
this.#popover?.connect();
|
|
93
|
+
}
|
|
94
|
+
#disconnect() {
|
|
95
|
+
// A root disconnects only after it connected, so it is listening.
|
|
96
|
+
this.#listening.abort();
|
|
97
|
+
this.#listening = null;
|
|
98
|
+
this.#popover?.disconnect();
|
|
99
|
+
}
|
|
100
|
+
#click(event) {
|
|
101
|
+
const target = event.target;
|
|
102
|
+
if (this.#trigger?.contains(target))
|
|
103
|
+
this.#change(!this.open);
|
|
104
|
+
else if (this.#closers.some((button) => button.contains(target)))
|
|
105
|
+
this.#change(false);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Decorate `content` from now on: the controller follows it. Only a
|
|
109
|
+
* reconcile calls this, and a root reconciles only while connected, so the
|
|
110
|
+
* new controller listens at once.
|
|
111
|
+
*/
|
|
112
|
+
#use(content) {
|
|
113
|
+
// Stryker disable next-line ConditionalExpression: equivalent — rebuilding the controller over the same panel swaps one listener set for an identical one.
|
|
114
|
+
if (content === this.#content)
|
|
115
|
+
return;
|
|
116
|
+
this.#popover?.disconnect();
|
|
117
|
+
this.#content = content;
|
|
118
|
+
this.#popover = content ? new PopoverController(content, this.#host()) : null;
|
|
119
|
+
this.#popover?.connect();
|
|
120
|
+
}
|
|
121
|
+
reconcile() {
|
|
122
|
+
const open = this.open;
|
|
123
|
+
const state = open ? "open" : "closed";
|
|
124
|
+
const rootId = this.rootId();
|
|
125
|
+
write(this, "data-state", state);
|
|
126
|
+
const [content = null] = this.parts(CONTENT);
|
|
127
|
+
const [triggerPart] = this.parts(TRIGGER);
|
|
128
|
+
const [anchorPart] = this.parts(ANCHOR);
|
|
129
|
+
const [titlePart] = this.parts(TITLE);
|
|
130
|
+
const [descriptionPart] = this.parts(DESCRIPTION);
|
|
131
|
+
this.#trigger = triggerPart ? decoratable(triggerPart, "a <button>") : null;
|
|
132
|
+
this.#anchor = anchorPart ? decoratable(anchorPart, "the element to anchor to") : null;
|
|
133
|
+
const title = titlePart ? decoratable(titlePart, "a heading") : null;
|
|
134
|
+
const description = descriptionPart ? decoratable(descriptionPart, "a <p>") : null;
|
|
135
|
+
if (title && !title.id)
|
|
136
|
+
title.id = `${rootId}-title`;
|
|
137
|
+
if (description && !description.id)
|
|
138
|
+
description.id = `${rootId}-description`;
|
|
139
|
+
if (content) {
|
|
140
|
+
if (!content.id)
|
|
141
|
+
content.id = `${rootId}-content`;
|
|
142
|
+
write(content, "role", "dialog");
|
|
143
|
+
if (!content.hasAttribute("popover"))
|
|
144
|
+
content.setAttribute("popover", "auto");
|
|
145
|
+
write(content, "tabindex", "-1");
|
|
146
|
+
write(content, "data-state", state);
|
|
147
|
+
write(content, "aria-labelledby", title ? title.id : null);
|
|
148
|
+
write(content, "aria-describedby", description ? description.id : null);
|
|
149
|
+
}
|
|
150
|
+
if (this.#trigger) {
|
|
151
|
+
write(this.#trigger, "aria-haspopup", "dialog");
|
|
152
|
+
write(this.#trigger, "aria-expanded", String(open));
|
|
153
|
+
write(this.#trigger, "aria-controls", content ? content.id : null);
|
|
154
|
+
}
|
|
155
|
+
this.#closers = this.parts(CLOSE).flatMap((part) => decoratable(part, "a <button>") ?? []);
|
|
156
|
+
this.#use(content);
|
|
157
|
+
this.#popover?.sync();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* `<primitiv-popover-trigger>` — an interactive part. It decorates exactly one
|
|
162
|
+
* child the consumer writes, a `<button>`, which toggles the panel and receives
|
|
163
|
+
* `aria-haspopup="dialog"`, `aria-expanded` and `aria-controls`, so the
|
|
164
|
+
* rendered DOM matches React's (RFC 0033 D24).
|
|
165
|
+
*
|
|
166
|
+
* @customElement primitiv-popover-trigger
|
|
167
|
+
*/
|
|
168
|
+
export class PrimitivPopoverTriggerElement extends ElementBase {
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* `<primitiv-popover-anchor>` — optional. It decorates exactly one element the
|
|
172
|
+
* consumer writes, which the panel is anchored to in place of the trigger: a
|
|
173
|
+
* field, say, whose trigger is an icon inside it.
|
|
174
|
+
*
|
|
175
|
+
* @customElement primitiv-popover-anchor
|
|
176
|
+
*/
|
|
177
|
+
export class PrimitivPopoverAnchorElement extends ElementBase {
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* `<primitiv-popover-content>` — the panel. Structural: the root gives it an
|
|
181
|
+
* id, `role="dialog"`, `popover="auto"` (write it in your markup too, so the
|
|
182
|
+
* panel is hidden before the script loads), `tabindex="-1"`, `data-state`, and
|
|
183
|
+
* `aria-labelledby` / `aria-describedby` pointing at the title and description.
|
|
184
|
+
*
|
|
185
|
+
* @customElement primitiv-popover-content
|
|
186
|
+
*/
|
|
187
|
+
export class PrimitivPopoverContentElement extends ElementBase {
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* `<primitiv-popover-title>` — decorates exactly one heading the consumer
|
|
191
|
+
* writes, whose id becomes the panel's `aria-labelledby`.
|
|
192
|
+
*
|
|
193
|
+
* @customElement primitiv-popover-title
|
|
194
|
+
*/
|
|
195
|
+
export class PrimitivPopoverTitleElement extends ElementBase {
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* `<primitiv-popover-description>` — decorates exactly one element the
|
|
199
|
+
* consumer writes (a `<p>`, usually), whose id becomes the panel's
|
|
200
|
+
* `aria-describedby`.
|
|
201
|
+
*
|
|
202
|
+
* @customElement primitiv-popover-description
|
|
203
|
+
*/
|
|
204
|
+
export class PrimitivPopoverDescriptionElement extends ElementBase {
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* `<primitiv-popover-close>` — an interactive part. It decorates exactly one
|
|
208
|
+
* `<button>` the consumer writes, which closes the panel. A popover may have
|
|
209
|
+
* several.
|
|
210
|
+
*
|
|
211
|
+
* @customElement primitiv-popover-close
|
|
212
|
+
*/
|
|
213
|
+
export class PrimitivPopoverCloseElement extends ElementBase {
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Define the seven Popover elements on `registry` (the global `customElements`
|
|
217
|
+
* by default). Safe to call more than once, and a no-op where there is no
|
|
218
|
+
* registry (a server render).
|
|
219
|
+
*/
|
|
220
|
+
export function definePopover(registry = globalThis.customElements) {
|
|
221
|
+
defineElements(registry, [
|
|
222
|
+
[ROOT, PrimitivPopoverElement],
|
|
223
|
+
[TRIGGER, PrimitivPopoverTriggerElement],
|
|
224
|
+
[ANCHOR, PrimitivPopoverAnchorElement],
|
|
225
|
+
[CONTENT, PrimitivPopoverContentElement],
|
|
226
|
+
[TITLE, PrimitivPopoverTitleElement],
|
|
227
|
+
[DESCRIPTION, PrimitivPopoverDescriptionElement],
|
|
228
|
+
[CLOSE, PrimitivPopoverCloseElement],
|
|
229
|
+
]);
|
|
230
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { PopoverController, type PopoverControllerHost } from "./PopoverController.js";
|
|
2
|
+
export { definePopover, PrimitivPopoverAnchorElement, PrimitivPopoverCloseElement, PrimitivPopoverContentElement, PrimitivPopoverDescriptionElement, PrimitivPopoverElement, PrimitivPopoverTitleElement, PrimitivPopoverTriggerElement, } from "./elements.js";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { PopoverController } from "./PopoverController.js";
|
|
2
|
+
export { definePopover, PrimitivPopoverAnchorElement, PrimitivPopoverCloseElement, PrimitivPopoverContentElement, PrimitivPopoverDescriptionElement, PrimitivPopoverElement, PrimitivPopoverTitleElement, PrimitivPopoverTriggerElement, } from "./elements.js";
|
package/dist/primitiv-web.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function v(e,t,o){return`${e}-${t}-${o}`}function M({orientation:e,dir:t,homeEnd:o=!1,activate:n=!1}){let l={};if(e==="horizontal"||e==="both"){let s=t==="rtl"?"ArrowLeft":"ArrowRight",d=t==="rtl"?"ArrowRight":"ArrowLeft";l[s]="next",l[d]="prev"}return(e==="vertical"||e==="both")&&(l.ArrowDown="next",l.ArrowUp="prev"),o&&(l.Home="first",l.End="last"),n&&(l.Enter="activate",l[" "]="activate"),l}function O({action:e,navigable:t,currentKey:o}){let n=t.length;if(n===0)return null;switch(e){case"activate":return{target:o};case"first":return{target:t[0]};case"last":return{target:t[n-1]};case void 0:return null;case"next":case"prev":{let l=t.indexOf(o);if(l===-1)return null;let s=e==="next"?1:n-1;return{target:t[(l+s)%n]}}}}function V(e){return e.active??e.values[0]}function w(e,t){return e.disabled.has(t)||e.active===t?null:t}function X(e,t,o){let n=M({orientation:e.orientation,dir:e.dir,homeEnd:!0,activate:!0})[o],l=O({action:n,navigable:e.values,currentKey:t});if(!l)return null;let s=l.target,d=e.activationMode==="automatic"||n==="activate";return{focus:s,activate:d?w(e,s):null}}function Y(e,t){return e.disabled.has(t)?null:e.expanded.includes(t)?e.expanded.filter(o=>o!==t):e.multiple?[...e.expanded,t]:[t]}function J(e,t,o){let n=M({orientation:e.orientation,dir:e.dir,homeEnd:!0,activate:!0})[o];if(n==="activate")return e.disabled.has(t)?null:{focus:null,toggle:t};let l=e.values.filter(d=>!e.disabled.has(d)),s=O({action:n,navigable:l,currentKey:t});return s&&{focus:s.target,toggle:null}}function H(e){return e.disabled?null:!e.open}function Q(e,t){if(t!=="Enter"&&t!==" ")return null;let o=H(e);return o===null?null:{open:o}}var Z=["a[href]","button:not([disabled])","input:not([disabled])","select:not([disabled])","textarea:not([disabled])",'[tabindex]:not([tabindex="-1"])'].join(",");function tt({key:e,shiftKey:t,active:o,count:n}){if(e!=="Tab")return null;if(n===0)return{focus:null};let l=n-1;return!t&&o===l?{focus:0}:t&&o===0?{focus:l}:null}function et(e,t){return t.x<e.left||t.x>e.right||t.y<e.top||t.y>e.bottom}var b=class{#t;#e=new Map;#o=null;constructor(t){this.#t=t}registerTrigger(t,o,n={}){this.#e.set(o,{value:t,disabled:n.disabled??!1})}setTriggers(t){this.#e.clear();for(let{value:o,element:n,disabled:l}of t)this.registerTrigger(o,n,{disabled:l})}unregisterTrigger(t){this.#e.delete(t)}connect(){if(this.#o)return;this.#o=new AbortController;let{signal:t}=this.#o;this.#t.addEventListener("click",o=>this.#n(o,this.onTriggerClick),{signal:t}),this.#t.addEventListener("keydown",o=>this.#n(o,this.onTriggerKeydown),{signal:t})}disconnect(){this.#o?.abort(),this.#o=null}ordered(){return[...this.#e].sort(([t],[o])=>t.compareDocumentPosition(o)&Node.DOCUMENT_POSITION_FOLLOWING?-1:1)}disabled(){let t=new Set;for(let o of this.#e.values())o.disabled&&t.add(o.value);return t}#n(t,o){for(let[n,l]of this.#e)if(n.contains(t.target))return o.call(this,l,t)}};var x=class extends b{#t;constructor(t,o){super(t),this.#t=o}#e(t){let{expanded:o,multiple:n}=this.#t.state(),l=Y({disabled:this.disabled(),expanded:o,multiple:n},t);l!==null&&this.#t.change(l)}onTriggerClick(t){this.#e(t.value)}onTriggerKeydown(t,o){let{expanded:n,multiple:l,orientation:s,dir:d}=this.#t.state(),u=this.ordered(),a=u.map(([,c])=>c.value),i=J({values:a,disabled:this.disabled(),expanded:n,multiple:l,orientation:s,dir:d},t.value,o.key);i&&(o.preventDefault(),i.toggle!==null&&this.#e(i.toggle),i.focus!==null&&u[a.indexOf(i.focus)][0].focus())}};var p=globalThis.HTMLElement??class{};function r(e,t,o){e.getAttribute(t)!==o&&(o===null?e.removeAttribute(t):e.setAttribute(t,o))}var gt=new WeakSet;function h(e,t){gt.has(e)||(gt.add(e),console.error(t,e))}function f(e,t){if(e)for(let[o,n]of t)e.get(o)||e.define(o,n)}function y(e,t,o){return[...e.querySelectorAll(t)].filter(n=>n.closest(o)===e)}var At=0,m=class extends p{#t;#e=new MutationObserver(()=>this.reconcile());connectedCallback(){this.controller.connect(),this.#e.observe(this,{subtree:!0,childList:!0,attributes:!0,attributeFilter:[...this.reconcileOn]}),this.reconcile()}disconnectedCallback(){this.controller.disconnect(),this.#e.disconnect()}rootId(){return this.id||(this.#t??=`${this.localName}-${++At}`)}direction(){return this.matches(":dir(rtl)")?"rtl":"ltr"}parts(t){return y(this,t,this.localName)}announce(t,o){return this.dispatchEvent(new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:o}))}};var Mt="primitiv-accordion",T="primitiv-accordion-item",ot="primitiv-accordion-trigger",ht="primitiv-accordion-content",mt="primitiv-accordion-trigger-icon";function Ot(e){return(e??"").split(/\s+/).filter(Boolean)}function wt(e){return e.childElementCount===1?e.firstElementChild:(h(e,`<${ot}> needs exactly one element child (a <button>) to decorate.`),null)}var R=class extends m{#t=new Set;#e=!1;controller=new x(this,this.#o());reconcileOn=["value","disabled","orientation","force-mount"];get value(){return Ot(this.getAttribute("value"))}set value(t){this.setAttribute("value",typeof t=="string"?t:t.join(" "))}#o(){return{state:()=>({expanded:this.value,multiple:this.hasAttribute("multiple"),orientation:this.#n(),dir:this.direction()}),change:t=>{this.announce("primitiv-accordion-change",{value:t})&&(this.value=t)}}}#n(){return this.getAttribute("orientation")==="horizontal"?"horizontal":"vertical"}reconcile(){let t=new Set(this.value),o=this.rootId();r(this,"data-orientation",this.#n());let n=[];for(let l of this.parts(T)){let s=l.getAttribute("value");if(s===null){h(l,`<${T}> needs a value, which pairs it with the root's value.`);continue}let d=t.has(s),u=d?"open":"closed",[a]=y(l,ot,T),[i]=y(l,ht,T),c=a?wt(a):null;if(c){let g=a.hasAttribute("disabled");n.push({value:s,element:c,disabled:g}),c.id||(c.id=v(o,"heading",s)),r(c,"aria-expanded",String(d)),r(c,"aria-disabled",String(g)),r(c,"data-disabled",String(g)),r(c,"data-state",u)}if(i){i.id||(i.id=v(o,"panel",s));let g=i.hasAttribute("force-mount");i.hasAttribute("role")||i.setAttribute("role","region"),r(i,"aria-labelledby",c?c.id:null),r(i,"hidden",d||g?null:""),r(i,"aria-hidden",d?null:"true"),r(i,"data-state",u),this.#e&&d&&!this.#t.has(s)&&i.dispatchEvent(new CustomEvent("primitiv-accordion-show",{bubbles:!0}))}c&&r(c,"aria-controls",i?i.id:null);for(let g of y(l,mt,T))r(g,"aria-hidden","true"),r(g,"data-state",u)}this.controller.setTriggers(n),this.#t=t,this.#e=!0}},L=class extends p{},K=class extends p{},S=class extends p{},k=class extends p{};function nt(e=globalThis.customElements){f(e,[[Mt,R],[T,L],[ot,K],[ht,S],[mt,k]])}var E=class extends b{#t;constructor(t,o){super(t),this.#t=o}onTriggerClick(t){let o=H({open:this.#t.state().open,disabled:t.disabled});o!==null&&this.#t.change(o)}onTriggerKeydown(t,o){let n=Q({open:this.#t.state().open,disabled:t.disabled},o.key);n&&(o.preventDefault(),this.#t.change(n.open))}};var Ht="primitiv-collapsible",it="primitiv-collapsible-trigger",bt="primitiv-collapsible-content",ft="primitiv-collapsible-trigger-icon",Rt="--primitiv-collapsible-collapsed-height";function Lt(e){return e.childElementCount===1?e.firstElementChild:(h(e,`<${it}> needs exactly one element child (a <button>) to decorate.`),null)}function Kt(e){return Number.isNaN(Number(e))?e:`${e}px`}function St(e,t,o){e.style.getPropertyValue(t)!==o&&(e.style.setProperty(t,o),e.getAttribute("style")===""&&e.removeAttribute("style"))}var I=class extends m{#t;controller=new E(this,this.#e());reconcileOn=["open","disabled","force-mount","collapsed-height"];get open(){return this.hasAttribute("open")}set open(t){this.toggleAttribute("open",t)}#e(){return{state:()=>({open:this.open}),change:t=>{this.announce("primitiv-collapsible-change",{open:t})&&(this.open=t)}}}reconcile(){let t=this.open,o=t?"open":"closed",n=this.hasAttribute("disabled"),l=this.rootId();r(this,"data-state",o),r(this,"data-disabled",String(n));let[s]=this.parts(it),[d]=this.parts(bt),u=s?Lt(s):null;if(u&&(u.id||(u.id=`${l}-trigger`),r(u,"aria-expanded",String(t)),r(u,"aria-disabled",n?"true":null),r(u,"data-disabled",String(n)),r(u,"data-state",o)),d){d.id||(d.id=`${l}-content`);let a=d.getAttribute("collapsed-height"),i=a!==null,c=i||d.hasAttribute("force-mount");r(d,"hidden",t||c?null:""),r(d,"aria-hidden",t||i?null:"true"),r(d,"data-state",o),r(d,"data-disabled",String(n)),r(d,"data-clamped",i?"true":null),St(d,Rt,i?Kt(a):""),t&&this.#t===!1&&d.dispatchEvent(new CustomEvent("primitiv-collapsible-show",{bubbles:!0}))}u&&r(u,"aria-controls",d?d.id:null);for(let a of this.parts(ft))r(a,"aria-hidden","true"),r(a,"data-state",o);this.controller.setTriggers(u?[{value:"trigger",element:u,disabled:n}]:[]),this.#t=t}},D=class extends p{},N=class extends p{},P=class extends p{};function rt(e=globalThis.customElements){f(e,[[Ht,I],[it,D],[bt,N],[ft,P]])}var C=class{#t;#e;#o=null;constructor(t,o){this.#t=t,this.#e=o}connect(){if(this.#o)return;this.#o=new AbortController;let{signal:t}=this.#o,o=this.#t;o.addEventListener("close",()=>this.#e.change(!1),{signal:t}),o.addEventListener("cancel",n=>this.#n(n),{signal:t}),o.addEventListener("pointerdown",n=>this.#i(n),{signal:t}),o.addEventListener("keydown",n=>this.#r(n),{signal:t})}disconnect(){this.#o?.abort(),this.#o=null}sync(){let{open:t}=this.#e.state();t&&!this.#t.open?this.#t.showModal():!t&&this.#t.open&&this.#t.close()}#n(t){this.#e.escapeKeyDown?.(t);let o=t.defaultPrevented;t.preventDefault(),o||this.#e.change(!1)}#i(t){let o={x:t.clientX,y:t.clientY};et(this.#t.getBoundingClientRect(),o)&&(this.#e.pointerDownOutside?.(t),t.defaultPrevented||this.#e.change(!1))}#r(t){if(!this.#t.open)return;let o=[...this.#t.querySelectorAll(Z)],n=tt({key:t.key,shiftKey:t.shiftKey,active:o.indexOf(document.activeElement),count:o.length});n&&(t.preventDefault(),n.focus!==null&&o[n.focus].focus())}};var kt="primitiv-modal",vt="primitiv-modal-trigger",st="primitiv-modal-content",yt="primitiv-modal-title",Tt="primitiv-modal-description",xt="primitiv-modal-close";function lt(e,t){return e.childElementCount===1?e.firstElementChild:(h(e,`<${e.localName}> needs exactly one element child (${t}) to decorate.`),null)}function It(e){let t=e.firstElementChild;return e.childElementCount===1&&t instanceof HTMLDialogElement?t:(h(e,`<${st}> needs exactly one <dialog> child to decorate.`),null)}var $=class extends m{#t=null;#e=null;#o=[];#n=[];#i=null;controller={connect:()=>this.#a(),disconnect:()=>this.#d()};reconcileOn=["open"];get open(){return this.hasAttribute("open")}set open(t){this.toggleAttribute("open",t)}#r(){return{state:()=>({open:this.open}),change:t=>this.#l(t)}}#l(t){t!==this.open&&(this.announce("primitiv-modal-change",{open:t})?this.open=t:this.#e?.sync())}#a(){this.#i=new AbortController,this.addEventListener("click",t=>this.#c(t),{signal:this.#i.signal}),this.#e?.connect()}#d(){this.#i.abort(),this.#i=null,this.#e?.disconnect(),this.#t&&r(this.#t,"open",null)}#c(t){let o=t.target;this.#o.some(n=>n.contains(o))?this.#l(!0):this.#n.some(n=>n.contains(o))&&this.#l(!1)}#u(t){t!==this.#t&&(this.#e?.disconnect(),t&&r(t,"open",null),this.#t=t,this.#e=t?new C(t,this.#r()):null,this.#e?.connect())}reconcile(){let t=this.open,o=t?"open":"closed",n=this.rootId();r(this,"data-state",o);let[l]=this.parts(st),s=l?It(l):null,[d]=this.parts(yt),u=d?lt(d,"a heading"):null,[a]=this.parts(Tt),i=a?lt(a,"a <p>"):null;u&&!u.id&&(u.id=`${n}-title`),i&&!i.id&&(i.id=`${n}-description`),s&&(s.id||(s.id=`${n}-content`),r(s,"data-state",o),r(s,"aria-labelledby",u?u.id:null),r(s,"aria-describedby",i?i.id:null)),this.#o=this.#s(vt);for(let c of this.#o)r(c,"aria-haspopup","dialog"),r(c,"aria-expanded",String(t)),r(c,"aria-controls",s?s.id:null);this.#n=this.#s(xt),this.#u(s),this.#e?.sync()}#s(t){return this.parts(t).flatMap(o=>lt(o,"a <button>")??[])}},B=class extends p{},G=class extends p{},z=class extends p{},F=class extends p{},U=class extends p{};function at(e=globalThis.customElements){f(e,[[kt,$],[vt,B],[st,G],[yt,z],[Tt,F],[xt,U]])}var A=class extends b{#t;constructor(t,o){super(t),this.#t=o}onTriggerClick(t){let{active:o}=this.#t.state(),n=w({disabled:this.disabled(),active:o},t.value);if(n!==null){let l=this.ordered().map(([,s])=>s.value);this.#t.activate(n,l.indexOf(n))}}onTriggerKeydown(t,o){let{active:n,orientation:l,dir:s,activationMode:d}=this.#t.state(),u=this.ordered(),a=u.map(([,c])=>c.value),i=X({values:a,disabled:this.disabled(),active:n,orientation:l,dir:s,activationMode:d},t.value,o.key);i&&(o.preventDefault(),i.activate!==null&&this.#t.activate(i.activate,a.indexOf(i.activate)),u[a.indexOf(i.focus)][0].focus())}};var Dt="primitiv-tabs",Et="primitiv-tabs-list",dt="primitiv-tabs-trigger",Ct="primitiv-tabs-content";function Nt(e){let t=e.getAttribute("value");return e.childElementCount===1&&t!==null?{tab:e.firstElementChild,value:t}:(h(e,`<${dt}> needs a value and exactly one element child (a <button>, an <a href> or a router link) to decorate.`),null)}var _=class extends m{#t;#e=!1;controller=new A(this,this.#o());reconcileOn=["value","disabled","label","orientation"];get value(){return this.getAttribute("value")??void 0}set value(t){this.setAttribute("value",t)}#o(){return{state:()=>({active:this.value,orientation:this.#i(),dir:this.direction(),activationMode:this.#n()}),activate:(t,o)=>{this.announce("primitiv-tabs-change",{value:t,index:o})&&(this.value=t)}}}#n(){let t=this.getAttribute("activation-mode");return t==="manual"?t:"automatic"}#i(){return this.getAttribute("orientation")==="vertical"?"vertical":"horizontal"}reconcile(){let t=this.#i(),o=this.value,n=this.rootId();r(this,"data-orientation",t);for(let a of this.parts(Et)){r(a,"role","tablist"),r(a,"aria-orientation",t),r(a,"data-orientation",t);let i=a.getAttribute("label");i!==null&&r(a,"aria-label",i)}let l=new Map;for(let a of this.parts(Ct)){let i=a.getAttribute("value");i!==null&&l.set(i,a)}let s=this.parts(dt).flatMap(a=>{let i=Nt(a);return i?[{trigger:a,...i}]:[]}),d=V({values:s.map(a=>a.value),active:o});this.controller.setTriggers(s.map(({trigger:a,tab:i,value:c})=>({value:c,element:i,disabled:a.hasAttribute("disabled")})));for(let{trigger:a,tab:i,value:c}of s){let g=l.get(c),ut=c===o,pt=a.hasAttribute("disabled");i.id||(i.id=v(n,"trigger",c)),g&&!g.id&&(g.id=v(n,"panel",c)),r(i,"role","tab"),r(i,"aria-selected",String(ut)),r(i,"aria-controls",g?g.id:null),r(i,"tabindex",c===d?"0":"-1"),r(i,"aria-disabled",pt?"true":null),r(i,"data-disabled",pt?"":null),r(i,"data-state",ut?"active":"inactive"),r(i,"data-orientation",t),g&&r(g,"aria-labelledby",i.id)}for(let[a,i]of l){let c=a===o;r(i,"role","tabpanel"),r(i,"tabindex","0"),r(i,"hidden",c?null:""),r(i,"data-state",c?"active":"inactive"),r(i,"data-orientation",t)}let u=l.get(o);this.#e&&o!==this.#t&&u&&u.dispatchEvent(new CustomEvent("primitiv-tabs-show",{bubbles:!0})),this.#t=o,this.#e=!0}},q=class extends p{},j=class extends p{},W=class extends p{};function ct(e=globalThis.customElements){f(e,[[Dt,_],[Et,q],[dt,j],[Ct,W]])}nt();rt();at();ct();export{x as AccordionController,E as CollapsibleController,C as ModalController,S as PrimitivAccordionContentElement,R as PrimitivAccordionElement,L as PrimitivAccordionItemElement,K as PrimitivAccordionTriggerElement,k as PrimitivAccordionTriggerIconElement,N as PrimitivCollapsibleContentElement,I as PrimitivCollapsibleElement,D as PrimitivCollapsibleTriggerElement,P as PrimitivCollapsibleTriggerIconElement,U as PrimitivModalCloseElement,G as PrimitivModalContentElement,F as PrimitivModalDescriptionElement,$ as PrimitivModalElement,z as PrimitivModalTitleElement,B as PrimitivModalTriggerElement,W as PrimitivTabsContentElement,_ as PrimitivTabsElement,q as PrimitivTabsListElement,j as PrimitivTabsTriggerElement,A as TabsController,nt as defineAccordion,rt as defineCollapsible,at as defineModal,ct as defineTabs};
|
|
1
|
+
function b(e,t,o){return`${e}-${t}-${o}`}function R({orientation:e,dir:t,homeEnd:o=!1,activate:n=!1}){let i={};if(e==="horizontal"||e==="both"){let l=t==="rtl"?"ArrowLeft":"ArrowRight",c=t==="rtl"?"ArrowRight":"ArrowLeft";i[l]="next",i[c]="prev"}return(e==="vertical"||e==="both")&&(i.ArrowDown="next",i.ArrowUp="prev"),o&&(i.Home="first",i.End="last"),n&&(i.Enter="activate",i[" "]="activate"),i}function P({action:e,navigable:t,currentKey:o}){let n=t.length;if(n===0)return null;switch(e){case"activate":return{target:o};case"first":return{target:t[0]};case"last":return{target:t[n-1]};case void 0:return null;case"next":case"prev":{let i=t.indexOf(o);if(i===-1)return null;let l=e==="next"?1:n-1;return{target:t[(i+l)%n]}}}}function pt(e){return e.active??e.values[0]}function K(e,t){return e.disabled.has(t)||e.active===t?null:t}function ut(e,t,o){let n=R({orientation:e.orientation,dir:e.dir,homeEnd:!0,activate:!0})[o],i=P({action:n,navigable:e.values,currentKey:t});if(!i)return null;let l=i.target,c=e.activationMode==="automatic"||n==="activate";return{focus:l,activate:c?K(e,l):null}}function ht(e,t){return e.disabled.has(t)?null:e.expanded.includes(t)?e.expanded.filter(o=>o!==t):e.multiple?[...e.expanded,t]:[t]}function mt(e,t,o){let n=R({orientation:e.orientation,dir:e.dir,homeEnd:!0,activate:!0})[o];if(n==="activate")return e.disabled.has(t)?null:{focus:null,toggle:t};let i=e.values.filter(c=>!e.disabled.has(c)),l=P({action:n,navigable:i,currentKey:t});return l&&{focus:l.target,toggle:null}}function S(e){return e.disabled?null:!e.open}function gt(e,t){if(t!=="Enter"&&t!==" ")return null;let o=S(e);return o===null?null:{open:o}}var x=["a[href]","button:not([disabled])","input:not([disabled])","select:not([disabled])","textarea:not([disabled])",'[tabindex]:not([tabindex="-1"])'].join(",");function ft({key:e,shiftKey:t,active:o,count:n}){if(e!=="Tab")return null;if(n===0)return{focus:null};let i=n-1;return!t&&o===i?{focus:0}:t&&o===0?{focus:i}:null}function vt(e,t){return t.x<e.left||t.x>e.right||t.y<e.top||t.y>e.bottom}function bt(e){return e==="Escape"?{open:!1}:null}function yt({open:e,nativeLightDismiss:t,inTrigger:o,inPopover:n}){return e&&!t&&!o&&!n}function Tt({groupOpen:e,delay:t}){return e?0:t}var v=class{#t;#e=new Map;#o=null;constructor(t){this.#t=t}registerTrigger(t,o,n={}){this.#e.set(o,{value:t,disabled:n.disabled??!1})}setTriggers(t){this.#e.clear();for(let{value:o,element:n,disabled:i}of t)this.registerTrigger(o,n,{disabled:i})}unregisterTrigger(t){this.#e.delete(t)}connect(){if(this.#o)return;this.#o=new AbortController;let{signal:t}=this.#o;this.#t.addEventListener("click",o=>this.#n(o,this.onTriggerClick),{signal:t}),this.#t.addEventListener("keydown",o=>this.#n(o,this.onTriggerKeydown),{signal:t})}disconnect(){this.#o?.abort(),this.#o=null}ordered(){return[...this.#e].sort(([t],[o])=>t.compareDocumentPosition(o)&Node.DOCUMENT_POSITION_FOLLOWING?-1:1)}disabled(){let t=new Set;for(let o of this.#e.values())o.disabled&&t.add(o.value);return t}#n(t,o){for(let[n,i]of this.#e)if(n.contains(t.target))return o.call(this,i,t)}};var C=class extends v{#t;constructor(t,o){super(t),this.#t=o}#e(t){let{expanded:o,multiple:n}=this.#t.state(),i=ht({disabled:this.disabled(),expanded:o,multiple:n},t);i!==null&&this.#t.change(i)}onTriggerClick(t){this.#e(t.value)}onTriggerKeydown(t,o){let{expanded:n,multiple:i,orientation:l,dir:c}=this.#t.state(),p=this.ordered(),a=p.map(([,d])=>d.value),s=mt({values:a,disabled:this.disabled(),expanded:n,multiple:i,orientation:l,dir:c},t.value,o.key);s&&(o.preventDefault(),s.toggle!==null&&this.#e(s.toggle),s.focus!==null&&p[a.indexOf(s.focus)][0].focus())}};var u=globalThis.HTMLElement??class{};function r(e,t,o){e.getAttribute(t)!==o&&(o===null?e.removeAttribute(t):e.setAttribute(t,o))}var It=new WeakSet;function m(e,t){It.has(e)||(It.add(e),console.error(t,e))}function f(e,t){if(e)for(let[o,n]of t)e.get(o)||e.define(o,n)}function y(e,t,o){return[...e.querySelectorAll(t)].filter(n=>n.closest(o)===e)}var ie=0,g=class extends u{#t;#e=new MutationObserver(()=>this.reconcile());connectedCallback(){this.controller.connect(),this.#e.observe(this,{subtree:!0,childList:!0,attributes:!0,attributeFilter:[...this.reconcileOn]}),this.reconcile()}disconnectedCallback(){this.controller.disconnect(),this.#e.disconnect()}rootId(){return this.id||(this.#t??=`${this.localName}-${++ie}`)}direction(){return this.matches(":dir(rtl)")?"rtl":"ltr"}parts(t){return y(this,t,this.localName)}announce(t,o){return this.dispatchEvent(new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:o}))}};var re="primitiv-accordion",T="primitiv-accordion-item",Et="primitiv-accordion-trigger",Gt="primitiv-accordion-content",$t="primitiv-accordion-trigger-icon";function se(e){return(e??"").split(/\s+/).filter(Boolean)}function le(e){return e.childElementCount===1?e.firstElementChild:(m(e,`<${Et}> needs exactly one element child (a <button>) to decorate.`),null)}var D=class extends g{#t=new Set;#e=!1;controller=new C(this,this.#o());reconcileOn=["value","disabled","orientation","force-mount"];get value(){return se(this.getAttribute("value"))}set value(t){this.setAttribute("value",typeof t=="string"?t:t.join(" "))}#o(){return{state:()=>({expanded:this.value,multiple:this.hasAttribute("multiple"),orientation:this.#n(),dir:this.direction()}),change:t=>{this.announce("primitiv-accordion-change",{value:t})&&(this.value=t)}}}#n(){return this.getAttribute("orientation")==="horizontal"?"horizontal":"vertical"}reconcile(){let t=new Set(this.value),o=this.rootId();r(this,"data-orientation",this.#n());let n=[];for(let i of this.parts(T)){let l=i.getAttribute("value");if(l===null){m(i,`<${T}> needs a value, which pairs it with the root's value.`);continue}let c=t.has(l),p=c?"open":"closed",[a]=y(i,Et,T),[s]=y(i,Gt,T),d=a?le(a):null;if(d){let h=a.hasAttribute("disabled");n.push({value:l,element:d,disabled:h}),d.id||(d.id=b(o,"heading",l)),r(d,"aria-expanded",String(c)),r(d,"aria-disabled",String(h)),r(d,"data-disabled",String(h)),r(d,"data-state",p)}if(s){s.id||(s.id=b(o,"panel",l));let h=s.hasAttribute("force-mount");s.hasAttribute("role")||s.setAttribute("role","region"),r(s,"aria-labelledby",d?d.id:null),r(s,"hidden",c||h?null:""),r(s,"aria-hidden",c?null:"true"),r(s,"data-state",p),this.#e&&c&&!this.#t.has(l)&&s.dispatchEvent(new CustomEvent("primitiv-accordion-show",{bubbles:!0}))}d&&r(d,"aria-controls",s?s.id:null);for(let h of y(i,$t,T))r(h,"aria-hidden","true"),r(h,"data-state",p)}this.controller.setTriggers(n),this.#t=t,this.#e=!0}},N=class extends u{},k=class extends u{},I=class extends u{},G=class extends u{};function xt(e=globalThis.customElements){f(e,[[re,D],[T,N],[Et,k],[Gt,I],[$t,G]])}var A=class extends v{#t;constructor(t,o){super(t),this.#t=o}onTriggerClick(t){let o=S({open:this.#t.state().open,disabled:t.disabled});o!==null&&this.#t.change(o)}onTriggerKeydown(t,o){let n=gt({open:this.#t.state().open,disabled:t.disabled},o.key);n&&(o.preventDefault(),this.#t.change(n.open))}};var ae="primitiv-collapsible",Ct="primitiv-collapsible-trigger",Bt="primitiv-collapsible-content",_t="primitiv-collapsible-trigger-icon",de="--primitiv-collapsible-collapsed-height";function ce(e){return e.childElementCount===1?e.firstElementChild:(m(e,`<${Ct}> needs exactly one element child (a <button>) to decorate.`),null)}function pe(e){return Number.isNaN(Number(e))?e:`${e}px`}function ue(e,t,o){e.style.getPropertyValue(t)!==o&&(e.style.setProperty(t,o),e.getAttribute("style")===""&&e.removeAttribute("style"))}var $=class extends g{#t;controller=new A(this,this.#e());reconcileOn=["open","disabled","force-mount","collapsed-height"];get open(){return this.hasAttribute("open")}set open(t){this.toggleAttribute("open",t)}#e(){return{state:()=>({open:this.open}),change:t=>{this.announce("primitiv-collapsible-change",{open:t})&&(this.open=t)}}}reconcile(){let t=this.open,o=t?"open":"closed",n=this.hasAttribute("disabled"),i=this.rootId();r(this,"data-state",o),r(this,"data-disabled",String(n));let[l]=this.parts(Ct),[c]=this.parts(Bt),p=l?ce(l):null;if(p&&(p.id||(p.id=`${i}-trigger`),r(p,"aria-expanded",String(t)),r(p,"aria-disabled",n?"true":null),r(p,"data-disabled",String(n)),r(p,"data-state",o)),c){c.id||(c.id=`${i}-content`);let a=c.getAttribute("collapsed-height"),s=a!==null,d=s||c.hasAttribute("force-mount");r(c,"hidden",t||d?null:""),r(c,"aria-hidden",t||s?null:"true"),r(c,"data-state",o),r(c,"data-disabled",String(n)),r(c,"data-clamped",s?"true":null),ue(c,de,s?pe(a):""),t&&this.#t===!1&&c.dispatchEvent(new CustomEvent("primitiv-collapsible-show",{bubbles:!0}))}p&&r(p,"aria-controls",c?c.id:null);for(let a of this.parts(_t))r(a,"aria-hidden","true"),r(a,"data-state",o);this.controller.setTriggers(p?[{value:"trigger",element:p,disabled:n}]:[]),this.#t=t}},B=class extends u{},_=class extends u{},F=class extends u{};function At(e=globalThis.customElements){f(e,[[ae,$],[Ct,B],[Bt,_],[_t,F]])}var w=class{#t;#e;#o=null;constructor(t,o){this.#t=t,this.#e=o}connect(){if(this.#o)return;this.#o=new AbortController;let{signal:t}=this.#o,o=this.#t;o.addEventListener("close",()=>this.#e.change(!1),{signal:t}),o.addEventListener("cancel",n=>this.#n(n),{signal:t}),o.addEventListener("pointerdown",n=>this.#i(n),{signal:t}),o.addEventListener("keydown",n=>this.#r(n),{signal:t})}disconnect(){this.#o?.abort(),this.#o=null}sync(){let{open:t}=this.#e.state();t&&!this.#t.open?this.#t.showModal():!t&&this.#t.open&&this.#t.close()}#n(t){this.#e.escapeKeyDown?.(t);let o=t.defaultPrevented;t.preventDefault(),o||this.#e.change(!1)}#i(t){let o={x:t.clientX,y:t.clientY};vt(this.#t.getBoundingClientRect(),o)&&(this.#e.pointerDownOutside?.(t),t.defaultPrevented||this.#e.change(!1))}#r(t){if(!this.#t.open)return;let o=[...this.#t.querySelectorAll(x)],n=ft({key:t.key,shiftKey:t.shiftKey,active:o.indexOf(document.activeElement),count:o.length});n&&(t.preventDefault(),n.focus!==null&&o[n.focus].focus())}};var he="primitiv-modal",Ft="primitiv-modal-trigger",Mt="primitiv-modal-content",Ut="primitiv-modal-title",zt="primitiv-modal-description",qt="primitiv-modal-close";function wt(e,t){return e.childElementCount===1?e.firstElementChild:(m(e,`<${e.localName}> needs exactly one element child (${t}) to decorate.`),null)}function me(e){let t=e.firstElementChild;return e.childElementCount===1&&t instanceof HTMLDialogElement?t:(m(e,`<${Mt}> needs exactly one <dialog> child to decorate.`),null)}var U=class extends g{#t=null;#e=null;#o=[];#n=[];#i=null;controller={connect:()=>this.#l(),disconnect:()=>this.#a()};reconcileOn=["open"];get open(){return this.hasAttribute("open")}set open(t){this.toggleAttribute("open",t)}#r(){return{state:()=>({open:this.open}),change:t=>this.#s(t)}}#s(t){t!==this.open&&(this.announce("primitiv-modal-change",{open:t})?this.open=t:this.#e?.sync())}#l(){this.#i=new AbortController,this.addEventListener("click",t=>this.#c(t),{signal:this.#i.signal}),this.#e?.connect()}#a(){this.#i.abort(),this.#i=null,this.#e?.disconnect(),this.#t&&r(this.#t,"open",null)}#c(t){let o=t.target;this.#o.some(n=>n.contains(o))?this.#s(!0):this.#n.some(n=>n.contains(o))&&this.#s(!1)}#p(t){t!==this.#t&&(this.#e?.disconnect(),t&&r(t,"open",null),this.#t=t,this.#e=t?new w(t,this.#r()):null,this.#e?.connect())}reconcile(){let t=this.open,o=t?"open":"closed",n=this.rootId();r(this,"data-state",o);let[i]=this.parts(Mt),l=i?me(i):null,[c]=this.parts(Ut),p=c?wt(c,"a heading"):null,[a]=this.parts(zt),s=a?wt(a,"a <p>"):null;p&&!p.id&&(p.id=`${n}-title`),s&&!s.id&&(s.id=`${n}-description`),l&&(l.id||(l.id=`${n}-content`),r(l,"data-state",o),r(l,"aria-labelledby",p?p.id:null),r(l,"aria-describedby",s?s.id:null)),this.#o=this.#d(Ft);for(let d of this.#o)r(d,"aria-haspopup","dialog"),r(d,"aria-expanded",String(t)),r(d,"aria-controls",l?l.id:null);this.#n=this.#d(qt),this.#p(l),this.#e?.sync()}#d(t){return this.parts(t).flatMap(o=>wt(o,"a <button>")??[])}},z=class extends u{},q=class extends u{},W=class extends u{},Y=class extends u{},j=class extends u{};function Ot(e=globalThis.customElements){f(e,[[he,U],[Ft,z],[Mt,q],[Ut,W],[zt,Y],[qt,j]])}var M=class{#t;#e;#o=null;#n=!1;constructor(t,o){this.#t=t,this.#e=o}connect(){if(this.#o)return;this.#o=new AbortController;let{signal:t}=this.#o;this.#t.addEventListener("toggle",o=>this.#i(o),{signal:t}),this.#t.addEventListener("keydown",o=>this.#r(o),{signal:t}),document.addEventListener("click",o=>this.#s(o),{signal:t})}disconnect(){this.#o?.abort(),this.#o=null,this.#n=!1}sync(){let{open:t}=this.#e.state();if(t&&!this.#n){this.#n=!0;let o=this.#e.source?.();o?this.#t.showPopover({source:o}):this.#t.showPopover(),(this.#t.querySelector(x)??this.#t).focus()}else if(!t&&this.#n){this.#n=!1;try{this.#t.hidePopover()}catch{}}}#i(t){t.newState==="closed"&&(this.#n=!1,this.#e.change(!1))}#r(t){t.defaultPrevented||!bt(t.key)||(t.preventDefault(),this.#e.change(!1),this.#e.trigger()?.focus())}#s(t){let o=t.target;yt({open:this.#e.state().open,nativeLightDismiss:"popover"in HTMLElement.prototype,inTrigger:this.#e.trigger()?.contains(o)??!1,inPopover:o.closest("[popover]")!==null})&&this.#e.change(!1)}};var ge="primitiv-popover",Wt="primitiv-popover-trigger",Yt="primitiv-popover-anchor",jt="primitiv-popover-content",Vt="primitiv-popover-title",Xt="primitiv-popover-description",Jt="primitiv-popover-close";function O(e,t){return e.childElementCount===1?e.firstElementChild:(m(e,`<${e.localName}> needs exactly one element child (${t}) to decorate.`),null)}var V=class extends g{#t=null;#e=null;#o=null;#n=null;#i=[];#r=null;controller={connect:()=>this.#a(),disconnect:()=>this.#c()};reconcileOn=["open"];get open(){return this.hasAttribute("open")}set open(t){this.toggleAttribute("open",t)}#s(){return{state:()=>({open:this.open}),change:t=>this.#l(t),trigger:()=>this.#o,source:()=>this.#n??this.#o}}#l(t){t!==this.open&&(this.announce("primitiv-popover-change",{open:t})?this.open=t:queueMicrotask(()=>this.#e?.sync()))}#a(){this.#r=new AbortController,this.addEventListener("click",t=>this.#p(t),{signal:this.#r.signal}),this.#e?.connect()}#c(){this.#r.abort(),this.#r=null,this.#e?.disconnect()}#p(t){let o=t.target;this.#o?.contains(o)?this.#l(!this.open):this.#i.some(n=>n.contains(o))&&this.#l(!1)}#d(t){t!==this.#t&&(this.#e?.disconnect(),this.#t=t,this.#e=t?new M(t,this.#s()):null,this.#e?.connect())}reconcile(){let t=this.open,o=t?"open":"closed",n=this.rootId();r(this,"data-state",o);let[i=null]=this.parts(jt),[l]=this.parts(Wt),[c]=this.parts(Yt),[p]=this.parts(Vt),[a]=this.parts(Xt);this.#o=l?O(l,"a <button>"):null,this.#n=c?O(c,"the element to anchor to"):null;let s=p?O(p,"a heading"):null,d=a?O(a,"a <p>"):null;s&&!s.id&&(s.id=`${n}-title`),d&&!d.id&&(d.id=`${n}-description`),i&&(i.id||(i.id=`${n}-content`),r(i,"role","dialog"),i.hasAttribute("popover")||i.setAttribute("popover","auto"),r(i,"tabindex","-1"),r(i,"data-state",o),r(i,"aria-labelledby",s?s.id:null),r(i,"aria-describedby",d?d.id:null)),this.#o&&(r(this.#o,"aria-haspopup","dialog"),r(this.#o,"aria-expanded",String(t)),r(this.#o,"aria-controls",i?i.id:null)),this.#i=this.parts(Jt).flatMap(h=>O(h,"a <button>")??[]),this.#d(i),this.#e?.sync()}},X=class extends u{},J=class extends u{},Q=class extends u{},Z=class extends u{},tt=class extends u{},et=class extends u{};function Lt(e=globalThis.customElements){f(e,[[ge,V],[Wt,X],[Yt,J],[jt,Q],[Vt,Z],[Xt,tt],[Jt,et]])}var L=class extends v{#t;constructor(t,o){super(t),this.#t=o}onTriggerClick(t){let{active:o}=this.#t.state(),n=K({disabled:this.disabled(),active:o},t.value);if(n!==null){let i=this.ordered().map(([,l])=>l.value);this.#t.activate(n,i.indexOf(n))}}onTriggerKeydown(t,o){let{active:n,orientation:i,dir:l,activationMode:c}=this.#t.state(),p=this.ordered(),a=p.map(([,d])=>d.value),s=ut({values:a,disabled:this.disabled(),active:n,orientation:i,dir:l,activationMode:c},t.value,o.key);s&&(o.preventDefault(),s.activate!==null&&this.#t.activate(s.activate,a.indexOf(s.activate)),p[a.indexOf(s.focus)][0].focus())}};var fe="primitiv-tabs",Qt="primitiv-tabs-list",Ht="primitiv-tabs-trigger",Zt="primitiv-tabs-content";function ve(e){let t=e.getAttribute("value");return e.childElementCount===1&&t!==null?{tab:e.firstElementChild,value:t}:(m(e,`<${Ht}> needs a value and exactly one element child (a <button>, an <a href> or a router link) to decorate.`),null)}var ot=class extends g{#t;#e=!1;controller=new L(this,this.#o());reconcileOn=["value","disabled","label","orientation"];get value(){return this.getAttribute("value")??void 0}set value(t){this.setAttribute("value",t)}#o(){return{state:()=>({active:this.value,orientation:this.#i(),dir:this.direction(),activationMode:this.#n()}),activate:(t,o)=>{this.announce("primitiv-tabs-change",{value:t,index:o})&&(this.value=t)}}}#n(){let t=this.getAttribute("activation-mode");return t==="manual"?t:"automatic"}#i(){return this.getAttribute("orientation")==="vertical"?"vertical":"horizontal"}reconcile(){let t=this.#i(),o=this.value,n=this.rootId();r(this,"data-orientation",t);for(let a of this.parts(Qt)){r(a,"role","tablist"),r(a,"aria-orientation",t),r(a,"data-orientation",t);let s=a.getAttribute("label");s!==null&&r(a,"aria-label",s)}let i=new Map;for(let a of this.parts(Zt)){let s=a.getAttribute("value");s!==null&&i.set(s,a)}let l=this.parts(Ht).flatMap(a=>{let s=ve(a);return s?[{trigger:a,...s}]:[]}),c=pt({values:l.map(a=>a.value),active:o});this.controller.setTriggers(l.map(({trigger:a,tab:s,value:d})=>({value:d,element:s,disabled:a.hasAttribute("disabled")})));for(let{trigger:a,tab:s,value:d}of l){let h=i.get(d),Dt=d===o,Nt=a.hasAttribute("disabled");s.id||(s.id=b(n,"trigger",d)),h&&!h.id&&(h.id=b(n,"panel",d)),r(s,"role","tab"),r(s,"aria-selected",String(Dt)),r(s,"aria-controls",h?h.id:null),r(s,"tabindex",d===c?"0":"-1"),r(s,"aria-disabled",Nt?"true":null),r(s,"data-disabled",Nt?"":null),r(s,"data-state",Dt?"active":"inactive"),r(s,"data-orientation",t),h&&r(h,"aria-labelledby",s.id)}for(let[a,s]of i){let d=a===o;r(s,"role","tabpanel"),r(s,"tabindex","0"),r(s,"hidden",d?null:""),r(s,"data-state",d?"active":"inactive"),r(s,"data-orientation",t)}let p=i.get(o);this.#e&&o!==this.#t&&p&&p.dispatchEvent(new CustomEvent("primitiv-tabs-show",{bubbles:!0})),this.#t=o,this.#e=!0}},nt=class extends u{},it=class extends u{},rt=class extends u{};function Rt(e=globalThis.customElements){f(e,[[fe,ot],[Qt,nt],[Ht,it],[Zt,rt]])}var H=class{#t;#e=null;#o=null;#n=t=>this.#l(t);#i=t=>this.#a(t);constructor(t){this.#t=t}openNow(){this.#s(),this.#t.change(!0),this.#t.tooltipGroup().opened()}hoverStart(){let t=Tt({groupOpen:this.#t.tooltipGroup().isOpen(),delay:this.#t.delay()});if(t===0){this.openNow();return}this.#e=setTimeout(()=>{this.openNow(),this.#e=null},t)}closeNow(){this.holdOpen(),this.#t.change(!1),this.#t.tooltipGroup().closed()}hoverEnd(){this.#s(),this.#o=setTimeout(()=>{this.#t.change(!1),this.#t.tooltipGroup().closed(),this.#o=null},100)}holdOpen(){this.#o!==null&&(clearTimeout(this.#o),this.#o=null)}sync(){let{open:t}=this.#t.state();t?(document.addEventListener("keydown",this.#n),document.addEventListener("pointerdown",this.#i)):this.#r()}#r(){document.removeEventListener("keydown",this.#n),document.removeEventListener("pointerdown",this.#i)}disconnect(){this.#s(),this.holdOpen(),this.#r()}#s(){this.#e!==null&&(clearTimeout(this.#e),this.#e=null)}#l(t){t.key==="Escape"&&(this.#t.escapeKeyDown?.(t),t.defaultPrevented||this.closeNow())}#a(t){this.#t.content()?.contains(t.target)||(this.#t.pointerDownOutside?.(t),t.defaultPrevented||this.closeNow())}};var E=class{#t;#e=!1;#o=null;constructor(t){this.#t=t}isOpen(){return this.#e}opened(){this.#o!==null&&(clearTimeout(this.#o),this.#o=null),this.#e=!0}closed(){clearTimeout(this.#o??void 0),this.#o=setTimeout(()=>{this.#e=!1,this.#o=null},this.#t())}dispose(){clearTimeout(this.#o??void 0)}};var be="primitiv-tooltip",ee="primitiv-tooltip-provider",Kt="primitiv-tooltip-trigger",oe="primitiv-tooltip-content",ye="primitiv-tooltip-arrow",te=700,ne=300;function Pt(e,t,o){let n=Number(e.getAttribute(t)??Number.NaN);return Number.isNaN(n)?o:n}var Te=new E(()=>ne),st=class extends u{#t;get group(){return this.#t??=new E(()=>Pt(this,"skip-delay-duration",ne))}},lt=class extends g{#t=null;#e=null;#o=null;#n=null;#i=new H(this.#r());controller={connect:()=>{},disconnect:()=>this.#s()};reconcileOn=["open"];get open(){return this.hasAttribute("open")}set open(t){this.toggleAttribute("open",t)}#r(){let t=()=>this.closest(ee);return{state:()=>({open:this.open}),change:o=>{o!==this.open&&this.announce("primitiv-tooltip-change",{open:o})&&(this.open=o)},delay:()=>{let o=t();return Pt(this,"delay-duration",o?Pt(o,"delay-duration",te):te)},tooltipGroup:()=>t()?.group??Te,content:()=>this.#e}}#s(){this.open&&this.#r().tooltipGroup().closed(),this.#n?.abort(),this.#n=null,this.#t=null,this.#e=null,this.#o=null,this.#i.disconnect()}#l(t,o){if(t===this.#t&&o===this.#e)return;this.#n?.abort(),this.#n=new AbortController,this.#t=t,this.#e=o;let n={signal:this.#n.signal},i=this.#i;t?.addEventListener("pointerenter",()=>i.hoverStart(),n),t?.addEventListener("pointerleave",()=>this.hasAttribute("disable-hoverable-content")?i.closeNow():i.hoverEnd(),n),t?.addEventListener("focus",()=>i.openNow(),n),t?.addEventListener("blur",()=>i.closeNow(),n),o?.addEventListener("pointerenter",()=>i.holdOpen(),n),o?.addEventListener("pointerleave",()=>i.closeNow(),n)}reconcile(){let t=this.open,o=t?"open":"closed";r(this,"data-state",o);let[n]=this.parts(Kt),[i=null]=this.parts(oe),l=n?Ee(n):null;i&&(i.id||(i.id=`${this.rootId()}-content`),r(i,"role","tooltip"),i.hasAttribute("popover")||i.setAttribute("popover","manual"),r(i,"data-state",o)),l&&(r(l,"aria-describedby",i?i.id:null),r(l,"data-state",o)),this.#l(l,i),this.#a(t?i:null),this.#i.sync()}#a(t){if(t===this.#o)return;if(this.#o)try{this.#o.hidePopover()}catch{}if(this.#o=t,!t)return;let o=t;this.#t?o.showPopover({source:this.#t}):o.showPopover()}};function Ee(e){return e.childElementCount===1?e.firstElementChild:(m(e,`<${Kt}> needs exactly one element child (a <button>) to decorate.`),null)}var at=class extends u{},dt=class extends u{},ct=class extends u{};function St(e=globalThis.customElements){f(e,[[be,lt],[ee,st],[Kt,at],[oe,dt],[ye,ct]])}xt();At();Ot();Lt();Rt();St();export{C as AccordionController,A as CollapsibleController,w as ModalController,M as PopoverController,I as PrimitivAccordionContentElement,D as PrimitivAccordionElement,N as PrimitivAccordionItemElement,k as PrimitivAccordionTriggerElement,G as PrimitivAccordionTriggerIconElement,_ as PrimitivCollapsibleContentElement,$ as PrimitivCollapsibleElement,B as PrimitivCollapsibleTriggerElement,F as PrimitivCollapsibleTriggerIconElement,j as PrimitivModalCloseElement,q as PrimitivModalContentElement,Y as PrimitivModalDescriptionElement,U as PrimitivModalElement,W as PrimitivModalTitleElement,z as PrimitivModalTriggerElement,J as PrimitivPopoverAnchorElement,et as PrimitivPopoverCloseElement,Q as PrimitivPopoverContentElement,tt as PrimitivPopoverDescriptionElement,V as PrimitivPopoverElement,Z as PrimitivPopoverTitleElement,X as PrimitivPopoverTriggerElement,rt as PrimitivTabsContentElement,ot as PrimitivTabsElement,nt as PrimitivTabsListElement,it as PrimitivTabsTriggerElement,ct as PrimitivTooltipArrowElement,dt as PrimitivTooltipContentElement,lt as PrimitivTooltipElement,st as PrimitivTooltipProviderElement,at as PrimitivTooltipTriggerElement,L as TabsController,H as TooltipController,E as TooltipGroup,xt as defineAccordion,At as defineCollapsible,Ot as defineModal,Lt as definePopover,Rt as defineTabs,St as defineTooltip};
|
package/dist/tags.d.ts
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
import type { PrimitivAccordionContentElement, PrimitivAccordionElement, PrimitivAccordionItemElement, PrimitivAccordionTriggerElement, PrimitivAccordionTriggerIconElement } from "./accordion/elements.js";
|
|
16
16
|
import type { PrimitivCollapsibleContentElement, PrimitivCollapsibleElement, PrimitivCollapsibleTriggerElement, PrimitivCollapsibleTriggerIconElement } from "./collapsible/elements.js";
|
|
17
17
|
import type { PrimitivModalCloseElement, PrimitivModalContentElement, PrimitivModalDescriptionElement, PrimitivModalElement, PrimitivModalTitleElement, PrimitivModalTriggerElement } from "./modal/elements.js";
|
|
18
|
+
import type { PrimitivPopoverAnchorElement, PrimitivPopoverCloseElement, PrimitivPopoverContentElement, PrimitivPopoverDescriptionElement, PrimitivPopoverElement, PrimitivPopoverTitleElement, PrimitivPopoverTriggerElement } from "./popover/elements.js";
|
|
18
19
|
import type { PrimitivTabsContentElement, PrimitivTabsElement, PrimitivTabsListElement, PrimitivTabsTriggerElement } from "./tabs/elements.js";
|
|
20
|
+
import type { PrimitivTooltipArrowElement, PrimitivTooltipContentElement, PrimitivTooltipElement, PrimitivTooltipProviderElement, PrimitivTooltipTriggerElement } from "./tooltip/elements.js";
|
|
19
21
|
declare global {
|
|
20
22
|
interface HTMLElementTagNameMap {
|
|
21
23
|
"primitiv-accordion": PrimitivAccordionElement;
|
|
@@ -33,10 +35,22 @@ declare global {
|
|
|
33
35
|
"primitiv-modal-title": PrimitivModalTitleElement;
|
|
34
36
|
"primitiv-modal-description": PrimitivModalDescriptionElement;
|
|
35
37
|
"primitiv-modal-close": PrimitivModalCloseElement;
|
|
38
|
+
"primitiv-popover": PrimitivPopoverElement;
|
|
39
|
+
"primitiv-popover-trigger": PrimitivPopoverTriggerElement;
|
|
40
|
+
"primitiv-popover-anchor": PrimitivPopoverAnchorElement;
|
|
41
|
+
"primitiv-popover-content": PrimitivPopoverContentElement;
|
|
42
|
+
"primitiv-popover-title": PrimitivPopoverTitleElement;
|
|
43
|
+
"primitiv-popover-description": PrimitivPopoverDescriptionElement;
|
|
44
|
+
"primitiv-popover-close": PrimitivPopoverCloseElement;
|
|
36
45
|
"primitiv-tabs": PrimitivTabsElement;
|
|
37
46
|
"primitiv-tabs-list": PrimitivTabsListElement;
|
|
38
47
|
"primitiv-tabs-trigger": PrimitivTabsTriggerElement;
|
|
39
48
|
"primitiv-tabs-content": PrimitivTabsContentElement;
|
|
49
|
+
"primitiv-tooltip": PrimitivTooltipElement;
|
|
50
|
+
"primitiv-tooltip-provider": PrimitivTooltipProviderElement;
|
|
51
|
+
"primitiv-tooltip-trigger": PrimitivTooltipTriggerElement;
|
|
52
|
+
"primitiv-tooltip-content": PrimitivTooltipContentElement;
|
|
53
|
+
"primitiv-tooltip-arrow": PrimitivTooltipArrowElement;
|
|
40
54
|
}
|
|
41
55
|
}
|
|
42
56
|
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { TooltipGroup } from "./TooltipGroup.js";
|
|
2
|
+
/**
|
|
3
|
+
* What a {@link TooltipController} asks of whoever hosts it. The controller
|
|
4
|
+
* owns behaviour and never owns state (RFC 0033 D28): the custom elements keep
|
|
5
|
+
* the open state in a reflected `open` attribute, the React adapter keeps it in
|
|
6
|
+
* React, and each answers these calls from wherever it keeps it.
|
|
7
|
+
*/
|
|
8
|
+
export type TooltipControllerHost = {
|
|
9
|
+
/** The state the controller cannot see for itself. */
|
|
10
|
+
state(): {
|
|
11
|
+
/** Whether the tooltip is open. */
|
|
12
|
+
open: boolean;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Apply a change: `open` is the state afterwards. A close is reported every
|
|
16
|
+
* time it is asked for, already closed or not, so the host decides what a
|
|
17
|
+
* repeat means.
|
|
18
|
+
*/
|
|
19
|
+
change(open: boolean): void;
|
|
20
|
+
/** The delay before opening on hover, in milliseconds. */
|
|
21
|
+
delay(): number;
|
|
22
|
+
/** The group whose skip-delay window this tooltip shares. */
|
|
23
|
+
tooltipGroup(): TooltipGroup;
|
|
24
|
+
/** The tooltip's content, so a press inside it is not a press outside. */
|
|
25
|
+
content(): HTMLElement | null;
|
|
26
|
+
/** Escape was pressed while open. Call `preventDefault()` on `event` to keep it open. Optional. */
|
|
27
|
+
escapeKeyDown?(event: KeyboardEvent): void;
|
|
28
|
+
/** A pointer went down outside the content while open. Call `preventDefault()` to keep it open. Optional. */
|
|
29
|
+
pointerDownOutside?(event: PointerEvent): void;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The behaviour of a tooltip: hover intent and dismissal. Its host wires the
|
|
33
|
+
* trigger's and the content's own events to the intents below, so the React
|
|
34
|
+
* adapter keeps composing them with its consumers' handlers as it always has;
|
|
35
|
+
* the controller owns the timers, and the document-wide Escape and outside
|
|
36
|
+
* press while open (WAI-ARIA tooltip pattern).
|
|
37
|
+
*
|
|
38
|
+
* - {@link TooltipController.hoverStart | `hoverStart`} — open after the
|
|
39
|
+
* delay, or at once while the group is in its skip-delay window.
|
|
40
|
+
* - {@link TooltipController.hoverEnd | `hoverEnd`} — close after a short
|
|
41
|
+
* grace period, long enough for the pointer to reach hoverable content.
|
|
42
|
+
* - {@link TooltipController.holdOpen | `holdOpen`} — the pointer reached the
|
|
43
|
+
* content: cancel that grace close.
|
|
44
|
+
* - {@link TooltipController.openNow | `openNow`} / {@link TooltipController.closeNow | `closeNow`} —
|
|
45
|
+
* focus and blur, Escape on the trigger, the pointer leaving the content.
|
|
46
|
+
* - {@link TooltipController.sync | `sync`} — listen for Escape and outside
|
|
47
|
+
* presses while open; a host calls it whenever the state may have changed.
|
|
48
|
+
*/
|
|
49
|
+
export declare class TooltipController {
|
|
50
|
+
#private;
|
|
51
|
+
constructor(host: TooltipControllerHost);
|
|
52
|
+
/** Open now, cancelling a pending hover open. */
|
|
53
|
+
openNow(): void;
|
|
54
|
+
/** Open after the delay, or at once while the group is in its skip-delay window. */
|
|
55
|
+
hoverStart(): void;
|
|
56
|
+
/** Close now, cancelling a pending grace close. */
|
|
57
|
+
closeNow(): void;
|
|
58
|
+
/** Close after the grace period, cancelling a pending hover open. */
|
|
59
|
+
hoverEnd(): void;
|
|
60
|
+
/** The pointer reached the content: cancel a pending grace close. */
|
|
61
|
+
holdOpen(): void;
|
|
62
|
+
/** Listen for Escape and outside presses while open, and only then. */
|
|
63
|
+
sync(): void;
|
|
64
|
+
/** Cancel every timer and stop listening, so nothing outlives the host. */
|
|
65
|
+
disconnect(): void;
|
|
66
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { TOOLTIP_GRACE_MS, tooltipOpenDelay } from "@primitiv-ui/core";
|
|
2
|
+
/**
|
|
3
|
+
* The behaviour of a tooltip: hover intent and dismissal. Its host wires the
|
|
4
|
+
* trigger's and the content's own events to the intents below, so the React
|
|
5
|
+
* adapter keeps composing them with its consumers' handlers as it always has;
|
|
6
|
+
* the controller owns the timers, and the document-wide Escape and outside
|
|
7
|
+
* press while open (WAI-ARIA tooltip pattern).
|
|
8
|
+
*
|
|
9
|
+
* - {@link TooltipController.hoverStart | `hoverStart`} — open after the
|
|
10
|
+
* delay, or at once while the group is in its skip-delay window.
|
|
11
|
+
* - {@link TooltipController.hoverEnd | `hoverEnd`} — close after a short
|
|
12
|
+
* grace period, long enough for the pointer to reach hoverable content.
|
|
13
|
+
* - {@link TooltipController.holdOpen | `holdOpen`} — the pointer reached the
|
|
14
|
+
* content: cancel that grace close.
|
|
15
|
+
* - {@link TooltipController.openNow | `openNow`} / {@link TooltipController.closeNow | `closeNow`} —
|
|
16
|
+
* focus and blur, Escape on the trigger, the pointer leaving the content.
|
|
17
|
+
* - {@link TooltipController.sync | `sync`} — listen for Escape and outside
|
|
18
|
+
* presses while open; a host calls it whenever the state may have changed.
|
|
19
|
+
*/
|
|
20
|
+
export class TooltipController {
|
|
21
|
+
#host;
|
|
22
|
+
#opening = null;
|
|
23
|
+
#grace = null;
|
|
24
|
+
// The document listeners, added and removed by hand rather than through an
|
|
25
|
+
// AbortSignal, so a host can see the very handler removed. Adding the same
|
|
26
|
+
// function twice is a no-op, so syncing open again never stacks them.
|
|
27
|
+
#onKeydown = (event) => this.#keydown(event);
|
|
28
|
+
#onPointerdown = (event) => this.#pointerdown(event);
|
|
29
|
+
constructor(host) {
|
|
30
|
+
this.#host = host;
|
|
31
|
+
}
|
|
32
|
+
/** Open now, cancelling a pending hover open. */
|
|
33
|
+
openNow() {
|
|
34
|
+
this.#cancelOpening();
|
|
35
|
+
this.#host.change(true);
|
|
36
|
+
this.#host.tooltipGroup().opened();
|
|
37
|
+
}
|
|
38
|
+
/** Open after the delay, or at once while the group is in its skip-delay window. */
|
|
39
|
+
hoverStart() {
|
|
40
|
+
const delay = tooltipOpenDelay({ groupOpen: this.#host.tooltipGroup().isOpen(), delay: this.#host.delay() });
|
|
41
|
+
if (delay === 0) {
|
|
42
|
+
this.openNow();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this.#opening = setTimeout(() => {
|
|
46
|
+
this.openNow();
|
|
47
|
+
this.#opening = null;
|
|
48
|
+
}, delay);
|
|
49
|
+
}
|
|
50
|
+
/** Close now, cancelling a pending grace close. */
|
|
51
|
+
closeNow() {
|
|
52
|
+
this.holdOpen();
|
|
53
|
+
this.#host.change(false);
|
|
54
|
+
this.#host.tooltipGroup().closed();
|
|
55
|
+
}
|
|
56
|
+
/** Close after the grace period, cancelling a pending hover open. */
|
|
57
|
+
hoverEnd() {
|
|
58
|
+
this.#cancelOpening();
|
|
59
|
+
this.#grace = setTimeout(() => {
|
|
60
|
+
this.#host.change(false);
|
|
61
|
+
this.#host.tooltipGroup().closed();
|
|
62
|
+
this.#grace = null;
|
|
63
|
+
}, TOOLTIP_GRACE_MS);
|
|
64
|
+
}
|
|
65
|
+
/** The pointer reached the content: cancel a pending grace close. */
|
|
66
|
+
holdOpen() {
|
|
67
|
+
// Stryker disable next-line ConditionalExpression: equivalent — clearTimeout(null) is a spec'd no-op, so forcing this guard true is harmless when no grace close is pending.
|
|
68
|
+
if (this.#grace !== null) {
|
|
69
|
+
clearTimeout(this.#grace);
|
|
70
|
+
this.#grace = null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Listen for Escape and outside presses while open, and only then. */
|
|
74
|
+
sync() {
|
|
75
|
+
const { open } = this.#host.state();
|
|
76
|
+
if (open) {
|
|
77
|
+
document.addEventListener("keydown", this.#onKeydown);
|
|
78
|
+
document.addEventListener("pointerdown", this.#onPointerdown);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
this.#stopListening();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
#stopListening() {
|
|
85
|
+
document.removeEventListener("keydown", this.#onKeydown);
|
|
86
|
+
document.removeEventListener("pointerdown", this.#onPointerdown);
|
|
87
|
+
}
|
|
88
|
+
/** Cancel every timer and stop listening, so nothing outlives the host. */
|
|
89
|
+
disconnect() {
|
|
90
|
+
this.#cancelOpening();
|
|
91
|
+
this.holdOpen();
|
|
92
|
+
this.#stopListening();
|
|
93
|
+
}
|
|
94
|
+
#cancelOpening() {
|
|
95
|
+
// Stryker disable next-line ConditionalExpression: equivalent — clearTimeout(null) is a spec'd no-op, so forcing this guard true is harmless when no hover open is pending.
|
|
96
|
+
if (this.#opening !== null) {
|
|
97
|
+
clearTimeout(this.#opening);
|
|
98
|
+
this.#opening = null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
#keydown(event) {
|
|
102
|
+
if (event.key !== "Escape")
|
|
103
|
+
return;
|
|
104
|
+
this.#host.escapeKeyDown?.(event);
|
|
105
|
+
if (!event.defaultPrevented)
|
|
106
|
+
this.closeNow();
|
|
107
|
+
}
|
|
108
|
+
#pointerdown(event) {
|
|
109
|
+
if (this.#host.content()?.contains(event.target))
|
|
110
|
+
return;
|
|
111
|
+
this.#host.pointerDownOutside?.(event);
|
|
112
|
+
if (!event.defaultPrevented)
|
|
113
|
+
this.closeNow();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A set of tooltips that share a skip-delay window (React's `Tooltip.Provider`):
|
|
3
|
+
* once one has opened, the next to be hovered opens at once rather than
|
|
4
|
+
* waiting its delay, until the group has been quiet for the skip delay. The
|
|
5
|
+
* React Provider and the custom elements each hold one.
|
|
6
|
+
*/
|
|
7
|
+
export declare class TooltipGroup {
|
|
8
|
+
#private;
|
|
9
|
+
/** `skipDelay` is read each time a tooltip closes, so a changed value applies. */
|
|
10
|
+
constructor(skipDelay: () => number);
|
|
11
|
+
/** Whether a tooltip in the group is open, or closed within the skip delay. */
|
|
12
|
+
isOpen(): boolean;
|
|
13
|
+
/** A tooltip in the group opened: the window is open, and any pending close is off. */
|
|
14
|
+
opened(): void;
|
|
15
|
+
/**
|
|
16
|
+
* A tooltip in the group closed: the window stays open for the skip delay.
|
|
17
|
+
* A second close restarts it, cancelling the first timer rather than
|
|
18
|
+
* orphaning it — two closes in a row are routine (Escape, then a blur).
|
|
19
|
+
*/
|
|
20
|
+
closed(): void;
|
|
21
|
+
/** Cancel a pending close, so no timer outlives the group's host. */
|
|
22
|
+
dispose(): void;
|
|
23
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A set of tooltips that share a skip-delay window (React's `Tooltip.Provider`):
|
|
3
|
+
* once one has opened, the next to be hovered opens at once rather than
|
|
4
|
+
* waiting its delay, until the group has been quiet for the skip delay. The
|
|
5
|
+
* React Provider and the custom elements each hold one.
|
|
6
|
+
*/
|
|
7
|
+
export class TooltipGroup {
|
|
8
|
+
#skipDelay;
|
|
9
|
+
#open = false;
|
|
10
|
+
#closing = null;
|
|
11
|
+
/** `skipDelay` is read each time a tooltip closes, so a changed value applies. */
|
|
12
|
+
constructor(skipDelay) {
|
|
13
|
+
this.#skipDelay = skipDelay;
|
|
14
|
+
}
|
|
15
|
+
/** Whether a tooltip in the group is open, or closed within the skip delay. */
|
|
16
|
+
isOpen() {
|
|
17
|
+
return this.#open;
|
|
18
|
+
}
|
|
19
|
+
/** A tooltip in the group opened: the window is open, and any pending close is off. */
|
|
20
|
+
opened() {
|
|
21
|
+
// Stryker disable next-line ConditionalExpression: equivalent — clearTimeout(null) is a spec'd no-op, so forcing this guard true is harmless when no close is pending.
|
|
22
|
+
if (this.#closing !== null) {
|
|
23
|
+
clearTimeout(this.#closing);
|
|
24
|
+
this.#closing = null;
|
|
25
|
+
}
|
|
26
|
+
this.#open = true;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A tooltip in the group closed: the window stays open for the skip delay.
|
|
30
|
+
* A second close restarts it, cancelling the first timer rather than
|
|
31
|
+
* orphaning it — two closes in a row are routine (Escape, then a blur).
|
|
32
|
+
*/
|
|
33
|
+
closed() {
|
|
34
|
+
clearTimeout(this.#closing ?? undefined);
|
|
35
|
+
this.#closing = setTimeout(() => {
|
|
36
|
+
this.#open = false;
|
|
37
|
+
this.#closing = null;
|
|
38
|
+
}, this.#skipDelay());
|
|
39
|
+
}
|
|
40
|
+
/** Cancel a pending close, so no timer outlives the group's host. */
|
|
41
|
+
dispose() {
|
|
42
|
+
clearTimeout(this.#closing ?? undefined);
|
|
43
|
+
}
|
|
44
|
+
}
|