pixelize-design-library 2.4.2-beta.63 → 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 +130 -8
- package/COMPONENT-GOTCHAS.md +63 -7
- package/DESIGN-GUIDE.md +8 -3
- package/dist/Components/Button/Button.styles.js +7 -2
- 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/Dropdown/DropDown.js +2 -9
- package/dist/Components/FilePreview/FilePreview.js +2 -1
- package/dist/Components/FileUploader/FileUploader.js +4 -0
- package/dist/Components/FileUploader/FileUploaderProps.d.ts +1 -1
- 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/Input/TextInput.styles.js +10 -6
- package/dist/Components/KanbanBoard/KanbanBoard.js +6 -5
- 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/NavigationBar/NavigationBar.js +2 -1
- package/dist/Components/OrgSwitcher/OrgSwitcher.js +2 -4
- package/dist/Components/ProfilePhotoViewer/ProfilePhotoViewer.js +6 -5
- package/dist/Components/ScrollToTop/ScrollToTop.js +4 -3
- package/dist/Components/SearchSelect/SearchSelect.js +4 -16
- package/dist/Components/Select/SelectPopup.js +1 -3
- package/dist/Components/Select/optionRowInk.d.ts +12 -0
- package/dist/Components/Select/optionRowInk.js +10 -1
- package/dist/Components/SelectSearch/SelectSearch.js +1 -10
- package/dist/Components/Table/Table.js +3 -2
- package/dist/Components/Table/TableProps.d.ts +1 -1
- package/dist/Components/Table/components/TableBody.js +9 -1
- package/dist/Components/Table/components/TableHeader.js +9 -2
- package/dist/Components/Table/components/TableLoading.d.ts +4 -1
- package/dist/Components/Table/components/TableLoading.js +13 -6
- package/dist/Components/Table/filters/CompactSelect.js +1 -10
- package/dist/Components/Table/variants/TableVariantContext.d.ts +1 -1
- package/dist/Components/Table/variants/TableVariantContext.js +3 -3
- 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/WorkspaceTrayPreview.js +4 -0
- package/dist/Components/WorkspaceWindow/WorkspaceWindow.d.ts +2 -2
- package/dist/Components/WorkspaceWindow/WorkspaceWindow.js +26 -18
- package/dist/Components/WorkspaceWindow/WorkspaceWindowProps.d.ts +38 -8
- package/dist/Theme/chakra/Menu.styles.d.ts +8 -0
- package/dist/Theme/chakra/Menu.styles.js +7 -0
- package/dist/Theme/chakra/componentStyles.d.ts +8 -0
- package/dist/Theme/chakra/componentStyles.js +2 -2
- package/dist/Theme/chakra/ringShadow.d.ts +3 -0
- package/dist/Theme/chakra/ringShadow.js +7 -0
- package/dist/Theme/tokens/builders/accentText.d.ts +2 -0
- package/dist/Theme/tokens/builders/accentText.js +7 -1
- package/dist/Theme/tokens/builders/buildDarkPalette.d.ts +1 -1
- package/dist/Theme/tokens/builders/buildDarkPalette.js +5 -5
- package/dist/Theme/tokens/builders/filledSurface.d.ts +5 -1
- package/dist/Theme/tokens/builders/filledSurface.js +17 -4
- package/dist/Theme/tokens/builders/planeStep.d.ts +1 -0
- package/dist/Theme/tokens/builders/planeStep.js +5 -4
- package/dist/Utils/table.js +2 -2
- package/dist/esm/Components/Button/Button.styles.js +8 -3
- package/dist/esm/Components/CopyButton/CopyButton.js +2 -2
- package/dist/esm/Components/Drawer/Drawer.js +6 -2
- package/dist/esm/Components/Dropdown/DropDown.js +2 -9
- package/dist/esm/Components/FilePreview/FilePreview.js +3 -2
- package/dist/esm/Components/FileUploader/FileUploader.js +4 -0
- package/dist/esm/Components/FormDrawer/FormDrawer.js +3 -3
- package/dist/esm/Components/Input/TextInput.styles.js +10 -6
- package/dist/esm/Components/KanbanBoard/KanbanBoard.js +6 -5
- 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/NavigationBar/NavigationBar.js +2 -1
- package/dist/esm/Components/OrgSwitcher/OrgSwitcher.js +2 -4
- package/dist/esm/Components/ProfilePhotoViewer/ProfilePhotoViewer.js +7 -6
- package/dist/esm/Components/ScrollToTop/ScrollToTop.js +5 -4
- package/dist/esm/Components/SearchSelect/SearchSelect.js +6 -18
- package/dist/esm/Components/Select/SelectPopup.js +2 -4
- package/dist/esm/Components/Select/optionRowInk.js +7 -0
- package/dist/esm/Components/SelectSearch/SelectSearch.js +2 -11
- package/dist/esm/Components/Table/Table.js +4 -3
- package/dist/esm/Components/Table/components/TableBody.js +10 -2
- package/dist/esm/Components/Table/components/TableHeader.js +10 -3
- package/dist/esm/Components/Table/components/TableLoading.js +10 -6
- package/dist/esm/Components/Table/filters/CompactSelect.js +2 -11
- package/dist/esm/Components/Table/variants/TableVariantContext.js +3 -3
- package/dist/esm/Components/WizardDialog/WizardDialog.js +15 -3
- package/dist/esm/Components/WorkspaceWindow/WorkspaceTrayPreview.js +4 -0
- package/dist/esm/Components/WorkspaceWindow/WorkspaceWindow.js +28 -20
- package/dist/esm/Theme/chakra/Menu.styles.js +7 -0
- package/dist/esm/Theme/chakra/componentStyles.js +2 -2
- package/dist/esm/Theme/chakra/ringShadow.js +3 -0
- package/dist/esm/Theme/tokens/builders/accentText.js +5 -0
- package/dist/esm/Theme/tokens/builders/buildDarkPalette.js +5 -5
- package/dist/esm/Theme/tokens/builders/filledSurface.js +18 -5
- package/dist/esm/Theme/tokens/builders/planeStep.js +3 -3
- package/dist/esm/Utils/table.js +2 -2
- package/dist/esm/index.js +6 -5
- package/dist/index.d.ts +10 -8
- package/dist/index.js +14 -6
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ElementType, KeyboardEvent, MouseEvent, ReactNode } from "react";
|
|
2
2
|
import type { AppOption } from "../AppSwitcher/AppSwitcherProps";
|
|
3
|
+
import type { LauncherSynonyms } from "./engine";
|
|
3
4
|
/** Rendered as `<Icon size={16} aria-hidden />`, so any lucide-compatible component works. */
|
|
4
5
|
export type LauncherIcon = ElementType;
|
|
5
6
|
/** One destination the launcher can open; screens are what pins, Frequent and Recent refer to. */
|
|
@@ -33,6 +34,19 @@ export type LauncherSection = {
|
|
|
33
34
|
label: string;
|
|
34
35
|
modules: readonly LauncherModule[];
|
|
35
36
|
};
|
|
37
|
+
/** One label/value pair of a result, e.g. PAN: ABCDE1234F; plain text only, never HTML. */
|
|
38
|
+
export type LauncherFact = {
|
|
39
|
+
label: string;
|
|
40
|
+
value: string;
|
|
41
|
+
/** Adds a Copy button; a blocked clipboard selects the value instead. */
|
|
42
|
+
copyable?: boolean;
|
|
43
|
+
};
|
|
44
|
+
/** Direct-answer payload, e.g. label "Next holiday", value "24 Oct — Diwali". */
|
|
45
|
+
export type LauncherAnswer = {
|
|
46
|
+
label: string;
|
|
47
|
+
value: string;
|
|
48
|
+
hint?: string;
|
|
49
|
+
};
|
|
36
50
|
/** A search row that is not a screen: an action (`run`) or an entity result (`href`). */
|
|
37
51
|
export type LauncherSearchItem = {
|
|
38
52
|
id: string;
|
|
@@ -40,18 +54,35 @@ export type LauncherSearchItem = {
|
|
|
40
54
|
/** Second line, e.g. the module a result belongs to. */
|
|
41
55
|
context?: string;
|
|
42
56
|
icon?: LauncherIcon;
|
|
43
|
-
/** Extra terms the query may match; never rendered. */
|
|
57
|
+
/** Extra terms the query may match; never rendered. Separate phrases with commas. */
|
|
44
58
|
keywords?: string;
|
|
45
59
|
hint?: string;
|
|
46
60
|
href?: string;
|
|
47
61
|
run?: () => void;
|
|
62
|
+
/** Shown in the Preview pane for the active result; searchable in spotlight. */
|
|
63
|
+
facts?: readonly LauncherFact[];
|
|
64
|
+
/** Read only for items passed in `answers`: the card's label, value and hint. */
|
|
65
|
+
answer?: LauncherAnswer;
|
|
48
66
|
};
|
|
49
67
|
/** A titled batch of remote results, rendered after Screens in the order given. */
|
|
50
68
|
export type LauncherResultGroup = {
|
|
51
69
|
id: string;
|
|
52
70
|
label: string;
|
|
53
71
|
items: readonly LauncherSearchItem[];
|
|
72
|
+
/** Scope chip this group belongs to; defaults to `id`. */
|
|
73
|
+
scope?: string;
|
|
74
|
+
/** Launcher filters and ranks these items itself; by default they show as given. */
|
|
75
|
+
matchLocally?: boolean;
|
|
76
|
+
};
|
|
77
|
+
/** A filter chip; "screens" and "actions" are built in, others match `LauncherResultGroup.scope`. */
|
|
78
|
+
export type LauncherScope = {
|
|
79
|
+
id: string;
|
|
80
|
+
label: string;
|
|
81
|
+
/** Field placeholder while this chip is selected, e.g. "Search legal name, PAN, GSTIN…"; the field's accessible name stays `labels.searchPlaceholder`. */
|
|
82
|
+
placeholder?: string;
|
|
54
83
|
};
|
|
84
|
+
/** "classic": registry order, substring matching; "spotlight": relevance ranking plus a Top hit. */
|
|
85
|
+
export type LauncherSearchMode = "classic" | "spotlight";
|
|
55
86
|
/** Every visible string and accessible name; pass any subset, the rest fall back to English. */
|
|
56
87
|
export type LauncherLabels = {
|
|
57
88
|
/** Accessible name of the dialog and the results list; no longer drawn as a header pill. */
|
|
@@ -86,7 +117,7 @@ export type LauncherLabels = {
|
|
|
86
117
|
/** Footer hint after the ESC key cap. */
|
|
87
118
|
hintClose: string;
|
|
88
119
|
/** @deprecated No longer rendered: the launcher is already open, so the open chord is not shown. */
|
|
89
|
-
hintOpenMenu
|
|
120
|
+
hintOpenMenu?: string;
|
|
90
121
|
/** Footer hint after the mod key cap for a modified click, e.g. "+ click for new tab". */
|
|
91
122
|
hintClickNewTab: string;
|
|
92
123
|
/** Tooltip of the arrow link beside a row. */
|
|
@@ -121,6 +152,36 @@ export type LauncherLabels = {
|
|
|
121
152
|
pinTip: string;
|
|
122
153
|
/** Tooltip on a pinned star. */
|
|
123
154
|
unpinTip: string;
|
|
155
|
+
/** Heading of the best-match card in spotlight mode. */
|
|
156
|
+
topHit?: string;
|
|
157
|
+
/** Prefix of the Top hit line naming the keyword or fact that matched. */
|
|
158
|
+
matchedOn?: string;
|
|
159
|
+
/** Name of the answers group. */
|
|
160
|
+
answers?: string;
|
|
161
|
+
/** Footer-style hint on an answer card that copies its value. */
|
|
162
|
+
hintCopy?: string;
|
|
163
|
+
/** Name of the scope chip group. */
|
|
164
|
+
scopes?: string;
|
|
165
|
+
/** Chip prepended to `scopes` when the list has no "all". */
|
|
166
|
+
scopeAll?: string;
|
|
167
|
+
/** Polite announcement when the scope changes. */
|
|
168
|
+
scopeChanged?: (label: string) => string;
|
|
169
|
+
/** Accessible name of a chip while a query is active, e.g. "People, 3 results". */
|
|
170
|
+
scopeCount?: (label: string, count: number) => string;
|
|
171
|
+
/** Shown when a chip is picked with an empty query and its scope has nothing to list yet. */
|
|
172
|
+
scopeEmpty?: (label: string) => string;
|
|
173
|
+
/** Name of the quick actions row. */
|
|
174
|
+
quickActions?: string;
|
|
175
|
+
/** Option that expands a collapsed result group. */
|
|
176
|
+
showMore?: (count: number) => string;
|
|
177
|
+
/** Name of the preview pane. */
|
|
178
|
+
preview?: string;
|
|
179
|
+
/** Accessible name and tooltip of a fact's Copy button. */
|
|
180
|
+
copy?: (label: string) => string;
|
|
181
|
+
/** Polite announcement after a copy. */
|
|
182
|
+
copied?: string;
|
|
183
|
+
/** Announcement when the clipboard is blocked and the value was selected instead. */
|
|
184
|
+
copySelected?: string;
|
|
124
185
|
};
|
|
125
186
|
/** Passed to `onNavigate`; `newTab` is true for mod+Enter. */
|
|
126
187
|
export type LauncherNavigateOptions = {
|
|
@@ -165,7 +226,7 @@ export interface NavLauncherProps {
|
|
|
165
226
|
openShortcutHint?: string;
|
|
166
227
|
/** Replaces the modifier cap of the mod+Enter hint; defaults to "⌘" on Apple platforms, "Ctrl" elsewhere. */
|
|
167
228
|
modKeyLabel?: string;
|
|
168
|
-
/** Rendered
|
|
229
|
+
/** Rendered just before the close button, e.g. a help or customise icon button; the dialog name stays `labels.title` whatever is here. */
|
|
169
230
|
headerActions?: ReactNode;
|
|
170
231
|
/** Panel width at `lg`+; always full width below `lg`. Defaults to "1080px". */
|
|
171
232
|
maxWidth?: string;
|
|
@@ -178,6 +239,21 @@ export interface NavLauncherProps {
|
|
|
178
239
|
onAppSelect?: (app: AppOption, event: MouseEvent | KeyboardEvent) => void;
|
|
179
240
|
/** Default "apps-on-compact": the section shows only below `md`, where the 9-dot is usually hidden. */
|
|
180
241
|
appsVisibility?: "always" | "apps-on-compact";
|
|
242
|
+
/** Default "classic"; "spotlight" adds typo-tolerant ranking, synonyms, usage boosts and a Top hit. */
|
|
243
|
+
searchMode?: LauncherSearchMode;
|
|
244
|
+
/** Spotlight only: equivalent terms, both directions, e.g. `{ pf: ["provident fund"] }`. */
|
|
245
|
+
synonyms?: LauncherSynonyms;
|
|
246
|
+
/** Filter chips in the header row (their own row below `md`), always shown, with live counts while a query is active; "all" is prepended unless listed. A chip other than All or Screens picked with an empty query lists that scope's items. */
|
|
247
|
+
scopes?: readonly LauncherScope[];
|
|
248
|
+
/** Controlled scope id; omit for uncontrolled ("all", reset on close). */
|
|
249
|
+
activeScope?: string;
|
|
250
|
+
onScopeChange?: (scope: string) => void;
|
|
251
|
+
/** Up to 5 pill buttons at the top of the menu while the query is empty and the scope is All; each runs or links like an action. */
|
|
252
|
+
quickActions?: readonly LauncherSearchItem[];
|
|
253
|
+
/** Direct answers, matched against the query and shown above every result. */
|
|
254
|
+
answers?: readonly LauncherSearchItem[];
|
|
255
|
+
/** Rows per group before "Show N more"; default 5 in spotlight, unlimited in classic. */
|
|
256
|
+
groupLimit?: number;
|
|
181
257
|
"data-testid"?: string;
|
|
182
258
|
}
|
|
183
259
|
/** Props for the star pin toggle used on launcher rows and reusable on a landing page; shows only while its row is hovered or focused. */
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type ScopeChip = {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
count?: number;
|
|
5
|
+
};
|
|
6
|
+
type ScopeBarProps = {
|
|
7
|
+
scopes: readonly ScopeChip[];
|
|
8
|
+
value: string;
|
|
9
|
+
onChange: (scope: string) => void;
|
|
10
|
+
label: string;
|
|
11
|
+
countLabel: (label: string, count: number) => string;
|
|
12
|
+
};
|
|
13
|
+
export declare function ScopeBar({ scopes, value, onChange, label, countLabel, }: ScopeBarProps): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ScopeBar = ScopeBar;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("@chakra-ui/react");
|
|
6
|
+
const lucide_react_1 = require("lucide-react");
|
|
7
|
+
const react_2 = require("react");
|
|
8
|
+
const focusRing_styles_1 = require("../../Theme/chakra/focusRing.styles");
|
|
9
|
+
const motion_1 = require("../../Theme/chakra/motion");
|
|
10
|
+
const outlineRung_1 = require("../../Theme/tokens/builders/outlineRung");
|
|
11
|
+
const rowStates_1 = require("../../Theme/tokens/builders/rowStates");
|
|
12
|
+
const motion_2 = require("../../Theme/tokens/primitives/motion");
|
|
13
|
+
const useCustomTheme_1 = require("../../Theme/useCustomTheme");
|
|
14
|
+
const EDGE = 36;
|
|
15
|
+
const MAX_COUNT = 99;
|
|
16
|
+
const STEPS = {
|
|
17
|
+
ArrowRight: 1,
|
|
18
|
+
ArrowDown: 1,
|
|
19
|
+
ArrowLeft: -1,
|
|
20
|
+
ArrowUp: -1,
|
|
21
|
+
};
|
|
22
|
+
const HIDDEN_SCROLLBAR = {
|
|
23
|
+
scrollbarWidth: "none",
|
|
24
|
+
"&::-webkit-scrollbar": { display: "none" },
|
|
25
|
+
};
|
|
26
|
+
const NO_EDGES = { start: false, end: false };
|
|
27
|
+
const fadeMask = ({ start, end }) => start || end
|
|
28
|
+
? `linear-gradient(to right, ${start ? "transparent" : "black"}, black ${EDGE}px, black calc(100% - ${EDGE}px), ${end ? "transparent" : "black"})`
|
|
29
|
+
: "none";
|
|
30
|
+
function PageArrow({ side, onPage, }) {
|
|
31
|
+
const { colors } = (0, useCustomTheme_1.useCustomTheme)();
|
|
32
|
+
const Icon = side === "start" ? lucide_react_1.ChevronLeft : lucide_react_1.ChevronRight;
|
|
33
|
+
return ((0, jsx_runtime_1.jsx)(react_1.Box, { as: "button", type: "button", tabIndex: -1, "aria-hidden": true, "data-testid": `launcher-scopes-${side}`, onMouseDown: (event) => event.preventDefault(), onClick: onPage, position: "absolute", top: "50%", left: side === "start" ? 0 : undefined, right: side === "end" ? 0 : undefined, transform: "translateY(-50%)", display: "inline-flex", alignItems: "center", justifyContent: "center", boxSize: "28px", borderRadius: "full", borderWidth: "1px", borderColor: colors.boxborder[200], bg: colors.surfaceRaised, boxShadow: `0 1px 3px ${colors.boxShadow.default}`, color: colors.textMuted, cursor: "pointer", _hover: { color: colors.text[900] }, sx: { "@media (hover: none)": { display: "none" } }, children: (0, jsx_runtime_1.jsx)(Icon, { size: 16, "aria-hidden": true }) }));
|
|
34
|
+
}
|
|
35
|
+
function ScopeBar({ scopes, value, onChange, label, countLabel, }) {
|
|
36
|
+
const { colors } = (0, useCustomTheme_1.useCustomTheme)();
|
|
37
|
+
const stripRef = (0, react_2.useRef)(null);
|
|
38
|
+
const chipRefs = (0, react_2.useRef)([]);
|
|
39
|
+
const [edges, setEdges] = (0, react_2.useState)(NO_EDGES);
|
|
40
|
+
const reduced = (0, motion_1.usePrefersReducedMotion)();
|
|
41
|
+
const selected = Math.max(0, scopes.findIndex((scope) => scope.id === value));
|
|
42
|
+
const layoutKey = scopes.map((s) => { var _a; return `${s.id}:${(_a = s.count) !== null && _a !== void 0 ? _a : ""}`; }).join("|");
|
|
43
|
+
const edge = (0, outlineRung_1.liftToContrast)(colors.primary[500], colors.surfaceRaised, outlineRung_1.AA_NON_TEXT);
|
|
44
|
+
const measure = (0, react_2.useCallback)(() => {
|
|
45
|
+
const strip = stripRef.current;
|
|
46
|
+
if (!strip)
|
|
47
|
+
return;
|
|
48
|
+
const start = strip.scrollLeft > 1;
|
|
49
|
+
const end = strip.scrollLeft + strip.clientWidth < strip.scrollWidth - 1;
|
|
50
|
+
setEdges((prev) => prev.start === start && prev.end === end ? prev : { start, end });
|
|
51
|
+
}, []);
|
|
52
|
+
(0, react_2.useEffect)(() => {
|
|
53
|
+
const strip = stripRef.current;
|
|
54
|
+
if (!strip || typeof ResizeObserver === "undefined")
|
|
55
|
+
return undefined;
|
|
56
|
+
const observer = new ResizeObserver(measure);
|
|
57
|
+
observer.observe(strip);
|
|
58
|
+
if (strip.firstElementChild)
|
|
59
|
+
observer.observe(strip.firstElementChild);
|
|
60
|
+
return () => observer.disconnect();
|
|
61
|
+
}, [measure]);
|
|
62
|
+
(0, react_2.useEffect)(() => {
|
|
63
|
+
const strip = stripRef.current;
|
|
64
|
+
const chip = chipRefs.current[selected];
|
|
65
|
+
if (strip && chip) {
|
|
66
|
+
const from = chip.offsetLeft - EDGE;
|
|
67
|
+
const to = chip.offsetLeft + chip.offsetWidth + EDGE - strip.clientWidth;
|
|
68
|
+
if (from < strip.scrollLeft)
|
|
69
|
+
strip.scrollLeft = Math.max(0, from);
|
|
70
|
+
else if (to > strip.scrollLeft)
|
|
71
|
+
strip.scrollLeft = to;
|
|
72
|
+
}
|
|
73
|
+
measure();
|
|
74
|
+
}, [selected, layoutKey, measure]);
|
|
75
|
+
const onKeyDown = (event) => {
|
|
76
|
+
var _a;
|
|
77
|
+
if (event.altKey || event.ctrlKey || event.metaKey)
|
|
78
|
+
return;
|
|
79
|
+
const step = STEPS[event.key];
|
|
80
|
+
const last = scopes.length - 1;
|
|
81
|
+
const next = step !== undefined
|
|
82
|
+
? (selected + step + scopes.length) % scopes.length
|
|
83
|
+
: event.key === "Home"
|
|
84
|
+
? 0
|
|
85
|
+
: event.key === "End"
|
|
86
|
+
? last
|
|
87
|
+
: -1;
|
|
88
|
+
if (next < 0)
|
|
89
|
+
return;
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
(_a = chipRefs.current[next]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
92
|
+
onChange(scopes[next].id);
|
|
93
|
+
};
|
|
94
|
+
const onWheel = (event) => {
|
|
95
|
+
const strip = event.currentTarget;
|
|
96
|
+
if (Math.abs(event.deltaY) <= Math.abs(event.deltaX))
|
|
97
|
+
return;
|
|
98
|
+
if (strip.scrollWidth > strip.clientWidth)
|
|
99
|
+
strip.scrollLeft += event.deltaY;
|
|
100
|
+
};
|
|
101
|
+
const page = (direction) => {
|
|
102
|
+
const strip = stripRef.current;
|
|
103
|
+
if (!strip)
|
|
104
|
+
return;
|
|
105
|
+
const step = Math.max(strip.clientWidth - EDGE * 2, EDGE);
|
|
106
|
+
const max = Math.max(0, strip.scrollWidth - strip.clientWidth);
|
|
107
|
+
const left = Math.min(max, Math.max(0, strip.scrollLeft + direction * step));
|
|
108
|
+
if (reduced || typeof strip.scrollTo !== "function")
|
|
109
|
+
strip.scrollLeft = left;
|
|
110
|
+
else
|
|
111
|
+
strip.scrollTo({ left, behavior: "smooth" });
|
|
112
|
+
};
|
|
113
|
+
const mask = fadeMask(edges);
|
|
114
|
+
return ((0, jsx_runtime_1.jsxs)(react_1.Box, { position: "relative", minW: 0, flexShrink: 1, children: [(0, jsx_runtime_1.jsx)(react_1.Box, { ref: stripRef, "data-testid": "launcher-scopes", overflowX: "auto", overflowY: "hidden", onScroll: measure, onWheel: onWheel, sx: { ...HIDDEN_SCROLLBAR, maskImage: mask, WebkitMaskImage: mask }, children: (0, jsx_runtime_1.jsx)(react_1.Flex, { role: "radiogroup", "aria-label": label, onKeyDown: onKeyDown, gap: "6px", w: "max-content", p: "5px", children: scopes.map((scope, index) => {
|
|
115
|
+
var _a, _b;
|
|
116
|
+
const isSelected = index === selected;
|
|
117
|
+
const hasCount = scope.count !== undefined;
|
|
118
|
+
return ((0, jsx_runtime_1.jsxs)(react_1.Box, { as: "button", type: "button", ref: (node) => {
|
|
119
|
+
chipRefs.current[index] = node;
|
|
120
|
+
}, role: "radio", "aria-checked": isSelected, "aria-label": hasCount
|
|
121
|
+
? countLabel(scope.label, (_a = scope.count) !== null && _a !== void 0 ? _a : 0)
|
|
122
|
+
: undefined, tabIndex: isSelected ? 0 : -1, onClick: () => onChange(scope.id), display: "inline-flex", alignItems: "center", gap: "6px", flexShrink: 0, h: "32px", pl: "12px", pr: hasCount ? "6px" : "12px", borderRadius: "full", borderWidth: "1px", borderColor: isSelected ? edge : colors.boxborder[200], bg: isSelected ? colors.primary[50] : colors.surfaceRaised, color: isSelected ? colors.accentText : colors.textMuted, fontSize: "xs-plus", fontWeight: isSelected ? 600 : 500, whiteSpace: "nowrap", cursor: "pointer", transition: (0, motion_2.transitionFor)([
|
|
123
|
+
"background-color",
|
|
124
|
+
"border-color",
|
|
125
|
+
"color",
|
|
126
|
+
]), _hover: isSelected
|
|
127
|
+
? undefined
|
|
128
|
+
: {
|
|
129
|
+
bg: (0, rowStates_1.listRowBackgrounds)(colors).active,
|
|
130
|
+
color: colors.text[900],
|
|
131
|
+
}, sx: { ...(0, focusRing_styles_1.focusVisibleStyles)(colors), ...(0, motion_1.reducedMotionSx)() }, children: [scope.label, hasCount ? ((0, jsx_runtime_1.jsx)(react_1.Box, { as: "span", minW: "20px", h: "20px", px: "6px", borderRadius: "full", bg: isSelected
|
|
132
|
+
? colors.surfaceRaised
|
|
133
|
+
: colors.backgroundColor.subtle, fontSize: "11.5px", fontWeight: 600, display: "inline-flex", alignItems: "center", justifyContent: "center", sx: { fontVariantNumeric: "tabular-nums" }, children: ((_b = scope.count) !== null && _b !== void 0 ? _b : 0) > MAX_COUNT
|
|
134
|
+
? `${MAX_COUNT}+`
|
|
135
|
+
: scope.count })) : null] }, scope.id));
|
|
136
|
+
}) }) }), edges.start ? (0, jsx_runtime_1.jsx)(PageArrow, { side: "start", onPage: () => page(-1) }) : null, edges.end ? (0, jsx_runtime_1.jsx)(PageArrow, { side: "end", onPage: () => page(1) }) : null] }));
|
|
137
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Characters of a string, or the written letters of one (see `letters`). */
|
|
2
|
+
export type Sequence = string | readonly string[];
|
|
3
|
+
/** Damerau-Levenshtein (optimal string alignment) distance; returns `max + 1` once it exceeds `max`. */
|
|
4
|
+
export declare const editDistance: (a: Sequence, b: Sequence, max: number) => number;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.editDistance = void 0;
|
|
4
|
+
/** Damerau-Levenshtein (optimal string alignment) distance; returns `max + 1` once it exceeds `max`. */
|
|
5
|
+
const editDistance = (a, b, max) => {
|
|
6
|
+
if (a === b)
|
|
7
|
+
return 0;
|
|
8
|
+
const la = a.length;
|
|
9
|
+
const lb = b.length;
|
|
10
|
+
if (Math.abs(la - lb) > max)
|
|
11
|
+
return max + 1;
|
|
12
|
+
if (la === 0 || lb === 0)
|
|
13
|
+
return Math.max(la, lb);
|
|
14
|
+
let before = new Int32Array(lb + 1);
|
|
15
|
+
let prev = new Int32Array(lb + 1);
|
|
16
|
+
let cur = new Int32Array(lb + 1);
|
|
17
|
+
for (let j = 0; j <= lb; j += 1)
|
|
18
|
+
prev[j] = j;
|
|
19
|
+
for (let i = 1; i <= la; i += 1) {
|
|
20
|
+
cur[0] = i;
|
|
21
|
+
let rowMin = i;
|
|
22
|
+
for (let j = 1; j <= lb; j += 1) {
|
|
23
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
24
|
+
let value = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + cost);
|
|
25
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
|
|
26
|
+
value = Math.min(value, before[j - 2] + 1);
|
|
27
|
+
}
|
|
28
|
+
cur[j] = value;
|
|
29
|
+
if (value < rowMin)
|
|
30
|
+
rowMin = value;
|
|
31
|
+
}
|
|
32
|
+
if (rowMin > max)
|
|
33
|
+
return max + 1;
|
|
34
|
+
const spare = before;
|
|
35
|
+
before = prev;
|
|
36
|
+
prev = cur;
|
|
37
|
+
cur = spare;
|
|
38
|
+
}
|
|
39
|
+
return Math.min(prev[lb], max + 1);
|
|
40
|
+
};
|
|
41
|
+
exports.editDistance = editDistance;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { editDistance } from "./distance";
|
|
2
|
+
import { foldText, splitWords, tokenize } from "./fold";
|
|
3
|
+
/** One searchable string; `weight` scales its matches (label 1, keyword 0.75, context 0.4). */
|
|
4
|
+
export type LauncherSearchField = {
|
|
5
|
+
name: string;
|
|
6
|
+
text: string;
|
|
7
|
+
weight?: number;
|
|
8
|
+
};
|
|
9
|
+
/** Anything searchable: an id plus its weighted fields, in the order results should tie-break. */
|
|
10
|
+
export type LauncherSearchDocument = {
|
|
11
|
+
id: string;
|
|
12
|
+
fields: readonly LauncherSearchField[];
|
|
13
|
+
};
|
|
14
|
+
/** How a query token matched a field, strongest first. */
|
|
15
|
+
export type LauncherMatchKind = "exact" | "prefix" | "word" | "word-prefix" | "initialism" | "substring" | "fuzzy" | "subsequence";
|
|
16
|
+
/** The field that gave a query token its best score. */
|
|
17
|
+
export type LauncherTokenMatch = {
|
|
18
|
+
token: string;
|
|
19
|
+
field: string;
|
|
20
|
+
text: string;
|
|
21
|
+
kind: LauncherMatchKind;
|
|
22
|
+
};
|
|
23
|
+
/** A ranked document: `index` is its input position, `synonym` the expansion used. */
|
|
24
|
+
export type LauncherSearchHit = {
|
|
25
|
+
id: string;
|
|
26
|
+
index: number;
|
|
27
|
+
score: number;
|
|
28
|
+
matches: LauncherTokenMatch[];
|
|
29
|
+
synonym?: string;
|
|
30
|
+
};
|
|
31
|
+
/** Term -> equivalent phrases, matched both ways: `{ pf: ["provident fund"] }`. */
|
|
32
|
+
export type LauncherSynonyms = Readonly<Record<string, readonly string[]>>;
|
|
33
|
+
/** Ranking knobs; `boosts` adds points per document id to documents that already match. */
|
|
34
|
+
export type LauncherSearchOptions = {
|
|
35
|
+
synonyms?: LauncherSynonyms;
|
|
36
|
+
boosts?: Readonly<Record<string, number>>;
|
|
37
|
+
/** Typo tolerance; defaults to true. */
|
|
38
|
+
fuzzy?: boolean;
|
|
39
|
+
/** Drops hits under this share of the best score; 0 keeps all. */
|
|
40
|
+
relativeCutoff?: number;
|
|
41
|
+
limit?: number;
|
|
42
|
+
};
|
|
43
|
+
type IndexedField = {
|
|
44
|
+
name: string;
|
|
45
|
+
text: string;
|
|
46
|
+
weight: number;
|
|
47
|
+
folded: string;
|
|
48
|
+
compact: string;
|
|
49
|
+
words: string[];
|
|
50
|
+
starts: number[];
|
|
51
|
+
initials: string;
|
|
52
|
+
mask: number;
|
|
53
|
+
letterWords: readonly (readonly string[])[] | null;
|
|
54
|
+
};
|
|
55
|
+
type IndexedDocument = {
|
|
56
|
+
id: string;
|
|
57
|
+
index: number;
|
|
58
|
+
fields: IndexedField[];
|
|
59
|
+
mask: number;
|
|
60
|
+
};
|
|
61
|
+
/** Pre-folded documents; build once with `createIndex`, query many times. */
|
|
62
|
+
export type LauncherSearchIndex = {
|
|
63
|
+
readonly size: number;
|
|
64
|
+
readonly documents: readonly IndexedDocument[];
|
|
65
|
+
};
|
|
66
|
+
export declare const createSearchIndex: (documents: readonly LauncherSearchDocument[]) => LauncherSearchIndex;
|
|
67
|
+
type CompileOptions = Pick<LauncherSearchOptions, "synonyms" | "fuzzy">;
|
|
68
|
+
export declare const searchIndex: (index: LauncherSearchIndex, query: string, options?: LauncherSearchOptions) => LauncherSearchHit[];
|
|
69
|
+
export declare const searchDocuments: (documents: readonly LauncherSearchDocument[], query: string, options?: LauncherSearchOptions) => LauncherSearchHit[];
|
|
70
|
+
/** Boolean test over one row's texts; an empty query matches every row. */
|
|
71
|
+
export type LauncherMatcher = (...texts: ReadonlyArray<string | null | undefined>) => boolean;
|
|
72
|
+
/** Compiles `query` once; every query word must hit some text of the row. */
|
|
73
|
+
export declare const createMatcher: (query: string, options?: CompileOptions) => LauncherMatcher;
|
|
74
|
+
/** One-off form of `createMatcher`: does `query` match the row made of `texts`? */
|
|
75
|
+
export declare const matchesQuery: (query: string, ...texts: ReadonlyArray<string | null | undefined>) => boolean;
|
|
76
|
+
/** Character ranges of `text` a query matched, in original-text offsets, merged and sorted. */
|
|
77
|
+
export declare const highlightRanges: (text: string, query: string, options?: Pick<LauncherSearchOptions, "synonyms" | "fuzzy">) => Array<[number, number]>;
|
|
78
|
+
/** Additive ranking points from usage: pinned flat, frequent and recent decaying by position. */
|
|
79
|
+
export declare const usageBoosts: (usage: {
|
|
80
|
+
pinnedIds?: readonly string[];
|
|
81
|
+
frequentIds?: readonly string[];
|
|
82
|
+
recentIds?: readonly string[];
|
|
83
|
+
}) => Record<string, number>;
|
|
84
|
+
export { editDistance, foldText, splitWords, tokenize };
|