@tensor_1/react 2.24.0 → 2.26.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 +32 -1
- package/dist/components/Card.d.ts +8 -1
- package/dist/components/Card.js +8 -2
- package/dist/components/Drawer.d.ts +7 -1
- package/dist/components/Drawer.js +3 -2
- package/dist/components/EditorialHome.d.ts +9 -2
- package/dist/components/EditorialHome.js +8 -2
- package/dist/components/Field.d.ts +9 -1
- package/dist/components/Field.js +8 -2
- package/dist/components/InfoTip.d.ts +29 -0
- package/dist/components/InfoTip.js +185 -0
- package/dist/components/PageHeader.d.ts +9 -2
- package/dist/components/PageHeader.js +8 -2
- package/dist/components/Peek.js +2 -9
- package/dist/components/RichTextEditor.d.ts +9 -1
- package/dist/components/RichTextEditor.js +23 -10
- package/dist/components/SearchSuggest.d.ts +87 -0
- package/dist/components/SearchSuggest.js +299 -0
- package/dist/hooks/backdropDismiss.d.ts +7 -0
- package/dist/hooks/backdropDismiss.js +16 -0
- package/dist/index.cjs +1800 -1212
- package/dist/index.d.cts +170 -9
- package/dist/index.d.ts +3 -0
- package/dist/index.js +7 -0
- package/dist/info-tip-model.d.ts +10 -0
- package/dist/info-tip-model.js +8 -0
- package/dist/rich-text.cjs +23 -10
- package/dist/rich-text.d.cts +9 -1
- package/dist/search-suggest-model.d.ts +20 -0
- package/dist/search-suggest-model.js +74 -0
- package/package.json +1 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { HTMLAttributes, ReactNode, Ref } from 'react';
|
|
3
|
+
|
|
4
|
+
/** One result row. */
|
|
5
|
+
interface SearchSuggestOption {
|
|
6
|
+
/** Stable id, returned by `onSelect`. */
|
|
7
|
+
id: string;
|
|
8
|
+
/** The result's name. Matched letters are emphasised. */
|
|
9
|
+
title: string;
|
|
10
|
+
/** Where the result lives, outermost first (a breadcrumb). Middle crumbs truncate first. */
|
|
11
|
+
meta?: readonly string[];
|
|
12
|
+
/** One or two lines of context, clamped at two lines. Matched letters are emphasised. */
|
|
13
|
+
description?: string;
|
|
14
|
+
/** Optional leading glyph (decorative). */
|
|
15
|
+
icon?: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
/** A labelled action inside the list (the footer row, the heading action). */
|
|
18
|
+
interface SearchSuggestAction {
|
|
19
|
+
/** Visible text of the action. */
|
|
20
|
+
label: string;
|
|
21
|
+
/** Runs when the action is chosen. */
|
|
22
|
+
onSelect: () => void;
|
|
23
|
+
}
|
|
24
|
+
/** Props of {@link SearchSuggest}. */
|
|
25
|
+
interface SearchSuggestProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange' | 'onSelect' | 'title' | 'defaultValue'> {
|
|
26
|
+
/** The text in the field (controlled). */
|
|
27
|
+
value: string;
|
|
28
|
+
/** Called on every edit, and with an empty string when the field is cleared. */
|
|
29
|
+
onValueChange: (value: string) => void;
|
|
30
|
+
/** The suggestions to show, best first. The consumer filters; this component only renders. */
|
|
31
|
+
options: readonly SearchSuggestOption[];
|
|
32
|
+
/** Called when a row is chosen by click or Enter. The list closes afterwards. */
|
|
33
|
+
onSelect: (id: string, option: SearchSuggestOption) => void;
|
|
34
|
+
/** Accessible name of the field (there is no visible label; the placeholder is not a label). */
|
|
35
|
+
label: string;
|
|
36
|
+
placeholder?: string;
|
|
37
|
+
/** Text to emphasise in rows. Defaults to `value`. */
|
|
38
|
+
highlight?: string;
|
|
39
|
+
/** A title above the options, e.g. "Recently opened". */
|
|
40
|
+
heading?: string;
|
|
41
|
+
/** A small action next to the heading, e.g. "Clear". */
|
|
42
|
+
headingAction?: SearchSuggestAction;
|
|
43
|
+
/** A last row after the options, e.g. "Show all 23 results". Reachable by arrow keys. */
|
|
44
|
+
footer?: SearchSuggestAction;
|
|
45
|
+
/** The field is fetching suggestions: a spinner shows in the field and, with no options yet, in the list. */
|
|
46
|
+
loading?: boolean;
|
|
47
|
+
/** Announced while loading, and shown in the list when it has no rows yet. */
|
|
48
|
+
loadingLabel?: string;
|
|
49
|
+
/** Shown when the field has text, nothing is loading and there are no options. */
|
|
50
|
+
empty?: ReactNode;
|
|
51
|
+
/** Shown instead of the empty state when suggestions could not be loaded. */
|
|
52
|
+
error?: ReactNode;
|
|
53
|
+
/** A keyboard hint shown in the empty, unfocused field, e.g. "/". */
|
|
54
|
+
shortcutHint?: string;
|
|
55
|
+
/** Accessible name of the clear button (default "Clear search"). */
|
|
56
|
+
clearLabel?: string;
|
|
57
|
+
/** A polite announcement, e.g. "8 results". Rendered in a visually hidden live region. */
|
|
58
|
+
status?: string;
|
|
59
|
+
/** `lg` for a page-level search (hubs, start pages); `md` for a field in a toolbar or header. */
|
|
60
|
+
size?: 'md' | 'lg';
|
|
61
|
+
/** Controlled open state of the suggestion list (optional). */
|
|
62
|
+
open?: boolean;
|
|
63
|
+
/** Called when the list opens or closes. */
|
|
64
|
+
onOpenChange?: (open: boolean) => void;
|
|
65
|
+
/**
|
|
66
|
+
* `absolute` (default) places the list under the field inside its own box. `fixed` floats it on
|
|
67
|
+
* the viewport, so a clipping ancestor (`overflow: hidden` toolbars and headers) cannot cut it off;
|
|
68
|
+
* it follows the field on scroll and resize and flips above it when there is no room below.
|
|
69
|
+
*/
|
|
70
|
+
strategy?: 'absolute' | 'fixed';
|
|
71
|
+
/** Focus the field from outside (e.g. a "/" shortcut). */
|
|
72
|
+
inputRef?: Ref<HTMLInputElement | null>;
|
|
73
|
+
/** Focus the field on mount. */
|
|
74
|
+
autoFocus?: boolean;
|
|
75
|
+
/** Form field name of the input. */
|
|
76
|
+
name?: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* A search field with a suggestion list underneath: the "search and jump" pattern of Windows
|
|
80
|
+
* Settings, Android Settings and most docs sites. Each row shows a name, where it lives and a line
|
|
81
|
+
* of context. ARIA combobox pattern: focus stays in the field, arrow keys move the active row
|
|
82
|
+
* (`aria-activedescendant`), Enter opens it (the first row when none is active), Escape closes the
|
|
83
|
+
* list and a second Escape clears the text.
|
|
84
|
+
*/
|
|
85
|
+
declare function SearchSuggest({ value, onValueChange, options, onSelect, label, placeholder, highlight, heading, headingAction, footer, loading, loadingLabel, empty, error, shortcutHint, clearLabel, status, size, open: openProp, strategy, onOpenChange, inputRef, autoFocus, name, className, onBlur, ...props }: SearchSuggestProps): react.JSX.Element;
|
|
86
|
+
|
|
87
|
+
export { SearchSuggest, type SearchSuggestAction, type SearchSuggestOption, type SearchSuggestProps };
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
|
|
4
|
+
import { cx } from "../cx.js";
|
|
5
|
+
import { useAnchoredPosition } from "../hooks/useAnchoredPosition.js";
|
|
6
|
+
import { searchSegments } from "../search-suggest-model.js";
|
|
7
|
+
import { Spinner } from "./Spinner.js";
|
|
8
|
+
const SearchIcon = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
9
|
+
/* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "7" }),
|
|
10
|
+
/* @__PURE__ */ jsx("path", { d: "m20 20-3.5-3.5" })
|
|
11
|
+
] });
|
|
12
|
+
const ClearIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M4 4l8 8M12 4l-8 8" }) });
|
|
13
|
+
function Emphasised({ text, query }) {
|
|
14
|
+
return /* @__PURE__ */ jsx(Fragment, { children: searchSegments(text, query).map(
|
|
15
|
+
(segment, index) => segment.match ? /* @__PURE__ */ jsx("mark", { className: "uix-search-suggest__match", children: segment.text }, index) : /* @__PURE__ */ jsx("span", { children: segment.text }, index)
|
|
16
|
+
) });
|
|
17
|
+
}
|
|
18
|
+
const keepFocus = (event) => event.preventDefault();
|
|
19
|
+
const useIsomorphicLayoutEffect = typeof document !== "undefined" ? useLayoutEffect : useEffect;
|
|
20
|
+
function SearchSuggest({
|
|
21
|
+
value,
|
|
22
|
+
onValueChange,
|
|
23
|
+
options,
|
|
24
|
+
onSelect,
|
|
25
|
+
label,
|
|
26
|
+
placeholder,
|
|
27
|
+
highlight,
|
|
28
|
+
heading,
|
|
29
|
+
headingAction,
|
|
30
|
+
footer,
|
|
31
|
+
loading = false,
|
|
32
|
+
loadingLabel,
|
|
33
|
+
empty,
|
|
34
|
+
error,
|
|
35
|
+
shortcutHint,
|
|
36
|
+
clearLabel = "Clear search",
|
|
37
|
+
status,
|
|
38
|
+
size = "md",
|
|
39
|
+
open: openProp,
|
|
40
|
+
strategy = "absolute",
|
|
41
|
+
onOpenChange,
|
|
42
|
+
inputRef,
|
|
43
|
+
autoFocus,
|
|
44
|
+
name,
|
|
45
|
+
className,
|
|
46
|
+
onBlur,
|
|
47
|
+
...props
|
|
48
|
+
}) {
|
|
49
|
+
const id = useId();
|
|
50
|
+
const listboxId = `${id}-listbox`;
|
|
51
|
+
const headingId = `${id}-heading`;
|
|
52
|
+
const rootRef = useRef(null);
|
|
53
|
+
const fieldRef = useRef(null);
|
|
54
|
+
useImperativeHandle(inputRef, () => fieldRef.current, []);
|
|
55
|
+
const [openState, setOpenState] = useState(false);
|
|
56
|
+
const open = openProp ?? openState;
|
|
57
|
+
const setOpen = (next) => {
|
|
58
|
+
if (openProp === void 0) setOpenState(next);
|
|
59
|
+
if (next !== open) onOpenChange?.(next);
|
|
60
|
+
};
|
|
61
|
+
const [focused, setFocused] = useState(false);
|
|
62
|
+
const [activeState, setActive] = useState(-1);
|
|
63
|
+
const itemCount = options.length + (footer ? 1 : 0);
|
|
64
|
+
const active = activeState < itemCount ? activeState : -1;
|
|
65
|
+
const optionKey = options.map((option) => option.id).join("|");
|
|
66
|
+
useEffect(() => setActive(-1), [optionKey]);
|
|
67
|
+
const hasText = value.trim().length > 0;
|
|
68
|
+
const showEmpty = hasText && !loading && !error && options.length === 0 && empty != null;
|
|
69
|
+
const showLoading = loading && options.length === 0;
|
|
70
|
+
const visible = open && (itemCount > 0 || showLoading || showEmpty || error != null);
|
|
71
|
+
const optionId = (index) => `${id}-option-${index}`;
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!visible || active < 0) return;
|
|
74
|
+
const node = document.getElementById(optionId(active));
|
|
75
|
+
node?.scrollIntoView?.({ block: "nearest" });
|
|
76
|
+
}, [active, visible]);
|
|
77
|
+
const choose = (index) => {
|
|
78
|
+
const option = options[index];
|
|
79
|
+
if (option) onSelect(option.id, option);
|
|
80
|
+
else if (footer && index === options.length) footer.onSelect();
|
|
81
|
+
else return;
|
|
82
|
+
setOpen(false);
|
|
83
|
+
setActive(-1);
|
|
84
|
+
};
|
|
85
|
+
const handleKeyDown = (event) => {
|
|
86
|
+
if (event.nativeEvent.isComposing || event.keyCode === 229) return;
|
|
87
|
+
switch (event.key) {
|
|
88
|
+
case "ArrowDown":
|
|
89
|
+
case "ArrowUp": {
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
if (!itemCount) {
|
|
92
|
+
setOpen(true);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const down = event.key === "ArrowDown";
|
|
96
|
+
if (!visible) {
|
|
97
|
+
setOpen(true);
|
|
98
|
+
setActive(down ? 0 : itemCount - 1);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
setActive((stored) => {
|
|
102
|
+
const current = stored < itemCount ? stored : -1;
|
|
103
|
+
if (current < 0) return down ? 0 : itemCount - 1;
|
|
104
|
+
return (current + (down ? 1 : -1) + itemCount) % itemCount;
|
|
105
|
+
});
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
case "Home":
|
|
109
|
+
case "End":
|
|
110
|
+
if (visible && active >= 0 && itemCount) {
|
|
111
|
+
event.preventDefault();
|
|
112
|
+
setActive(event.key === "Home" ? 0 : itemCount - 1);
|
|
113
|
+
}
|
|
114
|
+
return;
|
|
115
|
+
case "Enter":
|
|
116
|
+
if (visible && active >= 0) {
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
choose(active);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (visible && hasText && options.length) {
|
|
122
|
+
event.preventDefault();
|
|
123
|
+
choose(0);
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
case "Escape":
|
|
127
|
+
if (visible) {
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
event.stopPropagation();
|
|
130
|
+
setOpen(false);
|
|
131
|
+
setActive(-1);
|
|
132
|
+
} else if (value) {
|
|
133
|
+
event.preventDefault();
|
|
134
|
+
event.stopPropagation();
|
|
135
|
+
onValueChange("");
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
default:
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const handleBlur = (event) => {
|
|
142
|
+
onBlur?.(event);
|
|
143
|
+
if (!rootRef.current?.contains(event.relatedTarget)) {
|
|
144
|
+
setFocused(false);
|
|
145
|
+
setOpen(false);
|
|
146
|
+
setActive(-1);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
const clear = () => {
|
|
150
|
+
onValueChange("");
|
|
151
|
+
setActive(-1);
|
|
152
|
+
fieldRef.current?.focus();
|
|
153
|
+
};
|
|
154
|
+
const activeId = visible && active >= 0 ? optionId(active) : void 0;
|
|
155
|
+
const fieldBoxRef = useRef(null);
|
|
156
|
+
const popupRef = useRef(null);
|
|
157
|
+
const floating = strategy === "fixed";
|
|
158
|
+
useIsomorphicLayoutEffect(() => {
|
|
159
|
+
const popup = popupRef.current;
|
|
160
|
+
const box = fieldBoxRef.current;
|
|
161
|
+
if (!floating || !visible || !popup || !box) return;
|
|
162
|
+
const size2 = () => {
|
|
163
|
+
popup.style.width = `${box.offsetWidth}px`;
|
|
164
|
+
};
|
|
165
|
+
size2();
|
|
166
|
+
window.addEventListener("resize", size2);
|
|
167
|
+
return () => window.removeEventListener("resize", size2);
|
|
168
|
+
}, [floating, visible]);
|
|
169
|
+
useAnchoredPosition(floating ? fieldBoxRef : null, popupRef, { open: floating && visible, placement: "bottom-start", offset: 4 });
|
|
170
|
+
return /* @__PURE__ */ jsxs(
|
|
171
|
+
"div",
|
|
172
|
+
{
|
|
173
|
+
ref: rootRef,
|
|
174
|
+
className: cx("uix-search-suggest", size === "lg" && "uix-search-suggest--lg", className),
|
|
175
|
+
"data-open": visible || void 0,
|
|
176
|
+
onBlur: handleBlur,
|
|
177
|
+
...props,
|
|
178
|
+
children: [
|
|
179
|
+
/* @__PURE__ */ jsxs("div", { ref: fieldBoxRef, className: "uix-search-suggest__field", children: [
|
|
180
|
+
/* @__PURE__ */ jsx("span", { className: "uix-search-suggest__lead", "aria-hidden": "true", children: /* @__PURE__ */ jsx(SearchIcon, {}) }),
|
|
181
|
+
/* @__PURE__ */ jsx(
|
|
182
|
+
"input",
|
|
183
|
+
{
|
|
184
|
+
ref: fieldRef,
|
|
185
|
+
className: "uix-input uix-search-suggest__input",
|
|
186
|
+
type: "text",
|
|
187
|
+
role: "combobox",
|
|
188
|
+
"aria-label": label,
|
|
189
|
+
"aria-autocomplete": "list",
|
|
190
|
+
"aria-expanded": visible,
|
|
191
|
+
"aria-controls": listboxId,
|
|
192
|
+
"aria-activedescendant": activeId,
|
|
193
|
+
"aria-busy": loading || void 0,
|
|
194
|
+
autoComplete: "off",
|
|
195
|
+
spellCheck: false,
|
|
196
|
+
enterKeyHint: "search",
|
|
197
|
+
name,
|
|
198
|
+
value,
|
|
199
|
+
placeholder,
|
|
200
|
+
autoFocus,
|
|
201
|
+
onChange: (event) => {
|
|
202
|
+
onValueChange(event.target.value);
|
|
203
|
+
setActive(-1);
|
|
204
|
+
setOpen(true);
|
|
205
|
+
},
|
|
206
|
+
onFocus: () => {
|
|
207
|
+
setFocused(true);
|
|
208
|
+
setOpen(true);
|
|
209
|
+
},
|
|
210
|
+
onClick: () => setOpen(true),
|
|
211
|
+
onKeyDown: handleKeyDown
|
|
212
|
+
}
|
|
213
|
+
),
|
|
214
|
+
/* @__PURE__ */ jsxs("span", { className: "uix-search-suggest__trail", children: [
|
|
215
|
+
loading && /* @__PURE__ */ jsx(Spinner, { className: "uix-search-suggest__spinner", label: loadingLabel }),
|
|
216
|
+
!loading && value && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-search-suggest__clear", "aria-label": clearLabel, onMouseDown: keepFocus, onClick: clear, children: /* @__PURE__ */ jsx(ClearIcon, {}) }),
|
|
217
|
+
!loading && !value && !focused && shortcutHint && /* @__PURE__ */ jsx("kbd", { className: "uix-kbd", "aria-hidden": "true", children: shortcutHint })
|
|
218
|
+
] })
|
|
219
|
+
] }),
|
|
220
|
+
/* @__PURE__ */ jsxs("div", { ref: popupRef, className: "uix-search-suggest__popup", "data-strategy": floating ? "fixed" : void 0, hidden: !visible, children: [
|
|
221
|
+
heading && options.length > 0 && /* @__PURE__ */ jsxs("div", { className: "uix-search-suggest__header", children: [
|
|
222
|
+
/* @__PURE__ */ jsx("span", { className: "uix-search-suggest__heading", id: headingId, children: heading }),
|
|
223
|
+
headingAction && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-search-suggest__header-action", onMouseDown: keepFocus, onClick: headingAction.onSelect, children: headingAction.label })
|
|
224
|
+
] }),
|
|
225
|
+
/* @__PURE__ */ jsxs(
|
|
226
|
+
"ul",
|
|
227
|
+
{
|
|
228
|
+
id: listboxId,
|
|
229
|
+
role: "listbox",
|
|
230
|
+
className: "uix-search-suggest__list",
|
|
231
|
+
"aria-label": heading && options.length > 0 ? void 0 : label,
|
|
232
|
+
"aria-labelledby": heading && options.length > 0 ? headingId : void 0,
|
|
233
|
+
"aria-busy": loading || void 0,
|
|
234
|
+
children: [
|
|
235
|
+
options.map((option, index) => {
|
|
236
|
+
const crumbs = option.meta ?? [];
|
|
237
|
+
const descId = option.description ? `${optionId(index)}-desc` : void 0;
|
|
238
|
+
return /* @__PURE__ */ jsxs(
|
|
239
|
+
"li",
|
|
240
|
+
{
|
|
241
|
+
id: optionId(index),
|
|
242
|
+
role: "option",
|
|
243
|
+
"aria-selected": index === active,
|
|
244
|
+
"aria-label": crumbs.length ? `${option.title}, ${crumbs.join(" \u203A ")}` : option.title,
|
|
245
|
+
"aria-describedby": descId,
|
|
246
|
+
className: "uix-search-suggest__option",
|
|
247
|
+
"data-active": index === active || void 0,
|
|
248
|
+
onMouseDown: keepFocus,
|
|
249
|
+
onMouseMove: () => {
|
|
250
|
+
if (active !== index) setActive(index);
|
|
251
|
+
},
|
|
252
|
+
onClick: () => choose(index),
|
|
253
|
+
children: [
|
|
254
|
+
option.icon != null && /* @__PURE__ */ jsx("span", { className: "uix-search-suggest__icon", "aria-hidden": "true", children: option.icon }),
|
|
255
|
+
/* @__PURE__ */ jsxs("span", { className: "uix-search-suggest__body", children: [
|
|
256
|
+
/* @__PURE__ */ jsx("span", { className: "uix-search-suggest__title", children: /* @__PURE__ */ jsx(Emphasised, { text: option.title, query: highlight ?? value }) }),
|
|
257
|
+
crumbs.length > 0 && /* @__PURE__ */ jsx("span", { className: "uix-search-suggest__meta", title: crumbs.join(" \u203A "), children: crumbs.map((crumb, crumbIndex) => /* @__PURE__ */ jsx("span", { className: "uix-search-suggest__crumb", children: crumb }, crumbIndex)) }),
|
|
258
|
+
option.description && /* @__PURE__ */ jsx("span", { id: descId, className: "uix-search-suggest__desc", children: /* @__PURE__ */ jsx(Emphasised, { text: option.description, query: highlight ?? value }) })
|
|
259
|
+
] })
|
|
260
|
+
]
|
|
261
|
+
},
|
|
262
|
+
option.id
|
|
263
|
+
);
|
|
264
|
+
}),
|
|
265
|
+
footer && /* @__PURE__ */ jsx(
|
|
266
|
+
"li",
|
|
267
|
+
{
|
|
268
|
+
id: optionId(options.length),
|
|
269
|
+
role: "option",
|
|
270
|
+
"aria-selected": active === options.length,
|
|
271
|
+
className: "uix-search-suggest__footer",
|
|
272
|
+
"data-active": active === options.length || void 0,
|
|
273
|
+
onMouseDown: keepFocus,
|
|
274
|
+
onMouseMove: () => {
|
|
275
|
+
if (active !== options.length) setActive(options.length);
|
|
276
|
+
},
|
|
277
|
+
onClick: () => choose(options.length),
|
|
278
|
+
children: footer.label
|
|
279
|
+
}
|
|
280
|
+
)
|
|
281
|
+
]
|
|
282
|
+
}
|
|
283
|
+
),
|
|
284
|
+
showLoading && // The field's spinner is the announced status; this row is the visual echo in the list.
|
|
285
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-search-suggest__state", "aria-hidden": "true", children: [
|
|
286
|
+
/* @__PURE__ */ jsx("span", { className: "uix-spinner" }),
|
|
287
|
+
loadingLabel && /* @__PURE__ */ jsx("span", { children: loadingLabel })
|
|
288
|
+
] }),
|
|
289
|
+
error != null && options.length === 0 && !loading && /* @__PURE__ */ jsx("div", { className: "uix-search-suggest__state", children: error }),
|
|
290
|
+
showEmpty && /* @__PURE__ */ jsx("div", { className: "uix-search-suggest__state", children: empty })
|
|
291
|
+
] }),
|
|
292
|
+
/* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", role: "status", "aria-live": "polite", children: status ?? "" })
|
|
293
|
+
]
|
|
294
|
+
}
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
export {
|
|
298
|
+
SearchSuggest
|
|
299
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { MouseEventHandler, MouseEvent } from 'react';
|
|
2
|
+
|
|
3
|
+
declare function isBackdropClick(e: MouseEvent<HTMLDialogElement>): boolean;
|
|
4
|
+
/** The dialog's onClick: runs the consumer's own onClick first, then closes on a backdrop click. */
|
|
5
|
+
declare function backdropDismiss(onClose: (() => void) | undefined, onClick?: MouseEventHandler<HTMLDialogElement>, enabled?: boolean): MouseEventHandler<HTMLDialogElement>;
|
|
6
|
+
|
|
7
|
+
export { backdropDismiss, isBackdropClick };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function isBackdropClick(e) {
|
|
2
|
+
if (e.target !== e.currentTarget) return false;
|
|
3
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
4
|
+
if (r.width === 0) return false;
|
|
5
|
+
return e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom;
|
|
6
|
+
}
|
|
7
|
+
function backdropDismiss(onClose, onClick, enabled = true) {
|
|
8
|
+
return (e) => {
|
|
9
|
+
onClick?.(e);
|
|
10
|
+
if (enabled && onClose && isBackdropClick(e)) onClose();
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
backdropDismiss,
|
|
15
|
+
isBackdropClick
|
|
16
|
+
};
|