inline-chat-kit 0.51.0 → 0.54.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 +303 -0
- package/README.md +36 -0
- package/dist/Artifact/ArtifactCard.d.ts +8 -1
- package/dist/Artifact/ChatLayout.d.ts +10 -1
- package/dist/Conversation/Conversation.d.ts +51 -0
- package/dist/ReplyThreadPopup/placePanel.d.ts +43 -0
- package/dist/inline-chat-kit.css +1 -1
- package/dist/inline-chat-kit.js +1386 -1153
- package/dist/inline-chat-kit.js.map +1 -1
- package/package.json +1 -1
- package/theming.md +51 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,309 @@ The versions before 1.0 follow the pre-release convention: **a breaking change
|
|
|
6
6
|
or new public API bumps the minor**, and the patch is for fixes. Anything that would break an
|
|
7
7
|
existing install is called out under **Breaking**, with what to do about it.
|
|
8
8
|
|
|
9
|
+
## 0.54.0 — 2026-09-12
|
|
10
|
+
|
|
11
|
+
Everything here came out of using the kit on a phone. It is better there than
|
|
12
|
+
it was, and the README now says plainly that a phone is not what it is for —
|
|
13
|
+
see *Where it is meant to run*.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **A sent message travels to the top instead of cutting to it.** Sending adds
|
|
18
|
+
a whole turn at once, so the content grew by its height in a single frame and
|
|
19
|
+
the scroll was assigned its new position in that same frame. Traced at
|
|
20
|
+
1120×680 across two answers: three moves over 24px in the whole session and
|
|
21
|
+
every one of them a single frame — 154px, **416px**, 172px. The 416 was the
|
|
22
|
+
send.
|
|
23
|
+
|
|
24
|
+
The reasoning block folding away at the end was never the problem: it was
|
|
25
|
+
already gradual (−29, −58, −206 across frames) and moved the scroll by
|
|
26
|
+
nothing. Now the one move that is a decision is eased and the thousand that
|
|
27
|
+
are tracking stay instant, because easing the arrival of text would mean the
|
|
28
|
+
line being read slides for a third of a second. Measured after: `178 → 206 →
|
|
29
|
+
244 → 287 → 326 → 358 → 384`, largest single step 42px.
|
|
30
|
+
|
|
31
|
+
- **The room under the last turn is measured rather than guessed.** A turn can
|
|
32
|
+
only be brought to the top if there is room under it to scroll into, and that
|
|
33
|
+
room was a `99vh` typed into the demo's stylesheet. At the end of the scroll:
|
|
34
|
+
673px of it on desktop, 836px on a phone, and the last turn **61px above the
|
|
35
|
+
top edge** — a completely blank screen at the bottom of every conversation.
|
|
36
|
+
|
|
37
|
+
`Conversation` takes a `tail` prop now, `"auto"` by default, and the
|
|
38
|
+
measurement is `screen - anchorOffset - (the anchored turn's top to the end
|
|
39
|
+
of the content)`. That makes the end of the scroll exactly where the anchored
|
|
40
|
+
turn sits at the anchor, so coming to rest and running out of scroll are the
|
|
41
|
+
same place and the room is spent holding the turn up rather than left over
|
|
42
|
+
underneath it. It also explains why chasing an answer costs nothing: the
|
|
43
|
+
content grows and the room shrinks by the same pixel, so the scroll height
|
|
44
|
+
does not move and neither does the turn at the top.
|
|
45
|
+
|
|
46
|
+
- **A short answer leaves its question at the top too.** The room was being
|
|
47
|
+
sized correctly and then not rested at: resting still meant the end of the
|
|
48
|
+
*content*, which agrees with the end of the scroll only while the answer is
|
|
49
|
+
longer than the screen. Below that the difference was left unspent with the
|
|
50
|
+
turn stranded mid-view — 575px from the top in a 1174px window, with 475px
|
|
51
|
+
still to scroll.
|
|
52
|
+
|
|
53
|
+
- **`endOfContent` counted the conversation's top padding twice.** A
|
|
54
|
+
conversation ending at 729 read as ending at 829, so the composer came to
|
|
55
|
+
rest 100px above the `endOffset` it was given. See **Breaking**.
|
|
56
|
+
|
|
57
|
+
- **Only a control that folds lets go of the end.** Releasing the follow on any
|
|
58
|
+
press meant clicking into the composer released it too, so when an answer
|
|
59
|
+
settled there was nothing to bring the view back — and settling is also when
|
|
60
|
+
the reasoning block folds itself away, so the content shrank by its height
|
|
61
|
+
and everything above dropped into view. It read as the conversation jumping
|
|
62
|
+
to show an older message.
|
|
63
|
+
|
|
64
|
+
- **An attachment's remove button is always there on a phone.** It sat under a
|
|
65
|
+
wash over the whole square, revealed on `:hover`, and there is nothing to
|
|
66
|
+
hover with — so it was drawn, in the accessibility tree, present to every
|
|
67
|
+
test that asks whether it is *there*, and unreachable. Under
|
|
68
|
+
`@media (hover: none)` it is a button in the top-right corner instead:
|
|
69
|
+
measured 28×28, 4px in from the corner of the 64px square, and a press at its
|
|
70
|
+
middle lands on it. The wash stays on a pointer, where it is the better
|
|
71
|
+
answer.
|
|
72
|
+
|
|
73
|
+
- **A picture attachment was never actually drawn.** `background-image`
|
|
74
|
+
computed to `none` in both engines: the URL went into an unquoted `url()`,
|
|
75
|
+
and a bracket inside it closes the `url()` early, so the browser threw the
|
|
76
|
+
whole declaration away with no error anywhere. An SVG data URL says
|
|
77
|
+
`fill="url(#g)"`, and `encodeURIComponent` does not touch brackets — so the
|
|
78
|
+
stories showed a row of blank squares under the heading "pictures — they show
|
|
79
|
+
themselves" for as long as they had existed, and any host whose image URL
|
|
80
|
+
contains a bracket had the same.
|
|
81
|
+
|
|
82
|
+
- **The reply thread stays on the screen.** It hung off the phrase from a fixed
|
|
83
|
+
top and grew downwards with nothing stopping it: at 390×844 with a passage
|
|
84
|
+
556px down, the panel ended at 920 after one reply — 76px past the bottom
|
|
85
|
+
edge, and every message after it pushed more out of reach. `placePanel`
|
|
86
|
+
returns a `maxHeight` now and stops following a phrase down once there would
|
|
87
|
+
be less than 260px left. A window short enough had this at any width.
|
|
88
|
+
|
|
89
|
+
- **The disclosure row's fades work at any height.** They required
|
|
90
|
+
`clientHeight >= 630`, the thread's old fixed ceiling written again as a
|
|
91
|
+
number, so with the height coming from the screen they would have stopped
|
|
92
|
+
appearing exactly where they are most needed — the short panel is the one
|
|
93
|
+
whose content does not fit.
|
|
94
|
+
|
|
95
|
+
- **`transition: all` on the glass button.** 400ms, on what the composer's mic,
|
|
96
|
+
plus and send are made of, and the X on an attachment. Among the properties
|
|
97
|
+
it was animating: `backdrop-filter`, a blur re-composited every frame for
|
|
98
|
+
four hundred milliseconds, and the same blur before and after. Named now —
|
|
99
|
+
colour and shadow at 200ms, the lift at 160ms.
|
|
100
|
+
|
|
101
|
+
- **`:hover` no longer moves anything on a touchscreen.** The glass button's
|
|
102
|
+
lift and the artifact card's were ungated, and a touchscreen fires a hover on
|
|
103
|
+
tap and leaves it there until the next tap somewhere else — so they stood a
|
|
104
|
+
pixel or two high for as long as you did not touch anything. Both are inside
|
|
105
|
+
`@media (hover: hover) and (pointer: fine)`. The colour and shadow stay on
|
|
106
|
+
touch: those say "this is the one under your finger", which is true.
|
|
107
|
+
|
|
108
|
+
### Added
|
|
109
|
+
|
|
110
|
+
- **A bottom sheet on a phone, for the reply thread.** Below 760px it comes up
|
|
111
|
+
from the bottom instead of hanging off the phrase, with a grabber and a drag
|
|
112
|
+
to put it away — the same sheet the artifact pane becomes, with the same
|
|
113
|
+
tokens. Capping its height alone would have turned it into a letterbox: 289px
|
|
114
|
+
of thread in the lower third, under a phrase you can no longer see. It grows
|
|
115
|
+
from the bottom edge with the thread rather than standing at a fixed 88%,
|
|
116
|
+
because one reply is what a thread usually has and a sheet that is mostly
|
|
117
|
+
white reads as something that failed to load.
|
|
118
|
+
|
|
119
|
+
- **The type scale rises on a touch device, and only where it earns it.**
|
|
120
|
+
`@media (pointer: coarse)` puts the prose and anything you type into at 16px
|
|
121
|
+
and caps everything else at 14. iOS zooms the page in to any editable whose
|
|
122
|
+
text computes under 16px and does not zoom back out; the answer is to stop
|
|
123
|
+
being under 16px rather than to fight the viewport.
|
|
124
|
+
|
|
125
|
+
| | before | after | mouse |
|
|
126
|
+
| --- | --- | --- | --- |
|
|
127
|
+
| prose | 12px | 16px | 12px |
|
|
128
|
+
| composer | 12px | 16px | 12px |
|
|
129
|
+
| header, chips | 13–14px | 14px | 13–14px |
|
|
130
|
+
|
|
131
|
+
- **`--ick-field-size`** — the size text somebody types into draws at, which is
|
|
132
|
+
not the same idea as a step on the type scale even when they share a number.
|
|
133
|
+
`max(1rem, …)` under a coarse pointer: a floor, not an override, so a theme
|
|
134
|
+
at 18px keeps 18 and a theme at 13 gets 16.
|
|
135
|
+
|
|
136
|
+
- **`--ick-icon`** — icons sized from the text beside them. Around fifty
|
|
137
|
+
`size={13..16}` call sites were fixed pixels, so raising the text left every
|
|
138
|
+
one behind. `1.15em` on touch, and `auto` elsewhere, which on an inline SVG
|
|
139
|
+
means its own width and height attributes — so a pointer keeps exactly the
|
|
140
|
+
sizes the call sites asked for.
|
|
141
|
+
|
|
142
|
+
- **`--ick-control-xs` … `--ick-control-xl`** — `Button`'s five heights, as
|
|
143
|
+
tokens. They were pixels in its stylesheet, flattered 13px text, and are
|
|
144
|
+
tight around 14: the tool row was 16px of text in a 26px box. 28/32/38/44/52
|
|
145
|
+
on touch against 24/28/32/40/48. The look changes; the hit area does not —
|
|
146
|
+
that is `--ick-touch-target`, and it has always been 44px without anything
|
|
147
|
+
moving.
|
|
148
|
+
|
|
149
|
+
- **`tail` on `<Conversation>`** — `"auto"` or a number of pixels. See
|
|
150
|
+
**Fixed**, and **Breaking** for what it supersedes.
|
|
151
|
+
|
|
152
|
+
- **`npm run reach-check`** — what a thumb can reach on a device with no hover,
|
|
153
|
+
in both engines at 390×844, against Storybook rather than the demo (the demo
|
|
154
|
+
holds one attachment; the question needs two side by side, because these
|
|
155
|
+
buttons grow an invisible 44px box under `pointer: coarse` and two of those
|
|
156
|
+
6px apart would have the wrong one answering).
|
|
157
|
+
|
|
158
|
+
- **`npm run follow-check:tall`** — the same follow check in a 1400px window.
|
|
159
|
+
The short window made every answer taller than the screen, which is the one
|
|
160
|
+
regime where two different numbers agree, so an assertion that there is
|
|
161
|
+
nowhere further to scroll passed while being unable to fail.
|
|
162
|
+
|
|
163
|
+
- **`motionRules.test.ts`** — two rules read out of the stylesheets, because
|
|
164
|
+
neither is visible on screen: no `transition: all`, and no `:hover` that
|
|
165
|
+
moves something outside a pointer query. A screenshot is taken with no
|
|
166
|
+
pointer, so the second one can never appear in a picture.
|
|
167
|
+
|
|
168
|
+
### Changed
|
|
169
|
+
|
|
170
|
+
- **`zoom-check` measures font sizes rather than a proxy for them.** It used to
|
|
171
|
+
assert that `maximum-scale=1` was in place at the instant focus landed, for a
|
|
172
|
+
hook that locked the host's viewport while a field had focus. No engine
|
|
173
|
+
outside a real iPhone implements zoom-on-focus, so the check observed a stand-in
|
|
174
|
+
and twice went green over a version that still zoomed. A font size is the
|
|
175
|
+
input to the platform's own rule, is the same number in every engine, and is
|
|
176
|
+
either under 16 or not.
|
|
177
|
+
|
|
178
|
+
### Breaking
|
|
179
|
+
|
|
180
|
+
- **`endOffset` on `<Conversation>` now means what it says.** `endOfContent`
|
|
181
|
+
added the content wrapper's `offsetTop` to the last turn's, and both are
|
|
182
|
+
measured from the same positioned ancestor — so it counted the viewport's
|
|
183
|
+
padding twice and every value was effectively `endOffset + padding`. If you
|
|
184
|
+
set it, the composer will now rest closer to the bottom edge than it did by
|
|
185
|
+
exactly your conversation's top padding. **What to do:** raise `endOffset` by
|
|
186
|
+
that padding to keep what you had. The demo went from `24` to `120`.
|
|
187
|
+
|
|
188
|
+
- **`--ick-conversation-tail` is written by the component.** `<Conversation>`
|
|
189
|
+
measures the room its anchor needs and sets the token on the content wrapper,
|
|
190
|
+
which beats a value set in a stylesheet. **What to do:** nothing, if you were
|
|
191
|
+
not setting it. If you were, pass `tail={<px>}` instead — a number takes the
|
|
192
|
+
measurement over, and the token remains the value used before the first
|
|
193
|
+
measurement.
|
|
194
|
+
|
|
195
|
+
- **The type scale is different on a touch device.** Nothing renamed and
|
|
196
|
+
nothing removed, but a phone or tablet now draws the prose and the composer
|
|
197
|
+
at 16px, the rest at 14, icons at `1.15em`, and controls 4–8px taller. **What
|
|
198
|
+
to do:** nothing, unless you want the old sizes, in which case re-declare the
|
|
199
|
+
`--ick-text-*`, `--ick-answer-size`, `--ick-composer-size`, `--ick-field-size`,
|
|
200
|
+
`--ick-icon` and `--ick-control-*` tokens inside your own
|
|
201
|
+
`@media (pointer: coarse)` block.
|
|
202
|
+
|
|
203
|
+
## 0.53.0 — 2026-09-03
|
|
204
|
+
|
|
205
|
+
### Changed
|
|
206
|
+
|
|
207
|
+
- **The artifact card moves instead of changing colour.** Pointing at it lifted
|
|
208
|
+
nothing and washed it grey — `--ick-inset`, ink at about 4% — while the card
|
|
209
|
+
sits on a ground that is ink at 5%. So the hover slid the card *towards* the
|
|
210
|
+
colour of the thing it is meant to be raised off, and it read as smudged
|
|
211
|
+
rather than raised. It now travels 2px up under the pointer with the shadow
|
|
212
|
+
thrown further, and 1px down under the press, and its surface never changes.
|
|
213
|
+
|
|
214
|
+
Measured, in both themes: rest 0, hover −2, press +1, with the shadow's total
|
|
215
|
+
throw going 28 → 47 → 9 and the background one value throughout. The press
|
|
216
|
+
did not work in the first cut — `.card:active` lost to
|
|
217
|
+
`.artifact:not([data-open]) .card:hover` four specificity points to two, so
|
|
218
|
+
the card stayed lifted under the finger. A press that does nothing looks like
|
|
219
|
+
a press arriving a frame late, which is why this was found by the
|
|
220
|
+
measurement and not by looking.
|
|
221
|
+
|
|
222
|
+
- **Nothing is drawn for the open card any more.** It had a 2px ink ring. Next
|
|
223
|
+
to a whole pane holding the same document, that was a second answer to a
|
|
224
|
+
question already answered, and on its own it read as a selected row in a
|
|
225
|
+
list. What marks it now is that it stops lifting under the pointer — there is
|
|
226
|
+
nowhere for it to go — and `aria-expanded`, which says it outright to anybody
|
|
227
|
+
not using one. The card is the only `aria-expanded` in the kit without an
|
|
228
|
+
`aria-controls` beside it, because what it opens is not its to render.
|
|
229
|
+
|
|
230
|
+
- **The pane opens and closes instead of appearing.** `ChatLayout` rendered the
|
|
231
|
+
pane the instant it had one: the conversation jumped narrower, the pane was
|
|
232
|
+
simply there, and closing it was the same in reverse. Both now animate, and
|
|
233
|
+
the two halves are deliberately not the same — the room opens flat and the
|
|
234
|
+
pane arrives with a spring in it, because `width` is what the conversation is
|
|
235
|
+
laid out against and any overshoot there re-wraps every line of the answer
|
|
236
|
+
twice on the way past. Leaving is quicker and in the other order.
|
|
237
|
+
|
|
238
|
+
The document inside is laid out **once**, at the width it ends up at, and the
|
|
239
|
+
slot uncovers it: measured at 369.59px held across every frame of an entrance
|
|
240
|
+
in which the room travelled 409px and the conversation followed it.
|
|
241
|
+
|
|
242
|
+
### Added
|
|
243
|
+
|
|
244
|
+
- **`--ick-shadow-float-lift` and `--ick-shadow-float-press`** — the same card
|
|
245
|
+
pointed at, and pressed. Stated per theme like `--ick-shadow-float`, and for
|
|
246
|
+
the same reason: a dark shadow has more work to do on a near-black page and
|
|
247
|
+
still must not cut the card out of it.
|
|
248
|
+
|
|
249
|
+
- **`npm run showcase:artifact`** — records the card and the pane, and measures
|
|
250
|
+
what the recording is meant to show. The numbers are the half that is not
|
|
251
|
+
visible at speed.
|
|
252
|
+
|
|
253
|
+
### Breaking
|
|
254
|
+
|
|
255
|
+
- **`--ick-artifact-surface-hover`, `--ick-artifact-open-edge` and
|
|
256
|
+
`--ick-artifact-open-edge-width` are gone.** They were the grey hover fill
|
|
257
|
+
and the open card's ring, neither of which is drawn any more. If you were
|
|
258
|
+
overriding them, there is nothing to override; the hover is now
|
|
259
|
+
`--ick-shadow-float-lift` and the open state draws nothing.
|
|
260
|
+
|
|
261
|
+
- **`--ick-artifact-pane-width` and `--ick-artifact-pane-width-wide` are in
|
|
262
|
+
`cqi`, not `%`.** `max(360px, 33cqi)` rather than `max(360px, 33%)`. The
|
|
263
|
+
slot's own width is what animates, so a percentage of it would be a
|
|
264
|
+
percentage of a number moving from zero — the pane would re-wrap its text on
|
|
265
|
+
every frame of its own entrance. Container units are measured off
|
|
266
|
+
`ChatLayout`, which is not moving. Same numbers in the same places: measured
|
|
267
|
+
at 370px in a 1120px layout and 475px in a 1440px one, unchanged from before.
|
|
268
|
+
|
|
269
|
+
If you place an `ArtifactPane` **without** `ChatLayout`, give its parent
|
|
270
|
+
`container-type: inline-size` — outside a container, `cqi` falls back to the
|
|
271
|
+
viewport.
|
|
272
|
+
|
|
273
|
+
## 0.52.1 — 2026-09-03
|
|
274
|
+
|
|
275
|
+
### Fixed
|
|
276
|
+
|
|
277
|
+
- **The composer's editor was 16px wider than the box that clips it.**
|
|
278
|
+
`.editor` had `width: 100%` and 8px of padding either side with no
|
|
279
|
+
`box-sizing`, so its border box overflowed `.editorWrap` — which clips —
|
|
280
|
+
and the right edge of the text was cut. `scrollWidth` 292 against
|
|
281
|
+
`clientWidth` 276 on a 276px composer.
|
|
282
|
+
|
|
283
|
+
This kit has no global `box-sizing` reset on purpose, because a reset inside
|
|
284
|
+
a CSS module leaks into the host page; the rule is that every padded box
|
|
285
|
+
states its own. This one had not, and it had been that way unnoticed.
|
|
286
|
+
|
|
287
|
+
Found by the new visual QA pass on its first run, which reported it on 25
|
|
288
|
+
separate stories.
|
|
289
|
+
|
|
290
|
+
## 0.52.0 — 2026-09-02
|
|
291
|
+
|
|
292
|
+
### Added
|
|
293
|
+
|
|
294
|
+
- **`--ick-tray` and `--ick-tray-thumb`** — a segmented control's recessed
|
|
295
|
+
track and the raised thumb in it. Every chat has one somewhere, and until now
|
|
296
|
+
the demo carried the colours in variables of its own, so the control had no
|
|
297
|
+
background anywhere else.
|
|
298
|
+
|
|
299
|
+
The thumb has to be **lighter than its tray in both themes**, and the two get
|
|
300
|
+
there from opposite ends: in light it goes all the way to paper, in dark pure
|
|
301
|
+
white is too loud so it is a wash of ink instead. Stated per theme rather
|
|
302
|
+
than mixed, because a mix cannot reverse.
|
|
303
|
+
|
|
304
|
+
Measured from painted pixels rather than declared values — with alpha
|
|
305
|
+
involved, `getComputedStyle` reports what was written, not what was drawn:
|
|
306
|
+
|
|
307
|
+
| | tray | thumb | |
|
|
308
|
+
| --- | --- | --- | --- |
|
|
309
|
+
| light | `rgb(236)` | `rgb(253)` | 1.16:1 |
|
|
310
|
+
| dark | `rgb(36)` | `rgb(72)` | 1.70:1 |
|
|
311
|
+
|
|
9
312
|
## 0.51.0 — 2026-09-02
|
|
10
313
|
|
|
11
314
|
### Added
|
package/README.md
CHANGED
|
@@ -10,6 +10,42 @@ answers you can draw on with a marker,
|
|
|
10
10
|
syntax-highlighted code blocks, a scroll container that keeps up with an
|
|
11
11
|
answer, and reply-in-thread popups.
|
|
12
12
|
|
|
13
|
+
## Where it is meant to run
|
|
14
|
+
|
|
15
|
+
**Desktop and tablet.** That is what this is designed for, tuned for, and what
|
|
16
|
+
the checks measure.
|
|
17
|
+
|
|
18
|
+
It runs on a phone and does not break. The artifact pane becomes a bottom
|
|
19
|
+
sheet, so does a reply thread; every control is reachable by a thumb; nothing
|
|
20
|
+
zooms when a field takes focus; the type scale rises so reading text is 16px
|
|
21
|
+
and the icons rise with it. There are guards for each of those, in two engines,
|
|
22
|
+
at 390×844 — `npm run zoom-check` and `npm run reach-check`.
|
|
23
|
+
|
|
24
|
+
**But it is not tuned for a phone, and the inline idea itself is weaker there.**
|
|
25
|
+
Worth saying plainly rather than leaving you to discover it.
|
|
26
|
+
|
|
27
|
+
The argument for an inline composer is that you see your question and its
|
|
28
|
+
answer at once, because the thing you type into *is* the message. A phone gives
|
|
29
|
+
you about half a screen once the keyboard is up — and the browser will not tell
|
|
30
|
+
you how much. So the platform takes away the one thing the design exists for.
|
|
31
|
+
|
|
32
|
+
Four more that no amount of CSS reaches:
|
|
33
|
+
|
|
34
|
+
| | |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| the keyboard's height | the browser does not report it reliably; a native scroll view is simply told |
|
|
37
|
+
| text selection | the system's own selection UI fights a marker drawn in prose — this kit ships a selection-mode toggle *because* of that |
|
|
38
|
+
| scroll physics | rubber-banding and momentum belong to the browser, not to you |
|
|
39
|
+
| haptics | no API at all on iOS Safari |
|
|
40
|
+
|
|
41
|
+
If you need a phone-first chat, treat this as a specification for one rather
|
|
42
|
+
than an implementation of one. The parts that would carry over are the ones
|
|
43
|
+
with no DOM in them — `useChatTurns`, `turnParts`, `placePanel` — and they are
|
|
44
|
+
plain TypeScript on purpose.
|
|
45
|
+
|
|
46
|
+
Desktop **applications** are a different story: an Electron or Tauri renderer
|
|
47
|
+
is a browser, so this works there today with nothing changed.
|
|
48
|
+
|
|
13
49
|
## Install
|
|
14
50
|
|
|
15
51
|
```bash
|
|
@@ -32,7 +32,14 @@ export interface ArtifactCardProps extends Omit<HTMLAttributes<HTMLDivElement>,
|
|
|
32
32
|
lines?: number;
|
|
33
33
|
/** Open it. Without one the card is a record rather than a control. */
|
|
34
34
|
onOpen?: (id: string) => void;
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* True while its pane is the one on screen.
|
|
37
|
+
*
|
|
38
|
+
* Nothing is drawn for it. The pane standing beside the conversation is
|
|
39
|
+
* already the answer, and a ring on the card was a second one — so what
|
|
40
|
+
* marks it is that it no longer lifts under the pointer, and
|
|
41
|
+
* `aria-expanded`, which says it outright to anybody not using a pointer.
|
|
42
|
+
*/
|
|
36
43
|
open?: boolean;
|
|
37
44
|
}
|
|
38
45
|
/**
|
|
@@ -19,6 +19,15 @@ export interface ChatLayoutProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
19
19
|
* changes it belongs in the pane's own header, where somebody can find it.
|
|
20
20
|
*/
|
|
21
21
|
pane?: (state: PaneState) => ReactNode;
|
|
22
|
+
/**
|
|
23
|
+
* Close it — dragged down, or the conversation behind it pressed.
|
|
24
|
+
*
|
|
25
|
+
* Only used on a phone, where the pane is a sheet. Both of those are ways
|
|
26
|
+
* out that belong to the *layout* rather than to the pane: the pane owns its
|
|
27
|
+
* close button, the layout owns the gesture and the ground around it. Leave
|
|
28
|
+
* it out and the sheet has neither, which is a sheet a thumb cannot dismiss.
|
|
29
|
+
*/
|
|
30
|
+
onDismiss?: () => void;
|
|
22
31
|
}
|
|
23
32
|
/**
|
|
24
33
|
* Where the pane goes, decided once.
|
|
@@ -32,4 +41,4 @@ export interface ChatLayoutProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
32
41
|
* What is left open is the part that actually differs — what is *in* the pane.
|
|
33
42
|
* See `ArtifactPane`.
|
|
34
43
|
*/
|
|
35
|
-
export declare function ChatLayout({ children, pane, className, ...rest }: ChatLayoutProps): import("react").JSX.Element;
|
|
44
|
+
export declare function ChatLayout({ children, pane, onDismiss, className, ...rest }: ChatLayoutProps): import("react").JSX.Element;
|
|
@@ -30,6 +30,57 @@ export interface ConversationProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
30
30
|
* neatly underneath and out of sight.
|
|
31
31
|
*/
|
|
32
32
|
anchorOffset?: number;
|
|
33
|
+
/**
|
|
34
|
+
* How much room is left under the last turn when the view is at the end, in
|
|
35
|
+
* pixels.
|
|
36
|
+
*
|
|
37
|
+
* Zero puts the last line flush with the bottom edge, which is right for a
|
|
38
|
+
* transcript and wrong for this kit: the last turn *is* the composer — the
|
|
39
|
+
* input is the message — so flush means the thing you type into is jammed
|
|
40
|
+
* against the edge of the screen the moment an answer finishes. On a phone
|
|
41
|
+
* that is also where the browser's own chrome lives.
|
|
42
|
+
*/
|
|
43
|
+
endOffset?: number;
|
|
44
|
+
/**
|
|
45
|
+
* The empty room under the last turn, which is what lets a turn be brought
|
|
46
|
+
* to the top at all.
|
|
47
|
+
*
|
|
48
|
+
* An element cannot be scrolled to the top of a container that ends just
|
|
49
|
+
* below it — so `anchorId` only works if there is somewhere to scroll into.
|
|
50
|
+
* That room used to be a guess written in the host's stylesheet, `99vh`,
|
|
51
|
+
* and a guess is wrong in both directions at once: too much of it and the
|
|
52
|
+
* reader can scroll a whole screen into nothing (measured: 697px of blank
|
|
53
|
+
* in a 680px view, with the last turn above the top edge), too little and
|
|
54
|
+
* the newest message cannot reach the top.
|
|
55
|
+
*
|
|
56
|
+
* `"auto"` measures it instead:
|
|
57
|
+
*
|
|
58
|
+
* screen - anchorOffset - (the anchored turn's top to the end) - padding
|
|
59
|
+
*
|
|
60
|
+
* and the second term is the whole point. Measuring the **last turn's own
|
|
61
|
+
* height** instead — which this did first — leaves everything between the
|
|
62
|
+
* anchor and the end unaccounted for: the last turn is the composer, 44px
|
|
63
|
+
* of it, so an answer that already filled the screen still got 536px of
|
|
64
|
+
* room under it to scroll into. What the room is for is lifting the
|
|
65
|
+
* anchored turn to the top, and what stands between that turn and the
|
|
66
|
+
* bottom edge is the whole stack under it.
|
|
67
|
+
*
|
|
68
|
+
* Sized this way, the end of the scroll lands exactly where the anchored
|
|
69
|
+
* turn sits at the anchor. So the view comes to rest **at** the end of the
|
|
70
|
+
* scroll and there is nowhere further to go — the room is spent holding the
|
|
71
|
+
* turn up, not left over underneath. It shrinks as the answer grows, which
|
|
72
|
+
* is also why an answer does not have to be chased: the content grows and
|
|
73
|
+
* the room shrinks by the same pixel, so the scroll height does not move
|
|
74
|
+
* and neither does the turn at the top.
|
|
75
|
+
*
|
|
76
|
+
* Never below `endOffset`, so a turn taller than the screen still rests
|
|
77
|
+
* with the composer clear of the bottom edge instead of jammed against it.
|
|
78
|
+
*
|
|
79
|
+
* Zero unless this conversation anchors, because the room exists for
|
|
80
|
+
* anchoring. A number takes it over, in pixels; so does the
|
|
81
|
+
* `--ick-conversation-tail` token, which this writes.
|
|
82
|
+
*/
|
|
83
|
+
tail?: "auto" | number;
|
|
33
84
|
/** Switch the whole thing off and it is a plain scroll container. */
|
|
34
85
|
follow?: boolean;
|
|
35
86
|
/**
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the reply panel goes, as arithmetic.
|
|
3
|
+
*
|
|
4
|
+
* A function rather than ten lines inside the render, and the reason is that
|
|
5
|
+
* it could not otherwise be tested. The panel's geometry reaches the DOM
|
|
6
|
+
* through Motion's `animate` prop, and Motion animations never complete under
|
|
7
|
+
* jsdom — so a test that reads `panel.style.left` reads the `initial` value
|
|
8
|
+
* for ever, which is the anchor. The first guard written for this passed
|
|
9
|
+
* against the broken version because of it.
|
|
10
|
+
*/
|
|
11
|
+
export interface Anchor {
|
|
12
|
+
left: number;
|
|
13
|
+
top: number;
|
|
14
|
+
width: number;
|
|
15
|
+
}
|
|
16
|
+
export interface Placement {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
width: number;
|
|
20
|
+
/**
|
|
21
|
+
* The room it has between where it starts and the bottom of the screen.
|
|
22
|
+
*
|
|
23
|
+
* The panel hangs off a phrase and grows downwards as the thread fills, and
|
|
24
|
+
* nothing used to stop it growing past the bottom edge. Measured on a 390px
|
|
25
|
+
* phone with a passage 556px down: after one reply the panel ended at 920
|
|
26
|
+
* in an 844px screen, and every message after that pushed more of it
|
|
27
|
+
* further out of reach. This is what it may use, so the thread scrolls
|
|
28
|
+
* inside it instead of running off the end of the world.
|
|
29
|
+
*/
|
|
30
|
+
maxHeight: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* **Fit first, then place.** That order is the whole of it.
|
|
34
|
+
*
|
|
35
|
+
* The version before this widened to 480 whatever the screen was, then ran two
|
|
36
|
+
* clamps in a row: push the left edge in to the padding, then push the right
|
|
37
|
+
* edge in to `screenWidth - padding`. For a box that does not fit, the second
|
|
38
|
+
* always undoes the first — on a 390px phone it finished at `390 - 24 - 480`,
|
|
39
|
+
* so the panel sat at **x = −114** with its heading off the side of the world.
|
|
40
|
+
* Clamping a box into a space smaller than itself has no answer; the box has
|
|
41
|
+
* to be made smaller first.
|
|
42
|
+
*/
|
|
43
|
+
export declare function placePanel(anchor: Anchor, screenWidth: number, screenHeight?: number): Placement;
|