@startsimpli/ui 0.4.144 → 0.4.145
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@startsimpli/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.145",
|
|
4
4
|
"description": "Shared UI components package for StartSimpli applications",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -152,8 +152,8 @@
|
|
|
152
152
|
"tailwindcss": "^3.4.19",
|
|
153
153
|
"tsup": "^8.5.1",
|
|
154
154
|
"typescript": "^6.0.3",
|
|
155
|
-
"@startsimpli/
|
|
156
|
-
"@startsimpli/
|
|
155
|
+
"@startsimpli/i18n": "0.1.3",
|
|
156
|
+
"@startsimpli/realtime": "0.0.2"
|
|
157
157
|
},
|
|
158
158
|
"keywords": [
|
|
159
159
|
"ui",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* Presentational: every write is a callback, so the host decides the
|
|
25
25
|
* precondition (the version an accept asserts) and folds the result.
|
|
26
26
|
*/
|
|
27
|
-
import { useId, useMemo, useRef, useState, type KeyboardEvent, type ReactNode } from 'react';
|
|
27
|
+
import { useId, useLayoutEffect, useMemo, useRef, useState, type KeyboardEvent, type ReactNode } from 'react';
|
|
28
28
|
|
|
29
29
|
import { ActorIdentity, actorKindTitle } from '../ActorIdentity';
|
|
30
30
|
import { describeChange, describeSuggestion, describeThread, quoted, who } from './describe';
|
|
@@ -587,6 +587,20 @@ function Composer({
|
|
|
587
587
|
const [hi, setHi] = useState(0);
|
|
588
588
|
const ref = useRef<HTMLTextAreaElement>(null);
|
|
589
589
|
const listId = `mention-${useId()}`;
|
|
590
|
+
// Where the caret goes after a picked mention. Applied in a LAYOUT effect,
|
|
591
|
+
// i.e. synchronously after React writes the new value and before the
|
|
592
|
+
// browser can deliver another keystroke. A requestAnimationFrame here let a
|
|
593
|
+
// fast typist's next characters land at the end of the text and then jump
|
|
594
|
+
// (caught on the deployed gate: "...nly).(sandbox o").
|
|
595
|
+
const caretRef = useRef<number | null>(null);
|
|
596
|
+
useLayoutEffect(() => {
|
|
597
|
+
const at = caretRef.current;
|
|
598
|
+
const el = ref.current;
|
|
599
|
+
if (at == null || !el) return;
|
|
600
|
+
caretRef.current = null;
|
|
601
|
+
el.focus();
|
|
602
|
+
el.setSelectionRange(at, at);
|
|
603
|
+
}, [body]);
|
|
590
604
|
|
|
591
605
|
const matches = useMemo(() => {
|
|
592
606
|
if (!q) return [];
|
|
@@ -603,11 +617,7 @@ function Composer({
|
|
|
603
617
|
setBody(next);
|
|
604
618
|
setPicked((p) => (p.some((x) => x.sub === m.sub) ? p : [...p, m]));
|
|
605
619
|
setQ(null);
|
|
606
|
-
|
|
607
|
-
const pos = q.start + tag.length;
|
|
608
|
-
el?.focus();
|
|
609
|
-
el?.setSelectionRange(pos, pos);
|
|
610
|
-
});
|
|
620
|
+
caretRef.current = q.start + tag.length;
|
|
611
621
|
};
|
|
612
622
|
|
|
613
623
|
const submit = async () => {
|
|
@@ -373,6 +373,18 @@ describe('ChangesRail', () => {
|
|
|
373
373
|
await waitFor(() => expect(onComment).toHaveBeenCalledWith('Ask @rita@x.io', ['rita'], selection))
|
|
374
374
|
})
|
|
375
375
|
|
|
376
|
+
it('puts the caret right after a picked mention before the next keystroke can land', async () => {
|
|
377
|
+
render(<ChangesRail {...base} canAccept={false} onComment={jest.fn()} mentionable={[{ sub: 'rita', label: 'rita@x.io' }]} />)
|
|
378
|
+
const box = screen.getByLabelText('New comment on Blog') as HTMLTextAreaElement
|
|
379
|
+
fireEvent.change(box, { target: { value: 'Ask @ri and more', selectionStart: 7 } })
|
|
380
|
+
fireEvent.mouseDown(await screen.findByRole('option', { name: 'rita@x.io' }))
|
|
381
|
+
// Synchronously, with no animation frame in between: a fast typist's next
|
|
382
|
+
// character must go after the tag, not wherever the value update left it.
|
|
383
|
+
expect(box.value).toBe('Ask @rita@x.io and more')
|
|
384
|
+
expect(box.selectionStart).toBe('Ask @rita@x.io '.length)
|
|
385
|
+
expect(document.activeElement).toBe(box)
|
|
386
|
+
})
|
|
387
|
+
|
|
376
388
|
it('keeps the typed comment when the write fails', async () => {
|
|
377
389
|
const onComment = jest.fn().mockRejectedValue(new Error('Could not add the comment: no.'))
|
|
378
390
|
render(<ChangesRail {...base} canAccept={false} onComment={onComment} />)
|