@adea-ai/ui 0.63.3 → 0.64.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/README.md +46 -0
- package/dist/NOTICE +54 -0
- package/dist/components/conversation/busy-send-button.d.ts +28 -0
- package/dist/components/conversation/busy-send-button.d.ts.map +1 -0
- package/dist/components/conversation/busy-send-button.js +184 -0
- package/dist/components/conversation/busy-send-button.js.map +1 -0
- package/dist/components/conversation/conversation-surface.d.ts +5 -1
- package/dist/components/conversation/conversation-surface.d.ts.map +1 -1
- package/dist/components/conversation/conversation-surface.js +42 -62
- package/dist/components/conversation/conversation-surface.js.map +1 -1
- package/dist/components/conversation/index.d.ts +1 -0
- package/dist/components/conversation/index.d.ts.map +1 -1
- package/dist/components/conversation/index.js +2 -1
- package/dist/components/conversation/scroll-follow-core.d.ts +181 -0
- package/dist/components/conversation/scroll-follow-core.d.ts.map +1 -0
- package/dist/components/conversation/scroll-follow-core.js +139 -0
- package/dist/components/conversation/scroll-follow-core.js.map +1 -0
- package/dist/components/conversation/scroll-follow.d.ts +13 -0
- package/dist/components/conversation/scroll-follow.d.ts.map +1 -0
- package/dist/components/conversation/scroll-follow.js +102 -0
- package/dist/components/conversation/scroll-follow.js.map +1 -0
- package/dist/index.js +2 -1
- package/dist/lib/tokens.d.ts +3 -0
- package/dist/lib/tokens.d.ts.map +1 -1
- package/dist/lib/tokens.js +3 -0
- package/dist/lib/tokens.js.map +1 -1
- package/dist/r/conversation.json +17 -0
- package/dist/r/registry.json +17 -0
- package/dist/r/src/components/conversation/busy-send-button.tsx +161 -0
- package/dist/r/src/components/conversation/conversation-surface.tsx +45 -48
- package/dist/r/src/components/conversation/index.ts +1 -0
- package/dist/r/src/components/conversation/scroll-follow-core.ts +314 -0
- package/dist/r/src/components/conversation/scroll-follow.ts +133 -0
- package/dist/r/src/lib/tokens.ts +3 -0
- package/dist/r/src/styles/theme.css +9 -0
- package/package.json +1 -1
- package/registry.json +17 -0
- package/src/components/conversation/busy-send-button.tsx +161 -0
- package/src/components/conversation/conversation-surface.tsx +45 -48
- package/src/components/conversation/index.ts +1 -0
- package/src/components/conversation/scroll-follow-core.ts +314 -0
- package/src/components/conversation/scroll-follow.ts +133 -0
- package/src/lib/tokens.ts +3 -0
- package/src/styles/theme.css +9 -0
package/README.md
CHANGED
|
@@ -153,6 +153,52 @@ The toolbar switches theme, accent, typeface and density live, which is the
|
|
|
153
153
|
fastest way to see how a component behaves across the catalogue rather than in
|
|
154
154
|
one theme.
|
|
155
155
|
|
|
156
|
+
## Busy composer action
|
|
157
|
+
|
|
158
|
+
Square icon controls use the control-height tokens through the Tailwind `--size`
|
|
159
|
+
mapping; `--spacing-control-*` remains the separate inline-padding ladder.
|
|
160
|
+
Text controls, icon buttons and toggles share the same height in both densities.
|
|
161
|
+
Dimensions remain rem-based and follow the consumer's root font size. The browser
|
|
162
|
+
geometry lane checks all six rungs, heights and padding under both themes/densities.
|
|
163
|
+
|
|
164
|
+
`BusySendButton` translates KiroCrew's split fire/mode-picker composition. Pass
|
|
165
|
+
controlled `mode`, `onModeChange` and `onFire`. `disabled` prevents firing while
|
|
166
|
+
leaving mode choice available before typing; `selectionDisabled` gates choosing.
|
|
167
|
+
Inject `unavailable` reasons for unsupported or unauthorized modes: rows remain
|
|
168
|
+
visible, and the selected action cannot fire. Supply `alternateActionHint` only
|
|
169
|
+
when the host implements the described keyboard chord. The component adds no
|
|
170
|
+
keyboard chord, preference storage, runtime command or queue. Persistence and
|
|
171
|
+
same-session preference synchronization remain application-owned.
|
|
172
|
+
|
|
173
|
+
Kobalte owns the menu and focus lifecycle. Its Tab prevention lacks row cycling,
|
|
174
|
+
so a menu-scoped donor translation cycles enabled rows without a document listener.
|
|
175
|
+
Stories cover before-typing, working,
|
|
176
|
+
unavailable and read-only states. `bun run test:components` checks the isolated
|
|
177
|
+
Solid composition headlessly in Chromium and WebKit. Full Chat composition,
|
|
178
|
+
packed distribution and real consumer/native acceptance remain separate gates.
|
|
179
|
+
|
|
180
|
+
## Transcript follow
|
|
181
|
+
|
|
182
|
+
`ConversationSurface` follows streamed text, earlier-row growth, turn collapse
|
|
183
|
+
and pane resizing using the KiroCrew plain-scroller decision contract. A
|
|
184
|
+
deliberate upward scroll releases follow, including small moves near the bottom.
|
|
185
|
+
Returning to the bottom or choosing **Jump to latest** re-arms it; the action
|
|
186
|
+
does not estimate unread messages from pixel distance. Instant pins keep
|
|
187
|
+
self-scroll attribution synchronized. Keyboard jumping returns focus to the
|
|
188
|
+
transcript when its jump control disappears; automatic pins never move focus.
|
|
189
|
+
|
|
190
|
+
`threshold` controls jump-button visibility (80px by default), independently of
|
|
191
|
+
follow intent. `follow={false}` is fully inert: positioning belongs to the host,
|
|
192
|
+
with no observer or mount pin. Re-enabling it explicitly re-arms at the bottom;
|
|
193
|
+
it is not an automatic restore-and-resume flag. An optional `resetKey` also
|
|
194
|
+
re-arms follow when that identity changes. Canonical
|
|
195
|
+
session identity, durable restoration and virtualized transcript windows remain
|
|
196
|
+
application-owned. `ref` and `onScroll` still reach the inner scroller.
|
|
197
|
+
|
|
198
|
+
`bun run test:components` checks the built component in Chromium and WebKit.
|
|
199
|
+
The `SurfaceFollowing` story exercises streaming, collapse and pane resizing;
|
|
200
|
+
source/license/test provenance is recorded in `NOTICE`.
|
|
201
|
+
|
|
156
202
|
## Composer keyboard behavior
|
|
157
203
|
|
|
158
204
|
`MessageComposer` preserves native IME candidate commits and suppresses sending
|
package/dist/NOTICE
CHANGED
|
@@ -49,6 +49,60 @@ Origin UI / coss (AGPL-3.0-or-later) was consulted for visual ideas only. No
|
|
|
49
49
|
code, class string, or asset from that project is reproduced here, because its
|
|
50
50
|
licence is incompatible with this distribution.
|
|
51
51
|
|
|
52
|
+
Busy composer action translated from KiroCrew
|
|
53
|
+
--------------------------------------------
|
|
54
|
+
Original: website/src/components/BusySendButton.tsx
|
|
55
|
+
Also translated: website/src/hooks/useMenuKeyboard.ts (enabled-row Tab cycle)
|
|
56
|
+
Revision: 283e136c0f902e965a535a7c9548c57c7504fed0
|
|
57
|
+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
58
|
+
This product includes software developed at Amazon.com, Inc.
|
|
59
|
+
Apache License, Version 2.0 (see LICENSE).
|
|
60
|
+
Destination: packages/ui/src/components/conversation/busy-send-button.tsx
|
|
61
|
+
|
|
62
|
+
The split fire/mode-picker structure, descriptions, independent disabled-fire
|
|
63
|
+
state, selected radio treatment and conditional alternate-action hint are
|
|
64
|
+
substantially translated to Solid and the shared design system. Kobalte owns
|
|
65
|
+
portal positioning, arrow navigation, dismissal and focus restoration. Its
|
|
66
|
+
Tab prevention lacks row cycling, so the donor cycle is translated as a scoped
|
|
67
|
+
menu handler over enabled rows; no document listener or manual focus trap.
|
|
68
|
+
Donor per-slot storage/migration/broadcast, i18n/platform lookup, React runtime,
|
|
69
|
+
manual portal and document listeners are not copied. Applications inject mode,
|
|
70
|
+
action and unavailable reasons. No runtime queue or force-reset authority added.
|
|
71
|
+
Nearest tests: BusySendButton.menuKeyboard.test.tsx and
|
|
72
|
+
BusySendButton.slotScope.test.tsx; controlled mode replaces donor storage tests,
|
|
73
|
+
while keyboard/focus expectations are exercised in Chromium and WebKit.
|
|
74
|
+
|
|
75
|
+
## Plain transcript follow translated from KiroCrew
|
|
76
|
+
|
|
77
|
+
`packages/ui/src/components/conversation/scroll-follow-core.ts`,
|
|
78
|
+
`scroll-follow.ts` and ConversationSurface substantially translate the plain
|
|
79
|
+
scroller lifecycle in `website/src/app-sdk/useChatScrollFollow.ts` and its pure
|
|
80
|
+
`website/src/hooks/virtualizer/FollowController.ts` decisions, pinned at
|
|
81
|
+
`283e136c0f902e965a535a7c9548c57c7504fed0`.
|
|
82
|
+
|
|
83
|
+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
84
|
+
This product includes software developed at Amazon.com, Inc.
|
|
85
|
+
(https://www.amazon.com/). Licensed under the Apache License, Version 2.0.
|
|
86
|
+
|
|
87
|
+
Retained: separate follow intent and jump visibility, synchronous scroll-up race
|
|
88
|
+
fencing, direction-aware re-engagement, self-scroll attribution, DPR tolerance,
|
|
89
|
+
content growth/collapse and viewport shrink/growth distinctions, instant pins,
|
|
90
|
+
explicit disabled/reset behavior and content/viewport observer cleanup. Solid
|
|
91
|
+
ownership replaces React refs/effects. Applications retain identity, durable
|
|
92
|
+
scroll restoration and virtualized row measurement/anchor authority.
|
|
93
|
+
|
|
94
|
+
The jump label follows `website/src/app-sdk/ChatScrollChrome.tsx`'s action-only
|
|
95
|
+
contract; pixel distance cannot establish an unread message count. The existing
|
|
96
|
+
shared button appearance and configurable 80px visibility default are retained.
|
|
97
|
+
Keyboard jumping returns focus to the focusable transcript as its jump control
|
|
98
|
+
disappears; this hardens a donor edge case without moving focus during automatic
|
|
99
|
+
pins. No donor edge-fade styling, React runtime, virtualizer, asset, font or session
|
|
100
|
+
store is copied. Tests translate applicable cases from
|
|
101
|
+
`website/src/test/useChatScrollFollow.test.tsx` and `FollowController.test.ts`;
|
|
102
|
+
Bun/browser integration and deterministic bounded grids replace React/jsdom,
|
|
103
|
+
Vitest and fast-check without adding dependencies. The full Chat product
|
|
104
|
+
composition and application adoption remain separate gates.
|
|
105
|
+
|
|
52
106
|
## Composer IME input ownership translated from KiroCrew
|
|
53
107
|
|
|
54
108
|
`packages/ui/src/components/conversation/ime-guard.ts` and its MessageComposer
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type BusySendMode = 'steer' | 'queue';
|
|
2
|
+
export type BusySendButtonProps = {
|
|
3
|
+
mode: BusySendMode;
|
|
4
|
+
onModeChange: (mode: BusySendMode) => void;
|
|
5
|
+
/** Executes only the selected action. The host owns the draft and command. */
|
|
6
|
+
onFire: () => void;
|
|
7
|
+
/** An empty draft or pending delivery disables fire, but not choosing a mode. */
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
/** Disable mode selection when the host has no authority to change preferences. */
|
|
10
|
+
selectionDisabled?: boolean;
|
|
11
|
+
/** Host capability/authority failures: unavailable rows remain visible. */
|
|
12
|
+
unavailable?: Partial<Record<BusySendMode, string>>;
|
|
13
|
+
/** Supply only when the host implements this alternate-action keyboard chord. */
|
|
14
|
+
alternateActionHint?: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Kiro's split action/menu composition, with app-owned mode and availability.
|
|
18
|
+
* Picking a mode never fires it. Persistence, migration and same-session
|
|
19
|
+
* broadcasts belong to the consumer; this component has no storage or global listeners.
|
|
20
|
+
* Kobalte replaces the donor manual portal/positioning and document listeners:
|
|
21
|
+
* arrows wrap and selection/Escape return focus. Kobalte 0.13.14 prevents Tab
|
|
22
|
+
* without moving focus, so a menu-scoped translation of the donor Tab cycle
|
|
23
|
+
* fills that gap. It discovers enabled radio rows only; no document trap.
|
|
24
|
+
* No force-reset timer or runtime authority is translated from the adjacent
|
|
25
|
+
* donor stop flow.
|
|
26
|
+
*/
|
|
27
|
+
export declare function BusySendButton(props: BusySendButtonProps): import("solid-js").JSX.Element;
|
|
28
|
+
//# sourceMappingURL=busy-send-button.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"busy-send-button.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/busy-send-button.tsx"],"names":[],"mappings":"AAmBA,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,OAAO,CAAA;AAC5C,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,YAAY,CAAA;IAClB,YAAY,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAA;IAC1C,8EAA8E;IAC9E,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,mFAAmF;IACnF,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,2EAA2E;IAC3E,WAAW,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAA;IACnD,iFAAiF;IACjF,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B,CAAA;AASD;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,kCA2GxD"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { Button } from "../ui/button/button.js";
|
|
2
|
+
import { ButtonGroup } from "../ui/button-group/button-group.js";
|
|
3
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger } from "../ui/dropdown-menu/dropdown-menu.js";
|
|
4
|
+
import { createComponent, insert, setAttribute, template } from "solid-js/web";
|
|
5
|
+
import { ArrowUpFromLine, ChevronDown, Target } from "lucide-solid";
|
|
6
|
+
import { For, Show, createUniqueId } from "solid-js";
|
|
7
|
+
//#region src/components/conversation/busy-send-button.tsx
|
|
8
|
+
var _tmpl$ = /*#__PURE__*/ template(`<p class="text-muted-foreground px-2 py-1 text-xs">`);
|
|
9
|
+
var _tmpl$2 = /*#__PURE__*/ template(`<div data-slot=busy-send-button class="flex w-fit flex-col gap-1">`);
|
|
10
|
+
var _tmpl$3 = /*#__PURE__*/ template(`<div class="flex min-w-0 flex-col gap-1"><span></span><span class="text-muted-foreground text-xs">`);
|
|
11
|
+
var _tmpl$4 = /*#__PURE__*/ template(`<span class="text-muted-foreground text-xs">`);
|
|
12
|
+
var _tmpl$5 = /*#__PURE__*/ template(`<p class="text-muted-foreground text-xs">`);
|
|
13
|
+
var modes = ["steer", "queue"];
|
|
14
|
+
var labels = {
|
|
15
|
+
steer: "Steer",
|
|
16
|
+
queue: "Queue"
|
|
17
|
+
};
|
|
18
|
+
var descriptions = {
|
|
19
|
+
steer: "Act on this right away",
|
|
20
|
+
queue: "Run after the current work finishes"
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Kiro's split action/menu composition, with app-owned mode and availability.
|
|
24
|
+
* Picking a mode never fires it. Persistence, migration and same-session
|
|
25
|
+
* broadcasts belong to the consumer; this component has no storage or global listeners.
|
|
26
|
+
* Kobalte replaces the donor manual portal/positioning and document listeners:
|
|
27
|
+
* arrows wrap and selection/Escape return focus. Kobalte 0.13.14 prevents Tab
|
|
28
|
+
* without moving focus, so a menu-scoped translation of the donor Tab cycle
|
|
29
|
+
* fills that gap. It discovers enabled radio rows only; no document trap.
|
|
30
|
+
* No force-reset timer or runtime authority is translated from the adjacent
|
|
31
|
+
* donor stop flow.
|
|
32
|
+
*/
|
|
33
|
+
function BusySendButton(props) {
|
|
34
|
+
const reasonId = createUniqueId();
|
|
35
|
+
const reason = () => props.unavailable?.[props.mode];
|
|
36
|
+
const variant = () => props.mode === "steer" ? "default" : "secondary";
|
|
37
|
+
return (() => {
|
|
38
|
+
var _el$ = _tmpl$2();
|
|
39
|
+
insert(_el$, createComponent(DropdownMenu, {
|
|
40
|
+
placement: "top-end",
|
|
41
|
+
modal: false,
|
|
42
|
+
get children() {
|
|
43
|
+
return [createComponent(ButtonGroup, {
|
|
44
|
+
label: "Send while busy",
|
|
45
|
+
get children() {
|
|
46
|
+
return [createComponent(Button, {
|
|
47
|
+
type: "button",
|
|
48
|
+
size: "icon-sm",
|
|
49
|
+
get variant() {
|
|
50
|
+
return variant();
|
|
51
|
+
},
|
|
52
|
+
get ["aria-label"]() {
|
|
53
|
+
return props.mode === "steer" ? "Steer" : "Queue message";
|
|
54
|
+
},
|
|
55
|
+
get ["aria-describedby"]() {
|
|
56
|
+
return reason() ? reasonId : void 0;
|
|
57
|
+
},
|
|
58
|
+
get disabled() {
|
|
59
|
+
return props.disabled || !!reason();
|
|
60
|
+
},
|
|
61
|
+
onClick: () => {
|
|
62
|
+
if (!props.disabled && !reason()) props.onFire();
|
|
63
|
+
},
|
|
64
|
+
get children() {
|
|
65
|
+
return createComponent(Show, {
|
|
66
|
+
get when() {
|
|
67
|
+
return props.mode === "steer";
|
|
68
|
+
},
|
|
69
|
+
get fallback() {
|
|
70
|
+
return createComponent(ArrowUpFromLine, {});
|
|
71
|
+
},
|
|
72
|
+
get children() {
|
|
73
|
+
return createComponent(Target, {});
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}), createComponent(DropdownMenuTrigger, {
|
|
78
|
+
as: Button,
|
|
79
|
+
type: "button",
|
|
80
|
+
size: "icon-sm",
|
|
81
|
+
get variant() {
|
|
82
|
+
return variant();
|
|
83
|
+
},
|
|
84
|
+
"aria-label": "Send options",
|
|
85
|
+
get disabled() {
|
|
86
|
+
return props.selectionDisabled;
|
|
87
|
+
},
|
|
88
|
+
get children() {
|
|
89
|
+
return createComponent(ChevronDown, {});
|
|
90
|
+
}
|
|
91
|
+
})];
|
|
92
|
+
}
|
|
93
|
+
}), createComponent(DropdownMenuContent, {
|
|
94
|
+
hideArrow: true,
|
|
95
|
+
get children() {
|
|
96
|
+
return [createComponent(DropdownMenuRadioGroup, {
|
|
97
|
+
"on:keydown": (event) => {
|
|
98
|
+
if (event.key !== "Tab" || event.isComposing || !(event.currentTarget instanceof HTMLElement)) return;
|
|
99
|
+
const rows = Array.from(event.currentTarget.querySelectorAll("[role=\"menuitemradio\"]:not([aria-disabled=\"true\"])"));
|
|
100
|
+
if (!rows.length) return;
|
|
101
|
+
event.preventDefault();
|
|
102
|
+
event.stopPropagation();
|
|
103
|
+
const active = document.activeElement;
|
|
104
|
+
const index = active instanceof HTMLElement ? rows.indexOf(active) : -1;
|
|
105
|
+
rows[index < 0 ? event.shiftKey ? rows.length - 1 : 0 : (index + (event.shiftKey ? -1 : 1) + rows.length) % rows.length]?.focus();
|
|
106
|
+
},
|
|
107
|
+
get value() {
|
|
108
|
+
return props.mode;
|
|
109
|
+
},
|
|
110
|
+
onChange: (value) => {
|
|
111
|
+
if ((value === "steer" || value === "queue") && !props.selectionDisabled && !props.unavailable?.[value]) props.onModeChange(value);
|
|
112
|
+
},
|
|
113
|
+
get children() {
|
|
114
|
+
return createComponent(For, {
|
|
115
|
+
each: modes,
|
|
116
|
+
children: (mode) => createComponent(DropdownMenuRadioItem, {
|
|
117
|
+
value: mode,
|
|
118
|
+
closeOnSelect: true,
|
|
119
|
+
get disabled() {
|
|
120
|
+
return props.selectionDisabled || !!props.unavailable?.[mode];
|
|
121
|
+
},
|
|
122
|
+
get children() {
|
|
123
|
+
return [createComponent(Show, {
|
|
124
|
+
when: mode === "steer",
|
|
125
|
+
get fallback() {
|
|
126
|
+
return createComponent(ArrowUpFromLine, {});
|
|
127
|
+
},
|
|
128
|
+
get children() {
|
|
129
|
+
return createComponent(Target, {});
|
|
130
|
+
}
|
|
131
|
+
}), (() => {
|
|
132
|
+
var _el$3 = _tmpl$3(), _el$4 = _el$3.firstChild, _el$5 = _el$4.nextSibling;
|
|
133
|
+
insert(_el$4, () => labels[mode]);
|
|
134
|
+
insert(_el$5, () => descriptions[mode]);
|
|
135
|
+
insert(_el$3, createComponent(Show, {
|
|
136
|
+
get when() {
|
|
137
|
+
return props.unavailable?.[mode];
|
|
138
|
+
},
|
|
139
|
+
children: (message) => (() => {
|
|
140
|
+
var _el$6 = _tmpl$4();
|
|
141
|
+
insert(_el$6, message);
|
|
142
|
+
return _el$6;
|
|
143
|
+
})()
|
|
144
|
+
}), null);
|
|
145
|
+
return _el$3;
|
|
146
|
+
})()];
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}), createComponent(Show, {
|
|
152
|
+
get when() {
|
|
153
|
+
return props.alternateActionHint;
|
|
154
|
+
},
|
|
155
|
+
get children() {
|
|
156
|
+
return [createComponent(DropdownMenuSeparator, {}), (() => {
|
|
157
|
+
var _el$2 = _tmpl$();
|
|
158
|
+
insert(_el$2, () => props.alternateActionHint);
|
|
159
|
+
return _el$2;
|
|
160
|
+
})()];
|
|
161
|
+
}
|
|
162
|
+
})];
|
|
163
|
+
}
|
|
164
|
+
})];
|
|
165
|
+
}
|
|
166
|
+
}), null);
|
|
167
|
+
insert(_el$, createComponent(Show, {
|
|
168
|
+
get when() {
|
|
169
|
+
return reason();
|
|
170
|
+
},
|
|
171
|
+
children: (message) => (() => {
|
|
172
|
+
var _el$7 = _tmpl$5();
|
|
173
|
+
setAttribute(_el$7, "id", reasonId);
|
|
174
|
+
insert(_el$7, message);
|
|
175
|
+
return _el$7;
|
|
176
|
+
})()
|
|
177
|
+
}), null);
|
|
178
|
+
return _el$;
|
|
179
|
+
})();
|
|
180
|
+
}
|
|
181
|
+
//#endregion
|
|
182
|
+
export { BusySendButton };
|
|
183
|
+
|
|
184
|
+
//# sourceMappingURL=busy-send-button.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"busy-send-button.js","names":["ArrowUpFromLine","ChevronDown","Target","For","Show","createUniqueId","Button","ButtonGroup","DropdownMenu","DropdownMenuContent","DropdownMenuRadioGroup","DropdownMenuRadioItem","DropdownMenuSeparator","DropdownMenuTrigger","BusySendMode","BusySendButtonProps","mode","onModeChange","onFire","disabled","selectionDisabled","unavailable","Partial","Record","alternateActionHint","modes","labels","steer","queue","descriptions","BusySendButton","props","reasonId","reason","variant","const","_el$","_tmpl$2","_$insert","_$createComponent","placement","modal","children","label","type","size","aria-label","aria-describedby","undefined","onClick","when","fallback","as","hideArrow","on:keydown","event","KeyboardEvent","key","isComposing","currentTarget","HTMLElement","rows","Array","from","querySelectorAll","length","preventDefault","stopPropagation","active","document","activeElement","index","indexOf","next","shiftKey","focus","value","onChange","each","closeOnSelect","_el$3","_tmpl$3","_el$4","firstChild","_el$5","nextSibling","message","_el$6","_tmpl$4","_el$2","_tmpl$","_el$7","_tmpl$5","_$setAttribute","_$createComponent"],"sources":["../../../src/components/conversation/busy-send-button.tsx"],"sourcesContent":["/*\n * Substantially translated from KiroCrew website/src/components/BusySendButton.tsx\n * at 283e136c0f902e965a535a7c9548c57c7504fed0.\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * Licensed under Apache-2.0; see LICENSE and NOTICE.\n */\nimport { ArrowUpFromLine, ChevronDown, Target } from 'lucide-solid'\nimport { For, Show, createUniqueId } from 'solid-js'\nimport { Button } from '../ui/button/button'\nimport { ButtonGroup } from '../ui/button-group/button-group'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '../ui/dropdown-menu/dropdown-menu'\n\nexport type BusySendMode = 'steer' | 'queue'\nexport type BusySendButtonProps = {\n mode: BusySendMode\n onModeChange: (mode: BusySendMode) => void\n /** Executes only the selected action. The host owns the draft and command. */\n onFire: () => void\n /** An empty draft or pending delivery disables fire, but not choosing a mode. */\n disabled?: boolean\n /** Disable mode selection when the host has no authority to change preferences. */\n selectionDisabled?: boolean\n /** Host capability/authority failures: unavailable rows remain visible. */\n unavailable?: Partial<Record<BusySendMode, string>>\n /** Supply only when the host implements this alternate-action keyboard chord. */\n alternateActionHint?: string\n}\n\nconst modes: BusySendMode[] = ['steer', 'queue']\nconst labels = { steer: 'Steer', queue: 'Queue' }\nconst descriptions = {\n steer: 'Act on this right away',\n queue: 'Run after the current work finishes',\n}\n\n/**\n * Kiro's split action/menu composition, with app-owned mode and availability.\n * Picking a mode never fires it. Persistence, migration and same-session\n * broadcasts belong to the consumer; this component has no storage or global listeners.\n * Kobalte replaces the donor manual portal/positioning and document listeners:\n * arrows wrap and selection/Escape return focus. Kobalte 0.13.14 prevents Tab\n * without moving focus, so a menu-scoped translation of the donor Tab cycle\n * fills that gap. It discovers enabled radio rows only; no document trap.\n * No force-reset timer or runtime authority is translated from the adjacent\n * donor stop flow.\n */\nexport function BusySendButton(props: BusySendButtonProps) {\n const reasonId = createUniqueId()\n const reason = () => props.unavailable?.[props.mode]\n const variant = () => (props.mode === 'steer' ? ('default' as const) : ('secondary' as const))\n return (\n <div data-slot=\"busy-send-button\" class=\"flex w-fit flex-col gap-1\">\n <DropdownMenu placement=\"top-end\" modal={false}>\n <ButtonGroup label=\"Send while busy\">\n <Button\n type=\"button\"\n size=\"icon-sm\"\n variant={variant()}\n aria-label={props.mode === 'steer' ? 'Steer' : 'Queue message'}\n aria-describedby={reason() ? reasonId : undefined}\n disabled={props.disabled || !!reason()}\n onClick={() => {\n if (!props.disabled && !reason()) props.onFire()\n }}\n >\n <Show when={props.mode === 'steer'} fallback={<ArrowUpFromLine />}>\n <Target />\n </Show>\n </Button>\n <DropdownMenuTrigger\n as={Button}\n type=\"button\"\n size=\"icon-sm\"\n variant={variant()}\n aria-label=\"Send options\"\n disabled={props.selectionDisabled}\n >\n <ChevronDown />\n </DropdownMenuTrigger>\n </ButtonGroup>\n <DropdownMenuContent hideArrow>\n <DropdownMenuRadioGroup\n on:keydown={(event: KeyboardEvent) => {\n if (\n event.key !== 'Tab' ||\n event.isComposing ||\n !(event.currentTarget instanceof HTMLElement)\n )\n return\n const rows = Array.from(\n event.currentTarget.querySelectorAll<HTMLElement>(\n '[role=\"menuitemradio\"]:not([aria-disabled=\"true\"])'\n )\n )\n if (!rows.length) return\n event.preventDefault()\n event.stopPropagation()\n const active = document.activeElement\n const index = active instanceof HTMLElement ? rows.indexOf(active) : -1\n const next =\n index < 0\n ? event.shiftKey\n ? rows.length - 1\n : 0\n : (index + (event.shiftKey ? -1 : 1) + rows.length) % rows.length\n rows[next]?.focus()\n }}\n value={props.mode}\n onChange={(value) => {\n if (\n (value === 'steer' || value === 'queue') &&\n !props.selectionDisabled &&\n !props.unavailable?.[value]\n )\n props.onModeChange(value)\n }}\n >\n <For each={modes}>\n {(mode) => (\n <DropdownMenuRadioItem\n value={mode}\n closeOnSelect\n disabled={props.selectionDisabled || !!props.unavailable?.[mode]}\n >\n <Show when={mode === 'steer'} fallback={<ArrowUpFromLine />}>\n <Target />\n </Show>\n <div class=\"flex min-w-0 flex-col gap-1\">\n <span>{labels[mode]}</span>\n <span class=\"text-muted-foreground text-xs\">{descriptions[mode]}</span>\n <Show when={props.unavailable?.[mode]}>\n {(message) => <span class=\"text-muted-foreground text-xs\">{message()}</span>}\n </Show>\n </div>\n </DropdownMenuRadioItem>\n )}\n </For>\n </DropdownMenuRadioGroup>\n <Show when={props.alternateActionHint}>\n <DropdownMenuSeparator />\n <p class=\"text-muted-foreground px-2 py-1 text-xs\">{props.alternateActionHint}</p>\n </Show>\n </DropdownMenuContent>\n </DropdownMenu>\n <Show when={reason()}>\n {(message) => (\n <p id={reasonId} class=\"text-muted-foreground text-xs\">\n {message()}\n </p>\n )}\n </Show>\n </div>\n )\n}\n"],"mappings":"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"}
|
|
@@ -10,7 +10,7 @@ import type { ComponentProps, JSX } from 'solid-js';
|
|
|
10
10
|
* to the end by an arriving message, and that is the single most common way a
|
|
11
11
|
* chat surface is made unusable.
|
|
12
12
|
* - **Offer a way back.** Once the reader is not at the bottom, a control appears
|
|
13
|
-
* that returns them
|
|
13
|
+
* that returns them. Pixel distance cannot prove an unread message count.
|
|
14
14
|
* - **`overscroll-contain`.** The transcript scrolls to its end and stops, rather
|
|
15
15
|
* than dragging the pane behind it — which in a desktop shell means the whole
|
|
16
16
|
* window moves.
|
|
@@ -18,6 +18,10 @@ import type { ComponentProps, JSX } from 'solid-js';
|
|
|
18
18
|
export type ConversationSurfaceProps = ComponentProps<'div'> & {
|
|
19
19
|
/** How far from the bottom still counts as "at the bottom", in px. */
|
|
20
20
|
threshold?: number;
|
|
21
|
+
/** Disable automatic positioning while the host owns scroll restoration. */
|
|
22
|
+
follow?: boolean;
|
|
23
|
+
/** Optional conversation identity: changing it re-arms follow at the bottom. */
|
|
24
|
+
resetKey?: string;
|
|
21
25
|
/** Rendered above the transcript when there is nothing in it. */
|
|
22
26
|
empty?: JSX.Element;
|
|
23
27
|
/** A header that scrolls with the transcript, e.g. a day divider or a banner. */
|
|
@@ -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;
|
|
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,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,iEAAiE;IACjE,KAAK,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACnB,iFAAiF;IACjF,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;CACrB,CAAA;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,eAmElE"}
|
|
@@ -1,63 +1,50 @@
|
|
|
1
1
|
import { cn } from "../../lib/utils.js";
|
|
2
2
|
import { Button } from "../ui/button/button.js";
|
|
3
|
-
import {
|
|
3
|
+
import { createScrollFollow } from "./scroll-follow.js";
|
|
4
|
+
import { className, createComponent, effect, insert, mergeProps, spread, template, use } from "solid-js/web";
|
|
4
5
|
import { ArrowDown } from "lucide-solid";
|
|
5
|
-
import { Show,
|
|
6
|
+
import { Show, splitProps } from "solid-js";
|
|
6
7
|
//#region src/components/conversation/conversation-surface.tsx
|
|
7
8
|
var _tmpl$ = /*#__PURE__*/ template(`<div class="pointer-events-none absolute inset-x-0 bottom-3 flex justify-center">`);
|
|
8
|
-
var _tmpl$2 = /*#__PURE__*/ template(`<div data-slot=conversation-surface><div class="min-h-0 flex-1 overflow-y-auto overscroll-contain">`);
|
|
9
|
+
var _tmpl$2 = /*#__PURE__*/ template(`<div data-slot=conversation-surface><div class="min-h-0 flex-1 overflow-y-auto overscroll-contain"><div data-slot=conversation-content>`);
|
|
9
10
|
var _tmpl$3 = /*#__PURE__*/ template(`<div class=p-4>`);
|
|
10
|
-
/** How far the reader is from the end of the transcript, in px. */
|
|
11
|
-
function distanceFromBottom(element) {
|
|
12
|
-
return element.scrollHeight - element.scrollTop - element.clientHeight;
|
|
13
|
-
}
|
|
14
11
|
function ConversationSurface(props) {
|
|
15
12
|
const [local, rest] = splitProps(props, [
|
|
16
13
|
"class",
|
|
17
14
|
"threshold",
|
|
15
|
+
"follow",
|
|
16
|
+
"resetKey",
|
|
18
17
|
"empty",
|
|
19
18
|
"header",
|
|
20
|
-
"children"
|
|
19
|
+
"children",
|
|
20
|
+
"onScroll",
|
|
21
|
+
"ref",
|
|
22
|
+
"tabindex"
|
|
21
23
|
]);
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const jumpToLatest = () => {
|
|
33
|
-
scroller?.scrollTo({
|
|
34
|
-
top: scroller.scrollHeight,
|
|
35
|
-
behavior: "smooth"
|
|
36
|
-
});
|
|
37
|
-
setAtBottom(true);
|
|
38
|
-
setMissed(0);
|
|
39
|
-
};
|
|
40
|
-
const observer = () => {
|
|
41
|
-
if (!scroller) return;
|
|
42
|
-
const mutation = new MutationObserver(() => {
|
|
43
|
-
if (atBottom()) scroller?.scrollTo({ top: scroller.scrollHeight });
|
|
44
|
-
else onScroll();
|
|
45
|
-
});
|
|
46
|
-
mutation.observe(scroller, {
|
|
47
|
-
childList: true,
|
|
48
|
-
subtree: true
|
|
49
|
-
});
|
|
50
|
-
onCleanup(() => mutation.disconnect());
|
|
24
|
+
const follow = createScrollFollow({
|
|
25
|
+
enabled: () => local.follow !== false,
|
|
26
|
+
resetKey: () => local.resetKey,
|
|
27
|
+
threshold: () => local.threshold ?? 80
|
|
28
|
+
});
|
|
29
|
+
const onScroll = (event) => {
|
|
30
|
+
follow.onScroll();
|
|
31
|
+
const handler = local.onScroll;
|
|
32
|
+
if (typeof handler === "function") handler(event);
|
|
33
|
+
else if (handler) handler[0](handler[1], event);
|
|
51
34
|
};
|
|
52
35
|
return (() => {
|
|
53
|
-
var _el$ = _tmpl$2(), _el$2 = _el$.firstChild;
|
|
36
|
+
var _el$ = _tmpl$2(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild;
|
|
54
37
|
_el$2.addEventListener("scroll", onScroll);
|
|
55
38
|
use((element) => {
|
|
56
|
-
|
|
57
|
-
|
|
39
|
+
follow.bindScroller(element);
|
|
40
|
+
if (typeof local.ref === "function") local.ref(element);
|
|
58
41
|
}, _el$2);
|
|
59
|
-
spread(_el$2,
|
|
60
|
-
|
|
42
|
+
spread(_el$2, mergeProps({ get tabindex() {
|
|
43
|
+
return local.tabindex ?? 0;
|
|
44
|
+
} }, rest), false, true);
|
|
45
|
+
var _ref$ = follow.bindContent;
|
|
46
|
+
typeof _ref$ === "function" ? use(_ref$, _el$3) : follow.bindContent = _el$3;
|
|
47
|
+
insert(_el$3, createComponent(Show, {
|
|
61
48
|
get when() {
|
|
62
49
|
return local.header;
|
|
63
50
|
},
|
|
@@ -65,15 +52,15 @@ function ConversationSurface(props) {
|
|
|
65
52
|
return local.header;
|
|
66
53
|
}
|
|
67
54
|
}), null);
|
|
68
|
-
insert(_el$
|
|
55
|
+
insert(_el$3, createComponent(Show, {
|
|
69
56
|
get when() {
|
|
70
57
|
return local.children;
|
|
71
58
|
},
|
|
72
59
|
get fallback() {
|
|
73
60
|
return (() => {
|
|
74
|
-
var _el$
|
|
75
|
-
insert(_el$
|
|
76
|
-
return _el$
|
|
61
|
+
var _el$5 = _tmpl$3();
|
|
62
|
+
insert(_el$5, () => local.empty);
|
|
63
|
+
return _el$5;
|
|
77
64
|
})();
|
|
78
65
|
},
|
|
79
66
|
get children() {
|
|
@@ -82,30 +69,23 @@ function ConversationSurface(props) {
|
|
|
82
69
|
}), null);
|
|
83
70
|
insert(_el$, createComponent(Show, {
|
|
84
71
|
get when() {
|
|
85
|
-
return !atBottom();
|
|
72
|
+
return !follow.atBottom();
|
|
86
73
|
},
|
|
87
74
|
get children() {
|
|
88
|
-
var _el$
|
|
89
|
-
insert(_el$
|
|
75
|
+
var _el$4 = _tmpl$();
|
|
76
|
+
insert(_el$4, createComponent(Button, {
|
|
77
|
+
type: "button",
|
|
90
78
|
size: "sm",
|
|
91
79
|
variant: "secondary",
|
|
92
80
|
"class": "pointer-events-auto gap-1.5 shadow-md",
|
|
93
|
-
onClick
|
|
81
|
+
get onClick() {
|
|
82
|
+
return follow.jump;
|
|
83
|
+
},
|
|
94
84
|
get children() {
|
|
95
|
-
return [createComponent(ArrowDown, {}),
|
|
96
|
-
get when() {
|
|
97
|
-
return missed() > 0;
|
|
98
|
-
},
|
|
99
|
-
get fallback() {
|
|
100
|
-
return "Jump to latest";
|
|
101
|
-
},
|
|
102
|
-
get children() {
|
|
103
|
-
return [memo(() => missed()), " new"];
|
|
104
|
-
}
|
|
105
|
-
})];
|
|
85
|
+
return [createComponent(ArrowDown, {}), "Jump to latest"];
|
|
106
86
|
}
|
|
107
87
|
}));
|
|
108
|
-
return _el$
|
|
88
|
+
return _el$4;
|
|
109
89
|
}
|
|
110
90
|
}), null);
|
|
111
91
|
effect(() => className(_el$, cn("relative flex min-h-0 flex-1 flex-col", local.class)));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation-surface.js","names":["ArrowDown","ComponentProps","JSX","Show","
|
|
1
|
+
{"version":3,"file":"conversation-surface.js","names":["ArrowDown","ComponentProps","JSX","Show","splitProps","cn","Button","createScrollFollow","ConversationSurfaceProps","threshold","follow","resetKey","empty","Element","header","ConversationSurface","props","local","rest","enabled","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","class","_$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 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 /** 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\nexport function ConversationSurface(props: ConversationSurfaceProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'threshold',\n 'follow',\n 'resetKey',\n 'empty',\n 'header',\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 })\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=\"min-h-0 flex-1 overflow-y-auto overscroll-contain\"\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":";;;;;;;;;;AAoCA,SAAgBe,oBAAoBC,OAAiC;CACnE,MAAM,CAACC,OAAOC,QAAQd,WAAWY,OAAO;EACtC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CAAU,CACX;CAID,MAAMN,SAASH,mBAAmB;EAChCY,eAAeF,MAAMP,WAAW;EAChCC,gBAAgBM,MAAMN;EACtBF,iBAAiBQ,MAAMR,aAAa;CACtC,CAAC;CACD,MAAMW,YAA0DI,UAAU;EACxEd,OAAOU,SAAS;EAChB,MAAMK,UAAUR,MAAMG;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;GAChBvB,OAAOwB,aAAaD,OAAO;GAC3B,IAAI,OAAOhB,MAAMkB,QAAQ,YAAYlB,MAAMkB,IAAIF,OAAO;EACxD,GAACL,KAAA;EAAAQ,OAAAR,OAAAS,WAAA,EAAA,IAEDC,WAAQ;GAAA,OAAErB,MAAMqB,YAAY;EAAC,EAAA,GAEzBpB,IAAI,GAAA,OAAA,IAAA;EAAA,IAAAqB,QAEE7B,OAAO8B;EAAW,OAAAD,UAAA,aAAAP,IAAAO,OAAAT,KAAA,IAAlBpB,OAAO8B,cAAWV;EAAAW,OAAAX,OAAAY,gBACzBvC,MAAI;GAAA,IAACwC,OAAI;IAAA,OAAE1B,MAAMH;GAAM;GAAA,IAAA8B,WAAA;IAAA,OAAG3B,MAAMH;GAAM;EAAA,CAAA,GAAA,IAAA;EAAA2B,OAAAX,OAAAY,gBACtCvC,MAAI;GAAA,IAACwC,OAAI;IAAA,OAAE1B,MAAM2B;GAAQ;GAAA,IAAEC,WAAQ;IAAA,cAAA;KAAA,IAAAC,QAAAC,QAAA;KAAAN,OAAAK,aAAoB7B,MAAML,KAAK;KAAA,OAAAkC;IAAA,EAAA,CAAA;GAAA;GAAA,IAAAF,WAAA;IAAA,OAChE3B,MAAM2B;GAAQ;EAAA,CAAA,GAAA,IAAA;EAAAH,OAAAf,MAAAgB,gBAKpBvC,MAAI;GAAA,IAACwC,OAAI;IAAA,OAAE,CAACjC,OAAOsC,SAAS;GAAC;GAAA,IAAAJ,WAAA;IAAA,IAAAK,QAAAC,OAAA;IAAAT,OAAAQ,OAAAP,gBAEzBpC,QAAM;KACL6C,MAAI;KACJC,MAAI;KACJC,SAAO;KAAA,SAAA;KAAA,IAEPC,UAAO;MAAA,OAAE5C,OAAO6C;KAAI;KAAA,IAAAX,WAAA;MAAA,OAAA,CAAAF,gBAEnB1C,WAAS,CAAA,CAAA,GAAA,gBAAA;KAAA;IAAA,CAAA,CAAA;IAAA,OAAAiD;GAAA;EAAA,CAAA,GAAA,IAAA;EAAAO,aAAAC,UAAA/B,MA7BTrB,GAAG,yCAAyCY,MAAMyC,KAAK,CAAC,CAAA;EAAA,OAAAhC;CAAA,EAAA,CAAA;AAoCrE"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { BusySendButton, type BusySendMode, type BusySendButtonProps } from './busy-send-button';
|
|
1
2
|
export { ConversationAvatar, type ConversationAvatarKind, type ConversationAvatarProps, } from './conversation-avatar';
|
|
2
3
|
export { ConversationSurface, type ConversationSurfaceProps } from './conversation-surface';
|
|
3
4
|
export { ComposerAttachmentButton, ComposerHint, MessageComposer, type MessageComposerProps, } from './message-composer';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,GAC7B,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,mBAAmB,EAAE,KAAK,wBAAwB,EAAE,MAAM,wBAAwB,CAAA;AAC3F,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,cAAc,EACd,WAAW,EACX,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/index.ts"],"names":[],"mappings":"AAAA,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,EAAE,mBAAmB,EAAE,KAAK,wBAAwB,EAAE,MAAM,wBAAwB,CAAA;AAC3F,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,cAAc,EACd,WAAW,EACX,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAA"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { BusySendButton } from "./busy-send-button.js";
|
|
1
2
|
import { ConversationAvatar } from "./conversation-avatar.js";
|
|
2
3
|
import { ConversationSurface } from "./conversation-surface.js";
|
|
3
4
|
import { ComposerAttachmentButton, ComposerHint, MessageComposer } from "./message-composer.js";
|
|
4
5
|
import { MessageBody, MessageDayDivider, MessageGroup, MessageRow } from "./message-row.js";
|
|
5
6
|
import { AttachmentCard, ThreadPanel } from "./thread-panel.js";
|
|
6
|
-
export { AttachmentCard, ComposerAttachmentButton, ComposerHint, ConversationAvatar, ConversationSurface, MessageBody, MessageComposer, MessageDayDivider, MessageGroup, MessageRow, ThreadPanel };
|
|
7
|
+
export { AttachmentCard, BusySendButton, ComposerAttachmentButton, ComposerHint, ConversationAvatar, ConversationSurface, MessageBody, MessageComposer, MessageDayDivider, MessageGroup, MessageRow, ThreadPanel };
|