@hidemikimura/chit-ui 0.2.0 → 0.3.1

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +148 -3
  3. package/dist/chit-ui.iife.min.js +218 -99
  4. package/dist/chit-ui.iife.min.js.map +1 -1
  5. package/dist/chit-ui.min.js +209 -90
  6. package/dist/chit-ui.min.js.map +1 -1
  7. package/dist/types/chit-ui.d.ts +64 -4
  8. package/dist/types/controllers/composer-controller.d.ts +4 -0
  9. package/dist/types/controllers/drag-controller.d.ts +118 -0
  10. package/dist/types/controllers/scroll-lock-controller.d.ts +37 -0
  11. package/dist/types/controllers/viewport-controller.d.ts +38 -0
  12. package/dist/types/events.d.ts +1 -0
  13. package/dist/types/global.d.ts +7 -1
  14. package/dist/types/i18n/labels.d.ts +8 -0
  15. package/dist/types/index.d.ts +162 -0
  16. package/dist/types/render/message-list.d.ts +0 -1
  17. package/dist/types/theme/default-theme.d.ts +8 -0
  18. package/dist/types/types.d.ts +40 -0
  19. package/package.json +1 -1
  20. package/src/chit-ui.js +261 -4
  21. package/src/controllers/composer-controller.js +2 -0
  22. package/src/controllers/drag-controller.js +319 -0
  23. package/src/controllers/scroll-controller.js +21 -1
  24. package/src/controllers/scroll-lock-controller.js +185 -0
  25. package/src/controllers/viewport-controller.js +102 -0
  26. package/src/events.js +1 -0
  27. package/src/global.d.ts +7 -1
  28. package/src/i18n/labels.js +6 -0
  29. package/src/index.js +34 -0
  30. package/src/render/launcher.js +9 -1
  31. package/src/render/message-list.js +40 -10
  32. package/src/render/panel.js +17 -1
  33. package/src/styles/composer.css.js +18 -0
  34. package/src/styles/launcher.css.js +13 -0
  35. package/src/styles/message.css.js +60 -15
  36. package/src/styles/panel.css.js +30 -3
  37. package/src/theme/default-theme.js +16 -0
  38. package/src/types.js +18 -0
@@ -34,6 +34,12 @@ export class ChitUI extends LitElement {
34
34
  };
35
35
  typing: {
36
36
  type: ObjectConstructor;
37
+ noAccessor: boolean;
38
+ };
39
+ loading: {
40
+ type: BooleanConstructor;
41
+ reflect: boolean;
42
+ noAccessor: boolean;
37
43
  };
38
44
  busy: {
39
45
  type: BooleanConstructor;
@@ -83,10 +89,6 @@ export class ChitUI extends LitElement {
83
89
  theme: Theme;
84
90
  /** @type {Message[]} Rendered as given. The library never mutates this. */
85
91
  messages: Message[];
86
- /** @type {boolean | { html: string }} Show the "typing" bubble. */
87
- typing: boolean | {
88
- html: string;
89
- };
90
92
  /** @type {boolean} Lock the composer while a reply is in flight. */
91
93
  busy: boolean;
92
94
  /** @type {boolean} */
@@ -119,6 +121,63 @@ export class ChitUI extends LitElement {
119
121
  sanitize: ((html: string) => string) | undefined;
120
122
  /** @type {((time: Date) => string) | undefined} */
121
123
  formatTime: ((time: Date) => string) | undefined;
124
+ /** The launcher's drag, for the render function to hook up. */
125
+ get launcherDrag(): DragController;
126
+ /** The panel's drag, likewise. */
127
+ get panelDrag(): DragController;
128
+ set dragOffset(value: {
129
+ x: number;
130
+ y: number;
131
+ } | null);
132
+ /**
133
+ * How far the reader has dragged the widget from where the theme put it, in
134
+ * screen pixels, or null when it is still there.
135
+ *
136
+ * There is one of these for the whole widget rather than one per state: the
137
+ * launcher and the panel are the same widget wearing two shapes, so moving
138
+ * either moves both, and the panel opens where the launcher was left.
139
+ *
140
+ * @type {{ x: number, y: number } | null}
141
+ */
142
+ get dragOffset(): {
143
+ x: number;
144
+ y: number;
145
+ } | null;
146
+ /** Forget the dragged position and go back to what the theme says. */
147
+ resetPosition(): void;
148
+ /**
149
+ * The composer calls this once a submit has gone out and no listener
150
+ * cancelled it. Listening for `chat-submit` here instead would be wrong:
151
+ * this element's own listener runs before the consumer's, so it cannot see
152
+ * a `preventDefault()` that is still to come.
153
+ *
154
+ * @returns {void}
155
+ */
156
+ handleSubmitted(): void;
157
+ set loading(value: boolean);
158
+ /**
159
+ * Show the "waiting for an answer" indicator. Turning this on turns
160
+ * `typing` off, for the reason given above.
161
+ *
162
+ * @type {boolean}
163
+ */
164
+ get loading(): boolean;
165
+ set typing(value: boolean | {
166
+ html: string;
167
+ });
168
+ /**
169
+ * Show the "typing" bubble: `true`, or `{ html }` for your own markup.
170
+ *
171
+ * Turning this on turns `loading` off. The two describe the same pause in
172
+ * the conversation — one says a person is writing, the other that a server
173
+ * has not answered yet — and showing both would leave the reader to work
174
+ * out the difference.
175
+ *
176
+ * @type {boolean | { html: string }}
177
+ */
178
+ get typing(): boolean | {
179
+ html: string;
180
+ };
122
181
  set state(value: ChatState);
123
182
  /**
124
183
  * Current state. Assigning it starts the transition, exactly as calling the
@@ -270,6 +329,7 @@ import { LitElement } from 'lit';
270
329
  import type { Theme } from './types.js';
271
330
  import type { Message } from './types.js';
272
331
  import type { Labels } from './i18n/labels.js';
332
+ import { DragController } from './controllers/drag-controller.js';
273
333
  import type { ChatState } from './types.js';
274
334
  import type { Device } from './types.js';
275
335
  import type { ResolvedTheme } from './types.js';
@@ -49,6 +49,10 @@ export type ComposerHost = {
49
49
  sendOnEnter: boolean;
50
50
  maxLength: number | undefined;
51
51
  currentLabels: import("../i18n/labels.js").Labels;
52
+ /**
53
+ * Called after a submit that no listener cancelled.
54
+ */
55
+ handleSubmitted: () => void;
52
56
  };
53
57
  import type { ReactiveController } from 'lit';
54
58
  import type { LitElement } from 'lit';
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Moves one thing — the launcher or the panel — around the viewport.
3
+ *
4
+ * What a gesture produces is not a position but a displacement: how far the
5
+ * reader has dragged the widget from wherever the theme put it, in screen
6
+ * pixels. The host keeps one such displacement for the whole widget and hands
7
+ * it to both controllers, which is what makes the two states travel together:
8
+ * drag the launcher into a corner and the panel opens in that same corner,
9
+ * because both are the theme's position plus the same shift.
10
+ *
11
+ * Each controller then turns the displacement into the terms its own corner
12
+ * uses — at a right-hand corner, moving right means a smaller offset — and
13
+ * writes it into the two custom properties the stylesheet already reads. The
14
+ * offset lives on the host as an inline property, which outranks the theme's
15
+ * sheet and the page's CSS: a reader who dragged the widget somewhere means
16
+ * it, and until the drag is cleared theirs is the most specific word on where
17
+ * it goes.
18
+ *
19
+ * @implements {ReactiveController}
20
+ */
21
+ export class DragController implements ReactiveController {
22
+ /**
23
+ * @param {LitElement} host
24
+ * @param {DragOptions} options
25
+ */
26
+ constructor(host: LitElement, options: DragOptions);
27
+ hostConnected(): void;
28
+ hostDisconnected(): void;
29
+ /**
30
+ * True when the gesture that just ended was a drag. Reading it clears it, so
31
+ * the click a pointer-up fires can be skipped exactly once.
32
+ *
33
+ * @returns {boolean}
34
+ */
35
+ consumeDrag(): boolean;
36
+ /**
37
+ * Put this thing where the displacement says, within the viewport.
38
+ *
39
+ * @param {{ x: number, y: number } | null} displacement
40
+ * @returns {{ x: number, y: number } | undefined} Where it ended up, when it is on screen.
41
+ */
42
+ place(displacement: {
43
+ x: number;
44
+ y: number;
45
+ } | null): {
46
+ x: number;
47
+ y: number;
48
+ } | undefined;
49
+ /**
50
+ * Begin a drag.
51
+ *
52
+ * @param {PointerEvent} event
53
+ * @returns {void}
54
+ */
55
+ start(event: PointerEvent): void;
56
+ /**
57
+ * Move with the arrow keys, so the same thing can be done without a pointer.
58
+ *
59
+ * @param {KeyboardEvent} event
60
+ * @returns {boolean} True when the key was used.
61
+ */
62
+ nudge(event: KeyboardEvent): boolean;
63
+ #private;
64
+ }
65
+ export type DragOptions = {
66
+ name: "launcher" | "panel";
67
+ enabled: () => boolean;
68
+ /**
69
+ * What moves.
70
+ */
71
+ element: () => HTMLElement | null;
72
+ /**
73
+ * How big it means to be.
74
+ */
75
+ size: () => {
76
+ width: number;
77
+ height: number;
78
+ };
79
+ /**
80
+ * Which corner the offset counts from.
81
+ */
82
+ corner: () => Position;
83
+ /**
84
+ * The theme's offset, before any drag.
85
+ */
86
+ base: () => {
87
+ x: number;
88
+ y: number;
89
+ };
90
+ /**
91
+ * The widget's displacement now.
92
+ */
93
+ current: () => {
94
+ x: number;
95
+ y: number;
96
+ } | null;
97
+ /**
98
+ * A new displacement.
99
+ */
100
+ onMove: (displacement: {
101
+ x: number;
102
+ y: number;
103
+ }) => void;
104
+ /**
105
+ * The gesture ended here.
106
+ */
107
+ onSettle: (name: "launcher" | "panel") => void;
108
+ /**
109
+ * The custom properties to write.
110
+ */
111
+ vars: {
112
+ x: string;
113
+ y: string;
114
+ };
115
+ };
116
+ import type { ReactiveController } from 'lit';
117
+ import type { LitElement } from 'lit';
118
+ import type { Position } from '../types.js';
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Keeps a touch that lands on the open panel from scrolling the page behind
3
+ * it.
4
+ *
5
+ * `overscroll-behavior: contain` is on the message list already, and it does
6
+ * the job for the case it covers: a list that can scroll, dragged past its
7
+ * own end. It has nothing to say about the cases that actually leak. A short
8
+ * conversation does not overflow, so the list is not a scroll container at
9
+ * all and the touch goes straight through to the document — which is what a
10
+ * reader on an iPhone sees as the page sliding around underneath a chat that
11
+ * looks like it should be holding still. The same happens for a touch that
12
+ * starts on the title bar or beside the input.
13
+ *
14
+ * So the rule is decided here instead: a touch on the panel may scroll
15
+ * something inside the panel, and otherwise it does nothing. On the first
16
+ * move of each gesture the controller looks along the touch's own path —
17
+ * `composedPath`, so a scroller inside a consumer's component counts too —
18
+ * for something that can still travel in the direction the finger is going.
19
+ * Finding one, it stands aside for the rest of the gesture; finding none, it
20
+ * cancels the move, and iOS then takes the whole gesture as "not a scroll".
21
+ *
22
+ * Two touches are left alone: that is a pinch, and zooming is the reader's.
23
+ * A move that is no longer cancelable is left alone too — the browser has
24
+ * already committed to scrolling, and `overscroll-behavior` is what keeps
25
+ * that inside the list.
26
+ *
27
+ * @implements {ReactiveController}
28
+ */
29
+ export class ScrollLockController implements ReactiveController {
30
+ /** @param {LitElement} host */
31
+ constructor(host: LitElement);
32
+ hostUpdated(): void;
33
+ hostDisconnected(): void;
34
+ #private;
35
+ }
36
+ import type { ReactiveController } from 'lit';
37
+ import type { LitElement } from 'lit';
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Keeps the full-screen panel inside the part of the screen the reader can
3
+ * actually see.
4
+ *
5
+ * On a phone the panel is `position: fixed` across the whole viewport, and
6
+ * that viewport is not what is on screen once a keyboard slides up: the
7
+ * layout viewport keeps its full height, so the composer — and with it the
8
+ * last few messages — ends up behind the keyboard. iOS then scrolls the page
9
+ * to chase the focused field, which moves the fixed panel out of frame
10
+ * instead of helping. `dvh` is no use here either; it tracks the browser's
11
+ * own chrome collapsing, not the keyboard.
12
+ *
13
+ * `visualViewport` is the one thing that does report the visible box, so the
14
+ * panel is pinned to it: its top to `offsetTop`, its height to `height`. The
15
+ * composer lands just above the keyboard, and the conversation gets whatever
16
+ * is left.
17
+ *
18
+ * A pinch is left alone. Zooming is how a reader gets a closer look, and a
19
+ * panel that re-fitted itself to the magnified box would take that away by
20
+ * reflowing the text back to the same apparent size.
21
+ *
22
+ * @implements {ReactiveController}
23
+ */
24
+ export class ViewportController implements ReactiveController {
25
+ /**
26
+ * @param {LitElement} host
27
+ * @param {{ enabled: () => boolean }} options
28
+ */
29
+ constructor(host: LitElement, { enabled }: {
30
+ enabled: () => boolean;
31
+ });
32
+ hostConnected(): void;
33
+ hostDisconnected(): void;
34
+ hostUpdated(): void;
35
+ #private;
36
+ }
37
+ import type { ReactiveController } from 'lit';
38
+ import type { LitElement } from 'lit';
@@ -27,4 +27,5 @@ export const Events: Readonly<{
27
27
  BREAKPOINT_CHANGE: "chat-breakpoint-change";
28
28
  HOME: "chat-home";
29
29
  ATTACH: "chat-attach";
30
+ MOVE: "chat-move";
30
31
  }>;
@@ -1,5 +1,5 @@
1
1
  import type { ChitUI } from './chit-ui.js';
2
- import type { Message, ChatState, Trigger, Device } from './types.js';
2
+ import type { Message, ChatState, Trigger, Device, Position } from './types.js';
3
3
 
4
4
  export interface ChitUIEventMap {
5
5
  'chat-submit': CustomEvent<{ text: string }>;
@@ -17,6 +17,12 @@ export interface ChitUIEventMap {
17
17
  'chat-breakpoint-change': CustomEvent<{ device: Device }>;
18
18
  'chat-home': CustomEvent<{ trigger: Trigger }>;
19
19
  'chat-attach': CustomEvent<{ files: File[] }>;
20
+ 'chat-move': CustomEvent<{
21
+ target: 'launcher' | 'panel';
22
+ position: Position;
23
+ offset: { x: number; y: number };
24
+ displacement: { x: number; y: number };
25
+ }>;
20
26
  }
21
27
 
22
28
  declare global {
@@ -53,6 +53,14 @@ export type Labels = {
53
53
  * Announced while the other side is typing.
54
54
  */
55
55
  typing: string;
56
+ /**
57
+ * Announced while waiting for an answer.
58
+ */
59
+ loading: string;
60
+ /**
61
+ * The panel's drag handle.
62
+ */
63
+ move: string;
56
64
  /**
57
65
  * "Jump to newest" button.
58
66
  */
@@ -1,4 +1,166 @@
1
1
  /// <reference path="./global.d.ts" />
2
2
  export { ChitUI };
3
3
  export { Events } from "./events.js";
4
+ /**
5
+ * The public types, re-exported so a consumer can write
6
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
7
+ * into `dist/types/` for them.
8
+ */
9
+ export type Role = import("./types.js").Role;
10
+ /**
11
+ * The public types, re-exported so a consumer can write
12
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
13
+ * into `dist/types/` for them.
14
+ */
15
+ export type MessageStatus = import("./types.js").MessageStatus;
16
+ /**
17
+ * The public types, re-exported so a consumer can write
18
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
19
+ * into `dist/types/` for them.
20
+ */
21
+ export type ChatState = import("./types.js").ChatState;
22
+ /**
23
+ * The public types, re-exported so a consumer can write
24
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
25
+ * into `dist/types/` for them.
26
+ */
27
+ export type Trigger = import("./types.js").Trigger;
28
+ /**
29
+ * The public types, re-exported so a consumer can write
30
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
31
+ * into `dist/types/` for them.
32
+ */
33
+ export type Device = import("./types.js").Device;
34
+ /**
35
+ * The public types, re-exported so a consumer can write
36
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
37
+ * into `dist/types/` for them.
38
+ */
39
+ export type Effect = import("./types.js").Effect;
40
+ /**
41
+ * The public types, re-exported so a consumer can write
42
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
43
+ * into `dist/types/` for them.
44
+ */
45
+ export type Position = import("./types.js").Position;
46
+ /**
47
+ * The public types, re-exported so a consumer can write
48
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
49
+ * into `dist/types/` for them.
50
+ */
51
+ export type BubbleTail = import("./types.js").BubbleTail;
52
+ /**
53
+ * The public types, re-exported so a consumer can write
54
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
55
+ * into `dist/types/` for them.
56
+ */
57
+ export type LoadingStyle = import("./types.js").LoadingStyle;
58
+ /**
59
+ * The public types, re-exported so a consumer can write
60
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
61
+ * into `dist/types/` for them.
62
+ */
63
+ export type MessageBase = import("./types.js").MessageBase;
64
+ /**
65
+ * The public types, re-exported so a consumer can write
66
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
67
+ * into `dist/types/` for them.
68
+ */
69
+ export type TextMessage = import("./types.js").TextMessage;
70
+ /**
71
+ * The public types, re-exported so a consumer can write
72
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
73
+ * into `dist/types/` for them.
74
+ */
75
+ export type HtmlMessage = import("./types.js").HtmlMessage;
76
+ /**
77
+ * The public types, re-exported so a consumer can write
78
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
79
+ * into `dist/types/` for them.
80
+ */
81
+ export type TemplateMessage = import("./types.js").TemplateMessage;
82
+ /**
83
+ * The public types, re-exported so a consumer can write
84
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
85
+ * into `dist/types/` for them.
86
+ */
87
+ export type ElementMessage = import("./types.js").ElementMessage;
88
+ /**
89
+ * The public types, re-exported so a consumer can write
90
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
91
+ * into `dist/types/` for them.
92
+ */
93
+ export type ComponentMessage = import("./types.js").ComponentMessage;
94
+ /**
95
+ * The public types, re-exported so a consumer can write
96
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
97
+ * into `dist/types/` for them.
98
+ */
99
+ export type Message = import("./types.js").Message;
100
+ /**
101
+ * The public types, re-exported so a consumer can write
102
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
103
+ * into `dist/types/` for them.
104
+ */
105
+ export type Animation = import("./types.js").Animation;
106
+ /**
107
+ * The public types, re-exported so a consumer can write
108
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
109
+ * into `dist/types/` for them.
110
+ */
111
+ export type Offset = import("./types.js").Offset;
112
+ /**
113
+ * The public types, re-exported so a consumer can write
114
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
115
+ * into `dist/types/` for them.
116
+ */
117
+ export type ClosedTheme = import("./types.js").ClosedTheme;
118
+ /**
119
+ * The public types, re-exported so a consumer can write
120
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
121
+ * into `dist/types/` for them.
122
+ */
123
+ export type OpenColors = import("./types.js").OpenColors;
124
+ /**
125
+ * The public types, re-exported so a consumer can write
126
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
127
+ * into `dist/types/` for them.
128
+ */
129
+ export type SpeakerTheme = import("./types.js").SpeakerTheme;
130
+ /**
131
+ * The public types, re-exported so a consumer can write
132
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
133
+ * into `dist/types/` for them.
134
+ */
135
+ export type LoadingTheme = import("./types.js").LoadingTheme;
136
+ /**
137
+ * The public types, re-exported so a consumer can write
138
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
139
+ * into `dist/types/` for them.
140
+ */
141
+ export type OpenTheme = import("./types.js").OpenTheme;
142
+ /**
143
+ * The public types, re-exported so a consumer can write
144
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
145
+ * into `dist/types/` for them.
146
+ */
147
+ export type Theme = import("./types.js").Theme;
148
+ /**
149
+ * The public types, re-exported so a consumer can write
150
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
151
+ * into `dist/types/` for them.
152
+ */
153
+ export type ResolvedClosed = import("./types.js").ResolvedClosed;
154
+ /**
155
+ * The public types, re-exported so a consumer can write
156
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
157
+ * into `dist/types/` for them.
158
+ */
159
+ export type ResolvedOpen = import("./types.js").ResolvedOpen;
160
+ /**
161
+ * The public types, re-exported so a consumer can write
162
+ * `import type { Message } from '@hidemikimura/chit-ui'` instead of reaching
163
+ * into `dist/types/` for them.
164
+ */
165
+ export type ResolvedTheme = import("./types.js").ResolvedTheme;
4
166
  import { ChitUI } from './chit-ui.js';
@@ -1,4 +1,3 @@
1
- /** @import { ChitUI } from '../chit-ui.js' */
2
1
  /**
3
2
  * The scrolling conversation.
4
3
  *
@@ -19,6 +19,7 @@ export namespace defaultTheme {
19
19
  }
20
20
  let radius: number;
21
21
  let launcher: "hidden";
22
+ let draggable: boolean;
22
23
  namespace header {
23
24
  let visible: boolean;
24
25
  let title: null;
@@ -64,6 +65,13 @@ export namespace defaultTheme {
64
65
  export { radius_1 as radius };
65
66
  export let tail: "none";
66
67
  }
68
+ namespace loading {
69
+ export let auto: boolean;
70
+ export let style: "spinner";
71
+ let text_1: null;
72
+ export { text_1 as text };
73
+ export let timeout: number;
74
+ }
67
75
  namespace speaker {
68
76
  namespace assistant {
69
77
  let name: null;
@@ -102,6 +102,10 @@ export type ClosedTheme = {
102
102
  */
103
103
  image?: string | null | undefined;
104
104
  label?: string | null | undefined;
105
+ /**
106
+ * Let the reader move it around the viewport.
107
+ */
108
+ draggable?: boolean | undefined;
105
109
  /**
106
110
  * A component that draws the whole launcher.
107
111
  */
@@ -119,6 +123,31 @@ export type ClosedTheme = {
119
123
  idle?: "none" | "pulse" | "bounce";
120
124
  }) | undefined;
121
125
  };
126
+ /**
127
+ * The wait between sending and the answer arriving.
128
+ */
129
+ export type LoadingStyle = "dots" | "spinner" | "text";
130
+ /**
131
+ * The wait between sending and the answer arriving.
132
+ */
133
+ export type LoadingTheme = {
134
+ /**
135
+ * Show it from `chat-submit` until the next message from the other side.
136
+ */
137
+ auto?: boolean | undefined;
138
+ /**
139
+ * What it looks like.
140
+ */
141
+ style?: LoadingStyle | undefined;
142
+ /**
143
+ * Wording beside the animation, or on its own for `'text'`.
144
+ */
145
+ text?: string | null | undefined;
146
+ /**
147
+ * ms after which it gives up on its own; 0 leaves it to the consumer.
148
+ */
149
+ timeout?: number | undefined;
150
+ };
122
151
  /**
123
152
  * Who is talking, as the theme describes them. A message that carries its own
124
153
  * `name` or `avatar` wins; `null` on the message hides what the theme set.
@@ -166,6 +195,10 @@ export type OpenTheme = {
166
195
  * Keep the launcher visible while open.
167
196
  */
168
197
  launcher?: "hidden" | "visible" | undefined;
198
+ /**
199
+ * Let the reader move the panel by its title bar.
200
+ */
201
+ draggable?: boolean | undefined;
169
202
  /**
170
203
  * The title bar: whether it is there at all, its text, its image, and whether it offers a way back to the start.
171
204
  */
@@ -193,6 +226,10 @@ export type OpenTheme = {
193
226
  assistant?: SpeakerTheme;
194
227
  user?: SpeakerTheme;
195
228
  } | undefined;
229
+ /**
230
+ * The wait for an answer.
231
+ */
232
+ loading?: LoadingTheme | undefined;
196
233
  animation?: (Animation & {
197
234
  scroll?: "smooth" | "instant";
198
235
  }) | undefined;
@@ -241,6 +278,7 @@ export type ResolvedClosed = {
241
278
  y: number;
242
279
  };
243
280
  radius: number;
281
+ draggable: boolean;
244
282
  image: string | null;
245
283
  label: string | null;
246
284
  component: (new () => HTMLElement) | string | null;
@@ -271,6 +309,7 @@ export type ResolvedOpen = {
271
309
  };
272
310
  radius: number;
273
311
  launcher: "hidden" | "visible";
312
+ draggable: boolean;
274
313
  header: {
275
314
  visible: boolean;
276
315
  title: string | null;
@@ -289,6 +328,7 @@ export type ResolvedOpen = {
289
328
  assistant: Required<SpeakerTheme>;
290
329
  user: Required<SpeakerTheme>;
291
330
  };
331
+ loading: Required<LoadingTheme>;
292
332
  animation: {
293
333
  enter: Effect;
294
334
  exit: Effect;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hidemikimura/chit-ui",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Lit-based chat widget UI: launcher, panel, themable, any HTML or Lit component as a message",
5
5
  "license": "MIT",
6
6
  "author": "Hidemi Kimura",