@astryxdesign/lab 0.7.6-canary.dfc83ee → 0.7.6-canary.e1e704b

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.
@@ -1 +1 @@
1
- {"version":3,"file":"CommentComposer.d.ts","sourceRoot":"","sources":["../../src/Comments/CommentComposer.tsx"],"names":[],"mappings":"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"}
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,+BAkX1B;AAED,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAChD,SAAS,CAAC,WAAW,CAAC,EACtB,UAAU,GAAG,UAAU,CACxB;IACC,yCAAyC;IACzC,GAAG,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;IACvB,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,2EAA2E;IAC3E,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,qDAAqD;IACrD,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,2EAA2E;IAC3E,cAAc,CAAC,EAAE,qBAAqB,CAAC;IACvC;;;OAGG;IACH,cAAc,CAAC,EAAE,CACf,KAAK,EAAE,SAAS,IAAI,EAAE,KAEtB,SAAS,oBAAoB,EAAE,GAAG,OAAO,CAAC,SAAS,oBAAoB,EAAE,CAAC,CAAC;IAC7E,wDAAwD;IACxD,QAAQ,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC1C,6DAA6D;IAC7D,aAAa,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,2DAA2D;IAC3D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,KAAK,EACL,YAAoB,EACpB,KAAK,EACL,OAAO,EACP,MAAM,EACN,eAAe,EACf,cAAyB,EACzB,cAAc,EACd,QAAsB,EACtB,aAAiC,EACjC,cAAqB,EACrB,QAAQ,EACR,GAAG,EACH,GAAG,SAAS,EACb,EAAE,oBAAoB,+BAyBtB;yBA5Ce,eAAe"}
@@ -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
- onActionPendingChange?.(nextPending);
188
- }, [onActionPendingChange]);
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
- onActionPendingChange?.(false);
203
+ onActionPendingChangeRef.current?.(false);
196
204
  }
197
205
  };
198
- }, [onActionPendingChange]);
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.dfc83ee",
3
+ "version": "0.7.6-canary.e1e704b",
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.dfc83ee",
52
+ "@astryxdesign/core": "0.7.6-canary.e1e704b",
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.dfc83ee",
63
- "@astryxdesign/cli": "0.7.6-canary.dfc83ee",
62
+ "@astryxdesign/a11y-spec": "0.7.6-canary.e1e704b",
63
+ "@astryxdesign/cli": "0.7.6-canary.e1e704b",
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: boolean) => {
274
- if (actionPendingRef.current === nextPending) {
275
- return;
276
- }
277
- actionPendingRef.current = nextPending;
278
- setIsActionPending(nextPending);
279
- onActionPendingChange?.(nextPending);
280
- },
281
- [onActionPendingChange],
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
- onActionPendingChange?.(false);
294
+ onActionPendingChangeRef.current?.(false);
290
295
  }
291
296
  };
292
- }, [onActionPendingChange]);
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
  });