@flamingo-stack/openframe-frontend-core 0.0.847 → 0.0.849

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 (188) hide show
  1. package/dist/build/export-map.json +1 -1
  2. package/dist/{case-study-card-DJOHIHYU.cjs → case-study-card-X2HKPKT7.cjs} +5 -5
  3. package/dist/{chunk-CNMXP77I.cjs → chunk-36DUCHI7.cjs} +4 -4
  4. package/dist/{chunk-MPVOPSU6.cjs → chunk-36XWCVSM.cjs} +96 -96
  5. package/dist/{chunk-J2IXYCEH.cjs → chunk-3W4ASAXN.cjs} +15 -15
  6. package/dist/{chunk-6LDL3UZE.cjs → chunk-4YBS4HIO.cjs} +20 -20
  7. package/dist/{chunk-ABL5OBSF.cjs → chunk-533QK4R6.cjs} +296 -250
  8. package/dist/{chunk-QK2A334V.cjs → chunk-5CBI2ZYV.cjs} +12 -12
  9. package/dist/{chunk-D5DMWXF2.cjs → chunk-5PIR77V5.cjs} +4 -4
  10. package/dist/{chunk-HPXET3LH.cjs → chunk-6AJYGXXS.cjs} +27 -27
  11. package/dist/{chunk-4R4HAFXI.cjs → chunk-ALRYVZ3E.cjs} +6 -6
  12. package/dist/{chunk-FLW4WULE.cjs → chunk-BU2PSRAS.cjs} +17 -61
  13. package/dist/{chunk-HBDLJUI3.cjs → chunk-C3P7IUTU.cjs} +39 -14
  14. package/dist/{chunk-SSSI2RBV.cjs → chunk-FNQWL4W4.cjs} +14 -14
  15. package/dist/{chunk-TBOWFTEN.cjs → chunk-FRG724DU.cjs} +22 -22
  16. package/dist/{chunk-SIFRIUPW.cjs → chunk-FXLROQUT.cjs} +345 -356
  17. package/dist/{chunk-3V5JWG27.cjs → chunk-GG466XFQ.cjs} +2 -2
  18. package/dist/{chunk-XCVH5BCZ.cjs → chunk-GRCOEICK.cjs} +6 -6
  19. package/dist/{chunk-5GBLXY5F.cjs → chunk-HTYJUMFB.cjs} +7 -7
  20. package/dist/{chunk-URP6GYTD.cjs → chunk-IG6ROHPS.cjs} +56 -56
  21. package/dist/{chunk-DGQDGCQH.cjs → chunk-IHY7E4EX.cjs} +2 -2
  22. package/dist/{chunk-WCK5T3UD.cjs → chunk-IKOL6NJY.cjs} +101 -69
  23. package/dist/{chunk-RAJ5FYFL.cjs → chunk-IN3LA2FU.cjs} +14 -14
  24. package/dist/{chunk-ZTO7KNYV.cjs → chunk-KXBZCUDW.cjs} +35 -35
  25. package/dist/{chunk-57JYB3E2.cjs → chunk-MC3L525K.cjs} +3 -3
  26. package/dist/{chunk-BIOSHDXQ.cjs → chunk-MDLT6D46.cjs} +61 -61
  27. package/dist/{chunk-3TOWCFIK.cjs → chunk-PMUCEUFQ.cjs} +13 -1
  28. package/dist/{chunk-PV3NKXWS.cjs → chunk-PV2XQJ2R.cjs} +99 -178
  29. package/dist/{chunk-J2ZSGOUC.cjs → chunk-R5JPZ6TY.cjs} +27 -27
  30. package/dist/{chunk-744JXTDP.cjs → chunk-SA2NNWIJ.cjs} +23 -23
  31. package/dist/{chunk-DEEQSVLX.cjs → chunk-SHQY4GOE.cjs} +90 -90
  32. package/dist/{chunk-BHOVF4KP.cjs → chunk-SOSIXRQ3.cjs} +2 -2
  33. package/dist/{chunk-Y2OEC7UW.cjs → chunk-TEKN6TJ5.cjs} +40 -40
  34. package/dist/{chunk-PZNWIJRZ.cjs → chunk-TIVPOVIM.cjs} +2 -2
  35. package/dist/{chunk-BZZE4KIV.cjs → chunk-TX4NTH22.cjs} +155 -136
  36. package/dist/{chunk-NFNSPLGC.cjs → chunk-W7OFAROY.cjs} +14 -9
  37. package/dist/{chunk-N2A24FDF.cjs → chunk-YLETC7LP.cjs} +994 -953
  38. package/dist/{chunk-YC52LSPP.cjs → chunk-YYHQY3K6.cjs} +40 -40
  39. package/dist/{chunk-2FUTZKLB.cjs → chunk-Z5UCDEKZ.cjs} +6 -6
  40. package/dist/{chunk-SWF6TNZ3.cjs → chunk-ZGEWT3SF.cjs} +2 -2
  41. package/dist/{chunk-PQDCSZ3W.cjs → chunk-ZY34R37A.cjs} +85 -85
  42. package/dist/{chunk-5SZ4ILU3.cjs → chunk-ZZDFMUEU.cjs} +3 -3
  43. package/dist/{cloud-tenants-XEUUMABT.cjs → cloud-tenants-HKUUEUAY.cjs} +21 -21
  44. package/dist/components/case-studies/index.cjs +36 -36
  45. package/dist/components/chat/chat-composer.d.ts.map +1 -1
  46. package/dist/components/chat/chat-composer.js +4 -3
  47. package/dist/components/chat/chat-container.d.ts.map +1 -1
  48. package/dist/components/chat/chat-container.js +2 -2
  49. package/dist/components/chat/entity-cards/design-doc-card.d.ts.map +1 -1
  50. package/dist/components/chat/entity-cards/design-doc-card.js +5 -17
  51. package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
  52. package/dist/components/chat/entity-cards/dispatch.js +5 -7
  53. package/dist/components/chat/index.cjs +15 -15
  54. package/dist/components/contact/index.cjs +22 -22
  55. package/dist/components/docs/doc-viewer.d.ts +7 -7
  56. package/dist/components/docs/doc-viewer.d.ts.map +1 -1
  57. package/dist/components/docs/doc-viewer.js +29 -16
  58. package/dist/components/docs/docs-hub-page.d.ts +1 -1
  59. package/dist/components/docs/docs-hub-page.d.ts.map +1 -1
  60. package/dist/components/docs/docs-hub-page.js +4 -11
  61. package/dist/components/docs/index.cjs +25 -25
  62. package/dist/components/docs/use-document-tree.d.ts.map +1 -1
  63. package/dist/components/docs/use-document-tree.js +24 -24
  64. package/dist/components/embeds/claude-embed.d.ts +2 -2
  65. package/dist/components/embeds/claude-embed.d.ts.map +1 -1
  66. package/dist/components/embeds/claude-embed.js +3 -3
  67. package/dist/components/embeds/embed-iframe.d.ts +5 -3
  68. package/dist/components/embeds/embed-iframe.d.ts.map +1 -1
  69. package/dist/components/embeds/embed-iframe.js +4 -3
  70. package/dist/components/embeds/embed-viewer-frame.d.ts +12 -9
  71. package/dist/components/embeds/embed-viewer-frame.d.ts.map +1 -1
  72. package/dist/components/embeds/embed-viewer-frame.js +21 -7
  73. package/dist/components/embeds/figma-embed.d.ts +4 -2
  74. package/dist/components/embeds/figma-embed.d.ts.map +1 -1
  75. package/dist/components/embeds/figma-embed.js +4 -3
  76. package/dist/components/embeds/file-download-card.d.ts +3 -1
  77. package/dist/components/embeds/file-download-card.d.ts.map +1 -1
  78. package/dist/components/embeds/file-download-card.js +18 -3
  79. package/dist/components/embeds/google-sheets-viewer.d.ts +3 -1
  80. package/dist/components/embeds/google-sheets-viewer.d.ts.map +1 -1
  81. package/dist/components/embeds/google-sheets-viewer.js +7 -4
  82. package/dist/components/embeds/index.cjs +22 -22
  83. package/dist/components/embeds/pdf-viewer.d.ts +3 -1
  84. package/dist/components/embeds/pdf-viewer.d.ts.map +1 -1
  85. package/dist/components/embeds/pdf-viewer.js +6 -2
  86. package/dist/components/faq/index.cjs +23 -23
  87. package/dist/components/features/index.cjs +21 -21
  88. package/dist/components/features/status-filter-component.d.ts +7 -1
  89. package/dist/components/features/status-filter-component.d.ts.map +1 -1
  90. package/dist/components/features/status-filter-component.js +9 -3
  91. package/dist/components/help-center-pages/index.cjs +140 -140
  92. package/dist/components/index.cjs +137 -135
  93. package/dist/components/layout/back-button.d.ts +7 -1
  94. package/dist/components/layout/back-button.d.ts.map +1 -1
  95. package/dist/components/layout/back-button.js +34 -2
  96. package/dist/components/loading/unified-skeleton.d.ts +6 -1
  97. package/dist/components/loading/unified-skeleton.d.ts.map +1 -1
  98. package/dist/components/loading/unified-skeleton.js +13 -1
  99. package/dist/components/meeting-scheduler/index.cjs +119 -119
  100. package/dist/components/navigation/index.cjs +24 -24
  101. package/dist/components/onboarding-guides/index.cjs +24 -24
  102. package/dist/components/product-demo/index.cjs +33 -33
  103. package/dist/components/related-content/index.cjs +11 -11
  104. package/dist/components/tickets/index.cjs +28 -28
  105. package/dist/components/ui/file-manager/file-manager-table.d.ts.map +1 -1
  106. package/dist/components/ui/file-manager/file-manager-table.js +59 -50
  107. package/dist/components/ui/file-manager/index.cjs +71 -62
  108. package/dist/components/ui/index.cjs +23 -21
  109. package/dist/components/ui/markdown/engine.d.ts +5 -4
  110. package/dist/components/ui/markdown/engine.d.ts.map +1 -1
  111. package/dist/components/ui/markdown/engine.js +15 -15
  112. package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts.map +1 -1
  113. package/dist/components/ui/markdown/rich/rich-markdown-renderer.js +7 -3
  114. package/dist/components/ui/people-cell.d.ts +9 -2
  115. package/dist/components/ui/people-cell.d.ts.map +1 -1
  116. package/dist/components/ui/people-cell.js +39 -23
  117. package/dist/components/ui/progress-bar.d.ts +6 -0
  118. package/dist/components/ui/progress-bar.d.ts.map +1 -1
  119. package/dist/components/ui/progress-bar.js +2 -0
  120. package/dist/components/ui/tag.d.ts +8 -0
  121. package/dist/components/ui/tag.d.ts.map +1 -1
  122. package/dist/components/ui/tag.js +5 -0
  123. package/dist/{devices-7YDIHVVP.cjs → devices-P7M4Z7VP.cjs} +22 -22
  124. package/dist/embed-shims/index.cjs +3 -3
  125. package/dist/embed-shims/next-image.cjs +3 -3
  126. package/dist/{embeddable-chat-Z3S37SSM.cjs → embeddable-chat-322NDFP5.cjs} +94 -94
  127. package/dist/hooks/index.cjs +6 -6
  128. package/dist/index.cjs +32 -30
  129. package/dist/{knowledge-SQVPQSKM.cjs → knowledge-TC7JLOWI.cjs} +22 -22
  130. package/dist/{logs-M5DFGUBC.cjs → logs-GPOYCP4B.cjs} +28 -28
  131. package/dist/{policies-T3VQEJUD.cjs → policies-ZUHOUMDD.cjs} +25 -25
  132. package/dist/{remote-session-EE6477O3.cjs → remote-session-NT4NG7EF.cjs} +10 -10
  133. package/dist/{scripts-JFRFJKPH.cjs → scripts-FOEVDGTH.cjs} +28 -28
  134. package/dist/{software-update-E7W3AAIY.cjs → software-update-XGTMNFST.cjs} +25 -25
  135. package/dist/{tickets-board-44HO3LR4.cjs → tickets-board-7FLGQEHF.cjs} +10 -10
  136. package/dist/types/design-doc.d.ts +45 -8
  137. package/dist/types/design-doc.d.ts.map +1 -1
  138. package/dist/types/doc-source.d.ts +2 -2
  139. package/dist/types/doc-source.d.ts.map +1 -1
  140. package/dist/utils/design-doc-readiness.d.ts +17 -15
  141. package/dist/utils/design-doc-readiness.d.ts.map +1 -1
  142. package/dist/utils/design-doc-readiness.js +5 -11
  143. package/dist/utils/doc-tree-nav.d.ts +7 -0
  144. package/dist/utils/doc-tree-nav.d.ts.map +1 -1
  145. package/dist/utils/doc-tree-nav.js +19 -0
  146. package/dist/utils/index.cjs +30 -13
  147. package/dist/utils/list-url.d.ts.map +1 -1
  148. package/dist/utils/list-url.js +2 -1
  149. package/dist/utils/source-grouping.d.ts +2 -2
  150. package/dist/utils/source-grouping.d.ts.map +1 -1
  151. package/dist/utils/source-grouping.js +1 -1
  152. package/dist/utils/source-icons.d.ts.map +1 -1
  153. package/dist/utils/source-icons.js +3 -0
  154. package/package.json +1 -1
  155. package/src/components/chat/chat-composer.tsx +10 -3
  156. package/src/components/chat/chat-container.tsx +6 -4
  157. package/src/components/chat/entity-cards/design-doc-card.tsx +12 -17
  158. package/src/components/chat/entity-cards/dispatch.tsx +10 -10
  159. package/src/components/docs/doc-viewer.tsx +56 -35
  160. package/src/components/docs/docs-hub-page.tsx +13 -37
  161. package/src/components/docs/use-document-tree.ts +33 -23
  162. package/src/components/embeds/claude-embed.tsx +4 -4
  163. package/src/components/embeds/embed-iframe.tsx +8 -5
  164. package/src/components/embeds/embed-viewer-frame.tsx +35 -15
  165. package/src/components/embeds/figma-embed.tsx +9 -4
  166. package/src/components/embeds/file-download-card.tsx +28 -4
  167. package/src/components/embeds/google-sheets-viewer.tsx +9 -3
  168. package/src/components/embeds/pdf-viewer.tsx +9 -2
  169. package/src/components/features/status-filter-component.tsx +15 -0
  170. package/src/components/layout/back-button.tsx +54 -2
  171. package/src/components/loading/unified-skeleton.tsx +18 -2
  172. package/src/components/ui/file-manager/file-manager-table.tsx +8 -3
  173. package/src/components/ui/markdown/engine.tsx +21 -30
  174. package/src/components/ui/markdown/rich/rich-markdown-renderer.tsx +4 -0
  175. package/src/components/ui/people-cell.tsx +53 -17
  176. package/src/components/ui/progress-bar.tsx +8 -0
  177. package/src/components/ui/tag.tsx +14 -0
  178. package/src/types/design-doc.ts +59 -10
  179. package/src/types/doc-source.ts +2 -2
  180. package/src/utils/design-doc-readiness.ts +22 -23
  181. package/src/utils/doc-tree-nav.ts +27 -0
  182. package/src/utils/list-url.ts +2 -1
  183. package/src/utils/source-grouping.ts +2 -2
  184. package/src/utils/source-icons.ts +3 -0
  185. package/dist/components/docs/skeletons.d.ts +0 -25
  186. package/dist/components/docs/skeletons.d.ts.map +0 -1
  187. package/dist/components/docs/skeletons.js +0 -83
  188. package/src/components/docs/skeletons.tsx +0 -131
@@ -6,7 +6,9 @@ import {
6
6
  stripFolderIndexFromPath,
7
7
  findDocNodeByPath,
8
8
  getDocAncestorNodeIds,
9
- DEFAULT_FOLDER_INDEX_FILE
9
+ DEFAULT_FOLDER_INDEX_FILE,
10
+ findFirstDocPath,
11
+ rootDocumentPath
10
12
  } from "../../utils/doc-tree-nav.js";
11
13
  import { contentFetch } from "../../utils/embed-content-fetch.js";
12
14
  import { navigateSamePageHash, HUB_HEADER_OFFSET_PX } from "../../utils/same-page-hash-nav.js";
@@ -21,21 +23,6 @@ function scrollToContent() {
21
23
  scrollElementIntoView(document.documentElement);
22
24
  }
23
25
  }
24
- function findFirstDocPath(folder) {
25
- const children = folder.children ?? [];
26
- for (const child of children) {
27
- if (child.type === "file" && !child.path.toLowerCase().endsWith(".mmd")) {
28
- return child.path;
29
- }
30
- }
31
- for (const child of children) {
32
- if (child.type === "folder") {
33
- const nested = findFirstDocPath(child);
34
- if (nested) return nested;
35
- }
36
- }
37
- return null;
38
- }
39
26
  function isRecord(value) {
40
27
  return typeof value === "object" && value !== null;
41
28
  }
@@ -127,6 +114,10 @@ function useDocumentTree(config, initialPath) {
127
114
  const folderIndexFile = config.folderIndexFile ?? DEFAULT_FOLDER_INDEX_FILE;
128
115
  const cleanInitialPath = stripFolderIndexFromPath(initialPath?.replace(/\/$/, "") || "", folderIndexFile);
129
116
  const [structure, setStructure] = useState([]);
117
+ const structureRef = useRef(structure);
118
+ useEffect(() => {
119
+ structureRef.current = structure;
120
+ }, [structure]);
130
121
  const [selectedPath, setSelectedPath] = useState(cleanInitialPath);
131
122
  const [content, setContent] = useState(null);
132
123
  const [isLoadingStructure, setIsLoadingStructure] = useState(true);
@@ -239,9 +230,10 @@ function useDocumentTree(config, initialPath) {
239
230
  setContent(null);
240
231
  return;
241
232
  }
242
- const probe = findDocNodeByPath(stripFolderIndexFromPath(path, folderIndexFile), structure);
233
+ const latestStructure = structureRef.current;
234
+ const probe = findDocNodeByPath(stripFolderIndexFromPath(path, folderIndexFile), latestStructure);
243
235
  const probeIsNoReadmeFolder = !!probe && probe.type === "folder" && !probe.hasReadme;
244
- const preStructureFolderLike = structure.length === 0 && !path.endsWith(".md");
236
+ const preStructureFolderLike = latestStructure.length === 0 && !path.endsWith(".md");
245
237
  if (probeIsNoReadmeFolder || preStructureFolderLike) {
246
238
  lastFetchedPath.current = null;
247
239
  setError(null);
@@ -296,7 +288,12 @@ function useDocumentTree(config, initialPath) {
296
288
  if (!structure || structure.length === 0) return;
297
289
  let pathToFetch = null;
298
290
  if (selectedPath === "") {
299
- pathToFetch = folderIndexFile;
291
+ const rootPath = rootDocumentPath(structure, folderIndexFile);
292
+ if (!rootPath) {
293
+ lastFetchedPath.current = null;
294
+ return;
295
+ }
296
+ pathToFetch = rootPath;
300
297
  } else {
301
298
  const node = findDocNodeByPath(selectedPath, structure);
302
299
  if (node && node.type === "folder" && !node.hasReadme) {
@@ -434,18 +431,21 @@ function useDocumentTree(config, initialPath) {
434
431
  });
435
432
  }, [docNavigation, normalizedBaseRoute, structure, selectNode, folderIndexFile]);
436
433
  const isEmptyFolder = useMemo(() => {
437
- if (!selectedPath || structure.length === 0) return false;
434
+ if (structure.length === 0) return false;
435
+ if (!selectedPath) return !rootDocumentPath(structure, folderIndexFile);
438
436
  const node = findDocNodeByPath(selectedPath, structure);
439
437
  return !!node && node.type === "folder" && !node.hasReadme && !findFirstDocPath(node);
440
- }, [selectedPath, structure]);
438
+ }, [selectedPath, structure, folderIndexFile]);
439
+ const isEmptySource = !isLoadingStructure && structure.length === 0;
440
+ const settled = isEmptyFolder || isEmptySource;
441
441
  return {
442
442
  structure,
443
443
  selectedPath,
444
444
  setSelectedPath,
445
- content: isEmptyFolder ? null : content,
445
+ content: settled ? null : content,
446
446
  isLoadingStructure,
447
- isLoadingContent: isEmptyFolder ? false : isLoadingContent,
448
- error: isEmptyFolder ? null : error,
447
+ isLoadingContent: settled ? false : isLoadingContent,
448
+ error: settled ? null : error,
449
449
  expandedNodes,
450
450
  toggleNode,
451
451
  selectNode,
@@ -9,7 +9,7 @@ export interface ClaudeEmbedProps {
9
9
  /** iframe height (CSS value), matching `FigmaEmbed`'s prop. */
10
10
  height?: string;
11
11
  /** iframe loading strategy. Defaults to `lazy`, as `FigmaEmbed` does. */
12
- loading?: 'eager' | 'lazy';
12
+ iframeLoading?: 'eager' | 'lazy';
13
13
  }
14
14
  /**
15
15
  * A Claude artifact, in the SAME chrome as every other embed viewer
@@ -35,5 +35,5 @@ export interface ClaudeEmbedProps {
35
35
  * a Claude Design url (no embed route), an artifact whose author has not
36
36
  * allow-listed this host, or an http host (mixed content).
37
37
  */
38
- export declare function ClaudeEmbed({ url, kind, title, height, loading }: ClaudeEmbedProps): import("react").JSX.Element;
38
+ export declare function ClaudeEmbed({ url, kind, title, height, iframeLoading }: ClaudeEmbedProps): import("react").JSX.Element;
39
39
  //# sourceMappingURL=claude-embed.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"claude-embed.d.ts","sourceRoot":"","sources":["../../../src/components/embeds/claude-embed.tsx"],"names":[],"mappings":"AASA,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,QAAQ,CAAC;AAEpD,MAAM,WAAW,gBAAgB;IAC/B,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,mEAAmE;IACnE,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC5B;AAOD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,WAAW,CAAC,EAAE,GAAG,EAAE,IAAiB,EAAE,KAAK,EAAE,MAAM,EAAE,OAAgB,EAAE,EAAE,gBAAgB,+BAkExG"}
1
+ {"version":3,"file":"claude-embed.d.ts","sourceRoot":"","sources":["../../../src/components/embeds/claude-embed.tsx"],"names":[],"mappings":"AASA,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,QAAQ,CAAC;AAEpD,MAAM,WAAW,gBAAgB;IAC/B,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,mEAAmE;IACnE,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAClC;AAOD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,WAAW,CAAC,EAAE,GAAG,EAAE,IAAiB,EAAE,KAAK,EAAE,MAAM,EAAE,aAAsB,EAAE,EAAE,gBAAgB,+BAkE9G"}
@@ -10,7 +10,7 @@ const KIND_HEADING = {
10
10
  artifact: "Claude Artifact",
11
11
  design: "Claude Design"
12
12
  };
13
- function ClaudeEmbed({ url, kind = "artifact", title, height, loading = "lazy" }) {
13
+ function ClaudeEmbed({ url, kind = "artifact", title, height, iframeLoading = "lazy" }) {
14
14
  const { src: mirrorSrc, status } = useClaudeMirrorSrc(url);
15
15
  const isProbing = status === "probing";
16
16
  const embedUrl = isProbing ? null : mirrorSrc ?? toClaudeEmbedUrl(url);
@@ -21,7 +21,7 @@ function ClaudeEmbed({ url, kind = "artifact", title, height, loading = "lazy" }
21
21
  icon: /* @__PURE__ */ jsx(ClaudeIcon, { className: "h-5 w-5 shrink-0" }),
22
22
  title: title?.trim() || KIND_HEADING[kind],
23
23
  titleVariant: "h6",
24
- isLoading: isProbing,
24
+ resolving: isProbing,
25
25
  actions: /* @__PURE__ */ jsx(
26
26
  Button,
27
27
  {
@@ -37,7 +37,7 @@ function ClaudeEmbed({ url, kind = "artifact", title, height, loading = "lazy" }
37
37
  ),
38
38
  src: embedUrl,
39
39
  fullscreenControl: true,
40
- loading,
40
+ iframeLoading,
41
41
  height,
42
42
  allow: "clipboard-write; clipboard-read; fullscreen",
43
43
  sandbox: mirrorSrc ? "allow-scripts allow-popups allow-forms" : "allow-scripts allow-same-origin allow-popups allow-forms",
@@ -25,8 +25,10 @@ export interface EmbedIframeProps {
25
25
  allow?: string;
26
26
  /** iframe `referrerPolicy` attribute */
27
27
  referrerPolicy?: React.IframeHTMLAttributes<HTMLIFrameElement>['referrerPolicy'];
28
- /** iframe `loading` attribute */
29
- loading?: 'eager' | 'lazy';
28
+ /** iframe `loading` attribute (eager or lazy). */
29
+ iframeLoading?: 'eager' | 'lazy';
30
+ /** The frame's source is not known yet: the frame draws its loading form. A loading scope does the same. */
31
+ loading?: boolean;
30
32
  /** iframe `allowFullScreen` attribute */
31
33
  allowFullScreen?: boolean;
32
34
  /**
@@ -48,5 +50,5 @@ export interface EmbedIframeProps {
48
50
  * - Setting iframe src to about:blank on unmount to release the embedded document
49
51
  * - Resetting loaded state when src changes
50
52
  */
51
- export declare function EmbedIframe({ src, title, className, height, allow, referrerPolicy, loading, allowFullScreen, sandbox, }: EmbedIframeProps): React.JSX.Element;
53
+ export declare function EmbedIframe({ src, title, className, height, allow, referrerPolicy, iframeLoading, loading, allowFullScreen, sandbox, }: EmbedIframeProps): React.JSX.Element;
52
54
  //# sourceMappingURL=embed-iframe.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"embed-iframe.d.ts","sourceRoot":"","sources":["../../../src/components/embeds/embed-iframe.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B;;;;;;;yEAOyE;AACzE,wBAAgB,oBAAoB,CAAC,EACnC,MAAM,EACN,MAAa,GACd,EAAE;IACD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0GAA0G;IAC1G,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,qBAQA;AAED,MAAM,WAAW,gBAAgB;IAC/B,uBAAuB;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,cAAc,CAAC,EAAE,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,CAAC,gBAAgB,CAAC,CAAC;IACjF,iCAAiC;IACjC,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC3B,yCAAyC;IACzC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,EAC1B,GAAG,EACH,KAAK,EACL,SAAS,EACT,MAAM,EACN,KAAK,EACL,cAAc,EACd,OAAO,EACP,eAAe,EACf,OAAO,GACR,EAAE,gBAAgB,qBAqDlB"}
1
+ {"version":3,"file":"embed-iframe.d.ts","sourceRoot":"","sources":["../../../src/components/embeds/embed-iframe.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B;;;;;;;yEAOyE;AACzE,wBAAgB,oBAAoB,CAAC,EACnC,MAAM,EACN,MAAa,GACd,EAAE;IACD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0GAA0G;IAC1G,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,qBAQA;AAED,MAAM,WAAW,gBAAgB;IAC/B,uBAAuB;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,cAAc,CAAC,EAAE,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,CAAC,gBAAgB,CAAC,CAAC;IACjF,kDAAkD;IAClD,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACjC,4GAA4G;IAC5G,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yCAAyC;IACzC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,EAC1B,GAAG,EACH,KAAK,EACL,SAAS,EACT,MAAM,EACN,KAAK,EACL,cAAc,EACd,aAAa,EACb,OAAe,EACf,eAAe,EACf,OAAO,GACR,EAAE,gBAAgB,qBAqDlB"}
@@ -23,7 +23,8 @@ function EmbedIframe({
23
23
  height,
24
24
  allow,
25
25
  referrerPolicy,
26
- loading,
26
+ iframeLoading,
27
+ loading = false,
27
28
  allowFullScreen,
28
29
  sandbox
29
30
  }) {
@@ -31,7 +32,7 @@ function EmbedIframe({
31
32
  const isLoaded = loadedSrc === src;
32
33
  const iframeRef = useRef(null);
33
34
  const handleLoad = useCallback(() => setLoadedSrc(src), [src]);
34
- const held = useLoadingScope();
35
+ const held = useLoadingScope() || loading;
35
36
  useEffect(() => {
36
37
  const iframe = iframeRef.current;
37
38
  return () => {
@@ -62,7 +63,7 @@ function EmbedIframe({
62
63
  onLoad: handleLoad,
63
64
  allow,
64
65
  referrerPolicy,
65
- loading,
66
+ loading: iframeLoading,
66
67
  allowFullScreen: allow?.includes("fullscreen") ? void 0 : allowFullScreen,
67
68
  sandbox
68
69
  },
@@ -30,21 +30,22 @@ export interface EmbedViewerFrameProps {
30
30
  /** Rendered verbatim in the header row — the caller owns its own container/state. */
31
31
  actions?: React.ReactNode;
32
32
  /** Iframe source; falsy renders the inline empty state (or the loading
33
- * state when `isLoading`) under the header. */
33
+ * state when `loading` or `resolving`) under the header. */
34
34
  src: string | null | undefined;
35
- /** While true AND there is no `src` yet, the body shows the shared embed
36
- * loading skeleton (`EmbedLoadingSkeleton`) instead of the empty state —
37
- * for a viewer that is still resolving where its frame points (e.g.
38
- * ClaudeEmbed probing for a self-hosted mirror), so a cold first view
39
- * reads as "loading" (identical to every other embed), not "nothing here". */
40
- isLoading?: boolean;
35
+ /** The document is being read (the standard `loading` prop; a loading scope does the same): the whole frame is its
36
+ * loading form, a skeleton title, its buttons' loading forms and the frame's loading box. */
37
+ loading?: boolean;
38
+ /** The document is known (its title shows) but the viewer is still finding where its frame points (ClaudeEmbed
39
+ * probing for a self-hosted mirror): only the body is its loading box. */
40
+ resolving?: boolean;
41
41
  /** Large icon for the inline empty state (the viewers pass `w-16 h-16`). */
42
42
  emptyIcon?: React.ReactNode;
43
43
  emptyMessage?: string;
44
44
  height?: string;
45
45
  allow?: string;
46
46
  allowFullScreen?: boolean;
47
- loading?: 'eager' | 'lazy';
47
+ /** iframe loading strategy (eager or lazy). */
48
+ iframeLoading?: 'eager' | 'lazy';
48
49
  /** iframe `sandbox` — see `EmbedIframe`. Set it for user-authored contents. */
49
50
  sandbox?: string;
50
51
  /** Wrapper classes; defaults to the sheets/pdf `space-y-4`. */
@@ -56,6 +57,8 @@ export interface EmbedViewerFrameProps {
56
57
  * button (the Figma player) and some cannot (a self-hosted Claude
57
58
  * artifact mirror) — the shell provides the affordance uniformly. */
58
59
  fullscreenControl?: boolean;
60
+ /** How many buttons `actions` draws (PDF: Preview and Download): the header's loading form draws as many. Default 1. */
61
+ loadingActionCount?: number;
59
62
  }
60
- export declare function EmbedViewerFrame({ icon, title, titleVariant, actions, src, isLoading: isLoadingGiven, emptyIcon, emptyMessage, height, allow, allowFullScreen, loading, sandbox, className, fullscreenControl, }: EmbedViewerFrameProps): React.JSX.Element;
63
+ export declare function EmbedViewerFrame({ icon, title, titleVariant, actions, src, loading: loadingGiven, resolving, emptyIcon, emptyMessage, height, allow, allowFullScreen, iframeLoading, sandbox, className, fullscreenControl, loadingActionCount, }: EmbedViewerFrameProps): React.JSX.Element;
61
64
  //# sourceMappingURL=embed-viewer-frame.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"embed-viewer-frame.d.ts","sourceRoot":"","sources":["../../../src/components/embeds/embed-viewer-frame.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAQ/B;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,qBAAqB;IACpC,6EAA6E;IAC7E,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,YAAY,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC3B,qFAAqF;IACrF,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B;oDACgD;IAChD,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAC/B;;;;mFAI+E;IAC/E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC3B,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;0EAKsE;IACtE,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,IAAI,EACJ,KAAK,EACL,YAAmB,EACnB,OAAO,EACP,GAAG,EACH,SAAS,EAAE,cAAc,EACzB,SAAS,EACT,YAAsC,EACtC,MAAM,EACN,KAAK,EACL,eAAe,EACf,OAAO,EACP,OAAO,EACP,SAAS,EACT,iBAAiB,GAClB,EAAE,qBAAqB,qBAgHvB"}
1
+ {"version":3,"file":"embed-viewer-frame.d.ts","sourceRoot":"","sources":["../../../src/components/embeds/embed-viewer-frame.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAS/B;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,qBAAqB;IACpC,6EAA6E;IAC7E,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,YAAY,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC3B,qFAAqF;IACrF,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B;iEAC6D;IAC7D,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAC/B;kGAC8F;IAC9F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;+EAC2E;IAC3E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,+CAA+C;IAC/C,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACjC,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;0EAKsE;IACtE,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,wHAAwH;IACxH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,IAAI,EACJ,KAAK,EACL,YAAmB,EACnB,OAAO,EACP,GAAG,EACH,OAAO,EAAE,YAAoB,EAC7B,SAAiB,EACjB,SAAS,EACT,YAAsC,EACtC,MAAM,EACN,KAAK,EACL,eAAe,EACf,aAAa,EACb,OAAO,EACP,SAAS,EACT,iBAAiB,EACjB,kBAAsB,GACvB,EAAE,qBAAqB,qBA8HvB"}
@@ -4,6 +4,7 @@ import { Maximize2, Minimize2 } from "lucide-react";
4
4
  import { useEffect, useRef, useState } from "react";
5
5
  import { cn } from "../../utils/cn.js";
6
6
  import { FullscreenSwitchController } from "../../utils/fullscreen-switch.js";
7
+ import { InteractiveSkeleton, TextLineSkeleton } from "../loading/unified-skeleton.js";
7
8
  import { Button } from "../ui/button/button.js";
8
9
  import { useLoadingScope } from "../ui/loading-scope.js";
9
10
  import { EmbedIframe, EmbedLoadingSkeleton } from "./embed-iframe.js";
@@ -13,18 +14,21 @@ function EmbedViewerFrame({
13
14
  titleVariant = "h3",
14
15
  actions,
15
16
  src,
16
- isLoading: isLoadingGiven,
17
+ loading: loadingGiven = false,
18
+ resolving = false,
17
19
  emptyIcon,
18
20
  emptyMessage = "Content not available",
19
21
  height,
20
22
  allow,
21
23
  allowFullScreen,
22
- loading,
24
+ iframeLoading,
23
25
  sandbox,
24
26
  className,
25
- fullscreenControl
27
+ fullscreenControl,
28
+ loadingActionCount = 1
26
29
  }) {
27
- const isLoading = useLoadingScope() || !!isLoadingGiven;
30
+ const loadingForm = useLoadingScope() || loadingGiven;
31
+ const isLoading = loadingForm || resolving;
28
32
  const frameRef = useRef(null);
29
33
  const [isFullscreen, setIsFullscreen] = useState(false);
30
34
  const controllerRef = useRef(null);
@@ -57,9 +61,19 @@ function EmbedViewerFrame({
57
61
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 content-sm:flex-row content-sm:items-center content-sm:justify-between", children: [
58
62
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
59
63
  icon,
60
- titleVariant === "h3" ? /* @__PURE__ */ jsx("h2", { className: "truncate text-ods-text-primary text-h3", children: title }) : /* @__PURE__ */ jsx("span", { className: "truncate font-semibold text-ods-text-primary text-h6", children: title })
64
+ loadingForm ? (
65
+ // The document is being read: its name is not known yet. (A viewer still resolving its frame, with a
66
+ // known title, keeps it.)
67
+ /* @__PURE__ */ jsx(TextLineSkeleton, { type: titleVariant, className: "w-48 max-w-full" })
68
+ ) : titleVariant === "h3" ? /* @__PURE__ */ jsx("h2", { className: "truncate text-ods-text-primary text-h3", children: title }) : /* @__PURE__ */ jsx("span", { className: "truncate font-semibold text-ods-text-primary text-h6", children: title })
61
69
  ] }),
62
- fullscreenControl && src ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-[var(--spacing-system-xsf)] content-sm:flex-row content-sm:items-center", children: [
70
+ loadingForm ? (
71
+ // The actions point at the document being read: each its button's loading form, in the actions' row.
72
+ /* @__PURE__ */ jsx("div", { className: "flex w-full items-center gap-2 content-sm:w-auto", children: Array.from({ length: loadingActionCount }, (_, i) => (
73
+ // biome-ignore lint/suspicious/noArrayIndexKey: loading buttons never reorder
74
+ /* @__PURE__ */ jsx(InteractiveSkeleton.Button, { size: "small-legacy", className: "flex-1 content-sm:flex-initial" }, i)
75
+ )) })
76
+ ) : fullscreenControl && src ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-[var(--spacing-system-xsf)] content-sm:flex-row content-sm:items-center", children: [
63
77
  actions,
64
78
  /* @__PURE__ */ jsx(
65
79
  Button,
@@ -83,7 +97,7 @@ function EmbedViewerFrame({
83
97
  height: isFullscreen ? "calc(100vh - 96px)" : height,
84
98
  allow,
85
99
  allowFullScreen,
86
- loading,
100
+ iframeLoading,
87
101
  sandbox
88
102
  }
89
103
  ) : isLoading ? (
@@ -10,7 +10,9 @@ export interface FigmaEmbedProps {
10
10
  */
11
11
  height?: string;
12
12
  /** iframe loading strategy. Defaults to `"lazy"`; the data-room viewer passes `"eager"`. */
13
- loading?: 'eager' | 'lazy';
13
+ iframeLoading?: 'eager' | 'lazy';
14
+ /** The document is being read: the embed draws its loading form. A loading scope does the same. */
15
+ loading?: boolean;
14
16
  }
15
17
  /**
16
18
  * Single source of truth for every Figma surface — the data-room document viewer
@@ -26,5 +28,5 @@ export interface FigmaEmbedProps {
26
28
  * plain ReactNode) — flipping it recomputes `embedSrc`, and the frame's
27
29
  * `EmbedIframe` remounts on the new src exactly as before the extraction.
28
30
  */
29
- export declare function FigmaEmbed({ url, title, height, loading }: FigmaEmbedProps): import("react").JSX.Element;
31
+ export declare function FigmaEmbed({ url, title, height, iframeLoading, loading }: FigmaEmbedProps): import("react").JSX.Element;
30
32
  //# sourceMappingURL=figma-embed.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"figma-embed.d.ts","sourceRoot":"","sources":["../../../src/components/embeds/figma-embed.tsx"],"names":[],"mappings":"AASA,MAAM,WAAW,eAAe;IAC9B,4FAA4F;IAC5F,GAAG,EAAE,MAAM,CAAC;IACZ,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4FAA4F;IAC5F,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC5B;AA8CD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,OAAgB,EAAE,EAAE,eAAe,+BAmDnF"}
1
+ {"version":3,"file":"figma-embed.d.ts","sourceRoot":"","sources":["../../../src/components/embeds/figma-embed.tsx"],"names":[],"mappings":"AASA,MAAM,WAAW,eAAe;IAC9B,4FAA4F;IAC5F,GAAG,EAAE,MAAM,CAAC;IACZ,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4FAA4F;IAC5F,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACjC,mGAAmG;IACnG,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAgDD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,aAAsB,EAAE,OAAe,EAAE,EAAE,eAAe,+BAoD1G"}
@@ -20,7 +20,7 @@ function SlidesViewToggle({ view, onChange }) {
20
20
  if (v && v !== view) onChange(v);
21
21
  },
22
22
  "aria-label": "Figma slides view mode",
23
- className: "flex shrink-0 items-center gap-0.5 rounded-lg border border-ods-border bg-ods-card p-0.5",
23
+ className: "flex h-10 shrink-0 items-center gap-0.5 rounded-lg border border-ods-border bg-ods-card p-0.5",
24
24
  children: options.map(({ key, label, Icon }) => {
25
25
  const active = view === key;
26
26
  return /* @__PURE__ */ jsxs(
@@ -28,7 +28,7 @@ function SlidesViewToggle({ view, onChange }) {
28
28
  {
29
29
  value: key,
30
30
  "aria-label": label,
31
- className: `flex items-center gap-1.5 rounded-md px-2.5 py-1 transition-colors text-h6 ${active ? "bg-ods-accent text-ods-text-on-accent" : "text-ods-text-secondary hover:bg-ods-bg-hover hover:text-ods-text-primary"}`,
31
+ className: `flex h-full items-center gap-1.5 rounded-md px-2.5 py-1 transition-colors text-h6 ${active ? "bg-ods-accent text-ods-text-on-accent" : "text-ods-text-secondary hover:bg-ods-bg-hover hover:text-ods-text-primary"}`,
32
32
  children: [
33
33
  /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0" }),
34
34
  label
@@ -40,7 +40,7 @@ function SlidesViewToggle({ view, onChange }) {
40
40
  }
41
41
  );
42
42
  }
43
- function FigmaEmbed({ url, title, height, loading = "lazy" }) {
43
+ function FigmaEmbed({ url, title, height, iframeLoading = "lazy", loading = false }) {
44
44
  const [view, setView] = useState("present");
45
45
  const isSlides = url ? isFigmaSlidesUrl(url) : false;
46
46
  const embedSrc = url ? toFigmaEmbedUrl(url, { slidesView: view }) : null;
@@ -82,6 +82,7 @@ function FigmaEmbed({ url, title, height, loading = "lazy" }) {
82
82
  ] }),
83
83
  src: embedSrc,
84
84
  allow: "clipboard-write; clipboard-read; fullscreen",
85
+ iframeLoading,
85
86
  loading,
86
87
  height,
87
88
  allowFullScreen: true,
@@ -3,6 +3,8 @@ export interface FileDownloadCardProps {
3
3
  mimeType?: string;
4
4
  fileSize?: number;
5
5
  fileUrl?: string;
6
+ /** The file is being read: the card draws its loading form. A loading scope does the same. */
7
+ loading?: boolean;
6
8
  }
7
9
  /**
8
10
  * Generic downloadable-file card for the `file` document type. Used by
@@ -13,5 +15,5 @@ export interface FileDownloadCardProps {
13
15
  * renders the filename + type + size so the user knows what they were
14
16
  * about to download).
15
17
  */
16
- export declare function FileDownloadCard({ fileName, mimeType, fileSize, fileUrl }: FileDownloadCardProps): import("react").JSX.Element;
18
+ export declare function FileDownloadCard({ fileName, mimeType, fileSize, fileUrl, loading }: FileDownloadCardProps): import("react").JSX.Element;
17
19
  //# sourceMappingURL=file-download-card.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-download-card.d.ts","sourceRoot":"","sources":["../../../src/components/embeds/file-download-card.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,qBAAqB,+BAoBhG"}
1
+ {"version":3,"file":"file-download-card.d.ts","sourceRoot":"","sources":["../../../src/components/embeds/file-download-card.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8FAA8F;IAC9F,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAOD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAe,EAAE,EAAE,qBAAqB,+BAmCjH"}
@@ -2,10 +2,25 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { FileText, Download } from "lucide-react";
4
4
  import { formatFileSize } from "../../utils/index.js";
5
+ import { InteractiveSkeleton, TextLineSkeleton } from "../loading/unified-skeleton.js";
5
6
  import { Button } from "../ui/index.js";
6
- function FileDownloadCard({ fileName, mimeType, fileSize, fileUrl }) {
7
- return /* @__PURE__ */ jsx("div", { className: "flex flex-col items-center justify-center py-16", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-md space-y-4 rounded-xl border border-ods-border bg-ods-card p-8 text-center", children: [
8
- /* @__PURE__ */ jsx(FileText, { className: "mx-auto h-16 w-16 text-ods-text-secondary" }),
7
+ import { useLoadingScope } from "../ui/loading-scope.js";
8
+ const FILE_CARD_OUTER_CLASS = "flex flex-col items-center justify-center py-16";
9
+ const FILE_CARD_CLASS = "w-full max-w-md space-y-4 rounded-xl border border-ods-border bg-ods-card p-8 text-center";
10
+ const FILE_ICON_CLASS = "mx-auto h-16 w-16 text-ods-text-secondary";
11
+ function FileDownloadCard({ fileName, mimeType, fileSize, fileUrl, loading = false }) {
12
+ if (useLoadingScope() || loading) {
13
+ return /* @__PURE__ */ jsx("div", { className: FILE_CARD_OUTER_CLASS, children: /* @__PURE__ */ jsxs("div", { className: FILE_CARD_CLASS, children: [
14
+ /* @__PURE__ */ jsx(FileText, { className: FILE_ICON_CLASS }),
15
+ /* @__PURE__ */ jsxs("div", { children: [
16
+ /* @__PURE__ */ jsx(TextLineSkeleton, { type: "h3", className: "mx-auto w-2/3" }),
17
+ /* @__PURE__ */ jsx(TextLineSkeleton, { type: "h6", className: "mx-auto mt-2 w-1/2" })
18
+ ] }),
19
+ /* @__PURE__ */ jsx(InteractiveSkeleton.Button, { className: "mx-auto" })
20
+ ] }) });
21
+ }
22
+ return /* @__PURE__ */ jsx("div", { className: FILE_CARD_OUTER_CLASS, children: /* @__PURE__ */ jsxs("div", { className: FILE_CARD_CLASS, children: [
23
+ /* @__PURE__ */ jsx(FileText, { className: FILE_ICON_CLASS }),
9
24
  /* @__PURE__ */ jsxs("div", { children: [
10
25
  /* @__PURE__ */ jsx("h3", { className: "text-ods-text-primary text-h3", children: fileName || "File" }),
11
26
  /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center justify-center gap-3 text-ods-text-secondary text-h6", children: [
@@ -2,6 +2,8 @@ export interface GoogleSheetsViewerProps {
2
2
  externalUrl: string;
3
3
  fileName?: string;
4
4
  height?: string;
5
+ /** The document is being read: the viewer draws its loading form. A loading scope does the same. */
6
+ loading?: boolean;
5
7
  }
6
- export declare function GoogleSheetsViewer({ externalUrl, fileName, height }: GoogleSheetsViewerProps): import("react").JSX.Element;
8
+ export declare function GoogleSheetsViewer({ externalUrl, fileName, height, loading }: GoogleSheetsViewerProps): import("react").JSX.Element;
7
9
  //# sourceMappingURL=google-sheets-viewer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"google-sheets-viewer.d.ts","sourceRoot":"","sources":["../../../src/components/embeds/google-sheets-viewer.tsx"],"names":[],"mappings":"AAQA,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,uBAAuB,+BAmC5F"}
1
+ {"version":3,"file":"google-sheets-viewer.d.ts","sourceRoot":"","sources":["../../../src/components/embeds/google-sheets-viewer.tsx"],"names":[],"mappings":"AASA,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oGAAoG;IACpG,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAe,EAAE,EAAE,uBAAuB,+BAsC7G"}
@@ -4,10 +4,12 @@ import { ExternalLink } from "lucide-react";
4
4
  import { toGoogleSheetsEmbedUrl, toGoogleSheetsOriginalUrl } from "../../utils/embed-url-converters.js";
5
5
  import { GoogleSheetsIcon } from "../icons-v2-generated/index.js";
6
6
  import { Button } from "../ui/index.js";
7
+ import { useLoadingScope } from "../ui/loading-scope.js";
7
8
  import { EmbedViewerFrame } from "./embed-viewer-frame.js";
8
- function GoogleSheetsViewer({ externalUrl, fileName, height }) {
9
+ function GoogleSheetsViewer({ externalUrl, fileName, height, loading = false }) {
9
10
  const displayName = fileName || "Google Sheet";
10
- if (!externalUrl) {
11
+ const isLoading = useLoadingScope() || loading;
12
+ if (!externalUrl && !isLoading) {
11
13
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [
12
14
  /* @__PURE__ */ jsx(GoogleSheetsIcon, { className: "mb-4 h-16 w-16 text-ods-text-secondary" }),
13
15
  /* @__PURE__ */ jsx("p", { className: "text-ods-text-secondary", children: "Google Sheet URL not configured" })
@@ -31,8 +33,9 @@ function GoogleSheetsViewer({ externalUrl, fileName, height }) {
31
33
  children: "Open in Google Sheets"
32
34
  }
33
35
  ),
34
- src: toGoogleSheetsEmbedUrl(externalUrl),
35
- height
36
+ src: externalUrl ? toGoogleSheetsEmbedUrl(externalUrl) : null,
37
+ height,
38
+ loading
36
39
  }
37
40
  );
38
41
  }
@@ -4,7 +4,7 @@ require('../../chunk-7SEARXQW.cjs');
4
4
 
5
5
 
6
6
 
7
- var _chunkHBDLJUI3cjs = require('../../chunk-HBDLJUI3.cjs');
7
+ var _chunkC3P7IUTUcjs = require('../../chunk-C3P7IUTU.cjs');
8
8
 
9
9
 
10
10
 
@@ -25,22 +25,22 @@ var _chunkHBDLJUI3cjs = require('../../chunk-HBDLJUI3.cjs');
25
25
 
26
26
 
27
27
 
28
- var _chunkN2A24FDFcjs = require('../../chunk-N2A24FDF.cjs');
29
- require('../../chunk-WCK5T3UD.cjs');
30
- require('../../chunk-744JXTDP.cjs');
31
- require('../../chunk-DGQDGCQH.cjs');
28
+ var _chunkYLETC7LPcjs = require('../../chunk-YLETC7LP.cjs');
29
+ require('../../chunk-IKOL6NJY.cjs');
30
+ require('../../chunk-SA2NNWIJ.cjs');
31
+ require('../../chunk-IHY7E4EX.cjs');
32
32
  require('../../chunk-HQQE74RO.cjs');
33
33
  require('../../chunk-DEY6XXGO.cjs');
34
- require('../../chunk-SSSI2RBV.cjs');
34
+ require('../../chunk-FNQWL4W4.cjs');
35
35
  require('../../chunk-IUDBG3HJ.cjs');
36
36
  require('../../chunk-24I6RFVU.cjs');
37
37
  require('../../chunk-MZ4KBSH7.cjs');
38
- require('../../chunk-5SZ4ILU3.cjs');
39
- require('../../chunk-BZZE4KIV.cjs');
38
+ require('../../chunk-ZZDFMUEU.cjs');
39
+ require('../../chunk-TX4NTH22.cjs');
40
40
  require('../../chunk-OT6P57ZS.cjs');
41
- require('../../chunk-SIFRIUPW.cjs');
42
- require('../../chunk-J2IXYCEH.cjs');
43
- require('../../chunk-6LDL3UZE.cjs');
41
+ require('../../chunk-FXLROQUT.cjs');
42
+ require('../../chunk-3W4ASAXN.cjs');
43
+ require('../../chunk-4YBS4HIO.cjs');
44
44
  require('../../chunk-YJZ7R3W5.cjs');
45
45
  require('../../chunk-PGAMWM6C.cjs');
46
46
  require('../../chunk-46547WQW.cjs');
@@ -1833,7 +1833,7 @@ require('../../chunk-TMVC7S2R.cjs');
1833
1833
  require('../../chunk-IYHH5EIO.cjs');
1834
1834
  require('../../chunk-XIBCMGBW.cjs');
1835
1835
  require('../../chunk-HLJXCOMA.cjs');
1836
- require('../../chunk-XCVH5BCZ.cjs');
1836
+ require('../../chunk-GRCOEICK.cjs');
1837
1837
  require('../../chunk-FW7RKWPY.cjs');
1838
1838
  require('../../chunk-BUEEQD3G.cjs');
1839
1839
  require('../../chunk-JKX2C3I2.cjs');
@@ -1842,11 +1842,11 @@ require('../../chunk-SKI6NKOE.cjs');
1842
1842
  require('../../chunk-EWPR7662.cjs');
1843
1843
  require('../../chunk-IX2E3AN7.cjs');
1844
1844
  require('../../chunk-7DNL734S.cjs');
1845
- require('../../chunk-FLW4WULE.cjs');
1846
- require('../../chunk-ABL5OBSF.cjs');
1847
- require('../../chunk-5GBLXY5F.cjs');
1848
- require('../../chunk-NFNSPLGC.cjs');
1849
- require('../../chunk-BIOSHDXQ.cjs');
1845
+ require('../../chunk-BU2PSRAS.cjs');
1846
+ require('../../chunk-533QK4R6.cjs');
1847
+ require('../../chunk-HTYJUMFB.cjs');
1848
+ require('../../chunk-W7OFAROY.cjs');
1849
+ require('../../chunk-MDLT6D46.cjs');
1850
1850
  require('../../chunk-JBTH3O2C.cjs');
1851
1851
  require('../../chunk-X432Y6RM.cjs');
1852
1852
  require('../../chunk-S5C3OZCO.cjs');
@@ -1860,20 +1860,20 @@ require('../../chunk-BLH2SFOO.cjs');
1860
1860
  require('../../chunk-UAI4VMEF.cjs');
1861
1861
  require('../../chunk-COWMXUHN.cjs');
1862
1862
  require('../../chunk-YSDB6HGQ.cjs');
1863
- require('../../chunk-PZNWIJRZ.cjs');
1863
+ require('../../chunk-TIVPOVIM.cjs');
1864
1864
  require('../../chunk-FX35L4Y6.cjs');
1865
1865
  require('../../chunk-JQCQCE6U.cjs');
1866
1866
  require('../../chunk-NTPX5GXI.cjs');
1867
1867
  require('../../chunk-GI3MMBP3.cjs');
1868
- require('../../chunk-3V5JWG27.cjs');
1868
+ require('../../chunk-GG466XFQ.cjs');
1869
1869
  require('../../chunk-UJ7AXHRZ.cjs');
1870
- require('../../chunk-SWF6TNZ3.cjs');
1870
+ require('../../chunk-ZGEWT3SF.cjs');
1871
1871
  require('../../chunk-N3MBQRCQ.cjs');
1872
1872
  require('../../chunk-S3QXLD4X.cjs');
1873
1873
  require('../../chunk-Z74QE57T.cjs');
1874
1874
  require('../../chunk-XZJCBIYR.cjs');
1875
1875
  require('../../chunk-2XCORRJV.cjs');
1876
- require('../../chunk-3TOWCFIK.cjs');
1876
+ require('../../chunk-PMUCEUFQ.cjs');
1877
1877
  require('../../chunk-FCZAUK4D.cjs');
1878
1878
  require('../../chunk-63A4GZMB.cjs');
1879
1879
  require('../../chunk-V45KLJQP.cjs');
@@ -1901,4 +1901,4 @@ require('../../chunk-QDB2JO55.cjs');
1901
1901
 
1902
1902
 
1903
1903
 
1904
- exports.ClaudeEmbed = _chunkN2A24FDFcjs.ClaudeEmbed; exports.EMBED_SIZES = _chunkN2A24FDFcjs.EMBED_SIZES; exports.EmbedContainer = _chunkN2A24FDFcjs.EmbedContainer; exports.EmbedIframe = _chunkN2A24FDFcjs.EmbedIframe; exports.EmbedViewerFrame = _chunkN2A24FDFcjs.EmbedViewerFrame; exports.FigmaEmbed = _chunkN2A24FDFcjs.FigmaEmbed; exports.FileDownloadCard = _chunkHBDLJUI3cjs.FileDownloadCard; exports.GoogleSheetsViewer = _chunkHBDLJUI3cjs.GoogleSheetsViewer; exports.LinkPreviewContainer = _chunkN2A24FDFcjs.LinkPreviewContainer; exports.LinkedInContainer = _chunkN2A24FDFcjs.LinkedInContainer; exports.LinkedInEmbedClient = _chunkN2A24FDFcjs.LinkedInEmbedClient; exports.MarkdownImage = _chunkN2A24FDFcjs.MarkdownImage; exports.OGLinkErrorBoundary = _chunkN2A24FDFcjs.OGLinkErrorBoundary; exports.OGLinkPreview = _chunkN2A24FDFcjs.OGLinkPreview; exports.PdfViewer = _chunkHBDLJUI3cjs.PdfViewer; exports.RedditContainer = _chunkN2A24FDFcjs.RedditContainer; exports.RedditEmbedClient = _chunkN2A24FDFcjs.RedditEmbedClient; exports.RichMarkdownRuntimeProvider = _chunkN2A24FDFcjs.RichMarkdownRuntimeProvider; exports.TwitterContainer = _chunkN2A24FDFcjs.TwitterContainer; exports.TwitterEmbedClient = _chunkN2A24FDFcjs.TwitterEmbedClient; exports.YouTubeContainer = _chunkN2A24FDFcjs.YouTubeContainer; exports.useRichMarkdownRuntime = _chunkN2A24FDFcjs.useRichMarkdownRuntime;
1904
+ exports.ClaudeEmbed = _chunkYLETC7LPcjs.ClaudeEmbed; exports.EMBED_SIZES = _chunkYLETC7LPcjs.EMBED_SIZES; exports.EmbedContainer = _chunkYLETC7LPcjs.EmbedContainer; exports.EmbedIframe = _chunkYLETC7LPcjs.EmbedIframe; exports.EmbedViewerFrame = _chunkYLETC7LPcjs.EmbedViewerFrame; exports.FigmaEmbed = _chunkYLETC7LPcjs.FigmaEmbed; exports.FileDownloadCard = _chunkC3P7IUTUcjs.FileDownloadCard; exports.GoogleSheetsViewer = _chunkC3P7IUTUcjs.GoogleSheetsViewer; exports.LinkPreviewContainer = _chunkYLETC7LPcjs.LinkPreviewContainer; exports.LinkedInContainer = _chunkYLETC7LPcjs.LinkedInContainer; exports.LinkedInEmbedClient = _chunkYLETC7LPcjs.LinkedInEmbedClient; exports.MarkdownImage = _chunkYLETC7LPcjs.MarkdownImage; exports.OGLinkErrorBoundary = _chunkYLETC7LPcjs.OGLinkErrorBoundary; exports.OGLinkPreview = _chunkYLETC7LPcjs.OGLinkPreview; exports.PdfViewer = _chunkC3P7IUTUcjs.PdfViewer; exports.RedditContainer = _chunkYLETC7LPcjs.RedditContainer; exports.RedditEmbedClient = _chunkYLETC7LPcjs.RedditEmbedClient; exports.RichMarkdownRuntimeProvider = _chunkYLETC7LPcjs.RichMarkdownRuntimeProvider; exports.TwitterContainer = _chunkYLETC7LPcjs.TwitterContainer; exports.TwitterEmbedClient = _chunkYLETC7LPcjs.TwitterEmbedClient; exports.YouTubeContainer = _chunkYLETC7LPcjs.YouTubeContainer; exports.useRichMarkdownRuntime = _chunkYLETC7LPcjs.useRichMarkdownRuntime;
@@ -4,6 +4,8 @@ export interface PdfViewerProps {
4
4
  onPreview?: () => void;
5
5
  onDownload?: () => void;
6
6
  height?: string;
7
+ /** The document is being read: the viewer draws its loading form. A loading scope does the same. */
8
+ loading?: boolean;
7
9
  }
8
- export declare function PdfViewer({ src, fileName, onPreview, onDownload, height }: PdfViewerProps): import("react").JSX.Element;
10
+ export declare function PdfViewer({ src, fileName, onPreview, onDownload, height, loading }: PdfViewerProps): import("react").JSX.Element;
9
11
  //# sourceMappingURL=pdf-viewer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pdf-viewer.d.ts","sourceRoot":"","sources":["../../../src/components/embeds/pdf-viewer.tsx"],"names":[],"mappings":"AAOA,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,SAAS,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,cAAc,+BA+CzF"}
1
+ {"version":3,"file":"pdf-viewer.d.ts","sourceRoot":"","sources":["../../../src/components/embeds/pdf-viewer.tsx"],"names":[],"mappings":"AAQA,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oGAAoG;IACpG,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,SAAS,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,OAAe,EAAE,EAAE,cAAc,+BAmD1G"}
@@ -3,10 +3,12 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { Download, Eye } from "lucide-react";
4
4
  import { AdobePdfIcon } from "../icons-v2-generated/index.js";
5
5
  import { Button } from "../ui/index.js";
6
+ import { useLoadingScope } from "../ui/loading-scope.js";
6
7
  import { EmbedViewerFrame } from "./embed-viewer-frame.js";
7
- function PdfViewer({ src, fileName, onPreview, onDownload, height }) {
8
+ function PdfViewer({ src, fileName, onPreview, onDownload, height, loading = false }) {
8
9
  const displayName = fileName || "PDF Document";
9
- if (!src) {
10
+ const isLoading = useLoadingScope() || loading;
11
+ if (!src && !isLoading) {
10
12
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [
11
13
  /* @__PURE__ */ jsx(AdobePdfIcon, { className: "mb-4 h-16 w-16 text-ods-text-secondary" }),
12
14
  /* @__PURE__ */ jsx("p", { className: "text-ods-text-secondary", children: "PDF file not available" })
@@ -46,6 +48,8 @@ function PdfViewer({ src, fileName, onPreview, onDownload, height }) {
46
48
  )
47
49
  ] }),
48
50
  src,
51
+ loadingActionCount: 2,
52
+ loading,
49
53
  height
50
54
  }
51
55
  );