@hidemikimura/chit-ui 0.1.0 → 0.2.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/CHANGELOG.md +58 -0
- package/README.md +206 -9
- package/dist/chit-ui.iife.min.js +287 -60
- package/dist/chit-ui.iife.min.js.map +1 -1
- package/dist/chit-ui.min.js +287 -60
- package/dist/chit-ui.min.js.map +1 -1
- package/dist/types/bundle.d.ts +1 -0
- package/dist/types/chit-ui.d.ts +25 -0
- package/dist/types/events.d.ts +2 -0
- package/dist/types/global.d.ts +2 -0
- package/dist/types/i18n/labels.d.ts +8 -0
- package/dist/types/render/composer.d.ts +0 -1
- package/dist/types/theme/default-theme.d.ts +22 -1
- package/dist/types/themes/green.d.ts +16 -0
- package/dist/types/themes/index.d.ts +1 -0
- package/dist/types/types.d.ts +65 -4
- package/package.json +6 -1
- package/src/bundle.js +1 -0
- package/src/chit-ui.js +37 -0
- package/src/events.js +2 -0
- package/src/global.d.ts +2 -0
- package/src/i18n/labels.js +6 -0
- package/src/render/composer.js +58 -0
- package/src/render/message.js +13 -4
- package/src/render/panel.js +56 -10
- package/src/styles/composer.css.js +39 -0
- package/src/styles/host.css.js +2 -0
- package/src/styles/message.css.js +117 -3
- package/src/styles/panel.css.js +21 -2
- package/src/theme/default-theme.js +21 -2
- package/src/theme/theme-to-css.js +5 -0
- package/src/themes/green.js +47 -0
- package/src/themes/index.js +15 -0
- package/src/types.js +20 -6
package/src/styles/host.css.js
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
import { css } from 'lit';
|
|
3
3
|
|
|
4
4
|
export const messageStyles = css`
|
|
5
|
+
:host {
|
|
6
|
+
/* Internal: the box the tail's path is drawn in. The path is written in
|
|
7
|
+
these units, so the two move together. */
|
|
8
|
+
--_chit-tail-w: 18px;
|
|
9
|
+
--_chit-tail-h: 20px;
|
|
10
|
+
/* How deep the tail's root sits inside the bubble. Less than the
|
|
11
|
+
bubble's own padding, so it never reaches the text. */
|
|
12
|
+
--_chit-tail-root: 9px;
|
|
13
|
+
/* Far enough down that the bubble's edge has straightened out under it. */
|
|
14
|
+
--_chit-tail-offset: calc(var(--chit-bubble-radius) / 4 + 1px);
|
|
15
|
+
}
|
|
16
|
+
|
|
5
17
|
[part~='messages'] {
|
|
6
18
|
flex: 1 1 auto;
|
|
7
19
|
min-height: 0;
|
|
@@ -26,7 +38,13 @@ export const messageStyles = css`
|
|
|
26
38
|
*/
|
|
27
39
|
[part~='message'] {
|
|
28
40
|
display: flex;
|
|
29
|
-
|
|
41
|
+
/*
|
|
42
|
+
* The icon sits level with the top of the speaker's block — beside the
|
|
43
|
+
* name when there is one, beside the bubble's first line otherwise. At
|
|
44
|
+
* the bottom it would end up next to the timestamp, away from everything
|
|
45
|
+
* it identifies.
|
|
46
|
+
*/
|
|
47
|
+
align-items: flex-start;
|
|
30
48
|
gap: 0.5em;
|
|
31
49
|
width: 100%;
|
|
32
50
|
max-width: 100%;
|
|
@@ -52,7 +70,7 @@ export const messageStyles = css`
|
|
|
52
70
|
[part~='bubble'] {
|
|
53
71
|
position: relative;
|
|
54
72
|
padding: 0.6em 0.85em;
|
|
55
|
-
border-radius:
|
|
73
|
+
border-radius: var(--chit-bubble-radius);
|
|
56
74
|
background: var(--chit-color-assistant-bg);
|
|
57
75
|
color: var(--chit-color-assistant-text);
|
|
58
76
|
}
|
|
@@ -67,6 +85,90 @@ export const messageStyles = css`
|
|
|
67
85
|
border-bottom-left-radius: 4px;
|
|
68
86
|
}
|
|
69
87
|
|
|
88
|
+
/* --- tails -------------------------------------------------------------
|
|
89
|
+
*
|
|
90
|
+
* A tail is one pseudo-element: a box in the bubble's colour, clipped to a
|
|
91
|
+
* leaf whose point sits outside the bubble and whose root is buried inside
|
|
92
|
+
* it. Burying the root is what makes the join disappear — the bubble's own
|
|
93
|
+
* background covers the seam, so every corner keeps its radius and the two
|
|
94
|
+
* read as one shape at any radius.
|
|
95
|
+
*
|
|
96
|
+
* It hangs a little below the corner, where the bubble's edge has
|
|
97
|
+
* straightened out, so the root has something flat to sit against. The
|
|
98
|
+
* offset follows the radius for that reason.
|
|
99
|
+
*
|
|
100
|
+
* The other side and the downward version are the same path mirrored, so
|
|
101
|
+
* the curve is written once.
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
[part~='bubble']::after,
|
|
105
|
+
[part~='typing']::after {
|
|
106
|
+
content: '';
|
|
107
|
+
display: none;
|
|
108
|
+
position: absolute;
|
|
109
|
+
width: var(--_chit-tail-w);
|
|
110
|
+
height: var(--_chit-tail-h);
|
|
111
|
+
clip-path: path('M 18 0.5 Q 8 0.8 0.5 3.5 Q 0 5.2 2 7 Q 10 9.5 18 19 Z');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/*
|
|
115
|
+
* Everything below needs clip-path: path() to draw the leaf. Where that is
|
|
116
|
+
* missing the tail stays hidden and the bubble keeps its default shape,
|
|
117
|
+
* rather than a bare rectangle poking out of its side.
|
|
118
|
+
*/
|
|
119
|
+
@supports (clip-path: path('M 0 0 Z')) {
|
|
120
|
+
:host([data-bubble-tail='top']) [part~='bubble']::after,
|
|
121
|
+
:host([data-bubble-tail='bottom']) [part~='bubble']::after,
|
|
122
|
+
:host([data-bubble-tail='top']) [part~='typing']::after,
|
|
123
|
+
:host([data-bubble-tail='bottom']) [part~='typing']::after {
|
|
124
|
+
display: block;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/*
|
|
128
|
+
* With a tail there is no need to clip the speaker's corner as well: one
|
|
129
|
+
* bubble carries one cue about who is talking, not two.
|
|
130
|
+
*/
|
|
131
|
+
:host([data-bubble-tail='top']) [part~='bubble'],
|
|
132
|
+
:host([data-bubble-tail='bottom']) [part~='bubble'],
|
|
133
|
+
:host([data-bubble-tail='top']) [part~='typing'],
|
|
134
|
+
:host([data-bubble-tail='bottom']) [part~='typing'] {
|
|
135
|
+
border-radius: var(--chit-bubble-radius);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Their side: the leaf points left, its root inside the bubble. */
|
|
139
|
+
:host([data-bubble-tail]) [part~='message-assistant'] [part~='bubble']::after,
|
|
140
|
+
:host([data-bubble-tail]) [part~='typing']::after {
|
|
141
|
+
right: calc(100% - var(--_chit-tail-root));
|
|
142
|
+
background: var(--chit-color-assistant-bg);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* Your side: the same leaf, mirrored. */
|
|
146
|
+
:host([data-bubble-tail]) [part~='message-user'] [part~='bubble']::after {
|
|
147
|
+
left: calc(100% - var(--_chit-tail-root));
|
|
148
|
+
background: var(--chit-color-user-bg);
|
|
149
|
+
transform: scaleX(-1);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
:host([data-bubble-tail='top']) [part~='bubble']::after,
|
|
153
|
+
:host([data-bubble-tail='top']) [part~='typing']::after {
|
|
154
|
+
top: var(--_chit-tail-offset);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
:host([data-bubble-tail='bottom']) [part~='bubble']::after,
|
|
158
|
+
:host([data-bubble-tail='bottom']) [part~='typing']::after {
|
|
159
|
+
bottom: var(--_chit-tail-offset);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
:host([data-bubble-tail='bottom']) [part~='message-assistant'] [part~='bubble']::after,
|
|
163
|
+
:host([data-bubble-tail='bottom']) [part~='typing']::after {
|
|
164
|
+
transform: scaleY(-1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
:host([data-bubble-tail='bottom']) [part~='message-user'] [part~='bubble']::after {
|
|
168
|
+
transform: scale(-1, -1);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
70
172
|
[part~='avatar'] {
|
|
71
173
|
flex: none;
|
|
72
174
|
width: 2em;
|
|
@@ -75,6 +177,15 @@ export const messageStyles = css`
|
|
|
75
177
|
object-fit: cover;
|
|
76
178
|
}
|
|
77
179
|
|
|
180
|
+
/*
|
|
181
|
+
* A message that hides the theme's icon (avatar: null, for the second and
|
|
182
|
+
* later messages in a run) keeps its place in the row, so the bubbles below
|
|
183
|
+
* stay in line with the one that shows the face.
|
|
184
|
+
*/
|
|
185
|
+
[part~='avatar'][data-placeholder] {
|
|
186
|
+
visibility: hidden;
|
|
187
|
+
}
|
|
188
|
+
|
|
78
189
|
[part~='name'] {
|
|
79
190
|
font-size: 0.8em;
|
|
80
191
|
color: var(--chit-color-system-text);
|
|
@@ -118,9 +229,12 @@ export const messageStyles = css`
|
|
|
118
229
|
}
|
|
119
230
|
|
|
120
231
|
[part~='typing'] {
|
|
232
|
+
position: relative;
|
|
121
233
|
align-self: flex-start;
|
|
234
|
+
/* Indented past the speaker's icon, when the theme gives them one. */
|
|
235
|
+
margin-inline-start: var(--_chit-speaker-gutter, 0px);
|
|
122
236
|
padding: 0.7em 0.9em;
|
|
123
|
-
border-radius:
|
|
237
|
+
border-radius: var(--chit-bubble-radius);
|
|
124
238
|
border-bottom-left-radius: 4px;
|
|
125
239
|
background: var(--chit-color-assistant-bg);
|
|
126
240
|
color: var(--chit-color-assistant-text);
|
package/src/styles/panel.css.js
CHANGED
|
@@ -76,18 +76,33 @@ export const panelStyles = css`
|
|
|
76
76
|
color: var(--chit-color-header-text);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/*
|
|
80
|
+
* The image and the title are one flex item. A slot is display: contents,
|
|
81
|
+
* so without this wrapper its children become items of the header itself
|
|
82
|
+
* and space-between pushes the image to one edge and the title to the
|
|
83
|
+
* middle, away from each other.
|
|
84
|
+
*/
|
|
85
|
+
[part~='header-title'] {
|
|
86
|
+
display: inline-flex;
|
|
87
|
+
min-width: 0;
|
|
88
|
+
align-items: center;
|
|
89
|
+
gap: 0.5em;
|
|
90
|
+
}
|
|
91
|
+
|
|
79
92
|
[part~='header-heading'] {
|
|
93
|
+
overflow: hidden;
|
|
80
94
|
font-size: 1.05em;
|
|
81
95
|
font-weight: 600;
|
|
96
|
+
text-overflow: ellipsis;
|
|
97
|
+
white-space: nowrap;
|
|
82
98
|
}
|
|
83
99
|
|
|
84
100
|
[part~='header-logo'] {
|
|
101
|
+
flex: none;
|
|
85
102
|
width: 1.75em;
|
|
86
103
|
height: 1.75em;
|
|
87
|
-
margin-inline-end: 0.5em;
|
|
88
104
|
border-radius: 50%;
|
|
89
105
|
object-fit: cover;
|
|
90
|
-
vertical-align: middle;
|
|
91
106
|
}
|
|
92
107
|
|
|
93
108
|
[part~='header-actions'] {
|
|
@@ -96,6 +111,7 @@ export const panelStyles = css`
|
|
|
96
111
|
gap: 0.25em;
|
|
97
112
|
}
|
|
98
113
|
|
|
114
|
+
[part~='home-button'],
|
|
99
115
|
[part~='close-button'] {
|
|
100
116
|
display: grid;
|
|
101
117
|
place-items: center;
|
|
@@ -109,15 +125,18 @@ export const panelStyles = css`
|
|
|
109
125
|
cursor: pointer;
|
|
110
126
|
}
|
|
111
127
|
|
|
128
|
+
[part~='home-button']:hover,
|
|
112
129
|
[part~='close-button']:hover {
|
|
113
130
|
background: color-mix(in srgb, currentColor 10%, transparent);
|
|
114
131
|
}
|
|
115
132
|
|
|
133
|
+
[part~='home-button']:focus-visible,
|
|
116
134
|
[part~='close-button']:focus-visible {
|
|
117
135
|
outline: 2px solid var(--chit-color-accent);
|
|
118
136
|
outline-offset: 1px;
|
|
119
137
|
}
|
|
120
138
|
|
|
139
|
+
[part~='home-button'] svg,
|
|
121
140
|
[part~='close-button'] svg {
|
|
122
141
|
width: 1.1em;
|
|
123
142
|
height: 1.1em;
|
|
@@ -72,7 +72,7 @@ export const defaultTheme = {
|
|
|
72
72
|
offset: { x: 24, y: 24 },
|
|
73
73
|
radius: 16,
|
|
74
74
|
launcher: /** @type {const} */ ('hidden'),
|
|
75
|
-
header: { title: null, logo: null,
|
|
75
|
+
header: { visible: true, title: null, logo: null, home: false },
|
|
76
76
|
background: { image: null },
|
|
77
77
|
colors: {
|
|
78
78
|
background: PALETTE.surface,
|
|
@@ -91,6 +91,16 @@ export const defaultTheme = {
|
|
|
91
91
|
inputText: PALETTE.ink,
|
|
92
92
|
inputPlaceholder: PALETTE.muted,
|
|
93
93
|
},
|
|
94
|
+
// No tail by default: the squared-off corner on the speaker's side is
|
|
95
|
+
// enough to show who is talking, and a tail is a strong look to impose on
|
|
96
|
+
// a widget that has to sit in someone else's page.
|
|
97
|
+
bubble: { radius: 14, tail: /** @type {const} */ ('none') },
|
|
98
|
+
// Who is talking. Nothing by default: a widget that invents a name and a
|
|
99
|
+
// face for the consumer's support desk would be guessing.
|
|
100
|
+
speaker: {
|
|
101
|
+
assistant: { name: null, avatar: null },
|
|
102
|
+
user: { name: null, avatar: null },
|
|
103
|
+
},
|
|
94
104
|
animation: {
|
|
95
105
|
enter: /** @type {const} */ ('scale'),
|
|
96
106
|
exit: /** @type {const} */ ('fade'),
|
|
@@ -100,7 +110,16 @@ export const defaultTheme = {
|
|
|
100
110
|
// ScrollController for why.
|
|
101
111
|
scroll: /** @type {const} */ ('smooth'),
|
|
102
112
|
},
|
|
103
|
-
|
|
113
|
+
// `attach` is off by default: a widget that offers to take files when
|
|
114
|
+
// nothing is listening for them would be promising something it cannot
|
|
115
|
+
// keep. `accept` and `multiple` go straight to the file input.
|
|
116
|
+
input: {
|
|
117
|
+
maxRows: 5,
|
|
118
|
+
placeholder: null,
|
|
119
|
+
attach: false,
|
|
120
|
+
accept: 'image/*,video/*',
|
|
121
|
+
multiple: false,
|
|
122
|
+
},
|
|
104
123
|
},
|
|
105
124
|
hidden: {
|
|
106
125
|
animation: { exit: /** @type {const} */ ('fade'), duration: 150 },
|
|
@@ -76,7 +76,12 @@ export function themeToVariables(theme) {
|
|
|
76
76
|
'--chit-color-input-text': c.inputText,
|
|
77
77
|
'--chit-color-input-placeholder': c.inputPlaceholder,
|
|
78
78
|
|
|
79
|
+
'--chit-bubble-radius': px(open.bubble.radius),
|
|
80
|
+
|
|
79
81
|
'--_chit-input-max-rows': String(open.input.maxRows),
|
|
82
|
+
// Internal: the width the speaker's icon takes up, so the typing
|
|
83
|
+
// indicator lines up with the bubbles it belongs to.
|
|
84
|
+
'--_chit-speaker-gutter': open.speaker.assistant.avatar ? '2.5em' : '0px',
|
|
80
85
|
};
|
|
81
86
|
}
|
|
82
87
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/** @import { Theme } from '../types.js' */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A green messenger look: a pale blue-grey conversation background, white
|
|
7
|
+
* bubbles for them, bright green for you, tails on the top corner.
|
|
8
|
+
*
|
|
9
|
+
* This is Chit UI's own palette in that familiar shape. It carries no other
|
|
10
|
+
* product's logo, wordmark or artwork, and the greens are Chit UI's own: the
|
|
11
|
+
* deep green is dark enough that white text and the send icon clear WCAG AA
|
|
12
|
+
* on it, which the bright green of the bubbles could not do.
|
|
13
|
+
* `test/theme.test.js` re-checks every pair here, so a later tweak cannot
|
|
14
|
+
* quietly drop below AA.
|
|
15
|
+
*
|
|
16
|
+
* @type {Theme}
|
|
17
|
+
*/
|
|
18
|
+
export const greenTheme = {
|
|
19
|
+
closed: {
|
|
20
|
+
colors: {
|
|
21
|
+
background: '#0a6b36',
|
|
22
|
+
text: '#ffffff',
|
|
23
|
+
shadow: '0 4px 12px rgba(16, 42, 26, 0.32)',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
open: {
|
|
27
|
+
radius: 12,
|
|
28
|
+
colors: {
|
|
29
|
+
background: '#d3ddea',
|
|
30
|
+
text: '#16212c',
|
|
31
|
+
accent: '#0a6b36',
|
|
32
|
+
border: '#b6c5d6',
|
|
33
|
+
shadow: '0 8px 32px rgba(16, 32, 48, 0.28)',
|
|
34
|
+
headerBackground: '#ffffff',
|
|
35
|
+
headerText: '#16212c',
|
|
36
|
+
userBubble: '#9ce26c',
|
|
37
|
+
userText: '#14240c',
|
|
38
|
+
assistantBubble: '#ffffff',
|
|
39
|
+
assistantText: '#16212c',
|
|
40
|
+
systemText: '#37475a',
|
|
41
|
+
inputBackground: '#ffffff',
|
|
42
|
+
inputText: '#16212c',
|
|
43
|
+
inputPlaceholder: '#5c6370',
|
|
44
|
+
},
|
|
45
|
+
bubble: { radius: 18, tail: 'top' },
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Themes the library ships with.
|
|
5
|
+
*
|
|
6
|
+
* A preset is an ordinary `Theme` object, so it is assigned like any other
|
|
7
|
+
* theme and customised by spreading over it:
|
|
8
|
+
*
|
|
9
|
+
* import { greenTheme } from '@hidemikimura/chit-ui/themes.js';
|
|
10
|
+
* el.theme = { ...greenTheme, open: { ...greenTheme.open, width: 420 } };
|
|
11
|
+
*
|
|
12
|
+
* Importing this module does not register the element or pull the widget in;
|
|
13
|
+
* it is data only.
|
|
14
|
+
*/
|
|
15
|
+
export { greenTheme } from './green.js';
|
package/src/types.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* @typedef {'pc' | 'mobile'} Device
|
|
12
12
|
* @typedef {'fade' | 'scale' | 'slide' | 'none'} Effect
|
|
13
13
|
* @typedef {'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'} Position
|
|
14
|
+
* @typedef {'none' | 'top' | 'bottom'} BubbleTail
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -18,8 +19,8 @@
|
|
|
18
19
|
* @property {string} id Unique within the array; used as the diffing key.
|
|
19
20
|
* @property {Role} role
|
|
20
21
|
* @property {string | Date} [time]
|
|
21
|
-
* @property {string} [name]
|
|
22
|
-
* @property {string} [avatar]
|
|
22
|
+
* @property {string | null} [name] Overrides the theme's speaker name; null hides it.
|
|
23
|
+
* @property {string | null} [avatar] Overrides the theme's speaker image; null hides it.
|
|
23
24
|
* @property {MessageStatus} [status]
|
|
24
25
|
* @property {boolean} [streaming]
|
|
25
26
|
* @property {unknown} [meta] Never touched by the library.
|
|
@@ -57,6 +58,15 @@
|
|
|
57
58
|
* @property {Animation & { idle?: 'none' | 'pulse' | 'bounce' }} [animation]
|
|
58
59
|
*/
|
|
59
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Who is talking, as the theme describes them. A message that carries its own
|
|
63
|
+
* `name` or `avatar` wins; `null` on the message hides what the theme set.
|
|
64
|
+
*
|
|
65
|
+
* @typedef {Object} SpeakerTheme
|
|
66
|
+
* @property {string | null} [name] Shown above the bubble.
|
|
67
|
+
* @property {string | null} [avatar] Image URL for the speaker's icon.
|
|
68
|
+
*/
|
|
69
|
+
|
|
60
70
|
/**
|
|
61
71
|
* @typedef {Object} OpenColors
|
|
62
72
|
* @property {string} [background]
|
|
@@ -84,11 +94,13 @@
|
|
|
84
94
|
* @property {Offset} [offset]
|
|
85
95
|
* @property {number} [radius]
|
|
86
96
|
* @property {'hidden' | 'visible'} [launcher] Keep the launcher visible while open.
|
|
87
|
-
* @property {{ title?: string | null, logo?: string | null,
|
|
97
|
+
* @property {{ visible?: boolean, title?: string | null, logo?: string | null, home?: boolean }} [header] The title bar: whether it is there at all, its text, its image, and whether it offers a way back to the start.
|
|
88
98
|
* @property {{ image?: string | null }} [background]
|
|
89
99
|
* @property {OpenColors} [colors]
|
|
100
|
+
* @property {{ radius?: number, tail?: BubbleTail }} [bubble] Bubble corner radius, and where the tail points from.
|
|
101
|
+
* @property {{ assistant?: SpeakerTheme, user?: SpeakerTheme }} [speaker] Default name and icon per side.
|
|
90
102
|
* @property {Animation & { scroll?: 'smooth' | 'instant' }} [animation]
|
|
91
|
-
* @property {{ maxRows?: number, placeholder?: string | null }} [input]
|
|
103
|
+
* @property {{ maxRows?: number, placeholder?: string | null, attach?: boolean, accept?: string, multiple?: boolean }} [input] Composer: rows, placeholder, and the attach button.
|
|
92
104
|
*/
|
|
93
105
|
|
|
94
106
|
/**
|
|
@@ -124,11 +136,13 @@
|
|
|
124
136
|
* @property {{ x: number, y: number }} offset
|
|
125
137
|
* @property {number} radius
|
|
126
138
|
* @property {'hidden' | 'visible'} launcher
|
|
127
|
-
* @property {{ title: string | null, logo: string | null,
|
|
139
|
+
* @property {{ visible: boolean, title: string | null, logo: string | null, home: boolean }} header
|
|
128
140
|
* @property {{ image: string | null }} background
|
|
129
141
|
* @property {Required<OpenColors>} colors
|
|
142
|
+
* @property {{ radius: number, tail: BubbleTail }} bubble
|
|
143
|
+
* @property {{ assistant: Required<SpeakerTheme>, user: Required<SpeakerTheme> }} speaker
|
|
130
144
|
* @property {{ enter: Effect, exit: Effect, duration: number, scroll: 'smooth' | 'instant' }} animation
|
|
131
|
-
* @property {{ maxRows: number, placeholder: string | null }} input
|
|
145
|
+
* @property {{ maxRows: number, placeholder: string | null, attach: boolean, accept: string, multiple: boolean }} input
|
|
132
146
|
*
|
|
133
147
|
* @typedef {Object} ResolvedTheme
|
|
134
148
|
* @property {number} breakpoint
|