pixelize-design-library 2.4.2-beta.64 → 2.4.2-beta.65
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/CHANGELOG.md +83 -8
- package/COMPONENT-GOTCHAS.md +37 -4
- package/DESIGN-GUIDE.md +7 -3
- package/dist/Components/CopyButton/CopyButton.d.ts +3 -1
- package/dist/Components/CopyButton/CopyButton.js +2 -2
- package/dist/Components/Drawer/Drawer.d.ts +1 -1
- package/dist/Components/Drawer/Drawer.js +6 -2
- package/dist/Components/Drawer/DrawerProps.d.ts +4 -0
- package/dist/Components/FormDrawer/FormDrawer.d.ts +1 -1
- package/dist/Components/FormDrawer/FormDrawer.js +3 -3
- package/dist/Components/FormDrawer/FormDrawerProps.d.ts +2 -0
- package/dist/Components/Modal/dialogNaming.d.ts +8 -0
- package/dist/Components/Modal/dialogNaming.js +21 -0
- package/dist/Components/NavLauncher/LauncherTabs.js +2 -2
- package/dist/Components/NavLauncher/NavLauncher.d.ts +1 -1
- package/dist/Components/NavLauncher/NavLauncher.js +257 -47
- package/dist/Components/NavLauncher/NavLauncherProps.d.ts +79 -3
- package/dist/Components/NavLauncher/ScopeBar.d.ts +14 -0
- package/dist/Components/NavLauncher/ScopeBar.js +137 -0
- package/dist/Components/NavLauncher/engine/distance.d.ts +4 -0
- package/dist/Components/NavLauncher/engine/distance.js +41 -0
- package/dist/Components/NavLauncher/engine/engine.d.ts +84 -0
- package/dist/Components/NavLauncher/engine/engine.js +471 -0
- package/dist/Components/NavLauncher/engine/fold.d.ts +15 -0
- package/dist/Components/NavLauncher/engine/fold.js +83 -0
- package/dist/Components/NavLauncher/engine/index.d.ts +21 -0
- package/dist/Components/NavLauncher/engine/index.js +17 -0
- package/dist/Components/NavLauncher/launcherParts.d.ts +2 -0
- package/dist/Components/NavLauncher/launcherParts.js +11 -7
- package/dist/Components/NavLauncher/match.d.ts +13 -2
- package/dist/Components/NavLauncher/match.js +26 -12
- package/dist/Components/NavLauncher/searchLayout.d.ts +60 -0
- package/dist/Components/NavLauncher/searchLayout.js +108 -0
- package/dist/Components/NavLauncher/spotlight.d.ts +39 -0
- package/dist/Components/NavLauncher/spotlight.js +202 -0
- package/dist/Components/NavLauncher/spotlightParts.d.ts +49 -0
- package/dist/Components/NavLauncher/spotlightParts.js +108 -0
- package/dist/Components/Table/components/TableHeader.js +3 -1
- package/dist/Components/Table/components/TableLoading.js +8 -2
- package/dist/Components/WizardDialog/WizardDialog.d.ts +1 -1
- package/dist/Components/WizardDialog/WizardDialog.js +15 -3
- package/dist/Components/WizardDialog/WizardDialogProps.d.ts +5 -3
- package/dist/Components/WorkspaceWindow/WorkspaceWindow.js +3 -2
- package/dist/Components/WorkspaceWindow/WorkspaceWindowProps.d.ts +2 -8
- package/dist/Theme/chakra/ringShadow.d.ts +3 -0
- package/dist/Theme/chakra/ringShadow.js +7 -0
- package/dist/Theme/tokens/builders/buildDarkPalette.d.ts +1 -1
- package/dist/Theme/tokens/builders/buildDarkPalette.js +5 -5
- package/dist/esm/Components/CopyButton/CopyButton.js +2 -2
- package/dist/esm/Components/Drawer/Drawer.js +6 -2
- package/dist/esm/Components/FormDrawer/FormDrawer.js +3 -3
- package/dist/esm/Components/Modal/dialogNaming.js +18 -0
- package/dist/esm/Components/NavLauncher/LauncherTabs.js +2 -2
- package/dist/esm/Components/NavLauncher/NavLauncher.js +259 -49
- package/dist/esm/Components/NavLauncher/ScopeBar.js +134 -0
- package/dist/esm/Components/NavLauncher/engine/distance.js +37 -0
- package/dist/esm/Components/NavLauncher/engine/engine.js +458 -0
- package/dist/esm/Components/NavLauncher/engine/fold.js +74 -0
- package/dist/esm/Components/NavLauncher/engine/index.js +14 -0
- package/dist/esm/Components/NavLauncher/launcherParts.js +10 -6
- package/dist/esm/Components/NavLauncher/match.js +25 -12
- package/dist/esm/Components/NavLauncher/searchLayout.js +100 -0
- package/dist/esm/Components/NavLauncher/spotlight.js +194 -0
- package/dist/esm/Components/NavLauncher/spotlightParts.js +95 -0
- package/dist/esm/Components/Table/components/TableHeader.js +4 -2
- package/dist/esm/Components/Table/components/TableLoading.js +6 -3
- package/dist/esm/Components/WizardDialog/WizardDialog.js +15 -3
- package/dist/esm/Components/WorkspaceWindow/WorkspaceWindow.js +4 -3
- package/dist/esm/Theme/chakra/ringShadow.js +3 -0
- package/dist/esm/Theme/tokens/builders/buildDarkPalette.js +5 -5
- package/dist/esm/index.js +2 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +4 -1
- package/package.json +1 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Accents, nukta and joiners fold away; vowel signs and viramas belong to the letter.
|
|
2
|
+
const OPTIONAL_MARKS =
|
|
3
|
+
// eslint-disable-next-line no-misleading-character-class -- each mark is removed on its own
|
|
4
|
+
/[\u0300-\u036f\u0591-\u05c7\u0610-\u061a\u064b-\u065f\u0670\u06d6-\u06ed\u093c\u1ab0-\u1aff\u1dc0-\u1dff\u200c\u200d\u20d0-\u20ff\ufe20-\ufe2f]/g;
|
|
5
|
+
const CHANDRABINDU = /\u0901/g;
|
|
6
|
+
const ANUSVARA = "\u0902";
|
|
7
|
+
const MARK = /\p{M}/u;
|
|
8
|
+
const SEPARATOR = /[^\p{L}\p{N}\p{M}]+/u;
|
|
9
|
+
const SPECIAL = {
|
|
10
|
+
ß: "ss",
|
|
11
|
+
æ: "ae",
|
|
12
|
+
œ: "oe",
|
|
13
|
+
ø: "o",
|
|
14
|
+
ł: "l",
|
|
15
|
+
đ: "d",
|
|
16
|
+
ı: "i",
|
|
17
|
+
};
|
|
18
|
+
const SPECIAL_RE = /[ßæœøłđı]/g;
|
|
19
|
+
const DEVANAGARI_VIRAMA = 0x094d;
|
|
20
|
+
/** Case- and accent-insensitive form used for every comparison: "Crème Brûlée" -> "creme brulee". */
|
|
21
|
+
export const foldText = (text) => text
|
|
22
|
+
.normalize("NFKD")
|
|
23
|
+
.replace(OPTIONAL_MARKS, "")
|
|
24
|
+
.replace(CHANDRABINDU, ANUSVARA)
|
|
25
|
+
.toLowerCase()
|
|
26
|
+
.replace(SPECIAL_RE, (ch) => SPECIAL[ch]);
|
|
27
|
+
export const splitWords = (folded) => folded.split(SEPARATOR).filter(Boolean);
|
|
28
|
+
/** Folded, de-duplicated query tokens in typing order. */
|
|
29
|
+
export const tokenize = (query) => Array.from(new Set(splitWords(foldText(query))));
|
|
30
|
+
/** True when folded text carries combining marks, so its written letters span several characters. */
|
|
31
|
+
export const hasMarks = (folded) => MARK.test(folded);
|
|
32
|
+
const isDevanagariConsonant = (code) => (code >= 0x0915 && code <= 0x0939) || (code >= 0x0978 && code <= 0x097f);
|
|
33
|
+
const markCodes = new Map();
|
|
34
|
+
const isMarkCode = (code, char) => {
|
|
35
|
+
if (code < 0x0300)
|
|
36
|
+
return false;
|
|
37
|
+
let known = markCodes.get(code);
|
|
38
|
+
if (known === undefined) {
|
|
39
|
+
known = MARK.test(char);
|
|
40
|
+
markCodes.set(code, known);
|
|
41
|
+
}
|
|
42
|
+
return known;
|
|
43
|
+
};
|
|
44
|
+
/** Written letters: a base with its marks; a Devanagari conjunct counts as one. */
|
|
45
|
+
export const letters = (word) => {
|
|
46
|
+
var _a;
|
|
47
|
+
const out = [];
|
|
48
|
+
let previous = 0;
|
|
49
|
+
for (const char of word) {
|
|
50
|
+
const code = (_a = char.codePointAt(0)) !== null && _a !== void 0 ? _a : 0;
|
|
51
|
+
const joins = isMarkCode(code, char) ||
|
|
52
|
+
(previous === DEVANAGARI_VIRAMA && isDevanagariConsonant(code));
|
|
53
|
+
if (joins && out.length > 0)
|
|
54
|
+
out[out.length - 1] += char;
|
|
55
|
+
else
|
|
56
|
+
out.push(char);
|
|
57
|
+
previous = code;
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
};
|
|
61
|
+
export const foldWithMap = (text) => {
|
|
62
|
+
let folded = "";
|
|
63
|
+
const map = [];
|
|
64
|
+
let at = 0;
|
|
65
|
+
for (const char of text) {
|
|
66
|
+
const piece = foldText(char);
|
|
67
|
+
for (let i = 0; i < piece.length; i += 1)
|
|
68
|
+
map.push(at);
|
|
69
|
+
folded += piece;
|
|
70
|
+
at += char.length;
|
|
71
|
+
}
|
|
72
|
+
map.push(text.length);
|
|
73
|
+
return { folded, map };
|
|
74
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createMatcher, createSearchIndex, editDistance, foldText, highlightRanges, matchesQuery, searchDocuments, searchIndex, tokenize, usageBoosts, } from "./engine.js";
|
|
2
|
+
/** Framework-free ranked search used by NavLauncher; reusable by any list, picker or palette. */
|
|
3
|
+
export const LauncherSearchEngine = Object.freeze({
|
|
4
|
+
createIndex: createSearchIndex,
|
|
5
|
+
search: searchIndex,
|
|
6
|
+
searchDocuments,
|
|
7
|
+
createMatcher,
|
|
8
|
+
matches: matchesQuery,
|
|
9
|
+
highlightRanges,
|
|
10
|
+
usageBoosts,
|
|
11
|
+
foldText,
|
|
12
|
+
tokenize,
|
|
13
|
+
editDistance,
|
|
14
|
+
});
|
|
@@ -3,6 +3,7 @@ import { Box, Flex, Heading, Text } from "@chakra-ui/react";
|
|
|
3
3
|
import { Lock, Star } from "lucide-react";
|
|
4
4
|
import { createContext, Fragment, useContext, } from "react";
|
|
5
5
|
import { FOCUS_VISIBLE_OVER_STATE, focusVisibleStyles, } from "../../Theme/chakra/focusRing.styles.js";
|
|
6
|
+
import { insetRingShadow } from "../../Theme/chakra/ringShadow.js";
|
|
6
7
|
import { resolveFilledSurface } from "../../Theme/tokens/builders/filledSurface.js";
|
|
7
8
|
import { AA_NON_TEXT, liftToContrast, } from "../../Theme/tokens/builders/outlineRung.js";
|
|
8
9
|
import { listRowBackgrounds } from "../../Theme/tokens/builders/rowStates.js";
|
|
@@ -41,7 +42,7 @@ export const activeRowSurface = (colors) => {
|
|
|
41
42
|
const { edge, fill } = activeRowStyle(colors);
|
|
42
43
|
return {
|
|
43
44
|
backgroundColor: fill,
|
|
44
|
-
boxShadow:
|
|
45
|
+
boxShadow: insetRingShadow(edge),
|
|
45
46
|
outline: "2px solid transparent",
|
|
46
47
|
outlineOffset: "-2px",
|
|
47
48
|
};
|
|
@@ -110,9 +111,12 @@ export function RowIcon({ icon: Icon, size, stroke = 1.75, mt }) {
|
|
|
110
111
|
return null;
|
|
111
112
|
return (_jsx(Box, { as: "span", color: colors.textMuted, display: "inline-flex", flexShrink: 0, mt: mt, transition: transitionFor(["color"]), _groupHover: { color: colors.accentText }, _groupFocusWithin: { color: colors.accentText }, sx: { "[data-reveal=true] &": { color: colors.accentText } }, children: _jsx(Icon, { size: size, strokeWidth: stroke, "aria-hidden": true }) }));
|
|
112
113
|
}
|
|
114
|
+
const HighlightOptionsContext = createContext(undefined);
|
|
115
|
+
export const HighlightOptionsProvider = HighlightOptionsContext.Provider;
|
|
113
116
|
export function Highlight({ text, query }) {
|
|
114
117
|
const { colors } = useCustomTheme();
|
|
115
|
-
|
|
118
|
+
const options = useContext(HighlightOptionsContext);
|
|
119
|
+
return (_jsx(_Fragment, { children: highlightSegments(text, query, options).map((segment, i) => segment.isMatch ? (_jsx(Box, { as: "mark", bg: "transparent", color: "inherit", fontWeight: 650, textDecoration: "underline", textDecorationColor: colors.primary.opacity[48], textDecorationThickness: "1.5px", textUnderlineOffset: "2px", children: segment.text }, i)) : (_jsx(Fragment, { children: segment.text }, i))) }));
|
|
116
120
|
}
|
|
117
121
|
export function RowActions({ children }) {
|
|
118
122
|
const { colors } = useCustomTheme();
|
|
@@ -129,7 +133,7 @@ export function RowActions({ children }) {
|
|
|
129
133
|
}
|
|
130
134
|
export function RowTip({ text }) {
|
|
131
135
|
const { colors } = useCustomTheme();
|
|
132
|
-
return (_jsx(Box, { role: "tooltip", position: "absolute", right: "20px", top: "-26px", zIndex: 2, px: "8px", py: "3px", borderRadius: "6px", fontSize: "
|
|
136
|
+
return (_jsx(Box, { role: "tooltip", position: "absolute", right: "20px", top: "-26px", zIndex: 2, px: "8px", py: "3px", borderRadius: "6px", fontSize: "xs-minus", fontWeight: 600, whiteSpace: "nowrap", bg: colors.text[900], color: colors.surfaceRaised, visibility: "hidden", pointerEvents: "none", _groupHover: { visibility: "visible" }, children: text }));
|
|
133
137
|
}
|
|
134
138
|
export function ModuleRow({ module, keyScreens, isPinned, onTogglePin, pinId, labels, link, }) {
|
|
135
139
|
const { colors } = useCustomTheme();
|
|
@@ -150,11 +154,11 @@ export function RailRow({ screen, module, link, isStrong = false, trailing, pinI
|
|
|
150
154
|
const hover = useRowHover();
|
|
151
155
|
const focus = useRowFocus();
|
|
152
156
|
const isTouch = useTouchDensity();
|
|
153
|
-
return (_jsxs(Flex, { "data-group": true, position: "relative", align: "center", gap: "10px", h: isTouch ? "44px" : "32px", pl: "8px", pr: "4px", borderRadius: "8px", transition: transitionFor(["background-color"]), _hover: hover._hover, _focusWithin: hover._focusWithin, sx: focus.sx, children: [_jsx(RowIcon, { icon: (_a = screen.icon) !== null && _a !== void 0 ? _a : module.icon, size: 15 }), _jsx(OverflowToolTip, { label: screen.label, placement: "top-start", children: _jsx(Box, { as: link.linkAs, href: screen.href, "data-launcher-item": true, ...focus.primary, "data-pin-id": pinId, onClick: link.onPlainClick, flex: 1, minW: 0, fontSize: "
|
|
157
|
+
return (_jsxs(Flex, { "data-group": true, position: "relative", align: "center", gap: "10px", h: isTouch ? "44px" : "32px", pl: "8px", pr: "4px", borderRadius: "8px", transition: transitionFor(["background-color"]), _hover: hover._hover, _focusWithin: hover._focusWithin, sx: focus.sx, children: [_jsx(RowIcon, { icon: (_a = screen.icon) !== null && _a !== void 0 ? _a : module.icon, size: 15 }), _jsx(OverflowToolTip, { label: screen.label, placement: "top-start", children: _jsx(Box, { as: link.linkAs, href: screen.href, "data-launcher-item": true, ...focus.primary, "data-pin-id": pinId, onClick: link.onPlainClick, flex: 1, minW: 0, fontSize: "xs-plus", fontWeight: isStrong ? 500 : 400, color: colors.text[900], noOfLines: 1, borderRadius: "sm", sx: stretchedLink("8px"), children: screen.label }) }), _jsx(Flex, { position: "relative", zIndex: 1, align: "center", gap: "2px", children: trailing })] }));
|
|
154
158
|
}
|
|
155
159
|
export function PinTextButton({ label, text, onToggle }) {
|
|
156
160
|
const { colors } = useCustomTheme();
|
|
157
|
-
return (_jsxs(Box, { as: "button", type: "button", "aria-label": label, flexShrink: 0, h: "22px", px: "7px", display: "inline-flex", alignItems: "center", gap: "4px", borderRadius: "6px", borderWidth: "1px", borderColor: colors.boxborder[200], bg: colors.surfaceRaised, color: colors.text[700], fontSize: "
|
|
161
|
+
return (_jsxs(Box, { as: "button", type: "button", "aria-label": label, flexShrink: 0, h: "22px", px: "7px", display: "inline-flex", alignItems: "center", gap: "4px", borderRadius: "6px", borderWidth: "1px", borderColor: colors.boxborder[200], bg: colors.surfaceRaised, color: colors.text[700], fontSize: "xs-minus", fontWeight: 600, cursor: "pointer", opacity: 0, transition: transitionFor(["opacity", "background-color"]), sx: { ...revealStyles(colors), "@media (hover: none)": { opacity: 1 } }, onClick: (event) => {
|
|
158
162
|
event.preventDefault();
|
|
159
163
|
event.stopPropagation();
|
|
160
164
|
onToggle();
|
|
@@ -166,7 +170,7 @@ export function PinnedTile({ screen, module, link, labels, onTogglePin, }) {
|
|
|
166
170
|
const Icon = (_a = screen.icon) !== null && _a !== void 0 ? _a : module.icon;
|
|
167
171
|
const focus = useRowFocus(true);
|
|
168
172
|
const isTouch = useTouchDensity();
|
|
169
|
-
return (_jsxs(Flex, { "data-group": true, "data-nav-cell": true, position: "relative", align: "center", gap: "8px", h: isTouch ? "44px" : "36px", pl: "9px", pr: "4px", borderRadius: "9px", borderWidth: "1px", borderColor: colors.boxborder[200], bg: colors.surfaceRaised, transition: transitionFor(["border-color", "background-color"]), _hover: { borderColor: colors.gray[300] }, sx: focus.sx, children: [Icon ? (_jsx(Box, { as: "span", display: "inline-flex", flexShrink: 0, color: colors.accentText, children: _jsx(Icon, { size: 15, strokeWidth: 1.8, "aria-hidden": true }) })) : null, _jsx(OverflowToolTip, { label: screen.label, placement: "top-start", children: _jsx(Box, { as: link.linkAs, href: screen.href, "data-launcher-item": true, ...focus.primary, "data-pin-id": onTogglePin ? screen.id : undefined, onClick: link.onPlainClick, flex: 1, minW: 0, fontSize: "
|
|
173
|
+
return (_jsxs(Flex, { "data-group": true, "data-nav-cell": true, position: "relative", align: "center", gap: "8px", h: isTouch ? "44px" : "36px", pl: "9px", pr: "4px", borderRadius: "9px", borderWidth: "1px", borderColor: colors.boxborder[200], bg: colors.surfaceRaised, transition: transitionFor(["border-color", "background-color"]), _hover: { borderColor: colors.gray[300] }, sx: focus.sx, children: [Icon ? (_jsx(Box, { as: "span", display: "inline-flex", flexShrink: 0, color: colors.accentText, children: _jsx(Icon, { size: 15, strokeWidth: 1.8, "aria-hidden": true }) })) : null, _jsx(OverflowToolTip, { label: screen.label, placement: "top-start", children: _jsx(Box, { as: link.linkAs, href: screen.href, "data-launcher-item": true, ...focus.primary, "data-pin-id": onTogglePin ? screen.id : undefined, onClick: link.onPlainClick, flex: 1, minW: 0, fontSize: "xs-plus", fontWeight: 500, color: colors.text[900], noOfLines: 1, borderRadius: "sm", sx: stretchedLink("9px"), children: screen.label }) }), _jsx(CountBadge, { value: screen.badge, size: "tile" }), onTogglePin ? (_jsx(Box, { position: "relative", zIndex: 1, flexShrink: 0, w: "0", overflow: "hidden", transition: transitionFor(["width"]), _groupHover: { w: "22px" }, _groupFocusWithin: { w: "22px" }, children: _jsx(PinToggle, { isPinned: true, size: 12, label: labels.unpin(screen.label), title: labels.unpinTip, onToggle: onTogglePin }) })) : null] }));
|
|
170
174
|
}
|
|
171
175
|
export function AppsSection({ apps, hues, currentAppId, heading, hideHeading = false, onSelect, }) {
|
|
172
176
|
const { colors } = useCustomTheme();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { highlightRanges } from "./engine/engine.js";
|
|
1
2
|
const normalize = (text) => text.toLocaleLowerCase().normalize("NFKD");
|
|
2
3
|
function score(label, haystack, tokens) {
|
|
3
4
|
if (!tokens.every((t) => haystack.includes(t)))
|
|
@@ -23,6 +24,12 @@ function rank(rows, text, tokens) {
|
|
|
23
24
|
.sort((a, b) => a.s - b.s || a.index - b.index)
|
|
24
25
|
.map((r) => r.row);
|
|
25
26
|
}
|
|
27
|
+
export function filterItems(items, query) {
|
|
28
|
+
const tokens = normalize(query).split(/\s+/).filter(Boolean);
|
|
29
|
+
if (tokens.length === 0)
|
|
30
|
+
return [];
|
|
31
|
+
return rank([...items], (a) => { var _a, _b; return `${a.label} ${(_a = a.context) !== null && _a !== void 0 ? _a : ""} ${(_b = a.keywords) !== null && _b !== void 0 ? _b : ""}`; }, tokens);
|
|
32
|
+
}
|
|
26
33
|
export function flattenScreens(sections) {
|
|
27
34
|
var _a, _b, _c;
|
|
28
35
|
const seen = new Set();
|
|
@@ -48,6 +55,7 @@ export function flattenScreens(sections) {
|
|
|
48
55
|
}
|
|
49
56
|
return out;
|
|
50
57
|
}
|
|
58
|
+
/** @deprecated Legacy flat search; NavLauncher uses searchLauncherSections (classic) or the spotlight engine. */
|
|
51
59
|
export function searchLauncher(input) {
|
|
52
60
|
var _a, _b;
|
|
53
61
|
const tokens = normalize(input.query).split(/\s+/).filter(Boolean);
|
|
@@ -137,8 +145,23 @@ export function searchLauncherSections(input) {
|
|
|
137
145
|
].filter((g) => g.items.length > 0);
|
|
138
146
|
return { sections, groups };
|
|
139
147
|
}
|
|
148
|
+
const toSegments = (text, ranges) => {
|
|
149
|
+
const out = [];
|
|
150
|
+
let cursor = 0;
|
|
151
|
+
for (const [start, end] of ranges) {
|
|
152
|
+
if (start > cursor)
|
|
153
|
+
out.push({ text: text.slice(cursor, start), isMatch: false });
|
|
154
|
+
out.push({ text: text.slice(start, end), isMatch: true });
|
|
155
|
+
cursor = end;
|
|
156
|
+
}
|
|
157
|
+
if (cursor < text.length)
|
|
158
|
+
out.push({ text: text.slice(cursor), isMatch: false });
|
|
159
|
+
return out.length ? out : [{ text, isMatch: false }];
|
|
160
|
+
};
|
|
140
161
|
/** Splits `text` so every case-insensitive occurrence of a query token is its own matched segment. */
|
|
141
|
-
export function highlightSegments(text, query) {
|
|
162
|
+
export function highlightSegments(text, query, options) {
|
|
163
|
+
if (options === null || options === void 0 ? void 0 : options.fuzzy)
|
|
164
|
+
return toSegments(text, highlightRanges(text, query, { synonyms: options.synonyms }));
|
|
142
165
|
const lower = text.toLocaleLowerCase();
|
|
143
166
|
const tokens = query.toLocaleLowerCase().split(/\s+/).filter(Boolean);
|
|
144
167
|
if (tokens.length === 0 || lower.length !== text.length)
|
|
@@ -158,15 +181,5 @@ export function highlightSegments(text, query) {
|
|
|
158
181
|
else
|
|
159
182
|
merged.push([...range]);
|
|
160
183
|
}
|
|
161
|
-
|
|
162
|
-
let cursor = 0;
|
|
163
|
-
for (const [start, end] of merged) {
|
|
164
|
-
if (start > cursor)
|
|
165
|
-
out.push({ text: text.slice(cursor, start), isMatch: false });
|
|
166
|
-
out.push({ text: text.slice(start, end), isMatch: true });
|
|
167
|
-
cursor = end;
|
|
168
|
-
}
|
|
169
|
-
if (cursor < text.length)
|
|
170
|
-
out.push({ text: text.slice(cursor), isMatch: false });
|
|
171
|
-
return out.length ? out : [{ text, isMatch: false }];
|
|
184
|
+
return toSegments(text, merged);
|
|
172
185
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { SCOPE_ACTIONS, SCOPE_ALL } from "./spotlight.js";
|
|
2
|
+
export const inScope = (groups, scope) => scope === SCOPE_ALL ? groups : groups.filter((g) => g.scope === scope);
|
|
3
|
+
/** A scope's items as passed, unranked, for browsing before typing. */
|
|
4
|
+
export const scopeGroups = (input) => {
|
|
5
|
+
var _a, _b, _c;
|
|
6
|
+
const groups = [];
|
|
7
|
+
if (input.scope === SCOPE_ACTIONS && ((_a = input.actions) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
8
|
+
groups.push({
|
|
9
|
+
id: SCOPE_ACTIONS,
|
|
10
|
+
label: input.actionsLabel,
|
|
11
|
+
scope: SCOPE_ACTIONS,
|
|
12
|
+
isSection: false,
|
|
13
|
+
items: input.actions.map((action) => ({ ...action, id: `action:${action.id}` })),
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
for (const group of (_b = input.extraResults) !== null && _b !== void 0 ? _b : []) {
|
|
17
|
+
const scope = (_c = group.scope) !== null && _c !== void 0 ? _c : group.id;
|
|
18
|
+
if (scope !== input.scope || group.items.length === 0)
|
|
19
|
+
continue;
|
|
20
|
+
groups.push({
|
|
21
|
+
id: group.id,
|
|
22
|
+
label: group.label,
|
|
23
|
+
scope,
|
|
24
|
+
isSection: false,
|
|
25
|
+
items: group.items.map((item) => ({ ...item, id: `${group.id}:${item.id}` })),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return groups;
|
|
29
|
+
};
|
|
30
|
+
export const countByScope = (groups, answers) => {
|
|
31
|
+
var _a;
|
|
32
|
+
const counts = new Map();
|
|
33
|
+
let all = answers;
|
|
34
|
+
for (const group of groups) {
|
|
35
|
+
counts.set(group.scope, ((_a = counts.get(group.scope)) !== null && _a !== void 0 ? _a : 0) + group.items.length);
|
|
36
|
+
all += group.items.length;
|
|
37
|
+
}
|
|
38
|
+
counts.set(SCOPE_ALL, all);
|
|
39
|
+
return counts;
|
|
40
|
+
};
|
|
41
|
+
export const takeTopHit = (groups) => {
|
|
42
|
+
let at = -1;
|
|
43
|
+
let index = -1;
|
|
44
|
+
let bestScore = 0;
|
|
45
|
+
for (let g = 0; g < groups.length; g += 1) {
|
|
46
|
+
groups[g].items.forEach((item, i) => {
|
|
47
|
+
var _a, _b;
|
|
48
|
+
if (((_a = item.score) !== null && _a !== void 0 ? _a : 0) <= bestScore)
|
|
49
|
+
return;
|
|
50
|
+
bestScore = (_b = item.score) !== null && _b !== void 0 ? _b : 0;
|
|
51
|
+
at = g;
|
|
52
|
+
index = i;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (at < 0)
|
|
56
|
+
return { groups };
|
|
57
|
+
return {
|
|
58
|
+
topHit: groups[at].items[index],
|
|
59
|
+
groups: groups
|
|
60
|
+
.map((group, g) => (g === at ? { ...group, items: group.items.filter((_, i) => i !== index) } : group))
|
|
61
|
+
.filter((group) => group.items.length > 0),
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
/** Flat option order for keyboard and ARIA plus per-row group sizes for `moveResult`. */
|
|
65
|
+
export const layoutSearch = (input) => {
|
|
66
|
+
const blocks = [];
|
|
67
|
+
const entries = [];
|
|
68
|
+
const sizes = [];
|
|
69
|
+
if (input.answers.length) {
|
|
70
|
+
blocks.push({ type: "answers", start: 0, items: input.answers });
|
|
71
|
+
for (const item of input.answers) {
|
|
72
|
+
entries.push({ type: "answer", item });
|
|
73
|
+
sizes.push(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (input.topHit) {
|
|
77
|
+
blocks.push({ type: "top", start: entries.length, item: input.topHit });
|
|
78
|
+
entries.push({ type: "top", item: input.topHit });
|
|
79
|
+
sizes.push(1);
|
|
80
|
+
}
|
|
81
|
+
let total = entries.length;
|
|
82
|
+
for (const group of input.groups) {
|
|
83
|
+
const cap = input.limit !== undefined && !input.expanded.has(group.id) ? Math.max(1, input.limit) : Infinity;
|
|
84
|
+
const visible = group.items.slice(0, cap);
|
|
85
|
+
const hidden = group.items.length - visible.length;
|
|
86
|
+
const start = entries.length;
|
|
87
|
+
for (const item of visible)
|
|
88
|
+
entries.push({ type: "row", item, groupId: group.id });
|
|
89
|
+
sizes.push(visible.length);
|
|
90
|
+
total += group.items.length;
|
|
91
|
+
let more;
|
|
92
|
+
if (hidden > 0) {
|
|
93
|
+
more = { index: entries.length, count: hidden };
|
|
94
|
+
entries.push({ type: "more", groupId: group.id, count: hidden });
|
|
95
|
+
sizes.push(1);
|
|
96
|
+
}
|
|
97
|
+
blocks.push({ type: "group", group, start, visible, more });
|
|
98
|
+
}
|
|
99
|
+
return { blocks, entries, sizes, total };
|
|
100
|
+
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { createSearchIndex, searchDocuments, searchIndex, usageBoosts, } from "./engine/engine.js";
|
|
2
|
+
export const SCOPE_ALL = "all";
|
|
3
|
+
export const SCOPE_SCREENS = "screens";
|
|
4
|
+
export const SCOPE_ACTIONS = "actions";
|
|
5
|
+
const SHOWN_FIELDS = new Set(["keyword", "fact", "answer"]);
|
|
6
|
+
const RELATIVE_CUTOFF = 0.25;
|
|
7
|
+
const keywordFields = (keywords) => (typeof keywords === "string" ? keywords.split(/[,;|]/) : (keywords !== null && keywords !== void 0 ? keywords : []))
|
|
8
|
+
.map((k) => k.trim())
|
|
9
|
+
.filter(Boolean)
|
|
10
|
+
.map((text) => ({ name: "keyword", text, weight: 0.75 }));
|
|
11
|
+
const label = (text) => ({
|
|
12
|
+
name: "label",
|
|
13
|
+
text,
|
|
14
|
+
weight: 1,
|
|
15
|
+
});
|
|
16
|
+
export const itemFields = (item) => {
|
|
17
|
+
var _a;
|
|
18
|
+
return [
|
|
19
|
+
label(item.label),
|
|
20
|
+
...keywordFields(item.keywords),
|
|
21
|
+
...(item.context
|
|
22
|
+
? [{ name: "context", text: item.context, weight: 0.45 }]
|
|
23
|
+
: []),
|
|
24
|
+
...((_a = item.facts) !== null && _a !== void 0 ? _a : []).map((f) => ({
|
|
25
|
+
name: "fact",
|
|
26
|
+
text: `${f.label}: ${f.value}`,
|
|
27
|
+
weight: 0.6,
|
|
28
|
+
})),
|
|
29
|
+
...(item.answer
|
|
30
|
+
? [
|
|
31
|
+
{ name: "answer", text: item.answer.label, weight: 0.9 },
|
|
32
|
+
{ name: "answer", text: item.answer.value, weight: 0.5 },
|
|
33
|
+
]
|
|
34
|
+
: []),
|
|
35
|
+
];
|
|
36
|
+
};
|
|
37
|
+
const decorate = (result, hit) => {
|
|
38
|
+
var _a;
|
|
39
|
+
return ({
|
|
40
|
+
...result,
|
|
41
|
+
score: hit.score,
|
|
42
|
+
keywordHint: (_a = hit.matches.find((m) => SHOWN_FIELDS.has(m.field))) === null || _a === void 0 ? void 0 : _a.text,
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
export function createSpotlightIndex(sections, actions, actionsLabel) {
|
|
46
|
+
var _a;
|
|
47
|
+
const documents = [];
|
|
48
|
+
const entries = [];
|
|
49
|
+
const groups = [];
|
|
50
|
+
const seen = new Set();
|
|
51
|
+
const add = (fields, entry) => {
|
|
52
|
+
documents.push({ id: entry.result.id, fields });
|
|
53
|
+
entries.push(entry);
|
|
54
|
+
};
|
|
55
|
+
for (const section of sections) {
|
|
56
|
+
const groupId = `section:${section.id}`;
|
|
57
|
+
groups.push({
|
|
58
|
+
id: groupId,
|
|
59
|
+
label: section.label,
|
|
60
|
+
scope: SCOPE_SCREENS,
|
|
61
|
+
isSection: true,
|
|
62
|
+
});
|
|
63
|
+
for (const module of section.modules) {
|
|
64
|
+
if (module.screens.length === 0)
|
|
65
|
+
continue;
|
|
66
|
+
add([
|
|
67
|
+
label(module.label),
|
|
68
|
+
...keywordFields(module.keywords),
|
|
69
|
+
{ name: "context", text: section.label, weight: 0.35 },
|
|
70
|
+
], {
|
|
71
|
+
groupId,
|
|
72
|
+
usageId: module.screens[0].id,
|
|
73
|
+
result: {
|
|
74
|
+
id: `module:${module.id}`,
|
|
75
|
+
kind: "module",
|
|
76
|
+
label: module.label,
|
|
77
|
+
icon: module.icon,
|
|
78
|
+
badge: module.badge,
|
|
79
|
+
isNew: module.isNew,
|
|
80
|
+
href: module.href,
|
|
81
|
+
screenId: module.screens[0].id,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
for (const screen of module.screens) {
|
|
85
|
+
if (screen.label === module.label || seen.has(screen.id))
|
|
86
|
+
continue;
|
|
87
|
+
seen.add(screen.id);
|
|
88
|
+
add([
|
|
89
|
+
label(screen.label),
|
|
90
|
+
...keywordFields(screen.keywords),
|
|
91
|
+
{ name: "context", text: module.label, weight: 0.5 },
|
|
92
|
+
{ name: "section", text: section.label, weight: 0.3 },
|
|
93
|
+
], {
|
|
94
|
+
groupId,
|
|
95
|
+
usageId: screen.id,
|
|
96
|
+
result: {
|
|
97
|
+
id: `screen:${screen.id}`,
|
|
98
|
+
kind: "screen",
|
|
99
|
+
label: screen.label,
|
|
100
|
+
context: module.label,
|
|
101
|
+
icon: (_a = screen.icon) !== null && _a !== void 0 ? _a : module.icon,
|
|
102
|
+
badge: screen.badge,
|
|
103
|
+
href: screen.href,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (actions === null || actions === void 0 ? void 0 : actions.length) {
|
|
110
|
+
groups.push({
|
|
111
|
+
id: SCOPE_ACTIONS,
|
|
112
|
+
label: actionsLabel,
|
|
113
|
+
scope: SCOPE_ACTIONS,
|
|
114
|
+
isSection: false,
|
|
115
|
+
});
|
|
116
|
+
for (const action of actions) {
|
|
117
|
+
add(itemFields(action), {
|
|
118
|
+
groupId: SCOPE_ACTIONS,
|
|
119
|
+
result: { ...action, id: `action:${action.id}` },
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { index: createSearchIndex(documents), entries, groups };
|
|
124
|
+
}
|
|
125
|
+
export function spotlightBoosts(spot, usage) {
|
|
126
|
+
const byScreen = usageBoosts(usage);
|
|
127
|
+
const out = {};
|
|
128
|
+
spot.entries.forEach((entry) => {
|
|
129
|
+
const boost = entry.usageId ? byScreen[entry.usageId] : undefined;
|
|
130
|
+
if (boost)
|
|
131
|
+
out[entry.result.id] = boost;
|
|
132
|
+
});
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
const searchGroup = (group, query, synonyms) => {
|
|
136
|
+
const hits = searchDocuments(group.items.map((item) => ({ id: item.id, fields: itemFields(item) })), query, { synonyms });
|
|
137
|
+
const result = (index) => ({
|
|
138
|
+
...group.items[index],
|
|
139
|
+
id: `${group.id}:${group.items[index].id}`,
|
|
140
|
+
});
|
|
141
|
+
if (group.matchLocally)
|
|
142
|
+
return hits.map((hit) => decorate(result(hit.index), hit));
|
|
143
|
+
const byIndex = new Map(hits.map((hit) => [hit.index, hit]));
|
|
144
|
+
return group.items.map((_, index) => {
|
|
145
|
+
const hit = byIndex.get(index);
|
|
146
|
+
return hit ? decorate(result(index), hit) : { ...result(index), score: 0 };
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
/** Ranked groups; local rows under 25% of the best score are dropped. */
|
|
150
|
+
export function searchSpotlight(spot, input) {
|
|
151
|
+
var _a, _b, _c, _d;
|
|
152
|
+
if (!input.query.trim())
|
|
153
|
+
return [];
|
|
154
|
+
const hits = searchIndex(spot.index, input.query, {
|
|
155
|
+
synonyms: input.synonyms,
|
|
156
|
+
boosts: input.boosts,
|
|
157
|
+
});
|
|
158
|
+
const byGroup = new Map();
|
|
159
|
+
for (const hit of hits) {
|
|
160
|
+
const entry = spot.entries[hit.index];
|
|
161
|
+
const rows = (_a = byGroup.get(entry.groupId)) !== null && _a !== void 0 ? _a : [];
|
|
162
|
+
rows.push(decorate(entry.result, hit));
|
|
163
|
+
byGroup.set(entry.groupId, rows);
|
|
164
|
+
}
|
|
165
|
+
const groups = spot.groups.map((g) => {
|
|
166
|
+
var _a;
|
|
167
|
+
return ({
|
|
168
|
+
...g,
|
|
169
|
+
items: (_a = byGroup.get(g.id)) !== null && _a !== void 0 ? _a : [],
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
for (const group of (_b = input.extraResults) !== null && _b !== void 0 ? _b : []) {
|
|
173
|
+
groups.push({
|
|
174
|
+
id: group.id,
|
|
175
|
+
label: group.label,
|
|
176
|
+
scope: (_c = group.scope) !== null && _c !== void 0 ? _c : group.id,
|
|
177
|
+
isSection: false,
|
|
178
|
+
items: searchGroup(group, input.query, input.synonyms),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
const remote = new Set((_d = input.extraResults) === null || _d === void 0 ? void 0 : _d.filter((g) => !g.matchLocally).map((g) => g.id));
|
|
182
|
+
const floor = RELATIVE_CUTOFF *
|
|
183
|
+
Math.max(0, ...groups.flatMap((g) => g.items.map((i) => { var _a; return (_a = i.score) !== null && _a !== void 0 ? _a : 0; })));
|
|
184
|
+
return groups
|
|
185
|
+
.map((g) => remote.has(g.id)
|
|
186
|
+
? g
|
|
187
|
+
: { ...g, items: g.items.filter((i) => { var _a; return ((_a = i.score) !== null && _a !== void 0 ? _a : 0) >= floor; }) })
|
|
188
|
+
.filter((g) => g.items.length > 0);
|
|
189
|
+
}
|
|
190
|
+
export function matchAnswers(answers, query, synonyms) {
|
|
191
|
+
if (!(answers === null || answers === void 0 ? void 0 : answers.length))
|
|
192
|
+
return [];
|
|
193
|
+
return searchGroup({ id: "answer", label: "", items: answers, matchLocally: true }, query, synonyms);
|
|
194
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Flex, Text } from "@chakra-ui/react";
|
|
3
|
+
import { ChevronDown, Search as SearchIcon } from "lucide-react";
|
|
4
|
+
import { useRef } from "react";
|
|
5
|
+
import { reducedMotionSx } from "../../Theme/chakra/motion.js";
|
|
6
|
+
import { listRowBackgrounds } from "../../Theme/tokens/builders/rowStates.js";
|
|
7
|
+
import { transitionFor } from "../../Theme/tokens/primitives/motion.js";
|
|
8
|
+
import { useCustomTheme } from "../../Theme/useCustomTheme.js";
|
|
9
|
+
import Button from "../Button/Button.js";
|
|
10
|
+
import CopyButton from "../CopyButton/CopyButton.js";
|
|
11
|
+
import { KbdShortcut } from "../Kbd/Kbd.js";
|
|
12
|
+
import { activeRowStyle, activeRowSurface, CountBadge, Highlight, useTouchDensity, } from "./launcherParts.js";
|
|
13
|
+
export const QUICK_ACTION_LIMIT = 5;
|
|
14
|
+
const HIDE_ON_TOUCH = { "@media (hover: none)": { display: "none" } };
|
|
15
|
+
export function QuickActionsRow({ items, label, link, onRun, }) {
|
|
16
|
+
return (_jsx(Flex, { role: "group", "aria-label": label, "data-testid": "launcher-quick-actions", gap: "6px", minW: 0, flexWrap: "wrap", px: "4px", children: items.slice(0, QUICK_ACTION_LIMIT).map((item) => {
|
|
17
|
+
const Icon = item.icon;
|
|
18
|
+
const common = {
|
|
19
|
+
variant: "outline",
|
|
20
|
+
colorScheme: "gray",
|
|
21
|
+
size: "sm",
|
|
22
|
+
borderRadius: "full",
|
|
23
|
+
flexShrink: 0,
|
|
24
|
+
label: item.label,
|
|
25
|
+
leftIcon: Icon ? (_jsx(Icon, { size: 14, strokeWidth: 2, "aria-hidden": true })) : undefined,
|
|
26
|
+
};
|
|
27
|
+
return item.href && !item.run ? (_jsx(Button, { ...common, as: link.linkAs, href: item.href, onClick: link.onPlainClick }, item.id)) : (_jsx(Button, { ...common, onClick: () => onRun(item) }, item.id));
|
|
28
|
+
}) }));
|
|
29
|
+
}
|
|
30
|
+
const useCardSurface = (isActive) => {
|
|
31
|
+
const { colors } = useCustomTheme();
|
|
32
|
+
const { edge, fill } = activeRowStyle(colors);
|
|
33
|
+
return {
|
|
34
|
+
position: "relative",
|
|
35
|
+
borderRadius: "xl",
|
|
36
|
+
borderWidth: "1px",
|
|
37
|
+
borderColor: isActive ? edge : colors.boxborder[200],
|
|
38
|
+
bg: isActive ? fill : colors.surfaceRaised,
|
|
39
|
+
transition: transitionFor(["background-color", "border-color"]),
|
|
40
|
+
sx: reducedMotionSx(),
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
function IconChip({ icon, size }) {
|
|
44
|
+
const { colors } = useCustomTheme();
|
|
45
|
+
const Icon = icon !== null && icon !== void 0 ? icon : SearchIcon;
|
|
46
|
+
return (_jsx(Flex, { boxSize: `${size}px`, borderRadius: "10px", bg: colors.primary.opacity[8], color: colors.accentText, align: "center", justify: "center", flexShrink: 0, children: _jsx(Icon, { size: Math.round(size / 2), strokeWidth: 1.8, "aria-hidden": true }) }));
|
|
47
|
+
}
|
|
48
|
+
function CardAnchor({ item, link }) {
|
|
49
|
+
if (!item.href)
|
|
50
|
+
return null;
|
|
51
|
+
return (_jsx(Box, { as: link.linkAs, href: item.href, tabIndex: -1, "aria-hidden": true, onClick: link.onPlainClick, position: "absolute", inset: 0, borderRadius: "xl" }));
|
|
52
|
+
}
|
|
53
|
+
export function TopHitCard({ item, query, isActive, optionId, link, labels, onOpen, onHover, describedBy, }) {
|
|
54
|
+
const { colors } = useCustomTheme();
|
|
55
|
+
const surface = useCardSurface(isActive);
|
|
56
|
+
const meta = [item.context, item.hint].filter(Boolean).join(" · ");
|
|
57
|
+
return (_jsxs(Flex, { role: "presentation", "data-group": true, "data-reveal": isActive, direction: "column", gap: "10px", p: "12px", onMouseMove: onHover, ...surface, children: [_jsxs(Flex, { id: optionId, role: "option", "aria-selected": isActive, "aria-describedby": describedBy, "data-active": isActive, align: "center", gap: "12px", minW: 0, cursor: "pointer", onClick: () => {
|
|
58
|
+
if (!item.href)
|
|
59
|
+
onOpen();
|
|
60
|
+
}, children: [_jsx(IconChip, { icon: item.icon, size: 40 }), _jsxs(Flex, { direction: "column", flex: 1, minW: 0, gap: "2px", children: [_jsx(Text, { fontSize: "md", lineHeight: "22px", fontWeight: 600, color: colors.text[900], noOfLines: 1, children: _jsx(Highlight, { text: item.label, query: query }) }), meta ? (_jsx(Text, { fontSize: "12.5px", color: colors.textMuted, noOfLines: 1, children: meta })) : null, item.keywordHint ? (_jsxs(Text, { fontSize: "xs", color: colors.textMuted, noOfLines: 1, children: [`${labels.matchedOn} “`, _jsx(Highlight, { text: item.keywordHint, query: query }), "\u201D"] })) : null] }), _jsx(CountBadge, { value: item.badge }), _jsx(Box, { "aria-hidden": true, flexShrink: 0, sx: HIDE_ON_TOUCH, children: _jsx(KbdShortcut, { shortcut: "enter", size: "sm" }) })] }), _jsx(CardAnchor, { item: item, link: link })] }));
|
|
61
|
+
}
|
|
62
|
+
export function AnswerCard({ item, isActive, optionId, link, labels, onOpen, onHover, describedBy, }) {
|
|
63
|
+
var _a, _b;
|
|
64
|
+
const { colors } = useCustomTheme();
|
|
65
|
+
const surface = useCardSurface(isActive);
|
|
66
|
+
const answer = (_a = item.answer) !== null && _a !== void 0 ? _a : {
|
|
67
|
+
label: (_b = item.context) !== null && _b !== void 0 ? _b : "",
|
|
68
|
+
value: item.label,
|
|
69
|
+
};
|
|
70
|
+
const acts = Boolean(item.href || item.run);
|
|
71
|
+
return (_jsxs(Flex, { role: "presentation", "data-group": true, p: "12px 14px", onMouseMove: onHover, ...surface, children: [_jsxs(Flex, { id: optionId, role: "option", "aria-selected": isActive, "aria-describedby": describedBy, "data-active": isActive, align: "center", gap: "12px", flex: 1, minW: 0, cursor: "pointer", onClick: () => {
|
|
72
|
+
if (!item.href)
|
|
73
|
+
onOpen();
|
|
74
|
+
}, children: [_jsx(IconChip, { icon: item.icon, size: 36 }), _jsxs(Flex, { direction: "column", flex: 1, minW: 0, gap: "1px", children: [answer.label ? (_jsx(Text, { fontSize: "xs-minus", fontWeight: 600, letterSpacing: "0.06em", textTransform: "uppercase", color: colors.textMuted, noOfLines: 1, children: answer.label })) : null, _jsx(Text, { id: `${optionId}-value`, fontSize: "lg", lineHeight: "24px", fontWeight: 600, color: colors.text[900], noOfLines: 2, children: answer.value }), answer.hint ? (_jsx(Text, { fontSize: "xs", color: colors.textMuted, noOfLines: 1, children: answer.hint })) : null] }), _jsxs(Flex, { "aria-hidden": true, align: "center", gap: "6px", flexShrink: 0, fontSize: "11.5px", color: colors.textMuted, sx: HIDE_ON_TOUCH, children: [_jsx(KbdShortcut, { shortcut: "enter", size: "xs" }), acts ? labels.hintOpen : labels.hintCopy] })] }), _jsx(CardAnchor, { item: item, link: link })] }));
|
|
75
|
+
}
|
|
76
|
+
export function ShowMoreOption({ count, isActive, optionId, label, onExpand, onHover, }) {
|
|
77
|
+
const { colors } = useCustomTheme();
|
|
78
|
+
const isTouch = useTouchDensity();
|
|
79
|
+
return (_jsxs(Flex, { id: optionId, role: "option", "aria-selected": isActive, "data-active": isActive, align: "center", gap: "6px", minH: isTouch ? "44px" : "30px", px: "8px", mt: "2px", borderRadius: "lg", cursor: "pointer", fontSize: "12.5px", fontWeight: 500, color: colors.accentText, onClick: onExpand, onMouseMove: onHover, _hover: { bg: listRowBackgrounds(colors).active }, sx: isActive ? activeRowSurface(colors) : undefined, children: [_jsx(ChevronDown, { size: 14, strokeWidth: 2, "aria-hidden": true }), label(count)] }));
|
|
80
|
+
}
|
|
81
|
+
export const describeFacts = (facts) => facts.map((f) => `${f.label}: ${f.value}`).join("; ");
|
|
82
|
+
export function FactList({ facts, labels, layout, onCopied, onCopyBlocked, }) {
|
|
83
|
+
const { colors } = useCustomTheme();
|
|
84
|
+
const values = useRef([]);
|
|
85
|
+
const isPane = layout === "pane";
|
|
86
|
+
return (_jsx(Box, { as: "dl", m: 0, display: "grid", gridTemplateColumns: isPane ? "minmax(0, 1fr)" : "repeat(auto-fill, minmax(11rem, 1fr))", gap: isPane ? "12px" : "6px 16px", children: facts.map((fact, i) => (_jsxs(Box, { minW: 0, children: [_jsx(Text, { as: "dt", fontSize: "11.5px", lineHeight: "16px", color: colors.textMuted, children: fact.label }), _jsxs(Flex, { as: "dd", m: 0, align: "center", gap: "2px", minW: 0, children: [_jsx(Text, { ref: (node) => {
|
|
87
|
+
values.current[i] = node;
|
|
88
|
+
}, fontSize: isPane ? "13.5px" : "12.5px", lineHeight: "20px", color: colors.text[900], wordBreak: "break-word", minW: 0, children: fact.value }), fact.copyable ? (_jsx(CopyButton, { value: fact.value, tooltip: labels.copy(fact.label), copiedLabel: labels.copied, size: isPane ? "xs" : "sm", iconSize: 14, onCopy: onCopied, onCopyError: () => onCopyBlocked(values.current[i]) })) : null] })] }, `${fact.label}-${i}`))) }));
|
|
89
|
+
}
|
|
90
|
+
export function PreviewPane({ item, placement, width, labels, onCopied, onCopyBlocked, }) {
|
|
91
|
+
var _a, _b, _c, _d, _e;
|
|
92
|
+
const { colors } = useCustomTheme();
|
|
93
|
+
const isDock = placement === "dock";
|
|
94
|
+
return (_jsx(Flex, { as: "aside", "aria-label": labels.preview, "data-testid": "launcher-preview", "data-scroll-region": true, direction: "column", gap: isDock ? "10px" : "16px", w: isDock ? "100%" : width, maxH: isDock ? "min(45vh, 20rem)" : undefined, flexShrink: 0, px: "18px", pt: isDock ? "12px" : "16px", pb: isDock ? "12px" : "16px", bg: colors.backgroundColor.subtle, ...(isDock ? { borderTopWidth: "1px" } : { borderLeftWidth: "1px" }), borderColor: colors.boxborder[200], overflowY: "auto", children: item ? (_jsxs(_Fragment, { children: [_jsxs(Flex, { align: "center", gap: "10px", minW: 0, children: [_jsx(IconChip, { icon: item.icon, size: isDock ? 28 : 36 }), _jsxs(Box, { minW: 0, children: [_jsx(Text, { fontSize: isDock ? "14px" : "15px", fontWeight: 600, color: colors.text[900], noOfLines: isDock ? 1 : 2, children: (_b = (_a = item.answer) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : item.label }), item.context || item.answer ? (_jsx(Text, { fontSize: "xs", color: colors.textMuted, noOfLines: 1, children: (_d = (_c = item.answer) === null || _c === void 0 ? void 0 : _c.label) !== null && _d !== void 0 ? _d : item.context })) : null] })] }), ((_e = item.facts) === null || _e === void 0 ? void 0 : _e.length) ? (_jsx(FactList, { facts: item.facts, labels: labels, layout: isDock ? "inline" : "pane", onCopied: onCopied, onCopyBlocked: onCopyBlocked }, item.id)) : (_jsxs(Flex, { "aria-hidden": true, align: "center", gap: "6px", fontSize: "xs", color: colors.textMuted, children: [_jsx(KbdShortcut, { shortcut: "enter", size: "xs" }), labels.hintOpen] }))] })) : null }));
|
|
95
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Box,
|
|
2
|
+
import { Box, Spinner, Th, Tr } from "@chakra-ui/react";
|
|
3
3
|
import { memo, useState } from "react";
|
|
4
4
|
import { calculateLeftOffset } from "../../../Utils/table.js";
|
|
5
5
|
import { useCustomTheme } from "../../../Theme/useCustomTheme.js";
|
|
@@ -8,8 +8,10 @@ import TableFilters from "../filters/TableFilters.js";
|
|
|
8
8
|
import { columnLabelText } from "../columnLabel.js";
|
|
9
9
|
import { ChevronDown, ChevronsUpDown, ChevronUp } from "lucide-react";
|
|
10
10
|
import CheckBox from "../../Checkbox/Checkbox.js";
|
|
11
|
+
import Skeletons from "../../Skeletons/Skeletons.js";
|
|
11
12
|
import { useTableVariant } from "../variants/TableVariantContext.js";
|
|
12
13
|
import { transitionFor } from "../../../Theme/tokens/primitives/motion.js";
|
|
14
|
+
import { BORDER_WIDTHS } from "../../../Theme/tokens/primitives/borderWidths.js";
|
|
13
15
|
import { reducedMotionSx } from "../../../Theme/chakra/motion.js";
|
|
14
16
|
import { focusVisibleRing } from "../../../Theme/chakra/focusRing.styles.js";
|
|
15
17
|
import { DETAIL_SLOT_PX, RowDetailHeaderCell } from "./rowDetail.js";
|
|
@@ -32,7 +34,7 @@ const SelectAllCell = memo(({ slotRef, freezedTextColor, noBorders, isSelecting,
|
|
|
32
34
|
});
|
|
33
35
|
const LoadingHeaderCells = ({ noBorders }) => {
|
|
34
36
|
const variant = useTableVariant();
|
|
35
|
-
return (_jsx(_Fragment, { children: Array.from({ length: LOADING_PLACEHOLDER_COLUMNS }, (_, i) => (_jsx(Th, { scope: "col", "aria-hidden": "true", px: 3, backgroundColor: variant.header.bg, borderBottom: noBorders ? "none" :
|
|
37
|
+
return (_jsx(_Fragment, { children: Array.from({ length: LOADING_PLACEHOLDER_COLUMNS }, (_, i) => (_jsx(Th, { scope: "col", "aria-hidden": "true", px: 3, backgroundColor: variant.header.bg, borderBottom: noBorders ? "none" : `${BORDER_WIDTHS.hairline} solid ${variant.header.ruleColor}`, children: _jsx(Skeletons, { type: "skeleton", height: "0.75rem", width: i % 2 ? "50%" : "70%", borderRadius: "full" }) }, i))) }));
|
|
36
38
|
};
|
|
37
39
|
const ContentSpacerCell = memo(({ noBorders }) => {
|
|
38
40
|
const variant = useTableVariant();
|