@astryxdesign/lab 0.7.6-canary.2c32c71 → 0.7.6-canary.2fa4cef
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/dist/Comments/CommentComposer.d.ts.map +1 -1
- package/dist/Comments/CommentComposer.js +12 -4
- package/package.json +4 -4
- package/src/Comments/CommentComposer.test.tsx +56 -0
- package/src/Comments/CommentComposer.tsx +18 -13
- package/src/Schedule/__tests__/ScheduleTimeDirection.a11y.chromium.spec.ts +12 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentComposer.d.ts","sourceRoot":"","sources":["../../src/Comments/CommentComposer.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAOL,KAAK,SAAS,EACd,KAAK,GAAG,EACT,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAGlD,OAAO,EAKL,KAAK,mBAAmB,EAEzB,MAAM,yBAAyB,CAAC;AAqBjC,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EACb,sBAAsB,EACtB,0BAA0B,EAC1B,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,SAAS,CAAC;AAkGjB,0DAA0D;AAC1D,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,sBAAsB,CAAC;IAChC,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,cAAc,EAAE,qBAAqB,CAAC;IACtC,cAAc,CAAC,EAAE,CACf,KAAK,EAAE,SAAS,IAAI,EAAE,KAEtB,SAAS,oBAAoB,EAAE,GAAG,OAAO,CAAC,SAAS,oBAAoB,EAAE,CAAC,CAAC;IAC7E,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACzC,aAAa,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC9C,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,qBAAqB,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IACrD,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,SAAS,CAAC;IAClE,OAAO,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;IAC3B,SAAS,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC,CAAC;CACnE;AAED,kDAAkD;AAClD,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,aAAa,EACb,OAAO,EACP,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,cAAc,EACd,cAAc,EACd,YAAY,EACZ,KAAK,EACL,qBAAqB,EACrB,eAAe,EACf,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,cAAc,EACd,WAAW,EACX,cAAc,EACd,SAAS,EACT,OAAO,EACP,QAAQ,EAAE,YAAY,EACtB,KAAK,GACN,EAAE,wBAAwB,+
|
|
1
|
+
{"version":3,"file":"CommentComposer.d.ts","sourceRoot":"","sources":["../../src/Comments/CommentComposer.tsx"],"names":[],"mappings":"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"}
|
|
@@ -152,6 +152,11 @@ export function CommentComposerBase({
|
|
|
152
152
|
latestValueRef.current = value;
|
|
153
153
|
const isMountedRef = useRef(true);
|
|
154
154
|
const actionPendingRef = useRef(false);
|
|
155
|
+
// The latest callback, read when reporting. An inline callback is new on
|
|
156
|
+
// every render; depending on it would re-run the unmount cleanup below on
|
|
157
|
+
// each render and leave the composer busy after its upload ends.
|
|
158
|
+
const onActionPendingChangeRef = useRef(onActionPendingChange);
|
|
159
|
+
onActionPendingChangeRef.current = onActionPendingChange;
|
|
155
160
|
const isSubmittingRef = useRef(isSubmitting);
|
|
156
161
|
isSubmittingRef.current = isSubmitting;
|
|
157
162
|
const imageInputRef = useRef(null);
|
|
@@ -184,18 +189,21 @@ export function CommentComposerBase({
|
|
|
184
189
|
}
|
|
185
190
|
actionPendingRef.current = nextPending;
|
|
186
191
|
setIsActionPending(nextPending);
|
|
187
|
-
|
|
188
|
-
}, [
|
|
192
|
+
onActionPendingChangeRef.current?.(nextPending);
|
|
193
|
+
}, []);
|
|
194
|
+
// Tracks mount for the upload's async completion, and on unmount releases
|
|
195
|
+
// the host's pending-action hold, which the upload's finally skips once
|
|
196
|
+
// unmounted.
|
|
189
197
|
useEffect(() => {
|
|
190
198
|
isMountedRef.current = true;
|
|
191
199
|
return () => {
|
|
192
200
|
isMountedRef.current = false;
|
|
193
201
|
if (actionPendingRef.current) {
|
|
194
202
|
actionPendingRef.current = false;
|
|
195
|
-
|
|
203
|
+
onActionPendingChangeRef.current?.(false);
|
|
196
204
|
}
|
|
197
205
|
};
|
|
198
|
-
}, [
|
|
206
|
+
}, []);
|
|
199
207
|
const uploadImages = async files => {
|
|
200
208
|
if (onUploadImages == null || files.length === 0) {
|
|
201
209
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/lab",
|
|
3
|
-
"version": "0.7.6-canary.
|
|
3
|
+
"version": "0.7.6-canary.2fa4cef",
|
|
4
4
|
"description": "Experimental Astryx components — published to npm only under the @canary dist-tag for early testing; never released as a stable (latest) version.",
|
|
5
5
|
"author": "Meta Open Source",
|
|
6
6
|
"license": "MIT",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"@stylexjs/stylex": ">=0.10.0",
|
|
52
|
-
"@astryxdesign/core": "0.7.6-canary.
|
|
52
|
+
"@astryxdesign/core": "0.7.6-canary.2fa4cef",
|
|
53
53
|
"react": ">=19.0.0",
|
|
54
54
|
"react-dom": ">=19.0.0"
|
|
55
55
|
},
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
"d3-shape": "^3.2.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@astryxdesign/a11y-spec": "0.7.6-canary.
|
|
63
|
-
"@astryxdesign/cli": "0.7.6-canary.
|
|
62
|
+
"@astryxdesign/a11y-spec": "0.7.6-canary.2fa4cef",
|
|
63
|
+
"@astryxdesign/cli": "0.7.6-canary.2fa4cef",
|
|
64
64
|
"@babel/cli": "^7.29.7",
|
|
65
65
|
"@babel/core": "^7.29.7",
|
|
66
66
|
"@babel/preset-react": "^7.29.7",
|
|
@@ -200,6 +200,62 @@ describe('CommentComposer', () => {
|
|
|
200
200
|
expect(screen.getByTestId('draft').textContent).toBe('Ship it 🎉');
|
|
201
201
|
});
|
|
202
202
|
|
|
203
|
+
it('ends the upload busy state when onActionPendingChange is a new function each render', async () => {
|
|
204
|
+
let finishUpload: (images: {url: string; alt: string}[]) => void = () => {};
|
|
205
|
+
const onUploadImages = vi.fn(
|
|
206
|
+
() =>
|
|
207
|
+
new Promise<{url: string; alt: string}[]>(resolve => {
|
|
208
|
+
finishUpload = resolve;
|
|
209
|
+
}),
|
|
210
|
+
);
|
|
211
|
+
function Host() {
|
|
212
|
+
const [draft, setDraft] = useState('See this');
|
|
213
|
+
const [pending, setPending] = useState(false);
|
|
214
|
+
return (
|
|
215
|
+
<>
|
|
216
|
+
<CommentComposerBase
|
|
217
|
+
value={draft}
|
|
218
|
+
onChange={setDraft}
|
|
219
|
+
onSubmit={() => {}}
|
|
220
|
+
placeholder="Reply"
|
|
221
|
+
context={{kind: 'comment'}}
|
|
222
|
+
highlightColor="yellow"
|
|
223
|
+
triggers={[]}
|
|
224
|
+
agentCommands={[]}
|
|
225
|
+
hasEmojiPicker={false}
|
|
226
|
+
isSubmitting={false}
|
|
227
|
+
onUploadImages={onUploadImages}
|
|
228
|
+
// Inline: a new callback on every render of the host.
|
|
229
|
+
onActionPendingChange={isPending => setPending(isPending)}
|
|
230
|
+
/>
|
|
231
|
+
<output data-testid="pending">{String(pending)}</output>
|
|
232
|
+
</>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
const {container} = render(<Host />);
|
|
236
|
+
const input = container.querySelector('input[type="file"]');
|
|
237
|
+
if (!(input instanceof HTMLInputElement)) {
|
|
238
|
+
throw new Error('File input not found');
|
|
239
|
+
}
|
|
240
|
+
const file = new File(['png'], 'chart.png', {type: 'image/png'});
|
|
241
|
+
await act(async () => {
|
|
242
|
+
fireEvent.change(input, {target: {files: [file]}});
|
|
243
|
+
await Promise.resolve();
|
|
244
|
+
});
|
|
245
|
+
await settle();
|
|
246
|
+
expect(onUploadImages).toHaveBeenCalledWith([file]);
|
|
247
|
+
expect(screen.getByTestId('pending').textContent).toBe('true');
|
|
248
|
+
|
|
249
|
+
await act(async () => {
|
|
250
|
+
finishUpload([{url: 'https://example.com/a.png', alt: 'Chart'}]);
|
|
251
|
+
await Promise.resolve();
|
|
252
|
+
});
|
|
253
|
+
await settle();
|
|
254
|
+
|
|
255
|
+
expect(screen.getByTestId('pending').textContent).toBe('false');
|
|
256
|
+
expect(container.querySelector('[aria-busy="true"]')).toBeNull();
|
|
257
|
+
});
|
|
258
|
+
|
|
203
259
|
it('uploads attached images and appends them as Markdown', async () => {
|
|
204
260
|
const onChange = vi.fn();
|
|
205
261
|
const onUploadImages = vi
|
|
@@ -239,6 +239,11 @@ export function CommentComposerBase({
|
|
|
239
239
|
latestValueRef.current = value;
|
|
240
240
|
const isMountedRef = useRef(true);
|
|
241
241
|
const actionPendingRef = useRef(false);
|
|
242
|
+
// The latest callback, read when reporting. An inline callback is new on
|
|
243
|
+
// every render; depending on it would re-run the unmount cleanup below on
|
|
244
|
+
// each render and leave the composer busy after its upload ends.
|
|
245
|
+
const onActionPendingChangeRef = useRef(onActionPendingChange);
|
|
246
|
+
onActionPendingChangeRef.current = onActionPendingChange;
|
|
242
247
|
const isSubmittingRef = useRef(isSubmitting);
|
|
243
248
|
isSubmittingRef.current = isSubmitting;
|
|
244
249
|
const imageInputRef = useRef<HTMLInputElement>(null);
|
|
@@ -269,27 +274,27 @@ export function CommentComposerBase({
|
|
|
269
274
|
const [isActionPending, setIsActionPending] = useState(false);
|
|
270
275
|
const [isPickerOpen, setIsPickerOpen] = useState(false);
|
|
271
276
|
|
|
272
|
-
const updateActionPending = useCallback(
|
|
273
|
-
(nextPending
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
277
|
+
const updateActionPending = useCallback((nextPending: boolean) => {
|
|
278
|
+
if (actionPendingRef.current === nextPending) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
actionPendingRef.current = nextPending;
|
|
282
|
+
setIsActionPending(nextPending);
|
|
283
|
+
onActionPendingChangeRef.current?.(nextPending);
|
|
284
|
+
}, []);
|
|
285
|
+
// Tracks mount for the upload's async completion, and on unmount releases
|
|
286
|
+
// the host's pending-action hold, which the upload's finally skips once
|
|
287
|
+
// unmounted.
|
|
283
288
|
useEffect(() => {
|
|
284
289
|
isMountedRef.current = true;
|
|
285
290
|
return () => {
|
|
286
291
|
isMountedRef.current = false;
|
|
287
292
|
if (actionPendingRef.current) {
|
|
288
293
|
actionPendingRef.current = false;
|
|
289
|
-
|
|
294
|
+
onActionPendingChangeRef.current?.(false);
|
|
290
295
|
}
|
|
291
296
|
};
|
|
292
|
-
}, [
|
|
297
|
+
}, []);
|
|
293
298
|
|
|
294
299
|
const uploadImages = async (files: readonly File[]) => {
|
|
295
300
|
if (onUploadImages == null || files.length === 0) {
|
|
@@ -33,6 +33,18 @@ const STORIES = [
|
|
|
33
33
|
const RTL_LOCALE = 'lab-schedule--right-to-left-locale';
|
|
34
34
|
|
|
35
35
|
let evidence: Evidence;
|
|
36
|
+
|
|
37
|
+
// The stories build their events from the browser's clock, at fixed hours of
|
|
38
|
+
// the browser's own day and week, and the Schedule shows them in
|
|
39
|
+
// America/Los_Angeles in a 7–19 grid. Run the browser in that zone, so every
|
|
40
|
+
// event lands at its hour in the week the Schedule shows, and pin the clock to
|
|
41
|
+
// the week US daylight saving time ends, so this contract also covers that
|
|
42
|
+
// week, whatever day it runs.
|
|
43
|
+
test.use({timezoneId: 'America/Los_Angeles'});
|
|
44
|
+
const PINNED_NOW = new Date('2026-11-01T10:00:00-08:00');
|
|
45
|
+
test.beforeEach(async ({page}) => {
|
|
46
|
+
await page.clock.install({time: PINNED_NOW});
|
|
47
|
+
});
|
|
36
48
|
test.beforeAll(async () => {
|
|
37
49
|
evidence = await startEvidence();
|
|
38
50
|
});
|