@elabs-ai/components-ai 5.4.0 → 5.5.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.
Files changed (112) hide show
  1. package/README.md +4 -6
  2. package/dist/_lazy-engine-boundary.js +1 -1
  3. package/dist/_persona-rive.js.map +1 -1
  4. package/dist/_streamdown-i18n.js +2 -2
  5. package/dist/agent.js +2 -2
  6. package/dist/asset-preview.js +5 -5
  7. package/dist/attachments.js +1 -1
  8. package/dist/audio-player.js +1 -2
  9. package/dist/{chunk-7RL6KZDF.js → chunk-24M6XWVJ.js} +25 -17
  10. package/dist/chunk-24M6XWVJ.js.map +1 -0
  11. package/dist/{chunk-VJZJWNEC.js → chunk-3KXIWGHW.js} +3 -3
  12. package/dist/{chunk-TWPHAJNM.js → chunk-3UHR3RLZ.js} +12 -6
  13. package/dist/chunk-3UHR3RLZ.js.map +1 -0
  14. package/dist/{chunk-V4DORGWJ.js → chunk-5AMSNG2T.js} +2 -2
  15. package/dist/chunk-D7BZMRI3.js +2 -0
  16. package/dist/chunk-D7BZMRI3.js.map +1 -0
  17. package/dist/{chunk-PQBF6ZIQ.js → chunk-DI645WR4.js} +4 -4
  18. package/dist/{chunk-NCL2UZKI.js → chunk-FFA7OPEH.js} +2 -2
  19. package/dist/{chunk-34CQ2VHM.js → chunk-FO44KY7Y.js} +10 -7
  20. package/dist/chunk-FO44KY7Y.js.map +1 -0
  21. package/dist/{chunk-RB7G65JC.js → chunk-GP7ZZCID.js} +7 -5
  22. package/dist/chunk-GP7ZZCID.js.map +1 -0
  23. package/dist/{chunk-6PB2MXCI.js → chunk-HLODND65.js} +2 -2
  24. package/dist/{chunk-ADOYAGGK.js → chunk-IFVO7LQJ.js} +2 -2
  25. package/dist/{chunk-2XOIG5TG.js → chunk-JSEV46SH.js} +18 -12
  26. package/dist/chunk-JSEV46SH.js.map +1 -0
  27. package/dist/{chunk-CZP3RHC4.js → chunk-LY7O4BMP.js} +2 -2
  28. package/dist/{chunk-AOOIAFDT.js → chunk-OCZNA5SO.js} +2 -2
  29. package/dist/{chunk-S3ISPW3A.js → chunk-PWQPVOZJ.js} +1 -1
  30. package/dist/chunk-PWQPVOZJ.js.map +1 -0
  31. package/dist/chunk-RLV64EWK.js +41 -0
  32. package/dist/chunk-RLV64EWK.js.map +1 -0
  33. package/dist/chunk-SSES3F4Q.js +126 -0
  34. package/dist/chunk-SSES3F4Q.js.map +1 -0
  35. package/dist/{chunk-3XNPMDZY.js → chunk-TBCMCF5O.js} +9 -56
  36. package/dist/chunk-TBCMCF5O.js.map +1 -0
  37. package/dist/{chunk-UHLVPAHG.js → chunk-WXUXFDJQ.js} +2 -2
  38. package/dist/{chunk-X47QMM6K.js → chunk-YLN7E4OV.js} +2 -2
  39. package/dist/code-block.js +1 -1
  40. package/dist/diff-view.js +2 -2
  41. package/dist/gallery.js +1 -1
  42. package/dist/generated-image.js +9 -0
  43. package/dist/generated-image.js.map +1 -0
  44. package/dist/grouped-parts.js +6 -6
  45. package/dist/image.js +4 -4
  46. package/dist/index.d.ts +92 -150
  47. package/dist/index.js +67 -65
  48. package/dist/markdown-view.js +3 -3
  49. package/dist/message.js +3 -3
  50. package/dist/model-provider-logo.js +1 -1
  51. package/dist/persona.js +2 -2
  52. package/dist/queue.js +1 -1
  53. package/dist/reasoning.js +3 -3
  54. package/dist/sandbox.js +3 -3
  55. package/dist/tool.js +2 -2
  56. package/package.json +8 -13
  57. package/schemas/a2ui-surface.v1.schema.json +1 -1
  58. package/src/__contract__/{image.contract.test.tsx → attachments.contract.test.tsx} +7 -7
  59. package/src/__contract__/generated-image.contract.test.tsx +49 -0
  60. package/src/_lazy-boundary-conformance.ts +12 -18
  61. package/src/_lazy-engine-boundary.tsx +1 -2
  62. package/src/_lazy-mermaid-absent.test.ts +2 -2
  63. package/src/_media-fixtures.ts +47 -0
  64. package/src/_persona-rive.tsx +1 -2
  65. package/src/a2ui/catalog.source.json +1 -1
  66. package/src/asset-preview.stories.tsx +12 -0
  67. package/src/asset-preview.test.tsx +14 -0
  68. package/src/asset-preview.tsx +4 -2
  69. package/src/attachments.stories.tsx +147 -0
  70. package/src/attachments.test.tsx +104 -0
  71. package/src/attachments.tsx +28 -20
  72. package/src/audio-player.stories.tsx +10 -45
  73. package/src/audio-player.test.tsx +166 -61
  74. package/src/audio-player.tsx +175 -266
  75. package/src/chat.stories.tsx +75 -3
  76. package/src/code-block.test.tsx +32 -0
  77. package/src/code-block.tsx +22 -5
  78. package/src/gallery.tsx +13 -77
  79. package/src/generated-image.stories.tsx +67 -0
  80. package/src/generated-image.test.tsx +107 -0
  81. package/src/generated-image.tsx +57 -0
  82. package/src/image.tsx +2 -115
  83. package/src/index.ts +1 -0
  84. package/src/model-provider-logo.tsx +20 -18
  85. package/src/queue.test.tsx +24 -0
  86. package/src/queue.tsx +8 -5
  87. package/dist/_audio-player-media-chrome.js +0 -85
  88. package/dist/_audio-player-media-chrome.js.map +0 -1
  89. package/dist/chunk-2XOIG5TG.js.map +0 -1
  90. package/dist/chunk-34CQ2VHM.js.map +0 -1
  91. package/dist/chunk-3XNPMDZY.js.map +0 -1
  92. package/dist/chunk-7RL6KZDF.js.map +0 -1
  93. package/dist/chunk-NSCLRHES.js +0 -102
  94. package/dist/chunk-NSCLRHES.js.map +0 -1
  95. package/dist/chunk-RB7G65JC.js.map +0 -1
  96. package/dist/chunk-S3ISPW3A.js.map +0 -1
  97. package/dist/chunk-TWPHAJNM.js.map +0 -1
  98. package/dist/chunk-V6E5DYZD.js +0 -80
  99. package/dist/chunk-V6E5DYZD.js.map +0 -1
  100. package/src/_audio-player-media-chrome.tsx +0 -233
  101. package/src/image.stories.tsx +0 -67
  102. package/src/image.test.tsx +0 -105
  103. /package/dist/{chunk-VJZJWNEC.js.map → chunk-3KXIWGHW.js.map} +0 -0
  104. /package/dist/{chunk-V4DORGWJ.js.map → chunk-5AMSNG2T.js.map} +0 -0
  105. /package/dist/{chunk-PQBF6ZIQ.js.map → chunk-DI645WR4.js.map} +0 -0
  106. /package/dist/{chunk-NCL2UZKI.js.map → chunk-FFA7OPEH.js.map} +0 -0
  107. /package/dist/{chunk-6PB2MXCI.js.map → chunk-HLODND65.js.map} +0 -0
  108. /package/dist/{chunk-ADOYAGGK.js.map → chunk-IFVO7LQJ.js.map} +0 -0
  109. /package/dist/{chunk-CZP3RHC4.js.map → chunk-LY7O4BMP.js.map} +0 -0
  110. /package/dist/{chunk-AOOIAFDT.js.map → chunk-OCZNA5SO.js.map} +0 -0
  111. /package/dist/{chunk-UHLVPAHG.js.map → chunk-WXUXFDJQ.js.map} +0 -0
  112. /package/dist/{chunk-X47QMM6K.js.map → chunk-YLN7E4OV.js.map} +0 -0
@@ -1,67 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
- import { Image } from "./image";
3
-
4
- // A tiny 1x1 solid-red PNG — enough to exercise decode + skeleton timing
5
- // without shipping a real asset in the story.
6
- const RED_DOT =
7
- "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC";
8
-
9
- const meta = {
10
- title: "AI/Image",
11
- component: Image,
12
- parameters: { layout: "padded" },
13
- } satisfies Meta<typeof Image>;
14
- export default meta;
15
- type Story = StoryObj<typeof meta>;
16
-
17
- // `showSkeleton` (default `Boolean(width && height)` — true here, since both
18
- // are given) covers the box with a `Skeleton` until the `<img>` finishes
19
- // decoding, reserving the `width`/`height` box so there is no layout shift
20
- // (loading-states.md, interaction-guidelines.md 'Images'). Without both
21
- // dimensions there is no box to reserve, so the skeleton defaults off in that
22
- // case rather than collapsing to nothing (see the `NoSkeleton` story). The
23
- // Skeleton window is real-decode-timed (there is no consumer `loading` prop
24
- // like Gallery's — see image.tsx), so it is too fast/racy to freeze reliably
25
- // in a real-browser Storybook snapshot; it is instead exercised
26
- // deterministically in image.test.tsx (jsdom never auto-resolves `<img>`
27
- // loads, so the pending state holds until the test fires `load` itself).
28
- export const Default: Story = {
29
- args: {
30
- base64: RED_DOT,
31
- mediaType: "image/png",
32
- uint8Array: new Uint8Array(),
33
- alt: "A generated red square",
34
- width: 128,
35
- height: 128,
36
- },
37
- };
38
-
39
- // BROKEN — a genuinely undecodable payload settles into the `onError`
40
- // fallback (an `ImageOff` glyph on a muted box, sized to `width`/`height`)
41
- // instead of leaving the browser's native broken-image glyph bleeding through
42
- // a perpetually-pulsing Skeleton.
43
- export const Broken: Story = {
44
- args: {
45
- base64: "not-a-real-image",
46
- mediaType: "image/png",
47
- uint8Array: new Uint8Array(),
48
- alt: "A generated image",
49
- width: 128,
50
- height: 128,
51
- },
52
- };
53
-
54
- // Disabling `showSkeleton` opts out for a context that already has its own
55
- // placeholder (e.g. a Gallery tile).
56
- export const NoSkeleton: Story = {
57
- name: "showSkeleton=false",
58
- args: {
59
- base64: RED_DOT,
60
- mediaType: "image/png",
61
- uint8Array: new Uint8Array(),
62
- alt: "A generated red square",
63
- width: 128,
64
- height: 128,
65
- showSkeleton: false,
66
- },
67
- };
@@ -1,105 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import { fireEvent, render } from "@testing-library/react";
3
- import { createRef } from "react";
4
- import { Image, type ImageProps } from "./image";
5
-
6
- const RED_DOT =
7
- "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC";
8
-
9
- const BASE_PROPS: Pick<ImageProps, "base64" | "mediaType" | "uint8Array"> = {
10
- base64: RED_DOT,
11
- mediaType: "image/png",
12
- uint8Array: new Uint8Array(),
13
- };
14
-
15
- describe("Image", () => {
16
- it("renders the img with the base64 data URI, alt, width and height", () => {
17
- const { container } = render(
18
- <Image {...BASE_PROPS} alt="A red square" width={64} height={64} />,
19
- );
20
- const img = container.querySelector("img");
21
- expect(img).not.toBeNull();
22
- expect(img?.getAttribute("src")).toBe(`data:image/png;base64,${RED_DOT}`);
23
- expect(img?.getAttribute("alt")).toBe("A red square");
24
- expect(img?.getAttribute("width")).toBe("64");
25
- expect(img?.getAttribute("height")).toBe("64");
26
- });
27
-
28
- it("defaults alt to empty (decorative) rather than omitting it", () => {
29
- const { container } = render(<Image {...BASE_PROPS} />);
30
- expect(container.querySelector("img")?.getAttribute("alt")).toBe("");
31
- });
32
-
33
- it("forwards a ref to the underlying <img>", () => {
34
- const ref = createRef<HTMLImageElement>();
35
- render(<Image ref={ref} {...BASE_PROPS} />);
36
- expect(ref.current).toBeInstanceOf(HTMLImageElement);
37
- });
38
- });
39
-
40
- describe("Image showSkeleton (#269, loading-states.md)", () => {
41
- it("shows a Skeleton (decorative + a live region) until onLoad fires, when width+height are given", () => {
42
- const { container } = render(<Image {...BASE_PROPS} alt="" width={64} height={64} />);
43
- const skeleton = container.querySelector(".animate-pulse");
44
- expect(skeleton).not.toBeNull();
45
- expect(skeleton).toHaveAttribute("aria-hidden", "true");
46
- // Exactly one live region for the region.
47
- expect(container.querySelectorAll('[role="status"]')).toHaveLength(1);
48
-
49
- const img = container.querySelector("img")!;
50
- fireEvent.load(img);
51
- expect(container.querySelector(".animate-pulse")).toBeNull();
52
- });
53
-
54
- it("defaults to no skeleton (and no live region) when width/height are not given — nothing to reserve", () => {
55
- const { container } = render(<Image {...BASE_PROPS} alt="" />);
56
- expect(container.querySelector(".animate-pulse")).toBeNull();
57
- expect(container.querySelector('[role="status"]')).toBeNull();
58
- });
59
-
60
- it("skips the skeleton entirely when showSkeleton is explicitly false, even with width+height", () => {
61
- const { container } = render(
62
- <Image {...BASE_PROPS} alt="" width={64} height={64} showSkeleton={false} />,
63
- );
64
- expect(container.querySelector(".animate-pulse")).toBeNull();
65
- expect(container.querySelector('[role="status"]')).toBeNull();
66
- });
67
-
68
- it("shows the skeleton when explicitly enabled even without width/height", () => {
69
- const { container } = render(<Image {...BASE_PROPS} alt="" showSkeleton />);
70
- expect(container.querySelector(".animate-pulse")).not.toBeNull();
71
- });
72
-
73
- it("does not wrap the <img> in a sizing span when no skeleton is shown, so className keeps resolving against the real parent", () => {
74
- const { container } = render(<Image {...BASE_PROPS} alt="" className="w-full" />);
75
- expect(container.firstElementChild?.tagName).toBe("IMG");
76
- });
77
-
78
- it("calls the consumer's onLoad in addition to clearing the skeleton", () => {
79
- const onLoad = vi.fn();
80
- const { container } = render(
81
- <Image {...BASE_PROPS} alt="" width={64} height={64} onLoad={onLoad} />,
82
- );
83
- fireEvent.load(container.querySelector("img")!);
84
- expect(onLoad).toHaveBeenCalledTimes(1);
85
- });
86
- });
87
-
88
- describe("Image onError fallback (visual regression: broken image bled through the pulsing Skeleton)", () => {
89
- it("replaces the img with an ImageOff fallback box instead of leaving the Skeleton pulsing forever", () => {
90
- const { container } = render(<Image {...BASE_PROPS} alt="A generated image" />);
91
- fireEvent.error(container.querySelector("img")!);
92
- // No native <img> (and its browser broken-image glyph) remains in the DOM.
93
- expect(container.querySelector("img")).toBeNull();
94
- // No skeleton keeps pulsing behind a failed image.
95
- expect(container.querySelector(".animate-pulse")).toBeNull();
96
- expect(container.querySelector("svg")).not.toBeNull();
97
- });
98
-
99
- it("calls the consumer's onError in addition to showing the fallback", () => {
100
- const onError = vi.fn();
101
- const { container } = render(<Image {...BASE_PROPS} alt="" onError={onError} />);
102
- fireEvent.error(container.querySelector("img")!);
103
- expect(onError).toHaveBeenCalledTimes(1);
104
- });
105
- });