@konce-pt/angular 0.8.3 → 0.8.5
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 +380 -0
- package/chat/src/lib/llms.txt +160 -0
- package/fesm2022/konce-pt-angular-chat.mjs +1270 -0
- package/fesm2022/konce-pt-angular-chat.mjs.map +1 -0
- package/fesm2022/konce-pt-angular.mjs +1755 -398
- package/fesm2022/konce-pt-angular.mjs.map +1 -1
- package/package.json +15 -8
- package/src/lib/backdrop/llms.txt +163 -0
- package/src/lib/chip/llms.txt +8 -0
- package/src/lib/data-view/llms.txt +18 -2
- package/src/lib/icon-button/llms.txt +1 -0
- package/src/lib/mention/llms.txt +60 -0
- package/src/lib/order-list/llms.txt +13 -2
- package/src/lib/peek/llms.txt +45 -0
- package/src/lib/pick-list/llms.txt +18 -2
- package/src/lib/rich-text/llms.txt +15 -0
- package/types/konce-pt-angular-chat.d.ts +334 -0
- package/types/konce-pt-angular-chat.d.ts.map +1 -0
- package/types/konce-pt-angular.d.ts +454 -18
- package/types/konce-pt-angular.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,385 @@
|
|
|
1
1
|
# @konce-pt/angular
|
|
2
2
|
|
|
3
|
+
## 0.8.5
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f615e93: Nowe wejście `adaptive` w `KptBackdrop`: barwy wpisane na sztywno przenoszą się na powierzchnię
|
|
8
|
+
bieżącego motywu.
|
|
9
|
+
|
|
10
|
+
Literał z założenia stoi w miejscu i tak ma być — `#5b8af2` to barwa marki, a nie sugestia.
|
|
11
|
+
Ale sekcja z `color="#000000"` na `background="#ffffff"` w motywie ciemnym zostawała białą kartą
|
|
12
|
+
wyciętą w ciemnej stronie, razem z treścią dobraną do tamtego tła. Do tej pory jedynym wyjściem
|
|
13
|
+
było przepisanie wejść na tokeny; teraz wystarczy `[adaptive]="true"`.
|
|
14
|
+
|
|
15
|
+
Adaptacja przenosi `color`, `colors` i `background` na powierzchnię bieżącego motywu, zachowując
|
|
16
|
+
odległość każdej barwy od tej powierzchni: czerń dobrana do bieli wychodzi bielą na tle
|
|
17
|
+
`neutral-950`, a białe tło wychodzi w jasności samej powierzchni. Rusza wyłącznie jasność — `a`
|
|
18
|
+
i `b` w OKLab zostają nietknięte, więc odcień i nasycenie przeżywają i barwa marki nie zmienia się
|
|
19
|
+
w inną barwę, tylko dobiera jasność do tła. Jasność wypychająca wynik poza sRGB zostaje przycięta
|
|
20
|
+
w drodze powrotnej, co kosztuje około trzech stopni odcienia i dziesiątą część nasycenia; za tłem
|
|
21
|
+
sekcji to niewidoczne, a własne mapowanie gamutu byłoby kosztem bez pokrycia.
|
|
22
|
+
|
|
23
|
+
Rusza wyłącznie literały. Puste wejście bierze token, a zapis przez `var()` też sięga po token —
|
|
24
|
+
jedno i drugie już idzie za motywem i adaptacja odwróciłaby je drugi raz, czyli z powrotem.
|
|
25
|
+
Punktem odniesienia jest motyw jasny, bo biblioteka trzyma jasny w `:root`, a ciemny jako
|
|
26
|
+
nadpisanie; to jedyne założenie, jakie ta zamiana robi.
|
|
27
|
+
|
|
28
|
+
W rdzeniu doszły `adaptLightness()` i `adaptToSurface()` — czysta arytmetyka z testami
|
|
29
|
+
`node --test` — oraz `readBackdropSurface()` i `adaptBackdropValue()` spinające je z drzewem.
|
|
30
|
+
Strażnik przebudowy liczy teraz kolor wiodący tą samą drogą co budowa; inaczej porównywałby
|
|
31
|
+
wartość sprzed adaptacji z zapamiętaną po adaptacji i przebudowywał renderer bez końca.
|
|
32
|
+
|
|
33
|
+
- f615e93: Sekcja z własnym tłem dobiera kolor treści do tego tła i ogłasza ton atrybutem `data-tone`.
|
|
34
|
+
|
|
35
|
+
Host malował `--kpt-color-surface` i zostawiał treści `--kpt-color-on-surface`. To jest słuszne,
|
|
36
|
+
dopóki sekcja nie dostanie własnego `background` — wtedy ten token opisuje stronę dookoła, a nie
|
|
37
|
+
to, na czym treść naprawdę leży. Sekcja z ciemnym tłem w motywie jasnym miała `color` prawie czarny
|
|
38
|
+
na prawie czarnym tle, czyli treść niewidoczną. Każdy konsument musiał to obchodzić sam; demo
|
|
39
|
+
playgroundu obchodziło, licząc jasność tła po swojej stronie, więc błąd nie był widoczny akurat
|
|
40
|
+
tam, gdzie się go szuka.
|
|
41
|
+
|
|
42
|
+
Teraz komponent liczy to sam: `color` na hoście idzie pod tło, które sekcja naprawdę maluje,
|
|
43
|
+
a wynik wychodzi na zewnątrz jako `data-tone` (`light` przy jasnym tle, `dark` przy ciemnym).
|
|
44
|
+
Napis bierze kolor dziedziczeniem, a to, czemu sam kolor tekstu nie wystarcza — przyciski i inne
|
|
45
|
+
komponenty w sekcji — podwiesza własne tokeny pod atrybut:
|
|
46
|
+
|
|
47
|
+
:where(kpt-backdrop[data-tone='light']) .hero { --kpt-button-filled-bg: #15181a; }
|
|
48
|
+
|
|
49
|
+
Ton liczy się po adaptacji, więc pod `adaptive` zgadza się z tłem malowanym, a nie z wpisanym
|
|
50
|
+
w szablonie. Sekcja bez własnego `background` nie ogłasza nic i treść zostaje na
|
|
51
|
+
`--kpt-color-on-surface` — dla niej nic się nie zmienia.
|
|
52
|
+
|
|
53
|
+
O kontraście rozstrzyga jasność względna WCAG (`relativeLuminance`, `prefersDarkContent`
|
|
54
|
+
w rdzeniu, z testami), a nie jasność OKLab: pierwsza mówi, ile z barwy wychodzi światła, druga jak
|
|
55
|
+
jasno barwa wygląda, i tylko na pierwszej stoi rachunek kontrastu.
|
|
56
|
+
|
|
57
|
+
Playground dostał przełącznik „Idź za motywem" dla `adaptive` i przestał liczyć jasność tła po
|
|
58
|
+
swojej stronie — podpina się pod `data-tone` komponentu, więc jedna polityka zamiast dwóch.
|
|
59
|
+
|
|
60
|
+
- f615e93: Rampa `colors` w `KptBackdrop` przyjmuje dowolny zapis CSS, nie tylko heksadecymalny.
|
|
61
|
+
|
|
62
|
+
Wstęga miesza barwy po biegunowej postaci OKLab, ale dostawały się tam dotąd wyłącznie przystanki
|
|
63
|
+
sparsowane przez `parseHexColor` — czyli `#rgb` i `#rrggbb`. `oklch()`, `color-mix()`, `rgb()`,
|
|
64
|
+
nazwy własne i `var(--kpt-*)` wypadały z rampy, a gdy wypadły wszystkie, renderer schodził do
|
|
65
|
+
`IDLE` i **kadr zostawał pusty bez żadnego sygnału**. Tym dotkliwiej, że rampa domyślna — ta,
|
|
66
|
+
której się nie podaje — od początku szła przez `readCssColor` i rozumiała każdy zapis, bo tokeny
|
|
67
|
+
biblioteki są zapisane w OKLCH. Podana rampa miała gorszy parser niż niepodana.
|
|
68
|
+
|
|
69
|
+
Teraz obie idą tą samą drogą: o znaczenie każdego przystanku pytamy przeglądarkę, a do renderera
|
|
70
|
+
trafia już sam hex — jego kontrakt się nie zmienia. Przystanek, którego przeglądarka nie przyjmuje,
|
|
71
|
+
wypada z rampy; gdy wypadną wszystkie, wchodzi rampa wyprowadzona z koloru wiodącego, zamiast gasić
|
|
72
|
+
sekcję. Najczęstsza pomyłka, `var(--kpt-color-primary)` w rampie, i tak celuje w ten właśnie kolor,
|
|
73
|
+
więc użytkownik dostaje niemal to, o co mu chodziło, plus jedno ostrzeżenie w konsoli w buildzie
|
|
74
|
+
dev. Wartość alfa wypada — tablica barw wstęgi jest RGB.
|
|
75
|
+
|
|
76
|
+
`readCssColor()` zwraca `null` na wartości, której przeglądarka nie uznaje za kolor. Dotąd dawała
|
|
77
|
+
czerń, bo `fillStyle` odrzuca taki zapis po cichu i zostaje przy poprzednim — nie dało się odróżnić
|
|
78
|
+
śmiecia od legalnej czerni ani o czymkolwiek ostrzec. Rozstrzyga to para różnych wartowników: przy
|
|
79
|
+
wartości nieprawidłowej każdy zostaje na swoim miejscu i oba przebiegi się rozchodzą. Przy okazji
|
|
80
|
+
sonda jest czyszczona przed każdym odczytem — barwa z kanałem alfa mieszała się z tym, co zostawiło
|
|
81
|
+
poprzednie wywołanie.
|
|
82
|
+
|
|
83
|
+
- f615e93: Wejścia koloru `KptBackdrop` rozumieją `var()`, więc barwa podana w szablonie może iść za motywem.
|
|
84
|
+
|
|
85
|
+
Kolor wiodący był dotąd brany z wejścia bez zmian, a sonda 1×1 jest oderwana od drzewa — zapis
|
|
86
|
+
`color="var(--kpt-color-primary)"` nie miał prawa zadziałać i lądował w ostrzeżeniu. Zamrożenie
|
|
87
|
+
literału jest poprawne (`#5b8af2` ma zostać `#5b8af2`), ale odcinało jedyny zapis, który sam
|
|
88
|
+
wiedziałby, co zrobić przy przełączeniu motywu: wyrażenie odwołujące się do tokenów.
|
|
89
|
+
|
|
90
|
+
Doszedł `resolveCssValue()`: wyrażenie trafia do własności-sondy na elemencie w drzewie, a z jego
|
|
91
|
+
wyliczonego stylu wraca już z podstawionym `var()`. Dalej idzie do `readCssColor`, bo `color-mix()`
|
|
92
|
+
zostaje w custom property niewyliczone, a canvas je wylicza. Dzięki temu
|
|
93
|
+
|
|
94
|
+
<kpt-backdrop color="color-mix(in oklab, #5b8af2, var(--kpt-color-surface) 30%)">
|
|
95
|
+
|
|
96
|
+
daje kwadraciki, które jaśnieją na jasnej powierzchni i ciemnieją na ciemnej, przy jednej wartości
|
|
97
|
+
w szablonie — przeliczane istniejącym już nasłuchem motywu. To samo działa dla przystanków rampy
|
|
98
|
+
`colors`. Biblioteka nie zgaduje przy tym, jaki kolor jest „odpowiedni" w drugim motywie: decyduje
|
|
99
|
+
autor wyrażenia. Zapis bez `var()` omija całą ścieżkę, więc literał nie płaci za nic.
|
|
100
|
+
|
|
101
|
+
Sonda musi pisać poza zasięgiem nasłuchu motywu, bo ten ogląda `style` hosta i jego przodków —
|
|
102
|
+
zapis na hoście budziłby nasłuch, ten sięgałby po kolor na nowo i pętla nie miałaby końca. Stąd
|
|
103
|
+
`resolveBackdropColors()` i `readBackdropLead()` dostają teraz canvas komponentu, a nie hosta;
|
|
104
|
+
tokeny czytają się z niego tak samo, bo `--_dot` dziedziczy się w dół.
|
|
105
|
+
|
|
106
|
+
Nazwa zmiennej, która nic nie daje, schodzi przy `color` do tokenu — tego chce autor literówki —
|
|
107
|
+
a przy `colors` wypada z rampy i trafia do istniejącego ostrzeżenia w buildzie dev.
|
|
108
|
+
|
|
109
|
+
- c1f8885: Nowe animowane tło `KptBackdrop` (`kpt-backdrop`) i pod nim nowa paczka `@konce-pt/backdrop` —
|
|
110
|
+
rdzeń pure-TS z testami `node --test`. Komponent jest opakowaniem sekcji: canvas leży pod spodem,
|
|
111
|
+
rzutowana treść warstwę wyżej i zachowuje się zwyczajnie, więc pod hero z nagłówkiem i przyciskami
|
|
112
|
+
nie trzeba układać niczego ręcznie.
|
|
113
|
+
|
|
114
|
+
Efekt `signal-grid` to drobna siatka kwadratów, które cicho migoczą — gęsta przy krawędzi wskazanej
|
|
115
|
+
przez `anchor`, wygasająca do pustki przy przeciwnej. Gradient bierze się z tego, **ile** kwadratów
|
|
116
|
+
się pali, a nie z przezroczystości nałożonej na równomierną siatkę: to różnica między czymś, co
|
|
117
|
+
czyta się jak sygnał, a fakturą z maską na wierzchu. Sterują tym `density` (udział zapalonych
|
|
118
|
+
komórek przy kotwicy) i `falloff` (jak szybko to gaśnie), a rozstawem `cell` i `gap`.
|
|
119
|
+
|
|
120
|
+
Każda komórka wylicza swoje losowania — czy się pali, fazę i tempo migotania — z własnej kolumny
|
|
121
|
+
i wiersza przez `kptHash01()`. Siatka nie może trzymać stanu między klatkami, bo przy zmianie
|
|
122
|
+
rozmiaru komórki wędrują i tablica `Math.random()` rozsypałaby cały obraz; przy losowaniu
|
|
123
|
+
z pozycji układ przeżywa zmianę rozmiaru okna zamiast tasować się od nowa. Tempa są rozstrzelone
|
|
124
|
+
w przedziale 0,45–1,55, bo przy jednym tempie cała siatka pulsuje zgodnie i zamiast migotania
|
|
125
|
+
wychodzi oddech. Siatka celowo wychodzi poza obszar, a nadmiar jest ucinany — siatka kończąca się
|
|
126
|
+
przed krawędzią wygląda na uciętą.
|
|
127
|
+
|
|
128
|
+
Rysowanie stoi, gdy nie ma czego oglądać: sekcja poza ekranem (`IntersectionObserver`), karta w tle,
|
|
129
|
+
ustawione `paused`, `speed` równe zeru albo `prefers-reduced-motion`, o który komponent pyta sam.
|
|
130
|
+
Zostaje wtedy jedna statyczna klatka ze zróżnicowaną jasnością, nie pustka — efekt jest częścią
|
|
131
|
+
kompozycji, nie ozdobą. `speed` wchodzi w zegar (`clock += krok * speed`), a nie w fazę, więc suwak
|
|
132
|
+
prędkości przyspiesza obraz zamiast go przeskakiwać.
|
|
133
|
+
|
|
134
|
+
Kolor kwadratów bierze się z `--kpt-color-primary`, tło z `--kpt-color-surface`; oba da się nadpisać
|
|
135
|
+
wejściami `color` i `background` albo własnościami `--kpt-backdrop-color` i `--kpt-backdrop-surface`,
|
|
136
|
+
bo sekcja promocyjna bywa w kolorach spoza palety aplikacji. Canvas nie rozwija `var()`, więc token
|
|
137
|
+
odczytuje się z wyliczonego stylu hosta i na nowo po zmianie motywu. Canvas nosi `aria-hidden="true"`
|
|
138
|
+
i `pointer-events: none` — to dekoracja bez treści i bez niczego do trafienia.
|
|
139
|
+
|
|
140
|
+
Rdzeń (`buildSignalGrid`, `signalGridAlpha`, `signalGridDensity`, `kptHash01`) jest re-eksportowany
|
|
141
|
+
z `@konce-pt/angular`, więc aplikacja nie instaluje paczki samodzielnie, a ten sam efekt da się
|
|
142
|
+
narysować własnym rendererem. Port Reacta dostanie komponent osobno.
|
|
143
|
+
|
|
144
|
+
- c1f8885: Drugi efekt tła — `wave` — i `KptBackdrop` w porcie Reacta.
|
|
145
|
+
|
|
146
|
+
`wave` to jedna miękka wstęga światła płynąca przez kadr, z barwami przesuwającymi się wzdłuż niej
|
|
147
|
+
i bez twardej krawędzi w całym obrazie. Linia środkowa jest sumą harmonicznych o niewspółmiernych
|
|
148
|
+
częstotliwościach, więc prąd faluje bez powtarzalnego wzoru; jasność opada od niej krzywą Gaussa
|
|
149
|
+
i to opadanie odróżnia prąd światła od paska. Nowe wejścia: `colors` (rampa barw), `rotation`
|
|
150
|
+
(kąt, pod jakim prąd przecina kadr — odpowiednik `anchor` z signal-grid, tyle że płynny), `band`
|
|
151
|
+
i `amplitude`. Wejście nienależące do bieżącego efektu po prostu nie jest czytane.
|
|
152
|
+
|
|
153
|
+
Rampa barw jest zamknięta w pętlę i mieszana po **biegunowej** postaci OKLab. Interpolacja sRGB
|
|
154
|
+
miesza wartości po gammie i wychodzi błotem, a nawet prostokątny OKLab przecina oś achromatyczną
|
|
155
|
+
dla przeciwnych odcieni: w połowie drogi z niebieskiego w żółty zostaje szarość dokładnie tam,
|
|
156
|
+
gdzie prąd świeci najmocniej. Odcień idzie krótszym łukiem, a przystanek bez nasycenia przejmuje
|
|
157
|
+
odcień sąsiada, bo `atan2` na zerowym nasyceniu zwraca kąt przypadkowy. Bez podanego `colors` rampą
|
|
158
|
+
jest `--kpt-color-primary` i dwie barwy wyprowadzone z niego obrotem odcienia — paleta biblioteki
|
|
159
|
+
jest neutralna, więc sięganie po tokeny statusowe do celów dekoracyjnych byłoby użyciem semantyki
|
|
160
|
+
nie na miejscu.
|
|
161
|
+
|
|
162
|
+
Wstęga liczy się w buforze o dłuższym boku 160 px i jest skalowana w górę: miękkość bierze się
|
|
163
|
+
z interpolacji przeglądarki, więc kilka tysięcy pikseli na klatkę obsługuje kadr dowolnej
|
|
164
|
+
wielkości. Krótszy bok ma osobną podłogę 72 próbek — przy proporcjonalnym skalowaniu szeroki kadr
|
|
165
|
+
miałby kilkadziesiąt wierszy i prąd obrócony blisko kąta prostego łamałby się właśnie wzdłuż tej
|
|
166
|
+
osi.
|
|
167
|
+
|
|
168
|
+
**Renderery przeniosły się do `@konce-pt/backdrop`.** Rysowanie nie potrzebuje frameworka, tylko
|
|
169
|
+
`CanvasRenderingContext2D`, więc `createBackdropRenderer()` (plus `resolveBackdropColors()`
|
|
170
|
+
i stałe cyklu życia) mieszka teraz w rdzeniu, a komponent w obu portach jest samym cyklem życia:
|
|
171
|
+
rAF, `ResizeObserver`, `IntersectionObserver`, zapytanie o ograniczony ruch i obserwator motywu.
|
|
172
|
+
Te same wejścia dają dzięki temu tę samą klatkę w Angularze i w Reakcie. Doszedł też
|
|
173
|
+
`readCssColor()` — tokeny są zapisane w OKLCH, a rampę trzeba wymieszać, nie tylko podać dalej,
|
|
174
|
+
więc o znaczenie zapisu pytamy przeglądarkę, malując kolor na canvasie 1×1.
|
|
175
|
+
|
|
176
|
+
`@konce-pt/react` dostaje `KptBackdrop` z kompletem propsów obu efektów, ten sam DOM i ten sam
|
|
177
|
+
arkusz `components.css`. Zmianę `colors` śledzi po treści tablicy, nie po jej tożsamości, więc
|
|
178
|
+
nowa tablica z renderu rodzica nie przebudowuje renderera.
|
|
179
|
+
|
|
180
|
+
- ce34ab3: Nowa rodzina `KptChat` w subpathu `@konce-pt/angular/chat`: `kpt-chat` (lista rozmowy),
|
|
181
|
+
`kpt-chat-message` (bąbel), `kpt-chat-typing` (wskaźnik pisania), `kpt-chat-composer` (pole
|
|
182
|
+
pisania) i `kpt-chat-dock` (pływający panel w rogu, zbudowany na `kpt-fab`). Subpath, nie rdzeń —
|
|
183
|
+
aplikacje bez czatu nie mają powodu pobierać docka razem z logiką breakpointów.
|
|
184
|
+
|
|
185
|
+
Nowa paczka `@konce-pt/chat` — rdzeń pure-TS z testami `node --test`. `buildChatRows()` zamienia
|
|
186
|
+
płaską listę wiadomości na wiersze do wyrenderowania: separatory dni, dzielnik nieprzeczytanych
|
|
187
|
+
i pozycję w grupie, z której wynika zapadnięty narożnik, ogonek, awatar i wiersz z godziną.
|
|
188
|
+
Grupa łamie się na zmianie autora, przerwie powyżej pięciu minut, granicy doby kalendarzowej,
|
|
189
|
+
wiadomości systemowej i dzielniku nieprzeczytanych. Doba liczona jest od północy do północy,
|
|
190
|
+
a nie odejmowaniem znaczników czasu — doba ze zmianą czasu ma 23 albo 25 godzin, więc „wczoraj
|
|
191
|
+
o 23:30" wypadałoby raz do roku o dzień obok.
|
|
192
|
+
|
|
193
|
+
Nieudana wysyłka umie o sobie powiedzieć: `status: 'failed'` obwodzi bąbel kolorem
|
|
194
|
+
`--kpt-color-danger-border` i stawia strzałkę ponowienia **poza** nim, po zewnętrznej stronie
|
|
195
|
+
listy. Taki bąbel pokazuje wiersz meta nawet w środku grupy — strzałka bez niczego, co ją
|
|
196
|
+
tłumaczy, byłaby zagadką. Pasek `[offline]` liczy wiadomości czekające na wysłanie przez
|
|
197
|
+
`pendingCount()`. Potwierdzenia z serwera przychodzą nie po kolei, więc `mergeStatus()` zostawia
|
|
198
|
+
wyższy stopień: spóźnione „dostarczone" nie cofnie `read`. Wyjątkiem jest `failed`, który wygrywa
|
|
199
|
+
zawsze — wiadomość odrzucona po wysłaniu nie może zostać na zawsze „wysłana".
|
|
200
|
+
|
|
201
|
+
Transport zostaje w aplikacji: komponent przyjmuje wiadomości i tylko zgłasza `(retry)`
|
|
202
|
+
oraz `(loadMore)`.
|
|
203
|
+
|
|
204
|
+
Bąble są rozłożone pod monochromatyczny motyw biblioteki: różnicę robi jasność, nie odcień.
|
|
205
|
+
Wiadomość przychodząca siada na `--kpt-color-muted`, własna domyślnie na pełnym
|
|
206
|
+
`--kpt-color-primary`. `--kpt-color-surface-variant` dzieli od tła listy 1,5% jasności, więc bąbel
|
|
207
|
+
przychodzący na nim po prostu znikał, a `--kpt-color-primary-subtle` ma dokładnie jasność
|
|
208
|
+
`--kpt-color-muted`, przez co własny i cudzy bąbel były nie do odróżnienia. Wariant `primary`
|
|
209
|
+
zostaje jako cichsza opcja dla własnych wiadomości; wariant `muted` zniknął, bo po tej zmianie
|
|
210
|
+
powielał `neutral`. Przyklejona pigułka separatora dnia przejeżdża nad wiadomościami, więc dostała
|
|
211
|
+
podniesione tło, ramkę i cień — bez nich zlewała się z bąblem, po którym właśnie jechała.
|
|
212
|
+
|
|
213
|
+
`kpt-scroll-top` przykleja się dokładnie w to samo miejsce co dock, z tym samym `--kpt-z-overlay`,
|
|
214
|
+
i połykał FAB czatu w całości — wystawała sama plakietka nieprzeczytanych, a przycisku nie dało się
|
|
215
|
+
kliknąć. Arkusz rozwiązuje to teraz sam: przy docku `bottom-end` przycisk powrotu na górę ustępuje
|
|
216
|
+
w bok o szerokość FAB-a. W bok, nie do góry, bo nad FAB-em stoi panel rozmowy — przesunięcie
|
|
217
|
+
w pionie zamieniłoby jedną kolizję na gorszą. Do tego `kpt-chat-dock` dostaje `offset` na wypadek
|
|
218
|
+
innych widgetów w tym rogu.
|
|
219
|
+
|
|
220
|
+
Słownik `@konce-pt/i18n` dostaje sekcję `chat` (EN i PL). Rdzeń zwraca **rodzaj** etykiety dnia
|
|
221
|
+
(`{ kind: 'today' }`), a nie napis — brzmienie należy do słownika, nazwa dnia tygodnia do `Intl`,
|
|
222
|
+
a paczka bez zależności nie ma po co wozić tłumaczeń.
|
|
223
|
+
|
|
224
|
+
Port Reacta dostaje subpath `@konce-pt/react/chat` z kompletem pięciu komponentów na tym samym
|
|
225
|
+
rdzeniu i tym samym arkuszu `components.css` — te same klasy `kpt-*` i te same atrybuty `data-*`,
|
|
226
|
+
więc oba porty renderują identycznie. `model()` z Angulara rozpada się na pary
|
|
227
|
+
controlled/uncontrolled (`value`/`defaultValue`/`onValueChange`, `open`/`defaultOpen`/`onOpenChange`),
|
|
228
|
+
a `<ng-template let-message>` na `renderMessage(message, row)`.
|
|
229
|
+
|
|
230
|
+
Zestaw wbudowany `@konce-pt/icons` rośnie z 86 do 92 ikon: `checks`, `paperclip`, `send`,
|
|
231
|
+
`message-circle`, `corner-up-left`, `mood-smile`. Bez `checks` nie dałoby się odróżnić
|
|
232
|
+
„dostarczono" od „wysłano", a komponenty biblioteki muszą działać bez `provideKptTablerIcons()`.
|
|
233
|
+
|
|
234
|
+
- 7626e71: `KptChip` dostaje wejście `textTransform` (`'none' | 'capitalize' | 'uppercase' | 'lowercase'`,
|
|
235
|
+
domyślnie `'none'`) w obu portach. Ląduje jako `data-text-transform` na `.kpt-chip` i tam ustawia
|
|
236
|
+
`text-transform`, więc etykieta zostaje w treści zapisana po swojemu, a o sposobie jej wyświetlenia
|
|
237
|
+
decyduje chip — pigułka nad nagłówkiem hero wygląda tak samo niezależnie od tego, czy tekst
|
|
238
|
+
przyszedł ze słownika, z CMS-a czy z API.
|
|
239
|
+
|
|
240
|
+
`capitalize` podnosi pierwszą literę każdego słowa — **także po dywizie** („Agent-Ready"), bo CSS
|
|
241
|
+
nie wie, gdzie kończy się wyraz złożony. Przy etykietach z myślnikiem pewniejszy jest zapis wprost
|
|
242
|
+
w treści; to samo ograniczenie ma każde `text-transform: capitalize`, a nie ta implementacja.
|
|
243
|
+
|
|
244
|
+
- a1c5468: `KptDataView` dostaje `pageSizeOptions`, a rozmiar strony staje się sterowalny — w Angularze
|
|
245
|
+
`pageSize` to teraz `model`, w React dochodzą `defaultPageSize` i `onPageSizeChange`. Do tej pory
|
|
246
|
+
komponent podawał paginatorowi `[pageSize()]` jako jedyną opcję, więc select „Wierszy" był martwą
|
|
247
|
+
kontrolką: pokazywał liczbę, której nie dało się zmienić. Pager zostaje widoczny także wtedy, gdy
|
|
248
|
+
wszystko mieści się na jednej stronie, o ile jest w czym wybierać — inaczej wybranie największego
|
|
249
|
+
rozmiaru chowałoby select, którego użytkownik właśnie użył.
|
|
250
|
+
|
|
251
|
+
Pasek paginatora w `KptDataView` znów stoi w jednym rzędzie po prawej. Opakowanie ustawiało
|
|
252
|
+
`.kpt-data-view .kpt-paginator { display: block }`, a selektor potomka bije `.kpt-paginator
|
|
253
|
+
{ display: flex }` z tej samej warstwy — trzy bloki paginatora rozpadały się na trzy linie.
|
|
254
|
+
|
|
255
|
+
`KptDataView` pozwala ustawić gęstość siatki: `gridColumns` (dwukierunkowe), `gridColumnsOptions`
|
|
256
|
+
i `gridMinWidth`. Przy więcej niż jednej opcji w układzie siatki pasek narzędzi dostaje drugą grupę
|
|
257
|
+
segmentową z ikoną `grid-dots`. Wybrana liczba jest sufitem, nie sztywną liczbą — minimum ścieżki
|
|
258
|
+
rośnie do szerokości jednej z N kolumn, więc wąski kontener schodzi niżej, zamiast ściskać karty
|
|
259
|
+
poniżej `gridMinWidth`. Do tej pory o gęstości decydował wyłącznie arkusz aplikacji, bo komponent
|
|
260
|
+
miał `minmax(10rem, 1fr)` wpisane na sztywno.
|
|
261
|
+
|
|
262
|
+
Przyciski przełącznika układu niosą wreszcie `aria-pressed` — czytnik ekranu nie ogłaszał, który
|
|
263
|
+
układ jest włączony.
|
|
264
|
+
|
|
265
|
+
Przycisk siatki w przełączniku układu rysuje `layout-grid` zamiast `menu`: hamburger to
|
|
266
|
+
semantycznie lista, nie siatka. `layout-grid` wchodzi do zestawu wbudowanego `KPT_ICONS`
|
|
267
|
+
(86 ikon, razem z `grid-dots` dla paska gęstości), bo komponent biblioteki musi działać bez `provideKptTablerIcons()`.
|
|
268
|
+
|
|
269
|
+
- 0daa856: Nowa karta podglądu `KptPeek` (`kpt-peek` + `kptPeekTrigger`) i na niej wzmianki w tekście:
|
|
270
|
+
`KptMention` (pojedyncza wzmianka) oraz `KptMentions` (akapit rozbity na treść i wzmianki).
|
|
271
|
+
Obie rzeczy w rdzeniu biblioteki, nie w subpathu — karta jest mała i przyda się wszędzie tam,
|
|
272
|
+
gdzie tooltip za mało niesie, a popover za dużo zasłania.
|
|
273
|
+
|
|
274
|
+
`kptTooltip` ma `pointer-events: none`, więc linku w jego środku nie da się kliknąć, a `kpt-popover`
|
|
275
|
+
otwiera dopiero klik i kładzie na stronie backdrop. `kpt-peek` stoi pomiędzy: hover otwiera kartę
|
|
276
|
+
po `openDelay`, zjazd kursora zamyka po `closeDelay` (najazd na kartę anuluje zamknięcie, bo kursor
|
|
277
|
+
musi mieć czas przejechać z triggera na kartę), a treść karty jest w pełni interaktywna i niczego
|
|
278
|
+
nie przykrywa. Fokus otwiera bez zwłoki, a klik, Enter albo dotyk **przypina** kartę: zostaje mimo
|
|
279
|
+
zjazdu kursora, a fokus wchodzi do środka. To jedyna droga do treści karty tam, gdzie hovera nie ma
|
|
280
|
+
w ogóle, więc interaktywna karta zostaje osiągalna na dotyku i z klawiatury. Escape zamyka i oddaje
|
|
281
|
+
fokus triggerowi.
|
|
282
|
+
|
|
283
|
+
Nowa paczka `@konce-pt/mention` — rdzeń pure-TS z testami `node --test`. `parseMentionText()`
|
|
284
|
+
rozbija akapit na treść i wzmianki (`@id` albo `@[Nazwa ze spacją](id)`), `mentionQuery()` mówi,
|
|
285
|
+
czy kursor stoi w zapytaniu `@…`, a `filterMentions()` dopasowuje podpowiedzi. Sklejenie segmentów
|
|
286
|
+
wraca do oryginału znak w znak — podgląd rozjeżdżający się z zapisaną treścią byłby gorszy niż brak
|
|
287
|
+
podglądu. `mail@example.com` wzmianką nie jest: `@` musi stać na początku tekstu albo po znaku,
|
|
288
|
+
który nie jest literą, cyfrą ani podkreśleniem. Dopasowanie zdejmuje diakrytyki, a litery z kreską
|
|
289
|
+
mapuje ręcznie — `ł` to osobny znak Unicode, nie `l` ze znakiem diakrytycznym, więc samo NFD
|
|
290
|
+
zostawiłoby „Łukasza" poza zasięgiem zapytania „lukasz".
|
|
291
|
+
|
|
292
|
+
W treści zapisuje się **identyfikator**, nie nazwa: `id` przeżyje zmianę nazwiska, a etykieta
|
|
293
|
+
bierze się z listy pozycji. Identyfikator spoza listy też się renderuje — etykietą zostaje wtedy on
|
|
294
|
+
sam, bo notatka ma dać się przeczytać także wtedy, gdy ktoś odszedł z zespołu. `kpt-mentions` nie
|
|
295
|
+
renderuje HTML-a: akapit wchodzi jako zwykły napis, a wzmianka jest elementem, który biblioteka sama
|
|
296
|
+
zbudowała — aplikacja nie musi przepuszczać przez sanitizer treści pisanej przez użytkowników tylko
|
|
297
|
+
po to, żeby wyszły z niej wzmianki. Bez szablonu karty wzmianka jest wyróżnionym słowem, a nie
|
|
298
|
+
przyciskiem: nie ma czego otworzyć, więc niczego nie udaje.
|
|
299
|
+
|
|
300
|
+
`kpt-rich-text` dostaje `mentions`, `mentionLimit` i `(mentioned)`: napisanie `@` otwiera listę
|
|
301
|
+
podpowiedzi zaczepioną w miejscu kursora, strzałki po niej chodzą, Enter albo Tab wstawia, Escape
|
|
302
|
+
zamyka. Zapytanie czytamy z węzła tekstowego przy kursorze, nie z HTML-a — w treści siedzą tagi,
|
|
303
|
+
a `@` ma widzieć tylko to, co naprawdę napisano w tym miejscu. Wstawiona wzmianka to
|
|
304
|
+
`<span class="kpt-mention" data-mention="id" contenteditable="false">` plus twarda spacja:
|
|
305
|
+
identyfikator przeżywa obieg przez wartość, `contenteditable="false"` trzyma wzmiankę w jednym
|
|
306
|
+
kawałku (bez tego kursor wchodzi w środek nazwiska), a spacja daje mu gdzie stanąć za blokiem,
|
|
307
|
+
w który nie da się wejść. Oba atrybuty weszły na allowlistę sanitizera w obu portach — są bierne,
|
|
308
|
+
nie wykonują niczego i nie wskazują zasobów.
|
|
309
|
+
|
|
310
|
+
`KptIconButton` w porcie Angulara dostaje `ariaExpanded` — jak `ariaPressed` musi zjechać na
|
|
311
|
+
wewnętrzny `<button>`, bo host nie ma roli przycisku. Słownik `@konce-pt/i18n` dostaje
|
|
312
|
+
`richText.mentions` (EN i PL) na etykietę listy podpowiedzi.
|
|
313
|
+
|
|
314
|
+
Port Reacta dostaje komplet: `KptPeek`, `KptMention`, `KptMentions` i te same trzy propsy
|
|
315
|
+
w `KptRichText`, na tym samym rdzeniu i tym samym arkuszu `components.css`. Propsy wyzwalacza
|
|
316
|
+
`KptPeek` lądują na samym elemencie podanym w `trigger` (klon z doklejonymi handlerami), więc
|
|
317
|
+
`aria-expanded` siedzi tam, gdzie czytnik ekranu naprawdę je przeczyta.
|
|
318
|
+
|
|
319
|
+
- `KptPickList` i `KptOrderList` dostają przeciąganie i dwuklik — w obu portach.
|
|
320
|
+
|
|
321
|
+
Nowe wejście `dragdrop` jest domyślnie wyłączone, tak samo jak `reorderable` w tabeli danych:
|
|
322
|
+
przeciąganie to zachowanie, na które trzeba się zgodzić, a nie takie, które ma się włączyć samo
|
|
323
|
+
komuś, kto tylko wyświetla listę. Włączone, porządkuje pozycje wewnątrz panelu, a w pick-liście
|
|
324
|
+
przenosi je także między panelami, na wskazane miejsce. Upuszczenie na pusty obszar listy dopina
|
|
325
|
+
element na koniec.
|
|
326
|
+
|
|
327
|
+
Upuszczony element ląduje **na pozycji tego, na który go upuszczono** — nie zamienia się z nim
|
|
328
|
+
miejscami. To ten sam wzór co przestawianie kolumn w tabeli; zamiana miejscami wygląda przy
|
|
329
|
+
dłuższej liście na przypadek, bo element skacze w dwa miejsca naraz.
|
|
330
|
+
|
|
331
|
+
Dwuklik w pick-liście przerzuca pojedynczą pozycję na drugą stronę, w obie strony i niezależnie
|
|
332
|
+
od zaznaczenia. Nie wymaga `dragdrop`, bo dwa kliknięcia pod spodem i tak się znoszą.
|
|
333
|
+
|
|
334
|
+
Pod spodem natywny HTML5 drag and drop, nie CDK. Port React ma mieć to samo zachowanie i ten sam
|
|
335
|
+
arkusz stylów, a dla CDK nie byłoby tam odpowiednika — jedna implementacja zamiast dwóch
|
|
336
|
+
rozjeżdżających się. Przyciski zostają pełną ścieżką z klawiatury, bo przeciąganie nie ma
|
|
337
|
+
klawiaturowego odpowiednika i bez nich sortowanie stałoby się niedostępne.
|
|
338
|
+
|
|
339
|
+
### Patch Changes
|
|
340
|
+
|
|
341
|
+
- f615e93: `KptBackdrop` idzie za tokenami, a nie za samym atrybutem motywu.
|
|
342
|
+
|
|
343
|
+
Canvas nie rozwija `var()`, więc kolor wiodący jest odczytywany raz i zapiekany w rendererze —
|
|
344
|
+
zmianę tokenów trzeba zauważyć i odczytać je na nowo. Dotąd nasłuch obejmował `data-theme`
|
|
345
|
+
i `class` na `<html>`, co wystarczało na przełącznik jasny/ciemny i zostawiało trzy dziury:
|
|
346
|
+
nadpisanie tokenu inline (konfigurator marki ustawia `--kpt-color-primary` na `<html>` — reszta
|
|
347
|
+
strony się przemalowywała, tło zostawało na starej barwie), motyw zakresowy (`[data-theme="dark"]`
|
|
348
|
+
nie jest przywiązany do `:root`, więc niesie go dowolny przodek) oraz aplikację trzymającą ciemne
|
|
349
|
+
tokeny w media query zamiast w atrybucie.
|
|
350
|
+
|
|
351
|
+
Nasłuch idzie teraz po łańcuchu od hosta aż do `<html>` i patrzy u każdego przodka na `data-theme`,
|
|
352
|
+
`class` i `style`, a obok tego na zapytanie `prefers-color-scheme`. Szersza sieć łapie też zmiany
|
|
353
|
+
niemające nic wspólnego z barwą — klasa nakładki na `<html>` przy otwarciu dialogu, dowolny zapis
|
|
354
|
+
inline — więc renderer przebudowuje się dopiero wtedy, gdy odczyt naprawdę dał inny kolor. Sam
|
|
355
|
+
strażnik jest tańszy niż dawna bezwarunkowa przebudowa: jeden `getComputedStyle`, bez sondy 1×1
|
|
356
|
+
i bez arytmetyki OKLab.
|
|
357
|
+
|
|
358
|
+
W rdzeniu doszły `readBackdropLead()` (sam kolor wiodący, wydzielony z `resolveBackdropColors()`)
|
|
359
|
+
i `observeBackdropTheme()` (nasłuch razem z odpięciem). Oba porty wołają to samo, bo rozjazd
|
|
360
|
+
znaczyłby, że ta sama sekcja reaguje na motyw inaczej w Angularze niż w Reakcie.
|
|
361
|
+
|
|
362
|
+
- Updated dependencies [f615e93]
|
|
363
|
+
- Updated dependencies [f615e93]
|
|
364
|
+
- Updated dependencies [f615e93]
|
|
365
|
+
- Updated dependencies [f615e93]
|
|
366
|
+
- Updated dependencies [c1f8885]
|
|
367
|
+
- Updated dependencies [f615e93]
|
|
368
|
+
- Updated dependencies [c1f8885]
|
|
369
|
+
- Updated dependencies [ce34ab3]
|
|
370
|
+
- Updated dependencies [a1c5468]
|
|
371
|
+
- Updated dependencies [0daa856]
|
|
372
|
+
- @konce-pt/backdrop@0.8.5
|
|
373
|
+
- @konce-pt/chat@0.8.5
|
|
374
|
+
- @konce-pt/icons@0.8.5
|
|
375
|
+
- @konce-pt/i18n@0.8.5
|
|
376
|
+
- @konce-pt/mention@0.8.5
|
|
377
|
+
- @konce-pt/chart@0.8.5
|
|
378
|
+
- @konce-pt/datetime@0.8.5
|
|
379
|
+
- @konce-pt/map@0.8.5
|
|
380
|
+
- @konce-pt/roadmap@0.8.5
|
|
381
|
+
- @konce-pt/table@0.8.5
|
|
382
|
+
|
|
3
383
|
## 0.8.3
|
|
4
384
|
|
|
5
385
|
### Patch Changes
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# KptChat (kpt-chat, kpt-chat-message, kpt-chat-typing, kpt-chat-composer, kpt-chat-dock)
|
|
2
|
+
|
|
3
|
+
A conversation: a grouped message list, a typing indicator, a composer and a floating dock in the
|
|
4
|
+
corner of the screen. The row-building core — grouping, day separators, send-status arithmetic —
|
|
5
|
+
lives in `@konce-pt/chat`: plain TypeScript, tested, shared with the React port.
|
|
6
|
+
|
|
7
|
+
Import: `import { KptChat, KptChatComposer, KptChatDock, KptChatMessage, KptChatTyping } from '@konce-pt/angular/chat';`
|
|
8
|
+
|
|
9
|
+
The library covers presentation and the local state of the list. Transport — a socket, HTTP, retry
|
|
10
|
+
with backoff — stays in the application: the component takes messages and only reports `(retry)`
|
|
11
|
+
and `(loadMore)`.
|
|
12
|
+
|
|
13
|
+
## KptChat — inputs / outputs
|
|
14
|
+
- `messages: readonly KptChatMessage<T>[]` — **in chronological order**; the component does not sort
|
|
15
|
+
- `currentUser: string` — the author id treated as "me": those messages go right and carry a status
|
|
16
|
+
- `groupGapMs: number` (default `KPT_CHAT_GROUP_GAP_MS`, 5 min) — the silence that breaks a group
|
|
17
|
+
- `unreadFrom: string | null` — the id of the first unread message; the divider goes above it
|
|
18
|
+
- `offline: boolean` — the warning strip with a count of messages waiting to be sent
|
|
19
|
+
- `typing: readonly KptChatAuthor[]` — who is typing right now
|
|
20
|
+
- `autoScroll: boolean` (default true), `actions: boolean`, `emptyLabel: string`
|
|
21
|
+
- `reactionOptions: readonly KptChatReactionOption[]` (default `KPT_CHAT_REACTIONS`) — passed to the bubbles
|
|
22
|
+
- `(retry)`, `(reply)` → `KptChatMessage<T>`; `(react)` → `KptChatReactEvent<T>` (`{ message, emoji }`);
|
|
23
|
+
`(quoted)` → `KptChatReplyRef`; `(loadMore)` → `void`, fired when the viewport reaches the top
|
|
24
|
+
|
|
25
|
+
## Example
|
|
26
|
+
<kpt-chat
|
|
27
|
+
[messages]="messages()"
|
|
28
|
+
currentUser="me"
|
|
29
|
+
[typing]="typing()"
|
|
30
|
+
[offline]="offline()"
|
|
31
|
+
(retry)="resend($event)"
|
|
32
|
+
(loadMore)="loadOlder()"
|
|
33
|
+
/>
|
|
34
|
+
<kpt-chat-composer allowAttachments [offline]="offline()" (send)="onSend($event)" />
|
|
35
|
+
|
|
36
|
+
The composer is a sibling, not a slot: a read-only conversation (an archive, a preview) should not
|
|
37
|
+
have to carry a text field it never shows.
|
|
38
|
+
|
|
39
|
+
## Custom bubble content
|
|
40
|
+
An `<ng-template>` inside `<kpt-chat>` replaces the bubble's text; the context is the message as
|
|
41
|
+
`$implicit` and the whole row as `row`. The bubble, its grouping and its meta row stay in place —
|
|
42
|
+
only the content is yours.
|
|
43
|
+
|
|
44
|
+
<kpt-chat [messages]="messages()" currentUser="me">
|
|
45
|
+
<ng-template let-message><kpt-card>{{ message.data.title }}</kpt-card></ng-template>
|
|
46
|
+
</kpt-chat>
|
|
47
|
+
|
|
48
|
+
## KptChatMessage — the bubble on its own
|
|
49
|
+
Works with a whole `[message]` object or with single inputs (`author`, `text`, `time`, `status`,
|
|
50
|
+
`attachments`, `reactions`, `replyTo`), and takes projected content, so it renders anything.
|
|
51
|
+
- `align`: 'start' | 'end' — someone else's message versus your own
|
|
52
|
+
- `variant`: 'neutral' | 'primary' | 'solid' | 'ghost' | 'system'
|
|
53
|
+
- `position`: 'single' | 'first' | 'middle' | 'last' — the place in a group
|
|
54
|
+
- `showAuthor` / `showAvatar` / `showMeta` / `tail` / `actions`: boolean
|
|
55
|
+
- `reactionOptions`: `readonly KptChatReactionOption[]` (default `KPT_CHAT_REACTIONS`) — the picker set
|
|
56
|
+
|
|
57
|
+
The default variant follows the alignment: your own message gets the full `solid`, someone else's
|
|
58
|
+
gets `neutral`. The theme is monochrome, so the difference between bubbles is lightness, not hue —
|
|
59
|
+
`--kpt-color-primary-subtle` has exactly the lightness of `--kpt-color-muted`, which would make the
|
|
60
|
+
two sides indistinguishable. For a quieter list set `variant="primary"` by hand.
|
|
61
|
+
|
|
62
|
+
## Bubble actions: reply and react
|
|
63
|
+
`actions` puts two buttons beside the bubble, shown on hover and on focus. The arrow emits
|
|
64
|
+
`(reply)` with the whole message — `replyRefFrom()` from `@konce-pt/chat` turns it into the
|
|
65
|
+
`replyTo` the composer's quote bar wants. The smiley opens a picker of five reactions (like, heart,
|
|
66
|
+
smile, wow, sad — `KPT_CHAT_REACTIONS`, overridable through `reactionOptions`) and emits `(react)`
|
|
67
|
+
with the chosen emoji.
|
|
68
|
+
|
|
69
|
+
Neither one writes to the model: the library says what was clicked, the application decides what
|
|
70
|
+
the conversation looks like afterwards. `toggleReaction(message.reactions, emoji)` does that
|
|
71
|
+
arithmetic — it adds the reaction, takes it back on a second click and drops an entry that reaches
|
|
72
|
+
zero, keeping the order of the rest.
|
|
73
|
+
|
|
74
|
+
(reply)="replyTo.set(replyRefFrom($event))"
|
|
75
|
+
(react)="onReact($event)" // toggleReaction(item.reactions, $event.emoji)
|
|
76
|
+
|
|
77
|
+
The picker closes on Escape, on a click outside and on picking; an option already given is marked
|
|
78
|
+
`aria-pressed`, so it is visible which one the next click takes back. The retry arrow of a failed
|
|
79
|
+
message takes the place of both — a message that has not gone out has nothing to reply to yet.
|
|
80
|
+
|
|
81
|
+
## Grouping is what the shape is made of
|
|
82
|
+
`position` decides which corner collapses, which bubble wears the tail, carries the avatar and shows
|
|
83
|
+
the clock. Inside a group the corner on the sender's side drops to `--kpt-radius-sm`, so the bubbles
|
|
84
|
+
read as one utterance. `kpt-chat` gets all of it from `buildChatRows()`; set it by hand only when
|
|
85
|
+
you build the list yourself.
|
|
86
|
+
|
|
87
|
+
A group breaks on a different author, a gap longer than `groupGapMs`, a calendar-day boundary, a
|
|
88
|
+
system message and the unread divider.
|
|
89
|
+
|
|
90
|
+
The bubble that wears the tail has a **square** bottom corner on that side — a rounded one curved
|
|
91
|
+
away from the tail and the list background showed through the gap, which made the tail read as a
|
|
92
|
+
separate arrow stuck next to the bubble rather than a part of it. Bubbles without a tail keep the
|
|
93
|
+
rounded corner.
|
|
94
|
+
|
|
95
|
+
## One day pill, not a stack of them
|
|
96
|
+
The separators in the list travel with the content; which day the view is standing in is told by one
|
|
97
|
+
pill floating over the list (`.kpt-chat__day`, `aria-hidden`, outside the scrolling area). Sticky
|
|
98
|
+
separators looked right until the first collision: every next one pinned to the same edge, so a stack
|
|
99
|
+
of frames grew up there and the older, wider pills stuck out from behind the newest.
|
|
100
|
+
|
|
101
|
+
On every scroll (and after every re-render and resize) the component measures the separators, the
|
|
102
|
+
unread divider and the system notices, hands the rectangles to `chatDayIndicator()` from
|
|
103
|
+
`@konce-pt/chat` and swaps the pill's text. A centred label driving into the pill's band puts the
|
|
104
|
+
pill out — it is the pill that yields, never the content — and with a separator that is also the
|
|
105
|
+
handover, since the arriving "Today" says the same thing. Bubbles are not blockers: a pill over an
|
|
106
|
+
utterance is the normal chat picture. The pill keeps its last label while it is out, so its band does
|
|
107
|
+
not change height under the script that measures it.
|
|
108
|
+
|
|
109
|
+
## An unsent message must be able to say so
|
|
110
|
+
`status: 'failed'` outlines the bubble with `--kpt-color-danger-border` and puts a retry arrow
|
|
111
|
+
**outside** it, on the outer side of the list. That bubble also shows its meta row even in the
|
|
112
|
+
middle of a group — the arrow with nothing to explain it would be a mystery.
|
|
113
|
+
Statuses: `sending`, `sent`, `delivered`, `read` (the same double check, in the accent colour) and
|
|
114
|
+
`failed`. Out-of-order acks are what `mergeStatus()` from `@konce-pt/chat` is for.
|
|
115
|
+
|
|
116
|
+
## KptChatComposer
|
|
117
|
+
`[(value)]`, `placeholder`, `disabled`, `offline`, `allowAttachments`, `maxRows` (default 6),
|
|
118
|
+
`enterToSend` (default true), `[(replyTo)]`, `(send)` → `{ text, files, replyTo }`, `(typing)`.
|
|
119
|
+
Enter sends, Shift+Enter breaks the line; `enterToSend=false` swaps the two. The field grows with
|
|
120
|
+
the content up to `maxRows` and then scrolls.
|
|
121
|
+
|
|
122
|
+
It is **not** a Signal Forms control and will not become one: it never enters a form, has no
|
|
123
|
+
`touched`/`invalid` state, and its value disappears on send. `model('')` describes that honestly.
|
|
124
|
+
|
|
125
|
+
## KptChatDock
|
|
126
|
+
`[(open)]`, `position` ('bottom-end' | 'bottom-start'), `unread`, `header`, `icon`, `width`,
|
|
127
|
+
`offset` (distance from the corner, both axes, default `--kpt-space-6`).
|
|
128
|
+
|
|
129
|
+
## Sharing the corner with kpt-scroll-top
|
|
130
|
+
`kpt-scroll-top` pins itself to the same spot with the same `--kpt-z-overlay`, and it would swallow
|
|
131
|
+
the chat FAB whole — only the unread badge would stick out, and the button could not be clicked.
|
|
132
|
+
The stylesheet resolves it by itself: when a `bottom-end` dock is on the page, the scroll-top button
|
|
133
|
+
steps **aside** (`:root:has()` shifts it left by the width of the FAB plus a gap). Sideways, not up,
|
|
134
|
+
because the conversation panel stands above the FAB — moving it up would trade one collision for a
|
|
135
|
+
worse one, with the button landing on the messages.
|
|
136
|
+
It applies to `bottom-end` only, and it measures from the default offset: an application that sets
|
|
137
|
+
`offset` positions the scroll-top button itself.
|
|
138
|
+
The FAB is `kpt-fab` — the dock uses it, it does not replace it. The panel content is projected, so
|
|
139
|
+
the dock knows nothing about messages. Opening moves focus to the composer, `Escape` closes and
|
|
140
|
+
returns focus to the FAB. Below `--kpt-breakpoint-sm` the panel goes full-screen.
|
|
141
|
+
|
|
142
|
+
<kpt-chat-dock [(open)]="open" [unread]="unread()" header="Support">
|
|
143
|
+
<kpt-chat [messages]="messages()" currentUser="me" />
|
|
144
|
+
<kpt-chat-composer (send)="onSend($event)" />
|
|
145
|
+
</kpt-chat-dock>
|
|
146
|
+
|
|
147
|
+
## Accessibility
|
|
148
|
+
The list is `role="log"` with `aria-live="polite"`, so a new message is announced without cutting
|
|
149
|
+
off what is being read. Each bubble is an `article` labelled with author, time and status — content
|
|
150
|
+
alone would say nothing about who wrote it or whether it was sent. The typing indicator is
|
|
151
|
+
`role="status"`; under `prefers-reduced-motion` the dots stop bouncing and pulse instead — they are
|
|
152
|
+
the only carrier of "someone is typing", so switching them off entirely would remove the message.
|
|
153
|
+
|
|
154
|
+
## Tokens
|
|
155
|
+
Bubble `--kpt-color-muted` (incoming) / `--kpt-color-primary` + `--kpt-color-on-primary` (own);
|
|
156
|
+
failed `--kpt-color-danger-subtle` + `--kpt-color-danger-border`; offline strip
|
|
157
|
+
`--kpt-color-warning-subtle`; the day pill `--kpt-color-surface-raised` + a border and
|
|
158
|
+
`--kpt-elevation-1` (the same look in the list and floating above it — the floating one travels over
|
|
159
|
+
the bubbles and has to stand apart from them); dock `--kpt-elevation-3`
|
|
160
|
+
and `--kpt-z-overlay`.
|