@adea-ai/ui 0.120.0 → 0.121.0
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/components/conversation/composer-menu.d.ts +31 -0
- package/dist/components/conversation/composer-menu.d.ts.map +1 -0
- package/dist/components/conversation/composer-menu.js +63 -0
- package/dist/components/conversation/composer-menu.js.map +1 -0
- package/dist/components/conversation/conversation-pane.d.ts +38 -0
- package/dist/components/conversation/conversation-pane.d.ts.map +1 -0
- package/dist/components/conversation/conversation-pane.js +83 -0
- package/dist/components/conversation/conversation-pane.js.map +1 -0
- package/dist/components/conversation/conversation-surface.d.ts +6 -0
- package/dist/components/conversation/conversation-surface.d.ts.map +1 -1
- package/dist/components/conversation/conversation-surface.js +10 -4
- package/dist/components/conversation/conversation-surface.js.map +1 -1
- package/dist/components/conversation/index.d.ts +2 -0
- package/dist/components/conversation/index.d.ts.map +1 -1
- package/dist/components/conversation/index.js +3 -1
- package/dist/components/conversation/message-composer.d.ts +6 -0
- package/dist/components/conversation/message-composer.d.ts.map +1 -1
- package/dist/components/conversation/message-composer.js +10 -1
- package/dist/components/conversation/message-composer.js.map +1 -1
- package/dist/components/ui/kbd/index.d.ts +1 -0
- package/dist/components/ui/kbd/index.d.ts.map +1 -1
- package/dist/components/ui/kbd/index.js +2 -1
- package/dist/components/ui/kbd/platform-modifier.d.ts +28 -0
- package/dist/components/ui/kbd/platform-modifier.d.ts.map +1 -0
- package/dist/components/ui/kbd/platform-modifier.js +37 -0
- package/dist/components/ui/kbd/platform-modifier.js.map +1 -0
- package/dist/index.js +4 -1
- package/dist/r/conversation.json +10 -0
- package/dist/r/kbd.json +5 -0
- package/dist/r/registry.json +15 -0
- package/dist/r/src/components/conversation/composer-menu.tsx +67 -0
- package/dist/r/src/components/conversation/conversation-pane.tsx +111 -0
- package/dist/r/src/components/conversation/conversation-surface.tsx +12 -1
- package/dist/r/src/components/conversation/index.ts +7 -0
- package/dist/r/src/components/conversation/message-composer.tsx +8 -0
- package/dist/r/src/components/ui/kbd/index.ts +5 -0
- package/dist/r/src/components/ui/kbd/platform-modifier.ts +37 -0
- package/package.json +1 -1
- package/registry.json +15 -0
- package/src/components/conversation/composer-menu.tsx +67 -0
- package/src/components/conversation/conversation-pane.tsx +111 -0
- package/src/components/conversation/conversation-surface.tsx +12 -1
- package/src/components/conversation/index.ts +7 -0
- package/src/components/conversation/message-composer.tsx +8 -0
- package/src/components/ui/kbd/index.ts +5 -0
- package/src/components/ui/kbd/platform-modifier.ts +37 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ComponentProps } from 'solid-js';
|
|
2
|
+
import { Button } from '../ui/button/button';
|
|
3
|
+
export type ComposerMenuProps = ComponentProps<'div'> & {
|
|
4
|
+
/** A name for what the menu offers, e.g. "Mention an Agent". */
|
|
5
|
+
label?: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* ComposerMenu.
|
|
9
|
+
*
|
|
10
|
+
* The panel a composer raises above the field: mention suggestions while the
|
|
11
|
+
* reader types a name, the picker of what can be attached, follow-up prompts.
|
|
12
|
+
* It is deliberately *not* a `Menu`: there is no trigger and no focus trap —
|
|
13
|
+
* the reader keeps typing while it is open, and it opens and closes on the
|
|
14
|
+
* draft's own grammar rather than on a button press.
|
|
15
|
+
*
|
|
16
|
+
* Rows are `ComposerMenuItem`s; anything else a caller renders (a checkbox
|
|
17
|
+
* list, a heading) sits inside the same panel. Placement stays with the host —
|
|
18
|
+
* a suggestion menu renders in the composer's `menu` slot, a picker anchors to
|
|
19
|
+
* its own control.
|
|
20
|
+
*/
|
|
21
|
+
export declare function ComposerMenu(props: ComposerMenuProps): import("solid-js").JSX.Element;
|
|
22
|
+
export type ComposerMenuItemProps = ComponentProps<typeof Button>;
|
|
23
|
+
/**
|
|
24
|
+
* ComposerMenuItem.
|
|
25
|
+
*
|
|
26
|
+
* One row of a `ComposerMenu`. A ghost button stretched across the panel —
|
|
27
|
+
* the same row every menu in the library draws, so a suggestion never reads as
|
|
28
|
+
* a stray button floating over the draft.
|
|
29
|
+
*/
|
|
30
|
+
export declare function ComposerMenuItem(props: ComposerMenuItemProps): import("solid-js").JSX.Element;
|
|
31
|
+
//# sourceMappingURL=composer-menu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composer-menu.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/composer-menu.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAI9C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAE5C,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG;IACtD,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,kCAkBpD;AAED,MAAM,MAAM,qBAAqB,GAAG,cAAc,CAAC,OAAO,MAAM,CAAC,CAAA;AAEjE;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,kCAY5D"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { cn } from "../../lib/utils.js";
|
|
2
|
+
import { overlaySurface } from "../../lib/overlay.js";
|
|
3
|
+
import { Button } from "../ui/button/button.js";
|
|
4
|
+
import { createComponent, insert, mergeProps, spread, template } from "solid-js/web";
|
|
5
|
+
import { splitProps } from "solid-js";
|
|
6
|
+
//#region src/components/conversation/composer-menu.tsx
|
|
7
|
+
var _tmpl$ = /*#__PURE__*/ template(`<div role=group data-slot=composer-menu>`);
|
|
8
|
+
/**
|
|
9
|
+
* ComposerMenu.
|
|
10
|
+
*
|
|
11
|
+
* The panel a composer raises above the field: mention suggestions while the
|
|
12
|
+
* reader types a name, the picker of what can be attached, follow-up prompts.
|
|
13
|
+
* It is deliberately *not* a `Menu`: there is no trigger and no focus trap —
|
|
14
|
+
* the reader keeps typing while it is open, and it opens and closes on the
|
|
15
|
+
* draft's own grammar rather than on a button press.
|
|
16
|
+
*
|
|
17
|
+
* Rows are `ComposerMenuItem`s; anything else a caller renders (a checkbox
|
|
18
|
+
* list, a heading) sits inside the same panel. Placement stays with the host —
|
|
19
|
+
* a suggestion menu renders in the composer's `menu` slot, a picker anchors to
|
|
20
|
+
* its own control.
|
|
21
|
+
*/
|
|
22
|
+
function ComposerMenu(props) {
|
|
23
|
+
const [local, rest] = splitProps(props, [
|
|
24
|
+
"class",
|
|
25
|
+
"label",
|
|
26
|
+
"children"
|
|
27
|
+
]);
|
|
28
|
+
return (() => {
|
|
29
|
+
var _el$ = _tmpl$();
|
|
30
|
+
spread(_el$, mergeProps({
|
|
31
|
+
get ["aria-label"]() {
|
|
32
|
+
return local.label;
|
|
33
|
+
},
|
|
34
|
+
get ["class"]() {
|
|
35
|
+
return cn(overlaySurface, "flex max-h-56 min-w-60 flex-col gap-0.5 overflow-y-auto p-1", local.class);
|
|
36
|
+
}
|
|
37
|
+
}, rest), false, true);
|
|
38
|
+
insert(_el$, () => local.children);
|
|
39
|
+
return _el$;
|
|
40
|
+
})();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* ComposerMenuItem.
|
|
44
|
+
*
|
|
45
|
+
* One row of a `ComposerMenu`. A ghost button stretched across the panel —
|
|
46
|
+
* the same row every menu in the library draws, so a suggestion never reads as
|
|
47
|
+
* a stray button floating over the draft.
|
|
48
|
+
*/
|
|
49
|
+
function ComposerMenuItem(props) {
|
|
50
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
51
|
+
return createComponent(Button, mergeProps({
|
|
52
|
+
variant: "ghost",
|
|
53
|
+
get ["class"]() {
|
|
54
|
+
return cn("w-full justify-start gap-2 text-start font-normal", local.class);
|
|
55
|
+
}
|
|
56
|
+
}, rest, { get children() {
|
|
57
|
+
return local.children;
|
|
58
|
+
} }));
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
export { ComposerMenu, ComposerMenuItem };
|
|
62
|
+
|
|
63
|
+
//# sourceMappingURL=composer-menu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composer-menu.js","names":["ComponentProps","splitProps","cn","overlaySurface","Button","ComposerMenuProps","label","ComposerMenu","props","local","rest","_el$","_tmpl$","_$spread","_$mergeProps","aria-label","class","_$insert","children","ComposerMenuItemProps","ComposerMenuItem","_$createComponent","variant","_$mergeProps","_$createComponent"],"sources":["../../../src/components/conversation/composer-menu.tsx"],"sourcesContent":["import type { ComponentProps } from 'solid-js'\nimport { splitProps } from 'solid-js'\nimport { cn } from '#lib/utils'\nimport { overlaySurface } from '#lib/overlay'\nimport { Button } from '../ui/button/button'\n\nexport type ComposerMenuProps = ComponentProps<'div'> & {\n /** A name for what the menu offers, e.g. \"Mention an Agent\". */\n label?: string\n}\n\n/**\n * ComposerMenu.\n *\n * The panel a composer raises above the field: mention suggestions while the\n * reader types a name, the picker of what can be attached, follow-up prompts.\n * It is deliberately *not* a `Menu`: there is no trigger and no focus trap —\n * the reader keeps typing while it is open, and it opens and closes on the\n * draft's own grammar rather than on a button press.\n *\n * Rows are `ComposerMenuItem`s; anything else a caller renders (a checkbox\n * list, a heading) sits inside the same panel. Placement stays with the host —\n * a suggestion menu renders in the composer's `menu` slot, a picker anchors to\n * its own control.\n */\nexport function ComposerMenu(props: ComposerMenuProps) {\n const [local, rest] = splitProps(props, ['class', 'label', 'children'])\n\n return (\n <div\n role=\"group\"\n aria-label={local.label}\n data-slot=\"composer-menu\"\n class={cn(\n overlaySurface,\n 'flex max-h-56 min-w-60 flex-col gap-0.5 overflow-y-auto p-1',\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n )\n}\n\nexport type ComposerMenuItemProps = ComponentProps<typeof Button>\n\n/**\n * ComposerMenuItem.\n *\n * One row of a `ComposerMenu`. A ghost button stretched across the panel —\n * the same row every menu in the library draws, so a suggestion never reads as\n * a stray button floating over the draft.\n */\nexport function ComposerMenuItem(props: ComposerMenuItemProps) {\n const [local, rest] = splitProps(props, ['class', 'children'])\n\n return (\n <Button\n variant=\"ghost\"\n class={cn('w-full justify-start gap-2 text-start font-normal', local.class)}\n {...rest}\n >\n {local.children}\n </Button>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgBO,aAAaC,OAA0B;CACrD,MAAM,CAACC,OAAOC,QAAQT,WAAWO,OAAO;EAAC;EAAS;EAAS;CAAU,CAAC;CAEtE,cAAA;EAAA,IAAAG,OAAAC,OAAA;EAAAC,OAAAF,MAAAG,WAAA;GAAA,KAAA,gBAAA;IAAA,OAGgBL,MAAMH;GAAK;GAAA,KAAA,WAAA;IAAA,OAEhBJ,GACLC,gBACA,+DACAM,MAAMO,KACR;GAAC;EAAA,GACGN,IAAI,GAAA,OAAA,IAAA;EAAAO,OAAAN,YAEPF,MAAMS,QAAQ;EAAA,OAAAP;CAAA,EAAA,CAAA;AAGrB;;;;;;;;AAWA,SAAgBS,iBAAiBZ,OAA8B;CAC7D,MAAM,CAACC,OAAOC,QAAQT,WAAWO,OAAO,CAAC,SAAS,UAAU,CAAC;CAE7D,OAAAa,gBACGjB,QAAMU,WAAA;EACLQ,SAAO;EAAA,KAAA,WAAA;GAAA,OACApB,GAAG,qDAAqDO,MAAMO,KAAK;EAAC;CAAA,GACvEN,MAAI,EAAA,IAAAQ,WAAA;EAAA,OAEPT,MAAMS;CAAQ,EAAA,CAAA,CAAA;AAGrB"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ComponentProps, JSX } from 'solid-js';
|
|
2
|
+
export type ConversationPaneProps = ComponentProps<'section'> & {
|
|
3
|
+
/** Above the transcript: the conversation's identity and its actions. */
|
|
4
|
+
header?: JSX.Element;
|
|
5
|
+
/** The transcript — typically a `ConversationSurface` with `gutter`. */
|
|
6
|
+
children: JSX.Element;
|
|
7
|
+
/** Below the transcript: the composer. */
|
|
8
|
+
composer?: JSX.Element;
|
|
9
|
+
/**
|
|
10
|
+
* The thread side panel. Its presence opens the second column; at narrow
|
|
11
|
+
* widths the panel overlays the conversation instead of squeezing it, and
|
|
12
|
+
* the conversation chrome steps aside — out of the accessibility tree with
|
|
13
|
+
* it, which is why the overlay hides with `visibility` rather than paint.
|
|
14
|
+
*/
|
|
15
|
+
thread?: JSX.Element;
|
|
16
|
+
/**
|
|
17
|
+
* Apply the shared page gutters to the composer, tracking the transcript's
|
|
18
|
+
* own gutter (`ConversationSurface`'s `gutter`) so the two read as one pane.
|
|
19
|
+
*/
|
|
20
|
+
gutter?: boolean;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* ConversationPane.
|
|
24
|
+
*
|
|
25
|
+
* The frame a conversation lives in: a header above, the transcript, a composer
|
|
26
|
+
* below, and — while a thread is open — the thread panel beside all of it.
|
|
27
|
+
*
|
|
28
|
+
* The layout earns its place in the library because the thread is where
|
|
29
|
+
* applications get it wrong. Two impulses both fail: shrinking the transcript
|
|
30
|
+
* to make room turns a busy thread into a one-word-per-line stripe, and
|
|
31
|
+
* *hiding* the transcript with `display: none` unmounts it, losing scroll
|
|
32
|
+
* position and DOM. So the desktop layout gives the thread a fixed column and
|
|
33
|
+
* leaves the transcript fluid; below the collapse width the panel overlays the
|
|
34
|
+
* conversation in full and the conversation steps aside invisibly — still
|
|
35
|
+
* mounted, still in the DOM, gone from the accessibility tree.
|
|
36
|
+
*/
|
|
37
|
+
export declare function ConversationPane(props: ConversationPaneProps): JSX.Element;
|
|
38
|
+
//# sourceMappingURL=conversation-pane.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-pane.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/conversation-pane.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAInD,MAAM,MAAM,qBAAqB,GAAG,cAAc,CAAC,SAAS,CAAC,GAAG;IAC9D,yEAAyE;IACzE,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACpB,wEAAwE;IACxE,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAA;IACrB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACtB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,eAsE5D"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { cn } from "../../lib/utils.js";
|
|
2
|
+
import { className, createComponent, effect, insert, mergeProps, spread, template } from "solid-js/web";
|
|
3
|
+
import { Show, splitProps } from "solid-js";
|
|
4
|
+
//#region src/components/conversation/conversation-pane.tsx
|
|
5
|
+
var _tmpl$ = /*#__PURE__*/ template(`<section data-slot=conversation-pane><div>`);
|
|
6
|
+
var _tmpl$2 = /*#__PURE__*/ template(`<div>`);
|
|
7
|
+
/**
|
|
8
|
+
* ConversationPane.
|
|
9
|
+
*
|
|
10
|
+
* The frame a conversation lives in: a header above, the transcript, a composer
|
|
11
|
+
* below, and — while a thread is open — the thread panel beside all of it.
|
|
12
|
+
*
|
|
13
|
+
* The layout earns its place in the library because the thread is where
|
|
14
|
+
* applications get it wrong. Two impulses both fail: shrinking the transcript
|
|
15
|
+
* to make room turns a busy thread into a one-word-per-line stripe, and
|
|
16
|
+
* *hiding* the transcript with `display: none` unmounts it, losing scroll
|
|
17
|
+
* position and DOM. So the desktop layout gives the thread a fixed column and
|
|
18
|
+
* leaves the transcript fluid; below the collapse width the panel overlays the
|
|
19
|
+
* conversation in full and the conversation steps aside invisibly — still
|
|
20
|
+
* mounted, still in the DOM, gone from the accessibility tree.
|
|
21
|
+
*/
|
|
22
|
+
function ConversationPane(props) {
|
|
23
|
+
const [local, rest] = splitProps(props, [
|
|
24
|
+
"class",
|
|
25
|
+
"header",
|
|
26
|
+
"composer",
|
|
27
|
+
"thread",
|
|
28
|
+
"gutter",
|
|
29
|
+
"children"
|
|
30
|
+
]);
|
|
31
|
+
const threadOpen = () => local.thread !== void 0;
|
|
32
|
+
return (() => {
|
|
33
|
+
var _el$ = _tmpl$(), _el$2 = _el$.firstChild;
|
|
34
|
+
spread(_el$, mergeProps({
|
|
35
|
+
get ["data-conversation-thread-open"]() {
|
|
36
|
+
return threadOpen() || void 0;
|
|
37
|
+
},
|
|
38
|
+
get ["class"]() {
|
|
39
|
+
return cn("relative grid h-full min-h-0 grid-rows-[auto_minmax(0,1fr)_auto]", threadOpen() ? "grid-cols-1 md:grid-cols-[minmax(20rem,1fr)_minmax(18rem,22rem)] lg:grid-cols-[minmax(24rem,1fr)_minmax(20rem,26rem)]" : "grid-cols-1", local.class);
|
|
40
|
+
}
|
|
41
|
+
}, rest), false, true);
|
|
42
|
+
insert(_el$, createComponent(Show, {
|
|
43
|
+
get when() {
|
|
44
|
+
return local.header;
|
|
45
|
+
},
|
|
46
|
+
children: (header) => (() => {
|
|
47
|
+
var _el$3 = _tmpl$2();
|
|
48
|
+
insert(_el$3, header);
|
|
49
|
+
effect(() => className(_el$3, cn("col-start-1 row-start-1 min-w-0", threadOpen() && "max-md:invisible")));
|
|
50
|
+
return _el$3;
|
|
51
|
+
})()
|
|
52
|
+
}), _el$2);
|
|
53
|
+
insert(_el$2, () => local.children);
|
|
54
|
+
insert(_el$, createComponent(Show, {
|
|
55
|
+
get when() {
|
|
56
|
+
return local.composer;
|
|
57
|
+
},
|
|
58
|
+
children: (composer) => (() => {
|
|
59
|
+
var _el$4 = _tmpl$2();
|
|
60
|
+
insert(_el$4, composer);
|
|
61
|
+
effect(() => className(_el$4, cn("col-start-1 row-start-3 min-w-0", local.gutter && "mx-[clamp(0.8rem,3vw,2.5rem)] mb-[1.15rem] max-[30rem]:mx-2", threadOpen() && "max-md:invisible")));
|
|
62
|
+
return _el$4;
|
|
63
|
+
})()
|
|
64
|
+
}), null);
|
|
65
|
+
insert(_el$, createComponent(Show, {
|
|
66
|
+
get when() {
|
|
67
|
+
return local.thread;
|
|
68
|
+
},
|
|
69
|
+
children: (thread) => (() => {
|
|
70
|
+
var _el$5 = _tmpl$2();
|
|
71
|
+
insert(_el$5, thread);
|
|
72
|
+
effect(() => className(_el$5, cn("min-h-0 min-w-0", threadOpen() && "max-md:absolute max-md:inset-0 max-md:z-(--z-docked) max-md:*:border-s-0 md:col-start-2 md:row-span-3 md:row-start-1")));
|
|
73
|
+
return _el$5;
|
|
74
|
+
})()
|
|
75
|
+
}), null);
|
|
76
|
+
effect(() => className(_el$2, cn("col-start-1 row-start-2 flex min-h-0 min-w-0 flex-col", threadOpen() && "max-md:invisible")));
|
|
77
|
+
return _el$;
|
|
78
|
+
})();
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { ConversationPane };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=conversation-pane.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-pane.js","names":["ComponentProps","JSX","Show","splitProps","cn","ConversationPaneProps","header","Element","children","composer","thread","gutter","ConversationPane","props","local","rest","threadOpen","undefined","_el$","_tmpl$","_el$2","firstChild","_$spread","_$mergeProps","data-conversation-thread-open","class","_$insert","_$createComponent","when","_el$3","_tmpl$2","_$effect","_$className","_el$4","_el$5","_$mergeProps","_$createComponent","_$className"],"sources":["../../../src/components/conversation/conversation-pane.tsx"],"sourcesContent":["import type { ComponentProps, JSX } from 'solid-js'\nimport { Show, splitProps } from 'solid-js'\nimport { cn } from '#lib/utils'\n\nexport type ConversationPaneProps = ComponentProps<'section'> & {\n /** Above the transcript: the conversation's identity and its actions. */\n header?: JSX.Element\n /** The transcript — typically a `ConversationSurface` with `gutter`. */\n children: JSX.Element\n /** Below the transcript: the composer. */\n composer?: JSX.Element\n /**\n * The thread side panel. Its presence opens the second column; at narrow\n * widths the panel overlays the conversation instead of squeezing it, and\n * the conversation chrome steps aside — out of the accessibility tree with\n * it, which is why the overlay hides with `visibility` rather than paint.\n */\n thread?: JSX.Element\n /**\n * Apply the shared page gutters to the composer, tracking the transcript's\n * own gutter (`ConversationSurface`'s `gutter`) so the two read as one pane.\n */\n gutter?: boolean\n}\n\n/**\n * ConversationPane.\n *\n * The frame a conversation lives in: a header above, the transcript, a composer\n * below, and — while a thread is open — the thread panel beside all of it.\n *\n * The layout earns its place in the library because the thread is where\n * applications get it wrong. Two impulses both fail: shrinking the transcript\n * to make room turns a busy thread into a one-word-per-line stripe, and\n * *hiding* the transcript with `display: none` unmounts it, losing scroll\n * position and DOM. So the desktop layout gives the thread a fixed column and\n * leaves the transcript fluid; below the collapse width the panel overlays the\n * conversation in full and the conversation steps aside invisibly — still\n * mounted, still in the DOM, gone from the accessibility tree.\n */\nexport function ConversationPane(props: ConversationPaneProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'header',\n 'composer',\n 'thread',\n 'gutter',\n 'children',\n ])\n const threadOpen = () => local.thread !== undefined\n\n return (\n <section\n data-slot=\"conversation-pane\"\n data-conversation-thread-open={threadOpen() || undefined}\n class={cn(\n 'relative grid h-full min-h-0 grid-rows-[auto_minmax(0,1fr)_auto]',\n threadOpen()\n ? 'grid-cols-1 md:grid-cols-[minmax(20rem,1fr)_minmax(18rem,22rem)] lg:grid-cols-[minmax(24rem,1fr)_minmax(20rem,26rem)]'\n : 'grid-cols-1',\n local.class\n )}\n {...rest}\n >\n <Show when={local.header}>\n {(header) => (\n <div class={cn('col-start-1 row-start-1 min-w-0', threadOpen() && 'max-md:invisible')}>\n {header()}\n </div>\n )}\n </Show>\n\n <div\n class={cn(\n 'col-start-1 row-start-2 flex min-h-0 min-w-0 flex-col',\n threadOpen() && 'max-md:invisible'\n )}\n >\n {local.children}\n </div>\n\n <Show when={local.composer}>\n {(composer) => (\n <div\n class={cn(\n 'col-start-1 row-start-3 min-w-0',\n local.gutter && 'mx-[clamp(0.8rem,3vw,2.5rem)] mb-[1.15rem] max-[30rem]:mx-2',\n threadOpen() && 'max-md:invisible'\n )}\n >\n {composer()}\n </div>\n )}\n </Show>\n\n <Show when={local.thread}>\n {(thread) => (\n <div\n class={cn(\n 'min-h-0 min-w-0',\n threadOpen() &&\n 'max-md:absolute max-md:inset-0 max-md:z-(--z-docked) max-md:*:border-s-0 md:col-start-2 md:row-span-3 md:row-start-1'\n )}\n >\n {thread()}\n </div>\n )}\n </Show>\n </section>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAwCA,SAAgBY,iBAAiBC,OAA8B;CAC7D,MAAM,CAACC,OAAOC,QAAQZ,WAAWU,OAAO;EACtC;EACA;EACA;EACA;EACA;EACA;CAAU,CACX;CACD,MAAMG,mBAAmBF,MAAMJ,WAAWO,KAAAA;CAE1C,cAAA;EAAA,IAAAC,OAAAC,OAAA,GAAAC,QAAAF,KAAAG;EAAAC,OAAAJ,MAAAK,WAAA;GAAA,KAAA,mCAAA;IAAA,OAGmCP,WAAW,KAAKC,KAAAA;GAAS;GAAA,KAAA,WAAA;IAAA,OACjDb,GACL,oEACAY,WAAW,IACP,0HACA,eACJF,MAAMW,KACR;GAAC;EAAA,GACGV,IAAI,GAAA,OAAA,IAAA;EAAAW,OAAAR,MAAAS,gBAEPzB,MAAI;GAAA,IAAC0B,OAAI;IAAA,OAAEd,MAAMR;GAAM;GAAAE,WACpBF,kBAAM;IAAA,IAAAuB,QAAAC,QAAA;IAAAJ,OAAAG,OAEHvB,MAAM;IAAAyB,aAAAC,UAAAH,OADGzB,GAAG,mCAAmCY,WAAW,KAAK,kBAAkB,CAAC,CAAA;IAAA,OAAAa;GAAA,EAAA,CAAA;EAGtF,CAAA,GAAAT,KAAA;EAAAM,OAAAN,aASAN,MAAMN,QAAQ;EAAAkB,OAAAR,MAAAS,gBAGhBzB,MAAI;GAAA,IAAC0B,OAAI;IAAA,OAAEd,MAAML;GAAQ;GAAAD,WACtBC,oBAAQ;IAAA,IAAAwB,QAAAH,QAAA;IAAAJ,OAAAO,OAQLxB,QAAQ;IAAAsB,aAAAC,UAAAC,OANF7B,GACL,mCACAU,MAAMH,UAAU,+DAChBK,WAAW,KAAK,kBAClB,CAAC,CAAA;IAAA,OAAAiB;GAAA,EAAA,CAAA;EAIJ,CAAA,GAAA,IAAA;EAAAP,OAAAR,MAAAS,gBAGFzB,MAAI;GAAA,IAAC0B,OAAI;IAAA,OAAEd,MAAMJ;GAAM;GAAAF,WACpBE,kBAAM;IAAA,IAAAwB,QAAAJ,QAAA;IAAAJ,OAAAQ,OAQHxB,MAAM;IAAAqB,aAAAC,UAAAE,OANA9B,GACL,mBACAY,WAAW,KACT,sHACJ,CAAC,CAAA;IAAA,OAAAkB;GAAA,EAAA,CAAA;EAIJ,CAAA,GAAA,IAAA;EAAAH,aAAAC,UAAAZ,OAjCMhB,GACL,yDACAY,WAAW,KAAK,kBAClB,CAAC,CAAA;EAAA,OAAAE;CAAA,EAAA,CAAA;AAkCT"}
|
|
@@ -34,6 +34,12 @@ export type ConversationSurfaceProps = ComponentProps<'div'> & {
|
|
|
34
34
|
empty?: JSX.Element;
|
|
35
35
|
/** A header that scrolls with the transcript, e.g. a day divider or a banner. */
|
|
36
36
|
header?: JSX.Element;
|
|
37
|
+
/**
|
|
38
|
+
* The shared page gutter: reading padding inside the scroller plus a stable
|
|
39
|
+
* gutter, so the scrollbar never shifts the text when it appears. The page
|
|
40
|
+
* frame around the surface is `ConversationPane`'s job.
|
|
41
|
+
*/
|
|
42
|
+
gutter?: boolean;
|
|
37
43
|
};
|
|
38
44
|
export declare function ConversationSurface(props: ConversationSurfaceProps): JSX.Element;
|
|
39
45
|
//# sourceMappingURL=conversation-surface.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation-surface.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/conversation-surface.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAMnD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC,CAAA;AAEvF,MAAM,MAAM,wBAAwB,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG;IAC7D,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,sFAAsF;IACtF,sBAAsB,CAAC,EAAE,2BAA2B,CAAA;IACpD,kFAAkF;IAClF,uBAAuB,CAAC,EAAE,CAAC,QAAQ,EAAE,2BAA2B,KAAK,IAAI,CAAA;IACzE,iEAAiE;IACjE,KAAK,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACnB,iFAAiF;IACjF,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"conversation-surface.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/conversation-surface.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAMnD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC,CAAA;AAEvF,MAAM,MAAM,wBAAwB,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG;IAC7D,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,sFAAsF;IACtF,sBAAsB,CAAC,EAAE,2BAA2B,CAAA;IACpD,kFAAkF;IAClF,uBAAuB,CAAC,EAAE,CAAC,QAAQ,EAAE,2BAA2B,KAAK,IAAI,CAAA;IACzE,iEAAiE;IACjE,KAAK,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACnB,iFAAiF;IACjF,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACpB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,eA4ElE"}
|
|
@@ -6,7 +6,7 @@ import { ArrowDown } from "lucide-solid";
|
|
|
6
6
|
import { Show, splitProps } from "solid-js";
|
|
7
7
|
//#region src/components/conversation/conversation-surface.tsx
|
|
8
8
|
var _tmpl$ = /*#__PURE__*/ template(`<div class="pointer-events-none absolute inset-x-0 bottom-3 flex justify-center">`);
|
|
9
|
-
var _tmpl$2 = /*#__PURE__*/ template(`<div data-slot=conversation-surface><div
|
|
9
|
+
var _tmpl$2 = /*#__PURE__*/ template(`<div data-slot=conversation-surface><div><div data-slot=conversation-content>`);
|
|
10
10
|
var _tmpl$3 = /*#__PURE__*/ template(`<div class=p-4>`);
|
|
11
11
|
function ConversationSurface(props) {
|
|
12
12
|
const [local, rest] = splitProps(props, [
|
|
@@ -18,6 +18,7 @@ function ConversationSurface(props) {
|
|
|
18
18
|
"onReadingPositionChange",
|
|
19
19
|
"empty",
|
|
20
20
|
"header",
|
|
21
|
+
"gutter",
|
|
21
22
|
"children",
|
|
22
23
|
"onScroll",
|
|
23
24
|
"ref",
|
|
@@ -43,9 +44,14 @@ function ConversationSurface(props) {
|
|
|
43
44
|
follow.bindScroller(element);
|
|
44
45
|
if (typeof local.ref === "function") local.ref(element);
|
|
45
46
|
}, _el$2);
|
|
46
|
-
spread(_el$2, mergeProps({
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
spread(_el$2, mergeProps({
|
|
48
|
+
get tabindex() {
|
|
49
|
+
return local.tabindex ?? 0;
|
|
50
|
+
},
|
|
51
|
+
get ["class"]() {
|
|
52
|
+
return cn("min-h-0 flex-1 overflow-y-auto overscroll-contain", local.gutter && "px-[clamp(0.8rem,3vw,2.5rem)] pt-[1.35rem] pb-[1.1rem] [scrollbar-gutter:stable] max-[30rem]:px-2");
|
|
53
|
+
}
|
|
54
|
+
}, rest), false, true);
|
|
49
55
|
var _ref$ = follow.bindContent;
|
|
50
56
|
typeof _ref$ === "function" ? use(_ref$, _el$3) : follow.bindContent = _el$3;
|
|
51
57
|
insert(_el$3, createComponent(Show, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation-surface.js","names":["ArrowDown","ComponentProps","JSX","Show","splitProps","cn","Button","createScrollFollow","ConversationReadingPosition","Readonly","top","following","ConversationSurfaceProps","threshold","follow","resetKey","initialReadingPosition","onReadingPositionChange","position","empty","Element","header","ConversationSurface","props","local","rest","enabled","initialPosition","onPositionChange","onScroll","EventHandlerUnion","HTMLDivElement","Event","event","handler","_el$","_tmpl$2","_el$2","firstChild","_el$3","addEventListener","_$use","element","bindScroller","ref","_$spread","_$mergeProps","tabindex","_ref$","bindContent","_$insert","_$createComponent","when","children","fallback","_el$5","_tmpl$3","atBottom","_el$4","_tmpl$","type","size","variant","onClick","jump","_$effect","_$className","
|
|
1
|
+
{"version":3,"file":"conversation-surface.js","names":["ArrowDown","ComponentProps","JSX","Show","splitProps","cn","Button","createScrollFollow","ConversationReadingPosition","Readonly","top","following","ConversationSurfaceProps","threshold","follow","resetKey","initialReadingPosition","onReadingPositionChange","position","empty","Element","header","gutter","ConversationSurface","props","local","rest","enabled","initialPosition","onPositionChange","onScroll","EventHandlerUnion","HTMLDivElement","Event","event","handler","_el$","_tmpl$2","_el$2","firstChild","_el$3","addEventListener","_$use","element","bindScroller","ref","_$spread","_$mergeProps","tabindex","class","_ref$","bindContent","_$insert","_$createComponent","when","children","fallback","_el$5","_tmpl$3","atBottom","_el$4","_tmpl$","type","size","variant","onClick","jump","_$effect","_$className","_$mergeProps","_$use","_$createComponent","_$className"],"sources":["../../../src/components/conversation/conversation-surface.tsx"],"sourcesContent":["import { ArrowDown } from 'lucide-solid'\nimport type { ComponentProps, JSX } from 'solid-js'\nimport { Show, splitProps } from 'solid-js'\nimport { cn } from '#lib/utils'\nimport { Button } from '../ui/button/button'\nimport { createScrollFollow } from './scroll-follow'\n\n/**\n * ConversationSurface.\n *\n * The scrolling transcript. Its job is small and it is the difference between a\n * transcript that stays readable as it grows and one that fights the reader:\n *\n * - **Stick to the bottom.** New messages scroll into view only when the reader is\n * already at the bottom. Someone reading back through history must not be yanked\n * to the end by an arriving message, and that is the single most common way a\n * chat surface is made unusable.\n * - **Offer a way back.** Once the reader is not at the bottom, a control appears\n * that returns them. Pixel distance cannot prove an unread message count.\n * - **`overscroll-contain`.** The transcript scrolls to its end and stops, rather\n * than dragging the pane behind it — which in a desktop shell means the whole\n * window moves.\n */\nexport type ConversationReadingPosition = Readonly<{ top: number; following: boolean }>\n\nexport type ConversationSurfaceProps = ComponentProps<'div'> & {\n /** How far from the bottom still counts as \"at the bottom\", in px. */\n threshold?: number\n /** Disable automatic positioning while the host owns scroll restoration. */\n follow?: boolean\n /** Optional conversation identity: changing it re-arms follow at the bottom. */\n resetKey?: string\n /** Host snapshot consumed on mount/identity reset, never on ordinary prop updates. */\n initialReadingPosition?: ConversationReadingPosition\n /** Actual follow intent and native offset; storage/identity remain host-owned. */\n onReadingPositionChange?: (position: ConversationReadingPosition) => void\n /** Rendered above the transcript when there is nothing in it. */\n empty?: JSX.Element\n /** A header that scrolls with the transcript, e.g. a day divider or a banner. */\n header?: JSX.Element\n /**\n * The shared page gutter: reading padding inside the scroller plus a stable\n * gutter, so the scrollbar never shifts the text when it appears. The page\n * frame around the surface is `ConversationPane`'s job.\n */\n gutter?: boolean\n}\n\nexport function ConversationSurface(props: ConversationSurfaceProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'threshold',\n 'follow',\n 'resetKey',\n 'initialReadingPosition',\n 'onReadingPositionChange',\n 'empty',\n 'header',\n 'gutter',\n 'children',\n 'onScroll',\n 'ref',\n 'tabindex',\n ])\n // Kobalte provides the control's semantics; transcript follow is a native\n // geometry contract. KiroCrew's plain-scroller controller handles streaming,\n // content collapse and pane resizing without treating them as user consent.\n const follow = createScrollFollow({\n enabled: () => local.follow !== false,\n resetKey: () => local.resetKey,\n threshold: () => local.threshold ?? 80,\n initialPosition: () => local.initialReadingPosition,\n onPositionChange: (position) => local.onReadingPositionChange?.(position),\n })\n const onScroll: JSX.EventHandlerUnion<HTMLDivElement, Event> = (event) => {\n follow.onScroll()\n const handler = local.onScroll\n if (typeof handler === 'function') handler(event)\n else if (handler) handler[0](handler[1], event)\n }\n\n return (\n <div\n data-slot=\"conversation-surface\"\n class={cn('relative flex min-h-0 flex-1 flex-col', local.class)}\n >\n <div\n ref={(element) => {\n follow.bindScroller(element)\n if (typeof local.ref === 'function') local.ref(element)\n }}\n onScroll={onScroll}\n tabindex={local.tabindex ?? 0}\n class={cn(\n 'min-h-0 flex-1 overflow-y-auto overscroll-contain',\n local.gutter &&\n 'px-[clamp(0.8rem,3vw,2.5rem)] pt-[1.35rem] pb-[1.1rem] [scrollbar-gutter:stable] max-[30rem]:px-2'\n )}\n {...rest}\n >\n <div ref={follow.bindContent} data-slot=\"conversation-content\">\n <Show when={local.header}>{local.header}</Show>\n <Show when={local.children} fallback={<div class=\"p-4\">{local.empty}</div>}>\n {local.children}\n </Show>\n </div>\n </div>\n\n <Show when={!follow.atBottom()}>\n <div class=\"pointer-events-none absolute inset-x-0 bottom-3 flex justify-center\">\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"secondary\"\n class=\"pointer-events-auto gap-1.5 shadow-md\"\n onClick={follow.jump}\n >\n <ArrowDown />\n Jump to latest\n </Button>\n </div>\n </Show>\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;AAgDA,SAAgBuB,oBAAoBC,OAAiC;CACnE,MAAM,CAACC,OAAOC,QAAQtB,WAAWoB,OAAO;EACtC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CAAU,CACX;CAID,MAAMV,SAASP,mBAAmB;EAChCoB,eAAeF,MAAMX,WAAW;EAChCC,gBAAgBU,MAAMV;EACtBF,iBAAiBY,MAAMZ,aAAa;EACpCe,uBAAuBH,MAAMT;EAC7Ba,mBAAmBX,aAAaO,MAAMR,0BAA0BC,QAAQ;CAC1E,CAAC;CACD,MAAMY,YAA0DI,UAAU;EACxEpB,OAAOgB,SAAS;EAChB,MAAMK,UAAUV,MAAMK;EACtB,IAAI,OAAOK,YAAY,YAAYA,QAAQD,KAAK;OAC3C,IAAIC,SAASA,QAAQ,EAAE,CAACA,QAAQ,IAAID,KAAK;CAChD;CAEA,cAAA;EAAA,IAAAE,OAAAC,QAAA,GAAAC,QAAAF,KAAAG,YAAAC,QAAAF,MAAAC;EAAAD,MAAAG,iBAAA,UAUgBX,QAAQ;EAAAY,KAJZC,YAAY;GAChB7B,OAAO8B,aAAaD,OAAO;GAC3B,IAAI,OAAOlB,MAAMoB,QAAQ,YAAYpB,MAAMoB,IAAIF,OAAO;EACxD,GAACL,KAAA;EAAAQ,OAAAR,OAAAS,WAAA;GAAA,IAEDC,WAAQ;IAAA,OAAEvB,MAAMuB,YAAY;GAAC;GAAA,KAAA,WAAA;IAAA,OACtB3C,GACL,qDACAoB,MAAMH,UACJ,mGACJ;GAAC;EAAA,GACGI,IAAI,GAAA,OAAA,IAAA;EAAA,IAAAwB,QAEEpC,OAAOqC;EAAW,OAAAD,UAAA,aAAAR,IAAAQ,OAAAV,KAAA,IAAlB1B,OAAOqC,cAAWX;EAAAY,OAAAZ,OAAAa,gBACzBlD,MAAI;GAAA,IAACmD,OAAI;IAAA,OAAE7B,MAAMJ;GAAM;GAAA,IAAAkC,WAAA;IAAA,OAAG9B,MAAMJ;GAAM;EAAA,CAAA,GAAA,IAAA;EAAA+B,OAAAZ,OAAAa,gBACtClD,MAAI;GAAA,IAACmD,OAAI;IAAA,OAAE7B,MAAM8B;GAAQ;GAAA,IAAEC,WAAQ;IAAA,cAAA;KAAA,IAAAC,QAAAC,QAAA;KAAAN,OAAAK,aAAoBhC,MAAMN,KAAK;KAAA,OAAAsC;IAAA,EAAA,CAAA;GAAA;GAAA,IAAAF,WAAA;IAAA,OAChE9B,MAAM8B;GAAQ;EAAA,CAAA,GAAA,IAAA;EAAAH,OAAAhB,MAAAiB,gBAKpBlD,MAAI;GAAA,IAACmD,OAAI;IAAA,OAAE,CAACxC,OAAO6C,SAAS;GAAC;GAAA,IAAAJ,WAAA;IAAA,IAAAK,QAAAC,OAAA;IAAAT,OAAAQ,OAAAP,gBAEzB/C,QAAM;KACLwD,MAAI;KACJC,MAAI;KACJC,SAAO;KAAA,SAAA;KAAA,IAEPC,UAAO;MAAA,OAAEnD,OAAOoD;KAAI;KAAA,IAAAX,WAAA;MAAA,OAAA,CAAAF,gBAEnBrD,WAAS,CAAA,CAAA,GAAA,gBAAA;KAAA;IAAA,CAAA,CAAA;IAAA,OAAA4D;GAAA;EAAA,CAAA,GAAA,IAAA;EAAAO,aAAAC,UAAAhC,MAjCT/B,GAAG,yCAAyCoB,MAAMwB,KAAK,CAAC,CAAA;EAAA,OAAAb;CAAA,EAAA,CAAA;AAwCrE"}
|
|
@@ -2,6 +2,8 @@ export { ChatComposer, type ChatComposerProps, type ChatComposerControl } from '
|
|
|
2
2
|
export { BusySendButton, type BusySendMode, type BusySendButtonProps } from './busy-send-button';
|
|
3
3
|
export { ConversationAvatar, type ConversationAvatarKind, type ConversationAvatarProps, } from './conversation-avatar';
|
|
4
4
|
export { ConversationSurface, type ConversationSurfaceProps, type ConversationReadingPosition, } from './conversation-surface';
|
|
5
|
+
export { ConversationPane, type ConversationPaneProps } from './conversation-pane';
|
|
6
|
+
export { ComposerMenu, ComposerMenuItem, type ComposerMenuProps, type ComposerMenuItemProps, } from './composer-menu';
|
|
5
7
|
export { ComposerAttachmentButton, ComposerHint, MessageComposer, type MessageComposerProps, } from './message-composer';
|
|
6
8
|
export { MessageBody, MessageDayDivider, MessageGroup, MessageRow, type MessageRowProps, } from './message-row';
|
|
7
9
|
export { TranscriptComposition, transcriptDisclosureKey, type TranscriptCompositionProps, type TranscriptCompositionFold, type TranscriptCompositionRowContext, type TranscriptCompositionRendererProps, type TranscriptDisplayKind, type GroupedTranscript, type TranscriptDisplayItem, type TranscriptFoldState, type TranscriptFoldMode, type TranscriptFoldPlan, type TranscriptMounting, type TranscriptRow, type TranscriptTurnItem, } from './transcript-composition';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAChG,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAChG,OAAO,EACL,kBAAkB,EAClB,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,GAC7B,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,GACjC,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACL,wBAAwB,EACxB,YAAY,EACZ,eAAe,EACf,KAAK,oBAAoB,GAC1B,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,KAAK,eAAe,GACrB,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,KAAK,0BAA0B,EAC/B,KAAK,yBAAyB,EAC9B,KAAK,+BAA+B,EACpC,KAAK,kCAAkC,EACvC,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,kBAAkB,GACxB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EACL,cAAc,EACd,WAAW,EACX,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EACL,UAAU,EACV,SAAS,EACT,eAAe,EACf,WAAW,EACX,YAAY,EACZ,OAAO,EACP,WAAW,EACX,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,uBAAuB,EACvB,YAAY,EACZ,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,KAAK,UAAU,GAChB,MAAM,gBAAgB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAChG,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAChG,OAAO,EACL,kBAAkB,EAClB,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,GAC7B,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,GACjC,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAClF,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,GAC3B,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,wBAAwB,EACxB,YAAY,EACZ,eAAe,EACf,KAAK,oBAAoB,GAC1B,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,KAAK,eAAe,GACrB,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,KAAK,0BAA0B,EAC/B,KAAK,yBAAyB,EAC9B,KAAK,+BAA+B,EACpC,KAAK,kCAAkC,EACvC,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,kBAAkB,GACxB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EACL,cAAc,EACd,WAAW,EACX,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EACL,UAAU,EACV,SAAS,EACT,eAAe,EACf,WAAW,EACX,YAAY,EACZ,OAAO,EACP,WAAW,EACX,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,uBAAuB,EACvB,YAAY,EACZ,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,KAAK,UAAU,GAChB,MAAM,gBAAgB,CAAA"}
|
|
@@ -2,9 +2,11 @@ import { BusySendButton } from "./busy-send-button.js";
|
|
|
2
2
|
import { ChatComposer } from "./chat-composer.js";
|
|
3
3
|
import { ConversationAvatar } from "./conversation-avatar.js";
|
|
4
4
|
import { ConversationSurface } from "./conversation-surface.js";
|
|
5
|
+
import { ConversationPane } from "./conversation-pane.js";
|
|
6
|
+
import { ComposerMenu, ComposerMenuItem } from "./composer-menu.js";
|
|
5
7
|
import { ComposerAttachmentButton, ComposerHint, MessageComposer } from "./message-composer.js";
|
|
6
8
|
import { MessageBody, MessageDayDivider, MessageGroup, MessageRow } from "./message-row.js";
|
|
7
9
|
import { TranscriptComposition, transcriptDisclosureKey } from "./transcript-composition.js";
|
|
8
10
|
import { AttachmentCard, ThreadPanel } from "./thread-panel.js";
|
|
9
11
|
import { PASTE_THRESHOLD_CHARS, PASTE_THRESHOLD_LINES, PASTE_TOKEN_REGEX, countLines, expandAll, findTokenRanges, formatToken, isPasteBlock, nextSeq, pruneBlocks, recollapsePastes, remapCarriedBlocks, shouldCollapse, stripTrailingBlankLines, tokenRangeAt } from "./paste-tokens.js";
|
|
10
|
-
export { AttachmentCard, BusySendButton, ChatComposer, ComposerAttachmentButton, ComposerHint, ConversationAvatar, ConversationSurface, MessageBody, MessageComposer, MessageDayDivider, MessageGroup, MessageRow, PASTE_THRESHOLD_CHARS, PASTE_THRESHOLD_LINES, PASTE_TOKEN_REGEX, ThreadPanel, TranscriptComposition, countLines, expandAll, findTokenRanges, formatToken, isPasteBlock, nextSeq, pruneBlocks, recollapsePastes, remapCarriedBlocks, shouldCollapse, stripTrailingBlankLines, tokenRangeAt, transcriptDisclosureKey };
|
|
12
|
+
export { AttachmentCard, BusySendButton, ChatComposer, ComposerAttachmentButton, ComposerHint, ComposerMenu, ComposerMenuItem, ConversationAvatar, ConversationPane, ConversationSurface, MessageBody, MessageComposer, MessageDayDivider, MessageGroup, MessageRow, PASTE_THRESHOLD_CHARS, PASTE_THRESHOLD_LINES, PASTE_TOKEN_REGEX, ThreadPanel, TranscriptComposition, countLines, expandAll, findTokenRanges, formatToken, isPasteBlock, nextSeq, pruneBlocks, recollapsePastes, remapCarriedBlocks, shouldCollapse, stripTrailingBlankLines, tokenRangeAt, transcriptDisclosureKey };
|
|
@@ -52,6 +52,12 @@ export type MessageComposerProps = Omit<ComponentProps<'form'>, 'onSubmit'> & {
|
|
|
52
52
|
trailing?: JSX.Element;
|
|
53
53
|
/** A menu rendered above the field, e.g. mention suggestions. */
|
|
54
54
|
menu?: JSX.Element;
|
|
55
|
+
/**
|
|
56
|
+
* Host progress rendered in the composer's own live region, e.g. dictation
|
|
57
|
+
* state — plain paragraphs are progress, a `role="alert"` paragraph is a
|
|
58
|
+
* failure. Sending and send-failure copy stays the composer's own.
|
|
59
|
+
*/
|
|
60
|
+
status?: JSX.Element;
|
|
55
61
|
/** The reply target, drawn as a strip above the field. */
|
|
56
62
|
replyTo?: {
|
|
57
63
|
label: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-composer.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/message-composer.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAIxD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAgB5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,GAAG;IAC5E,oFAAoF;IACpF,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,iFAAiF;IACjF,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,sDAAsD;IACtD,OAAO,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACrB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACtB,iEAAiE;IACjE,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IAClB,0DAA0D;IAC1D,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,IAAI,CAAA;KAAE,CAAA;IAClD,iFAAiF;IACjF,QAAQ,CAAC,EAAE,GAAG,CAAC,mBAAmB,CAAC,CAAA;IACnC,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gEAAgE;IAChE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,gFAAgF;IAChF,SAAS,CAAC,EAAE,SAAS,GAAG,aAAa,CAAA;IACrC,0DAA0D;IAC1D,WAAW,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,CAAA;CAC3C,CAAA;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,
|
|
1
|
+
{"version":3,"file":"message-composer.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/message-composer.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAIxD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAgB5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,GAAG;IAC5E,oFAAoF;IACpF,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,iFAAiF;IACjF,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,sDAAsD;IACtD,OAAO,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACrB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACtB,iEAAiE;IACjE,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IAClB;;;;OAIG;IACH,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACpB,0DAA0D;IAC1D,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,IAAI,CAAA;KAAE,CAAA;IAClD,iFAAiF;IACjF,QAAQ,CAAC,EAAE,GAAG,CAAC,mBAAmB,CAAC,CAAA;IACnC,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gEAAgE;IAChE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,gFAAgF;IAChF,SAAS,CAAC,EAAE,SAAS,GAAG,aAAa,CAAA;IACrC,0DAA0D;IAC1D,WAAW,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,CAAA;CAC3C,CAAA;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,eAqM1D;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,cAAc,CAAC,OAAO,MAAM,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,eAkB1E;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,eAetD"}
|
|
@@ -34,6 +34,7 @@ function MessageComposer(props) {
|
|
|
34
34
|
"leading",
|
|
35
35
|
"trailing",
|
|
36
36
|
"menu",
|
|
37
|
+
"status",
|
|
37
38
|
"replyTo",
|
|
38
39
|
"inputRef",
|
|
39
40
|
"inputId",
|
|
@@ -255,7 +256,15 @@ function MessageComposer(props) {
|
|
|
255
256
|
get children() {
|
|
256
257
|
return [memo(() => pendingLabel()), "…"];
|
|
257
258
|
}
|
|
258
|
-
}));
|
|
259
|
+
}), null);
|
|
260
|
+
insert(_el$10, createComponent(Show, {
|
|
261
|
+
get when() {
|
|
262
|
+
return local.status;
|
|
263
|
+
},
|
|
264
|
+
get children() {
|
|
265
|
+
return local.status;
|
|
266
|
+
}
|
|
267
|
+
}), null);
|
|
259
268
|
effect(() => className(_el$3, cn("border-input bg-card flex flex-col gap-1 rounded-lg border p-2", "transition-[color,box-shadow,border-color] ease-out", "focus-within:border-ring focus-within:ring-3 focus-within:ring-primary-subtle", local.disabled && "opacity-50")));
|
|
260
269
|
return _el$;
|
|
261
270
|
})();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-composer.js","names":["CornerDownLeft","Paperclip","Send","X","ComponentProps","JSX","Ref","Show","createSignal","createUniqueId","splitProps","cn","ActionButton","Button","Kbd","Spinner","Textarea","createComposerImeGuard","invokeEventHandler","handler","event","Event","Array","isArray","callback","data","MessageComposerProps","Omit","value","onValueChange","onSubmit","Promise","placeholder","disabled","submitLabel","showHint","leading","Element","trailing","menu","replyTo","label","onDismiss","inputRef","HTMLTextAreaElement","inputId","inputLabel","inputDescription","inputDescribedBy","inputSize","inputResize","MessageComposer","props","local","rest","sending","setSending","error","setError","ime","descriptionId","describedBy","undefined","filter","Boolean","join","actionLabel","pendingLabel","toLowerCase","canSend","trim","length","send","onKeyDown","EventHandlerUnion","KeyboardEvent","defaultPrevented","key","shiftKey","metaKey","ctrlKey","claimEnter","onFormKeyDown","HTMLFormElement","isComposing","keyCode","preventDefault","stopPropagation","_el$","_tmpl$3","_el$3","firstChild","_el$4","_el$5","_el$10","nextSibling","$$keydown","addEventListener","_$spread","_$mergeProps","class","_$insert","_$createComponent","when","children","_el$2","_tmpl$","reply","_el$11","_tmpl$4","_el$12","_el$13","type","variant","size","tooltip","onClick","ref","r$","_ref$","id","onCompositionStart","onCompositionEnd","onFocus","onBlur","onInput","currentTarget","rows","aria-label","aria-describedby","resize","description","_el$14","_tmpl$5","_$setAttribute","_$memo","fallback","_el$6","_tmpl$2","_el$7","_el$8","_el$9","_el$1","_el$0","message","_el$15","_tmpl$6","_$effect","_$className","ComposerAttachmentButton","count","open","aria-expanded","ComposerHint","_el$16","_tmpl$7","_$delegateEvents","_$mergeProps","_$createComponent","_$memo","_$className","_$delegateEvents"],"sources":["../../../src/components/conversation/message-composer.tsx"],"sourcesContent":["import { CornerDownLeft, Paperclip, Send, X } from 'lucide-solid'\nimport type { ComponentProps, JSX, Ref } from 'solid-js'\nimport { Show, createSignal, createUniqueId, splitProps } from 'solid-js'\nimport { cn } from '#lib/utils'\nimport { ActionButton } from '../composites/action-button/action-button'\nimport { Button } from '../ui/button/button'\nimport { Kbd } from '../ui/kbd/kbd'\nimport { Spinner } from '../ui/spinner/spinner'\nimport { Textarea } from '../ui/textarea/textarea'\nimport { createComposerImeGuard } from './ime-guard'\n\nfunction invokeEventHandler(handler: unknown, event: Event) {\n if (Array.isArray(handler)) {\n const [callback, data] = handler as [(data: unknown, event: Event) => void, unknown]\n callback(data, event)\n } else if (typeof handler === 'function') {\n const callback = handler as (event: Event) => void\n callback(event)\n }\n}\n\n/**\n * MessageComposer.\n *\n * The field a message is written in. It is a form, and it behaves like one: Enter\n * sends, Shift+Enter breaks the line, Escape clears the error from anywhere in\n * the composer unless a nested control or an active IME already owns the key.\n *\n * ## What it does not know\n *\n * No mention model, no attachment model, no dictation, no idempotency keys. The\n * first version of this composer was written against adea's agent and artifact\n * types, which meant it could only ever be used by adea. Here the attachment control\n * is a *slot*, the mention menu is a slot, and a caller that has an agent model\n * renders one; a caller that does not gets a composer without it.\n *\n * ## The three things it does own\n *\n * **Enter sends, Shift+Enter breaks the line.** That is the convention every chat\n * application shares, and getting it wrong is the single most irritating thing a\n * composer can do. `⌘`/`Ctrl`+Enter is accepted too, because someone arriving from an\n * editor will try it.\n *\n * **The send button is disabled when there is nothing to send.** A control that\n * accepts a press and does nothing is worse than one that says it cannot.\n *\n * **The status region is `aria-live`.** \"Sending…\" and a failure both land there, so\n * a screen reader hears the outcome without the composer stealing focus.\n *\n * Enter ownership follows KiroCrew's pinned IME latch: candidate commits keep\n * their native default action, and the post-composition Enter cannot send or\n * insert an accidental newline. Composition abandoned by focus/blur recovers;\n * the Solid owner's cleanup settles its timer. See ime-guard.ts for provenance.\n */\nexport type MessageComposerProps = Omit<ComponentProps<'form'>, 'onSubmit'> & {\n /** The draft. Controlled: the caller owns the text, because it usually persists. */\n value: string\n onValueChange: (value: string) => void\n /** Called on send. A rejection is surfaced as an error and the draft is kept. */\n onSubmit: () => void | Promise<void>\n placeholder?: string\n /** Disable the whole composer, e.g. while the channel is read-only. */\n disabled?: boolean\n /** Accessible name and tooltip for the action, e.g. \"Reply\". */\n submitLabel?: string\n /** Show the keyboard hint under the field. */\n showHint?: boolean\n /** A leading control group, e.g. an attach button. */\n leading?: JSX.Element\n /** A trailing group before the send button, e.g. a dictation toggle. */\n trailing?: JSX.Element\n /** A menu rendered above the field, e.g. mention suggestions. */\n menu?: JSX.Element\n /** The reply target, drawn as a strip above the field. */\n replyTo?: { label: string; onDismiss: () => void }\n /** Ref to the actual message textarea, for host focus and selection behavior. */\n inputRef?: Ref<HTMLTextAreaElement>\n /** Stable host-owned id for the actual textarea. */\n inputId?: string\n /** Accessible name for the actual textarea. Defaults to \"Message\" or \"Reply message\". */\n inputLabel?: string\n /** Additional help text associated with the actual textarea. */\n inputDescription?: string\n /** Existing host description id, combined with `inputDescription` when supplied. */\n inputDescribedBy?: string\n /** Comfortable uses the shared 4rem minimum and 12rem maximum field heights. */\n inputSize?: 'default' | 'comfortable'\n /** The shared resize behavior for the actual textarea. */\n inputResize?: 'vertical' | 'none' | 'both'\n}\n\nexport function MessageComposer(props: MessageComposerProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'value',\n 'onValueChange',\n 'onSubmit',\n 'placeholder',\n 'disabled',\n 'submitLabel',\n 'showHint',\n 'leading',\n 'trailing',\n 'menu',\n 'replyTo',\n 'inputRef',\n 'inputId',\n 'inputLabel',\n 'inputDescription',\n 'inputDescribedBy',\n 'inputSize',\n 'inputResize',\n 'onKeyDown',\n ])\n\n const [sending, setSending] = createSignal(false)\n const [error, setError] = createSignal<string | null>(null)\n const ime = createComposerImeGuard()\n const descriptionId = `message-composer-help-${createUniqueId()}`\n const describedBy = () =>\n [local.inputDescribedBy, local.inputDescription ? descriptionId : undefined]\n .filter(Boolean)\n .join(' ') || undefined\n const actionLabel = () => local.submitLabel ?? 'Send message'\n const pendingLabel = () =>\n `Sending ${local.submitLabel ? local.submitLabel.toLowerCase() : 'message'}`\n\n const canSend = () => local.value.trim().length > 0 && !local.disabled && !sending()\n\n const send = async () => {\n if (!canSend()) return\n setSending(true)\n setError(null)\n try {\n await local.onSubmit()\n } catch {\n // The draft is deliberately left in place. A send that fails and clears the\n // field loses the user's work, which is the worst outcome available here.\n setError('Message not sent. Your draft is still here; retry when the connection recovers.')\n } finally {\n setSending(false)\n }\n }\n\n const onKeyDown: JSX.EventHandlerUnion<HTMLTextAreaElement, KeyboardEvent> = (event) => {\n if (event.defaultPrevented) return\n if (event.key !== 'Enter') return\n // Shift breaks the line; a bare Enter, or a modified one, sends.\n if (event.shiftKey && !event.metaKey && !event.ctrlKey) return\n if (!ime.claimEnter(event)) return\n void send()\n }\n\n const onFormKeyDown: JSX.EventHandlerUnion<HTMLFormElement, KeyboardEvent> = (event) => {\n invokeEventHandler(local.onKeyDown, event)\n if (\n event.defaultPrevented ||\n event.key !== 'Escape' ||\n !error() ||\n event.isComposing ||\n event.keyCode === 229\n ) {\n return\n }\n setError(null)\n event.preventDefault()\n event.stopPropagation()\n }\n\n return (\n <form\n data-slot=\"message-composer\"\n class={cn('flex flex-col gap-2', local.class)}\n onSubmit={(event) => {\n event.preventDefault()\n void send()\n }}\n onKeyDown={onFormKeyDown}\n {...rest}\n >\n <Show when={local.menu}>\n <div>{local.menu}</div>\n </Show>\n\n <Show when={local.replyTo}>\n {(reply) => (\n <div class=\"bg-muted text-muted-foreground flex items-center gap-2 rounded-md px-2.5 py-1.5 text-xs\">\n <span class=\"min-w-0 flex-1 truncate\">Replying to {reply().label}</span>\n <ActionButton\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-2xs\"\n aria-label=\"Cancel reply\"\n tooltip=\"Cancel reply\"\n onClick={() => reply().onDismiss()}\n >\n <X />\n </ActionButton>\n </div>\n )}\n </Show>\n\n <div\n class={cn(\n 'border-input bg-card flex flex-col gap-1 rounded-lg border p-2',\n 'transition-[color,box-shadow,border-color] ease-out',\n 'focus-within:border-ring focus-within:ring-3 focus-within:ring-primary-subtle',\n local.disabled && 'opacity-50'\n )}\n >\n <Textarea\n ref={local.inputRef}\n id={local.inputId}\n onCompositionStart={ime.onCompositionStart}\n onCompositionEnd={ime.onCompositionEnd}\n onFocus={ime.onFocus}\n onBlur={ime.onBlur}\n value={local.value}\n onInput={(event) => local.onValueChange(event.currentTarget.value)}\n onKeyDown={onKeyDown}\n placeholder={local.placeholder ?? 'Write a message…'}\n disabled={local.disabled}\n rows={1}\n aria-label={\n local.inputLabel ?? (local.submitLabel ? `${local.submitLabel} message` : 'Message')\n }\n aria-describedby={describedBy()}\n variant=\"composer\"\n size={local.inputSize ?? 'comfortable'}\n resize={local.inputResize ?? 'vertical'}\n />\n <Show when={local.inputDescription}>\n {(description) => (\n <span id={descriptionId} class=\"sr-only\">\n {description()}\n </span>\n )}\n </Show>\n\n <div class=\"flex items-center gap-1\">\n <Show when={local.leading}>{local.leading}</Show>\n <div class=\"ms-auto flex items-center gap-1\">\n <Show when={local.trailing}>{local.trailing}</Show>\n <ActionButton\n type=\"submit\"\n size=\"icon-sm\"\n disabled={!canSend()}\n aria-label={sending() ? pendingLabel() : actionLabel()}\n tooltip={actionLabel()}\n >\n <Show when={sending()} fallback={<Send />}>\n <Spinner size=\"sm\" label={false} class=\"text-primary-foreground\" />\n </Show>\n </ActionButton>\n </div>\n </div>\n </div>\n\n <Show when={local.showHint}>\n <p class=\"text-muted-foreground flex items-center gap-1.5 text-2xs\">\n <Kbd>Enter</Kbd> to send\n <span aria-hidden=\"true\">·</span>\n <Kbd>Shift</Kbd> <Kbd>Enter</Kbd> for a new line\n </p>\n </Show>\n\n {/* A live region, so the outcome is announced without moving focus out of the\n field the user is typing in. */}\n <div role=\"status\" aria-live=\"polite\" class=\"min-h-0\">\n <Show\n when={sending()}\n fallback={\n <Show when={error()}>\n {(message) => (\n <p class=\"text-foreground border-s-2 border-destructive ps-2 text-xs\">\n {message()}\n </p>\n )}\n </Show>\n }\n >\n {pendingLabel()}…\n </Show>\n </div>\n </form>\n )\n}\n\n/**\n * The attachment control.\n *\n * A slot rather than a built-in, because what can be attached is the application's\n * business — but the *button* is shared, so it looks and behaves the same in both.\n * The count is announced, since a paperclip alone does not say whether anything is\n * attached.\n */\nexport function ComposerAttachmentButton(\n props: ComponentProps<typeof Button> & { count?: number; open?: boolean }\n) {\n const [local, rest] = splitProps(props, ['count', 'open', 'children'])\n\n return (\n <ActionButton\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={local.count ? `${local.count} attached, add an attachment` : 'Add an attachment'}\n aria-expanded={local.open}\n tooltip=\"Add an attachment\"\n class=\"text-muted-foreground\"\n {...rest}\n >\n {local.children ?? <Paperclip />}\n </ActionButton>\n )\n}\n\n/** The hint a composer shows when the field is empty, as a one-line prompt. */\nexport function ComposerHint(props: ComponentProps<'p'>) {\n const [local, rest] = splitProps(props, ['class', 'children'])\n return (\n <p\n class={cn('text-muted-foreground flex items-center gap-1.5 text-2xs', local.class)}\n {...rest}\n >\n {local.children ?? (\n <>\n <CornerDownLeft aria-hidden=\"true\" class=\"size-3\" />\n <Kbd>Enter</Kbd> to send\n </>\n )}\n </p>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"message-composer.js","names":["CornerDownLeft","Paperclip","Send","X","ComponentProps","JSX","Ref","Show","createSignal","createUniqueId","splitProps","cn","ActionButton","Button","Kbd","Spinner","Textarea","createComposerImeGuard","invokeEventHandler","handler","event","Event","Array","isArray","callback","data","MessageComposerProps","Omit","value","onValueChange","onSubmit","Promise","placeholder","disabled","submitLabel","showHint","leading","Element","trailing","menu","status","replyTo","label","onDismiss","inputRef","HTMLTextAreaElement","inputId","inputLabel","inputDescription","inputDescribedBy","inputSize","inputResize","MessageComposer","props","local","rest","sending","setSending","error","setError","ime","descriptionId","describedBy","undefined","filter","Boolean","join","actionLabel","pendingLabel","toLowerCase","canSend","trim","length","send","onKeyDown","EventHandlerUnion","KeyboardEvent","defaultPrevented","key","shiftKey","metaKey","ctrlKey","claimEnter","onFormKeyDown","HTMLFormElement","isComposing","keyCode","preventDefault","stopPropagation","_el$","_tmpl$3","_el$3","firstChild","_el$4","_el$5","_el$10","nextSibling","$$keydown","addEventListener","_$spread","_$mergeProps","class","_$insert","_$createComponent","when","children","_el$2","_tmpl$","reply","_el$11","_tmpl$4","_el$12","_el$13","type","variant","size","tooltip","onClick","ref","r$","_ref$","id","onCompositionStart","onCompositionEnd","onFocus","onBlur","onInput","currentTarget","rows","aria-label","aria-describedby","resize","description","_el$14","_tmpl$5","_$setAttribute","_$memo","fallback","_el$6","_tmpl$2","_el$7","_el$8","_el$9","_el$1","_el$0","message","_el$15","_tmpl$6","_$effect","_$className","ComposerAttachmentButton","count","open","aria-expanded","ComposerHint","_el$16","_tmpl$7","_$delegateEvents","_$mergeProps","_$createComponent","_$memo","_$className","_$delegateEvents"],"sources":["../../../src/components/conversation/message-composer.tsx"],"sourcesContent":["import { CornerDownLeft, Paperclip, Send, X } from 'lucide-solid'\nimport type { ComponentProps, JSX, Ref } from 'solid-js'\nimport { Show, createSignal, createUniqueId, splitProps } from 'solid-js'\nimport { cn } from '#lib/utils'\nimport { ActionButton } from '../composites/action-button/action-button'\nimport { Button } from '../ui/button/button'\nimport { Kbd } from '../ui/kbd/kbd'\nimport { Spinner } from '../ui/spinner/spinner'\nimport { Textarea } from '../ui/textarea/textarea'\nimport { createComposerImeGuard } from './ime-guard'\n\nfunction invokeEventHandler(handler: unknown, event: Event) {\n if (Array.isArray(handler)) {\n const [callback, data] = handler as [(data: unknown, event: Event) => void, unknown]\n callback(data, event)\n } else if (typeof handler === 'function') {\n const callback = handler as (event: Event) => void\n callback(event)\n }\n}\n\n/**\n * MessageComposer.\n *\n * The field a message is written in. It is a form, and it behaves like one: Enter\n * sends, Shift+Enter breaks the line, Escape clears the error from anywhere in\n * the composer unless a nested control or an active IME already owns the key.\n *\n * ## What it does not know\n *\n * No mention model, no attachment model, no dictation, no idempotency keys. The\n * first version of this composer was written against adea's agent and artifact\n * types, which meant it could only ever be used by adea. Here the attachment control\n * is a *slot*, the mention menu is a slot, and a caller that has an agent model\n * renders one; a caller that does not gets a composer without it.\n *\n * ## The three things it does own\n *\n * **Enter sends, Shift+Enter breaks the line.** That is the convention every chat\n * application shares, and getting it wrong is the single most irritating thing a\n * composer can do. `⌘`/`Ctrl`+Enter is accepted too, because someone arriving from an\n * editor will try it.\n *\n * **The send button is disabled when there is nothing to send.** A control that\n * accepts a press and does nothing is worse than one that says it cannot.\n *\n * **The status region is `aria-live`.** \"Sending…\" and a failure both land there, so\n * a screen reader hears the outcome without the composer stealing focus.\n *\n * Enter ownership follows KiroCrew's pinned IME latch: candidate commits keep\n * their native default action, and the post-composition Enter cannot send or\n * insert an accidental newline. Composition abandoned by focus/blur recovers;\n * the Solid owner's cleanup settles its timer. See ime-guard.ts for provenance.\n */\nexport type MessageComposerProps = Omit<ComponentProps<'form'>, 'onSubmit'> & {\n /** The draft. Controlled: the caller owns the text, because it usually persists. */\n value: string\n onValueChange: (value: string) => void\n /** Called on send. A rejection is surfaced as an error and the draft is kept. */\n onSubmit: () => void | Promise<void>\n placeholder?: string\n /** Disable the whole composer, e.g. while the channel is read-only. */\n disabled?: boolean\n /** Accessible name and tooltip for the action, e.g. \"Reply\". */\n submitLabel?: string\n /** Show the keyboard hint under the field. */\n showHint?: boolean\n /** A leading control group, e.g. an attach button. */\n leading?: JSX.Element\n /** A trailing group before the send button, e.g. a dictation toggle. */\n trailing?: JSX.Element\n /** A menu rendered above the field, e.g. mention suggestions. */\n menu?: JSX.Element\n /**\n * Host progress rendered in the composer's own live region, e.g. dictation\n * state — plain paragraphs are progress, a `role=\"alert\"` paragraph is a\n * failure. Sending and send-failure copy stays the composer's own.\n */\n status?: JSX.Element\n /** The reply target, drawn as a strip above the field. */\n replyTo?: { label: string; onDismiss: () => void }\n /** Ref to the actual message textarea, for host focus and selection behavior. */\n inputRef?: Ref<HTMLTextAreaElement>\n /** Stable host-owned id for the actual textarea. */\n inputId?: string\n /** Accessible name for the actual textarea. Defaults to \"Message\" or \"Reply message\". */\n inputLabel?: string\n /** Additional help text associated with the actual textarea. */\n inputDescription?: string\n /** Existing host description id, combined with `inputDescription` when supplied. */\n inputDescribedBy?: string\n /** Comfortable uses the shared 4rem minimum and 12rem maximum field heights. */\n inputSize?: 'default' | 'comfortable'\n /** The shared resize behavior for the actual textarea. */\n inputResize?: 'vertical' | 'none' | 'both'\n}\n\nexport function MessageComposer(props: MessageComposerProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'value',\n 'onValueChange',\n 'onSubmit',\n 'placeholder',\n 'disabled',\n 'submitLabel',\n 'showHint',\n 'leading',\n 'trailing',\n 'menu',\n 'status',\n 'replyTo',\n 'inputRef',\n 'inputId',\n 'inputLabel',\n 'inputDescription',\n 'inputDescribedBy',\n 'inputSize',\n 'inputResize',\n 'onKeyDown',\n ])\n\n const [sending, setSending] = createSignal(false)\n const [error, setError] = createSignal<string | null>(null)\n const ime = createComposerImeGuard()\n const descriptionId = `message-composer-help-${createUniqueId()}`\n const describedBy = () =>\n [local.inputDescribedBy, local.inputDescription ? descriptionId : undefined]\n .filter(Boolean)\n .join(' ') || undefined\n const actionLabel = () => local.submitLabel ?? 'Send message'\n const pendingLabel = () =>\n `Sending ${local.submitLabel ? local.submitLabel.toLowerCase() : 'message'}`\n\n const canSend = () => local.value.trim().length > 0 && !local.disabled && !sending()\n\n const send = async () => {\n if (!canSend()) return\n setSending(true)\n setError(null)\n try {\n await local.onSubmit()\n } catch {\n // The draft is deliberately left in place. A send that fails and clears the\n // field loses the user's work, which is the worst outcome available here.\n setError('Message not sent. Your draft is still here; retry when the connection recovers.')\n } finally {\n setSending(false)\n }\n }\n\n const onKeyDown: JSX.EventHandlerUnion<HTMLTextAreaElement, KeyboardEvent> = (event) => {\n if (event.defaultPrevented) return\n if (event.key !== 'Enter') return\n // Shift breaks the line; a bare Enter, or a modified one, sends.\n if (event.shiftKey && !event.metaKey && !event.ctrlKey) return\n if (!ime.claimEnter(event)) return\n void send()\n }\n\n const onFormKeyDown: JSX.EventHandlerUnion<HTMLFormElement, KeyboardEvent> = (event) => {\n invokeEventHandler(local.onKeyDown, event)\n if (\n event.defaultPrevented ||\n event.key !== 'Escape' ||\n !error() ||\n event.isComposing ||\n event.keyCode === 229\n ) {\n return\n }\n setError(null)\n event.preventDefault()\n event.stopPropagation()\n }\n\n return (\n <form\n data-slot=\"message-composer\"\n class={cn('flex flex-col gap-2', local.class)}\n onSubmit={(event) => {\n event.preventDefault()\n void send()\n }}\n onKeyDown={onFormKeyDown}\n {...rest}\n >\n <Show when={local.menu}>\n <div>{local.menu}</div>\n </Show>\n\n <Show when={local.replyTo}>\n {(reply) => (\n <div class=\"bg-muted text-muted-foreground flex items-center gap-2 rounded-md px-2.5 py-1.5 text-xs\">\n <span class=\"min-w-0 flex-1 truncate\">Replying to {reply().label}</span>\n <ActionButton\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-2xs\"\n aria-label=\"Cancel reply\"\n tooltip=\"Cancel reply\"\n onClick={() => reply().onDismiss()}\n >\n <X />\n </ActionButton>\n </div>\n )}\n </Show>\n\n <div\n class={cn(\n 'border-input bg-card flex flex-col gap-1 rounded-lg border p-2',\n 'transition-[color,box-shadow,border-color] ease-out',\n 'focus-within:border-ring focus-within:ring-3 focus-within:ring-primary-subtle',\n local.disabled && 'opacity-50'\n )}\n >\n <Textarea\n ref={local.inputRef}\n id={local.inputId}\n onCompositionStart={ime.onCompositionStart}\n onCompositionEnd={ime.onCompositionEnd}\n onFocus={ime.onFocus}\n onBlur={ime.onBlur}\n value={local.value}\n onInput={(event) => local.onValueChange(event.currentTarget.value)}\n onKeyDown={onKeyDown}\n placeholder={local.placeholder ?? 'Write a message…'}\n disabled={local.disabled}\n rows={1}\n aria-label={\n local.inputLabel ?? (local.submitLabel ? `${local.submitLabel} message` : 'Message')\n }\n aria-describedby={describedBy()}\n variant=\"composer\"\n size={local.inputSize ?? 'comfortable'}\n resize={local.inputResize ?? 'vertical'}\n />\n <Show when={local.inputDescription}>\n {(description) => (\n <span id={descriptionId} class=\"sr-only\">\n {description()}\n </span>\n )}\n </Show>\n\n <div class=\"flex items-center gap-1\">\n <Show when={local.leading}>{local.leading}</Show>\n <div class=\"ms-auto flex items-center gap-1\">\n <Show when={local.trailing}>{local.trailing}</Show>\n <ActionButton\n type=\"submit\"\n size=\"icon-sm\"\n disabled={!canSend()}\n aria-label={sending() ? pendingLabel() : actionLabel()}\n tooltip={actionLabel()}\n >\n <Show when={sending()} fallback={<Send />}>\n <Spinner size=\"sm\" label={false} class=\"text-primary-foreground\" />\n </Show>\n </ActionButton>\n </div>\n </div>\n </div>\n\n <Show when={local.showHint}>\n <p class=\"text-muted-foreground flex items-center gap-1.5 text-2xs\">\n <Kbd>Enter</Kbd> to send\n <span aria-hidden=\"true\">·</span>\n <Kbd>Shift</Kbd> <Kbd>Enter</Kbd> for a new line\n </p>\n </Show>\n\n {/* A live region, so the outcome is announced without moving focus out of the\n field the user is typing in. */}\n <div role=\"status\" aria-live=\"polite\" class=\"min-h-0\">\n <Show\n when={sending()}\n fallback={\n <Show when={error()}>\n {(message) => (\n <p class=\"text-foreground border-s-2 border-destructive ps-2 text-xs\">\n {message()}\n </p>\n )}\n </Show>\n }\n >\n {pendingLabel()}…\n </Show>\n <Show when={local.status}>{local.status}</Show>\n </div>\n </form>\n )\n}\n\n/**\n * The attachment control.\n *\n * A slot rather than a built-in, because what can be attached is the application's\n * business — but the *button* is shared, so it looks and behaves the same in both.\n * The count is announced, since a paperclip alone does not say whether anything is\n * attached.\n */\nexport function ComposerAttachmentButton(\n props: ComponentProps<typeof Button> & { count?: number; open?: boolean }\n) {\n const [local, rest] = splitProps(props, ['count', 'open', 'children'])\n\n return (\n <ActionButton\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={local.count ? `${local.count} attached, add an attachment` : 'Add an attachment'}\n aria-expanded={local.open}\n tooltip=\"Add an attachment\"\n class=\"text-muted-foreground\"\n {...rest}\n >\n {local.children ?? <Paperclip />}\n </ActionButton>\n )\n}\n\n/** The hint a composer shows when the field is empty, as a one-line prompt. */\nexport function ComposerHint(props: ComponentProps<'p'>) {\n const [local, rest] = splitProps(props, ['class', 'children'])\n return (\n <p\n class={cn('text-muted-foreground flex items-center gap-1.5 text-2xs', local.class)}\n {...rest}\n >\n {local.children ?? (\n <>\n <CornerDownLeft aria-hidden=\"true\" class=\"size-3\" />\n <Kbd>Enter</Kbd> to send\n </>\n )}\n </p>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { Kbd, KbdChord, KbdGroup, kbdChordKeys, kbdGroupVariants, kbdVariants, type KbdChordProps, type KbdSize, } from './kbd';
|
|
2
|
+
export { platformModifierKey, searchShortcutKeyshortcuts, searchShortcutLabel, } from './platform-modifier';
|
|
2
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/kbd/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,GAAG,EACH,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,KAAK,aAAa,EAClB,KAAK,OAAO,GACb,MAAM,OAAO,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/kbd/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,GAAG,EACH,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,KAAK,aAAa,EAClB,KAAK,OAAO,GACb,MAAM,OAAO,CAAA;AACd,OAAO,EACL,mBAAmB,EACnB,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,qBAAqB,CAAA"}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import { Kbd, KbdChord, KbdGroup, kbdChordKeys, kbdGroupVariants, kbdVariants } from "./kbd.js";
|
|
2
|
-
|
|
2
|
+
import { platformModifierKey, searchShortcutKeyshortcuts, searchShortcutLabel } from "./platform-modifier.js";
|
|
3
|
+
export { Kbd, KbdChord, KbdGroup, kbdChordKeys, kbdGroupVariants, kbdVariants, platformModifierKey, searchShortcutKeyshortcuts, searchShortcutLabel };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which modifier a chord should advertise.
|
|
3
|
+
*
|
|
4
|
+
* Every global chord in the product binds `(metaKey || ctrlKey)`, so the
|
|
5
|
+
* working key is the platform's command modifier wherever the app runs — but
|
|
6
|
+
* Apple platforms draw it as ⌘ and everyone else spells it Ctrl. A chord
|
|
7
|
+
* label drawn from anything else (a hardcoded ⌘, say) advertises a key that
|
|
8
|
+
* does not work as drawn on Windows and Linux.
|
|
9
|
+
*
|
|
10
|
+
* These helpers are the one place that decides the glyph, so every surface
|
|
11
|
+
* that draws or announces a shortcut — rail rows, menus, a pane's search
|
|
12
|
+
* field — draws the same one. They sit next to the `Kbd` caps because the
|
|
13
|
+
* glyph is a property of the drawing, not of the feature that shows it: a
|
|
14
|
+
* spelled-out `Ctrl` needs one cap per word (a `KbdGroup`), while `⌘K` can
|
|
15
|
+
* ride `KbdChord`'s one-cap-per-character split.
|
|
16
|
+
*/
|
|
17
|
+
/** The modifier glyph the running OS renders for Meta: ⌘ on Apple platforms, Ctrl elsewhere. */
|
|
18
|
+
export declare function platformModifierKey(platform?: string): '⌘' | 'Ctrl';
|
|
19
|
+
/** The workspace search chord label as the rail's hover text draws it: the
|
|
20
|
+
* platform modifier plus K (`⌘K`, or `Ctrl+K` where ⌘ does not exist). */
|
|
21
|
+
export declare function searchShortcutLabel(platform?: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* The `aria-keyshortcuts` for the search chord. The binding accepts Meta and
|
|
24
|
+
* Ctrl alike, so the announced shortcuts name both: a hardcoded `Meta+K`
|
|
25
|
+
* would promise a key the row does not keep on non-Apple platforms.
|
|
26
|
+
*/
|
|
27
|
+
export declare const searchShortcutKeyshortcuts = "Meta+K Control+K";
|
|
28
|
+
//# sourceMappingURL=platform-modifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform-modifier.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/kbd/platform-modifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,gGAAgG;AAChG,wBAAgB,mBAAmB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,GAAG,GAAG,MAAM,CAKnE;AAED;2EAC2E;AAC3E,wBAAgB,mBAAmB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,qBAAqB,CAAA"}
|