@tensor_1/react 2.41.0 → 2.42.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/dist/components/Button.d.ts +19 -1
- package/dist/components/Button.js +13 -8
- package/dist/components/DescriptionList.d.ts +11 -0
- package/dist/components/DiscardPrompt.d.ts +54 -0
- package/dist/components/DiscardPrompt.js +245 -0
- package/dist/components/Drawer.d.ts +19 -3
- package/dist/components/Drawer.js +60 -21
- package/dist/components/EditorialHome.d.ts +1 -1
- package/dist/components/Field.d.ts +7 -1
- package/dist/components/Field.js +5 -4
- package/dist/components/Input.d.ts +5 -0
- package/dist/components/Input.js +2 -1
- package/dist/components/Kbd.d.ts +20 -1
- package/dist/components/Kbd.js +30 -7
- package/dist/components/Menu.d.ts +11 -4
- package/dist/components/Menu.js +17 -3
- package/dist/components/MetricInput.d.ts +8 -1
- package/dist/components/MetricInput.js +11 -3
- package/dist/components/Modal.d.ts +15 -1
- package/dist/components/Modal.js +58 -13
- package/dist/components/SearchBar.d.ts +67 -0
- package/dist/components/SearchBar.js +117 -0
- package/dist/components/Select.js +5 -4
- package/dist/components/ShortcutHint.d.ts +29 -0
- package/dist/components/ShortcutHint.js +149 -0
- package/dist/components/Tooltip.d.ts +10 -1
- package/dist/components/Tooltip.js +15 -4
- package/dist/emoji.cjs +108 -31
- package/dist/hooks/useAnchoredPosition.d.ts +13 -1
- package/dist/hooks/useAnchoredPosition.js +19 -11
- package/dist/hooks/useDialog.d.ts +6 -1
- package/dist/hooks/useDialog.js +10 -1
- package/dist/index.cjs +3184 -2521
- package/dist/index.d.cts +221 -39
- package/dist/index.d.ts +3 -1
- package/dist/index.js +6 -1
- package/dist/labels-context.d.ts +8 -0
- package/dist/rich-text.cjs +17 -10
- package/package.json +1 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
|
+
import { useAnchoredPosition } from "../hooks/useAnchoredPosition.js";
|
|
6
|
+
import { KbdCombo, ariaKeyShortcuts, useKbdPlatform } from "./Kbd.js";
|
|
7
|
+
const useIsomorphicLayoutEffect = typeof document !== "undefined" ? useLayoutEffect : useEffect;
|
|
8
|
+
const isShown = (el) => {
|
|
9
|
+
try {
|
|
10
|
+
return !!el?.matches(":popover-open");
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const focusVisible = (el) => {
|
|
16
|
+
try {
|
|
17
|
+
return el.matches(":focus-visible");
|
|
18
|
+
} catch {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const stop = (event) => event.stopPropagation();
|
|
23
|
+
function ShortcutHint({ keys, label, ownsAria }) {
|
|
24
|
+
const markerRef = useRef(null);
|
|
25
|
+
const bubbleRef = useRef(null);
|
|
26
|
+
const anchorRef = useRef(null);
|
|
27
|
+
const [host, setHost] = useState(null);
|
|
28
|
+
const [open, setOpen] = useState(false);
|
|
29
|
+
const platform = useKbdPlatform();
|
|
30
|
+
const shortcuts = ariaKeyShortcuts(keys, platform);
|
|
31
|
+
useAnchoredPosition(anchorRef, bubbleRef, { open, placement: "top", offset: 6 });
|
|
32
|
+
useIsomorphicLayoutEffect(() => {
|
|
33
|
+
const button = markerRef.current?.parentElement;
|
|
34
|
+
if (!button || !ownsAria) return void 0;
|
|
35
|
+
const before = button.getAttribute("aria-keyshortcuts");
|
|
36
|
+
button.setAttribute("aria-keyshortcuts", shortcuts);
|
|
37
|
+
return () => {
|
|
38
|
+
if (button.getAttribute("aria-keyshortcuts") !== shortcuts) return;
|
|
39
|
+
if (before == null) button.removeAttribute("aria-keyshortcuts");
|
|
40
|
+
else button.setAttribute("aria-keyshortcuts", before);
|
|
41
|
+
};
|
|
42
|
+
}, [shortcuts, ownsAria]);
|
|
43
|
+
useIsomorphicLayoutEffect(() => {
|
|
44
|
+
const button = markerRef.current?.parentElement ?? null;
|
|
45
|
+
anchorRef.current = button;
|
|
46
|
+
if (!button || button.parentElement?.closest(".uix-tooltip")) {
|
|
47
|
+
setHost(null);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
setHost(button.closest("dialog") ?? button.ownerDocument.body);
|
|
51
|
+
}, []);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
const button = anchorRef.current;
|
|
54
|
+
const bubble = bubbleRef.current;
|
|
55
|
+
if (!host || !button || !bubble) return void 0;
|
|
56
|
+
let timer;
|
|
57
|
+
const show = () => {
|
|
58
|
+
window.clearTimeout(timer);
|
|
59
|
+
if (!isShown(bubble)) {
|
|
60
|
+
try {
|
|
61
|
+
bubble.showPopover();
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
setOpen(true);
|
|
66
|
+
};
|
|
67
|
+
const hide = () => {
|
|
68
|
+
window.clearTimeout(timer);
|
|
69
|
+
if (isShown(bubble)) {
|
|
70
|
+
try {
|
|
71
|
+
bubble.hidePopover();
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
setOpen(false);
|
|
76
|
+
};
|
|
77
|
+
const hideSoon = () => {
|
|
78
|
+
window.clearTimeout(timer);
|
|
79
|
+
timer = window.setTimeout(() => {
|
|
80
|
+
if (button.ownerDocument.activeElement === button && focusVisible(button)) return;
|
|
81
|
+
hide();
|
|
82
|
+
}, 100);
|
|
83
|
+
};
|
|
84
|
+
const onFocus = () => {
|
|
85
|
+
if (focusVisible(button)) show();
|
|
86
|
+
};
|
|
87
|
+
const onKey = (event) => {
|
|
88
|
+
if (event.key === "Escape") hide();
|
|
89
|
+
};
|
|
90
|
+
const watch = new MutationObserver(() => {
|
|
91
|
+
if (button.hasAttribute("disabled") || button.getAttribute("aria-disabled") === "true") hide();
|
|
92
|
+
});
|
|
93
|
+
watch.observe(button, { attributes: true, attributeFilter: ["disabled", "aria-disabled"] });
|
|
94
|
+
button.addEventListener("mouseenter", show);
|
|
95
|
+
button.addEventListener("mouseleave", hideSoon);
|
|
96
|
+
button.addEventListener("focus", onFocus);
|
|
97
|
+
button.addEventListener("blur", hide);
|
|
98
|
+
bubble.addEventListener("mouseenter", show);
|
|
99
|
+
bubble.addEventListener("mouseleave", hideSoon);
|
|
100
|
+
button.ownerDocument.addEventListener("keydown", onKey);
|
|
101
|
+
return () => {
|
|
102
|
+
window.clearTimeout(timer);
|
|
103
|
+
watch.disconnect();
|
|
104
|
+
button.removeEventListener("mouseenter", show);
|
|
105
|
+
button.removeEventListener("mouseleave", hideSoon);
|
|
106
|
+
button.removeEventListener("focus", onFocus);
|
|
107
|
+
button.removeEventListener("blur", hide);
|
|
108
|
+
bubble.removeEventListener("mouseenter", show);
|
|
109
|
+
bubble.removeEventListener("mouseleave", hideSoon);
|
|
110
|
+
button.ownerDocument.removeEventListener("keydown", onKey);
|
|
111
|
+
if (isShown(bubble)) {
|
|
112
|
+
try {
|
|
113
|
+
bubble.hidePopover();
|
|
114
|
+
} catch {
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}, [host]);
|
|
119
|
+
const bubbleAttr = { popover: "manual" };
|
|
120
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
121
|
+
/* @__PURE__ */ jsx("span", { ref: markerRef, hidden: true, "data-uix-shortcut-hint": "" }),
|
|
122
|
+
host && createPortal(
|
|
123
|
+
/* @__PURE__ */ jsxs(
|
|
124
|
+
"span",
|
|
125
|
+
{
|
|
126
|
+
ref: bubbleRef,
|
|
127
|
+
className: "uix-tooltip__bubble uix-tooltip__bubble--hint",
|
|
128
|
+
"aria-hidden": "true",
|
|
129
|
+
...bubbleAttr,
|
|
130
|
+
onClick: stop,
|
|
131
|
+
onDoubleClick: stop,
|
|
132
|
+
onContextMenu: stop,
|
|
133
|
+
onPointerDown: stop,
|
|
134
|
+
onPointerUp: stop,
|
|
135
|
+
onMouseDown: stop,
|
|
136
|
+
onMouseUp: stop,
|
|
137
|
+
children: [
|
|
138
|
+
label && /* @__PURE__ */ jsx("span", { children: label }),
|
|
139
|
+
/* @__PURE__ */ jsx("span", { className: "uix-tooltip__shortcut", children: /* @__PURE__ */ jsx(KbdCombo, { keys: [...keys], platform }) })
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
),
|
|
143
|
+
host
|
|
144
|
+
)
|
|
145
|
+
] });
|
|
146
|
+
}
|
|
147
|
+
export {
|
|
148
|
+
ShortcutHint
|
|
149
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { HTMLAttributes, ReactNode } from 'react';
|
|
3
3
|
import { Placement } from '../overlay-position.js';
|
|
4
|
+
import { KeyShortcut } from './Kbd.js';
|
|
4
5
|
|
|
5
6
|
interface TooltipProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'content'> {
|
|
6
7
|
/** Tooltip text. Pass this or `content`. */
|
|
@@ -13,6 +14,14 @@ interface TooltipProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'content'>
|
|
|
13
14
|
content?: ReactNode;
|
|
14
15
|
/** Preferred placement. Default `'top'`. */
|
|
15
16
|
placement?: Placement;
|
|
17
|
+
/**
|
|
18
|
+
* The trigger's keyboard shortcut, in `KbdCombo` key names, e.g. `['Mod', 'K']` (HAR-2031):
|
|
19
|
+
* shown with `Kbd` after the text, and set as the trigger's `aria-keyshortcuts` for the
|
|
20
|
+
* viewer's platform. Default: the `shortcut` of a `Button` child, so a Tooltip around a Button
|
|
21
|
+
* with a shortcut shows one bubble with the label and the keys. The kit only shows it: the
|
|
22
|
+
* product binds the key.
|
|
23
|
+
*/
|
|
24
|
+
shortcut?: KeyShortcut;
|
|
16
25
|
children?: ReactNode;
|
|
17
26
|
}
|
|
18
27
|
/**
|
|
@@ -23,6 +32,6 @@ interface TooltipProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'content'>
|
|
|
23
32
|
* (document-level, per WCAG 1.4.13), and hoverable: the bubble stays open while
|
|
24
33
|
* the pointer is over it (UIX-A11Y-1).
|
|
25
34
|
*/
|
|
26
|
-
declare function Tooltip({ label, content, placement, className, children, ...props }: TooltipProps): react.JSX.Element;
|
|
35
|
+
declare function Tooltip({ label, content, placement, shortcut, className, children, ...props }: TooltipProps): react.JSX.Element;
|
|
27
36
|
|
|
28
37
|
export { Tooltip, type TooltipProps };
|
|
@@ -3,12 +3,18 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { cloneElement, isValidElement, useEffect, useId, useRef, useState } from "react";
|
|
4
4
|
import { cx } from "../cx.js";
|
|
5
5
|
import { useAnchoredPosition } from "../hooks/useAnchoredPosition.js";
|
|
6
|
-
|
|
6
|
+
import { KbdCombo, ariaKeyShortcuts, isKeyShortcut, useKbdPlatform } from "./Kbd.js";
|
|
7
|
+
function Tooltip({ label, content, placement = "top", shortcut, className, children, ...props }) {
|
|
7
8
|
const triggerRef = useRef(null);
|
|
8
9
|
const bubbleRef = useRef(null);
|
|
9
10
|
const closeTimer = useRef(null);
|
|
10
11
|
const [open, setOpen] = useState(false);
|
|
11
12
|
const id = useId();
|
|
13
|
+
const platform = useKbdPlatform();
|
|
14
|
+
const childShortcut = isValidElement(children) ? children.props.shortcut : void 0;
|
|
15
|
+
const own = shortcut ?? (isKeyShortcut(childShortcut) ? childShortcut : void 0);
|
|
16
|
+
const keys = own && own.length > 0 ? own : null;
|
|
17
|
+
const keyshortcuts = keys ? ariaKeyShortcuts(keys, platform) : void 0;
|
|
12
18
|
useAnchoredPosition(triggerRef, bubbleRef, { open, placement, offset: 6 });
|
|
13
19
|
const cancelClose = () => {
|
|
14
20
|
if (closeTimer.current != null) {
|
|
@@ -63,7 +69,8 @@ function Tooltip({ label, content, placement = "top", className, children, ...pr
|
|
|
63
69
|
const bubbleAttr = { popover: "manual" };
|
|
64
70
|
const single = isValidElement(children);
|
|
65
71
|
const child = single ? cloneElement(children, {
|
|
66
|
-
"aria-describedby": [children.props["aria-describedby"], id].filter(Boolean).join(" ")
|
|
72
|
+
"aria-describedby": [children.props["aria-describedby"], id].filter(Boolean).join(" "),
|
|
73
|
+
...keyshortcuts ? { "aria-keyshortcuts": keyshortcuts } : {}
|
|
67
74
|
}) : children;
|
|
68
75
|
return /* @__PURE__ */ jsxs(
|
|
69
76
|
"span",
|
|
@@ -71,6 +78,7 @@ function Tooltip({ label, content, placement = "top", className, children, ...pr
|
|
|
71
78
|
ref: triggerRef,
|
|
72
79
|
className: cx("uix-tooltip", className),
|
|
73
80
|
"aria-describedby": single ? void 0 : id,
|
|
81
|
+
"aria-keyshortcuts": single ? void 0 : keyshortcuts,
|
|
74
82
|
tabIndex: single ? void 0 : 0,
|
|
75
83
|
onMouseEnter: show,
|
|
76
84
|
onMouseLeave: scheduleHide,
|
|
@@ -82,7 +90,7 @@ function Tooltip({ label, content, placement = "top", className, children, ...pr
|
|
|
82
90
|
...props,
|
|
83
91
|
children: [
|
|
84
92
|
child,
|
|
85
|
-
/* @__PURE__ */
|
|
93
|
+
/* @__PURE__ */ jsxs(
|
|
86
94
|
"span",
|
|
87
95
|
{
|
|
88
96
|
ref: bubbleRef,
|
|
@@ -92,7 +100,10 @@ function Tooltip({ label, content, placement = "top", className, children, ...pr
|
|
|
92
100
|
onMouseEnter: cancelClose,
|
|
93
101
|
onMouseLeave: scheduleHide,
|
|
94
102
|
...bubbleAttr,
|
|
95
|
-
children:
|
|
103
|
+
children: [
|
|
104
|
+
content ?? label,
|
|
105
|
+
keys && /* @__PURE__ */ jsx("span", { className: "uix-tooltip__shortcut", children: /* @__PURE__ */ jsx(KbdCombo, { keys: [...keys], platform }) })
|
|
106
|
+
]
|
|
96
107
|
}
|
|
97
108
|
)
|
|
98
109
|
]
|
package/dist/emoji.cjs
CHANGED
|
@@ -173,11 +173,16 @@ function translationOf(el) {
|
|
|
173
173
|
if (!t || t === "none" || typeof DOMMatrixReadOnly === "undefined") return { x: 0, y: 0 };
|
|
174
174
|
try {
|
|
175
175
|
const m = new DOMMatrixReadOnly(t);
|
|
176
|
-
|
|
176
|
+
const z = zoomOf(el);
|
|
177
|
+
return { x: m.m41 * z, y: m.m42 * z };
|
|
177
178
|
} catch {
|
|
178
179
|
return { x: 0, y: 0 };
|
|
179
180
|
}
|
|
180
181
|
}
|
|
182
|
+
function zoomOf(el) {
|
|
183
|
+
const z = el.currentCSSZoom;
|
|
184
|
+
return typeof z === "number" && Number.isFinite(z) && z > 0 ? z : 1;
|
|
185
|
+
}
|
|
181
186
|
var signed = (n) => `${n < 0 ? "-" : "+"} ${Math.round(Math.abs(n) * 100) / 100}px`;
|
|
182
187
|
var prepared = /* @__PURE__ */ new WeakSet();
|
|
183
188
|
function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-start", offset = 6, padding = 8, shiftMainAxis = true, capHeight = false, onAnchorHidden }) {
|
|
@@ -202,8 +207,9 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
|
|
|
202
207
|
const s = floating.style;
|
|
203
208
|
if (keepPlace && current.anchor.isConnected) {
|
|
204
209
|
const a = current.anchor.getBoundingClientRect();
|
|
205
|
-
|
|
206
|
-
s.
|
|
210
|
+
const z = zoomOf(floating);
|
|
211
|
+
s.top = `${Math.round(((current.yEdge === "bottom" ? a.bottom : a.top) + current.dy) / z)}px`;
|
|
212
|
+
s.left = `${Math.round(((current.xEdge === "right" ? a.right : a.left) + current.dx) / z)}px`;
|
|
207
213
|
}
|
|
208
214
|
s.removeProperty("position-anchor");
|
|
209
215
|
}
|
|
@@ -231,6 +237,7 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
|
|
|
231
237
|
written.current = "";
|
|
232
238
|
}
|
|
233
239
|
const { placement: p, offset: o, padding: pad, shiftMainAxis: keepInView, capHeight: cap } = opts.current;
|
|
240
|
+
const zoom = floating.getClientRects().length > 0 ? zoomOf(floating) : zoomOf(anchorEl);
|
|
234
241
|
if (cap) {
|
|
235
242
|
if (!heightCap.current) {
|
|
236
243
|
const own = getComputedStyle(floating);
|
|
@@ -244,7 +251,7 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
|
|
|
244
251
|
};
|
|
245
252
|
if (setOverflow) s.overflowY = "auto";
|
|
246
253
|
}
|
|
247
|
-
const room = `${Math.max(0, window.innerHeight - 2 * pad)}px`;
|
|
254
|
+
const room = `${Math.max(0, (window.innerHeight - 2 * pad * zoom) / zoom)}px`;
|
|
248
255
|
const max = heightCap.current.ownMaxHeight ? `min(${room}, ${heightCap.current.ownMaxHeight})` : room;
|
|
249
256
|
if (max !== heightCap.current.written) {
|
|
250
257
|
s.maxHeight = max;
|
|
@@ -254,19 +261,19 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
|
|
|
254
261
|
uncap();
|
|
255
262
|
}
|
|
256
263
|
const a = anchorEl.getBoundingClientRect();
|
|
257
|
-
const size = { width: floating.offsetWidth, height: floating.offsetHeight };
|
|
264
|
+
const size = { width: floating.offsetWidth * zoom, height: floating.offsetHeight * zoom };
|
|
258
265
|
const pos = computePosition(
|
|
259
266
|
{ x: a.x, y: a.y, width: a.width, height: a.height },
|
|
260
267
|
size,
|
|
261
268
|
{ width: window.innerWidth, height: window.innerHeight },
|
|
262
|
-
{ placement: p, offset: o, padding: pad, shiftMainAxis: keepInView, stickySide: side.current ?? void 0 }
|
|
269
|
+
{ placement: p, offset: o * zoom, padding: pad * zoom, shiftMainAxis: keepInView, stickySide: side.current ?? void 0 }
|
|
263
270
|
);
|
|
264
271
|
const measured = size.width > 0 || size.height > 0;
|
|
265
272
|
if (measured && session.current) side.current = pos.side;
|
|
266
273
|
if (floating.dataset.placement !== pos.placement) floating.dataset.placement = pos.placement;
|
|
267
274
|
const writeFixed = (at) => {
|
|
268
|
-
const left2 = `${Math.round(at.x)}px`;
|
|
269
|
-
const top2 = `${Math.round(at.y)}px`;
|
|
275
|
+
const left2 = `${Math.round(at.x / zoom)}px`;
|
|
276
|
+
const top2 = `${Math.round(at.y / zoom)}px`;
|
|
270
277
|
const key2 = `fixed ${left2} ${top2}`;
|
|
271
278
|
if (key2 === written.current) return;
|
|
272
279
|
s.left = left2;
|
|
@@ -293,8 +300,8 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
|
|
|
293
300
|
current.xEdge = pos.side === "right" ? "right" : "left";
|
|
294
301
|
current.dy = pos.y - (current.yEdge === "bottom" ? a.bottom : a.top);
|
|
295
302
|
current.dx = pos.x - (current.xEdge === "right" ? a.right : a.left);
|
|
296
|
-
const top = `round(anchor(${current.yEdge}) ${signed(current.dy)}, 1px)`;
|
|
297
|
-
const left = `round(anchor(${current.xEdge}) ${signed(current.dx)}, 1px)`;
|
|
303
|
+
const top = `round(anchor(${current.yEdge}) ${signed(current.dy / zoom)}, 1px)`;
|
|
304
|
+
const left = `round(anchor(${current.xEdge}) ${signed(current.dx / zoom)}, 1px)`;
|
|
298
305
|
const key = `anchored ${top} ${left}`;
|
|
299
306
|
if (key !== written.current) {
|
|
300
307
|
s.top = top;
|
|
@@ -1079,14 +1086,79 @@ function EditorIcon({ name, ...props }) {
|
|
|
1079
1086
|
}
|
|
1080
1087
|
|
|
1081
1088
|
// src/components/Tooltip.tsx
|
|
1089
|
+
var import_react6 = require("react");
|
|
1090
|
+
|
|
1091
|
+
// src/components/Kbd.tsx
|
|
1082
1092
|
var import_react5 = require("react");
|
|
1083
1093
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1094
|
+
var KEYS = {
|
|
1095
|
+
Mod: { mac: ["\u2318", "Command"], other: ["Ctrl", "Control"] },
|
|
1096
|
+
Ctrl: { mac: ["\u2303", "Control"], other: ["Ctrl", "Control"] },
|
|
1097
|
+
Shift: { mac: ["\u21E7", "Shift"], other: ["Shift", "Shift"] },
|
|
1098
|
+
Alt: { mac: ["\u2325", "Option"], other: ["Alt", "Alt"] },
|
|
1099
|
+
Enter: { mac: ["\u21B5", "Enter"], other: ["Enter", "Enter"] },
|
|
1100
|
+
Escape: { mac: ["Esc", "Escape"], other: ["Esc", "Escape"] },
|
|
1101
|
+
Esc: { mac: ["Esc", "Escape"], other: ["Esc", "Escape"] },
|
|
1102
|
+
Delete: { mac: ["Del", "Delete"], other: ["Del", "Delete"] },
|
|
1103
|
+
Del: { mac: ["Del", "Delete"], other: ["Del", "Delete"] },
|
|
1104
|
+
Space: { mac: ["Space", "Space"], other: ["Space", "Space"] },
|
|
1105
|
+
" ": { mac: ["Space", "Space"], other: ["Space", "Space"] },
|
|
1106
|
+
Backspace: { mac: ["\u232B", "Backspace"], other: ["Backspace", "Backspace"] },
|
|
1107
|
+
Tab: { mac: ["\u21E5", "Tab"], other: ["Tab", "Tab"] },
|
|
1108
|
+
ArrowUp: { mac: ["\u2191", "Up arrow"], other: ["\u2191", "Up arrow"] },
|
|
1109
|
+
ArrowDown: { mac: ["\u2193", "Down arrow"], other: ["\u2193", "Down arrow"] },
|
|
1110
|
+
ArrowLeft: { mac: ["\u2190", "Left arrow"], other: ["\u2190", "Left arrow"] },
|
|
1111
|
+
ArrowRight: { mac: ["\u2192", "Right arrow"], other: ["\u2192", "Right arrow"] }
|
|
1112
|
+
};
|
|
1113
|
+
var detectPlatform = () => {
|
|
1114
|
+
if (typeof navigator === "undefined") return "other";
|
|
1115
|
+
const nav = navigator;
|
|
1116
|
+
return /mac|iphone|ipad|ipod/i.test(nav.userAgentData?.platform ?? navigator.platform ?? "") ? "mac" : "other";
|
|
1117
|
+
};
|
|
1118
|
+
var noSubscription = () => () => {
|
|
1119
|
+
};
|
|
1120
|
+
var serverPlatform = () => "other";
|
|
1121
|
+
function useKbdPlatform(platform) {
|
|
1122
|
+
const detected = (0, import_react5.useSyncExternalStore)(noSubscription, detectPlatform, serverPlatform);
|
|
1123
|
+
return platform ?? detected;
|
|
1124
|
+
}
|
|
1125
|
+
var ARIA_KEYS = {
|
|
1126
|
+
Mod: { mac: "Meta", other: "Control" },
|
|
1127
|
+
Ctrl: { mac: "Control", other: "Control" },
|
|
1128
|
+
Esc: { mac: "Escape", other: "Escape" },
|
|
1129
|
+
Del: { mac: "Delete", other: "Delete" },
|
|
1130
|
+
" ": { mac: "Space", other: "Space" },
|
|
1131
|
+
"+": { mac: "Plus", other: "Plus" }
|
|
1132
|
+
};
|
|
1133
|
+
function ariaKeyShortcuts(keys, platform) {
|
|
1134
|
+
return keys.map((key) => ARIA_KEYS[key]?.[platform] ?? (key.length === 1 ? key.toUpperCase() : key)).join("+");
|
|
1135
|
+
}
|
|
1136
|
+
var isKeyShortcut = (value) => Array.isArray(value) && value.length > 0 && value.every((key) => typeof key === "string");
|
|
1137
|
+
function KbdCombo({ keys, platform, keyLabels, className, ...props }) {
|
|
1138
|
+
const active = useKbdPlatform(platform);
|
|
1139
|
+
const parts = keys.map((key) => {
|
|
1140
|
+
const known = KEYS[key]?.[active];
|
|
1141
|
+
return { glyph: known?.[0] ?? key, spoken: keyLabels?.[key] ?? known?.[1] ?? key };
|
|
1142
|
+
});
|
|
1143
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: cx("uix-kbd-combo", className), ...props, children: [
|
|
1144
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "uix-visually-hidden", children: parts.map((p) => p.spoken).join("+") }),
|
|
1145
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "uix-kbd-combo__keys", "aria-hidden": "true", children: parts.map((p, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("kbd", { className: "uix-kbd", children: p.glyph }, `${keys[i]}-${i}`)) })
|
|
1146
|
+
] });
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// src/components/Tooltip.tsx
|
|
1150
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1151
|
+
function Tooltip({ label, content, placement = "top", shortcut, className, children, ...props }) {
|
|
1152
|
+
const triggerRef = (0, import_react6.useRef)(null);
|
|
1153
|
+
const bubbleRef = (0, import_react6.useRef)(null);
|
|
1154
|
+
const closeTimer = (0, import_react6.useRef)(null);
|
|
1155
|
+
const [open, setOpen] = (0, import_react6.useState)(false);
|
|
1156
|
+
const id = (0, import_react6.useId)();
|
|
1157
|
+
const platform = useKbdPlatform();
|
|
1158
|
+
const childShortcut = (0, import_react6.isValidElement)(children) ? children.props.shortcut : void 0;
|
|
1159
|
+
const own = shortcut ?? (isKeyShortcut(childShortcut) ? childShortcut : void 0);
|
|
1160
|
+
const keys = own && own.length > 0 ? own : null;
|
|
1161
|
+
const keyshortcuts = keys ? ariaKeyShortcuts(keys, platform) : void 0;
|
|
1090
1162
|
useAnchoredPosition(triggerRef, bubbleRef, { open, placement, offset: 6 });
|
|
1091
1163
|
const cancelClose = () => {
|
|
1092
1164
|
if (closeTimer.current != null) {
|
|
@@ -1120,7 +1192,7 @@ function Tooltip({ label, content, placement = "top", className, children, ...pr
|
|
|
1120
1192
|
cancelClose();
|
|
1121
1193
|
closeTimer.current = window.setTimeout(hide, 100);
|
|
1122
1194
|
};
|
|
1123
|
-
(0,
|
|
1195
|
+
(0, import_react6.useEffect)(() => {
|
|
1124
1196
|
if (!open) return;
|
|
1125
1197
|
const onKey = (e) => {
|
|
1126
1198
|
if (e.key === "Escape") hide();
|
|
@@ -1128,7 +1200,7 @@ function Tooltip({ label, content, placement = "top", className, children, ...pr
|
|
|
1128
1200
|
document.addEventListener("keydown", onKey);
|
|
1129
1201
|
return () => document.removeEventListener("keydown", onKey);
|
|
1130
1202
|
}, [open]);
|
|
1131
|
-
(0,
|
|
1203
|
+
(0, import_react6.useEffect)(() => () => {
|
|
1132
1204
|
if (closeTimer.current != null) clearTimeout(closeTimer.current);
|
|
1133
1205
|
const b = bubbleRef.current;
|
|
1134
1206
|
if (b && b.matches(":popover-open")) {
|
|
@@ -1139,16 +1211,18 @@ function Tooltip({ label, content, placement = "top", className, children, ...pr
|
|
|
1139
1211
|
}
|
|
1140
1212
|
}, []);
|
|
1141
1213
|
const bubbleAttr = { popover: "manual" };
|
|
1142
|
-
const single = (0,
|
|
1143
|
-
const child = single ? (0,
|
|
1144
|
-
"aria-describedby": [children.props["aria-describedby"], id].filter(Boolean).join(" ")
|
|
1214
|
+
const single = (0, import_react6.isValidElement)(children);
|
|
1215
|
+
const child = single ? (0, import_react6.cloneElement)(children, {
|
|
1216
|
+
"aria-describedby": [children.props["aria-describedby"], id].filter(Boolean).join(" "),
|
|
1217
|
+
...keyshortcuts ? { "aria-keyshortcuts": keyshortcuts } : {}
|
|
1145
1218
|
}) : children;
|
|
1146
|
-
return /* @__PURE__ */ (0,
|
|
1219
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1147
1220
|
"span",
|
|
1148
1221
|
{
|
|
1149
1222
|
ref: triggerRef,
|
|
1150
1223
|
className: cx("uix-tooltip", className),
|
|
1151
1224
|
"aria-describedby": single ? void 0 : id,
|
|
1225
|
+
"aria-keyshortcuts": single ? void 0 : keyshortcuts,
|
|
1152
1226
|
tabIndex: single ? void 0 : 0,
|
|
1153
1227
|
onMouseEnter: show,
|
|
1154
1228
|
onMouseLeave: scheduleHide,
|
|
@@ -1160,7 +1234,7 @@ function Tooltip({ label, content, placement = "top", className, children, ...pr
|
|
|
1160
1234
|
...props,
|
|
1161
1235
|
children: [
|
|
1162
1236
|
child,
|
|
1163
|
-
/* @__PURE__ */ (0,
|
|
1237
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1164
1238
|
"span",
|
|
1165
1239
|
{
|
|
1166
1240
|
ref: bubbleRef,
|
|
@@ -1170,7 +1244,10 @@ function Tooltip({ label, content, placement = "top", className, children, ...pr
|
|
|
1170
1244
|
onMouseEnter: cancelClose,
|
|
1171
1245
|
onMouseLeave: scheduleHide,
|
|
1172
1246
|
...bubbleAttr,
|
|
1173
|
-
children:
|
|
1247
|
+
children: [
|
|
1248
|
+
content ?? label,
|
|
1249
|
+
keys && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "uix-tooltip__shortcut", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KbdCombo, { keys: [...keys], platform }) })
|
|
1250
|
+
]
|
|
1174
1251
|
}
|
|
1175
1252
|
)
|
|
1176
1253
|
]
|
|
@@ -1179,7 +1256,7 @@ function Tooltip({ label, content, placement = "top", className, children, ...pr
|
|
|
1179
1256
|
}
|
|
1180
1257
|
|
|
1181
1258
|
// src/components/ReactionBar.tsx
|
|
1182
|
-
var
|
|
1259
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1183
1260
|
var DEFAULT_REACTION_BAR_LABELS = {
|
|
1184
1261
|
group: "Reactions",
|
|
1185
1262
|
addReaction: "Add reaction",
|
|
@@ -1212,10 +1289,10 @@ function ReactionBar({
|
|
|
1212
1289
|
const names = hidden > 0 ? `${shown} ${formatLabel(labels.others, { count: hidden })}` : shown;
|
|
1213
1290
|
return formatLabel(labels.reactedWith, { names, emoji: reaction.emoji, count: reaction.count });
|
|
1214
1291
|
};
|
|
1215
|
-
return /* @__PURE__ */ (0,
|
|
1292
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: cx("uix-reactions", className), role: "group", "aria-label": labels.group, children: [
|
|
1216
1293
|
reactions.filter((r) => r.count > 0).map((reaction) => {
|
|
1217
1294
|
const text = describe(reaction);
|
|
1218
|
-
return /* @__PURE__ */ (0,
|
|
1295
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Tooltip, { label: text, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1219
1296
|
"button",
|
|
1220
1297
|
{
|
|
1221
1298
|
type: "button",
|
|
@@ -1226,13 +1303,13 @@ function ReactionBar({
|
|
|
1226
1303
|
disabled,
|
|
1227
1304
|
onClick: () => onToggle(reaction.emoji),
|
|
1228
1305
|
children: [
|
|
1229
|
-
/* @__PURE__ */ (0,
|
|
1230
|
-
/* @__PURE__ */ (0,
|
|
1306
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "aria-hidden": "true", children: renderEmoji(reaction.emoji) }),
|
|
1307
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "uix-reaction__count", "aria-hidden": "true", children: reaction.count })
|
|
1231
1308
|
]
|
|
1232
1309
|
}
|
|
1233
1310
|
) }, reaction.emoji);
|
|
1234
1311
|
}),
|
|
1235
|
-
/* @__PURE__ */ (0,
|
|
1312
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1236
1313
|
EmojiPicker,
|
|
1237
1314
|
{
|
|
1238
1315
|
quickPicks,
|
|
@@ -1243,7 +1320,7 @@ function ReactionBar({
|
|
|
1243
1320
|
onSelect: (emoji) => {
|
|
1244
1321
|
if (!reactions.some((r) => r.emoji === emoji && r.reactedByMe)) onToggle(emoji);
|
|
1245
1322
|
},
|
|
1246
|
-
trigger: /* @__PURE__ */ (0,
|
|
1323
|
+
trigger: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "uix-reaction-add", "aria-label": labels.addReaction, title: labels.addReaction, disabled, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(EditorIcon, { name: "addReaction" }) })
|
|
1247
1324
|
}
|
|
1248
1325
|
)
|
|
1249
1326
|
] });
|
|
@@ -30,6 +30,18 @@ interface UseAnchoredPositionOptions {
|
|
|
30
30
|
type AnchorArg = RefObject<HTMLElement | null> | HTMLElement | null | undefined;
|
|
31
31
|
/** @internal The element behind an anchor ref or element. */
|
|
32
32
|
declare const resolveAnchor: (a: AnchorArg) => HTMLElement | null;
|
|
33
|
+
/**
|
|
34
|
+
* The element's effective CSS `zoom` (HAR-1798): `currentCSSZoom` where the engine has it, else 1.
|
|
35
|
+
*
|
|
36
|
+
* Under `html { zoom: 2 }` (how a page emulates 200 % reflow) `getBoundingClientRect()` and the
|
|
37
|
+
* viewport size are in zoomed, on-screen pixels, while the `top` / `left` / `max-height` written
|
|
38
|
+
* on the element, its offsetWidth and its transform are in its own unzoomed CSS pixels, which the
|
|
39
|
+
* browser multiplies by the zoom. The placement is computed on screen and divided back on write;
|
|
40
|
+
* without that, an overlay opened low on the page landed at twice its offset, off-screen.
|
|
41
|
+
* Browser zoom (Ctrl +) changes the CSS pixel itself and leaves this at 1.
|
|
42
|
+
* @internal Also used by components that size an overlay from its anchor (Select).
|
|
43
|
+
*/
|
|
44
|
+
declare function zoomOf(el: HTMLElement): number;
|
|
33
45
|
/**
|
|
34
46
|
* Position a floating element (a native-popover overlay) against an anchor with
|
|
35
47
|
* cross-browser flip/shift — the DOM half of the overlay-position engine (UIX-FIX-02).
|
|
@@ -50,4 +62,4 @@ declare const resolveAnchor: (a: AnchorArg) => HTMLElement | null;
|
|
|
50
62
|
*/
|
|
51
63
|
declare function useAnchoredPosition(anchor: RefObject<HTMLElement | null> | HTMLElement | null | undefined, floatingRef: RefObject<HTMLElement | null>, { open, placement, offset, padding, shiftMainAxis, capHeight, onAnchorHidden }: UseAnchoredPositionOptions): () => void;
|
|
52
64
|
|
|
53
|
-
export { type UseAnchoredPositionOptions, resolveAnchor, useAnchoredPosition };
|
|
65
|
+
export { type UseAnchoredPositionOptions, resolveAnchor, useAnchoredPosition, zoomOf };
|
|
@@ -33,11 +33,16 @@ function translationOf(el) {
|
|
|
33
33
|
if (!t || t === "none" || typeof DOMMatrixReadOnly === "undefined") return { x: 0, y: 0 };
|
|
34
34
|
try {
|
|
35
35
|
const m = new DOMMatrixReadOnly(t);
|
|
36
|
-
|
|
36
|
+
const z = zoomOf(el);
|
|
37
|
+
return { x: m.m41 * z, y: m.m42 * z };
|
|
37
38
|
} catch {
|
|
38
39
|
return { x: 0, y: 0 };
|
|
39
40
|
}
|
|
40
41
|
}
|
|
42
|
+
function zoomOf(el) {
|
|
43
|
+
const z = el.currentCSSZoom;
|
|
44
|
+
return typeof z === "number" && Number.isFinite(z) && z > 0 ? z : 1;
|
|
45
|
+
}
|
|
41
46
|
const signed = (n) => `${n < 0 ? "-" : "+"} ${Math.round(Math.abs(n) * 100) / 100}px`;
|
|
42
47
|
const prepared = /* @__PURE__ */ new WeakSet();
|
|
43
48
|
function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-start", offset = 6, padding = 8, shiftMainAxis = true, capHeight = false, onAnchorHidden }) {
|
|
@@ -62,8 +67,9 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
|
|
|
62
67
|
const s = floating.style;
|
|
63
68
|
if (keepPlace && current.anchor.isConnected) {
|
|
64
69
|
const a = current.anchor.getBoundingClientRect();
|
|
65
|
-
|
|
66
|
-
s.
|
|
70
|
+
const z = zoomOf(floating);
|
|
71
|
+
s.top = `${Math.round(((current.yEdge === "bottom" ? a.bottom : a.top) + current.dy) / z)}px`;
|
|
72
|
+
s.left = `${Math.round(((current.xEdge === "right" ? a.right : a.left) + current.dx) / z)}px`;
|
|
67
73
|
}
|
|
68
74
|
s.removeProperty("position-anchor");
|
|
69
75
|
}
|
|
@@ -91,6 +97,7 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
|
|
|
91
97
|
written.current = "";
|
|
92
98
|
}
|
|
93
99
|
const { placement: p, offset: o, padding: pad, shiftMainAxis: keepInView, capHeight: cap } = opts.current;
|
|
100
|
+
const zoom = floating.getClientRects().length > 0 ? zoomOf(floating) : zoomOf(anchorEl);
|
|
94
101
|
if (cap) {
|
|
95
102
|
if (!heightCap.current) {
|
|
96
103
|
const own = getComputedStyle(floating);
|
|
@@ -104,7 +111,7 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
|
|
|
104
111
|
};
|
|
105
112
|
if (setOverflow) s.overflowY = "auto";
|
|
106
113
|
}
|
|
107
|
-
const room = `${Math.max(0, window.innerHeight - 2 * pad)}px`;
|
|
114
|
+
const room = `${Math.max(0, (window.innerHeight - 2 * pad * zoom) / zoom)}px`;
|
|
108
115
|
const max = heightCap.current.ownMaxHeight ? `min(${room}, ${heightCap.current.ownMaxHeight})` : room;
|
|
109
116
|
if (max !== heightCap.current.written) {
|
|
110
117
|
s.maxHeight = max;
|
|
@@ -114,19 +121,19 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
|
|
|
114
121
|
uncap();
|
|
115
122
|
}
|
|
116
123
|
const a = anchorEl.getBoundingClientRect();
|
|
117
|
-
const size = { width: floating.offsetWidth, height: floating.offsetHeight };
|
|
124
|
+
const size = { width: floating.offsetWidth * zoom, height: floating.offsetHeight * zoom };
|
|
118
125
|
const pos = computePosition(
|
|
119
126
|
{ x: a.x, y: a.y, width: a.width, height: a.height },
|
|
120
127
|
size,
|
|
121
128
|
{ width: window.innerWidth, height: window.innerHeight },
|
|
122
|
-
{ placement: p, offset: o, padding: pad, shiftMainAxis: keepInView, stickySide: side.current ?? void 0 }
|
|
129
|
+
{ placement: p, offset: o * zoom, padding: pad * zoom, shiftMainAxis: keepInView, stickySide: side.current ?? void 0 }
|
|
123
130
|
);
|
|
124
131
|
const measured = size.width > 0 || size.height > 0;
|
|
125
132
|
if (measured && session.current) side.current = pos.side;
|
|
126
133
|
if (floating.dataset.placement !== pos.placement) floating.dataset.placement = pos.placement;
|
|
127
134
|
const writeFixed = (at) => {
|
|
128
|
-
const left2 = `${Math.round(at.x)}px`;
|
|
129
|
-
const top2 = `${Math.round(at.y)}px`;
|
|
135
|
+
const left2 = `${Math.round(at.x / zoom)}px`;
|
|
136
|
+
const top2 = `${Math.round(at.y / zoom)}px`;
|
|
130
137
|
const key2 = `fixed ${left2} ${top2}`;
|
|
131
138
|
if (key2 === written.current) return;
|
|
132
139
|
s.left = left2;
|
|
@@ -153,8 +160,8 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
|
|
|
153
160
|
current.xEdge = pos.side === "right" ? "right" : "left";
|
|
154
161
|
current.dy = pos.y - (current.yEdge === "bottom" ? a.bottom : a.top);
|
|
155
162
|
current.dx = pos.x - (current.xEdge === "right" ? a.right : a.left);
|
|
156
|
-
const top = `round(anchor(${current.yEdge}) ${signed(current.dy)}, 1px)`;
|
|
157
|
-
const left = `round(anchor(${current.xEdge}) ${signed(current.dx)}, 1px)`;
|
|
163
|
+
const top = `round(anchor(${current.yEdge}) ${signed(current.dy / zoom)}, 1px)`;
|
|
164
|
+
const left = `round(anchor(${current.xEdge}) ${signed(current.dx / zoom)}, 1px)`;
|
|
158
165
|
const key = `anchored ${top} ${left}`;
|
|
159
166
|
if (key !== written.current) {
|
|
160
167
|
s.top = top;
|
|
@@ -221,5 +228,6 @@ function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-st
|
|
|
221
228
|
}
|
|
222
229
|
export {
|
|
223
230
|
resolveAnchor,
|
|
224
|
-
useAnchoredPosition
|
|
231
|
+
useAnchoredPosition,
|
|
232
|
+
zoomOf
|
|
225
233
|
};
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* `keepOpen` is asked when the browser closed the dialog on its own (Escape or a close request
|
|
3
|
+
* nobody cancelled, a `method="dialog"` form). Returning `true` reopens it instead of calling
|
|
4
|
+
* `onClose`: a form dialog with unsaved input or a save in flight (HAR-2029).
|
|
5
|
+
*/
|
|
6
|
+
declare function useDialog(open: boolean, onClose?: () => void, keepOpen?: () => boolean): React.RefObject<HTMLDialogElement>;
|
|
2
7
|
|
|
3
8
|
export { useDialog };
|