@gusnips/react 0.9.25 → 0.9.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -0
- package/dist/commands.d.ts +80 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +192 -0
- package/dist/commands.js.map +1 -0
- package/dist/ui/menu.d.ts +1 -1
- package/dist/ui/menu.d.ts.map +1 -1
- package/package.json +5 -1
- package/src/commands-hook.test.ts +444 -0
- package/src/commands.test.ts +245 -0
- package/src/commands.ts +268 -0
package/README.md
CHANGED
|
@@ -350,6 +350,38 @@ It keeps the page and drops only a fragment carrying a credential, because the p
|
|
|
350
350
|
worth returning to. `createRequireAuth` uses it; use it anywhere else you record where someone was:
|
|
351
351
|
a dead-session redirect is the common one.
|
|
352
352
|
|
|
353
|
+
## Keep the email between sign-in screens
|
|
354
|
+
|
|
355
|
+
```tsx
|
|
356
|
+
import { recallAuthEmail, rememberAuthEmail } from "@gusnips/react";
|
|
357
|
+
|
|
358
|
+
<input
|
|
359
|
+
name="email"
|
|
360
|
+
type="email"
|
|
361
|
+
defaultValue={recallAuthEmail()}
|
|
362
|
+
onChange={(event) => rememberAuthEmail(event.currentTarget.value)}
|
|
363
|
+
/>;
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Sign in, "use a code instead", forgot password and create account are separate screens, and each
|
|
367
|
+
one used to open with an empty email field. Put those two props on every one of them and the
|
|
368
|
+
address follows the person. Call `forgetAuthEmail()` where your app learns a session exists, so the
|
|
369
|
+
next person at this browser starts empty.
|
|
370
|
+
|
|
371
|
+
It is kept in `localStorage` for 30 minutes after the last keystroke, and never in a URL, where it
|
|
372
|
+
would reach `Referer`, access logs, history and analytics. It is `localStorage` and not
|
|
373
|
+
`sessionStorage` because an emailed link opens in a new tab, and a new tab does not inherit
|
|
374
|
+
`sessionStorage`. That tab is where a person lands on sign-in after confirming an account. Where
|
|
375
|
+
the browser blocks site data, or on React Native, the address lasts in memory until the page or the
|
|
376
|
+
app is left.
|
|
377
|
+
|
|
378
|
+
- Read it once, when the screen opens. It is not a store: nothing re-renders when it changes. On a
|
|
379
|
+
prerendered page, read it after hydration, never while rendering.
|
|
380
|
+
- Leave the field uncontrolled. A `defaultValue` and a change handler are enough. Also call
|
|
381
|
+
`rememberAuthEmail(value)` where you read the field at submit, because some password managers fill
|
|
382
|
+
a form without firing events.
|
|
383
|
+
- Never put a password, a code or a token here.
|
|
384
|
+
|
|
353
385
|
## Never dead-end anyone
|
|
354
386
|
|
|
355
387
|
```ts
|
|
@@ -497,6 +529,110 @@ toast needs no `catch`. Outside a component, `copyText(text)` does the write on
|
|
|
497
529
|
The words are yours, and so is the live region: a label that changes needs `aria-live` to be
|
|
498
530
|
heard.
|
|
499
531
|
|
|
532
|
+
## Slash commands in a text field
|
|
533
|
+
|
|
534
|
+
A slash command is text like `/help` that picks an action instead of sending a message.
|
|
535
|
+
|
|
536
|
+
```ts
|
|
537
|
+
import { parseCommand } from "@gusnips/react/commands";
|
|
538
|
+
|
|
539
|
+
parseCommand("/help");
|
|
540
|
+
// { name: "help" }
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
Names have no leading slash. Give each command other names (`aliases`) and search words (`tags`):
|
|
544
|
+
|
|
545
|
+
```ts
|
|
546
|
+
const commands = [
|
|
547
|
+
{
|
|
548
|
+
name: "help",
|
|
549
|
+
description: "See commands",
|
|
550
|
+
aliases: ["commands", "shortcuts"],
|
|
551
|
+
tags: ["keyboard", "guide"],
|
|
552
|
+
},
|
|
553
|
+
];
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
`/commands` runs `/help`. Searching for `keyboard` finds its menu row, but `/keyboard` is not an
|
|
557
|
+
executable name. `matchCommands(commands, query)` searches names, aliases, tags and descriptions,
|
|
558
|
+
ignoring case and accents. Exact names rank first, then aliases, name prefixes, alias prefixes,
|
|
559
|
+
tags and descriptions. Equal ranks keep registry order; each name gets one row.
|
|
560
|
+
|
|
561
|
+
`resolveCommand(commands, name)` answers `matched`, `unknown` or `ambiguous`. An exact name wins
|
|
562
|
+
over an alias. Two commands sharing an alias are ambiguous, not a reason to run the first one.
|
|
563
|
+
|
|
564
|
+
For a controlled text field, use the hook:
|
|
565
|
+
|
|
566
|
+
```tsx
|
|
567
|
+
import { useSlashCommands } from "@gusnips/react/commands";
|
|
568
|
+
|
|
569
|
+
const slash = useSlashCommands({
|
|
570
|
+
text,
|
|
571
|
+
commands,
|
|
572
|
+
onChange: setText,
|
|
573
|
+
onExecute: runCommand,
|
|
574
|
+
onError: showCommandError,
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
function submit() {
|
|
578
|
+
if (slash.submit()) return;
|
|
579
|
+
sendMessage(text);
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
The functions in that example belong to your app. `runCommand(command, arg, altKey)` gets the
|
|
584
|
+
original command object, the argument and whether Alt was held. It owns permissions and action
|
|
585
|
+
errors. Supply only commands allowed in this field, with descriptions and tags in the current
|
|
586
|
+
language. Changing those commands or the language refreshes the menu even if `text` stays the same.
|
|
587
|
+
|
|
588
|
+
Call `slash.handleKeyDown(event)` first in the field's key handler. If it returns `true`, stop
|
|
589
|
+
your own handler. Arrows move the selection; Enter picks it. Tab completes the name without
|
|
590
|
+
running it. Escape hides the menu until the query changes. Shift+Enter stays yours. During IME
|
|
591
|
+
composition (when a keyboard combines keystrokes into a character), the hook lets the browser
|
|
592
|
+
finish that character without running or sending anything.
|
|
593
|
+
|
|
594
|
+
Render the rows in your own menu. Keep focus in the field with the browser's `preventDefault`
|
|
595
|
+
on mouse down; use click for the action so keyboard and screen-reader clicks work too:
|
|
596
|
+
|
|
597
|
+
```tsx
|
|
598
|
+
return slash.open ? (
|
|
599
|
+
<div id={slash.listId} role="listbox" aria-label="Commands">
|
|
600
|
+
{slash.items.map((command, index) => (
|
|
601
|
+
<button
|
|
602
|
+
key={command.name}
|
|
603
|
+
id={slash.itemId(command)}
|
|
604
|
+
type="button"
|
|
605
|
+
role="option"
|
|
606
|
+
tabIndex={-1}
|
|
607
|
+
aria-selected={slash.index === index}
|
|
608
|
+
onMouseEnter={() => slash.highlight(index)}
|
|
609
|
+
onMouseDown={(event) => event.preventDefault()}
|
|
610
|
+
onClick={(event) => slash.pick(command, event.altKey)}
|
|
611
|
+
>
|
|
612
|
+
/{command.name}: {command.description}
|
|
613
|
+
</button>
|
|
614
|
+
))}
|
|
615
|
+
</div>
|
|
616
|
+
) : null;
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
Give the field `role="combobox"`, `aria-autocomplete="list"`, `aria-expanded={slash.open}`,
|
|
620
|
+
`aria-controls={slash.open ? slash.listId : undefined}` and `aria-activedescendant={slash.activeId}`.
|
|
621
|
+
Translate the menu label too. If there are no rows, explain how to find commands and offer a
|
|
622
|
+
"See commands" button that calls `slash.showAll()`. It opens the full list, even after Escape.
|
|
623
|
+
`onError` is required: show what was not recognized, or which names share an alias, with the
|
|
624
|
+
same way back to the list.
|
|
625
|
+
|
|
626
|
+
Set `takesArg: true` when a command accepts an argument. Selecting it, or submitting its bare
|
|
627
|
+
alias, completes `/name ` without running it. Completion keeps arguments already typed.
|
|
628
|
+
`parseCommand` leaves multiline text as a normal message.
|
|
629
|
+
|
|
630
|
+
Call `submit()` before every normal send, queue or quota check, including button and imperative
|
|
631
|
+
paths. It returns `true` for commands it ran, completed or rejected, so unknown commands never
|
|
632
|
+
silently become model messages. It returns `false` for ordinary text. The hook never sends a
|
|
633
|
+
message, clears a draft or changes attached files; those stay with the app. It also has no timer
|
|
634
|
+
or scheduler: offer a repeat command only when your app has real scheduling behind it.
|
|
635
|
+
|
|
500
636
|
## Also here
|
|
501
637
|
|
|
502
638
|
`ErrorBoundary`, an SSE reader split into a platform-free parser and a stream wrapper,
|
|
@@ -577,6 +713,7 @@ lives behind a subpath, so you install a dependency only if you import the thing
|
|
|
577
713
|
| `@gusnips/react/ui` | the seven wrappers | @base-ui/react |
|
|
578
714
|
| `@gusnips/react/contract` | two prerender names | nothing |
|
|
579
715
|
| `@gusnips/react/theme` | `createTheme`, `startTheme` | react |
|
|
716
|
+
| `@gusnips/react/commands` | slash matching and `useSlashCommands` | react |
|
|
580
717
|
|
|
581
718
|
The rule behind that table: **a peer marked optional must not be reachable from the main entry
|
|
582
719
|
point.** An optional peer the barrel imports anyway is not optional: it is a required one whose
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export interface SlashCommand {
|
|
2
|
+
/** One token, without the leading slash. */
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
/** Other executable names. Tags only help search. */
|
|
6
|
+
aliases: readonly string[];
|
|
7
|
+
tags: readonly string[];
|
|
8
|
+
takesArg?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface ParsedCommand {
|
|
11
|
+
name: string;
|
|
12
|
+
/** Undefined before a space; empty after a space with no argument yet. */
|
|
13
|
+
arg?: string;
|
|
14
|
+
}
|
|
15
|
+
/** Multiline text and a slash inside a message are not commands. */
|
|
16
|
+
export declare function parseCommand(text: string): ParsedCommand | null;
|
|
17
|
+
export type CommandResolution<T extends SlashCommand = SlashCommand> = {
|
|
18
|
+
kind: "matched";
|
|
19
|
+
command: T;
|
|
20
|
+
} | {
|
|
21
|
+
kind: "unknown";
|
|
22
|
+
name: string;
|
|
23
|
+
} | {
|
|
24
|
+
kind: "ambiguous";
|
|
25
|
+
name: string;
|
|
26
|
+
commands: readonly T[];
|
|
27
|
+
};
|
|
28
|
+
/** Exact names win over aliases. Search tags never execute an action. */
|
|
29
|
+
export declare function resolveCommand<T extends SlashCommand>(commands: readonly T[], name: string): CommandResolution<T>;
|
|
30
|
+
/** One row per name, in registry order when two results have the same rank. */
|
|
31
|
+
export declare function matchCommands<T extends SlashCommand>(commands: readonly T[], query: string): T[];
|
|
32
|
+
/** Replace only the name, keeping the argument already typed. */
|
|
33
|
+
export declare function completeCommand(command: SlashCommand, text: string): string;
|
|
34
|
+
export type CommandProblem<T extends SlashCommand = SlashCommand> = Exclude<CommandResolution<T>, {
|
|
35
|
+
kind: "matched";
|
|
36
|
+
}>;
|
|
37
|
+
export interface CommandOptions<T extends SlashCommand> {
|
|
38
|
+
text: string;
|
|
39
|
+
/** Supply only commands allowed here, with current translated descriptions and tags. */
|
|
40
|
+
commands: readonly T[];
|
|
41
|
+
onChange: (text: string) => void;
|
|
42
|
+
/** The app owns authorization, errors, draft cleanup and attachments. */
|
|
43
|
+
onExecute: (command: T, arg: string | undefined, altKey: boolean) => void;
|
|
44
|
+
/** Required: show a way back to the available commands, never send this text to the model. */
|
|
45
|
+
onError: (problem: CommandProblem<T>) => void;
|
|
46
|
+
}
|
|
47
|
+
/** Structural so React events and small test events both fit. */
|
|
48
|
+
export interface CommandKeyEvent {
|
|
49
|
+
key: string;
|
|
50
|
+
shiftKey?: boolean;
|
|
51
|
+
altKey?: boolean;
|
|
52
|
+
ctrlKey?: boolean;
|
|
53
|
+
metaKey?: boolean;
|
|
54
|
+
isComposing?: boolean;
|
|
55
|
+
keyCode?: number;
|
|
56
|
+
nativeEvent?: {
|
|
57
|
+
isComposing?: boolean;
|
|
58
|
+
keyCode?: number;
|
|
59
|
+
};
|
|
60
|
+
preventDefault: () => void;
|
|
61
|
+
}
|
|
62
|
+
export interface SlashCommandMenu<T extends SlashCommand = SlashCommand> {
|
|
63
|
+
parsed: ParsedCommand | null;
|
|
64
|
+
open: boolean;
|
|
65
|
+
items: readonly T[];
|
|
66
|
+
index: number;
|
|
67
|
+
active: T | undefined;
|
|
68
|
+
listId: string;
|
|
69
|
+
itemId: (command: T) => string;
|
|
70
|
+
activeId: string | undefined;
|
|
71
|
+
highlight: (index: number) => void;
|
|
72
|
+
close: () => void;
|
|
73
|
+
showAll: () => void;
|
|
74
|
+
pick: (command: T, altKey?: boolean) => void;
|
|
75
|
+
submit: (altKey?: boolean) => boolean;
|
|
76
|
+
handleKeyDown: (event: CommandKeyEvent) => boolean;
|
|
77
|
+
}
|
|
78
|
+
/** A controlled textarea menu. No UI, browser globals, timers or attachment state. */
|
|
79
|
+
export declare function useSlashCommands<T extends SlashCommand>({ text, commands, onChange, onExecute, onError, }: CommandOptions<T>): SlashCommandMenu<T>;
|
|
80
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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"}
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { useId, useState } from "react";
|
|
2
|
+
/** Multiline text and a slash inside a message are not commands. */
|
|
3
|
+
export function parseCommand(text) {
|
|
4
|
+
if (/[\r\n\u{2028}\u{2029}]/u.test(text))
|
|
5
|
+
return null;
|
|
6
|
+
const match = /^\s*\/(\S*)(?:[^\S\r\n]+(.*))?$/u.exec(text);
|
|
7
|
+
if (!match)
|
|
8
|
+
return null;
|
|
9
|
+
const name = (match[1] ?? "").toLowerCase();
|
|
10
|
+
return match[2] === undefined ? { name } : { name, arg: match[2] };
|
|
11
|
+
}
|
|
12
|
+
/** Exact names win over aliases. Search tags never execute an action. */
|
|
13
|
+
export function resolveCommand(commands, name) {
|
|
14
|
+
const token = name.toLowerCase();
|
|
15
|
+
if (!token)
|
|
16
|
+
return { kind: "unknown", name };
|
|
17
|
+
let matches = commands.filter((command) => command.name.toLowerCase() === token);
|
|
18
|
+
if (matches.length === 0) {
|
|
19
|
+
matches = commands.filter((command) => command.aliases.some((alias) => alias.toLowerCase() === token));
|
|
20
|
+
}
|
|
21
|
+
if (matches.length > 1)
|
|
22
|
+
return { kind: "ambiguous", name, commands: matches };
|
|
23
|
+
const command = matches[0];
|
|
24
|
+
return command ? { kind: "matched", command } : { kind: "unknown", name };
|
|
25
|
+
}
|
|
26
|
+
function searchText(text) {
|
|
27
|
+
return text.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase();
|
|
28
|
+
}
|
|
29
|
+
function rank(command, query) {
|
|
30
|
+
if (!query)
|
|
31
|
+
return 0;
|
|
32
|
+
const name = searchText(command.name);
|
|
33
|
+
const aliases = command.aliases.map(searchText);
|
|
34
|
+
if (name === query)
|
|
35
|
+
return 0;
|
|
36
|
+
if (aliases.includes(query))
|
|
37
|
+
return 1;
|
|
38
|
+
if (name.startsWith(query))
|
|
39
|
+
return 2;
|
|
40
|
+
if (aliases.some((alias) => alias.startsWith(query)))
|
|
41
|
+
return 3;
|
|
42
|
+
if (command.tags.some((tag) => searchText(tag).includes(query)))
|
|
43
|
+
return 4;
|
|
44
|
+
if (searchText(command.description).includes(query))
|
|
45
|
+
return 5;
|
|
46
|
+
return Infinity;
|
|
47
|
+
}
|
|
48
|
+
/** One row per name, in registry order when two results have the same rank. */
|
|
49
|
+
export function matchCommands(commands, query) {
|
|
50
|
+
const token = searchText(query.trim());
|
|
51
|
+
const names = new Set();
|
|
52
|
+
return commands
|
|
53
|
+
.filter((command) => {
|
|
54
|
+
const name = command.name.toLowerCase();
|
|
55
|
+
if (names.has(name))
|
|
56
|
+
return false;
|
|
57
|
+
names.add(name);
|
|
58
|
+
return true;
|
|
59
|
+
})
|
|
60
|
+
.map((command) => ({ command, rank: rank(command, token) }))
|
|
61
|
+
.filter((match) => Number.isFinite(match.rank))
|
|
62
|
+
.sort((a, b) => a.rank - b.rank)
|
|
63
|
+
.map((match) => match.command);
|
|
64
|
+
}
|
|
65
|
+
/** Replace only the name, keeping the argument already typed. */
|
|
66
|
+
export function completeCommand(command, text) {
|
|
67
|
+
const arg = parseCommand(text)?.arg;
|
|
68
|
+
if (arg !== undefined)
|
|
69
|
+
return `/${command.name} ${arg}`;
|
|
70
|
+
return `/${command.name}${command.takesArg ? " " : ""}`;
|
|
71
|
+
}
|
|
72
|
+
/** A controlled textarea menu. No UI, browser globals, timers or attachment state. */
|
|
73
|
+
export function useSlashCommands({ text, commands, onChange, onExecute, onError, }) {
|
|
74
|
+
const parsed = parseCommand(text);
|
|
75
|
+
const resolution = parsed ? resolveCommand(commands, parsed.name) : null;
|
|
76
|
+
const editingName = parsed !== null && (parsed.arg === undefined || resolution?.kind !== "matched");
|
|
77
|
+
const items = editingName ? matchCommands(commands, parsed.name) : [];
|
|
78
|
+
const key = JSON.stringify([text, items.map((command) => command.name)]);
|
|
79
|
+
const [state, setState] = useState({ key, text, index: 0, deliberate: false, dismissed: false });
|
|
80
|
+
// Reset selection on a changed result set, but keep Escape's dismissal until the draft changes.
|
|
81
|
+
if (state.key !== key) {
|
|
82
|
+
setState({
|
|
83
|
+
key,
|
|
84
|
+
text,
|
|
85
|
+
index: 0,
|
|
86
|
+
deliberate: false,
|
|
87
|
+
dismissed: state.text === text && state.dismissed,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
const listId = `${useId()}-commands`;
|
|
91
|
+
const open = editingName && !state.dismissed;
|
|
92
|
+
const active = open ? items[state.index] : undefined;
|
|
93
|
+
const itemId = (command) => `${listId}-${encodeURIComponent(command.name)}`;
|
|
94
|
+
function close() {
|
|
95
|
+
setState((current) => ({ ...current, dismissed: true }));
|
|
96
|
+
}
|
|
97
|
+
function showAll() {
|
|
98
|
+
setState((current) => ({ ...current, index: 0, deliberate: false, dismissed: false }));
|
|
99
|
+
onChange("/");
|
|
100
|
+
}
|
|
101
|
+
function highlight(index) {
|
|
102
|
+
if (!items[index])
|
|
103
|
+
return;
|
|
104
|
+
setState((current) => ({ ...current, index, deliberate: true }));
|
|
105
|
+
}
|
|
106
|
+
function pick(command, altKey = false) {
|
|
107
|
+
// Re-resolve against current capabilities, not a descriptor retained by a caller.
|
|
108
|
+
const selected = resolveCommand(commands, command.name);
|
|
109
|
+
if (selected.kind !== "matched") {
|
|
110
|
+
onError(selected);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (selected.command.takesArg || parsed?.arg) {
|
|
114
|
+
onChange(completeCommand(selected.command, text));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
onExecute(selected.command, parsed?.arg, altKey);
|
|
118
|
+
}
|
|
119
|
+
/** Call before the normal send/queue/quota path, including button and imperative submits. */
|
|
120
|
+
function submit(altKey = false) {
|
|
121
|
+
if (!parsed || !resolution)
|
|
122
|
+
return false;
|
|
123
|
+
if (resolution.kind !== "matched") {
|
|
124
|
+
onError(resolution);
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
if (resolution.command.takesArg &&
|
|
128
|
+
parsed.arg === undefined &&
|
|
129
|
+
parsed.name !== resolution.command.name.toLowerCase()) {
|
|
130
|
+
onChange(completeCommand(resolution.command, text));
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
onExecute(resolution.command, parsed.arg, altKey);
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
/** Call first. True means skip your own key handler; IME still gets the browser's default. */
|
|
137
|
+
function handleKeyDown(event) {
|
|
138
|
+
const native = event.nativeEvent ?? event;
|
|
139
|
+
if (native.isComposing || native.keyCode === 229)
|
|
140
|
+
return true;
|
|
141
|
+
if (event.shiftKey)
|
|
142
|
+
return false;
|
|
143
|
+
if (open && event.key === "Escape") {
|
|
144
|
+
event.preventDefault();
|
|
145
|
+
close();
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
if (open && items.length > 0 && !event.ctrlKey && !event.metaKey) {
|
|
149
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
150
|
+
event.preventDefault();
|
|
151
|
+
const step = event.key === "ArrowDown" ? 1 : -1;
|
|
152
|
+
highlight((state.index + step + items.length) % items.length);
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
if (active && (event.key === "Enter" || event.key === "Tab")) {
|
|
156
|
+
event.preventDefault();
|
|
157
|
+
if (event.key === "Tab") {
|
|
158
|
+
onChange(completeCommand(active, text));
|
|
159
|
+
}
|
|
160
|
+
else if (resolution?.kind === "ambiguous" && !state.deliberate) {
|
|
161
|
+
onError(resolution);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
pick(active, event.altKey);
|
|
165
|
+
}
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (parsed && event.key === "Enter") {
|
|
170
|
+
event.preventDefault();
|
|
171
|
+
return submit(event.altKey);
|
|
172
|
+
}
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
parsed,
|
|
177
|
+
open,
|
|
178
|
+
items,
|
|
179
|
+
index: state.index,
|
|
180
|
+
active,
|
|
181
|
+
listId,
|
|
182
|
+
itemId,
|
|
183
|
+
activeId: active ? itemId(active) : undefined,
|
|
184
|
+
highlight,
|
|
185
|
+
close,
|
|
186
|
+
showAll,
|
|
187
|
+
pick,
|
|
188
|
+
submit,
|
|
189
|
+
handleKeyDown,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAkBxC,oEAAoE;AACpE,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,IAAI,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,MAAM,KAAK,GAAG,kCAAkC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5D,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IAC5C,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACrE,CAAC;AAOD,yEAAyE;AACzE,MAAM,UAAU,cAAc,CAC5B,QAAsB,EACtB,IAAY;IAEZ,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACjC,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAC7C,IAAI,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,CAAC;IACjF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CACpC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,CAC/D,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAC9E,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3B,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AAC5E,CAAC;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AACpE,CAAC;AAED,SAAS,IAAI,CAAC,OAAqB,EAAE,KAAa;IAChD,IAAI,CAAC,KAAK;QAAE,OAAO,CAAC,CAAC;IACrB,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAChD,IAAI,IAAI,KAAK,KAAK;QAAE,OAAO,CAAC,CAAC;IAC7B,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACtC,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACrC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IAC/D,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IAC1E,IAAI,UAAU,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAC9D,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,aAAa,CAAyB,QAAsB,EAAE,KAAa;IACzF,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,OAAO,QAAQ;SACZ,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE;QAClB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QACxC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QAClC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAChB,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;SACD,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;SAC3D,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;SAC9C,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC;SAC/B,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AACnC,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,eAAe,CAAC,OAAqB,EAAE,IAAY;IACjE,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC;IACpC,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,IAAI,OAAO,CAAC,IAAI,IAAI,GAAG,EAAE,CAAC;IACxD,OAAO,IAAI,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AAC1D,CAAC;AAgDD,sFAAsF;AACtF,MAAM,UAAU,gBAAgB,CAAyB,EACvD,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,OAAO,GACW;IAClB,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACzE,MAAM,WAAW,GACf,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,KAAK,SAAS,IAAI,UAAU,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC;IAClF,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACtE,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACzE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;IACjG,gGAAgG;IAChG,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC;QACtB,QAAQ,CAAC;YACP,GAAG;YACH,IAAI;YACJ,KAAK,EAAE,CAAC;YACR,UAAU,EAAE,KAAK;YACjB,SAAS,EAAE,KAAK,CAAC,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,SAAS;SAClD,CAAC,CAAC;IACL,CAAC;IAED,MAAM,MAAM,GAAG,GAAG,KAAK,EAAE,WAAW,CAAC;IACrC,MAAM,IAAI,GAAG,WAAW,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC;IAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACrD,MAAM,MAAM,GAAG,CAAC,OAAU,EAAE,EAAE,CAAC,GAAG,MAAM,IAAI,kBAAkB,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;IAE/E,SAAS,KAAK;QACZ,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC3D,CAAC;IAED,SAAS,OAAO;QACd,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QACvF,QAAQ,CAAC,GAAG,CAAC,CAAC;IAChB,CAAC;IAED,SAAS,SAAS,CAAC,KAAa;QAC9B,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;YAAE,OAAO;QAC1B,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACnE,CAAC;IAED,SAAS,IAAI,CAAC,OAAU,EAAE,MAAM,GAAG,KAAK;QACtC,kFAAkF;QAClF,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QACxD,IAAI,QAAQ,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAChC,OAAO,CAAC,QAAQ,CAAC,CAAC;YAClB,OAAO;QACT,CAAC;QACD,IAAI,QAAQ,CAAC,OAAO,CAAC,QAAQ,IAAI,MAAM,EAAE,GAAG,EAAE,CAAC;YAC7C,QAAQ,CAAC,eAAe,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;YAClD,OAAO;QACT,CAAC;QACD,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;IACnD,CAAC;IAED,6FAA6F;IAC7F,SAAS,MAAM,CAAC,MAAM,GAAG,KAAK;QAC5B,IAAI,CAAC,MAAM,IAAI,CAAC,UAAU;YAAE,OAAO,KAAK,CAAC;QACzC,IAAI,UAAU,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAClC,OAAO,CAAC,UAAU,CAAC,CAAC;YACpB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IACE,UAAU,CAAC,OAAO,CAAC,QAAQ;YAC3B,MAAM,CAAC,GAAG,KAAK,SAAS;YACxB,MAAM,CAAC,IAAI,KAAK,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,EACrD,CAAC;YACD,QAAQ,CAAC,eAAe,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;YACpD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,SAAS,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAClD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,8FAA8F;IAC9F,SAAS,aAAa,CAAC,KAAsB;QAC3C,MAAM,MAAM,GAAG,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC;QAC1C,IAAI,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,OAAO,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAC9D,IAAI,KAAK,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;QACjC,IAAI,IAAI,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;YACnC,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,KAAK,EAAE,CAAC;YACR,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,IAAI,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;YACjE,IAAI,KAAK,CAAC,GAAG,KAAK,WAAW,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;gBACzD,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAChD,SAAS,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;gBAC9D,OAAO,IAAI,CAAC;YACd,CAAC;YACD,IAAI,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,KAAK,OAAO,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,CAAC,EAAE,CAAC;gBAC7D,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,EAAE,CAAC;oBACxB,QAAQ,CAAC,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;gBAC1C,CAAC;qBAAM,IAAI,UAAU,EAAE,IAAI,KAAK,WAAW,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;oBACjE,OAAO,CAAC,UAAU,CAAC,CAAC;gBACtB,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;gBAC7B,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QACD,IAAI,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC;YACpC,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC9B,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO;QACL,MAAM;QACN,IAAI;QACJ,KAAK;QACL,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,MAAM;QACN,MAAM;QACN,MAAM;QACN,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS;QAC7C,SAAS;QACT,KAAK;QACL,OAAO;QACP,IAAI;QACJ,MAAM;QACN,aAAa;KACd,CAAC;AACJ,CAAC"}
|
package/dist/ui/menu.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Menu as Primitive } from "@base-ui/react/menu";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
|
-
export declare const Menu:
|
|
3
|
+
export declare const Menu: typeof Primitive.Root;
|
|
4
4
|
export declare const MenuTrigger: Primitive.Trigger;
|
|
5
5
|
export declare const MenuPortal: import("react").ForwardRefExoticComponent<Omit<import("@base-ui/react").ContextMenuPortalProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
6
6
|
export declare const MenuPositioner: import("react").ForwardRefExoticComponent<Omit<import("@base-ui/react").MenuPositionerProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
package/dist/ui/menu.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../src/ui/menu.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAavC,eAAO,MAAM,IAAI,
|
|
1
|
+
{"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../src/ui/menu.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAavC,eAAO,MAAM,IAAI,uBAAiB,CAAC;AACnC,eAAO,MAAM,WAAW,mBAAoB,CAAC;AAC7C,eAAO,MAAM,UAAU,yJAAmB,CAAC;AAC3C,eAAO,MAAM,cAAc,sJAAuB,CAAC;AACnD,eAAO,MAAM,SAAS,wJAAkB,CAAC;AACzC,eAAO,MAAM,YAAY,2JAAqB,CAAC;AAC/C,eAAO,MAAM,SAAS,wJAAkB,CAAC;AACzC,eAAO,MAAM,YAAY,oJAAqB,CAAC;AAC/C,eAAO,MAAM,gBAAgB,4JAAyB,CAAC;AACvD,eAAO,MAAM,yBAAyB,yKAAkC,CAAC;AACzE,eAAO,MAAM,sBAAsB,sKAA+B,CAAC;AACnE,eAAO,MAAM,eAAe,8BAAwB,CAAC;AACrD,eAAO,MAAM,kBAAkB,8JAA2B,CAAC;AAE3D,MAAM,MAAM,gBAAgB,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,GAAG;IACrD;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,SAAS,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAChD;;;;;OAKG;IACH,eAAe,CAAC,EAAE,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC;CAC9C,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,EAC1B,SAAS,EACT,QAAQ,EACR,KAAK,EACL,SAAS,EACT,eAAe,EACf,GAAG,IAAI,EACR,EAAE,gBAAgB,+BA6BlB;AAcD,wBAAgB,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,SAAS,CAAC,IAAI,CAAC,KAAK,+BAEpE;AAED,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,SAAS,CAAC,SAAS,CAAC,KAAK,+BAI9E;AAED,oFAAoF;AACpF,KAAK,gBAAgB,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAa3C;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,SAAS,CAAC,KAAK,CAAC,KAAK,GAAG,gBAAgB,+BAS/F;AAED,sGAAsG;AACtG,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,QAAQ,EACR,GAAG,IAAI,EACR,EAAE,SAAS,CAAC,UAAU,CAAC,KAAK,GAAG,gBAAgB,+BAS/C;AAED,MAAM,MAAM,kBAAkB,GAAG,SAAS,CAAC,SAAS,CAAC,KAAK,GAAG;IAC3D,gCAAgC;IAChC,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,kBAAkB,+BAY5F"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gusnips/react",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.26",
|
|
4
4
|
"description": "The headless runtime under a Vite + React SPA: typed fetch client with single-flight refresh, auth store, query defaults, route guards, error boundary, stale-tab recovery, i18n bootstrap.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,6 +41,10 @@
|
|
|
41
41
|
"./theme": {
|
|
42
42
|
"types": "./dist/theme.d.ts",
|
|
43
43
|
"default": "./dist/theme.js"
|
|
44
|
+
},
|
|
45
|
+
"./commands": {
|
|
46
|
+
"types": "./dist/commands.d.ts",
|
|
47
|
+
"default": "./dist/commands.js"
|
|
44
48
|
}
|
|
45
49
|
},
|
|
46
50
|
"types": "./dist/index.d.ts",
|