@antadesign/anta 0.2.1 → 0.3.0
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 +14 -0
- package/dist/anta_helpers.d.ts +39 -1
- package/dist/anta_helpers.js +30 -2
- package/dist/components/Button.d.ts +7 -4
- package/dist/components/Button.js +12 -14
- package/dist/components/Checkbox.d.ts +97 -0
- package/dist/components/Checkbox.js +77 -0
- package/dist/components/Expander.d.ts +74 -0
- package/dist/components/Expander.js +53 -0
- package/dist/components/Input.d.ts +159 -0
- package/dist/components/Input.js +150 -0
- package/dist/components/Menu.d.ts +70 -0
- package/dist/components/Menu.js +42 -0
- package/dist/components/MenuGroup.d.ts +24 -0
- package/dist/components/MenuGroup.js +19 -0
- package/dist/components/MenuItem.d.ts +50 -0
- package/dist/components/MenuItem.js +41 -0
- package/dist/components/MenuSeparator.d.ts +14 -0
- package/dist/components/MenuSeparator.js +7 -0
- package/dist/components/Progress.d.ts +12 -6
- package/dist/components/Progress.js +7 -4
- package/dist/components/Radio.d.ts +37 -0
- package/dist/components/Radio.js +33 -0
- package/dist/components/RadioGroup.d.ts +119 -0
- package/dist/components/RadioGroup.js +108 -0
- package/dist/components/Tag.d.ts +38 -5
- package/dist/components/Tag.js +9 -5
- package/dist/components/Text.d.ts +27 -12
- package/dist/components/Text.js +6 -3
- package/dist/components/Title.d.ts +10 -1
- package/dist/components/Tooltip.d.ts +19 -9
- package/dist/components/Tooltip.js +2 -2
- package/dist/elements/a-button.css +1 -1
- package/dist/elements/a-button.d.ts +56 -0
- package/dist/elements/a-button.js +13 -11
- package/dist/elements/a-checkbox.css +1 -0
- package/dist/elements/a-checkbox.d.ts +52 -0
- package/dist/elements/a-checkbox.js +130 -0
- package/dist/elements/a-expander.css +1 -0
- package/dist/elements/a-expander.d.ts +28 -0
- package/dist/elements/a-expander.js +237 -0
- package/dist/elements/a-icon.d.ts +14 -0
- package/dist/elements/a-icon.shapes.css +1 -1
- package/dist/elements/a-icon.shapes.d.ts +10 -2
- package/dist/elements/a-icon.shapes.js +12 -3
- package/dist/elements/a-input.css +1 -0
- package/dist/elements/a-input.d.ts +68 -0
- package/dist/elements/a-input.js +511 -0
- package/dist/elements/a-menu-group.css +1 -0
- package/dist/elements/a-menu-group.d.ts +13 -0
- package/dist/elements/a-menu-group.js +15 -0
- package/dist/elements/a-menu-item.css +1 -0
- package/dist/elements/a-menu-item.d.ts +47 -0
- package/dist/elements/a-menu-item.js +30 -0
- package/dist/elements/a-menu-separator.css +1 -0
- package/dist/elements/a-menu-separator.d.ts +13 -0
- package/dist/elements/a-menu-separator.js +15 -0
- package/dist/elements/a-menu.css +1 -0
- package/dist/elements/a-menu.d.ts +171 -0
- package/dist/elements/a-menu.js +714 -0
- package/dist/elements/a-progress.css +1 -1
- package/dist/elements/a-progress.d.ts +12 -0
- package/dist/elements/a-progress.js +1 -0
- package/dist/elements/a-radio-group.css +1 -0
- package/dist/elements/a-radio-group.d.ts +33 -0
- package/dist/elements/a-radio-group.js +160 -0
- package/dist/elements/a-radio.css +1 -0
- package/dist/elements/a-radio.d.ts +14 -0
- package/dist/elements/a-radio.js +46 -0
- package/dist/elements/a-tag.css +1 -1
- package/dist/elements/a-text.css +1 -1
- package/dist/elements/a-text.d.ts +42 -3
- package/dist/elements/a-text.js +73 -33
- package/dist/elements/a-title.css +1 -1
- package/dist/elements/a-tooltip.d.ts +65 -15
- package/dist/elements/a-tooltip.js +155 -115
- package/dist/elements/index.d.ts +9 -0
- package/dist/elements/index.js +27 -0
- package/dist/general_types.d.ts +471 -19
- package/dist/index.d.ts +16 -0
- package/dist/index.js +16 -0
- package/dist/jsx-runtime.d.ts +42 -7
- package/dist/jsx-runtime.js +14 -2
- package/dist/tokens.css +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,42 @@
|
|
|
1
1
|
import { HTMLElementBase } from '../anta_helpers';
|
|
2
2
|
import './a-tooltip.css';
|
|
3
|
+
/**
|
|
4
|
+
* `<a-tooltip>` — floating bubble placed as a child of the element it
|
|
5
|
+
* describes.
|
|
6
|
+
*
|
|
7
|
+
* Styling notes (`a-tooltip.css` ships comment-free):
|
|
8
|
+
* - `a-tooltip:not(:defined)` is hidden — before upgrade the host is an
|
|
9
|
+
* unknown inline element and its content would flash *inside the anchor*.
|
|
10
|
+
* Once defined, the shadow `:host { display: contents }` governs and
|
|
11
|
+
* content renders only in the popover via the slot.
|
|
12
|
+
* - Only the bubble "chrome" is tokenized (`--tooltip-*`): it lives inside
|
|
13
|
+
* the shadow popover, unreachable from plain consumer CSS; the custom
|
|
14
|
+
* properties inherit across the shadow boundary. The content is slotted
|
|
15
|
+
* light DOM — its font/color/size are already styleable from the page and
|
|
16
|
+
* are intentionally NOT tokens.
|
|
17
|
+
* - The frost: `--tooltip-bg` is a mostly-opaque mix of `--bg-1` that, with
|
|
18
|
+
* the container's backdrop blur, reads as a frosted bubble. `--bg-1` is
|
|
19
|
+
* white in light / black in dark, so dark mode flips automatically (the
|
|
20
|
+
* `.dark` override just lowers the alpha and adds an inset white ring so
|
|
21
|
+
* the edge stays crisp on dark content). There's no real border by
|
|
22
|
+
* default — the hairline edge comes from `--tooltip-shadow`; set
|
|
23
|
+
* `--tooltip-border` for an actual one.
|
|
24
|
+
* - On coarse/no-hover pointers, an anchor that owns a tooltip
|
|
25
|
+
* (`:where(:has(> a-tooltip))`, specificity 0 for easy override)
|
|
26
|
+
* suppresses long-press text selection and the iOS callout so
|
|
27
|
+
* press-and-hold cleanly reveals the bubble (see the long-press handling
|
|
28
|
+
* below) instead of selecting text or popping the system menu.
|
|
29
|
+
*/
|
|
3
30
|
export declare class ATooltipElement extends HTMLElementBase {
|
|
4
31
|
static observedAttributes: string[];
|
|
5
32
|
/** Shadow-internal popover surface — the only thing we ever mutate. */
|
|
6
33
|
private container;
|
|
34
|
+
/** Inner bubble inside `.container`. This IS the `<slot>` (styled
|
|
35
|
+
* `display:block`), so it both projects the content and paints all visual
|
|
36
|
+
* chrome — one element instead of a div-wrapping-a-slot. It carries the
|
|
37
|
+
* proximity (distance-from-anchor) opacity, kept separate from the
|
|
38
|
+
* container's enter/exit/cross-fade opacity. */
|
|
39
|
+
private bubble;
|
|
7
40
|
private anchor;
|
|
8
41
|
listening: boolean;
|
|
9
42
|
private shown;
|
|
@@ -27,31 +60,48 @@ export declare class ATooltipElement extends HTMLElementBase {
|
|
|
27
60
|
* fed the latest cursor event, so it shows at the cursor and so moving back
|
|
28
61
|
* onto an anchor after the tooltip was dismissed re-arms it. */
|
|
29
62
|
private makeDebouncedShow;
|
|
30
|
-
/** The element's OWN window / document. The class may be defined in a
|
|
31
|
-
* different realm than the element lives in (e.g. the docs playground
|
|
32
|
-
* renders into an iframe but reuses the parent page's element class), so
|
|
33
|
-
* the module-global `window`/`document` can point at the wrong frame.
|
|
34
|
-
* Everything viewport- or document-scoped (clamping, scroll/key/move
|
|
35
|
-
* listeners) must go through these so it's correct in any frame. */
|
|
36
|
-
private get view();
|
|
37
|
-
private get doc();
|
|
38
63
|
private get isInteractive();
|
|
39
|
-
/** Pinned under the anchor (no cursor-following)
|
|
40
|
-
*
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
* a touch long-press (a finger can't track a following bubble). */
|
|
64
|
+
/** Pinned under the anchor (no cursor-following) — the DEFAULT. Following the
|
|
65
|
+
* cursor is opt-in via the `follow` attribute. `interactive` (you can't move
|
|
66
|
+
* into a bubble that chases the cursor) and a touch long-press (a finger
|
|
67
|
+
* can't track one) force pinned regardless of `follow`. */
|
|
44
68
|
private get isPinned();
|
|
45
69
|
private get prefersTop();
|
|
46
70
|
private get delay();
|
|
71
|
+
private positionFrame?;
|
|
72
|
+
private pendingPosition?;
|
|
73
|
+
private schedulePosition;
|
|
74
|
+
/** Pick the vertical origin: prefer the `above` candidate when `preferAbove`
|
|
75
|
+
* (auto-flipping to `below` if it'd clip the top), else `below` (flipping to
|
|
76
|
+
* `above` if it'd clip the bottom). Shared by anchor- and cursor-anchored
|
|
77
|
+
* placement — only the candidate origins differ. */
|
|
78
|
+
private flipVertical;
|
|
79
|
+
/** Clamp a candidate (left, top) into the viewport and write it as the
|
|
80
|
+
* container's transform — the single place the transform is set. */
|
|
81
|
+
private place;
|
|
47
82
|
private positionToTarget;
|
|
48
83
|
private positionToMouse;
|
|
49
84
|
private position;
|
|
85
|
+
/** Opacity for the inner bubble from the cursor's distance to the anchor rect
|
|
86
|
+
* (0 inside the rect): 1 within PROX_NEAR, a linear ramp to 0 at PROX_FAR.
|
|
87
|
+
* Cursor-follow only — drives the "fade as you move away" behaviour. */
|
|
88
|
+
private proximityOpacity;
|
|
50
89
|
show: (e?: MouseEvent) => void;
|
|
51
90
|
hide: () => void;
|
|
91
|
+
/** Remove every document / window listener this element binds and stop the
|
|
92
|
+
* fade timer. Used by hide() (after the exit fade) and by cleanup()
|
|
93
|
+
* (immediately). Idempotent. */
|
|
94
|
+
private detachGlobals;
|
|
95
|
+
/** Full, immediate teardown: close the popover now (no fade), detach every
|
|
96
|
+
* listener, clear all timers, release the coordinator slot, reset state.
|
|
97
|
+
* Idempotent. Called from disconnectedCallback, and from the orphan safety
|
|
98
|
+
* net (claimOpen / onDocMove) when the element is gone but
|
|
99
|
+
* disconnectedCallback didn't fire. Not `private` — the module coordinator
|
|
100
|
+
* calls it. */
|
|
101
|
+
cleanup(): void;
|
|
52
102
|
/** While shown, track the cursor even after it has left the anchor (during
|
|
53
|
-
* the close delay).
|
|
54
|
-
* stay
|
|
103
|
+
* the close delay). Only runs for `follow` tooltips — pinned bubbles
|
|
104
|
+
* (the default, `interactive`, touch) stay put. */
|
|
55
105
|
private onDocMove;
|
|
56
106
|
/** Hide after CLOSE_DELAY unless something cancels it first (re-enter, or
|
|
57
107
|
* another tooltip claiming the slot). Lets the bubble bridge the gap to a
|
|
@@ -8,6 +8,9 @@ const DEFAULT_DELAY = 250;
|
|
|
8
8
|
const FADE_MS = 150;
|
|
9
9
|
const GRACE_MS = 300;
|
|
10
10
|
const CLOSE_DELAY = 120;
|
|
11
|
+
const PROX_NEAR = 10;
|
|
12
|
+
const PROX_FAR = 100;
|
|
13
|
+
const PROX_FADE_MS = 60;
|
|
11
14
|
const ENTER_TOUCH_DELAY = 500;
|
|
12
15
|
const LEAVE_TOUCH_DELAY = 1500;
|
|
13
16
|
const TOUCH_SLOP = 10;
|
|
@@ -26,7 +29,10 @@ function clearGrace() {
|
|
|
26
29
|
}
|
|
27
30
|
}
|
|
28
31
|
function claimOpen(el) {
|
|
29
|
-
if (currentOpen && currentOpen !== el)
|
|
32
|
+
if (currentOpen && currentOpen !== el) {
|
|
33
|
+
if (currentOpen.isConnected) currentOpen.hide();
|
|
34
|
+
else currentOpen.cleanup();
|
|
35
|
+
}
|
|
30
36
|
currentOpen = el;
|
|
31
37
|
clearGrace();
|
|
32
38
|
}
|
|
@@ -58,6 +64,12 @@ class ATooltipElement extends HTMLElementBase {
|
|
|
58
64
|
static observedAttributes = ["delay"];
|
|
59
65
|
/** Shadow-internal popover surface — the only thing we ever mutate. */
|
|
60
66
|
container;
|
|
67
|
+
/** Inner bubble inside `.container`. This IS the `<slot>` (styled
|
|
68
|
+
* `display:block`), so it both projects the content and paints all visual
|
|
69
|
+
* chrome — one element instead of a div-wrapping-a-slot. It carries the
|
|
70
|
+
* proximity (distance-from-anchor) opacity, kept separate from the
|
|
71
|
+
* container's enter/exit/cross-fade opacity. */
|
|
72
|
+
bubble;
|
|
61
73
|
anchor = null;
|
|
62
74
|
listening = false;
|
|
63
75
|
shown = false;
|
|
@@ -81,38 +93,48 @@ class ATooltipElement extends HTMLElementBase {
|
|
|
81
93
|
|
|
82
94
|
.container {
|
|
83
95
|
--_dur: ${FADE_MS}ms;
|
|
84
|
-
|
|
85
96
|
position: fixed;
|
|
86
97
|
left: 0;
|
|
87
98
|
top: 0;
|
|
88
99
|
margin: 0;
|
|
100
|
+
padding: 0;
|
|
101
|
+
border: 0;
|
|
102
|
+
background: transparent;
|
|
103
|
+
color: inherit;
|
|
104
|
+
overflow: visible;
|
|
105
|
+
box-sizing: border-box;
|
|
106
|
+
width: fit-content;
|
|
107
|
+
pointer-events: none;
|
|
108
|
+
opacity: 0;
|
|
109
|
+
transition:
|
|
110
|
+
opacity var(--_dur) ease,
|
|
111
|
+
overlay var(--_dur) ease allow-discrete,
|
|
112
|
+
display var(--_dur) ease allow-discrete;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.container:popover-open { opacity: 1; }
|
|
116
|
+
|
|
117
|
+
@starting-style {
|
|
118
|
+
.container:popover-open { opacity: 0; }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.bubble {
|
|
122
|
+
display: block;
|
|
89
123
|
box-sizing: border-box;
|
|
90
124
|
width: fit-content;
|
|
91
125
|
max-width: var(--tooltip-max-width, min(calc(100vw - 20px), 80ch));
|
|
92
126
|
max-height: calc(100vh - 20px);
|
|
93
127
|
overflow: clip;
|
|
94
|
-
pointer-events: none;
|
|
95
128
|
text-align: left;
|
|
96
|
-
|
|
97
129
|
background: var(--tooltip-bg, Canvas);
|
|
98
130
|
color: var(--text-3, CanvasText);
|
|
99
131
|
box-shadow: var(--tooltip-shadow, 0 1px 8px rgba(0, 0, 0, 0.2));
|
|
100
132
|
-webkit-backdrop-filter: var(--tooltip-backdrop-filter, blur(8px));
|
|
101
133
|
backdrop-filter: var(--tooltip-backdrop-filter, blur(8px));
|
|
102
|
-
padding:
|
|
134
|
+
padding: var(--tooltip-padding, 4px 8px);
|
|
103
135
|
border: var(--tooltip-border, none);
|
|
104
136
|
border-radius: var(--tooltip-radius, 3px);
|
|
105
137
|
outline: none;
|
|
106
|
-
|
|
107
|
-
/* The bubble establishes its own text baseline so inheritable text
|
|
108
|
-
properties from the anchor (e.g. a Button's condensed "wdth" 88
|
|
109
|
-
axis, its 0.05ch letter-spacing, an uppercase transform, a custom
|
|
110
|
-
font) don't bleed into the slotted content. The content is slotted
|
|
111
|
-
light DOM, so it inherits these *from this container* \u2014 making this
|
|
112
|
-
the single choke point. Values mirror Anta's body text. Consumers
|
|
113
|
-
still customise a single tooltip by styling their own content
|
|
114
|
-
element directly (a class on the content overrides what it inherits
|
|
115
|
-
\u2014 see the Tooltip docs). */
|
|
116
138
|
font-family: var(--sans-serif, system-ui, sans-serif);
|
|
117
139
|
font-size: 14px;
|
|
118
140
|
font-weight: 400;
|
|
@@ -126,34 +148,20 @@ class ATooltipElement extends HTMLElementBase {
|
|
|
126
148
|
white-space: break-spaces;
|
|
127
149
|
word-break: break-word;
|
|
128
150
|
overflow-wrap: break-word;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
through its fade-out after hidePopover(), and @starting-style
|
|
132
|
-
gives every open a from-opacity:0 \u2014 so the first paint at a
|
|
133
|
-
not-yet-computed transform is invisible (no flash at a stale
|
|
134
|
-
spot), and re-appearing is always clean. */
|
|
135
|
-
opacity: 0;
|
|
136
|
-
transition:
|
|
137
|
-
opacity var(--_dur) ease,
|
|
138
|
-
overlay var(--_dur) ease allow-discrete,
|
|
139
|
-
display var(--_dur) ease allow-discrete;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
.container:popover-open { opacity: 1; }
|
|
143
|
-
|
|
144
|
-
@starting-style {
|
|
145
|
-
.container:popover-open { opacity: 0; }
|
|
151
|
+
opacity: 1;
|
|
152
|
+
transition: opacity ${PROX_FADE_MS}ms linear;
|
|
146
153
|
}
|
|
147
154
|
|
|
148
|
-
/* Interactive tooltips accept pointer events so their content (links,
|
|
149
|
-
buttons) is clickable; the default click-through bubble does not. */
|
|
150
155
|
:host([interactive]) .container { pointer-events: auto; }
|
|
151
156
|
`;
|
|
152
157
|
this.container = document.createElement("div");
|
|
153
158
|
this.container.className = "container";
|
|
154
159
|
this.container.setAttribute("popover", "manual");
|
|
155
160
|
this.container.setAttribute("role", "tooltip");
|
|
156
|
-
this.
|
|
161
|
+
this.bubble = document.createElement("slot");
|
|
162
|
+
this.bubble.className = "bubble";
|
|
163
|
+
this.bubble.setAttribute("part", "bubble");
|
|
164
|
+
this.container.append(this.bubble);
|
|
157
165
|
this.container.addEventListener("mouseenter", () => {
|
|
158
166
|
if (this.isInteractive) this.cancelHide();
|
|
159
167
|
});
|
|
@@ -170,17 +178,7 @@ class ATooltipElement extends HTMLElementBase {
|
|
|
170
178
|
lazyObserver?.observe(parent);
|
|
171
179
|
}
|
|
172
180
|
disconnectedCallback() {
|
|
173
|
-
this.
|
|
174
|
-
if (this.fadeTimer !== void 0) {
|
|
175
|
-
clearTimeout(this.fadeTimer);
|
|
176
|
-
this.fadeTimer = void 0;
|
|
177
|
-
}
|
|
178
|
-
this.fading = false;
|
|
179
|
-
if (this.docMoveBound) {
|
|
180
|
-
this.doc.removeEventListener("mousemove", this.onDocMove);
|
|
181
|
-
this.docMoveBound = false;
|
|
182
|
-
}
|
|
183
|
-
this.teardownListeners();
|
|
181
|
+
this.cleanup();
|
|
184
182
|
if (this.anchor) {
|
|
185
183
|
if (anchorToTooltip.get(this.anchor) === this) {
|
|
186
184
|
anchorToTooltip.delete(this.anchor);
|
|
@@ -200,30 +198,16 @@ class ATooltipElement extends HTMLElementBase {
|
|
|
200
198
|
this.debouncedShow?.cancel();
|
|
201
199
|
this.debouncedShow = debounce((e) => this.show(e), this.delay);
|
|
202
200
|
}
|
|
203
|
-
/** The element's OWN window / document. The class may be defined in a
|
|
204
|
-
* different realm than the element lives in (e.g. the docs playground
|
|
205
|
-
* renders into an iframe but reuses the parent page's element class), so
|
|
206
|
-
* the module-global `window`/`document` can point at the wrong frame.
|
|
207
|
-
* Everything viewport- or document-scoped (clamping, scroll/key/move
|
|
208
|
-
* listeners) must go through these so it's correct in any frame. */
|
|
209
|
-
get view() {
|
|
210
|
-
return this.ownerDocument?.defaultView ?? window;
|
|
211
|
-
}
|
|
212
|
-
get doc() {
|
|
213
|
-
return this.ownerDocument ?? document;
|
|
214
|
-
}
|
|
215
201
|
get isInteractive() {
|
|
216
202
|
return this.hasAttribute("interactive");
|
|
217
203
|
}
|
|
218
|
-
/** Pinned under the anchor (no cursor-following)
|
|
219
|
-
*
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
/** Pinned to the anchor for any reason: the `static`/`interactive` attrs, or
|
|
224
|
-
* a touch long-press (a finger can't track a following bubble). */
|
|
204
|
+
/** Pinned under the anchor (no cursor-following) — the DEFAULT. Following the
|
|
205
|
+
* cursor is opt-in via the `follow` attribute. `interactive` (you can't move
|
|
206
|
+
* into a bubble that chases the cursor) and a touch long-press (a finger
|
|
207
|
+
* can't track one) force pinned regardless of `follow`. */
|
|
225
208
|
get isPinned() {
|
|
226
|
-
|
|
209
|
+
if (this.isInteractive || this.touchOpen) return true;
|
|
210
|
+
return !this.hasAttribute("follow");
|
|
227
211
|
}
|
|
228
212
|
get prefersTop() {
|
|
229
213
|
return this.getAttribute("placement") === "top";
|
|
@@ -235,55 +219,73 @@ class ATooltipElement extends HTMLElementBase {
|
|
|
235
219
|
return Number.isFinite(n) ? n : DEFAULT_DELAY;
|
|
236
220
|
}
|
|
237
221
|
// --- positioning (sets only the shadow container's own transform) ---
|
|
222
|
+
// Single pending position frame: a burst of mousemoves within one frame
|
|
223
|
+
// coalesces to ONE layout read + transform write, using the LATEST geometry
|
|
224
|
+
// (earlier samples never matter — only where the cursor ends the frame).
|
|
225
|
+
positionFrame;
|
|
226
|
+
pendingPosition;
|
|
227
|
+
schedulePosition(compute) {
|
|
228
|
+
this.pendingPosition = compute;
|
|
229
|
+
if (this.positionFrame != null) return;
|
|
230
|
+
this.positionFrame = this.view.requestAnimationFrame(() => {
|
|
231
|
+
this.positionFrame = void 0;
|
|
232
|
+
const run = this.pendingPosition;
|
|
233
|
+
this.pendingPosition = void 0;
|
|
234
|
+
run?.();
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/** Pick the vertical origin: prefer the `above` candidate when `preferAbove`
|
|
238
|
+
* (auto-flipping to `below` if it'd clip the top), else `below` (flipping to
|
|
239
|
+
* `above` if it'd clip the bottom). Shared by anchor- and cursor-anchored
|
|
240
|
+
* placement — only the candidate origins differ. */
|
|
241
|
+
flipVertical(above, below, boxHeight, vh, preferAbove) {
|
|
242
|
+
if (preferAbove) return above < MARGIN ? below : above;
|
|
243
|
+
return below + boxHeight > vh ? above : below;
|
|
244
|
+
}
|
|
245
|
+
/** Clamp a candidate (left, top) into the viewport and write it as the
|
|
246
|
+
* container's transform — the single place the transform is set. */
|
|
247
|
+
place(left, top, boxWidth, vw) {
|
|
248
|
+
if (left + boxWidth > vw) left = vw - boxWidth - MARGIN;
|
|
249
|
+
left = Math.max(MARGIN, left);
|
|
250
|
+
top = Math.max(MARGIN, top);
|
|
251
|
+
this.container.style.transform = `translate(${Math.round(left)}px, ${Math.round(top)}px)`;
|
|
252
|
+
}
|
|
238
253
|
positionToTarget = () => {
|
|
239
|
-
|
|
254
|
+
this.schedulePosition(() => {
|
|
240
255
|
if (!this.anchor || !this.shown) return;
|
|
241
256
|
const a = this.anchor.getBoundingClientRect();
|
|
242
257
|
const box = this.container.getBoundingClientRect();
|
|
243
|
-
const
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
let left = a.left;
|
|
247
|
-
if (left + box.width > vw) left = vw - box.width - MARGIN;
|
|
248
|
-
left = Math.max(MARGIN, left);
|
|
249
|
-
let top;
|
|
250
|
-
if (this.prefersTop || this.touchOpen) {
|
|
251
|
-
top = a.top - box.height - MARGIN;
|
|
252
|
-
if (top < MARGIN) top = a.bottom + MARGIN;
|
|
253
|
-
} else {
|
|
254
|
-
top = a.bottom + MARGIN;
|
|
255
|
-
if (top + box.height > vh) top = a.top - box.height - MARGIN;
|
|
256
|
-
}
|
|
257
|
-
top = Math.max(MARGIN, top);
|
|
258
|
-
this.container.style.transform = `translate(${Math.round(left)}px, ${Math.round(top)}px)`;
|
|
258
|
+
const { innerWidth: vw, innerHeight: vh } = this.view;
|
|
259
|
+
const top = this.flipVertical(a.top - box.height - MARGIN, a.bottom + MARGIN, box.height, vh, this.prefersTop || this.touchOpen);
|
|
260
|
+
this.place(a.left, top, box.width, vw);
|
|
259
261
|
});
|
|
260
262
|
};
|
|
261
263
|
positionToMouse = (e) => {
|
|
262
|
-
|
|
264
|
+
this.schedulePosition(() => {
|
|
263
265
|
if (!this.shown && !this.fading) return;
|
|
264
266
|
const box = this.container.getBoundingClientRect();
|
|
265
|
-
const
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
let left = e.clientX - PADDING_X;
|
|
269
|
-
if (left + box.width > vw) left = vw - box.width - MARGIN;
|
|
270
|
-
left = Math.max(MARGIN, left);
|
|
271
|
-
let top;
|
|
272
|
-
if (this.prefersTop) {
|
|
273
|
-
top = e.clientY - box.height - MARGIN * 2;
|
|
274
|
-
if (top < MARGIN) top = e.clientY + CURSOR_SIZE;
|
|
275
|
-
} else {
|
|
276
|
-
top = e.clientY + CURSOR_SIZE;
|
|
277
|
-
if (top + box.height > vh) top = e.clientY - box.height - MARGIN * 2;
|
|
278
|
-
}
|
|
279
|
-
top = Math.max(MARGIN, top);
|
|
280
|
-
this.container.style.transform = `translate(${Math.round(left)}px, ${Math.round(top)}px)`;
|
|
267
|
+
const { innerWidth: vw, innerHeight: vh } = this.view;
|
|
268
|
+
const top = this.flipVertical(e.clientY - box.height - MARGIN * 2, e.clientY + CURSOR_SIZE, box.height, vh, this.prefersTop);
|
|
269
|
+
this.place(e.clientX - PADDING_X, top, box.width, vw);
|
|
281
270
|
});
|
|
282
271
|
};
|
|
283
272
|
position(e) {
|
|
284
273
|
if (!this.isPinned && e) this.positionToMouse(e);
|
|
285
274
|
else this.positionToTarget();
|
|
286
275
|
}
|
|
276
|
+
/** Opacity for the inner bubble from the cursor's distance to the anchor rect
|
|
277
|
+
* (0 inside the rect): 1 within PROX_NEAR, a linear ramp to 0 at PROX_FAR.
|
|
278
|
+
* Cursor-follow only — drives the "fade as you move away" behaviour. */
|
|
279
|
+
proximityOpacity(e) {
|
|
280
|
+
if (!this.anchor) return 1;
|
|
281
|
+
const r = this.anchor.getBoundingClientRect();
|
|
282
|
+
const dx = Math.max(r.left - e.clientX, 0, e.clientX - r.right);
|
|
283
|
+
const dy = Math.max(r.top - e.clientY, 0, e.clientY - r.bottom);
|
|
284
|
+
const dist = Math.hypot(dx, dy);
|
|
285
|
+
if (dist <= PROX_NEAR) return 1;
|
|
286
|
+
if (dist >= PROX_FAR) return 0;
|
|
287
|
+
return 1 - (dist - PROX_NEAR) / (PROX_FAR - PROX_NEAR);
|
|
288
|
+
}
|
|
287
289
|
// --- show / hide ---
|
|
288
290
|
show = (e) => {
|
|
289
291
|
if (!this.anchor) return;
|
|
@@ -295,6 +297,7 @@ class ATooltipElement extends HTMLElementBase {
|
|
|
295
297
|
if (!this.shown) {
|
|
296
298
|
this.shown = true;
|
|
297
299
|
this.fading = false;
|
|
300
|
+
this.bubble.style.opacity = "1";
|
|
298
301
|
if (this.fadeTimer !== void 0) {
|
|
299
302
|
clearTimeout(this.fadeTimer);
|
|
300
303
|
this.fadeTimer = void 0;
|
|
@@ -315,28 +318,65 @@ class ATooltipElement extends HTMLElementBase {
|
|
|
315
318
|
this.shown = false;
|
|
316
319
|
this.touchOpen = false;
|
|
317
320
|
this.container.hidePopover();
|
|
318
|
-
this.view.removeEventListener("scroll", this.hide, { capture: true });
|
|
319
|
-
this.doc.removeEventListener("keydown", this.onKeyDown, true);
|
|
320
321
|
this.fading = true;
|
|
321
322
|
if (this.fadeTimer !== void 0) clearTimeout(this.fadeTimer);
|
|
322
|
-
this.fadeTimer = setTimeout(() =>
|
|
323
|
-
this.fading = false;
|
|
324
|
-
this.fadeTimer = void 0;
|
|
325
|
-
if (this.docMoveBound) {
|
|
326
|
-
this.doc.removeEventListener("mousemove", this.onDocMove);
|
|
327
|
-
this.docMoveBound = false;
|
|
328
|
-
}
|
|
329
|
-
}, FADE_MS);
|
|
323
|
+
this.fadeTimer = setTimeout(() => this.detachGlobals(), FADE_MS);
|
|
330
324
|
releaseOpen(this);
|
|
331
325
|
};
|
|
326
|
+
/** Remove every document / window listener this element binds and stop the
|
|
327
|
+
* fade timer. Used by hide() (after the exit fade) and by cleanup()
|
|
328
|
+
* (immediately). Idempotent. */
|
|
329
|
+
detachGlobals() {
|
|
330
|
+
if (this.fadeTimer !== void 0) {
|
|
331
|
+
clearTimeout(this.fadeTimer);
|
|
332
|
+
this.fadeTimer = void 0;
|
|
333
|
+
}
|
|
334
|
+
this.fading = false;
|
|
335
|
+
this.view.removeEventListener("scroll", this.hide, { capture: true });
|
|
336
|
+
this.doc.removeEventListener("keydown", this.onKeyDown, true);
|
|
337
|
+
if (this.docMoveBound) {
|
|
338
|
+
this.doc.removeEventListener("mousemove", this.onDocMove);
|
|
339
|
+
this.docMoveBound = false;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/** Full, immediate teardown: close the popover now (no fade), detach every
|
|
343
|
+
* listener, clear all timers, release the coordinator slot, reset state.
|
|
344
|
+
* Idempotent. Called from disconnectedCallback, and from the orphan safety
|
|
345
|
+
* net (claimOpen / onDocMove) when the element is gone but
|
|
346
|
+
* disconnectedCallback didn't fire. Not `private` — the module coordinator
|
|
347
|
+
* calls it. */
|
|
348
|
+
cleanup() {
|
|
349
|
+
this.cancelHide();
|
|
350
|
+
if (this.shown) {
|
|
351
|
+
this.shown = false;
|
|
352
|
+
this.touchOpen = false;
|
|
353
|
+
if (this.container.matches(":popover-open")) this.container.hidePopover();
|
|
354
|
+
}
|
|
355
|
+
this.detachGlobals();
|
|
356
|
+
this.teardownListeners();
|
|
357
|
+
releaseOpen(this);
|
|
358
|
+
this.bubble.style.opacity = "";
|
|
359
|
+
}
|
|
332
360
|
/** While shown, track the cursor even after it has left the anchor (during
|
|
333
|
-
* the close delay).
|
|
334
|
-
* stay
|
|
361
|
+
* the close delay). Only runs for `follow` tooltips — pinned bubbles
|
|
362
|
+
* (the default, `interactive`, touch) stay put. */
|
|
335
363
|
onDocMove = (e) => {
|
|
336
|
-
if (
|
|
337
|
-
this.
|
|
338
|
-
|
|
364
|
+
if (!this.isConnected) {
|
|
365
|
+
this.cleanup();
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (!(this.shown || this.fading) || this.isPinned) return;
|
|
369
|
+
this.lastMouse = e;
|
|
370
|
+
const o = this.proximityOpacity(e);
|
|
371
|
+
if (o === 0) {
|
|
372
|
+
this.bubble.style.transition = "none";
|
|
373
|
+
this.bubble.style.opacity = "0";
|
|
374
|
+
if (this.shown) this.hide();
|
|
375
|
+
return;
|
|
339
376
|
}
|
|
377
|
+
this.positionToMouse(e);
|
|
378
|
+
this.bubble.style.transition = "";
|
|
379
|
+
this.bubble.style.opacity = String(o);
|
|
340
380
|
};
|
|
341
381
|
/** Hide after CLOSE_DELAY unless something cancels it first (re-enter, or
|
|
342
382
|
* another tooltip claiming the slot). Lets the bubble bridge the gap to a
|
package/dist/elements/index.d.ts
CHANGED
|
@@ -17,6 +17,15 @@ export { AProgressElement, register_a_progress } from './a-progress';
|
|
|
17
17
|
export { ATextElement, register_a_text } from './a-text';
|
|
18
18
|
export { AIconElement, register_a_icon } from './a-icon';
|
|
19
19
|
export { AButtonElement, register_a_button } from './a-button';
|
|
20
|
+
export { ACheckboxElement, register_a_checkbox } from './a-checkbox';
|
|
21
|
+
export { AExpanderElement, register_a_expander } from './a-expander';
|
|
20
22
|
export { ATooltipElement, register_a_tooltip } from './a-tooltip';
|
|
23
|
+
export { AInputElement, register_a_input } from './a-input';
|
|
24
|
+
export { ARadioElement, register_a_radio } from './a-radio';
|
|
25
|
+
export { ARadioGroupElement, register_a_radio_group } from './a-radio-group';
|
|
26
|
+
export { AMenuElement, register_a_menu } from './a-menu';
|
|
27
|
+
export { AMenuItemElement, register_a_menu_item } from './a-menu-item';
|
|
28
|
+
export { AMenuSeparatorElement, register_a_menu_separator } from './a-menu-separator';
|
|
29
|
+
export { AMenuGroupElement, register_a_menu_group } from './a-menu-group';
|
|
21
30
|
import './a-title.css';
|
|
22
31
|
import './a-tag.css';
|
package/dist/elements/index.js
CHANGED
|
@@ -2,18 +2,45 @@ import { AProgressElement, register_a_progress } from "./a-progress";
|
|
|
2
2
|
import { ATextElement, register_a_text } from "./a-text";
|
|
3
3
|
import { AIconElement, register_a_icon } from "./a-icon";
|
|
4
4
|
import { AButtonElement, register_a_button } from "./a-button";
|
|
5
|
+
import { ACheckboxElement, register_a_checkbox } from "./a-checkbox";
|
|
6
|
+
import { AExpanderElement, register_a_expander } from "./a-expander";
|
|
5
7
|
import { ATooltipElement, register_a_tooltip } from "./a-tooltip";
|
|
8
|
+
import { AInputElement, register_a_input } from "./a-input";
|
|
9
|
+
import { ARadioElement, register_a_radio } from "./a-radio";
|
|
10
|
+
import { ARadioGroupElement, register_a_radio_group } from "./a-radio-group";
|
|
11
|
+
import { AMenuElement, register_a_menu } from "./a-menu";
|
|
12
|
+
import { AMenuItemElement, register_a_menu_item } from "./a-menu-item";
|
|
13
|
+
import { AMenuSeparatorElement, register_a_menu_separator } from "./a-menu-separator";
|
|
14
|
+
import { AMenuGroupElement, register_a_menu_group } from "./a-menu-group";
|
|
6
15
|
import "./a-title.css";
|
|
7
16
|
import "./a-tag.css";
|
|
8
17
|
export {
|
|
9
18
|
AButtonElement,
|
|
19
|
+
ACheckboxElement,
|
|
20
|
+
AExpanderElement,
|
|
10
21
|
AIconElement,
|
|
22
|
+
AInputElement,
|
|
23
|
+
AMenuElement,
|
|
24
|
+
AMenuGroupElement,
|
|
25
|
+
AMenuItemElement,
|
|
26
|
+
AMenuSeparatorElement,
|
|
11
27
|
AProgressElement,
|
|
28
|
+
ARadioElement,
|
|
29
|
+
ARadioGroupElement,
|
|
12
30
|
ATextElement,
|
|
13
31
|
ATooltipElement,
|
|
14
32
|
register_a_button,
|
|
33
|
+
register_a_checkbox,
|
|
34
|
+
register_a_expander,
|
|
15
35
|
register_a_icon,
|
|
36
|
+
register_a_input,
|
|
37
|
+
register_a_menu,
|
|
38
|
+
register_a_menu_group,
|
|
39
|
+
register_a_menu_item,
|
|
40
|
+
register_a_menu_separator,
|
|
16
41
|
register_a_progress,
|
|
42
|
+
register_a_radio,
|
|
43
|
+
register_a_radio_group,
|
|
17
44
|
register_a_text,
|
|
18
45
|
register_a_tooltip
|
|
19
46
|
};
|