@carlonicora/nextjs-jsonapi 4.0.7 → 4.0.8
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/{BlockNoteEditor-KOHN3SSR.mjs → BlockNoteEditor-A2E4VUYB.mjs} +2 -2
- package/dist/{BlockNoteEditor-J7EF7BRX.js → BlockNoteEditor-MCYQBKT5.js} +9 -9
- package/dist/{BlockNoteEditor-J7EF7BRX.js.map → BlockNoteEditor-MCYQBKT5.js.map} +1 -1
- package/dist/billing/index.js +335 -335
- package/dist/billing/index.mjs +1 -1
- package/dist/{chunk-3W3VVX2N.mjs → chunk-DJRTJBSZ.mjs} +76 -27
- package/dist/chunk-DJRTJBSZ.mjs.map +1 -0
- package/dist/{chunk-3M7D6NXU.js → chunk-PL2FHXHR.js} +76 -27
- package/dist/chunk-PL2FHXHR.js.map +1 -0
- package/dist/client/index.js +2 -2
- package/dist/client/index.mjs +1 -1
- package/dist/components/index.d.mts +10 -1
- package/dist/components/index.d.ts +10 -1
- package/dist/components/index.js +4 -2
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +3 -1
- package/dist/contexts/index.js +2 -2
- package/dist/contexts/index.mjs +1 -1
- package/dist/features/analytics/index.js +88 -88
- package/dist/features/analytics/index.mjs +1 -1
- package/dist/features/help/index.js +31 -31
- package/dist/features/help/index.mjs +1 -1
- package/dist/features/tokenusage/index.js +75 -75
- package/dist/features/tokenusage/index.mjs +1 -1
- package/package.json +1 -1
- package/src/features/company/hooks/useSubscriptionStatus.test.ts +76 -10
- package/src/features/company/hooks/useSubscriptionStatus.ts +9 -9
- package/src/features/how-to/components/containers/HowToCommand.spec.tsx +79 -6
- package/src/features/how-to/components/containers/HowToCommand.tsx +12 -2
- package/src/features/how-to/components/forms/HowToSelector.spec.tsx +85 -0
- package/src/features/how-to/components/forms/HowToSelector.tsx +1 -0
- package/src/features/role/components/forms/UserRoleAdd.spec.tsx +71 -0
- package/src/features/role/components/forms/UserRoleAdd.tsx +1 -0
- package/src/features/user/components/forms/UserSelector.spec.tsx +85 -0
- package/src/features/user/components/forms/UserSelector.tsx +1 -0
- package/src/features/user/components/lists/UserListInAdd.spec.tsx +84 -0
- package/src/features/user/components/lists/UserListInAdd.tsx +1 -0
- package/src/features/user/components/widgets/UserSearchPopover.spec.tsx +68 -0
- package/src/features/user/components/widgets/UserSearchPopover.tsx +1 -0
- package/src/shadcnui/custom/multi-select.spec.tsx +70 -0
- package/src/shadcnui/custom/multi-select.tsx +26 -2
- package/src/shadcnui/custom/multiple-selector.spec.tsx +111 -0
- package/src/shadcnui/custom/multiple-selector.tsx +33 -10
- package/src/shadcnui/ui/command.spec.ts +26 -0
- package/src/shadcnui/ui/command.tsx +13 -1
- package/dist/chunk-3M7D6NXU.js.map +0 -1
- package/dist/chunk-3W3VVX2N.mjs.map +0 -1
- /package/dist/{BlockNoteEditor-KOHN3SSR.mjs.map → BlockNoteEditor-A2E4VUYB.mjs.map} +0 -0
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
CommandItem,
|
|
15
15
|
CommandList,
|
|
16
16
|
CommandSeparator,
|
|
17
|
+
commandFilterByKeywords,
|
|
17
18
|
} from "../ui/command";
|
|
18
19
|
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
|
19
20
|
import { Separator } from "../ui/separator";
|
|
@@ -39,6 +40,18 @@ const multiSelectVariants = cva(
|
|
|
39
40
|
},
|
|
40
41
|
);
|
|
41
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Every row carries a unique `value` (an option's own value, or a reserved
|
|
45
|
+
* sentinel for the static rows) so that two options with the same label do not
|
|
46
|
+
* share cmdk's text-derived value and highlight/select together. The searchable
|
|
47
|
+
* text lives in `keywords`, and the filter scores the keywords alone: that keeps
|
|
48
|
+
* typing-to-search matching exactly what it matched when the value was the
|
|
49
|
+
* row's text, instead of also matching the option's raw value.
|
|
50
|
+
*/
|
|
51
|
+
const SELECT_ALL_VALUE = "__multi-select:select-all__";
|
|
52
|
+
const CLEAR_VALUE = "__multi-select:clear__";
|
|
53
|
+
const CLOSE_VALUE = "__multi-select:close__";
|
|
54
|
+
|
|
42
55
|
/**
|
|
43
56
|
* Props for MultiSelect component
|
|
44
57
|
*/
|
|
@@ -365,7 +378,7 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
|
|
|
365
378
|
style={{ minWidth: triggerWidth > 0 ? triggerWidth : undefined }}
|
|
366
379
|
align="start"
|
|
367
380
|
>
|
|
368
|
-
<Command>
|
|
381
|
+
<Command filter={commandFilterByKeywords}>
|
|
369
382
|
<CommandInput
|
|
370
383
|
autoFocus
|
|
371
384
|
placeholder="Search..."
|
|
@@ -383,6 +396,8 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
|
|
|
383
396
|
{!hideSelectAll && (
|
|
384
397
|
<CommandItem
|
|
385
398
|
key="all"
|
|
399
|
+
value={SELECT_ALL_VALUE}
|
|
400
|
+
keywords={["(Select All)"]}
|
|
386
401
|
onSelect={toggleAll}
|
|
387
402
|
className="cursor-pointer hover:bg-muted data-selected:hover:bg-muted bg-transparent data-selected:bg-transparent"
|
|
388
403
|
>
|
|
@@ -406,6 +421,8 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
|
|
|
406
421
|
return (
|
|
407
422
|
<CommandItem
|
|
408
423
|
key={option.value}
|
|
424
|
+
value={option.value}
|
|
425
|
+
keywords={[option.label]}
|
|
409
426
|
onSelect={() => toggleOption(option.value)}
|
|
410
427
|
className="cursor-pointer hover:bg-muted data-selected:hover:bg-muted bg-transparent data-selected:bg-transparent"
|
|
411
428
|
>
|
|
@@ -430,13 +447,20 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
|
|
|
430
447
|
<div className="flex items-center justify-between">
|
|
431
448
|
{selectedValues.length > 0 && (
|
|
432
449
|
<>
|
|
433
|
-
<CommandItem
|
|
450
|
+
<CommandItem
|
|
451
|
+
value={CLEAR_VALUE}
|
|
452
|
+
keywords={["Clear"]}
|
|
453
|
+
onSelect={handleClear}
|
|
454
|
+
className="flex-1 cursor-pointer justify-center"
|
|
455
|
+
>
|
|
434
456
|
Clear
|
|
435
457
|
</CommandItem>
|
|
436
458
|
<Separator orientation="vertical" className="flex h-full min-h-6" />
|
|
437
459
|
</>
|
|
438
460
|
)}
|
|
439
461
|
<CommandItem
|
|
462
|
+
value={CLOSE_VALUE}
|
|
463
|
+
keywords={["Close"]}
|
|
440
464
|
onSelect={() => setIsPopoverOpen(false)}
|
|
441
465
|
className="max-w-full flex-1 cursor-pointer justify-center hover:bg-muted data-selected:hover:bg-muted bg-transparent data-selected:bg-transparent"
|
|
442
466
|
>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { render, screen, waitFor, within } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { MultipleSelector, type MultipleSelectorProps, type Option } from "./multiple-selector";
|
|
6
|
+
|
|
7
|
+
// Two options with the SAME label: when the row value was the label, both rows shared
|
|
8
|
+
// one cmdk value and highlighted/selected together.
|
|
9
|
+
const options: Option[] = [
|
|
10
|
+
{ label: "Mario Rossi", value: "u1" },
|
|
11
|
+
{ label: "Mario Rossi", value: "u2" },
|
|
12
|
+
{ label: "Anna Bianchi", value: "u3" },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
async function openList(props: Partial<MultipleSelectorProps> = {}) {
|
|
16
|
+
render(<MultipleSelector defaultOptions={options} placeholder="pick" {...props} />);
|
|
17
|
+
await userEvent.click(screen.getByPlaceholderText("pick"));
|
|
18
|
+
return screen.findByRole("listbox");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const highlightedRows = (list: HTMLElement) =>
|
|
22
|
+
within(list)
|
|
23
|
+
.getAllByRole("option")
|
|
24
|
+
.filter((o) => o.getAttribute("aria-selected") === "true");
|
|
25
|
+
|
|
26
|
+
const optionNames = (list: HTMLElement) =>
|
|
27
|
+
within(list)
|
|
28
|
+
.getAllByRole("option")
|
|
29
|
+
.map((o) => o.textContent);
|
|
30
|
+
|
|
31
|
+
describe("MultipleSelector", () => {
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
Element.prototype.scrollIntoView = vi.fn();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("highlights only one of two same-labelled options and selects the one clicked", async () => {
|
|
37
|
+
const onChange = vi.fn();
|
|
38
|
+
const list = await openList({ onChange });
|
|
39
|
+
|
|
40
|
+
const highlighted = highlightedRows(list);
|
|
41
|
+
expect(highlighted).toHaveLength(1);
|
|
42
|
+
expect(highlighted[0]).toHaveTextContent("Mario Rossi");
|
|
43
|
+
|
|
44
|
+
await userEvent.keyboard("{ArrowDown}");
|
|
45
|
+
expect(highlightedRows(list)).toHaveLength(1);
|
|
46
|
+
|
|
47
|
+
const sameLabelled = within(list).getAllByRole("option", { name: "Mario Rossi" });
|
|
48
|
+
expect(sameLabelled).toHaveLength(2);
|
|
49
|
+
await userEvent.click(sameLabelled[1]);
|
|
50
|
+
expect(onChange).toHaveBeenLastCalledWith([options[1]]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("still filters by the option label when the user types", async () => {
|
|
54
|
+
const list = await openList();
|
|
55
|
+
await userEvent.type(screen.getByPlaceholderText("pick"), "anna");
|
|
56
|
+
|
|
57
|
+
await waitFor(() => expect(within(list).queryAllByRole("option", { name: "Mario Rossi" })).toHaveLength(0));
|
|
58
|
+
expect(within(list).getByRole("option", { name: "Anna Bianchi" })).toBeInTheDocument();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("does not match an option by its raw value, only by its label (as before)", async () => {
|
|
62
|
+
const list = await openList();
|
|
63
|
+
await userEvent.type(screen.getByPlaceholderText("pick"), "u3");
|
|
64
|
+
|
|
65
|
+
await waitFor(() => expect(within(list).queryByRole("option", { name: "Anna Bianchi" })).toBeNull());
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("ranks matches by label score as before", async () => {
|
|
69
|
+
const list = await openList({
|
|
70
|
+
defaultOptions: [
|
|
71
|
+
{ label: "Cabana", value: "o1" },
|
|
72
|
+
{ label: "Ananas", value: "o2" },
|
|
73
|
+
],
|
|
74
|
+
});
|
|
75
|
+
expect(optionNames(list)).toEqual(["Cabana", "Ananas"]);
|
|
76
|
+
|
|
77
|
+
await userEvent.type(screen.getByPlaceholderText("pick"), "ana");
|
|
78
|
+
await waitFor(() => expect(optionNames(list)).toEqual(["Ananas", "Cabana"]));
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("keeps the create row findable and creates the typed text", async () => {
|
|
82
|
+
const onChange = vi.fn();
|
|
83
|
+
const list = await openList({ onChange, creatable: true });
|
|
84
|
+
await userEvent.type(screen.getByPlaceholderText("pick"), "Zed");
|
|
85
|
+
|
|
86
|
+
const createRow = await within(list).findByRole("option", { name: 'Create "Zed"' });
|
|
87
|
+
expect(within(list).queryAllByRole("option", { name: "Mario Rossi" })).toHaveLength(0);
|
|
88
|
+
await userEvent.click(createRow);
|
|
89
|
+
expect(onChange).toHaveBeenLastCalledWith([{ value: "Zed", label: "Zed" }]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("filters by substring of the label when creatable, as before", async () => {
|
|
93
|
+
const list = await openList({ creatable: true });
|
|
94
|
+
await userEvent.type(screen.getByPlaceholderText("pick"), "bianchi");
|
|
95
|
+
|
|
96
|
+
await waitFor(() => expect(within(list).queryAllByRole("option", { name: "Mario Rossi" })).toHaveLength(0));
|
|
97
|
+
expect(within(list).getByRole("option", { name: "Anna Bianchi" })).toBeInTheDocument();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("hands a caller-supplied filter the row text, never the raw option value", async () => {
|
|
101
|
+
const filter = vi.fn((value: string, search: string) => (value.toLowerCase().includes(search) ? 1 : 0));
|
|
102
|
+
const list = await openList({ commandProps: { filter } });
|
|
103
|
+
await userEvent.type(screen.getByPlaceholderText("pick"), "anna");
|
|
104
|
+
|
|
105
|
+
await waitFor(() => expect(within(list).queryAllByRole("option", { name: "Mario Rossi" })).toHaveLength(0));
|
|
106
|
+
const seenValues = filter.mock.calls.map((c) => c[0]);
|
|
107
|
+
expect(seenValues).toContain("Anna Bianchi");
|
|
108
|
+
expect(seenValues).not.toContain("u1");
|
|
109
|
+
expect(seenValues).not.toContain("u3");
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -8,7 +8,7 @@ import { forwardRef, useEffect } from "react";
|
|
|
8
8
|
|
|
9
9
|
import { cn } from "../../utils/cn";
|
|
10
10
|
import { Badge } from "../ui/badge";
|
|
11
|
-
import { Command, CommandGroup, CommandItem, CommandList } from "../ui/command";
|
|
11
|
+
import { Command, CommandGroup, CommandItem, CommandList, commandFilterByKeywords } from "../ui/command";
|
|
12
12
|
|
|
13
13
|
export interface Option {
|
|
14
14
|
value: string;
|
|
@@ -140,6 +140,19 @@ function removePickedOption(groupOption: GroupOption, picked: Option[]) {
|
|
|
140
140
|
return cloneOption;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Every option row carries its own unique `value` (the option's `value`), and
|
|
145
|
+
* the "create" row a reserved one, so two options with the same label do not
|
|
146
|
+
* share cmdk's value and highlight/select together. The row's searchable text
|
|
147
|
+
* lives in `keywords`, and every filter below is given that text where it used
|
|
148
|
+
* to receive the value, so typing matches and ranks exactly as it did when the
|
|
149
|
+
* value was the label — and never matches an option's raw value.
|
|
150
|
+
*/
|
|
151
|
+
const CREATE_VALUE = "__multiple-selector:create__";
|
|
152
|
+
|
|
153
|
+
/** The text a row is searched by: its keywords, or its value when it has none (the "-" placeholders). */
|
|
154
|
+
const rowText = (value: string, keywords?: string[]) => (keywords && keywords.length > 0 ? keywords.join(" ") : value);
|
|
155
|
+
|
|
143
156
|
function isOptionsExist(groupOption: GroupOption, targetOption: Option[]) {
|
|
144
157
|
for (const [, value] of Object.entries(groupOption)) {
|
|
145
158
|
if (value.some((option) => targetOption.find((p) => p.value === option.value))) {
|
|
@@ -396,17 +409,22 @@ const MultipleSelector = React.forwardRef<MultipleSelectorRef, MultipleSelectorP
|
|
|
396
409
|
|
|
397
410
|
const Item = (
|
|
398
411
|
<CommandItem
|
|
399
|
-
value
|
|
412
|
+
// cmdk only re-reads an item's keywords when its value changes, so the
|
|
413
|
+
// typed text is part of the reserved value to keep the keywords current.
|
|
414
|
+
value={`${CREATE_VALUE}${inputValue}`}
|
|
415
|
+
keywords={[inputValue]}
|
|
400
416
|
className="cursor-pointer"
|
|
401
417
|
onMouseDown={(e) => {
|
|
402
418
|
e.preventDefault();
|
|
403
419
|
e.stopPropagation();
|
|
404
420
|
}}
|
|
405
|
-
onSelect={(
|
|
421
|
+
onSelect={() => {
|
|
406
422
|
if (selected.length >= maxSelected) {
|
|
407
423
|
onMaxSelected?.(selected.length);
|
|
408
424
|
return;
|
|
409
425
|
}
|
|
426
|
+
// cmdk used to hand back the row's value, which was the typed text trimmed.
|
|
427
|
+
const value = inputValue.trim();
|
|
410
428
|
setInputValue("");
|
|
411
429
|
const newOptions = [...selected, { value, label: value }];
|
|
412
430
|
setSelected(newOptions);
|
|
@@ -445,16 +463,19 @@ const MultipleSelector = React.forwardRef<MultipleSelectorRef, MultipleSelectorP
|
|
|
445
463
|
const selectables = React.useMemo<GroupOption>(() => removePickedOption(options, selected), [options, selected]);
|
|
446
464
|
|
|
447
465
|
const commandFilter = React.useCallback(() => {
|
|
448
|
-
|
|
449
|
-
|
|
466
|
+
const customFilter = commandProps?.filter;
|
|
467
|
+
if (customFilter) {
|
|
468
|
+
// A caller's filter keeps receiving the row text as `value` (no keywords), as before.
|
|
469
|
+
return (value: string, search: string, keywords?: string[]) =>
|
|
470
|
+
customFilter(rowText(value, keywords), search, []);
|
|
450
471
|
}
|
|
451
472
|
|
|
452
473
|
if (creatable) {
|
|
453
|
-
return (value: string, search: string) => {
|
|
454
|
-
return value.toLowerCase().includes(search.toLowerCase()) ? 1 : -1;
|
|
474
|
+
return (value: string, search: string, keywords?: string[]) => {
|
|
475
|
+
return rowText(value, keywords).toLowerCase().includes(search.toLowerCase()) ? 1 : -1;
|
|
455
476
|
};
|
|
456
477
|
}
|
|
457
|
-
return
|
|
478
|
+
return commandFilterByKeywords;
|
|
458
479
|
}, [creatable, commandProps?.filter]);
|
|
459
480
|
|
|
460
481
|
// Determine which badges to display
|
|
@@ -613,7 +634,8 @@ const MultipleSelector = React.forwardRef<MultipleSelectorRef, MultipleSelectorP
|
|
|
613
634
|
return (
|
|
614
635
|
<CommandItem
|
|
615
636
|
key={option.value}
|
|
616
|
-
value={option.
|
|
637
|
+
value={option.value}
|
|
638
|
+
keywords={[option.label]}
|
|
617
639
|
disabled={option.disable}
|
|
618
640
|
onMouseDown={(e) => {
|
|
619
641
|
e.preventDefault();
|
|
@@ -688,7 +710,8 @@ const MultipleSelector = React.forwardRef<MultipleSelectorRef, MultipleSelectorP
|
|
|
688
710
|
return (
|
|
689
711
|
<CommandItem
|
|
690
712
|
key={option.value}
|
|
691
|
-
value={option.
|
|
713
|
+
value={option.value}
|
|
714
|
+
keywords={[option.label]}
|
|
692
715
|
disabled={option.disable}
|
|
693
716
|
onMouseDown={(e) => {
|
|
694
717
|
e.preventDefault();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defaultFilter } from "cmdk";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { commandFilterByKeywords } from "./command";
|
|
5
|
+
|
|
6
|
+
describe("commandFilterByKeywords", () => {
|
|
7
|
+
it("scores the keywords exactly as cmdk scores the same text used as a value", () => {
|
|
8
|
+
expect(commandFilterByKeywords("u1", "mar", ["Mario Rossi"])).toBe(defaultFilter("Mario Rossi", "mar"));
|
|
9
|
+
expect(commandFilterByKeywords("u1", "mar", ["Mario Rossi"])).toBeGreaterThan(0);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("does not match an id-like value when the keywords do not match", () => {
|
|
13
|
+
expect(commandFilterByKeywords("9f3c-uuid-1234", "1234", ["Mario Rossi"])).toBe(0);
|
|
14
|
+
expect(commandFilterByKeywords("u3", "u3", ["Anna Bianchi"])).toBe(0);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("joins several keywords into one searchable text", () => {
|
|
18
|
+
expect(commandFilterByKeywords("u1", "rossi", ["Mario", "Rossi"])).toBeGreaterThan(0);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("falls back to the value when there are no keywords", () => {
|
|
22
|
+
expect(commandFilterByKeywords("Mario Rossi", "mar")).toBe(defaultFilter("Mario Rossi", "mar"));
|
|
23
|
+
expect(commandFilterByKeywords("Mario Rossi", "mar", [])).toBeGreaterThan(0);
|
|
24
|
+
expect(commandFilterByKeywords("Mario Rossi", "zzz", [])).toBe(0);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { Command as CommandPrimitive } from "cmdk";
|
|
4
|
+
import { Command as CommandPrimitive, defaultFilter } from "cmdk";
|
|
5
5
|
|
|
6
6
|
import { cn } from "@/lib/utils";
|
|
7
7
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|
@@ -167,6 +167,17 @@ function CommandShortcut({ className, ...props }: React.ComponentProps<"span">)
|
|
|
167
167
|
);
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
/**
|
|
171
|
+
* A `filter` for a `<Command>` whose items carry a unique `value` (an id) and
|
|
172
|
+
* their searchable text in `keywords`. cmdk's default filter scores the value
|
|
173
|
+
* AND the keywords, so an id value would make its own characters searchable;
|
|
174
|
+
* this one scores the keywords alone, and falls back to the value only for an
|
|
175
|
+
* item that has no keywords. Usage: `<Command filter={commandFilterByKeywords}>`
|
|
176
|
+
* with `<CommandItem value={id} keywords={[label]}>`.
|
|
177
|
+
*/
|
|
178
|
+
const commandFilterByKeywords = (value: string, search: string, keywords?: string[]) =>
|
|
179
|
+
defaultFilter(keywords && keywords.length > 0 ? keywords.join(" ") : value, search);
|
|
180
|
+
|
|
170
181
|
export {
|
|
171
182
|
Command,
|
|
172
183
|
CommandDialog,
|
|
@@ -177,4 +188,5 @@ export {
|
|
|
177
188
|
CommandItem,
|
|
178
189
|
CommandShortcut,
|
|
179
190
|
CommandSeparator,
|
|
191
|
+
commandFilterByKeywords,
|
|
180
192
|
};
|