@tensor_1/react 2.41.0 → 2.43.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.
Files changed (42) hide show
  1. package/README.md +24 -0
  2. package/dist/components/Button.d.ts +19 -1
  3. package/dist/components/Button.js +13 -8
  4. package/dist/components/DescriptionList.d.ts +11 -0
  5. package/dist/components/DiscardPrompt.d.ts +54 -0
  6. package/dist/components/DiscardPrompt.js +245 -0
  7. package/dist/components/Drawer.d.ts +19 -3
  8. package/dist/components/Drawer.js +60 -21
  9. package/dist/components/EditorialHome.d.ts +1 -1
  10. package/dist/components/Field.d.ts +7 -1
  11. package/dist/components/Field.js +5 -4
  12. package/dist/components/Input.d.ts +5 -0
  13. package/dist/components/Input.js +2 -1
  14. package/dist/components/Kbd.d.ts +20 -1
  15. package/dist/components/Kbd.js +30 -7
  16. package/dist/components/Menu.d.ts +11 -4
  17. package/dist/components/Menu.js +17 -3
  18. package/dist/components/MetricInput.d.ts +8 -1
  19. package/dist/components/MetricInput.js +11 -3
  20. package/dist/components/Modal.d.ts +15 -1
  21. package/dist/components/Modal.js +58 -13
  22. package/dist/components/OrgChart.d.ts +121 -0
  23. package/dist/components/OrgChart.js +683 -0
  24. package/dist/components/SearchBar.d.ts +67 -0
  25. package/dist/components/SearchBar.js +117 -0
  26. package/dist/components/Select.js +5 -4
  27. package/dist/components/ShortcutHint.d.ts +29 -0
  28. package/dist/components/ShortcutHint.js +149 -0
  29. package/dist/components/Tooltip.d.ts +10 -1
  30. package/dist/components/Tooltip.js +15 -4
  31. package/dist/emoji.cjs +108 -31
  32. package/dist/hooks/useAnchoredPosition.d.ts +13 -1
  33. package/dist/hooks/useAnchoredPosition.js +19 -11
  34. package/dist/hooks/useDialog.d.ts +6 -1
  35. package/dist/hooks/useDialog.js +10 -1
  36. package/dist/index.cjs +3911 -2567
  37. package/dist/index.d.cts +340 -39
  38. package/dist/index.d.ts +4 -1
  39. package/dist/index.js +11 -1
  40. package/dist/labels-context.d.ts +11 -0
  41. package/dist/rich-text.cjs +17 -10
  42. package/package.json +1 -1
@@ -0,0 +1,67 @@
1
+ import * as react from 'react';
2
+ import { HTMLAttributes, Ref } from 'react';
3
+ import { ButtonVariant } from './Button.js';
4
+ import '../link.js';
5
+ import './Kbd.js';
6
+
7
+ /** The words of a `SearchBar` (HAR-2030); the field's own name is its `label`. */
8
+ interface SearchBarLabels {
9
+ /** Accessible name of the clear button. */
10
+ clear: string;
11
+ /** Text of the submit button (`submitButton`). */
12
+ submit: string;
13
+ }
14
+ interface SearchBarProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onSubmit' | 'onChange' | 'defaultValue'> {
15
+ /**
16
+ * Accessible name of the field and of its search landmark, e.g. "Search services". It is not
17
+ * shown (the placeholder is not a label): the field shows `placeholder`.
18
+ */
19
+ label: string;
20
+ /** The text in the field (controlled). Leave it out, with `defaultValue`, for an uncontrolled field. */
21
+ value?: string;
22
+ defaultValue?: string;
23
+ /** Called on every edit, and with `''` when the field is cleared. */
24
+ onValueChange?: (value: string) => void;
25
+ /** Enter in the field, or the submit button: search for this text. */
26
+ onSubmit?: (value: string) => void;
27
+ /** After the clear button or Escape emptied the field. Focus stays in the field. */
28
+ onClear?: () => void;
29
+ placeholder?: string;
30
+ /** `md` (default) for a header or a toolbar; `lg` for a page-level or public search: 44 px, the touch floor. */
31
+ size?: 'md' | 'lg';
32
+ /** A submit button after the field, with `labels.submit`. Enter submits either way. */
33
+ submitButton?: boolean;
34
+ /** The submit button's variant. Default `primary`. */
35
+ submitVariant?: ButtonVariant;
36
+ /**
37
+ * A `search` landmark named by `label` (default). Pass `false` for a field that filters a list
38
+ * or a table, where one more landmark per toolbar would crowd the landmark list. Never wrap it
39
+ * in a `<search>` or `role="search"` of your own: that nests two landmarks.
40
+ */
41
+ landmark?: boolean;
42
+ /** Ids of elements that describe the field (a hint, a result count), as its `aria-describedby`. */
43
+ inputDescribedBy?: string;
44
+ /** The words. Default: `UixLabelsProvider` `searchBar`, then English. */
45
+ labels?: Partial<SearchBarLabels>;
46
+ /** The field's `name`. */
47
+ name?: string;
48
+ /** The field's `id`, e.g. for a "/" shortcut that focuses it. Default: generated. */
49
+ inputId?: string;
50
+ inputRef?: Ref<HTMLInputElement>;
51
+ autoFocus?: boolean;
52
+ disabled?: boolean;
53
+ }
54
+ /**
55
+ * One search field look and behaviour (HAR-2030; TENSOR audit UXC-02) over the `.uix-searchbar`
56
+ * contract: a leading magnifier, a clear button while there is text, Enter to search, Escape to
57
+ * clear (an empty field lets Escape through, so it still closes the dialog it sits in). It is a
58
+ * `search` landmark named by `label`.
59
+ *
60
+ * It is not a `<form>`, so it can sit inside one: Enter calls `onSubmit` and never submits a
61
+ * surrounding form. For suggestions while typing, use `SearchSuggest`. Inside an overlay that
62
+ * takes Escape itself before the field (a ConfirmPopover, the table's view menu), Escape closes
63
+ * the overlay rather than clearing the text.
64
+ */
65
+ declare function SearchBar({ label, value: valueProp, defaultValue, onValueChange, onSubmit, onClear, placeholder, size, submitButton, submitVariant, landmark, inputDescribedBy, labels: labelsProp, name, inputId, inputRef, autoFocus, disabled, className, ...props }: SearchBarProps): react.JSX.Element;
66
+
67
+ export { SearchBar, type SearchBarLabels, type SearchBarProps };
@@ -0,0 +1,117 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useId, useRef, useState } from "react";
4
+ import { cx } from "../cx.js";
5
+ import { useUixLabels } from "../labels-context.js";
6
+ import { Button } from "./Button.js";
7
+ const DEFAULT_SEARCH_BAR_LABELS = { clear: "Clear search", submit: "Search" };
8
+ const SearchIcon = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", children: [
9
+ /* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "8" }),
10
+ /* @__PURE__ */ jsx("path", { d: "m21 21-4.3-4.3" })
11
+ ] });
12
+ const ClearIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" }) });
13
+ function SearchBar({
14
+ label,
15
+ value: valueProp,
16
+ defaultValue = "",
17
+ onValueChange,
18
+ onSubmit,
19
+ onClear,
20
+ placeholder,
21
+ size = "md",
22
+ submitButton = false,
23
+ submitVariant = "primary",
24
+ landmark = true,
25
+ inputDescribedBy,
26
+ labels: labelsProp,
27
+ name,
28
+ inputId,
29
+ inputRef,
30
+ autoFocus,
31
+ disabled,
32
+ className,
33
+ ...props
34
+ }) {
35
+ const uixLabels = useUixLabels();
36
+ const labels = { ...DEFAULT_SEARCH_BAR_LABELS, ...uixLabels.searchBar, ...labelsProp };
37
+ const id = useId();
38
+ const fieldId = inputId ?? `${id}-field`;
39
+ const labelId = `${id}-label`;
40
+ const field = useRef(null);
41
+ const [own, setOwn] = useState(defaultValue);
42
+ const value = valueProp ?? own;
43
+ const change = (next) => {
44
+ setOwn(next);
45
+ onValueChange?.(next);
46
+ };
47
+ const clear = () => {
48
+ change("");
49
+ field.current?.focus();
50
+ onClear?.();
51
+ };
52
+ const onKeyDown = (event) => {
53
+ if (event.nativeEvent.isComposing || event.keyCode === 229) return;
54
+ if (event.key === "Enter") {
55
+ event.preventDefault();
56
+ onSubmit?.(value);
57
+ } else if (event.key === "Escape" && value !== "") {
58
+ event.preventDefault();
59
+ event.stopPropagation();
60
+ clear();
61
+ }
62
+ };
63
+ const setField = useCallback((node) => {
64
+ field.current = node;
65
+ if (typeof inputRef === "function") inputRef(node);
66
+ else if (inputRef) inputRef.current = node;
67
+ }, [inputRef]);
68
+ return /* @__PURE__ */ jsxs(
69
+ "div",
70
+ {
71
+ role: landmark ? "search" : void 0,
72
+ "aria-labelledby": landmark ? labelId : void 0,
73
+ className: cx("uix-searchbar", size === "md" && "uix-searchbar--md", className),
74
+ ...props,
75
+ children: [
76
+ /* @__PURE__ */ jsxs("div", { className: "uix-searchbar__wrap", children: [
77
+ /* @__PURE__ */ jsx("label", { id: labelId, htmlFor: fieldId, className: "uix-visually-hidden", children: label }),
78
+ /* @__PURE__ */ jsx(SearchIcon, {}),
79
+ /* @__PURE__ */ jsx(
80
+ "input",
81
+ {
82
+ ref: setField,
83
+ id: fieldId,
84
+ type: "search",
85
+ name,
86
+ className: "uix-input",
87
+ value,
88
+ placeholder,
89
+ autoComplete: "off",
90
+ enterKeyHint: "search",
91
+ "aria-describedby": inputDescribedBy,
92
+ autoFocus,
93
+ disabled,
94
+ onChange: (event) => change(event.currentTarget.value),
95
+ onKeyDown
96
+ }
97
+ ),
98
+ value !== "" && !disabled && /* @__PURE__ */ jsx(
99
+ "button",
100
+ {
101
+ type: "button",
102
+ className: "uix-searchbar__clear",
103
+ "aria-label": labels.clear,
104
+ onMouseDown: (event) => event.preventDefault(),
105
+ onClick: clear,
106
+ children: /* @__PURE__ */ jsx(ClearIcon, {})
107
+ }
108
+ )
109
+ ] }),
110
+ submitButton && /* @__PURE__ */ jsx(Button, { type: "button", variant: submitVariant, size: size === "lg" ? "lg" : "md", disabled, onClick: () => onSubmit?.(value), children: labels.submit })
111
+ ]
112
+ }
113
+ );
114
+ }
115
+ export {
116
+ SearchBar
117
+ };
@@ -3,7 +3,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { Children, Fragment as Fragment2, forwardRef, isValidElement, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
4
4
  import { cx } from "../cx.js";
5
5
  import { useUixLabels } from "../labels-context.js";
6
- import { useAnchoredPosition } from "../hooks/useAnchoredPosition.js";
6
+ import { useAnchoredPosition, zoomOf } from "../hooks/useAnchoredPosition.js";
7
7
  import { Drawer } from "./Drawer.js";
8
8
  import {
9
9
  buildSelectModel,
@@ -537,9 +537,10 @@ const Select = forwardRef(function Select2(props, forwardedRef) {
537
537
  }
538
538
  };
539
539
  if (showing) {
540
+ const zoom = zoomOf(trigger);
540
541
  const r = trigger.getBoundingClientRect();
541
- const room = Math.max(r.top, window.innerHeight - r.bottom) - OFFSET - EDGE;
542
- popup.style.minWidth = `${Math.round(r.width)}px`;
542
+ const room = Math.max(r.top, window.innerHeight - r.bottom) / zoom - OFFSET - EDGE;
543
+ popup.style.minWidth = `${Math.round(r.width / zoom)}px`;
543
544
  popup.style.maxHeight = `${Math.max(96, Math.min(MAX_HEIGHT, Math.floor(room)))}px`;
544
545
  try {
545
546
  if (!isOpen()) popup.showPopover();
@@ -581,7 +582,7 @@ const Select = forwardRef(function Select2(props, forwardedRef) {
581
582
  doc.addEventListener("pointerdown", onDown, true);
582
583
  return () => doc.removeEventListener("pointerdown", onDown, true);
583
584
  }, [showing]);
584
- useEffect(() => {
585
+ useIsomorphicLayoutEffect(() => {
585
586
  if (!open) return;
586
587
  if (active >= entries.length || active < 0 && entries.length) setActive(entries.length ? selectedIndexIn(entries) >= 0 ? selectedIndexIn(entries) : firstEnabled(entries) : -1);
587
588
  }, [open, entries.length, query]);
@@ -0,0 +1,29 @@
1
+ import * as react from 'react';
2
+ import { KeyShortcut } from './Kbd.js';
3
+
4
+ interface ShortcutHintProps {
5
+ keys: KeyShortcut;
6
+ /** Text before the keys, e.g. an icon button's name. */
7
+ label?: string;
8
+ /** Set the button's `aria-keyshortcuts`. `false` when the button already has its own (a prop, or a Tooltip's). */
9
+ ownsAria: boolean;
10
+ }
11
+ /**
12
+ * The hover / keyboard-focus hint of a `Button` with `shortcut` (HAR-2031). In the button it
13
+ * leaves only an empty, hidden marker, so the button's text, name, layout and selectors are
14
+ * unchanged (no wrapper, no sibling). The bubble itself is portalled after mount into the
15
+ * nearest `<dialog>` (outside one a modal dialog makes everything inert) or `<body>`, as a
16
+ * top-layer popover, `aria-hidden`: assistive technology gets the key from the button's
17
+ * `aria-keyshortcuts`, which this sets for the viewer's platform (Mod is Meta on Apple, Control
18
+ * elsewhere) unless the button already has its own.
19
+ *
20
+ * It opens on hover and on keyboard focus (`:focus-visible`), so focus a menu or dialog sends back
21
+ * after a mouse choice does not open it. It closes on leave, blur, Escape and when the button is
22
+ * disabled, and stays while the pointer is on it (WCAG 1.4.13). Inside a `Tooltip` it steps aside:
23
+ * the Tooltip shows the keys in its own bubble.
24
+ *
25
+ * It is its own client component so that `Button`, which has no hooks, still renders on a server.
26
+ */
27
+ declare function ShortcutHint({ keys, label, ownsAria }: ShortcutHintProps): react.JSX.Element;
28
+
29
+ export { ShortcutHint, type ShortcutHintProps };
@@ -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
- function Tooltip({ label, content, placement = "top", className, children, ...props }) {
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__ */ jsx(
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: content ?? label
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
  ]