@marimo-team/islands 0.20.5-dev24 → 0.20.5-dev25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{Combination-DPrRlMyq.js → Combination-Dk6JxauT.js} +1 -1
- package/dist/{ConnectedDataExplorerComponent-Bp9TusI-.js → ConnectedDataExplorerComponent-jj3D9AQT.js} +10 -10
- package/dist/{any-language-editor-BGcQxkAL.js → any-language-editor-BIj11a2e.js} +4 -4
- package/dist/{button-BJaX_M34.js → button-DQpBib29.js} +17 -9
- package/dist/{check-C9c-GtvX.js → check-DpqPQmzz.js} +1 -1
- package/dist/{copy-DhB8Zxuz.js → copy-BkBF0Xgk.js} +2 -2
- package/dist/{dist-DpeHj-vP.js → dist-WIWVvdBh.js} +2 -2
- package/dist/{error-banner-CJWv_j2x.js → error-banner-BctofTCP.js} +2 -2
- package/dist/{esm-DxAOF89q.js → esm-BBkPJL8N.js} +4 -4
- package/dist/{glide-data-editor-C2dhh3XY.js → glide-data-editor-B03jwvVe.js} +6 -6
- package/dist/{label-kL_UbziI.js → label-D036zdVU.js} +4 -4
- package/dist/{loader-C6xHQhFz.js → loader-C62dRCuy.js} +1 -1
- package/dist/main.js +128 -40
- package/dist/{mermaid-CzRLRASw.js → mermaid-BgeZPIms.js} +5 -5
- package/dist/{slides-component-D9AVSv2B.js → slides-component-DwvL_HJi.js} +2 -2
- package/dist/{spec-CXpiliig.js → spec-BI4Eyctn.js} +4 -4
- package/dist/{tooltip-CRsmtPeF.js → tooltip-SPkubVH3.js} +3 -3
- package/dist/{types-ChPKdlru.js → types-DqFw5XXR.js} +6 -6
- package/dist/{useAsyncData-CHYjZCox.js → useAsyncData-Ioeh75f8.js} +1 -1
- package/dist/{useDeepCompareMemoize-C1YH9SDF.js → useDeepCompareMemoize-DtbTAJq3.js} +4 -4
- package/dist/{useIframeCapabilities-Cc4UBysp.js → useIframeCapabilities-DFGZKWkO.js} +1 -1
- package/dist/{useTheme-B-ZI3PUl.js → useTheme-OvBNH9t3.js} +2 -2
- package/dist/{vega-component-BX_oDKB3.js → vega-component-B_4Lp3hK.js} +8 -8
- package/package.json +1 -1
- package/src/components/editor/actions/types.ts +6 -1
- package/src/components/editor/actions/useNotebookActions.tsx +16 -11
- package/src/components/editor/chrome/types.ts +17 -0
- package/src/components/editor/controls/command-palette.tsx +7 -0
- package/src/components/ui/command.tsx +2 -0
- package/src/core/hotkeys/hotkeys.ts +11 -1
- package/src/utils/__tests__/smartMatch.test.ts +61 -0
- package/src/utils/smartMatch.ts +62 -0
|
@@ -379,17 +379,20 @@ export function useNotebookActions() {
|
|
|
379
379
|
label: "Helper panel",
|
|
380
380
|
redundant: true,
|
|
381
381
|
handle: NOOP_HANDLER,
|
|
382
|
-
dropdown: PANELS.flatMap(
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
382
|
+
dropdown: PANELS.flatMap(
|
|
383
|
+
({ type: id, Icon, hidden, additionalKeywords }) => {
|
|
384
|
+
if (hidden) {
|
|
385
|
+
return [];
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
label: startCase(id),
|
|
389
|
+
rightElement: renderCheckboxElement(selectedPanel === id),
|
|
390
|
+
icon: <Icon size={14} strokeWidth={1.5} />,
|
|
391
|
+
handle: () => toggleApplication(id),
|
|
392
|
+
additionalKeywords,
|
|
393
|
+
};
|
|
394
|
+
},
|
|
395
|
+
),
|
|
393
396
|
},
|
|
394
397
|
|
|
395
398
|
{
|
|
@@ -510,6 +513,7 @@ export function useNotebookActions() {
|
|
|
510
513
|
label: "Restart kernel",
|
|
511
514
|
variant: "danger",
|
|
512
515
|
handle: restartKernel,
|
|
516
|
+
additionalKeywords: ["reset", "reload", "restart"],
|
|
513
517
|
},
|
|
514
518
|
{
|
|
515
519
|
icon: <FastForwardIcon size={14} strokeWidth={1.5} />,
|
|
@@ -567,6 +571,7 @@ export function useNotebookActions() {
|
|
|
567
571
|
label: "User settings",
|
|
568
572
|
handle: () => setSettingsDialogOpen((open) => !open),
|
|
569
573
|
redundant: true,
|
|
574
|
+
additionalKeywords: ["preferences", "options", "configuration"],
|
|
570
575
|
},
|
|
571
576
|
{
|
|
572
577
|
icon: <ExternalLinkIcon size={14} strokeWidth={1.5} />,
|
|
@@ -59,6 +59,8 @@ export interface PanelDescriptor {
|
|
|
59
59
|
defaultSection: PanelSection;
|
|
60
60
|
/** Capability required for this panel to be visible. If the capability is false, the panel is hidden. */
|
|
61
61
|
requiredCapability?: keyof Capabilities;
|
|
62
|
+
/** Additional search keywords for the command palette */
|
|
63
|
+
additionalKeywords?: string[];
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
/**
|
|
@@ -73,6 +75,7 @@ export const PANELS: PanelDescriptor[] = [
|
|
|
73
75
|
label: "Files",
|
|
74
76
|
tooltip: "View files",
|
|
75
77
|
defaultSection: "sidebar",
|
|
78
|
+
additionalKeywords: ["explorer", "browser", "directory"],
|
|
76
79
|
},
|
|
77
80
|
{
|
|
78
81
|
type: "variables",
|
|
@@ -80,6 +83,7 @@ export const PANELS: PanelDescriptor[] = [
|
|
|
80
83
|
label: "Variables",
|
|
81
84
|
tooltip: "Explore variables and data sources",
|
|
82
85
|
defaultSection: "sidebar",
|
|
86
|
+
additionalKeywords: ["state", "scope", "inspector"],
|
|
83
87
|
},
|
|
84
88
|
{
|
|
85
89
|
type: "packages",
|
|
@@ -87,6 +91,7 @@ export const PANELS: PanelDescriptor[] = [
|
|
|
87
91
|
label: "Packages",
|
|
88
92
|
tooltip: "Manage packages",
|
|
89
93
|
defaultSection: "sidebar",
|
|
94
|
+
additionalKeywords: ["dependencies", "pip", "install"],
|
|
90
95
|
},
|
|
91
96
|
{
|
|
92
97
|
type: "ai",
|
|
@@ -94,6 +99,7 @@ export const PANELS: PanelDescriptor[] = [
|
|
|
94
99
|
label: "AI",
|
|
95
100
|
tooltip: "Chat & Agents",
|
|
96
101
|
defaultSection: "sidebar",
|
|
102
|
+
additionalKeywords: ["chat", "copilot", "assistant"],
|
|
97
103
|
},
|
|
98
104
|
{
|
|
99
105
|
type: "outline",
|
|
@@ -101,6 +107,7 @@ export const PANELS: PanelDescriptor[] = [
|
|
|
101
107
|
label: "Outline",
|
|
102
108
|
tooltip: "View outline",
|
|
103
109
|
defaultSection: "sidebar",
|
|
110
|
+
additionalKeywords: ["toc", "structure", "headings"],
|
|
104
111
|
},
|
|
105
112
|
{
|
|
106
113
|
type: "documentation",
|
|
@@ -108,6 +115,7 @@ export const PANELS: PanelDescriptor[] = [
|
|
|
108
115
|
label: "Docs",
|
|
109
116
|
tooltip: "View live docs",
|
|
110
117
|
defaultSection: "sidebar",
|
|
118
|
+
additionalKeywords: ["reference", "api"],
|
|
111
119
|
},
|
|
112
120
|
{
|
|
113
121
|
type: "dependencies",
|
|
@@ -115,6 +123,7 @@ export const PANELS: PanelDescriptor[] = [
|
|
|
115
123
|
label: "Dependencies",
|
|
116
124
|
tooltip: "Explore dependencies",
|
|
117
125
|
defaultSection: "sidebar",
|
|
126
|
+
additionalKeywords: ["graph", "imports"],
|
|
118
127
|
},
|
|
119
128
|
// Developer panel defaults
|
|
120
129
|
{
|
|
@@ -123,6 +132,7 @@ export const PANELS: PanelDescriptor[] = [
|
|
|
123
132
|
label: "Errors",
|
|
124
133
|
tooltip: "View errors",
|
|
125
134
|
defaultSection: "developer-panel",
|
|
135
|
+
additionalKeywords: ["exceptions", "problems", "diagnostics"],
|
|
126
136
|
},
|
|
127
137
|
{
|
|
128
138
|
type: "scratchpad",
|
|
@@ -130,6 +140,7 @@ export const PANELS: PanelDescriptor[] = [
|
|
|
130
140
|
label: "Scratchpad",
|
|
131
141
|
tooltip: "Scratchpad",
|
|
132
142
|
defaultSection: "developer-panel",
|
|
143
|
+
additionalKeywords: ["scratch", "draft", "playground"],
|
|
133
144
|
},
|
|
134
145
|
{
|
|
135
146
|
type: "tracing",
|
|
@@ -137,6 +148,7 @@ export const PANELS: PanelDescriptor[] = [
|
|
|
137
148
|
label: "Tracing",
|
|
138
149
|
tooltip: "View tracing",
|
|
139
150
|
defaultSection: "developer-panel",
|
|
151
|
+
additionalKeywords: ["profiling", "performance"],
|
|
140
152
|
},
|
|
141
153
|
{
|
|
142
154
|
type: "secrets",
|
|
@@ -145,6 +157,7 @@ export const PANELS: PanelDescriptor[] = [
|
|
|
145
157
|
tooltip: "Manage secrets",
|
|
146
158
|
defaultSection: "developer-panel",
|
|
147
159
|
hidden: isWasm(),
|
|
160
|
+
additionalKeywords: ["env", "environment", "keys", "credentials"],
|
|
148
161
|
},
|
|
149
162
|
{
|
|
150
163
|
type: "logs",
|
|
@@ -152,6 +165,7 @@ export const PANELS: PanelDescriptor[] = [
|
|
|
152
165
|
label: "Logs",
|
|
153
166
|
tooltip: "View logs",
|
|
154
167
|
defaultSection: "developer-panel",
|
|
168
|
+
additionalKeywords: ["console", "stdout"],
|
|
155
169
|
},
|
|
156
170
|
{
|
|
157
171
|
type: "terminal",
|
|
@@ -161,6 +175,7 @@ export const PANELS: PanelDescriptor[] = [
|
|
|
161
175
|
hidden: isWasm(),
|
|
162
176
|
defaultSection: "developer-panel",
|
|
163
177
|
requiredCapability: "terminal",
|
|
178
|
+
additionalKeywords: ["shell", "console", "bash", "command"],
|
|
164
179
|
},
|
|
165
180
|
{
|
|
166
181
|
type: "snippets",
|
|
@@ -168,6 +183,7 @@ export const PANELS: PanelDescriptor[] = [
|
|
|
168
183
|
label: "Snippets",
|
|
169
184
|
tooltip: "Snippets",
|
|
170
185
|
defaultSection: "developer-panel",
|
|
186
|
+
additionalKeywords: ["templates", "examples"],
|
|
171
187
|
},
|
|
172
188
|
{
|
|
173
189
|
type: "cache",
|
|
@@ -176,6 +192,7 @@ export const PANELS: PanelDescriptor[] = [
|
|
|
176
192
|
tooltip: "View cache",
|
|
177
193
|
defaultSection: "developer-panel",
|
|
178
194
|
hidden: !getFeatureFlag("cache_panel"),
|
|
195
|
+
additionalKeywords: ["memory", "memoize"],
|
|
179
196
|
},
|
|
180
197
|
];
|
|
181
198
|
|
|
@@ -78,6 +78,7 @@ const CommandPalette = () => {
|
|
|
78
78
|
return (
|
|
79
79
|
<CommandItem
|
|
80
80
|
disabled={props.disabled}
|
|
81
|
+
keywords={hotkey.additionalKeywords}
|
|
81
82
|
onSelect={() => {
|
|
82
83
|
addRecentCommand(shortcut);
|
|
83
84
|
// Close first and then run the action, so the dialog doesn't steal focus
|
|
@@ -105,15 +106,18 @@ const CommandPalette = () => {
|
|
|
105
106
|
handle,
|
|
106
107
|
props = {},
|
|
107
108
|
hotkey,
|
|
109
|
+
additionalKeywords,
|
|
108
110
|
}: {
|
|
109
111
|
label: string;
|
|
110
112
|
handle: () => void;
|
|
111
113
|
props?: { disabled?: boolean; tooltip?: React.ReactNode };
|
|
112
114
|
hotkey?: HotkeyAction;
|
|
115
|
+
additionalKeywords?: string[];
|
|
113
116
|
}) => {
|
|
114
117
|
return (
|
|
115
118
|
<CommandItem
|
|
116
119
|
disabled={props.disabled}
|
|
120
|
+
keywords={additionalKeywords}
|
|
117
121
|
onSelect={() => {
|
|
118
122
|
addRecentCommand(label);
|
|
119
123
|
setOpen(false);
|
|
@@ -163,6 +167,7 @@ const CommandPalette = () => {
|
|
|
163
167
|
disabled: action.disabled,
|
|
164
168
|
tooltip: action.tooltip,
|
|
165
169
|
},
|
|
170
|
+
additionalKeywords: action.additionalKeywords,
|
|
166
171
|
});
|
|
167
172
|
}
|
|
168
173
|
return null;
|
|
@@ -190,6 +195,7 @@ const CommandPalette = () => {
|
|
|
190
195
|
label: action.label,
|
|
191
196
|
handle: action.handleHeadless || action.handle,
|
|
192
197
|
props: { disabled: action.disabled, tooltip: action.tooltip },
|
|
198
|
+
additionalKeywords: action.additionalKeywords,
|
|
193
199
|
});
|
|
194
200
|
})}
|
|
195
201
|
{cellActions.map((action) => {
|
|
@@ -200,6 +206,7 @@ const CommandPalette = () => {
|
|
|
200
206
|
label: `Cell > ${action.label}`,
|
|
201
207
|
handle: action.handleHeadless || action.handle,
|
|
202
208
|
props: { disabled: action.disabled, tooltip: action.tooltip },
|
|
209
|
+
additionalKeywords: action.additionalKeywords,
|
|
203
210
|
});
|
|
204
211
|
})}
|
|
205
212
|
</CommandGroup>
|
|
@@ -7,6 +7,7 @@ import { Search } from "lucide-react";
|
|
|
7
7
|
import * as React from "react";
|
|
8
8
|
import { Dialog, DialogContent } from "@/components/ui/dialog";
|
|
9
9
|
import { cn } from "@/utils/cn";
|
|
10
|
+
import { smartMatchFilter } from "@/utils/smartMatch";
|
|
10
11
|
import { Strings } from "@/utils/strings";
|
|
11
12
|
import {
|
|
12
13
|
MENU_ITEM_DISABLED,
|
|
@@ -21,6 +22,7 @@ const Command = React.forwardRef<
|
|
|
21
22
|
>(({ className, ...props }, ref) => (
|
|
22
23
|
<CommandPrimitive
|
|
23
24
|
ref={ref}
|
|
25
|
+
filter={smartMatchFilter}
|
|
24
26
|
className={cn(
|
|
25
27
|
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
|
26
28
|
className,
|
|
@@ -27,11 +27,13 @@ export interface Hotkey {
|
|
|
27
27
|
* @default true
|
|
28
28
|
*/
|
|
29
29
|
editable?: boolean;
|
|
30
|
+
additionalKeywords?: string[];
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
interface ResolvedHotkey {
|
|
33
34
|
name: string;
|
|
34
35
|
key: string;
|
|
36
|
+
additionalKeywords?: string[];
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
type ModKey = "Cmd" | "Ctrl";
|
|
@@ -110,6 +112,7 @@ const DEFAULT_HOT_KEY = {
|
|
|
110
112
|
name: "Run",
|
|
111
113
|
group: "Running Cells",
|
|
112
114
|
key: "Mod-Enter",
|
|
115
|
+
additionalKeywords: ["execute", "submit"],
|
|
113
116
|
},
|
|
114
117
|
"cell.runAndNewBelow": {
|
|
115
118
|
name: "Run and new below",
|
|
@@ -132,6 +135,7 @@ const DEFAULT_HOT_KEY = {
|
|
|
132
135
|
name: "Format cell",
|
|
133
136
|
group: "Editing",
|
|
134
137
|
key: "Mod-b",
|
|
138
|
+
additionalKeywords: ["lint"],
|
|
135
139
|
},
|
|
136
140
|
"cell.viewAsMarkdown": {
|
|
137
141
|
name: "View as Markdown",
|
|
@@ -201,6 +205,7 @@ const DEFAULT_HOT_KEY = {
|
|
|
201
205
|
name: "Delete cell",
|
|
202
206
|
group: "Editing",
|
|
203
207
|
key: "Shift-Backspace",
|
|
208
|
+
additionalKeywords: ["remove"],
|
|
204
209
|
},
|
|
205
210
|
"cell.hideCode": {
|
|
206
211
|
name: "Hide cell code",
|
|
@@ -320,6 +325,7 @@ const DEFAULT_HOT_KEY = {
|
|
|
320
325
|
name: "Save file",
|
|
321
326
|
group: "Other",
|
|
322
327
|
key: "Mod-s",
|
|
328
|
+
additionalKeywords: ["write", "persist"],
|
|
323
329
|
},
|
|
324
330
|
"global.commandPalette": {
|
|
325
331
|
name: "Show command palette",
|
|
@@ -485,23 +491,26 @@ export class HotkeyProvider implements IHotkeyProvider {
|
|
|
485
491
|
}
|
|
486
492
|
|
|
487
493
|
getHotkey(action: HotkeyAction): ResolvedHotkey {
|
|
488
|
-
const { name, key } = this.hotkeys[action];
|
|
494
|
+
const { name, key, additionalKeywords } = this.hotkeys[action];
|
|
489
495
|
if (typeof key === "string") {
|
|
490
496
|
return {
|
|
491
497
|
name,
|
|
492
498
|
key: key.replace("Mod", this.mod),
|
|
499
|
+
additionalKeywords,
|
|
493
500
|
};
|
|
494
501
|
}
|
|
495
502
|
if (key === NOT_SET) {
|
|
496
503
|
return {
|
|
497
504
|
name,
|
|
498
505
|
key: "",
|
|
506
|
+
additionalKeywords,
|
|
499
507
|
};
|
|
500
508
|
}
|
|
501
509
|
const platformKey = key[this.platform] || key.main;
|
|
502
510
|
return {
|
|
503
511
|
name,
|
|
504
512
|
key: platformKey.replace("Mod", this.mod),
|
|
513
|
+
additionalKeywords,
|
|
505
514
|
};
|
|
506
515
|
}
|
|
507
516
|
|
|
@@ -539,6 +548,7 @@ export class OverridingHotkeyProvider extends HotkeyProvider {
|
|
|
539
548
|
return {
|
|
540
549
|
name: base.name,
|
|
541
550
|
key: override ? normalizeKeyString(override) : base.key,
|
|
551
|
+
additionalKeywords: base.additionalKeywords,
|
|
542
552
|
};
|
|
543
553
|
}
|
|
544
554
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { smartMatch, smartMatchFilter } from "../smartMatch";
|
|
4
|
+
|
|
5
|
+
describe("smartMatch", () => {
|
|
6
|
+
it("matches exact word", () => {
|
|
7
|
+
expect(smartMatch("run", "Run cell")).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("matches word prefix", () => {
|
|
11
|
+
expect(smartMatch("exe", "execute start")).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("matches across array of haystacks", () => {
|
|
15
|
+
expect(smartMatch("exe", ["Run", "execute start"])).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("does not match unrelated terms", () => {
|
|
19
|
+
expect(smartMatch("xyz", "Run cell")).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("empty search matches everything", () => {
|
|
23
|
+
expect(smartMatch("", "anything")).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("multi-word needle requires all words to match", () => {
|
|
27
|
+
expect(smartMatch("run cell", "Run cell")).toBe(true);
|
|
28
|
+
expect(smartMatch("run xyz", "Run cell")).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("is case-insensitive", () => {
|
|
32
|
+
expect(smartMatch("RUN", "run cell")).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("skips null/undefined haystacks", () => {
|
|
36
|
+
expect(smartMatch("run", [null, undefined, "Run cell"])).toBe(true);
|
|
37
|
+
expect(smartMatch("run", [null, undefined])).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("smartMatchFilter", () => {
|
|
42
|
+
it("returns 1 for value match", () => {
|
|
43
|
+
expect(smartMatchFilter("Run cell", "run")).toBe(1);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("returns 0 for no match", () => {
|
|
47
|
+
expect(smartMatchFilter("Run cell", "xyz")).toBe(0);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("returns 0.8 for keyword-only match", () => {
|
|
51
|
+
expect(smartMatchFilter("Run", "execute", ["execute", "start"])).toBe(0.8);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("returns 1 when value matches even if keywords also match", () => {
|
|
55
|
+
expect(smartMatchFilter("Run", "run", ["execute", "start"])).toBe(1);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("does not match without relevant keywords", () => {
|
|
59
|
+
expect(smartMatchFilter("Run", "preferences", ["execute"])).toBe(0);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Split a string into lowercase words (letters/digits only).
|
|
5
|
+
*/
|
|
6
|
+
function words(s: string): string[] {
|
|
7
|
+
return s.toLowerCase().match(/[\da-z]+/g) || [];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns true when every word in `needle` is a prefix of at least one word
|
|
12
|
+
* in one of the `haystacks`.
|
|
13
|
+
*
|
|
14
|
+
* Examples:
|
|
15
|
+
* smartMatch("run", "Run cell") // true – "run" prefixes "run"
|
|
16
|
+
* smartMatch("exe", ["Run", "execute start"]) // true – "exe" prefixes "execute"
|
|
17
|
+
* smartMatch("xyz", "Run cell") // false
|
|
18
|
+
*/
|
|
19
|
+
export function smartMatch(
|
|
20
|
+
needle: string,
|
|
21
|
+
haystackOrHaystacks: string | (string | null | undefined)[],
|
|
22
|
+
): boolean {
|
|
23
|
+
const needleWords = words(needle);
|
|
24
|
+
if (needleWords.length === 0) {
|
|
25
|
+
return true; // empty search matches everything
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const haystacks = Array.isArray(haystackOrHaystacks)
|
|
29
|
+
? haystackOrHaystacks
|
|
30
|
+
: [haystackOrHaystacks];
|
|
31
|
+
|
|
32
|
+
// Collect all words from all haystacks
|
|
33
|
+
const haystackWords: string[] = [];
|
|
34
|
+
for (const h of haystacks) {
|
|
35
|
+
if (h) {
|
|
36
|
+
haystackWords.push(...words(h));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Every needle word must be a prefix of at least one haystack word
|
|
41
|
+
return needleWords.every((nw) => {
|
|
42
|
+
return haystackWords.some((hw) => hw.startsWith(nw));
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* cmdk-compatible filter function.
|
|
48
|
+
* Returns 1 for a value match, 0.8 for a keyword-only match, 0 for no match.
|
|
49
|
+
*/
|
|
50
|
+
export function smartMatchFilter(
|
|
51
|
+
value: string,
|
|
52
|
+
search: string,
|
|
53
|
+
keywords?: string[],
|
|
54
|
+
): number {
|
|
55
|
+
if (smartMatch(search, value)) {
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
58
|
+
if (keywords && smartMatch(search, keywords)) {
|
|
59
|
+
return 0.8;
|
|
60
|
+
}
|
|
61
|
+
return 0;
|
|
62
|
+
}
|