@hidemikimura/chit-ui 0.1.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/LICENSE +21 -0
- package/README.md +440 -0
- package/dist/chit-ui.iife.min.js +964 -0
- package/dist/chit-ui.iife.min.js.map +1 -0
- package/dist/chit-ui.min.js +964 -0
- package/dist/chit-ui.min.js.map +1 -0
- package/dist/types/bundle.d.ts +7 -0
- package/dist/types/chit-ui.d.ts +251 -0
- package/dist/types/controllers/breakpoint-controller.d.ts +29 -0
- package/dist/types/controllers/composer-controller.d.ts +54 -0
- package/dist/types/controllers/scroll-controller.d.ts +44 -0
- package/dist/types/controllers/state-controller.d.ts +61 -0
- package/dist/types/controllers/theme-controller.d.ts +47 -0
- package/dist/types/element.d.ts +1 -0
- package/dist/types/events.d.ts +28 -0
- package/dist/types/global.d.ts +25 -0
- package/dist/types/i18n/labels.d.ts +68 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/render/composer.d.ts +15 -0
- package/dist/types/render/content.d.ts +75 -0
- package/dist/types/render/launcher.d.ts +18 -0
- package/dist/types/render/message-list.d.ts +17 -0
- package/dist/types/render/message.d.ts +14 -0
- package/dist/types/render/panel.d.ts +10 -0
- package/dist/types/styles/adopted-sheet.d.ts +20 -0
- package/dist/types/styles/composer.css.d.ts +1 -0
- package/dist/types/styles/content.css.d.ts +9 -0
- package/dist/types/styles/host.css.d.ts +9 -0
- package/dist/types/styles/launcher.css.d.ts +1 -0
- package/dist/types/styles/message.css.d.ts +1 -0
- package/dist/types/styles/panel.css.d.ts +1 -0
- package/dist/types/theme/default-theme.d.ts +97 -0
- package/dist/types/theme/merge-theme.d.ts +33 -0
- package/dist/types/theme/theme-to-css.d.ts +24 -0
- package/dist/types/types.d.ts +268 -0
- package/package.json +71 -0
- package/src/bundle.js +11 -0
- package/src/chit-ui.js +548 -0
- package/src/controllers/breakpoint-controller.js +82 -0
- package/src/controllers/composer-controller.js +215 -0
- package/src/controllers/scroll-controller.js +252 -0
- package/src/controllers/state-controller.js +316 -0
- package/src/controllers/theme-controller.js +75 -0
- package/src/element.js +4 -0
- package/src/events.js +33 -0
- package/src/global.d.ts +25 -0
- package/src/i18n/labels.js +72 -0
- package/src/index.js +9 -0
- package/src/render/composer.js +72 -0
- package/src/render/content.js +211 -0
- package/src/render/launcher.js +64 -0
- package/src/render/message-list.js +67 -0
- package/src/render/message.js +82 -0
- package/src/render/panel.js +77 -0
- package/src/styles/adopted-sheet.js +72 -0
- package/src/styles/composer.css.js +108 -0
- package/src/styles/content.css.js +119 -0
- package/src/styles/host.css.js +160 -0
- package/src/styles/launcher.css.js +139 -0
- package/src/styles/message.css.js +192 -0
- package/src/styles/panel.css.js +126 -0
- package/src/theme/default-theme.js +111 -0
- package/src/theme/merge-theme.js +100 -0
- package/src/theme/theme-to-css.js +94 -0
- package/src/types.js +143 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/** @import { Message, Theme, ChatState, Trigger, Effect, Device, ResolvedTheme } from './types.js' */
|
|
2
|
+
/** @import { Labels } from './i18n/labels.js' */
|
|
3
|
+
/**
|
|
4
|
+
* The chat widget.
|
|
5
|
+
*
|
|
6
|
+
* One custom element, one shadow root. Everything inside is drawn by plain
|
|
7
|
+
* functions in `render/` and driven by the reactive controllers in
|
|
8
|
+
* `controllers/`; no nested custom elements, so `::part()` and the theme's
|
|
9
|
+
* custom properties reach every corner without being forwarded.
|
|
10
|
+
*
|
|
11
|
+
* @element chit-ui
|
|
12
|
+
*
|
|
13
|
+
* @slot launcher - Replaces the whole closed-state button content.
|
|
14
|
+
* @slot header - Replaces the panel header.
|
|
15
|
+
* @slot header-title - Replaces only the title area of the header.
|
|
16
|
+
* @slot header-actions - Extra buttons beside the close button.
|
|
17
|
+
* @slot footer - Below the composer (disclaimers, attribution).
|
|
18
|
+
*/
|
|
19
|
+
export class ChitUI extends LitElement {
|
|
20
|
+
/** @override */
|
|
21
|
+
static override styles: import("lit").CSSResult[];
|
|
22
|
+
/** @override */
|
|
23
|
+
static override properties: {
|
|
24
|
+
state: {
|
|
25
|
+
type: StringConstructor;
|
|
26
|
+
reflect: boolean;
|
|
27
|
+
noAccessor: boolean;
|
|
28
|
+
};
|
|
29
|
+
theme: {
|
|
30
|
+
type: ObjectConstructor;
|
|
31
|
+
};
|
|
32
|
+
messages: {
|
|
33
|
+
type: ArrayConstructor;
|
|
34
|
+
};
|
|
35
|
+
typing: {
|
|
36
|
+
type: ObjectConstructor;
|
|
37
|
+
};
|
|
38
|
+
busy: {
|
|
39
|
+
type: BooleanConstructor;
|
|
40
|
+
reflect: boolean;
|
|
41
|
+
};
|
|
42
|
+
inputDisabled: {
|
|
43
|
+
type: BooleanConstructor;
|
|
44
|
+
reflect: boolean;
|
|
45
|
+
attribute: string;
|
|
46
|
+
};
|
|
47
|
+
inputHidden: {
|
|
48
|
+
type: BooleanConstructor;
|
|
49
|
+
reflect: boolean;
|
|
50
|
+
attribute: string;
|
|
51
|
+
};
|
|
52
|
+
placeholder: {
|
|
53
|
+
type: StringConstructor;
|
|
54
|
+
reflect: boolean;
|
|
55
|
+
};
|
|
56
|
+
sendOnEnter: {
|
|
57
|
+
type: BooleanConstructor;
|
|
58
|
+
reflect: boolean;
|
|
59
|
+
attribute: string;
|
|
60
|
+
};
|
|
61
|
+
maxLength: {
|
|
62
|
+
type: NumberConstructor;
|
|
63
|
+
reflect: boolean;
|
|
64
|
+
attribute: string;
|
|
65
|
+
};
|
|
66
|
+
focusOnOpen: {
|
|
67
|
+
type: StringConstructor;
|
|
68
|
+
reflect: boolean;
|
|
69
|
+
attribute: string;
|
|
70
|
+
};
|
|
71
|
+
locale: {
|
|
72
|
+
type: StringConstructor;
|
|
73
|
+
reflect: boolean;
|
|
74
|
+
};
|
|
75
|
+
labels: {
|
|
76
|
+
type: ObjectConstructor;
|
|
77
|
+
};
|
|
78
|
+
messageStyles: {
|
|
79
|
+
type: StringConstructor;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
/** @type {Theme} Partial; unset keys fall back to the default theme. */
|
|
83
|
+
theme: Theme;
|
|
84
|
+
/** @type {Message[]} Rendered as given. The library never mutates this. */
|
|
85
|
+
messages: Message[];
|
|
86
|
+
/** @type {boolean | { html: string }} Show the "typing" bubble. */
|
|
87
|
+
typing: boolean | {
|
|
88
|
+
html: string;
|
|
89
|
+
};
|
|
90
|
+
/** @type {boolean} Lock the composer while a reply is in flight. */
|
|
91
|
+
busy: boolean;
|
|
92
|
+
/** @type {boolean} */
|
|
93
|
+
inputDisabled: boolean;
|
|
94
|
+
/** @type {boolean} */
|
|
95
|
+
inputHidden: boolean;
|
|
96
|
+
/** @type {string | undefined} */
|
|
97
|
+
placeholder: string | undefined;
|
|
98
|
+
/** @type {boolean} */
|
|
99
|
+
sendOnEnter: boolean;
|
|
100
|
+
/** @type {number | undefined} */
|
|
101
|
+
maxLength: number | undefined;
|
|
102
|
+
/**
|
|
103
|
+
* Focus the composer once the open transition finishes. 'auto' focuses on
|
|
104
|
+
* PC only, so a phone keyboard never pops up unasked.
|
|
105
|
+
*
|
|
106
|
+
* Named focusOnOpen rather than autofocus: HTMLElement.autofocus is a
|
|
107
|
+
* standard boolean property and cannot carry a third value.
|
|
108
|
+
*
|
|
109
|
+
* @type {'auto' | 'always' | 'never'}
|
|
110
|
+
*/
|
|
111
|
+
focusOnOpen: "auto" | "always" | "never";
|
|
112
|
+
/** @type {string | undefined} */
|
|
113
|
+
locale: string | undefined;
|
|
114
|
+
/** @type {Partial<Labels> | undefined} Overrides for the built-in UI strings. */
|
|
115
|
+
labels: Partial<Labels> | undefined;
|
|
116
|
+
/** @type {string} Extra CSS applied inside message content. */
|
|
117
|
+
messageStyles: string;
|
|
118
|
+
/** @type {((html: string) => string) | undefined} Applied to `html` messages only. */
|
|
119
|
+
sanitize: ((html: string) => string) | undefined;
|
|
120
|
+
/** @type {((time: Date) => string) | undefined} */
|
|
121
|
+
formatTime: ((time: Date) => string) | undefined;
|
|
122
|
+
set state(value: ChatState);
|
|
123
|
+
/**
|
|
124
|
+
* Current state. Assigning it starts the transition, exactly as calling the
|
|
125
|
+
* matching method would.
|
|
126
|
+
*
|
|
127
|
+
* @type {ChatState}
|
|
128
|
+
*/
|
|
129
|
+
get state(): ChatState;
|
|
130
|
+
/** @returns {Device} Which theme bucket the viewport currently falls in. */
|
|
131
|
+
get device(): Device;
|
|
132
|
+
/**
|
|
133
|
+
* The theme in force for this device, every gap filled in.
|
|
134
|
+
*
|
|
135
|
+
* @returns {ResolvedTheme}
|
|
136
|
+
*/
|
|
137
|
+
get currentTheme(): ResolvedTheme;
|
|
138
|
+
/** @returns {Labels} */
|
|
139
|
+
get currentLabels(): Labels;
|
|
140
|
+
/**
|
|
141
|
+
* The language in force: the `locale` property, else the document's.
|
|
142
|
+
* Labels and timestamps both read it, so a page in Japanese does not end up
|
|
143
|
+
* with Japanese buttons and American clock times.
|
|
144
|
+
*
|
|
145
|
+
* @returns {string | undefined}
|
|
146
|
+
*/
|
|
147
|
+
get resolvedLocale(): string | undefined;
|
|
148
|
+
/** @returns {ChatState} What the DOM is showing, which lags `state` while animating. */
|
|
149
|
+
get renderedState(): ChatState;
|
|
150
|
+
/** @returns {boolean} True when messages arrived while the reader was scrolled up. */
|
|
151
|
+
get hasUnseen(): boolean;
|
|
152
|
+
set value(next: string);
|
|
153
|
+
/**
|
|
154
|
+
* What is currently typed in the composer.
|
|
155
|
+
*
|
|
156
|
+
* Not a reactive property on purpose: a keystroke would otherwise re-render
|
|
157
|
+
* the whole shadow tree, every message included.
|
|
158
|
+
*
|
|
159
|
+
* @type {string}
|
|
160
|
+
*/
|
|
161
|
+
get value(): string;
|
|
162
|
+
/** @returns {boolean} Whether what is typed could be sent right now. */
|
|
163
|
+
get canSend(): boolean;
|
|
164
|
+
/** @returns {Promise<boolean>} Resolves once the open animation has finished. */
|
|
165
|
+
open(): Promise<boolean>;
|
|
166
|
+
/** @returns {Promise<boolean>} */
|
|
167
|
+
close(): Promise<boolean>;
|
|
168
|
+
/** @returns {Promise<boolean>} */
|
|
169
|
+
hide(): Promise<boolean>;
|
|
170
|
+
/** @returns {Promise<boolean>} From `hidden` back to the launcher. A no-op otherwise. */
|
|
171
|
+
show(): Promise<boolean>;
|
|
172
|
+
/** @returns {Promise<boolean>} */
|
|
173
|
+
toggle(): Promise<boolean>;
|
|
174
|
+
/**
|
|
175
|
+
* Send what is in the composer, or the given text, as `chat-submit`.
|
|
176
|
+
*
|
|
177
|
+
* Adding the message to `messages` stays the consumer's job; the widget only
|
|
178
|
+
* reports that someone pressed send.
|
|
179
|
+
*
|
|
180
|
+
* @param {string} [text]
|
|
181
|
+
* @returns {boolean} False when there was nothing to send.
|
|
182
|
+
*/
|
|
183
|
+
submit(text?: string): boolean;
|
|
184
|
+
/** Empty the composer and put the caret back in it. */
|
|
185
|
+
clearInput(): void;
|
|
186
|
+
/**
|
|
187
|
+
* Scroll the conversation to the newest message.
|
|
188
|
+
*
|
|
189
|
+
* @param {{ smooth?: boolean }} [options]
|
|
190
|
+
*/
|
|
191
|
+
scrollToBottom(options?: {
|
|
192
|
+
smooth?: boolean;
|
|
193
|
+
}): void;
|
|
194
|
+
/**
|
|
195
|
+
* The content container of one message, once it has been drawn.
|
|
196
|
+
*
|
|
197
|
+
* @param {string} id
|
|
198
|
+
* @returns {HTMLElement | null}
|
|
199
|
+
*/
|
|
200
|
+
getMessageElement(id: string): HTMLElement | null;
|
|
201
|
+
/**
|
|
202
|
+
* Put the caret in the composer.
|
|
203
|
+
*
|
|
204
|
+
* This obeys the caller, not `focusOnOpen`: someone who calls it has asked
|
|
205
|
+
* for focus. `focusOnOpen` only governs what happens by itself on open.
|
|
206
|
+
*
|
|
207
|
+
* @returns {boolean} False when there is no composer to focus.
|
|
208
|
+
*/
|
|
209
|
+
focusInput(): boolean;
|
|
210
|
+
/** @param {Event} event */
|
|
211
|
+
handleInput(event: Event): void;
|
|
212
|
+
/** @param {KeyboardEvent} event */
|
|
213
|
+
handleKeydown(event: KeyboardEvent): void;
|
|
214
|
+
/** @param {boolean} started */
|
|
215
|
+
handleComposition(started: boolean): void;
|
|
216
|
+
/**
|
|
217
|
+
* Render a consumer-supplied typing bubble.
|
|
218
|
+
*
|
|
219
|
+
* @param {string} markup
|
|
220
|
+
* @returns {unknown}
|
|
221
|
+
*/
|
|
222
|
+
renderTypingHtml(markup: string): unknown;
|
|
223
|
+
/**
|
|
224
|
+
* Re-publish a click inside a message as `chat-message-click`.
|
|
225
|
+
*
|
|
226
|
+
* `composedPath()[0]` rather than `event.target`: the target is retargeted to
|
|
227
|
+
* the message container once the event leaves a component's shadow root, and
|
|
228
|
+
* the consumer wants the element actually clicked.
|
|
229
|
+
*
|
|
230
|
+
* @param {MouseEvent} event
|
|
231
|
+
*/
|
|
232
|
+
handleMessageClick(event: MouseEvent): void;
|
|
233
|
+
/** Called by the launcher; the resulting events report `trigger: 'user'`. */
|
|
234
|
+
toggleFromUser(): Promise<boolean>;
|
|
235
|
+
/** Called by the close button and by Esc. */
|
|
236
|
+
closeFromUser(): Promise<boolean>;
|
|
237
|
+
/** @override */
|
|
238
|
+
override willUpdate(): void;
|
|
239
|
+
/** @override */
|
|
240
|
+
override updated(): void;
|
|
241
|
+
/** @override */
|
|
242
|
+
override render(): import("lit-html").TemplateResult<1>;
|
|
243
|
+
#private;
|
|
244
|
+
}
|
|
245
|
+
import { LitElement } from 'lit';
|
|
246
|
+
import type { Theme } from './types.js';
|
|
247
|
+
import type { Message } from './types.js';
|
|
248
|
+
import type { Labels } from './i18n/labels.js';
|
|
249
|
+
import type { ChatState } from './types.js';
|
|
250
|
+
import type { Device } from './types.js';
|
|
251
|
+
import type { ResolvedTheme } from './types.js';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** @import { ReactiveController, LitElement } from 'lit' */
|
|
2
|
+
/** @import { Device } from '../types.js' */
|
|
3
|
+
/**
|
|
4
|
+
* Tracks whether the viewport is narrow enough to count as a phone.
|
|
5
|
+
*
|
|
6
|
+
* Uses matchMedia rather than a resize listener: it only fires when the
|
|
7
|
+
* boundary is actually crossed, so nothing runs while the user drags a window
|
|
8
|
+
* around inside one bucket.
|
|
9
|
+
*
|
|
10
|
+
* @implements {ReactiveController}
|
|
11
|
+
*/
|
|
12
|
+
export class BreakpointController implements ReactiveController {
|
|
13
|
+
/** @param {LitElement} host */
|
|
14
|
+
constructor(host: LitElement);
|
|
15
|
+
/** @returns {Device} */
|
|
16
|
+
get device(): Device;
|
|
17
|
+
hostDisconnected(): void;
|
|
18
|
+
/**
|
|
19
|
+
* Point the controller at a breakpoint. Safe to call on every update; the
|
|
20
|
+
* media query is only rebuilt when the value actually changes.
|
|
21
|
+
*
|
|
22
|
+
* @param {number} breakpoint
|
|
23
|
+
*/
|
|
24
|
+
observe(breakpoint: number): void;
|
|
25
|
+
#private;
|
|
26
|
+
}
|
|
27
|
+
import type { ReactiveController } from 'lit';
|
|
28
|
+
import type { Device } from '../types.js';
|
|
29
|
+
import type { LitElement } from 'lit';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owns the composer: what has been typed, whether an IME is mid-composition,
|
|
3
|
+
* and when a keystroke counts as "send".
|
|
4
|
+
*
|
|
5
|
+
* The typed text is deliberately not a reactive property. A keystroke would
|
|
6
|
+
* otherwise re-render the whole shadow tree, `repeat` over every message
|
|
7
|
+
* included; instead the two things that depend on it — the send button's
|
|
8
|
+
* enabled state and the character counter — are updated in place.
|
|
9
|
+
*
|
|
10
|
+
* @implements {ReactiveController}
|
|
11
|
+
*/
|
|
12
|
+
export class ComposerController implements ReactiveController {
|
|
13
|
+
/** @param {LitElement & ComposerHost} host */
|
|
14
|
+
constructor(host: LitElement & ComposerHost);
|
|
15
|
+
set value(next: string);
|
|
16
|
+
/** @returns {string} */
|
|
17
|
+
get value(): string;
|
|
18
|
+
/** @returns {boolean} True while an IME candidate window is open. */
|
|
19
|
+
get composing(): boolean;
|
|
20
|
+
/** @returns {number} Characters typed, counted by code point. */
|
|
21
|
+
get length(): number;
|
|
22
|
+
/** @returns {boolean} Whether the current text could be sent right now. */
|
|
23
|
+
get canSend(): boolean;
|
|
24
|
+
hostDisconnected(): void;
|
|
25
|
+
hostUpdated(): void;
|
|
26
|
+
/** @param {Event} event */
|
|
27
|
+
onInput(event: Event): void;
|
|
28
|
+
onCompositionStart(): void;
|
|
29
|
+
onCompositionEnd(): void;
|
|
30
|
+
/** @param {KeyboardEvent} event */
|
|
31
|
+
onKeydown(event: KeyboardEvent): void;
|
|
32
|
+
/**
|
|
33
|
+
* Hand the text to the consumer and clear the box.
|
|
34
|
+
*
|
|
35
|
+
* The text is passed as typed: leading and trailing newlines are theirs to
|
|
36
|
+
* keep or strip, since only they know whether the message is prose or code.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} [text] Defaults to what is in the box.
|
|
39
|
+
* @returns {boolean} False when there was nothing to send.
|
|
40
|
+
*/
|
|
41
|
+
submit(text?: string): boolean;
|
|
42
|
+
clear(): void;
|
|
43
|
+
focus(): void;
|
|
44
|
+
#private;
|
|
45
|
+
}
|
|
46
|
+
export type ComposerHost = {
|
|
47
|
+
busy: boolean;
|
|
48
|
+
inputDisabled: boolean;
|
|
49
|
+
sendOnEnter: boolean;
|
|
50
|
+
maxLength: number | undefined;
|
|
51
|
+
currentLabels: import("../i18n/labels.js").Labels;
|
|
52
|
+
};
|
|
53
|
+
import type { ReactiveController } from 'lit';
|
|
54
|
+
import type { LitElement } from 'lit';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keeps the conversation pinned to the newest message while the reader is at
|
|
3
|
+
* the bottom, and gets out of the way the moment they scroll up.
|
|
4
|
+
*
|
|
5
|
+
* Two things follow from that rule. A reply arriving while the reader is up in
|
|
6
|
+
* the history must not yank them down — it raises the "jump to latest" button
|
|
7
|
+
* instead. And their own message always scrolls into view, because they just
|
|
8
|
+
* pressed send and expect to see it.
|
|
9
|
+
*
|
|
10
|
+
* @implements {ReactiveController}
|
|
11
|
+
*/
|
|
12
|
+
export class ScrollController implements ReactiveController {
|
|
13
|
+
/**
|
|
14
|
+
* @param {LitElement & { messages: Message[] }} host
|
|
15
|
+
* @param {{ behavior: () => 'smooth' | 'instant' }} options
|
|
16
|
+
*/
|
|
17
|
+
constructor(host: LitElement & {
|
|
18
|
+
messages: Message[];
|
|
19
|
+
}, { behavior }: {
|
|
20
|
+
behavior: () => "smooth" | "instant";
|
|
21
|
+
});
|
|
22
|
+
/** @returns {boolean} True when newer content arrived while scrolled up. */
|
|
23
|
+
get hasUnseen(): boolean;
|
|
24
|
+
/** @returns {boolean} */
|
|
25
|
+
get atBottom(): boolean;
|
|
26
|
+
hostDisconnected(): void;
|
|
27
|
+
/**
|
|
28
|
+
* Runs before the render, so a decision made here lands in the same frame.
|
|
29
|
+
* Whether the conversation grew is knowable from the array alone; only the
|
|
30
|
+
* scrolling itself has to wait for the DOM.
|
|
31
|
+
*/
|
|
32
|
+
hostUpdate(): void;
|
|
33
|
+
hostUpdated(): void;
|
|
34
|
+
/**
|
|
35
|
+
* @param {{ smooth?: boolean }} [options] Defaults to the theme's setting.
|
|
36
|
+
*/
|
|
37
|
+
scrollToBottom({ smooth }?: {
|
|
38
|
+
smooth?: boolean;
|
|
39
|
+
}): void;
|
|
40
|
+
#private;
|
|
41
|
+
}
|
|
42
|
+
import type { ReactiveController } from 'lit';
|
|
43
|
+
import type { LitElement } from 'lit';
|
|
44
|
+
import type { Message } from '../types.js';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owns everything about moving between closed / open / hidden.
|
|
3
|
+
*
|
|
4
|
+
* The host's `state` property changes the instant it is assigned; what the DOM
|
|
5
|
+
* shows lags behind it for as long as the transition animation runs. That
|
|
6
|
+
* lagging value is `renderedState`, and the render functions key off it — so a
|
|
7
|
+
* panel animating out is still in the DOM while `state` already reads 'closed'.
|
|
8
|
+
*
|
|
9
|
+
* @implements {ReactiveController}
|
|
10
|
+
*/
|
|
11
|
+
export class StateController implements ReactiveController {
|
|
12
|
+
/**
|
|
13
|
+
* @param {LitElement & { state: ChatState }} host
|
|
14
|
+
* @param {StateControllerOptions} options
|
|
15
|
+
*/
|
|
16
|
+
constructor(host: LitElement & {
|
|
17
|
+
state: ChatState;
|
|
18
|
+
}, { animation, keepsLauncher, onOpened }: StateControllerOptions);
|
|
19
|
+
/** @returns {ChatState} What the DOM currently shows. */
|
|
20
|
+
get renderedState(): ChatState;
|
|
21
|
+
/** @returns {boolean} True while the host's own setter must not call us back. */
|
|
22
|
+
get applying(): boolean;
|
|
23
|
+
hostConnected(): void;
|
|
24
|
+
hostDisconnected(): void;
|
|
25
|
+
hostUpdated(): void;
|
|
26
|
+
/**
|
|
27
|
+
* Move to `to`, running the before-event, the animations and the completion
|
|
28
|
+
* events in order.
|
|
29
|
+
*
|
|
30
|
+
* @param {ChatState} to
|
|
31
|
+
* @param {Trigger} trigger
|
|
32
|
+
* @returns {Promise<boolean>} false when a listener cancelled it, or when a
|
|
33
|
+
* later transition superseded this one.
|
|
34
|
+
*/
|
|
35
|
+
request(to: ChatState, trigger: Trigger): Promise<boolean>;
|
|
36
|
+
#private;
|
|
37
|
+
}
|
|
38
|
+
export type AnimationSpec = {
|
|
39
|
+
effect: Effect;
|
|
40
|
+
duration: number;
|
|
41
|
+
};
|
|
42
|
+
export type AnimationResolver = (from: ChatState, to: ChatState, phase: "enter" | "exit") => AnimationSpec;
|
|
43
|
+
export type StateControllerOptions = {
|
|
44
|
+
/**
|
|
45
|
+
* Which effect and duration to play.
|
|
46
|
+
*/
|
|
47
|
+
animation: AnimationResolver;
|
|
48
|
+
/**
|
|
49
|
+
* True when the launcher stays visible behind an open panel.
|
|
50
|
+
*/
|
|
51
|
+
keepsLauncher: () => boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Called once the panel is open and settled.
|
|
54
|
+
*/
|
|
55
|
+
onOpened: () => void;
|
|
56
|
+
};
|
|
57
|
+
import type { ReactiveController } from 'lit';
|
|
58
|
+
import type { ChatState } from '../types.js';
|
|
59
|
+
import type { Trigger } from '../types.js';
|
|
60
|
+
import type { LitElement } from 'lit';
|
|
61
|
+
import type { Effect } from '../types.js';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** @import { ReactiveController, LitElement } from 'lit' */
|
|
2
|
+
/** @import { Theme, ResolvedTheme, Device } from '../types.js' */
|
|
3
|
+
/**
|
|
4
|
+
* Turns the `theme` property into the custom properties everything else reads.
|
|
5
|
+
*
|
|
6
|
+
* The generated sheet is adopted *after* the component's own stylesheets, so a
|
|
7
|
+
* theme value beats the fallback declared alongside the rules that use it.
|
|
8
|
+
* Page CSS still wins over both: a `chit-ui { --chit-color-accent: ... }` rule
|
|
9
|
+
* lives in the outer tree, and the outer tree takes precedence over `:host`.
|
|
10
|
+
*
|
|
11
|
+
* @implements {ReactiveController}
|
|
12
|
+
*/
|
|
13
|
+
export class ThemeController implements ReactiveController {
|
|
14
|
+
/**
|
|
15
|
+
* The breakpoint a theme asks for. Needed before `apply`, because the device
|
|
16
|
+
* cannot be decided until the breakpoint is known.
|
|
17
|
+
*
|
|
18
|
+
* @param {Theme | undefined} theme
|
|
19
|
+
* @returns {number}
|
|
20
|
+
*/
|
|
21
|
+
static breakpointOf(theme: Theme | undefined): number;
|
|
22
|
+
/**
|
|
23
|
+
* @param {LitElement} host
|
|
24
|
+
* @param {{ device: () => Device }} options
|
|
25
|
+
*/
|
|
26
|
+
constructor(host: LitElement, { device }: {
|
|
27
|
+
device: () => Device;
|
|
28
|
+
});
|
|
29
|
+
/** @returns {ResolvedTheme} The theme in force, defaults filled in. */
|
|
30
|
+
get current(): ResolvedTheme;
|
|
31
|
+
hostConnected(): void;
|
|
32
|
+
hostDisconnected(): void;
|
|
33
|
+
/**
|
|
34
|
+
* Re-resolve for the current theme and device. Cheap to call on every update:
|
|
35
|
+
* the stylesheet is only rewritten when the generated text actually changes.
|
|
36
|
+
*
|
|
37
|
+
* @param {Theme | undefined} theme
|
|
38
|
+
* @param {Device} device
|
|
39
|
+
*/
|
|
40
|
+
apply(theme: Theme | undefined, device: Device): void;
|
|
41
|
+
#private;
|
|
42
|
+
}
|
|
43
|
+
import type { ReactiveController } from 'lit';
|
|
44
|
+
import type { ResolvedTheme } from '../types.js';
|
|
45
|
+
import type { Theme } from '../types.js';
|
|
46
|
+
import type { Device } from '../types.js';
|
|
47
|
+
import type { LitElement } from 'lit';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ChitUI } from "./chit-ui.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dispatch a composed, bubbling CustomEvent from the host.
|
|
3
|
+
*
|
|
4
|
+
* @param {HTMLElement} host
|
|
5
|
+
* @param {string} name
|
|
6
|
+
* @param {unknown} detail
|
|
7
|
+
* @param {{ cancelable?: boolean }} [options]
|
|
8
|
+
* @returns {boolean} false when a listener called preventDefault().
|
|
9
|
+
*/
|
|
10
|
+
export function emit(host: HTMLElement, name: string, detail: unknown, { cancelable }?: {
|
|
11
|
+
cancelable?: boolean;
|
|
12
|
+
}): boolean;
|
|
13
|
+
/** Every event this library dispatches. Never write the string literals elsewhere. */
|
|
14
|
+
export const Events: Readonly<{
|
|
15
|
+
SUBMIT: "chat-submit";
|
|
16
|
+
BEFORE_OPEN: "chat-before-open";
|
|
17
|
+
OPEN: "chat-open";
|
|
18
|
+
BEFORE_CLOSE: "chat-before-close";
|
|
19
|
+
CLOSE: "chat-close";
|
|
20
|
+
HIDE: "chat-hide";
|
|
21
|
+
SHOW: "chat-show";
|
|
22
|
+
STATE_CHANGE: "chat-state-change";
|
|
23
|
+
INPUT: "chat-input";
|
|
24
|
+
MESSAGE_RENDER: "chat-message-render";
|
|
25
|
+
MESSAGE_CLICK: "chat-message-click";
|
|
26
|
+
SCROLL_TOP: "chat-scroll-top";
|
|
27
|
+
BREAKPOINT_CHANGE: "chat-breakpoint-change";
|
|
28
|
+
}>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ChitUI } from './chit-ui.js';
|
|
2
|
+
import type { Message, ChatState, Trigger, Device } from './types.js';
|
|
3
|
+
|
|
4
|
+
export interface ChitUIEventMap {
|
|
5
|
+
'chat-submit': CustomEvent<{ text: string }>;
|
|
6
|
+
'chat-before-open': CustomEvent<{ from: ChatState; trigger: Trigger }>;
|
|
7
|
+
'chat-open': CustomEvent<{ from: ChatState; trigger: Trigger }>;
|
|
8
|
+
'chat-before-close': CustomEvent<{ from: ChatState; trigger: Trigger }>;
|
|
9
|
+
'chat-close': CustomEvent<{ from: ChatState; trigger: Trigger }>;
|
|
10
|
+
'chat-hide': CustomEvent<{ from: ChatState; to: 'hidden' }>;
|
|
11
|
+
'chat-show': CustomEvent<{ from: 'hidden'; to: ChatState }>;
|
|
12
|
+
'chat-state-change': CustomEvent<{ from: ChatState; to: ChatState; trigger: Trigger }>;
|
|
13
|
+
'chat-input': CustomEvent<{ value: string }>;
|
|
14
|
+
'chat-message-render': CustomEvent<{ message: Message; element: HTMLElement; instance?: HTMLElement }>;
|
|
15
|
+
'chat-message-click': CustomEvent<{ message: Message; target: Element; originalEvent: MouseEvent }>;
|
|
16
|
+
'chat-scroll-top': CustomEvent<Record<string, never>>;
|
|
17
|
+
'chat-breakpoint-change': CustomEvent<{ device: Device }>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
declare global {
|
|
21
|
+
interface HTMLElementTagNameMap {
|
|
22
|
+
'chit-ui': ChitUI;
|
|
23
|
+
}
|
|
24
|
+
interface HTMLElementEventMap extends ChitUIEventMap {}
|
|
25
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The language the widget speaks: the `locale` property, else the document's,
|
|
3
|
+
* else the browser's. Everything locale-dependent — labels and timestamps
|
|
4
|
+
* alike — goes through this, so they cannot disagree.
|
|
5
|
+
*
|
|
6
|
+
* @param {string | undefined} locale
|
|
7
|
+
* @returns {string | undefined} undefined means "whatever the browser uses".
|
|
8
|
+
*/
|
|
9
|
+
export function resolveLocale(locale: string | undefined): string | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Pick the label set for a locale, falling back to English for anything we do
|
|
12
|
+
* not ship. `overrides` is the host's `labels` property.
|
|
13
|
+
*
|
|
14
|
+
* @param {string | undefined} locale
|
|
15
|
+
* @param {Partial<Labels> | undefined} [overrides]
|
|
16
|
+
* @returns {Labels}
|
|
17
|
+
*/
|
|
18
|
+
export function resolveLabels(locale: string | undefined, overrides?: Partial<Labels> | undefined): Labels;
|
|
19
|
+
export type Labels = {
|
|
20
|
+
/**
|
|
21
|
+
* Accessible name of the closed-state button.
|
|
22
|
+
*/
|
|
23
|
+
launcher: string;
|
|
24
|
+
/**
|
|
25
|
+
* Accessible name of the dialog.
|
|
26
|
+
*/
|
|
27
|
+
panel: string;
|
|
28
|
+
/**
|
|
29
|
+
* Accessible name of the message log.
|
|
30
|
+
*/
|
|
31
|
+
conversation: string;
|
|
32
|
+
/**
|
|
33
|
+
* Close button.
|
|
34
|
+
*/
|
|
35
|
+
close: string;
|
|
36
|
+
/**
|
|
37
|
+
* Send button.
|
|
38
|
+
*/
|
|
39
|
+
send: string;
|
|
40
|
+
/**
|
|
41
|
+
* Composer textarea.
|
|
42
|
+
*/
|
|
43
|
+
input: string;
|
|
44
|
+
/**
|
|
45
|
+
* Announced while the other side is typing.
|
|
46
|
+
*/
|
|
47
|
+
typing: string;
|
|
48
|
+
/**
|
|
49
|
+
* "Jump to newest" button.
|
|
50
|
+
*/
|
|
51
|
+
toLatest: string;
|
|
52
|
+
/**
|
|
53
|
+
* Delivery state of one's own message.
|
|
54
|
+
*/
|
|
55
|
+
status: {
|
|
56
|
+
sending: string;
|
|
57
|
+
sent: string;
|
|
58
|
+
error: string;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Counter, with {n} for the number remaining.
|
|
62
|
+
*/
|
|
63
|
+
charactersLeft: string;
|
|
64
|
+
/**
|
|
65
|
+
* Counter past the limit, with {n} for the excess.
|
|
66
|
+
*/
|
|
67
|
+
overLimit: string;
|
|
68
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** @import { ChitUI } from '../chit-ui.js' */
|
|
2
|
+
/**
|
|
3
|
+
* The composer: a textarea that grows with its content, and a send button.
|
|
4
|
+
*
|
|
5
|
+
* The whole thing can be replaced through the `composer` slot; a consumer who
|
|
6
|
+
* does that calls `host.submit(text)` themselves. `input-before` and
|
|
7
|
+
* `input-after` are the lighter option, for an attach or emoji button beside
|
|
8
|
+
* the box the library still owns.
|
|
9
|
+
*
|
|
10
|
+
* @param {ChitUI} host
|
|
11
|
+
* @returns {import('lit').TemplateResult | typeof nothing}
|
|
12
|
+
*/
|
|
13
|
+
export function renderComposer(host: ChitUI): import("lit").TemplateResult | typeof nothing;
|
|
14
|
+
import type { ChitUI } from '../chit-ui.js';
|
|
15
|
+
import { nothing } from 'lit';
|