@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.
@@ -0,0 +1,83 @@
1
+ import { ElementBase, type ElementRegistry } from "../internal/element.js";
2
+ import { PrimitivRootElement } from "../internal/RootElement.js";
3
+ import { TooltipGroup } from "./TooltipGroup.js";
4
+ export type { ElementRegistry };
5
+ /**
6
+ * `<primitiv-tooltip-provider>` — optional. The tooltips inside it share one
7
+ * skip-delay window and its default delay, as inside React's
8
+ * `Tooltip.Provider`; tooltips outside any provider share a document-wide one.
9
+ *
10
+ * @customElement primitiv-tooltip-provider
11
+ * @attr {number} delay-duration - How long, in milliseconds, a pointer rests on a trigger before its tooltip opens. Default 700. A tooltip's own `delay-duration` overrides it.
12
+ * @attr {number} skip-delay-duration - How long, in milliseconds, after one tooltip closes that hovering another opens it at once. Default 300.
13
+ */
14
+ export declare class PrimitivTooltipProviderElement extends ElementBase {
15
+ #private;
16
+ /** The skip-delay window this provider's tooltips share, created on first use. */
17
+ get group(): TooltipGroup;
18
+ }
19
+ /**
20
+ * `<primitiv-tooltip>` — the state owner of a tooltip: a short description of
21
+ * its trigger that appears on hover after a delay, and at once on focus. It
22
+ * closes when the pointer leaves (after a moment, so it can be reached),
23
+ * on blur, on Escape and on a press outside. Moving from one trigger to the
24
+ * next opens each next tooltip at once (the skip delay). The tooltip is shown
25
+ * as a `popover="manual"` in the top layer, with the trigger as its source,
26
+ * which makes the trigger its implicit anchor for CSS anchor positioning.
27
+ *
28
+ * The root finds its own parts and reconciles their ARIA from its reflected
29
+ * state whenever a part arrives, leaves, or changes an attribute the root
30
+ * reads (the lifecycle is `PrimitivRootElement`'s).
31
+ *
32
+ * @customElement primitiv-tooltip
33
+ * @summary The state owner of a tooltip, shown on hover and focus.
34
+ * @attr {boolean} open - Whether the tooltip is showing. Reflected: it changes as the user hovers, focuses and dismisses it. Set it (or the `open` property) to show or hide it from script.
35
+ * @attr {number} delay-duration - How long, in milliseconds, a pointer rests on the trigger before the tooltip opens. Defaults to the provider's, or 700. Focus always opens it at once.
36
+ * @attr {boolean} disable-hoverable-content - Close as soon as the pointer leaves the trigger, rather than giving it a moment to reach the tooltip.
37
+ * @fires {CustomEvent<{ open: boolean }>} primitiv-tooltip-change - Bubbles, cancelable, fired before a user-driven change is applied — hover, focus, blur, Escape or a press outside — with the open state afterwards. Call `preventDefault()` to veto it.
38
+ */
39
+ export declare class PrimitivTooltipElement extends PrimitivRootElement {
40
+ #private;
41
+ protected readonly controller: {
42
+ connect(): void;
43
+ disconnect(): void;
44
+ };
45
+ protected readonly reconcileOn: readonly string[];
46
+ /** Whether the tooltip is showing, reflected as the boolean `open` attribute. */
47
+ get open(): boolean;
48
+ set open(next: boolean);
49
+ protected reconcile(): void;
50
+ }
51
+ /**
52
+ * `<primitiv-tooltip-trigger>` — an interactive part. It decorates exactly one
53
+ * child the consumer writes, a `<button>`, which opens the tooltip on hover and
54
+ * focus and is described by it (`aria-describedby`), so the rendered DOM
55
+ * matches React's (RFC 0033 D24).
56
+ *
57
+ * @customElement primitiv-tooltip-trigger
58
+ */
59
+ export declare class PrimitivTooltipTriggerElement extends ElementBase {
60
+ }
61
+ /**
62
+ * `<primitiv-tooltip-content>` — the tooltip itself. Structural: the root
63
+ * gives it an id, `role="tooltip"`, `popover="manual"` (write it in your markup
64
+ * too, so it is hidden before the script loads) and `data-state`.
65
+ *
66
+ * @customElement primitiv-tooltip-content
67
+ */
68
+ export declare class PrimitivTooltipContentElement extends ElementBase {
69
+ }
70
+ /**
71
+ * `<primitiv-tooltip-arrow>` — an optional, decorative pointer, inside the
72
+ * content. The element writes nothing on it; style it as you like.
73
+ *
74
+ * @customElement primitiv-tooltip-arrow
75
+ */
76
+ export declare class PrimitivTooltipArrowElement extends ElementBase {
77
+ }
78
+ /**
79
+ * Define the five Tooltip elements on `registry` (the global `customElements`
80
+ * by default). Safe to call more than once, and a no-op where there is no
81
+ * registry (a server render).
82
+ */
83
+ export declare function defineTooltip(registry?: ElementRegistry | undefined): void;
@@ -0,0 +1,222 @@
1
+ import { defineElements, ElementBase, reportOnce, write } from "../internal/element.js";
2
+ import { PrimitivRootElement } from "../internal/RootElement.js";
3
+ import { TooltipController } from "./TooltipController.js";
4
+ import { TooltipGroup } from "./TooltipGroup.js";
5
+ // Stryker disable StringLiteral: static — evaluated once at import, so runtime mutant switching never reaches them; the names are pinned by the defineTooltip registry test instead.
6
+ const ROOT = "primitiv-tooltip";
7
+ const PROVIDER = "primitiv-tooltip-provider";
8
+ const TRIGGER = "primitiv-tooltip-trigger";
9
+ const CONTENT = "primitiv-tooltip-content";
10
+ const ARROW = "primitiv-tooltip-arrow";
11
+ // Stryker restore StringLiteral
12
+ /** React's `Tooltip.Provider` defaults, for a tooltip and a provider that set none. */
13
+ const DEFAULT_DELAY = 700;
14
+ const DEFAULT_SKIP_DELAY = 300;
15
+ /** A numeric attribute in milliseconds, or `fallback` when it is absent or not a number. */
16
+ function milliseconds(element, name, fallback) {
17
+ const value = Number(element.getAttribute(name) ?? Number.NaN);
18
+ return Number.isNaN(value) ? fallback : value;
19
+ }
20
+ /** The skip-delay window every tooltip outside a provider shares. */
21
+ // Stryker disable next-line ArrowFunction: static — created once at import, so runtime mutant switching never reaches it; the 300ms window is pinned by the skip-delay interaction tests.
22
+ const documentGroup = new TooltipGroup(() => DEFAULT_SKIP_DELAY);
23
+ /**
24
+ * `<primitiv-tooltip-provider>` — optional. The tooltips inside it share one
25
+ * skip-delay window and its default delay, as inside React's
26
+ * `Tooltip.Provider`; tooltips outside any provider share a document-wide one.
27
+ *
28
+ * @customElement primitiv-tooltip-provider
29
+ * @attr {number} delay-duration - How long, in milliseconds, a pointer rests on a trigger before its tooltip opens. Default 700. A tooltip's own `delay-duration` overrides it.
30
+ * @attr {number} skip-delay-duration - How long, in milliseconds, after one tooltip closes that hovering another opens it at once. Default 300.
31
+ */
32
+ export class PrimitivTooltipProviderElement extends ElementBase {
33
+ #group;
34
+ /** The skip-delay window this provider's tooltips share, created on first use. */
35
+ get group() {
36
+ return (this.#group ??= new TooltipGroup(() => milliseconds(this, "skip-delay-duration", DEFAULT_SKIP_DELAY)));
37
+ }
38
+ }
39
+ /**
40
+ * `<primitiv-tooltip>` — the state owner of a tooltip: a short description of
41
+ * its trigger that appears on hover after a delay, and at once on focus. It
42
+ * closes when the pointer leaves (after a moment, so it can be reached),
43
+ * on blur, on Escape and on a press outside. Moving from one trigger to the
44
+ * next opens each next tooltip at once (the skip delay). The tooltip is shown
45
+ * as a `popover="manual"` in the top layer, with the trigger as its source,
46
+ * which makes the trigger its implicit anchor for CSS anchor positioning.
47
+ *
48
+ * The root finds its own parts and reconciles their ARIA from its reflected
49
+ * state whenever a part arrives, leaves, or changes an attribute the root
50
+ * reads (the lifecycle is `PrimitivRootElement`'s).
51
+ *
52
+ * @customElement primitiv-tooltip
53
+ * @summary The state owner of a tooltip, shown on hover and focus.
54
+ * @attr {boolean} open - Whether the tooltip is showing. Reflected: it changes as the user hovers, focuses and dismisses it. Set it (or the `open` property) to show or hide it from script.
55
+ * @attr {number} delay-duration - How long, in milliseconds, a pointer rests on the trigger before the tooltip opens. Defaults to the provider's, or 700. Focus always opens it at once.
56
+ * @attr {boolean} disable-hoverable-content - Close as soon as the pointer leaves the trigger, rather than giving it a moment to reach the tooltip.
57
+ * @fires {CustomEvent<{ open: boolean }>} primitiv-tooltip-change - Bubbles, cancelable, fired before a user-driven change is applied — hover, focus, blur, Escape or a press outside — with the open state afterwards. Call `preventDefault()` to veto it.
58
+ */
59
+ export class PrimitivTooltipElement extends PrimitivRootElement {
60
+ /** The decorated trigger button and the tooltip, as of the last reconcile. */
61
+ #trigger = null;
62
+ #content = null;
63
+ /** The tooltip this root has shown, while it shows. */
64
+ #shown = null;
65
+ /** The listeners on the trigger and the tooltip, rebuilt when either changes. */
66
+ #bindings = null;
67
+ #tooltip = new TooltipController(this.#host());
68
+ controller = {
69
+ connect: () => { },
70
+ disconnect: () => this.#disconnect(),
71
+ };
72
+ reconcileOn = ["open"];
73
+ /** Whether the tooltip is showing, reflected as the boolean `open` attribute. */
74
+ get open() {
75
+ return this.hasAttribute("open");
76
+ }
77
+ set open(next) {
78
+ this.toggleAttribute("open", next);
79
+ }
80
+ /** The controller's view of this element: state lives in the reflected attribute. */
81
+ #host() {
82
+ const provider = () => this.closest(PROVIDER);
83
+ return {
84
+ state: () => ({ open: this.open }),
85
+ change: (open) => {
86
+ // The controller reports every close it is asked for; announce a real change only.
87
+ if (open !== this.open && this.announce("primitiv-tooltip-change", { open }))
88
+ this.open = open;
89
+ },
90
+ delay: () => {
91
+ const fallback = provider();
92
+ return milliseconds(this, "delay-duration", fallback ? milliseconds(fallback, "delay-duration", DEFAULT_DELAY) : DEFAULT_DELAY);
93
+ },
94
+ tooltipGroup: () => provider()?.group ?? documentGroup,
95
+ content: () => this.#content,
96
+ };
97
+ }
98
+ #disconnect() {
99
+ // An open tooltip that leaves would hold its group's skip window open for
100
+ // good, opening every later tooltip at once: close the window instead.
101
+ if (this.open)
102
+ this.#host().tooltipGroup().closed();
103
+ this.#bindings?.abort();
104
+ this.#bindings = null;
105
+ this.#trigger = null;
106
+ this.#content = null;
107
+ // Leaving the document hides a popover with no event: show it again on the next connect.
108
+ this.#shown = null;
109
+ this.#tooltip.disconnect();
110
+ }
111
+ /** Listen on `trigger` and `content`, rebinding only when either changed. */
112
+ #bind(trigger, content) {
113
+ // Stryker disable next-line ConditionalExpression: equivalent — rebinding the same elements swaps one listener set for an identical one.
114
+ if (trigger === this.#trigger && content === this.#content)
115
+ return;
116
+ this.#bindings?.abort();
117
+ this.#bindings = new AbortController();
118
+ this.#trigger = trigger;
119
+ this.#content = content;
120
+ const options = { signal: this.#bindings.signal };
121
+ const tooltip = this.#tooltip;
122
+ trigger?.addEventListener("pointerenter", () => tooltip.hoverStart(), options);
123
+ trigger?.addEventListener("pointerleave", () => (this.hasAttribute("disable-hoverable-content") ? tooltip.closeNow() : tooltip.hoverEnd()), options);
124
+ trigger?.addEventListener("focus", () => tooltip.openNow(), options);
125
+ trigger?.addEventListener("blur", () => tooltip.closeNow(), options);
126
+ content?.addEventListener("pointerenter", () => tooltip.holdOpen(), options);
127
+ content?.addEventListener("pointerleave", () => tooltip.closeNow(), options);
128
+ }
129
+ reconcile() {
130
+ const open = this.open;
131
+ const state = open ? "open" : "closed";
132
+ write(this, "data-state", state);
133
+ const [triggerPart] = this.parts(TRIGGER);
134
+ const [content = null] = this.parts(CONTENT);
135
+ const trigger = triggerPart ? decoratable(triggerPart) : null;
136
+ if (content) {
137
+ if (!content.id)
138
+ content.id = `${this.rootId()}-content`;
139
+ write(content, "role", "tooltip");
140
+ if (!content.hasAttribute("popover"))
141
+ content.setAttribute("popover", "manual");
142
+ write(content, "data-state", state);
143
+ }
144
+ if (trigger) {
145
+ write(trigger, "aria-describedby", content ? content.id : null);
146
+ write(trigger, "data-state", state);
147
+ }
148
+ this.#bind(trigger, content);
149
+ this.#show(open ? content : null);
150
+ this.#tooltip.sync();
151
+ }
152
+ /** Show `content` (anchored to the trigger), or hide what is showing when it is `null`. */
153
+ #show(content) {
154
+ if (content === this.#shown)
155
+ return;
156
+ // Stryker disable next-line ConditionalExpression: equivalent — hiding nothing throws inside the try below, which swallows it.
157
+ if (this.#shown) {
158
+ try {
159
+ this.#shown.hidePopover();
160
+ }
161
+ catch {
162
+ // Already hidden, or no longer a popover.
163
+ }
164
+ }
165
+ this.#shown = content;
166
+ if (!content)
167
+ return;
168
+ const showable = content;
169
+ if (this.#trigger)
170
+ showable.showPopover({ source: this.#trigger });
171
+ else
172
+ showable.showPopover();
173
+ }
174
+ }
175
+ /** The one `<button>` a trigger decorates, or `null`, reported once, without exactly one element child. */
176
+ function decoratable(trigger) {
177
+ if (trigger.childElementCount === 1)
178
+ return trigger.firstElementChild;
179
+ reportOnce(trigger, `<${TRIGGER}> needs exactly one element child (a <button>) to decorate.`);
180
+ return null;
181
+ }
182
+ /**
183
+ * `<primitiv-tooltip-trigger>` — an interactive part. It decorates exactly one
184
+ * child the consumer writes, a `<button>`, which opens the tooltip on hover and
185
+ * focus and is described by it (`aria-describedby`), so the rendered DOM
186
+ * matches React's (RFC 0033 D24).
187
+ *
188
+ * @customElement primitiv-tooltip-trigger
189
+ */
190
+ export class PrimitivTooltipTriggerElement extends ElementBase {
191
+ }
192
+ /**
193
+ * `<primitiv-tooltip-content>` — the tooltip itself. Structural: the root
194
+ * gives it an id, `role="tooltip"`, `popover="manual"` (write it in your markup
195
+ * too, so it is hidden before the script loads) and `data-state`.
196
+ *
197
+ * @customElement primitiv-tooltip-content
198
+ */
199
+ export class PrimitivTooltipContentElement extends ElementBase {
200
+ }
201
+ /**
202
+ * `<primitiv-tooltip-arrow>` — an optional, decorative pointer, inside the
203
+ * content. The element writes nothing on it; style it as you like.
204
+ *
205
+ * @customElement primitiv-tooltip-arrow
206
+ */
207
+ export class PrimitivTooltipArrowElement extends ElementBase {
208
+ }
209
+ /**
210
+ * Define the five Tooltip elements on `registry` (the global `customElements`
211
+ * by default). Safe to call more than once, and a no-op where there is no
212
+ * registry (a server render).
213
+ */
214
+ export function defineTooltip(registry = globalThis.customElements) {
215
+ defineElements(registry, [
216
+ [ROOT, PrimitivTooltipElement],
217
+ [PROVIDER, PrimitivTooltipProviderElement],
218
+ [TRIGGER, PrimitivTooltipTriggerElement],
219
+ [CONTENT, PrimitivTooltipContentElement],
220
+ [ARROW, PrimitivTooltipArrowElement],
221
+ ]);
222
+ }
@@ -0,0 +1,3 @@
1
+ export { TooltipController, type TooltipControllerHost } from "./TooltipController.js";
2
+ export { TooltipGroup } from "./TooltipGroup.js";
3
+ export { defineTooltip, PrimitivTooltipArrowElement, PrimitivTooltipContentElement, PrimitivTooltipElement, PrimitivTooltipProviderElement, PrimitivTooltipTriggerElement, } from "./elements.js";
@@ -0,0 +1,3 @@
1
+ export { TooltipController } from "./TooltipController.js";
2
+ export { TooltipGroup } from "./TooltipGroup.js";
3
+ export { defineTooltip, PrimitivTooltipArrowElement, PrimitivTooltipContentElement, PrimitivTooltipElement, PrimitivTooltipProviderElement, PrimitivTooltipTriggerElement, } from "./elements.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primitiv-ui/web",
3
- "version": "0.1.54",
3
+ "version": "0.1.56",
4
4
  "description": "Primitiv's headless components as standard custom elements: light DOM, no framework, no runtime dependencies. Works in plain HTML, server-rendered stacks, React, Vue, Angular and anything else that renders HTML.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -22,10 +22,18 @@
22
22
  "types": "./dist/modal/index.d.ts",
23
23
  "default": "./dist/modal/index.js"
24
24
  },
25
+ "./popover": {
26
+ "types": "./dist/popover/index.d.ts",
27
+ "default": "./dist/popover/index.js"
28
+ },
25
29
  "./tabs": {
26
30
  "types": "./dist/tabs/index.d.ts",
27
31
  "default": "./dist/tabs/index.js"
28
32
  },
33
+ "./tooltip": {
34
+ "types": "./dist/tooltip/index.d.ts",
35
+ "default": "./dist/tooltip/index.js"
36
+ },
29
37
  "./define": {
30
38
  "types": "./dist/define/index.d.ts",
31
39
  "default": "./dist/define/index.js"
@@ -42,10 +50,18 @@
42
50
  "types": "./dist/define/modal.d.ts",
43
51
  "default": "./dist/define/modal.js"
44
52
  },
53
+ "./define/popover": {
54
+ "types": "./dist/define/popover.d.ts",
55
+ "default": "./dist/define/popover.js"
56
+ },
45
57
  "./define/tabs": {
46
58
  "types": "./dist/define/tabs.d.ts",
47
59
  "default": "./dist/define/tabs.js"
48
60
  },
61
+ "./define/tooltip": {
62
+ "types": "./dist/define/tooltip.d.ts",
63
+ "default": "./dist/define/tooltip.js"
64
+ },
49
65
  "./tags": {
50
66
  "types": "./dist/tags.d.ts",
51
67
  "default": "./dist/tags.js"
@@ -76,7 +92,7 @@
76
92
  "directory": "packages/web"
77
93
  },
78
94
  "dependencies": {
79
- "@primitiv-ui/core": "0.1.54"
95
+ "@primitiv-ui/core": "0.1.56"
80
96
  },
81
97
  "devDependencies": {
82
98
  "@custom-elements-manifest/analyzer": "0.11.0",