@mittwald/flow-react-components 1.3.0-next.3 → 1.3.0-next.30

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 (181) hide show
  1. package/AGENTS.md +56 -1
  2. package/MIGRATION.md +24 -0
  3. package/USAGE.md +14 -6
  4. package/dist/assets/component-index.json +1 -1
  5. package/dist/assets/doc-properties.json +1 -1
  6. package/dist/css/all-layered.css +1 -1
  7. package/dist/css/all.css +1 -1
  8. package/dist/js/_virtual/_24.locale.mjs +10 -2
  9. package/dist/js/_virtual/_28.locale.mjs +10 -0
  10. package/dist/js/packages/components/src/components/Accordion/Accordion.mjs +26 -15
  11. package/dist/js/packages/components/src/components/Accordion/Accordion.mjs.map +1 -1
  12. package/dist/js/packages/components/src/components/Avatar/Avatar.module.mjs.map +1 -1
  13. package/dist/js/packages/components/src/components/Calendar/Calendar.module.mjs +3 -1
  14. package/dist/js/packages/components/src/components/Calendar/Calendar.module.mjs.map +1 -1
  15. package/dist/js/packages/components/src/components/Calendar/components/CalendarHeader/CalendarHeader.mjs +19 -15
  16. package/dist/js/packages/components/src/components/Calendar/components/CalendarHeader/CalendarHeader.mjs.map +1 -1
  17. package/dist/js/packages/components/src/components/CartesianChart/CartesianChart.mjs +6 -2
  18. package/dist/js/packages/components/src/components/CartesianChart/CartesianChart.mjs.map +1 -1
  19. package/dist/js/packages/components/src/components/CartesianChart/CartesianChart.module.mjs.map +1 -1
  20. package/dist/js/packages/components/src/components/CartesianChart/components/Area/Area.mjs +7 -2
  21. package/dist/js/packages/components/src/components/CartesianChart/components/Area/Area.mjs.map +1 -1
  22. package/dist/js/packages/components/src/components/CartesianChart/components/Bar/Bar.mjs +15 -3
  23. package/dist/js/packages/components/src/components/CartesianChart/components/Bar/Bar.mjs.map +1 -1
  24. package/dist/js/packages/components/src/components/CartesianChart/components/Line/Line.mjs +4 -1
  25. package/dist/js/packages/components/src/components/CartesianChart/components/Line/Line.mjs.map +1 -1
  26. package/dist/js/packages/components/src/components/CartesianChart/context.mjs +4 -1
  27. package/dist/js/packages/components/src/components/CartesianChart/context.mjs.map +1 -1
  28. package/dist/js/packages/components/src/components/CartesianChart/hooks/useChartAnimation.mjs +27 -0
  29. package/dist/js/packages/components/src/components/CartesianChart/hooks/useChartAnimation.mjs.map +1 -0
  30. package/dist/js/packages/components/src/components/CartesianChart/hooks/useScopedStackId.mjs +16 -0
  31. package/dist/js/packages/components/src/components/CartesianChart/hooks/useScopedStackId.mjs.map +1 -0
  32. package/dist/js/packages/components/src/components/Chat/Chat.module.mjs.map +1 -1
  33. package/dist/js/packages/components/src/components/CheckboxGroup/CheckboxGroup.module.mjs.map +1 -1
  34. package/dist/js/packages/components/src/components/CodeBlock/CodeBlock.mjs.map +1 -1
  35. package/dist/js/packages/components/src/components/CodeBlock/CodeBlock.module.mjs.map +1 -1
  36. package/dist/js/packages/components/src/components/Combine/Combine.mjs +4 -1
  37. package/dist/js/packages/components/src/components/Combine/Combine.mjs.map +1 -1
  38. package/dist/js/packages/components/src/components/Combine/Combine.module.mjs.map +1 -1
  39. package/dist/js/packages/components/src/components/ComboBox/ComboBox.module.mjs.map +1 -1
  40. package/dist/js/packages/components/src/components/ContextMenu/ContextMenu.module.mjs.map +1 -1
  41. package/dist/js/packages/components/src/components/CopyButton/CopyButton.mjs +4 -3
  42. package/dist/js/packages/components/src/components/CopyButton/CopyButton.mjs.map +1 -1
  43. package/dist/js/packages/components/src/components/DateRangePicker/components/DateRangeInput/DateRangeInput.mjs +14 -11
  44. package/dist/js/packages/components/src/components/DateRangePicker/components/DateRangeInput/DateRangeInput.mjs.map +1 -1
  45. package/dist/js/packages/components/src/components/DateRangePicker/components/DateRangeInput/DateRangeInput.module.mjs +4 -2
  46. package/dist/js/packages/components/src/components/DateRangePicker/components/DateRangeInput/DateRangeInput.module.mjs.map +1 -1
  47. package/dist/js/packages/components/src/components/DonutChart/DonutChart.module.mjs +6 -3
  48. package/dist/js/packages/components/src/components/DonutChart/DonutChart.module.mjs.map +1 -1
  49. package/dist/js/packages/components/src/components/DonutChart/components/DonutChartFill.mjs +13 -2
  50. package/dist/js/packages/components/src/components/DonutChart/components/DonutChartFill.mjs.map +1 -1
  51. package/dist/js/packages/components/src/components/FileDropZone/FileDropZone.mjs +2 -2
  52. package/dist/js/packages/components/src/components/FileDropZone/FileDropZone.mjs.map +1 -1
  53. package/dist/js/packages/components/src/components/HeaderNavigation/HeaderNavigation.mjs +9 -6
  54. package/dist/js/packages/components/src/components/HeaderNavigation/HeaderNavigation.mjs.map +1 -1
  55. package/dist/js/packages/components/src/components/HeaderNavigation/HeaderNavigation.module.mjs +5 -1
  56. package/dist/js/packages/components/src/components/HeaderNavigation/HeaderNavigation.module.mjs.map +1 -1
  57. package/dist/js/packages/components/src/components/Heading/Heading.mjs +1 -1
  58. package/dist/js/packages/components/src/components/Heading/Heading.mjs.map +1 -1
  59. package/dist/js/packages/components/src/components/Heading/Heading.module.mjs.map +1 -1
  60. package/dist/js/packages/components/src/components/Image/Image.mjs +2 -2
  61. package/dist/js/packages/components/src/components/Image/Image.mjs.map +1 -1
  62. package/dist/js/packages/components/src/components/Image/Image.module.mjs +3 -1
  63. package/dist/js/packages/components/src/components/Image/Image.module.mjs.map +1 -1
  64. package/dist/js/packages/components/src/components/LayoutCard/LayoutCard.module.mjs.map +1 -1
  65. package/dist/js/packages/components/src/components/LightBox/LightBox.module.mjs.map +1 -1
  66. package/dist/js/packages/components/src/components/LightBox/components/LightBoxGallery/Controls.mjs +1 -1
  67. package/dist/js/packages/components/src/components/LightBox/components/LightBoxGallery/Controls.mjs.map +1 -1
  68. package/dist/js/packages/components/src/components/List/List.module.mjs.map +1 -1
  69. package/dist/js/packages/components/src/components/List/components/Header/components/SearchField/SearchField.module.mjs.map +1 -1
  70. package/dist/js/packages/components/src/components/List/components/Items/Items.mjs +3 -6
  71. package/dist/js/packages/components/src/components/List/components/Items/Items.mjs.map +1 -1
  72. package/dist/js/packages/components/src/components/List/components/Items/components/Item/Item.module.mjs.map +1 -1
  73. package/dist/js/packages/components/src/components/List/components/ListItemView/ListItemView.module.mjs.map +1 -1
  74. package/dist/js/packages/components/src/components/List/components/Table/Table.mjs +6 -2
  75. package/dist/js/packages/components/src/components/List/components/Table/Table.mjs.map +1 -1
  76. package/dist/js/packages/components/src/components/List/{components/Items/hooks → hooks}/useInfiniteScrollTrigger.mjs +11 -12
  77. package/dist/js/packages/components/src/components/List/hooks/useInfiniteScrollTrigger.mjs.map +1 -0
  78. package/dist/js/packages/components/src/components/Markdown/Markdown.module.mjs.map +1 -1
  79. package/dist/js/packages/components/src/components/MarkdownEditor/MarkdownEditor.mjs +16 -3
  80. package/dist/js/packages/components/src/components/MarkdownEditor/MarkdownEditor.mjs.map +1 -1
  81. package/dist/js/packages/components/src/components/MarkdownEditor/MarkdownEditor.module.mjs +4 -2
  82. package/dist/js/packages/components/src/components/MarkdownEditor/MarkdownEditor.module.mjs.map +1 -1
  83. package/dist/js/packages/components/src/components/MarkdownEditor/components/AttachmentButton.mjs +47 -0
  84. package/dist/js/packages/components/src/components/MarkdownEditor/components/AttachmentButton.mjs.map +1 -0
  85. package/dist/js/packages/components/src/components/MarkdownEditor/components/Toolbar.mjs +21 -20
  86. package/dist/js/packages/components/src/components/MarkdownEditor/components/Toolbar.mjs.map +1 -1
  87. package/dist/js/packages/components/src/components/MarkdownEditor/components/ToolbarButton.mjs +1 -1
  88. package/dist/js/packages/components/src/components/MarkdownEditor/components/ToolbarButton.mjs.map +1 -1
  89. package/dist/js/packages/components/src/components/MarkdownEditor/lib/fileUpload.mjs +73 -0
  90. package/dist/js/packages/components/src/components/MarkdownEditor/lib/fileUpload.mjs.map +1 -0
  91. package/dist/js/packages/components/src/components/MarkdownEditor/lib/useFileUpload.mjs +141 -0
  92. package/dist/js/packages/components/src/components/MarkdownEditor/lib/useFileUpload.mjs.map +1 -0
  93. package/dist/js/packages/components/src/components/MessageThread/MessageThread.module.mjs.map +1 -1
  94. package/dist/js/packages/components/src/components/Modal/Modal.module.mjs.map +1 -1
  95. package/dist/js/packages/components/src/components/Navigation/Navigation.module.mjs +3 -1
  96. package/dist/js/packages/components/src/components/Navigation/Navigation.module.mjs.map +1 -1
  97. package/dist/js/packages/components/src/components/Navigation/components/LinkListTunnelExit/LinkListTunnelExit.mjs +5 -1
  98. package/dist/js/packages/components/src/components/Navigation/components/LinkListTunnelExit/LinkListTunnelExit.mjs.map +1 -1
  99. package/dist/js/packages/components/src/components/Navigation/components/NavigationGroup/NavigationGroup.module.mjs.map +1 -1
  100. package/dist/js/packages/components/src/components/NumberField/NumberField.module.mjs.map +1 -1
  101. package/dist/js/packages/components/src/components/Overlay/Overlay.module.mjs +3 -1
  102. package/dist/js/packages/components/src/components/Overlay/Overlay.module.mjs.map +1 -1
  103. package/dist/js/packages/components/src/components/Overlay/components/OverlayContent.mjs +47 -9
  104. package/dist/js/packages/components/src/components/Overlay/components/OverlayContent.mjs.map +1 -1
  105. package/dist/js/packages/components/src/components/PasswordCreationField/PasswordCreationField.mjs +8 -1
  106. package/dist/js/packages/components/src/components/PasswordCreationField/PasswordCreationField.mjs.map +1 -1
  107. package/dist/js/packages/components/src/components/PasswordCreationField/components/PasswordGenerateButton/PasswordGenerateButton.mjs +2 -0
  108. package/dist/js/packages/components/src/components/PasswordCreationField/components/PasswordGenerateButton/PasswordGenerateButton.mjs.map +1 -1
  109. package/dist/js/packages/components/src/components/PasswordCreationField/components/ValidationResultButton/ValidationResultButton.mjs +13 -7
  110. package/dist/js/packages/components/src/components/PasswordCreationField/components/ValidationResultButton/ValidationResultButton.mjs.map +1 -1
  111. package/dist/js/packages/components/src/components/PasswordCreationField/components/ValidationResultButton/ValidationResultButton.module.mjs +9 -0
  112. package/dist/js/packages/components/src/components/PasswordCreationField/components/ValidationResultButton/ValidationResultButton.module.mjs.map +1 -0
  113. package/dist/js/packages/components/src/components/PasswordCreationField/components/ValidationResultEntry/ValidationResultEntry.mjs +12 -4
  114. package/dist/js/packages/components/src/components/PasswordCreationField/components/ValidationResultEntry/ValidationResultEntry.mjs.map +1 -1
  115. package/dist/js/packages/components/src/components/PasswordCreationField/lib/ariaAnnounce.mjs +52 -0
  116. package/dist/js/packages/components/src/components/PasswordCreationField/lib/ariaAnnounce.mjs.map +1 -0
  117. package/dist/js/packages/components/src/components/PasswordCreationField/lib/generateValidationTranslation.mjs +1 -0
  118. package/dist/js/packages/components/src/components/PasswordCreationField/lib/generateValidationTranslation.mjs.map +1 -1
  119. package/dist/js/packages/components/src/components/PasswordCreationField/lib/getStatusFromPolicyValidationResult.mjs +1 -0
  120. package/dist/js/packages/components/src/components/PasswordCreationField/lib/getStatusFromPolicyValidationResult.mjs.map +1 -1
  121. package/dist/js/packages/components/src/components/PasswordCreationField/lib/usePolicyValidationResult.mjs +1 -0
  122. package/dist/js/packages/components/src/components/PasswordCreationField/lib/usePolicyValidationResult.mjs.map +1 -1
  123. package/dist/js/packages/components/src/components/Popover/Popover.module.mjs +3 -1
  124. package/dist/js/packages/components/src/components/Popover/Popover.module.mjs.map +1 -1
  125. package/dist/js/packages/components/src/components/Popover/components/NonModalPopoverContent/NonModalPopoverContent.mjs +20 -11
  126. package/dist/js/packages/components/src/components/Popover/components/NonModalPopoverContent/NonModalPopoverContent.mjs.map +1 -1
  127. package/dist/js/packages/components/src/components/Popover/components/PopoverContent/PopoverContent.mjs +1 -0
  128. package/dist/js/packages/components/src/components/Popover/components/PopoverContent/PopoverContent.mjs.map +1 -1
  129. package/dist/js/packages/components/src/components/ProgressBar/ProgressBar.module.mjs +1 -0
  130. package/dist/js/packages/components/src/components/ProgressBar/ProgressBar.module.mjs.map +1 -1
  131. package/dist/js/packages/components/src/components/SearchField/SearchField.module.mjs +3 -3
  132. package/dist/js/packages/components/src/components/SearchField/SearchField.module.mjs.map +1 -1
  133. package/dist/js/packages/components/src/components/Section/Section.module.mjs.map +1 -1
  134. package/dist/js/packages/components/src/components/Section/components/SectionHeader/SectionHeader.module.mjs.map +1 -1
  135. package/dist/js/packages/components/src/components/Slider/Slider.module.mjs.map +1 -1
  136. package/dist/js/packages/components/src/components/TabNavigation/TabNavigation.mjs +2 -1
  137. package/dist/js/packages/components/src/components/TabNavigation/TabNavigation.mjs.map +1 -1
  138. package/dist/js/packages/components/src/components/TabNavigation/TabNavigation.module.mjs +5 -1
  139. package/dist/js/packages/components/src/components/TabNavigation/TabNavigation.module.mjs.map +1 -1
  140. package/dist/js/packages/components/src/components/Table/Table.module.mjs.map +1 -1
  141. package/dist/js/packages/components/src/components/Tabs/components/TabTitle/TabTitle.module.mjs.map +1 -1
  142. package/dist/js/packages/components/src/components/Text/Text.module.mjs.map +1 -1
  143. package/dist/js/packages/components/src/components/TextArea/TextArea.mjs +14 -13
  144. package/dist/js/packages/components/src/components/TextArea/TextArea.mjs.map +1 -1
  145. package/dist/js/packages/components/src/components/Tooltip/Tooltip.mjs +1 -0
  146. package/dist/js/packages/components/src/components/Tooltip/Tooltip.mjs.map +1 -1
  147. package/dist/js/packages/components/src/components/Tooltip/Tooltip.module.mjs +3 -1
  148. package/dist/js/packages/components/src/components/Tooltip/Tooltip.module.mjs.map +1 -1
  149. package/dist/js/packages/components/src/lib/files/acceptedFiles.mjs +34 -0
  150. package/dist/js/packages/components/src/lib/files/acceptedFiles.mjs.map +1 -0
  151. package/dist/js/packages/components/src/lib/hooks/dom/useKeepBrowserExtensionsInteractive.mjs +1 -1
  152. package/dist/js/packages/components/src/lib/hooks/dom/useKeepBrowserExtensionsInteractive.mjs.map +1 -1
  153. package/dist/js/packages/design-tokens/dist/json-runtime/all-dark.mjs +13 -1
  154. package/dist/js/packages/design-tokens/dist/json-runtime/all-dark.mjs.map +1 -1
  155. package/dist/js/packages/design-tokens/dist/json-runtime/all-light.mjs +13 -1
  156. package/dist/js/packages/design-tokens/dist/json-runtime/all-light.mjs.map +1 -1
  157. package/dist/types/components/CartesianChart/context.d.ts +6 -0
  158. package/dist/types/components/CartesianChart/hooks/useChartAnimation.d.ts +1 -0
  159. package/dist/types/components/CartesianChart/hooks/useScopedStackId.d.ts +6 -0
  160. package/dist/types/components/CopyButton/CopyButton.d.ts +5 -0
  161. package/dist/types/components/Image/Image.d.ts +7 -1
  162. package/dist/types/components/List/hooks/useInfiniteScrollTrigger.d.ts +17 -0
  163. package/dist/types/components/MarkdownEditor/MarkdownEditor.d.ts +20 -0
  164. package/dist/types/components/MarkdownEditor/components/AttachmentButton.d.ts +1 -0
  165. package/dist/types/components/MarkdownEditor/components/Toolbar.d.ts +11 -2
  166. package/dist/types/components/MarkdownEditor/components/ToolbarButton.d.ts +8 -2
  167. package/dist/types/components/MarkdownEditor/index.d.ts +1 -1
  168. package/dist/types/components/MarkdownEditor/lib/fileUpload.d.ts +19 -0
  169. package/dist/types/components/MarkdownEditor/lib/useFileUpload.d.ts +1 -0
  170. package/dist/types/components/PasswordCreationField/components/PasswordGenerateButton/PasswordGenerateButton.d.ts +0 -7
  171. package/dist/types/components/PasswordCreationField/components/ValidationResultButton/ValidationResultButton.d.ts +0 -9
  172. package/dist/types/components/PasswordCreationField/lib/ariaAnnounce.d.ts +1 -0
  173. package/dist/types/components/PasswordCreationField/lib/generateValidationTranslation.d.ts +0 -2
  174. package/dist/types/components/PasswordCreationField/lib/getStatusFromPolicyValidationResult.d.ts +0 -3
  175. package/dist/types/components/PasswordCreationField/lib/usePolicyValidationResult.d.ts +1 -7
  176. package/dist/types/lib/files/acceptedFiles.d.ts +1 -0
  177. package/dist/types/lib/hooks/dom/useKeepBrowserExtensionsInteractive.d.ts +1 -0
  178. package/dist/types/lib/theming/hooks/useDesignTokens.d.ts +14 -0
  179. package/package.json +10 -10
  180. package/dist/js/packages/components/src/components/List/components/Items/hooks/useInfiniteScrollTrigger.mjs.map +0 -1
  181. package/dist/types/components/List/components/Items/hooks/useInfiniteScrollTrigger.d.ts +0 -3
@@ -0,0 +1,73 @@
1
+ "use client"
2
+ /* */
3
+ //#region src/components/MarkdownEditor/lib/fileUpload.ts
4
+ var escapeComment = (fileName) => fileName.replace(/-(?=-)/g, "- ");
5
+ var escapeLinkLabel = (label) => label.replace(/[\\[\]]/g, "\\$&").replace(/\s+/g, " ").trim();
6
+ var escapeLinkTarget = (url) => /[\s()]/.test(url) ? `<${url.replace(/[<>]/g, encodeURIComponent)}>` : url;
7
+ /**
8
+ * What sits at the cursor while a file uploads. An HTML comment, so it stays
9
+ * out of the rendered preview and out of a message sent before the upload
10
+ * finished — the same marker GitHub leaves behind.
11
+ *
12
+ * @internal
13
+ */
14
+ var uploadPlaceholder = (fileName) => `<!-- Uploading "${escapeComment(fileName)}"... -->`;
15
+ /**
16
+ * The markdown a finished upload leaves behind: an embed for an image, a link
17
+ * for everything else.
18
+ *
19
+ * @internal
20
+ */
21
+ var uploadedFileMarkdown = (file, upload) => {
22
+ const link = `[${escapeLinkLabel(upload.name ?? file.name)}](${escapeLinkTarget(upload.url)})`;
23
+ return file.type.startsWith("image/") ? `!${link}` : link;
24
+ };
25
+ /**
26
+ * Splices `insert` into `value` at `caret`, on a line of its own, and reports
27
+ * where the cursor ends up.
28
+ *
29
+ * @internal
30
+ */
31
+ var insertAtCaret = (value, insert, caret) => {
32
+ const before = value.slice(0, caret);
33
+ const after = value.slice(caret);
34
+ const inserted = `${before.length > 0 && !before.endsWith("\n") ? "\n" : ""}${insert}${after.startsWith("\n") ? "" : "\n"}`;
35
+ return {
36
+ value: `${before}${inserted}${after}`,
37
+ selectionStart: caret + inserted.length,
38
+ selectionEnd: caret + inserted.length
39
+ };
40
+ };
41
+ /**
42
+ * Replaces the first occurrence of `search`, carrying the cursor along. Returns
43
+ * `null` when the editor no longer contains it — the user deleted the
44
+ * placeholder while the upload was running, and the result is dropped.
45
+ *
46
+ * @internal
47
+ */
48
+ var replacePlaceholder = (value, search, replacement, selectionStart, selectionEnd) => {
49
+ const index = value.indexOf(search);
50
+ if (index === -1) return null;
51
+ const end = index + search.length;
52
+ const delta = replacement.length - search.length;
53
+ const shift = (position) => {
54
+ if (position <= index) return position;
55
+ return position >= end ? position + delta : index + replacement.length;
56
+ };
57
+ return {
58
+ value: value.slice(0, index) + replacement + value.slice(end),
59
+ selectionStart: shift(selectionStart),
60
+ selectionEnd: shift(selectionEnd)
61
+ };
62
+ };
63
+ /**
64
+ * Takes a placeholder back out after a failed upload, including the line break
65
+ * it was inserted with.
66
+ *
67
+ * @internal
68
+ */
69
+ var removePlaceholder = (value, placeholder, selectionStart, selectionEnd) => replacePlaceholder(value, `${placeholder}\n`, "", selectionStart, selectionEnd) ?? replacePlaceholder(value, placeholder, "", selectionStart, selectionEnd);
70
+ //#endregion
71
+ export { insertAtCaret, removePlaceholder, replacePlaceholder, uploadPlaceholder, uploadedFileMarkdown };
72
+
73
+ //# sourceMappingURL=fileUpload.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileUpload.mjs","names":[],"sources":["../../../../../../../../src/components/MarkdownEditor/lib/fileUpload.ts"],"sourcesContent":["/**\n * The result of uploading a file dropped into, pasted into or selected in a\n * `MarkdownEditor`.\n */\nexport interface MarkdownEditorUpload {\n /** The URL the uploaded file is available at. */\n url: string;\n /**\n * The link text of the inserted markdown — the alt text for an image.\n * Defaults to the name of the uploaded file.\n */\n name?: string;\n}\n\n/** A new editor value together with where the cursor ends up in it. */\nexport interface ValueEdit {\n value: string;\n selectionStart: number;\n selectionEnd: number;\n}\n\n/*\n * A file name that closes the comment early would leave the rest of it as\n * visible text. Every way to close one starts with `--`: `-->` does, `--!>`\n * does too, and dropping only `-->` can forge a new one — `a-->>b` collapses to\n * `a-->b`. Breaking up every `--` is what holds in all three.\n */\nconst escapeComment = (fileName: string) => fileName.replace(/-(?=-)/g, \"- \");\n\n/*\n * Brackets are what ends a markdown link label, and a line break ends the link\n * altogether. A file name may legally contain both.\n *\n * The backslash is escaped in the same pass, and has to be: escaping only the\n * brackets turns `a\\]b` into `a\\\\]b`, which markdown reads as an escaped\n * backslash followed by a bare `]` — the label ends right there.\n */\nconst escapeLinkLabel = (label: string) =>\n label\n .replace(/[\\\\[\\]]/g, \"\\\\$&\")\n .replace(/\\s+/g, \" \")\n .trim();\n\n/*\n * A bare link target ends at the first whitespace or unbalanced parenthesis.\n * Pointy brackets take a target that contains either — and are themselves the\n * only thing that cannot appear inside them.\n */\nconst escapeLinkTarget = (url: string) =>\n /[\\s()]/.test(url) ? `<${url.replace(/[<>]/g, encodeURIComponent)}>` : url;\n\n/**\n * What sits at the cursor while a file uploads. An HTML comment, so it stays\n * out of the rendered preview and out of a message sent before the upload\n * finished — the same marker GitHub leaves behind.\n *\n * @internal\n */\nexport const uploadPlaceholder = (fileName: string): string =>\n `<!-- Uploading \"${escapeComment(fileName)}\"... -->`;\n\n/**\n * The markdown a finished upload leaves behind: an embed for an image, a link\n * for everything else.\n *\n * @internal\n */\nexport const uploadedFileMarkdown = (\n file: Pick<File, \"name\" | \"type\">,\n upload: MarkdownEditorUpload,\n): string => {\n const label = escapeLinkLabel(upload.name ?? file.name);\n const target = escapeLinkTarget(upload.url);\n const link = `[${label}](${target})`;\n\n return file.type.startsWith(\"image/\") ? `!${link}` : link;\n};\n\n/**\n * Splices `insert` into `value` at `caret`, on a line of its own, and reports\n * where the cursor ends up.\n *\n * @internal\n */\nexport const insertAtCaret = (\n value: string,\n insert: string,\n caret: number,\n): ValueEdit => {\n const before = value.slice(0, caret);\n const after = value.slice(caret);\n\n const prefix = before.length > 0 && !before.endsWith(\"\\n\") ? \"\\n\" : \"\";\n const suffix = after.startsWith(\"\\n\") ? \"\" : \"\\n\";\n const inserted = `${prefix}${insert}${suffix}`;\n\n return {\n value: `${before}${inserted}${after}`,\n selectionStart: caret + inserted.length,\n selectionEnd: caret + inserted.length,\n };\n};\n\n/**\n * Replaces the first occurrence of `search`, carrying the cursor along. Returns\n * `null` when the editor no longer contains it — the user deleted the\n * placeholder while the upload was running, and the result is dropped.\n *\n * @internal\n */\nexport const replacePlaceholder = (\n value: string,\n search: string,\n replacement: string,\n selectionStart: number,\n selectionEnd: number,\n): ValueEdit | null => {\n const index = value.indexOf(search);\n\n if (index === -1) {\n return null;\n }\n\n const end = index + search.length;\n const delta = replacement.length - search.length;\n\n /*\n * A cursor before the placeholder does not move, one after it moves by the\n * length difference, and one inside it — the user was typing where the file\n * landed — comes to rest behind the replacement.\n */\n const shift = (position: number) => {\n if (position <= index) {\n return position;\n }\n return position >= end ? position + delta : index + replacement.length;\n };\n\n return {\n value: value.slice(0, index) + replacement + value.slice(end),\n selectionStart: shift(selectionStart),\n selectionEnd: shift(selectionEnd),\n };\n};\n\n/**\n * Takes a placeholder back out after a failed upload, including the line break\n * it was inserted with.\n *\n * @internal\n */\nexport const removePlaceholder = (\n value: string,\n placeholder: string,\n selectionStart: number,\n selectionEnd: number,\n): ValueEdit | null =>\n replacePlaceholder(\n value,\n `${placeholder}\\n`,\n \"\",\n selectionStart,\n selectionEnd,\n ) ?? replacePlaceholder(value, placeholder, \"\", selectionStart, selectionEnd);\n"],"mappings":";;;AA2BA,IAAM,iBAAiB,aAAqB,SAAS,QAAQ,WAAW,IAAI;AAU5E,IAAM,mBAAmB,UACvB,MACG,QAAQ,YAAY,MAAM,CAAC,CAC3B,QAAQ,QAAQ,GAAG,CAAC,CACpB,KAAK;AAOV,IAAM,oBAAoB,QACxB,SAAS,KAAK,GAAG,IAAI,IAAI,IAAI,QAAQ,SAAS,kBAAkB,EAAE,KAAK;;;;;;;;AASzE,IAAa,qBAAqB,aAChC,mBAAmB,cAAc,QAAQ,EAAE;;;;;;;AAQ7C,IAAa,wBACX,MACA,WACW;CAGX,MAAM,OAAO,IAFC,gBAAgB,OAAO,QAAQ,KAAK,IAEjC,EAAM,IADR,iBAAiB,OAAO,GACZ,EAAO;CAElC,OAAO,KAAK,KAAK,WAAW,QAAQ,IAAI,IAAI,SAAS;AACvD;;;;;;;AAQA,IAAa,iBACX,OACA,QACA,UACc;CACd,MAAM,SAAS,MAAM,MAAM,GAAG,KAAK;CACnC,MAAM,QAAQ,MAAM,MAAM,KAAK;CAI/B,MAAM,WAAW,GAFF,OAAO,SAAS,KAAK,CAAC,OAAO,SAAS,IAAI,IAAI,OAAO,KAEvC,SADd,MAAM,WAAW,IAAI,IAAI,KAAK;CAG7C,OAAO;EACL,OAAO,GAAG,SAAS,WAAW;EAC9B,gBAAgB,QAAQ,SAAS;EACjC,cAAc,QAAQ,SAAS;CACjC;AACF;;;;;;;;AASA,IAAa,sBACX,OACA,QACA,aACA,gBACA,iBACqB;CACrB,MAAM,QAAQ,MAAM,QAAQ,MAAM;CAElC,IAAI,UAAU,IACZ,OAAO;CAGT,MAAM,MAAM,QAAQ,OAAO;CAC3B,MAAM,QAAQ,YAAY,SAAS,OAAO;CAO1C,MAAM,SAAS,aAAqB;EAClC,IAAI,YAAY,OACd,OAAO;EAET,OAAO,YAAY,MAAM,WAAW,QAAQ,QAAQ,YAAY;CAClE;CAEA,OAAO;EACL,OAAO,MAAM,MAAM,GAAG,KAAK,IAAI,cAAc,MAAM,MAAM,GAAG;EAC5D,gBAAgB,MAAM,cAAc;EACpC,cAAc,MAAM,YAAY;CAClC;AACF;;;;;;;AAQA,IAAa,qBACX,OACA,aACA,gBACA,iBAEA,mBACE,OACA,GAAG,YAAY,KACf,IACA,gBACA,YACF,KAAK,mBAAmB,OAAO,aAAa,IAAI,gBAAgB,YAAY"}
@@ -0,0 +1,141 @@
1
+ "use client"
2
+ /* */
3
+ import { useLocalizedStringFormatter } from "../../TranslationProvider/useLocalizedStringFormatter.mjs";
4
+ import { matchesAccept } from "../../../lib/files/acceptedFiles.mjs";
5
+ import __locale_default from "../../../../../../_virtual/_24.locale.mjs";
6
+ import { insertAtCaret, removePlaceholder, replacePlaceholder, uploadPlaceholder, uploadedFileMarkdown } from "./fileUpload.mjs";
7
+ import { useEffect, useRef, useState } from "react";
8
+ import { announce } from "@react-aria/live-announcer";
9
+ //#region src/components/MarkdownEditor/lib/useFileUpload.ts
10
+ /** The files of a paste or a drop that `accept` covers. */
11
+ var acceptedFrom = (files, accept) => Array.from(files ?? []).filter((file) => matchesAccept(file, accept));
12
+ /**
13
+ * Drop, paste and toolbar uploads for the `MarkdownEditor`: a placeholder
14
+ * comment goes to the cursor right away and is replaced by a markdown link once
15
+ * the consumer's upload resolves.
16
+ *
17
+ * @internal
18
+ */
19
+ var useFileUpload = (options) => {
20
+ const { accept, uploadFile, isDisabled, value, onChange, textAreaRef } = options;
21
+ const stringFormatter = useLocalizedStringFormatter(__locale_default, "MarkdownEditor");
22
+ const isEnabled = !!uploadFile && !isDisabled;
23
+ const valueRef = useRef(value);
24
+ useEffect(() => {
25
+ valueRef.current = value;
26
+ }, [value]);
27
+ const readValue = () => textAreaRef.current?.value ?? valueRef.current;
28
+ const wasFocusedRef = useRef(false);
29
+ useEffect(() => {
30
+ const textArea = textAreaRef.current;
31
+ if (!textArea) return;
32
+ const rememberFocus = () => {
33
+ wasFocusedRef.current = true;
34
+ };
35
+ textArea.addEventListener("focus", rememberFocus);
36
+ return () => textArea.removeEventListener("focus", rememberFocus);
37
+ }, [textAreaRef]);
38
+ const readSelection = () => {
39
+ const textArea = textAreaRef.current;
40
+ const fallback = readValue().length;
41
+ return {
42
+ start: textArea?.selectionStart ?? fallback,
43
+ end: textArea?.selectionEnd ?? fallback
44
+ };
45
+ };
46
+ const applyEdit = (edit) => {
47
+ valueRef.current = edit.value;
48
+ const textArea = textAreaRef.current;
49
+ if (textArea) {
50
+ textArea.value = edit.value;
51
+ requestAnimationFrame(() => {
52
+ textArea.setSelectionRange(edit.selectionStart, edit.selectionEnd);
53
+ });
54
+ }
55
+ onChange(edit.value);
56
+ };
57
+ const runUpload = async (upload, file, placeholder) => {
58
+ announce(stringFormatter.format("upload.pending", { name: file.name }), "polite");
59
+ try {
60
+ const result = await upload(file);
61
+ const selection = readSelection();
62
+ const edit = replacePlaceholder(readValue(), placeholder, uploadedFileMarkdown(file, result), selection.start, selection.end);
63
+ if (edit) applyEdit(edit);
64
+ announce(stringFormatter.format("upload.succeeded", { name: file.name }), "polite");
65
+ } catch {
66
+ const selection = readSelection();
67
+ const edit = removePlaceholder(readValue(), placeholder, selection.start, selection.end);
68
+ if (edit) applyEdit(edit);
69
+ announce(stringFormatter.format("upload.failed", { name: file.name }), "assertive");
70
+ }
71
+ };
72
+ const uploadFiles = (files) => {
73
+ if (!uploadFile || files.length === 0) return;
74
+ const textArea = textAreaRef.current;
75
+ const caret = textArea && wasFocusedRef.current ? textArea.selectionStart : readValue().length;
76
+ const uploads = files.map((file) => ({
77
+ file,
78
+ placeholder: uploadPlaceholder(file.name)
79
+ }));
80
+ applyEdit(insertAtCaret(readValue(), uploads.map((upload) => upload.placeholder).join("\n"), caret));
81
+ textArea?.focus();
82
+ for (const upload of uploads) runUpload(uploadFile, upload.file, upload.placeholder);
83
+ };
84
+ const uploadFilesRef = useRef(uploadFiles);
85
+ uploadFilesRef.current = uploadFiles;
86
+ useEffect(() => {
87
+ const textArea = textAreaRef.current;
88
+ if (!textArea || !isEnabled) return;
89
+ const handlePaste = (event) => {
90
+ const files = acceptedFrom(event.clipboardData?.files, accept);
91
+ if (files.length === 0) return;
92
+ event.preventDefault();
93
+ uploadFilesRef.current(files);
94
+ };
95
+ textArea.addEventListener("paste", handlePaste);
96
+ return () => textArea.removeEventListener("paste", handlePaste);
97
+ }, [
98
+ isEnabled,
99
+ accept,
100
+ textAreaRef
101
+ ]);
102
+ const dragHasFiles = (event) => event.dataTransfer.types.includes("Files");
103
+ const dragDepth = useRef(0);
104
+ const [isDropTarget, setIsDropTarget] = useState(false);
105
+ const endDrag = () => {
106
+ dragDepth.current = 0;
107
+ setIsDropTarget(false);
108
+ };
109
+ return {
110
+ dropProps: isEnabled ? {
111
+ onDragEnter: (event) => {
112
+ if (!dragHasFiles(event)) return;
113
+ event.preventDefault();
114
+ dragDepth.current += 1;
115
+ setIsDropTarget(true);
116
+ },
117
+ onDragOver: (event) => {
118
+ if (!dragHasFiles(event)) return;
119
+ event.preventDefault();
120
+ event.dataTransfer.dropEffect = "copy";
121
+ },
122
+ onDragLeave: (event) => {
123
+ if (!dragHasFiles(event)) return;
124
+ dragDepth.current = Math.max(0, dragDepth.current - 1);
125
+ if (dragDepth.current === 0) setIsDropTarget(false);
126
+ },
127
+ onDrop: (event) => {
128
+ if (!dragHasFiles(event)) return;
129
+ event.preventDefault();
130
+ endDrag();
131
+ uploadFilesRef.current(acceptedFrom(event.dataTransfer.files, accept));
132
+ }
133
+ } : {},
134
+ isDropTarget,
135
+ uploadFiles: uploadFile ? uploadFiles : void 0
136
+ };
137
+ };
138
+ //#endregion
139
+ export { useFileUpload };
140
+
141
+ //# sourceMappingURL=useFileUpload.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFileUpload.mjs","names":[],"sources":["../../../../../../../../src/components/MarkdownEditor/lib/useFileUpload.ts"],"sourcesContent":["import {\n type DragEvent as ReactDragEvent,\n type DragEventHandler,\n type RefObject,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport { announce } from \"@react-aria/live-announcer\";\nimport { matchesAccept } from \"@/lib/files/acceptedFiles\";\nimport {\n insertAtCaret,\n type MarkdownEditorUpload,\n removePlaceholder,\n replacePlaceholder,\n uploadedFileMarkdown,\n uploadPlaceholder,\n type ValueEdit,\n} from \"@/components/MarkdownEditor/lib/fileUpload\";\nimport { useLocalizedStringFormatter } from \"@/components/TranslationProvider/useLocalizedStringFormatter\";\nimport locales from \"../locales/*.locale.json\";\n\n/** The files of a paste or a drop that `accept` covers. */\nconst acceptedFrom = (files: FileList | null | undefined, accept?: string) =>\n Array.from(files ?? []).filter((file) => matchesAccept(file, accept));\n\n/** @internal */\nexport type MarkdownEditorFileUploadHandler = (\n file: File,\n) => Promise<MarkdownEditorUpload>;\n\ninterface Options {\n /** Which file types to take, as an `accept` attribute value. */\n accept?: string;\n /** Uploads one file. Uploading is off entirely while this is undefined. */\n uploadFile?: MarkdownEditorFileUploadHandler;\n /** Turns dropping, pasting and the toolbar button off. */\n isDisabled?: boolean;\n value: string;\n onChange: (value: string) => void;\n textAreaRef: RefObject<HTMLTextAreaElement | null>;\n}\n\ninterface DropProps {\n onDragEnter?: DragEventHandler;\n onDragOver?: DragEventHandler;\n onDragLeave?: DragEventHandler;\n onDrop?: DragEventHandler;\n}\n\ninterface FileUpload {\n /** Drop handling for the editor's root element. */\n dropProps: DropProps;\n /** Whether a drag is currently over the editor. */\n isDropTarget: boolean;\n /**\n * Uploads the files the toolbar button picked. `undefined` while uploading is\n * off.\n */\n uploadFiles?: (files: File[]) => void;\n}\n\n/**\n * Drop, paste and toolbar uploads for the `MarkdownEditor`: a placeholder\n * comment goes to the cursor right away and is replaced by a markdown link once\n * the consumer's upload resolves.\n *\n * @internal\n */\nexport const useFileUpload = (options: Options): FileUpload => {\n const { accept, uploadFile, isDisabled, value, onChange, textAreaRef } =\n options;\n\n const stringFormatter = useLocalizedStringFormatter(\n locales,\n \"MarkdownEditor\",\n );\n\n const isEnabled = !!uploadFile && !isDisabled;\n\n /*\n * An upload resolves long after the drop, and the editor has kept taking\n * input in the meantime. The textarea's own value is the only one that is\n * current by then — the `value` prop is a render old whenever two uploads\n * resolve in the same tick.\n */\n const valueRef = useRef(value);\n useEffect(() => {\n valueRef.current = value;\n }, [value]);\n\n const readValue = () => textAreaRef.current?.value ?? valueRef.current;\n\n /*\n * A textarea keeps its `selectionStart` after losing focus, so the caret is\n * still readable once the attachment button has taken the focus away. What it\n * cannot tell apart is a caret at position 0 from one that was never placed —\n * both read 0, and appending is the better answer for the second.\n */\n const wasFocusedRef = useRef(false);\n useEffect(() => {\n const textArea = textAreaRef.current;\n\n if (!textArea) {\n return;\n }\n\n const rememberFocus = () => {\n wasFocusedRef.current = true;\n };\n\n textArea.addEventListener(\"focus\", rememberFocus);\n return () => textArea.removeEventListener(\"focus\", rememberFocus);\n }, [textAreaRef]);\n\n const readSelection = () => {\n const textArea = textAreaRef.current;\n const fallback = readValue().length;\n return {\n start: textArea?.selectionStart ?? fallback,\n end: textArea?.selectionEnd ?? fallback,\n };\n };\n\n const applyEdit = (edit: ValueEdit) => {\n valueRef.current = edit.value;\n\n const textArea = textAreaRef.current;\n if (textArea) {\n /*\n * Written before the re-render so an upload resolving in the same tick\n * reads this one's result, and the selection restored after it, once\n * React has rendered the controlled value.\n */\n textArea.value = edit.value;\n requestAnimationFrame(() => {\n textArea.setSelectionRange(edit.selectionStart, edit.selectionEnd);\n });\n }\n\n onChange(edit.value);\n };\n\n const runUpload = async (\n upload: MarkdownEditorFileUploadHandler,\n file: File,\n placeholder: string,\n ) => {\n announce(\n stringFormatter.format(\"upload.pending\", { name: file.name }),\n \"polite\",\n );\n\n try {\n const result = await upload(file);\n const selection = readSelection();\n const edit = replacePlaceholder(\n readValue(),\n placeholder,\n uploadedFileMarkdown(file, result),\n selection.start,\n selection.end,\n );\n\n if (edit) {\n applyEdit(edit);\n }\n\n announce(\n stringFormatter.format(\"upload.succeeded\", { name: file.name }),\n \"polite\",\n );\n } catch {\n const selection = readSelection();\n const edit = removePlaceholder(\n readValue(),\n placeholder,\n selection.start,\n selection.end,\n );\n\n if (edit) {\n applyEdit(edit);\n }\n\n announce(\n stringFormatter.format(\"upload.failed\", { name: file.name }),\n \"assertive\",\n );\n }\n };\n\n const uploadFiles = (files: File[]) => {\n if (!uploadFile || files.length === 0) {\n return;\n }\n\n const textArea = textAreaRef.current;\n const caret =\n textArea && wasFocusedRef.current\n ? textArea.selectionStart\n : readValue().length;\n\n const uploads = files.map((file) => ({\n file,\n placeholder: uploadPlaceholder(file.name),\n }));\n\n applyEdit(\n insertAtCaret(\n readValue(),\n uploads.map((upload) => upload.placeholder).join(\"\\n\"),\n caret,\n ),\n );\n textArea?.focus();\n\n for (const upload of uploads) {\n void runUpload(uploadFile, upload.file, upload.placeholder);\n }\n };\n\n const uploadFilesRef = useRef(uploadFiles);\n uploadFilesRef.current = uploadFiles;\n\n /*\n * The paste listener is attached imperatively rather than through react-aria's\n * `useClipboard`, which calls `preventDefault` on every paste it sees and\n * would take plain text pasting with it.\n */\n useEffect(() => {\n const textArea = textAreaRef.current;\n\n if (!textArea || !isEnabled) {\n return;\n }\n\n const handlePaste = (event: ClipboardEvent) => {\n const files = acceptedFrom(event.clipboardData?.files, accept);\n\n if (files.length === 0) {\n return;\n }\n\n event.preventDefault();\n uploadFilesRef.current(files);\n };\n\n textArea.addEventListener(\"paste\", handlePaste);\n return () => textArea.removeEventListener(\"paste\", handlePaste);\n }, [isEnabled, accept, textAreaRef]);\n\n /*\n * Native handlers rather than react-aria's `useDrop`: that one claims every\n * drag it sees — its `onDrop` calls `preventDefault` and `stopPropagation`\n * unconditionally — which is right for a dedicated drop surface like\n * `FileDropZone`, but here it would swallow a plain text drag into the\n * textarea. A drag is claimed only when it carries files, and\n * `dataTransfer.types` is the only place that says so: the public\n * `DragTypes` of `getDropOperation` offers just `has(type)`, which cannot\n * express it — the wildcard type is always true, and WebKit reports every\n * file drag as an unknown type that answers true to anything.\n */\n const dragHasFiles = (event: ReactDragEvent) =>\n event.dataTransfer.types.includes(\"Files\");\n\n /*\n * `dragenter` and `dragleave` fire again for every child the pointer\n * crosses, so the highlight follows their balance rather than the last one.\n */\n const dragDepth = useRef(0);\n const [isDropTarget, setIsDropTarget] = useState(false);\n\n const endDrag = () => {\n dragDepth.current = 0;\n setIsDropTarget(false);\n };\n\n const dropProps: DropProps = isEnabled\n ? {\n onDragEnter: (event) => {\n if (!dragHasFiles(event)) {\n return;\n }\n event.preventDefault();\n dragDepth.current += 1;\n setIsDropTarget(true);\n },\n onDragOver: (event) => {\n if (!dragHasFiles(event)) {\n return;\n }\n // Without this the browser refuses the drop and opens the file instead.\n event.preventDefault();\n event.dataTransfer.dropEffect = \"copy\";\n },\n onDragLeave: (event) => {\n if (!dragHasFiles(event)) {\n return;\n }\n dragDepth.current = Math.max(0, dragDepth.current - 1);\n if (dragDepth.current === 0) {\n setIsDropTarget(false);\n }\n },\n onDrop: (event) => {\n if (!dragHasFiles(event)) {\n return;\n }\n event.preventDefault();\n endDrag();\n uploadFilesRef.current(\n acceptedFrom(event.dataTransfer.files, accept),\n );\n },\n }\n : {};\n\n return {\n dropProps,\n isDropTarget,\n uploadFiles: uploadFile ? uploadFiles : undefined,\n };\n};\n"],"mappings":";;;;;;;;;;AAuBA,IAAM,gBAAgB,OAAoC,WACxD,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,SAAS,cAAc,MAAM,MAAM,CAAC;;;;;;;;AA6CtE,IAAa,iBAAiB,YAAiC;CAC7D,MAAM,EAAE,QAAQ,YAAY,YAAY,OAAO,UAAU,gBACvD;CAEF,MAAM,kBAAkB,4BACtB,kBACA,gBACF;CAEA,MAAM,YAAY,CAAC,CAAC,cAAc,CAAC;CAQnC,MAAM,WAAW,OAAO,KAAK;CAC7B,gBAAgB;EACd,SAAS,UAAU;CACrB,GAAG,CAAC,KAAK,CAAC;CAEV,MAAM,kBAAkB,YAAY,SAAS,SAAS,SAAS;CAQ/D,MAAM,gBAAgB,OAAO,KAAK;CAClC,gBAAgB;EACd,MAAM,WAAW,YAAY;EAE7B,IAAI,CAAC,UACH;EAGF,MAAM,sBAAsB;GAC1B,cAAc,UAAU;EAC1B;EAEA,SAAS,iBAAiB,SAAS,aAAa;EAChD,aAAa,SAAS,oBAAoB,SAAS,aAAa;CAClE,GAAG,CAAC,WAAW,CAAC;CAEhB,MAAM,sBAAsB;EAC1B,MAAM,WAAW,YAAY;EAC7B,MAAM,WAAW,UAAU,CAAC,CAAC;EAC7B,OAAO;GACL,OAAO,UAAU,kBAAkB;GACnC,KAAK,UAAU,gBAAgB;EACjC;CACF;CAEA,MAAM,aAAa,SAAoB;EACrC,SAAS,UAAU,KAAK;EAExB,MAAM,WAAW,YAAY;EAC7B,IAAI,UAAU;GAMZ,SAAS,QAAQ,KAAK;GACtB,4BAA4B;IAC1B,SAAS,kBAAkB,KAAK,gBAAgB,KAAK,YAAY;GACnE,CAAC;EACH;EAEA,SAAS,KAAK,KAAK;CACrB;CAEA,MAAM,YAAY,OAChB,QACA,MACA,gBACG;EACH,SACE,gBAAgB,OAAO,kBAAkB,EAAE,MAAM,KAAK,KAAK,CAAC,GAC5D,QACF;EAEA,IAAI;GACF,MAAM,SAAS,MAAM,OAAO,IAAI;GAChC,MAAM,YAAY,cAAc;GAChC,MAAM,OAAO,mBACX,UAAU,GACV,aACA,qBAAqB,MAAM,MAAM,GACjC,UAAU,OACV,UAAU,GACZ;GAEA,IAAI,MACF,UAAU,IAAI;GAGhB,SACE,gBAAgB,OAAO,oBAAoB,EAAE,MAAM,KAAK,KAAK,CAAC,GAC9D,QACF;EACF,QAAQ;GACN,MAAM,YAAY,cAAc;GAChC,MAAM,OAAO,kBACX,UAAU,GACV,aACA,UAAU,OACV,UAAU,GACZ;GAEA,IAAI,MACF,UAAU,IAAI;GAGhB,SACE,gBAAgB,OAAO,iBAAiB,EAAE,MAAM,KAAK,KAAK,CAAC,GAC3D,WACF;EACF;CACF;CAEA,MAAM,eAAe,UAAkB;EACrC,IAAI,CAAC,cAAc,MAAM,WAAW,GAClC;EAGF,MAAM,WAAW,YAAY;EAC7B,MAAM,QACJ,YAAY,cAAc,UACtB,SAAS,iBACT,UAAU,CAAC,CAAC;EAElB,MAAM,UAAU,MAAM,KAAK,UAAU;GACnC;GACA,aAAa,kBAAkB,KAAK,IAAI;EAC1C,EAAE;EAEF,UACE,cACE,UAAU,GACV,QAAQ,KAAK,WAAW,OAAO,WAAW,CAAC,CAAC,KAAK,IAAI,GACrD,KACF,CACF;EACA,UAAU,MAAM;EAEhB,KAAK,MAAM,UAAU,SACnB,UAAe,YAAY,OAAO,MAAM,OAAO,WAAW;CAE9D;CAEA,MAAM,iBAAiB,OAAO,WAAW;CACzC,eAAe,UAAU;CAOzB,gBAAgB;EACd,MAAM,WAAW,YAAY;EAE7B,IAAI,CAAC,YAAY,CAAC,WAChB;EAGF,MAAM,eAAe,UAA0B;GAC7C,MAAM,QAAQ,aAAa,MAAM,eAAe,OAAO,MAAM;GAE7D,IAAI,MAAM,WAAW,GACnB;GAGF,MAAM,eAAe;GACrB,eAAe,QAAQ,KAAK;EAC9B;EAEA,SAAS,iBAAiB,SAAS,WAAW;EAC9C,aAAa,SAAS,oBAAoB,SAAS,WAAW;CAChE,GAAG;EAAC;EAAW;EAAQ;CAAW,CAAC;CAanC,MAAM,gBAAgB,UACpB,MAAM,aAAa,MAAM,SAAS,OAAO;CAM3C,MAAM,YAAY,OAAO,CAAC;CAC1B,MAAM,CAAC,cAAc,mBAAmB,SAAS,KAAK;CAEtD,MAAM,gBAAgB;EACpB,UAAU,UAAU;EACpB,gBAAgB,KAAK;CACvB;CA0CA,OAAO;EACL,WAzC2B,YACzB;GACE,cAAc,UAAU;IACtB,IAAI,CAAC,aAAa,KAAK,GACrB;IAEF,MAAM,eAAe;IACrB,UAAU,WAAW;IACrB,gBAAgB,IAAI;GACtB;GACA,aAAa,UAAU;IACrB,IAAI,CAAC,aAAa,KAAK,GACrB;IAGF,MAAM,eAAe;IACrB,MAAM,aAAa,aAAa;GAClC;GACA,cAAc,UAAU;IACtB,IAAI,CAAC,aAAa,KAAK,GACrB;IAEF,UAAU,UAAU,KAAK,IAAI,GAAG,UAAU,UAAU,CAAC;IACrD,IAAI,UAAU,YAAY,GACxB,gBAAgB,KAAK;GAEzB;GACA,SAAS,UAAU;IACjB,IAAI,CAAC,aAAa,KAAK,GACrB;IAEF,MAAM,eAAe;IACrB,QAAQ;IACR,eAAe,QACb,aAAa,MAAM,aAAa,OAAO,MAAM,CAC/C;GACF;EACF,IACA,CAAC;EAIH;EACA,aAAa,aAAa,cAAc,KAAA;CAC1C;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"MessageThread.module.mjs","names":[],"sources":["../../../../../../../src/components/MessageThread/MessageThread.module.scss"],"sourcesContent":[".messageThread {\n display: flex;\n flex-direction: column;\n row-gap: var(--message-thread--spacing);\n width: 100%;\n\n > li {\n list-style: none;\n }\n\n .message {\n &.responder {\n padding-inline-end: var(--message-thread--indentation);\n }\n\n &.sender {\n padding-inline-start: var(--message-thread--indentation);\n }\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"MessageThread.module.mjs","names":[],"sources":["../../../../../../../src/components/MessageThread/MessageThread.module.scss"],"sourcesContent":[".messageThread {\n display: flex;\n flex-direction: column;\n row-gap: var(--message-thread--spacing);\n width: 100%;\n\n /* `> li` is load-bearing: #3160 narrowed it from `li` so that an ordered\n list inside a message keeps its markers. The items come from `Message` /\n `MessageSeparator`, so a class would have to enumerate the slot. */\n // stylelint-disable-next-line selector-max-type -- see above\n > li {\n list-style: none;\n }\n\n .message {\n &.responder {\n padding-inline-end: var(--message-thread--indentation);\n }\n\n &.sender {\n padding-inline-start: var(--message-thread--indentation);\n }\n }\n}\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.module.mjs","names":[],"sources":["../../../../../../../src/components/Modal/Modal.module.scss"],"sourcesContent":["@use \"@/styles/mixins/scrollbar\";\n\n/* Caps the dialog width per size token (`size-s` / `size-m` / `size-l`). */\n@mixin size($size) {\n &.size-#{$size} {\n [role=\"dialog\"] {\n width: inherit;\n max-width: var(--modal--size--#{$size});\n }\n }\n}\n\n/* ===========================================================================\n Base — shared by Modal (centered / bottom sheet) and OffCanvas (side panel).\n =========================================================================== */\n.modal,\n.offCanvas {\n /* Fixed full-viewport layer + centering wrapper. Sized to the visual\n viewport so it tracks the on-screen keyboard (Overlay sets the var). */\n > div {\n position: fixed;\n height: var(--visual-viewport-height);\n display: flex;\n flex-direction: column;\n width: 100%;\n justify-content: center;\n align-items: center;\n pointer-events: none;\n\n > div {\n display: flex;\n justify-content: center;\n width: inherit;\n max-height: var(--visual-viewport-height);\n padding: var(--size-px--xl);\n }\n }\n\n /* Close button: hidden when an action group is present, unless forced. */\n &:has(.actionGroup) {\n .closeButton:not(.alwaysVisible) {\n display: none;\n }\n }\n\n .closeButton.alwaysHidden {\n display: none;\n }\n\n /* The dialog card itself — a flex column. It is also the scroll container on\n mobile, and the sticky header/footer stack against its content only. */\n [role=\"dialog\"] {\n display: flex;\n flex-direction: column;\n isolation: isolate;\n max-height: 100%;\n overflow: hidden;\n outline: none;\n pointer-events: all;\n background-color: var(--modal--background-color);\n border: var(--modal--border-width) var(--modal--border-style)\n var(--modal--border-color);\n\n > form {\n display: contents;\n }\n }\n\n /* Header */\n header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .header {\n display: flex;\n align-items: center;\n gap: var(--heading--icon-to-text-spacing);\n padding-top: var(--modal--header-padding-top);\n padding-inline: var(--modal--padding);\n\n /* Flatten the heading's text and content spans into the header row, so\n badges stay behind the title instead of wrapping onto their own line. */\n > span {\n display: contents;\n }\n\n .headerTitle {\n min-width: 0;\n }\n\n .closeButton {\n order: 1;\n margin-inline-start: auto;\n }\n }\n\n /* Content — the scrollable body */\n .content,\n .columnLayout {\n @include scrollbar.scrollbar;\n\n flex-grow: 1;\n min-height: var(--modal--content-min-height);\n padding: var(--modal--padding);\n overflow: hidden auto;\n background-color: var(--modal--background-color);\n }\n\n .columnLayout .accentBox {\n position: sticky;\n top: 0;\n height: fit-content;\n }\n\n /* Action group — the footer */\n .actionGroup {\n padding: var(--modal--padding);\n background-color: var(--modal--footer-background-color);\n }\n\n /* ActionGroup's \"sorted\" layout sets flex-grow:1 on itself; the modal footer\n must keep its natural height (otherwise it fills the full-height off-canvas\n panel). Scope via the dialog so flex-grow:0 wins the equal-specificity /\n source-order battle — without raising the specificity of the padding above,\n which the mobile rules override. */\n [role=\"dialog\"] .actionGroup {\n flex-grow: 0;\n }\n}\n\n/* ===========================================================================\n Modal — desktop (centered dialog, zoom in/out).\n =========================================================================== */\n@media (width > 768px) {\n .modal {\n @include size(s);\n @include size(m);\n @include size(l);\n\n [role=\"dialog\"] {\n border-radius: var(--modal--corner-radius);\n }\n\n > div > div {\n &[data-entering] {\n animation: modal-zoom var(--transition--duration--slow)\n cubic-bezier(0.1, 0.8, 0.3, 1.2);\n }\n\n &[data-exiting] {\n animation: modal-zoom var(--transition--duration--default)\n cubic-bezier(0.1, 0.8, 0.3, 1.2) reverse;\n }\n }\n }\n}\n\n/* ===========================================================================\n OffCanvas — side panel (all widths), full height, slides in from the side.\n =========================================================================== */\n.offCanvas {\n @include size(s);\n @include size(m);\n @include size(l);\n\n [role=\"dialog\"] {\n height: 100%;\n border-radius: 0;\n }\n\n > div {\n padding-inline-start: var(--size-px--m);\n\n > div {\n justify-content: flex-end;\n width: inherit;\n height: inherit;\n padding: unset;\n\n &[data-entering] {\n animation: modal-slide-in-from-right var(--transition--duration--slow);\n }\n\n &[data-exiting] {\n animation: modal-slide-in-from-right\n var(--transition--duration--default) reverse;\n }\n }\n }\n\n &.left {\n > div {\n padding-inline: 0 var(--size-px--m);\n\n > div {\n justify-content: flex-start;\n padding: unset;\n\n &[data-entering] {\n animation: modal-slide-in-from-left var(--transition--duration--slow);\n }\n\n &[data-exiting] {\n animation: modal-slide-in-from-left\n var(--transition--duration--default) reverse;\n }\n }\n }\n }\n}\n\n/* ===========================================================================\n Modal — mobile (bottom sheet), issue #2423.\n\n One scroll container: the dialog scrolls while the header sticks to the top\n and the footer to the bottom. While the virtual keyboard is open\n (.keyboardVisible) both un-stick so header + content + footer flow normally\n and the content gets the full available space.\n =========================================================================== */\n@media (max-width: 768px) {\n .modal {\n /* Bottom-anchored sheet: rounded top corners + slide-up animation. */\n > div > div {\n margin-top: auto;\n width: 100%;\n padding: unset;\n overflow: hidden;\n border-start-start-radius: var(--modal--corner-radius);\n border-start-end-radius: var(--modal--corner-radius);\n pointer-events: all;\n\n &[data-entering] {\n animation: modal-slide-up var(--transition--duration--slow);\n }\n\n &[data-exiting] {\n animation: modal-slide-up var(--transition--duration--default) reverse;\n }\n }\n\n /* The dialog is the single scroll container. Capped just below the visual\n viewport so a full-height sheet keeps a small gap from the top edge. */\n [role=\"dialog\"] {\n @include scrollbar.scrollbar;\n\n width: inherit;\n max-height: calc(\n var(--visual-viewport-height) - var(--modal--tray-top-spacing)\n );\n overflow: hidden auto;\n }\n\n [role=\"dialog\"] > form {\n width: inherit;\n }\n\n /* flex: 0 0 auto keeps natural heights; without flex-shrink:0 the flex\n column compresses the content box and the (overflow:visible) fields\n spill over the footer. */\n .header,\n .content,\n .columnLayout,\n .actionGroup {\n flex: 0 0 auto;\n }\n\n .content,\n .columnLayout {\n min-height: 0;\n overflow: visible;\n }\n\n /* Docked header + footer (sticky within the single scroll container). */\n .header {\n position: sticky;\n top: 0;\n z-index: 1;\n background-color: var(--modal--background-color);\n }\n\n .actionGroup {\n position: sticky;\n bottom: 0;\n z-index: 1;\n }\n }\n\n /* Keyboard open: un-stick both so everything flows for maximum typing space.\n The dialog fills the available height and the footer uses margin-top:auto,\n so with SHORT content the footer still sits at the bottom (not mid-air) and\n with tall content it scrolls away with everything else. Never hidden. */\n .modal.keyboardVisible {\n [role=\"dialog\"] {\n min-height: calc(\n var(--visual-viewport-height) - var(--modal--tray-top-spacing)\n );\n }\n\n .header,\n .actionGroup {\n position: static;\n }\n\n .actionGroup {\n margin-top: auto;\n }\n }\n\n /* Shared mobile spacing + full-width stacked actions (Modal + OffCanvas). */\n .modal,\n .offCanvas {\n .header {\n padding-inline: var(--modal--padding-mobile);\n }\n\n .content,\n .columnLayout,\n .actionGroup {\n padding: var(--modal--padding-mobile);\n }\n\n .actionGroup {\n flex-direction: column;\n\n button {\n width: 100%;\n }\n }\n }\n}\n\n/* ===========================================================================\n Animations\n =========================================================================== */\n@keyframes modal-zoom {\n /* Start with scale(1) to let ResizeObservers take the correct width on mount. */\n 0% {\n opacity: 0;\n transform: scale(1);\n }\n\n 0.1% {\n opacity: 1;\n transform: scale(0.8);\n }\n\n 100% {\n transform: scale(1);\n }\n}\n\n@keyframes modal-slide-up {\n from {\n translate: 0 30%;\n }\n\n to {\n translate: 0 0;\n }\n}\n\n@keyframes modal-slide-in-from-right {\n from {\n translate: 30% 0;\n }\n\n to {\n translate: 0 0;\n }\n}\n\n@keyframes modal-slide-in-from-left {\n from {\n translate: -30% 0;\n }\n\n to {\n translate: 0 0;\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"Modal.module.mjs","names":[],"sources":["../../../../../../../src/components/Modal/Modal.module.scss"],"sourcesContent":["@use \"@/styles/mixins/scrollbar\";\n\n/* Caps the dialog width per size token (`size-s` / `size-m` / `size-l`). */\n@mixin size($size) {\n &.size-#{$size} {\n [role=\"dialog\"] {\n width: inherit;\n max-width: var(--modal--size--#{$size});\n }\n }\n}\n\n/* ===========================================================================\n Base — shared by Modal (centered / bottom sheet) and OffCanvas (side panel).\n =========================================================================== */\n.modal,\n.offCanvas {\n /* Overlay.module.scss lays the viewport out; the dialog inside it is the only\n pointer target. */\n > :global(.flow--overlay--viewport) {\n pointer-events: none;\n\n /* react-aria's own modal wrapper — it gets no className from Flow, so\n its default `react-aria-Modal` class is the handle. */\n > :global(.react-aria-Modal) {\n display: flex;\n justify-content: center;\n width: inherit;\n max-height: var(--visual-viewport-height);\n padding: var(--size-px--xl);\n }\n }\n\n /* Close button: hidden when an action group is present, unless forced. */\n &:has(.actionGroup) {\n .closeButton:not(.alwaysVisible) {\n display: none;\n }\n }\n\n .closeButton.alwaysHidden {\n display: none;\n }\n\n /* The dialog card itself — a flex column. It is also the scroll container on\n mobile, and the sticky header/footer stack against its content only. */\n [role=\"dialog\"] {\n display: flex;\n flex-direction: column;\n isolation: isolate;\n max-height: 100%;\n overflow: hidden;\n outline: none;\n pointer-events: all;\n background-color: var(--modal--background-color);\n border: var(--modal--border-width) var(--modal--border-style)\n var(--modal--border-color);\n\n // A consumer `Form` (or raw `<form>`) wrapping the modal content. `Form`\n // is a plain FC, so the props context cannot give it a class.\n // stylelint-disable-next-line selector-max-type -- consumer form, no class\n > form {\n display: contents;\n }\n }\n\n /* Header */\n // A `Header`/`SectionHeader` — or a raw `<header>` — the consumer places in\n // the modal; both must match.\n // stylelint-disable-next-line selector-max-type -- consumer-placed header\n header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .header {\n display: flex;\n align-items: center;\n gap: var(--heading--icon-to-text-spacing);\n padding-top: var(--modal--header-padding-top);\n padding-inline: var(--modal--padding);\n\n /* Flatten the heading's text and content spans into the header row, so\n badges stay behind the title instead of wrapping onto their own line.\n Heading's spans do carry classes, but `.flow--heading--heading-content`\n already sets `display: none` on `:empty` at the same specificity a class\n selector here would reach — source order would then decide. */\n // stylelint-disable-next-line selector-max-type -- see above\n > span {\n display: contents;\n }\n\n .headerTitle {\n min-width: 0;\n }\n\n .closeButton {\n order: 1;\n margin-inline-start: auto;\n }\n }\n\n /* Content — the scrollable body */\n .content,\n .columnLayout {\n @include scrollbar.scrollbar;\n\n flex-grow: 1;\n min-height: var(--modal--content-min-height);\n padding: var(--modal--padding);\n overflow: hidden auto;\n background-color: var(--modal--background-color);\n }\n\n .columnLayout .accentBox {\n position: sticky;\n top: 0;\n height: fit-content;\n }\n\n /* Action group — the footer */\n .actionGroup {\n padding: var(--modal--padding);\n background-color: var(--modal--footer-background-color);\n }\n\n /* ActionGroup's \"sorted\" layout sets flex-grow:1 on itself; the modal footer\n must keep its natural height (otherwise it fills the full-height off-canvas\n panel). Scope via the dialog so flex-grow:0 wins the equal-specificity /\n source-order battle — without raising the specificity of the padding above,\n which the mobile rules override. */\n [role=\"dialog\"] .actionGroup {\n flex-grow: 0;\n }\n}\n\n/* ===========================================================================\n Modal — desktop (centered dialog, zoom in/out).\n =========================================================================== */\n@media (width > 768px) {\n .modal {\n @include size(s);\n @include size(m);\n @include size(l);\n\n [role=\"dialog\"] {\n border-radius: var(--modal--corner-radius);\n }\n\n > :global(.flow--overlay--viewport) > :global(.react-aria-Modal) {\n &[data-entering] {\n animation: modal-zoom var(--transition--duration--slow)\n cubic-bezier(0.1, 0.8, 0.3, 1.2);\n }\n\n &[data-exiting] {\n animation: modal-zoom var(--transition--duration--default)\n cubic-bezier(0.1, 0.8, 0.3, 1.2) reverse;\n }\n }\n }\n}\n\n/* ===========================================================================\n OffCanvas — side panel (all widths), full height, slides in from the side.\n =========================================================================== */\n.offCanvas {\n @include size(s);\n @include size(m);\n @include size(l);\n\n [role=\"dialog\"] {\n height: 100%;\n border-radius: 0;\n }\n\n > :global(.flow--overlay--viewport) {\n padding-inline-start: var(--size-px--m);\n\n > :global(.react-aria-Modal) {\n justify-content: flex-end;\n width: inherit;\n height: inherit;\n padding: unset;\n\n &[data-entering] {\n animation: modal-slide-in-from-right var(--transition--duration--slow);\n }\n\n &[data-exiting] {\n animation: modal-slide-in-from-right\n var(--transition--duration--default) reverse;\n }\n }\n }\n\n &.left {\n > :global(.flow--overlay--viewport) {\n padding-inline: 0 var(--size-px--m);\n\n > :global(.react-aria-Modal) {\n justify-content: flex-start;\n padding: unset;\n\n &[data-entering] {\n animation: modal-slide-in-from-left var(--transition--duration--slow);\n }\n\n &[data-exiting] {\n animation: modal-slide-in-from-left\n var(--transition--duration--default) reverse;\n }\n }\n }\n }\n}\n\n/* ===========================================================================\n Modal — mobile (bottom sheet), issue #2423.\n\n One scroll container: the dialog scrolls while the header sticks to the top\n and the footer to the bottom. While the virtual keyboard is open\n (.keyboardVisible) both un-stick so header + content + footer flow normally\n and the content gets the full available space.\n =========================================================================== */\n@media (max-width: 768px) {\n .modal {\n /* Bottom-anchored sheet: rounded top corners + slide-up animation. */\n > :global(.flow--overlay--viewport) > :global(.react-aria-Modal) {\n margin-top: auto;\n width: 100%;\n padding: unset;\n overflow: hidden;\n border-start-start-radius: var(--modal--corner-radius);\n border-start-end-radius: var(--modal--corner-radius);\n pointer-events: all;\n\n &[data-entering] {\n animation: modal-slide-up var(--transition--duration--slow);\n }\n\n &[data-exiting] {\n animation: modal-slide-up var(--transition--duration--default) reverse;\n }\n }\n\n /* The dialog is the single scroll container. Capped just below the visual\n viewport so a full-height sheet keeps a small gap from the top edge. */\n [role=\"dialog\"] {\n @include scrollbar.scrollbar;\n\n width: inherit;\n max-height: calc(\n var(--visual-viewport-height) - var(--modal--tray-top-spacing)\n );\n overflow: hidden auto;\n }\n\n // stylelint-disable-next-line selector-max-type -- consumer form, see above\n [role=\"dialog\"] > form {\n width: inherit;\n }\n\n /* flex: 0 0 auto keeps natural heights; without flex-shrink:0 the flex\n column compresses the content box and the (overflow:visible) fields\n spill over the footer. */\n .header,\n .content,\n .columnLayout,\n .actionGroup {\n flex: 0 0 auto;\n }\n\n .content,\n .columnLayout {\n min-height: 0;\n overflow: visible;\n }\n\n /* Docked header + footer (sticky within the single scroll container). */\n .header {\n position: sticky;\n top: 0;\n z-index: 1;\n background-color: var(--modal--background-color);\n }\n\n .actionGroup {\n position: sticky;\n bottom: 0;\n z-index: 1;\n }\n }\n\n /* Keyboard open: un-stick both so everything flows for maximum typing space.\n The dialog fills the available height and the footer uses margin-top:auto,\n so with SHORT content the footer still sits at the bottom (not mid-air) and\n with tall content it scrolls away with everything else. Never hidden. */\n .modal.keyboardVisible {\n [role=\"dialog\"] {\n min-height: calc(\n var(--visual-viewport-height) - var(--modal--tray-top-spacing)\n );\n }\n\n .header,\n .actionGroup {\n position: static;\n }\n\n .actionGroup {\n margin-top: auto;\n }\n }\n\n /* Shared mobile spacing + full-width stacked actions (Modal + OffCanvas). */\n .modal,\n .offCanvas {\n .header {\n padding-inline: var(--modal--padding-mobile);\n }\n\n .content,\n .columnLayout,\n .actionGroup {\n padding: var(--modal--padding-mobile);\n }\n\n .actionGroup {\n flex-direction: column;\n\n // The footer's buttons are consumer `Button`/`Action` children; a raw\n // `<button>` matches today too.\n // stylelint-disable-next-line selector-max-type -- consumer children\n button {\n width: 100%;\n }\n }\n }\n}\n\n/* ===========================================================================\n Animations\n =========================================================================== */\n@keyframes modal-zoom {\n /* Start with scale(1) to let ResizeObservers take the correct width on mount. */\n 0% {\n opacity: 0;\n transform: scale(1);\n }\n\n 0.1% {\n opacity: 1;\n transform: scale(0.8);\n }\n\n 100% {\n transform: scale(1);\n }\n}\n\n@keyframes modal-slide-up {\n from {\n translate: 0 30%;\n }\n\n to {\n translate: 0 0;\n }\n}\n\n@keyframes modal-slide-in-from-right {\n from {\n translate: 30% 0;\n }\n\n to {\n translate: 0 0;\n }\n}\n\n@keyframes modal-slide-in-from-left {\n from {\n translate: -30% 0;\n }\n\n to {\n translate: 0 0;\n }\n}\n"],"mappings":""}
@@ -2,6 +2,7 @@
2
2
  /* */
3
3
  //#region src/components/Navigation/Navigation.module.scss
4
4
  var navigation = "flow--navigation";
5
+ var linkList = "flow--navigation--link-list";
5
6
  var item = "flow--navigation--item";
6
7
  var icon = "flow--navigation--icon";
7
8
  var checkMark = "flow--navigation--check-mark";
@@ -11,6 +12,7 @@ var avatar = "flow--navigation--avatar";
11
12
  var badge = "flow--navigation--badge";
12
13
  var Navigation_module_default = {
13
14
  navigation,
15
+ linkList,
14
16
  item,
15
17
  "switch": "flow--navigation--switch",
16
18
  icon,
@@ -21,6 +23,6 @@ var Navigation_module_default = {
21
23
  badge
22
24
  };
23
25
  //#endregion
24
- export { ariaDisabled, avatar, badge, checkMark, controlIcon, Navigation_module_default as default, icon, item, navigation };
26
+ export { ariaDisabled, avatar, badge, checkMark, controlIcon, Navigation_module_default as default, icon, item, linkList, navigation };
25
27
 
26
28
  //# sourceMappingURL=Navigation.module.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Navigation.module.mjs","names":[],"sources":["../../../../../../../src/components/Navigation/Navigation.module.scss"],"sourcesContent":["@use \"@/styles/mixins/menuItem\";\n@use \"@/styles/mixins/ellipsis\";\n\n.navigation {\n display: flex;\n flex-direction: column;\n min-width: 0;\n\n ul {\n display: flex;\n flex-direction: column;\n row-gap: var(--menu--item-to-item-spacing);\n\n &:empty {\n display: none;\n }\n }\n\n .item {\n @include menuItem.menuItem();\n\n > .icon {\n order: 0;\n flex-shrink: 0;\n }\n\n > :not(.icon) {\n order: 1;\n }\n\n > :not(.icon, .badge) {\n @include ellipsis.ellipsis;\n\n min-width: 0;\n }\n\n .badge {\n margin-inline-start: auto;\n flex-shrink: 0;\n }\n }\n\n hr {\n margin-bottom: var(--menu--group-to-group-spacing);\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"Navigation.module.mjs","names":[],"sources":["../../../../../../../src/components/Navigation/Navigation.module.scss"],"sourcesContent":["@use \"@/styles/mixins/menuItem\";\n@use \"@/styles/mixins/ellipsis\";\n\n.navigation {\n display: flex;\n flex-direction: column;\n min-width: 0;\n\n .linkList {\n display: flex;\n flex-direction: column;\n row-gap: var(--menu--item-to-item-spacing);\n\n &:empty {\n display: none;\n }\n }\n\n .item {\n @include menuItem.menuItem();\n\n > .icon {\n order: 0;\n flex-shrink: 0;\n }\n\n > :not(.icon) {\n order: 1;\n }\n\n > :not(.icon, .badge) {\n @include ellipsis.ellipsis;\n\n min-width: 0;\n }\n\n .badge {\n margin-inline-start: auto;\n flex-shrink: 0;\n }\n }\n\n // A consumer-placed `Separator`, which is a plain FC — Navigation's props\n // context cannot inject a class into it.\n // stylelint-disable-next-line selector-max-type -- consumer separator\n hr {\n margin-bottom: var(--menu--group-to-group-spacing);\n }\n}\n"],"mappings":""}
@@ -1,6 +1,7 @@
1
1
  "use client"
2
2
  /* */
3
3
  import { UiComponentTunnelExit } from "../../../UiComponentTunnel/UiComponentTunnelExit.mjs";
4
+ import Navigation_module_default from "../../Navigation.module.mjs";
4
5
  import { Children } from "react";
5
6
  import { jsx } from "react/jsx-runtime";
6
7
  //#region src/components/Navigation/components/LinkListTunnelExit/LinkListTunnelExit.tsx
@@ -8,7 +9,10 @@ var LinkListTunnelExit = (props) => {
8
9
  return /* @__PURE__ */ jsx(UiComponentTunnelExit, {
9
10
  ...props,
10
11
  children: (children) => {
11
- if (Children.count(children) >= 1) return /* @__PURE__ */ jsx("ul", { children });
12
+ if (Children.count(children) >= 1) return /* @__PURE__ */ jsx("ul", {
13
+ className: Navigation_module_default.linkList,
14
+ children
15
+ });
12
16
  return null;
13
17
  }
14
18
  });
@@ -1 +1 @@
1
- {"version":3,"file":"LinkListTunnelExit.mjs","names":[],"sources":["../../../../../../../../../src/components/Navigation/components/LinkListTunnelExit/LinkListTunnelExit.tsx"],"sourcesContent":["import { Children } from \"react\";\nimport { UiComponentTunnelExit } from \"@/components/UiComponentTunnel/UiComponentTunnelExit\";\n\nexport const LinkListTunnelExit: typeof UiComponentTunnelExit = (props) => {\n return (\n <UiComponentTunnelExit\n {...props}\n children={(children) => {\n if (Children.count(children) >= 1) {\n return <ul>{children}</ul>;\n }\n\n return null;\n }}\n />\n );\n};\n"],"mappings":";;;;;;AAGA,IAAa,sBAAoD,UAAU;CACzE,OACE,oBAAC,uBAAD;EACE,GAAI;EACJ,WAAW,aAAa;GACtB,IAAI,SAAS,MAAM,QAAQ,KAAK,GAC9B,OAAO,oBAAC,MAAD,EAAK,SAAa,CAAA;GAG3B,OAAO;EACT;CACD,CAAA;AAEL"}
1
+ {"version":3,"file":"LinkListTunnelExit.mjs","names":[],"sources":["../../../../../../../../../src/components/Navigation/components/LinkListTunnelExit/LinkListTunnelExit.tsx"],"sourcesContent":["import { Children } from \"react\";\nimport { UiComponentTunnelExit } from \"@/components/UiComponentTunnel/UiComponentTunnelExit\";\nimport styles from \"../../Navigation.module.scss\";\n\nexport const LinkListTunnelExit: typeof UiComponentTunnelExit = (props) => {\n return (\n <UiComponentTunnelExit\n {...props}\n children={(children) => {\n if (Children.count(children) >= 1) {\n return <ul className={styles.linkList}>{children}</ul>;\n }\n\n return null;\n }}\n />\n );\n};\n"],"mappings":";;;;;;;AAIA,IAAa,sBAAoD,UAAU;CACzE,OACE,oBAAC,uBAAD;EACE,GAAI;EACJ,WAAW,aAAa;GACtB,IAAI,SAAS,MAAM,QAAQ,KAAK,GAC9B,OAAO,oBAAC,MAAD;IAAI,WAAW,0BAAO;IAAW;GAAa,CAAA;GAGvD,OAAO;EACT;CACD,CAAA;AAEL"}
@@ -1 +1 @@
1
- {"version":3,"file":"NavigationGroup.module.mjs","names":[],"sources":["../../../../../../../../../src/components/Navigation/components/NavigationGroup/NavigationGroup.module.scss"],"sourcesContent":[".navigationGroup {\n &:not(.collapsable) ul {\n padding-inline: var(--accordion--padding-x);\n padding-block: var(--accordion--padding-y);\n }\n\n &:not(:last-child) {\n margin-bottom: var(--menu--group-to-group-spacing);\n }\n\n .label {\n display: block;\n padding-block: var(--accordion--padding-y);\n padding-inline: var(--accordion--padding-x);\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"NavigationGroup.module.mjs","names":[],"sources":["../../../../../../../../../src/components/Navigation/components/NavigationGroup/NavigationGroup.module.scss"],"sourcesContent":[".navigationGroup {\n &:not(.collapsable) :global(.flow--navigation--link-list) {\n padding-inline: var(--accordion--padding-x);\n padding-block: var(--accordion--padding-y);\n }\n\n &:not(:last-child) {\n margin-bottom: var(--menu--group-to-group-spacing);\n }\n\n .label {\n display: block;\n padding-block: var(--accordion--padding-y);\n padding-inline: var(--accordion--padding-x);\n }\n}\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"NumberField.module.mjs","names":[],"sources":["../../../../../../../src/components/NumberField/NumberField.module.scss"],"sourcesContent":["@use \"@/styles/mixins/formControl\";\n\n.group {\n order: 2;\n display: grid;\n grid-template-columns: 1fr max-content;\n grid-template-areas:\n \"input increment\"\n \"input decrement\";\n\n .input {\n grid-area: input;\n\n @include formControl.formControl();\n }\n\n .decrementButton {\n grid-area: decrement;\n }\n\n .incrementButton {\n grid-area: increment;\n }\n\n .decrementButton,\n .incrementButton {\n border-width: var(--form-control--border-width);\n border-color: var(--form-control--border-color--default);\n border-style: solid;\n }\n\n &[aria-invalid] {\n .decrementButton,\n .incrementButton {\n border-color: var(--form-control--border-color--invalid);\n }\n }\n\n &[data-focus-within] {\n .decrementButton,\n .incrementButton {\n border-color: var(--form-control--border-color--focused);\n }\n }\n\n &[aria-disabled=\"true\"] {\n .decrementButton,\n .incrementButton {\n border-color: var(--form-control--border-color--disabled);\n }\n }\n\n &:has(input:read-only):not([aria-disabled=\"true\"]) {\n .decrementButton,\n .incrementButton {\n border-color: var(--form-control--border-color--default);\n }\n }\n}\n\n@media (pointer: fine) {\n .group {\n .input {\n border-start-end-radius: 0;\n border-end-end-radius: 0;\n }\n\n .incrementButton,\n .decrementButton {\n border-inline-start: none;\n border-start-start-radius: 0;\n border-end-start-radius: 0;\n padding-block: 0;\n\n .coarsePointerIcon {\n display: none;\n }\n }\n\n .decrementButton {\n border-start-end-radius: 0;\n }\n\n .incrementButton {\n border-block-end: none;\n border-end-end-radius: 0;\n }\n }\n}\n\n@media (pointer: coarse) {\n .group {\n grid-template-columns: auto 1fr auto;\n grid-template-areas: \"decrement input increment\";\n max-width: 100%;\n\n .input {\n border-radius: 0;\n min-width: 0;\n }\n\n .decrementButton,\n .incrementButton {\n padding-inline: var(--button--padding-icon-only);\n\n .coarsePointerIcon {\n width: var(--icon--size--m);\n height: var(--icon--size--m);\n }\n\n svg:not(.coarsePointerIcon) {\n display: none;\n }\n }\n\n .decrementButton {\n border-start-end-radius: 0;\n border-end-end-radius: 0;\n border-inline-end: none;\n }\n\n .incrementButton {\n border-start-start-radius: 0;\n border-end-start-radius: 0;\n border-inline-start: none;\n }\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"NumberField.module.mjs","names":[],"sources":["../../../../../../../src/components/NumberField/NumberField.module.scss"],"sourcesContent":["@use \"@/styles/mixins/formControl\";\n\n.group {\n order: 2;\n display: grid;\n grid-template-columns: 1fr max-content;\n grid-template-areas:\n \"input increment\"\n \"input decrement\";\n\n .input {\n grid-area: input;\n\n @include formControl.formControl();\n }\n\n .decrementButton {\n grid-area: decrement;\n }\n\n .incrementButton {\n grid-area: increment;\n }\n\n .decrementButton,\n .incrementButton {\n border-width: var(--form-control--border-width);\n border-color: var(--form-control--border-color--default);\n border-style: solid;\n }\n\n &[aria-invalid] {\n .decrementButton,\n .incrementButton {\n border-color: var(--form-control--border-color--invalid);\n }\n }\n\n &[data-focus-within] {\n .decrementButton,\n .incrementButton {\n border-color: var(--form-control--border-color--focused);\n }\n }\n\n &[aria-disabled=\"true\"] {\n .decrementButton,\n .incrementButton {\n border-color: var(--form-control--border-color--disabled);\n }\n }\n\n &:has(.input:read-only):not([aria-disabled=\"true\"]) {\n .decrementButton,\n .incrementButton {\n border-color: var(--form-control--border-color--default);\n }\n }\n}\n\n@media (pointer: fine) {\n .group {\n .input {\n border-start-end-radius: 0;\n border-end-end-radius: 0;\n }\n\n .incrementButton,\n .decrementButton {\n border-inline-start: none;\n border-start-start-radius: 0;\n border-end-start-radius: 0;\n padding-block: 0;\n\n .coarsePointerIcon {\n display: none;\n }\n }\n\n .decrementButton {\n border-start-end-radius: 0;\n }\n\n .incrementButton {\n border-block-end: none;\n border-end-end-radius: 0;\n }\n }\n}\n\n@media (pointer: coarse) {\n .group {\n grid-template-columns: auto 1fr auto;\n grid-template-areas: \"decrement input increment\";\n max-width: 100%;\n\n .input {\n border-radius: 0;\n min-width: 0;\n }\n\n .decrementButton,\n .incrementButton {\n padding-inline: var(--button--padding-icon-only);\n\n .coarsePointerIcon {\n width: var(--icon--size--m);\n height: var(--icon--size--m);\n }\n\n // Hides every icon the stepper buttons render except the coarse-pointer\n // one — Button's own pending/success/failure icons included, and those\n // share no class with the chevrons.\n // stylelint-disable-next-line selector-max-type -- see above\n svg:not(.coarsePointerIcon) {\n display: none;\n }\n }\n\n .decrementButton {\n border-start-end-radius: 0;\n border-end-end-radius: 0;\n border-inline-end: none;\n }\n\n .incrementButton {\n border-start-start-radius: 0;\n border-end-start-radius: 0;\n border-inline-start: none;\n }\n }\n}\n"],"mappings":""}
@@ -2,13 +2,15 @@
2
2
  /* */
3
3
  //#region src/components/Overlay/Overlay.module.scss
4
4
  var overlay = "flow--overlay";
5
+ var viewport = "flow--overlay--viewport";
5
6
  var suspense = "flow--overlay--suspense";
6
7
  var Overlay_module_default = {
7
8
  overlay,
8
9
  "overlay-fade": "flow--overlay--overlay-fade",
10
+ viewport,
9
11
  suspense
10
12
  };
11
13
  //#endregion
12
- export { Overlay_module_default as default, overlay, suspense };
14
+ export { Overlay_module_default as default, overlay, suspense, viewport };
13
15
 
14
16
  //# sourceMappingURL=Overlay.module.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Overlay.module.mjs","names":[],"sources":["../../../../../../../src/components/Overlay/Overlay.module.scss"],"sourcesContent":["/* Full-page backdrop behind every overlay (Modal, LightBox, …).\n\n Sized to `--visual-viewport-height`, the property the Modal wrapper uses too,\n so the whole overlay stack tracks the on-screen keyboard as one. react-aria\n publishes it on this element and updates it with the visual viewport.\n\n Not `--page-height` (the full document height): this element is `fixed`, so\n anything past the viewport never shows, while `backdrop-filter` blurs the\n whole layer every frame. Chromium drops an oversized one for single frames\n while scrolling, flickering backdrop and modal alike. #2338 switched\n `absolute` → `fixed` and added the blur, but kept the `--page-height` that\n `absolute` had needed. */\n.overlay {\n position: fixed;\n top: 0;\n inset-inline-start: 0;\n width: 100%;\n height: var(--visual-viewport-height);\n isolation: isolate;\n background: var(--overlay--backdrop-color);\n backdrop-filter: blur(var(--blur));\n\n &[data-entering] {\n animation: overlay-fade var(--transition--duration--slow);\n }\n\n &[data-exiting] {\n animation: overlay-fade var(--transition--duration--default) reverse ease-in;\n }\n}\n\n/* Fallback shown while suspended overlay content loads. */\n.suspense {\n padding: var(--size-px--xl);\n}\n\n@keyframes overlay-fade {\n 0% {\n opacity: 0;\n }\n\n 50% {\n opacity: 1;\n }\n\n 100% {\n opacity: 1;\n }\n}\n\n@media (prefers-contrast: more), (prefers-reduced-transparency) {\n .overlay {\n backdrop-filter: none;\n background-color: var(--dark--color--600);\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"Overlay.module.mjs","names":[],"sources":["../../../../../../../src/components/Overlay/Overlay.module.scss"],"sourcesContent":["/* Full-page backdrop behind every overlay (Modal, LightBox, …).\n\n Sized to `--visual-viewport-height`, the property the Modal wrapper uses too,\n so the whole overlay stack tracks the on-screen keyboard as one. react-aria\n publishes it on this element and updates it with the visual viewport.\n\n Not `--page-height` (the full document height): this element is `fixed`, so\n anything past the viewport never shows, while `backdrop-filter` blurs the\n whole layer every frame. Chromium drops an oversized one for single frames\n while scrolling, flickering backdrop and modal alike. #2338 switched\n `absolute` → `fixed` and added the blur, but kept the `--page-height` that\n `absolute` had needed. */\n.overlay {\n position: fixed;\n top: 0;\n inset-inline-start: 0;\n width: 100%;\n height: var(--visual-viewport-height);\n isolation: isolate;\n background: var(--overlay--backdrop-color);\n backdrop-filter: blur(var(--blur));\n\n &[data-entering] {\n animation: overlay-fade var(--transition--duration--slow);\n }\n\n &[data-exiting] {\n animation: overlay-fade var(--transition--duration--default) reverse ease-in;\n }\n}\n\n/* Centering viewport between the backdrop and react-aria's modal wrapper.\n Sized to the visual viewport so it tracks the on-screen keyboard. Modal,\n OffCanvas and LightBox refine it (padding, pointer-events) — they used to\n repeat this whole block behind a `> div` selector each. */\n.viewport {\n position: fixed;\n height: var(--visual-viewport-height);\n display: flex;\n flex-direction: column;\n width: 100%;\n justify-content: center;\n align-items: center;\n}\n\n/* Fallback shown while suspended overlay content loads. */\n.suspense {\n padding: var(--size-px--xl);\n}\n\n@keyframes overlay-fade {\n 0% {\n opacity: 0;\n }\n\n 50% {\n opacity: 1;\n }\n\n 100% {\n opacity: 1;\n }\n}\n\n@media (prefers-contrast: more), (prefers-reduced-transparency) {\n .overlay {\n backdrop-filter: none;\n background-color: var(--dark--color--600);\n }\n}\n"],"mappings":""}
@@ -4,7 +4,7 @@ import { useIsActivityActive } from "../../Activity/context.mjs";
4
4
  import DivView from "../../../views/DivView.mjs";
5
5
  import Overlay_module_default from "../Overlay.module.mjs";
6
6
  import { OverlaySuspenseFallback } from "./OverlaySuspenseFallback.mjs";
7
- import useKeepBrowserExtensionsInteractive from "../../../lib/hooks/dom/useKeepBrowserExtensionsInteractive.mjs";
7
+ import useKeepBrowserExtensionsInteractive, { isBrowserExtensionNode } from "../../../lib/hooks/dom/useKeepBrowserExtensionsInteractive.mjs";
8
8
  import { Suspense } from "react";
9
9
  import { jsx } from "react/jsx-runtime";
10
10
  import * as Aria from "react-aria-components";
@@ -12,6 +12,41 @@ import { UNSAFE_PortalProvider } from "react-aria";
12
12
  //#region src/components/Overlay/components/OverlayContent.tsx
13
13
  var overlayContainerAttribute = "data-flow-overlays";
14
14
  /**
15
+ * Nodes a browser extension appends (e.g. 1Password's inline menu) don't count
16
+ * as covering: they belong on top anyway, and moving the container for them
17
+ * blurs a focused field inside it (#3268). The same heuristic also spares
18
+ * custom elements and shadow hosts the page itself appends, which then render
19
+ * above open overlays.
20
+ */
21
+ var isCoveredByPageContent = (container) => {
22
+ for (let node = container.nextElementSibling; node; node = node.nextElementSibling) if (!isBrowserExtensionNode(node)) return true;
23
+ return false;
24
+ };
25
+ /**
26
+ * Only the container moves, never the nodes appended after it: those are
27
+ * foreign, and moving them costs state we cannot restore (an iframe reloads).
28
+ * The container's own state we can. Moving a node blurs the focused element
29
+ * inside it and resets every scroll container inside to the top. `moveBefore`
30
+ * keeps the focus (and, in Chromium, the scroll positions); the rest is
31
+ * restored afterwards.
32
+ */
33
+ var moveToEnd = (container) => {
34
+ const body = document.body;
35
+ const focused = document.activeElement;
36
+ const scrolled = Array.from(container.querySelectorAll("*")).filter((element) => element.scrollTop !== 0 || element.scrollLeft !== 0).map((element) => [
37
+ element,
38
+ element.scrollTop,
39
+ element.scrollLeft
40
+ ]);
41
+ if (typeof body.moveBefore === "function") body.moveBefore(container, null);
42
+ else body.append(container);
43
+ for (const [element, scrollTop, scrollLeft] of scrolled) {
44
+ element.scrollTop = scrollTop;
45
+ element.scrollLeft = scrollLeft;
46
+ }
47
+ if (focused instanceof HTMLElement && container.contains(focused) && document.activeElement !== focused) focused.focus({ preventScroll: true });
48
+ };
49
+ /**
15
50
  * Render overlays into a dedicated container instead of `document.body`.
16
51
  *
17
52
  * React Aria `FocusScope` relies on nearby sibling nodes (sentinels + overlay).
@@ -33,7 +68,7 @@ var getOverlayContainer = () => {
33
68
  if (typeof document === "undefined") return null;
34
69
  const existingContainer = document.querySelector(`body > [${overlayContainerAttribute}]`);
35
70
  if (existingContainer) {
36
- if (existingContainer !== document.body.lastElementChild) document.body.append(existingContainer);
71
+ if (isCoveredByPageContent(existingContainer)) moveToEnd(existingContainer);
37
72
  return existingContainer;
38
73
  }
39
74
  const container = document.createElement("div");
@@ -58,13 +93,16 @@ var OverlayContent = (props) => {
58
93
  children: /* @__PURE__ */ jsx(Aria.ModalOverlay, {
59
94
  ...restProps,
60
95
  className,
61
- children: /* @__PURE__ */ jsx(DivView, { children: /* @__PURE__ */ jsx(Aria.Modal, { children: /* @__PURE__ */ jsx(Suspense, {
62
- fallback: /* @__PURE__ */ jsx(Fallback, {}),
63
- children: /* @__PURE__ */ jsx(Aria.Dialog, {
64
- "aria-labelledby": ariaLabelledBy,
65
- children
66
- })
67
- }) }) })
96
+ children: /* @__PURE__ */ jsx(DivView, {
97
+ className: Overlay_module_default.viewport,
98
+ children: /* @__PURE__ */ jsx(Aria.Modal, { children: /* @__PURE__ */ jsx(Suspense, {
99
+ fallback: /* @__PURE__ */ jsx(Fallback, {}),
100
+ children: /* @__PURE__ */ jsx(Aria.Dialog, {
101
+ "aria-labelledby": ariaLabelledBy,
102
+ children
103
+ })
104
+ }) })
105
+ })
68
106
  })
69
107
  });
70
108
  };
@@ -1 +1 @@
1
- {"version":3,"file":"OverlayContent.mjs","names":[],"sources":["../../../../../../../../src/components/Overlay/components/OverlayContent.tsx"],"sourcesContent":["import * as Aria from \"react-aria-components\";\nimport { type FC, type PropsWithChildren, type Ref, Suspense } from \"react\";\nimport { UNSAFE_PortalProvider } from \"react-aria\";\nimport type { PropsWithClassName } from \"@/lib/types/props\";\nimport { OverlaySuspenseFallback } from \"@/components/Overlay/components/OverlaySuspenseFallback\";\nimport styles from \"../Overlay.module.scss\";\nimport DivView from \"@/views/DivView\";\nimport { useKeepBrowserExtensionsInteractive } from \"@/lib/hooks/dom/useKeepBrowserExtensionsInteractive\";\nimport { useIsActivityActive } from \"@/components/Activity/context\";\n\nconst overlayContainerAttribute = \"data-flow-overlays\";\n\n/**\n * Render overlays into a dedicated container instead of `document.body`.\n *\n * React Aria `FocusScope` relies on nearby sibling nodes (sentinels + overlay).\n * Browser extensions (e.g. password managers) inject/reorder `body` children,\n * which can separate those nodes. Then focus scope detection breaks and may\n * cause recursive focus restoration.\n *\n * Keep the overlay container as the last child of body.\n *\n * React Aria normally portals overlays directly into body, so they are appended\n * last and render on top.\n *\n * If we create one persistent container only once, anything appended to body\n * later (toasts, third-party widgets, remounted app root) can render above open\n * overlays. Then overlays can appear behind page content, and the backdrop blur\n * no longer covers the page.\n */\nconst getOverlayContainer = (): HTMLElement | null => {\n if (typeof document === \"undefined\") {\n return null;\n }\n\n const existingContainer = document.querySelector<HTMLElement>(\n `body > [${overlayContainerAttribute}]`,\n );\n\n if (existingContainer) {\n if (existingContainer !== document.body.lastElementChild) {\n document.body.append(existingContainer);\n }\n return existingContainer;\n }\n\n const container = document.createElement(\"div\");\n container.setAttribute(overlayContainerAttribute, \"\");\n document.body.append(container);\n\n return container;\n};\n\nexport interface OverlayContentProps\n extends\n PropsWithChildren,\n PropsWithClassName,\n Pick<Aria.DialogProps, \"aria-labelledby\"> {\n ref?: Ref<HTMLDivElement>;\n onOpenChange: (isOpen: boolean) => void;\n isDismissable?: boolean;\n isOpen?: boolean;\n}\n\n/** @flr-generate all */\nexport const OverlayContent: FC<OverlayContentProps> = (props) => {\n const {\n children,\n className,\n \"aria-labelledby\": ariaLabelledBy,\n ...restProps\n } = props;\n\n const isActivityActive = useIsActivityActive();\n\n useKeepBrowserExtensionsInteractive(restProps.isOpen ?? false);\n\n const Fallback = () => {\n return (\n <DivView className={styles.suspense}>\n <OverlaySuspenseFallback {...restProps} />\n </DivView>\n );\n };\n\n if (!isActivityActive) {\n return null;\n }\n\n return (\n <UNSAFE_PortalProvider getContainer={getOverlayContainer}>\n <Aria.ModalOverlay {...restProps} className={className}>\n <DivView>\n <Aria.Modal>\n <Suspense fallback={<Fallback />}>\n <Aria.Dialog aria-labelledby={ariaLabelledBy}>\n {children}\n </Aria.Dialog>\n </Suspense>\n </Aria.Modal>\n </DivView>\n </Aria.ModalOverlay>\n </UNSAFE_PortalProvider>\n );\n};\n\nexport default OverlayContent;\n"],"mappings":";;;;;;;;;;;;AAUA,IAAM,4BAA4B;;;;;;;;;;;;;;;;;;;AAoBlC,IAAM,4BAAgD;CACpD,IAAI,OAAO,aAAa,aACtB,OAAO;CAGT,MAAM,oBAAoB,SAAS,cACjC,WAAW,0BAA0B,EACvC;CAEA,IAAI,mBAAmB;EACrB,IAAI,sBAAsB,SAAS,KAAK,kBACtC,SAAS,KAAK,OAAO,iBAAiB;EAExC,OAAO;CACT;CAEA,MAAM,YAAY,SAAS,cAAc,KAAK;CAC9C,UAAU,aAAa,2BAA2B,EAAE;CACpD,SAAS,KAAK,OAAO,SAAS;CAE9B,OAAO;AACT;;AAcA,IAAa,kBAA2C,UAAU;CAChE,MAAM,EACJ,UACA,WACA,mBAAmB,gBACnB,GAAG,cACD;CAEJ,MAAM,mBAAmB,oBAAoB;CAE7C,oCAAoC,UAAU,UAAU,KAAK;CAE7D,MAAM,iBAAiB;EACrB,OACE,oBAAC,SAAD;GAAS,WAAW,uBAAO;GACzB,UAAA,oBAAC,yBAAD,EAAyB,GAAI,UAAY,CAAA;EAClC,CAAA;CAEb;CAEA,IAAI,CAAC,kBACH,OAAO;CAGT,OACE,oBAAC,uBAAD;EAAuB,cAAc;EACnC,UAAA,oBAAC,KAAK,cAAN;GAAmB,GAAI;GAAsB;GAC3C,UAAA,oBAAC,SAAD,EAAA,UACE,oBAAC,KAAK,OAAN,EAAA,UACE,oBAAC,UAAD;IAAU,UAAU,oBAAC,UAAD,CAAW,CAAA;IAC7B,UAAA,oBAAC,KAAK,QAAN;KAAa,mBAAiB;KAC3B;IACU,CAAA;GACL,CAAA,EACA,CAAA,EACL,CAAA;EACQ,CAAA;CACE,CAAA;AAE3B"}
1
+ {"version":3,"file":"OverlayContent.mjs","names":[],"sources":["../../../../../../../../src/components/Overlay/components/OverlayContent.tsx"],"sourcesContent":["import * as Aria from \"react-aria-components\";\nimport { type FC, type PropsWithChildren, type Ref, Suspense } from \"react\";\nimport { UNSAFE_PortalProvider } from \"react-aria\";\nimport type { PropsWithClassName } from \"@/lib/types/props\";\nimport { OverlaySuspenseFallback } from \"@/components/Overlay/components/OverlaySuspenseFallback\";\nimport styles from \"../Overlay.module.scss\";\nimport DivView from \"@/views/DivView\";\nimport {\n isBrowserExtensionNode,\n useKeepBrowserExtensionsInteractive,\n} from \"@/lib/hooks/dom/useKeepBrowserExtensionsInteractive\";\nimport { useIsActivityActive } from \"@/components/Activity/context\";\n\nconst overlayContainerAttribute = \"data-flow-overlays\";\n\n/**\n * Nodes a browser extension appends (e.g. 1Password's inline menu) don't count\n * as covering: they belong on top anyway, and moving the container for them\n * blurs a focused field inside it (#3268). The same heuristic also spares\n * custom elements and shadow hosts the page itself appends, which then render\n * above open overlays.\n */\nconst isCoveredByPageContent = (container: HTMLElement): boolean => {\n for (\n let node = container.nextElementSibling;\n node;\n node = node.nextElementSibling\n ) {\n if (!isBrowserExtensionNode(node)) {\n return true;\n }\n }\n return false;\n};\n\n/**\n * Only the container moves, never the nodes appended after it: those are\n * foreign, and moving them costs state we cannot restore (an iframe reloads).\n * The container's own state we can. Moving a node blurs the focused element\n * inside it and resets every scroll container inside to the top. `moveBefore`\n * keeps the focus (and, in Chromium, the scroll positions); the rest is\n * restored afterwards.\n */\nconst moveToEnd = (container: HTMLElement): void => {\n const body = document.body;\n const focused = document.activeElement;\n const scrolled = Array.from(container.querySelectorAll<HTMLElement>(\"*\"))\n .filter((element) => element.scrollTop !== 0 || element.scrollLeft !== 0)\n .map(\n (element) => [element, element.scrollTop, element.scrollLeft] as const,\n );\n\n if (typeof body.moveBefore === \"function\") {\n body.moveBefore(container, null);\n } else {\n body.append(container);\n }\n\n for (const [element, scrollTop, scrollLeft] of scrolled) {\n element.scrollTop = scrollTop;\n element.scrollLeft = scrollLeft;\n }\n if (\n focused instanceof HTMLElement &&\n container.contains(focused) &&\n document.activeElement !== focused\n ) {\n focused.focus({ preventScroll: true });\n }\n};\n\n/**\n * Render overlays into a dedicated container instead of `document.body`.\n *\n * React Aria `FocusScope` relies on nearby sibling nodes (sentinels + overlay).\n * Browser extensions (e.g. password managers) inject/reorder `body` children,\n * which can separate those nodes. Then focus scope detection breaks and may\n * cause recursive focus restoration.\n *\n * Keep the overlay container as the last child of body.\n *\n * React Aria normally portals overlays directly into body, so they are appended\n * last and render on top.\n *\n * If we create one persistent container only once, anything appended to body\n * later (toasts, third-party widgets, remounted app root) can render above open\n * overlays. Then overlays can appear behind page content, and the backdrop blur\n * no longer covers the page.\n */\nconst getOverlayContainer = (): HTMLElement | null => {\n if (typeof document === \"undefined\") {\n return null;\n }\n\n const existingContainer = document.querySelector<HTMLElement>(\n `body > [${overlayContainerAttribute}]`,\n );\n\n if (existingContainer) {\n if (isCoveredByPageContent(existingContainer)) {\n moveToEnd(existingContainer);\n }\n return existingContainer;\n }\n\n const container = document.createElement(\"div\");\n container.setAttribute(overlayContainerAttribute, \"\");\n document.body.append(container);\n\n return container;\n};\n\nexport interface OverlayContentProps\n extends\n PropsWithChildren,\n PropsWithClassName,\n Pick<Aria.DialogProps, \"aria-labelledby\"> {\n ref?: Ref<HTMLDivElement>;\n onOpenChange: (isOpen: boolean) => void;\n isDismissable?: boolean;\n isOpen?: boolean;\n}\n\n/** @flr-generate all */\nexport const OverlayContent: FC<OverlayContentProps> = (props) => {\n const {\n children,\n className,\n \"aria-labelledby\": ariaLabelledBy,\n ...restProps\n } = props;\n\n const isActivityActive = useIsActivityActive();\n\n useKeepBrowserExtensionsInteractive(restProps.isOpen ?? false);\n\n const Fallback = () => {\n return (\n <DivView className={styles.suspense}>\n <OverlaySuspenseFallback {...restProps} />\n </DivView>\n );\n };\n\n if (!isActivityActive) {\n return null;\n }\n\n return (\n <UNSAFE_PortalProvider getContainer={getOverlayContainer}>\n <Aria.ModalOverlay {...restProps} className={className}>\n {/* The centering viewport between the backdrop and react-aria's modal\n wrapper. Classed so Modal/OffCanvas/LightBox can position it\n without selecting a bare `div`. */}\n <DivView className={styles.viewport}>\n <Aria.Modal>\n <Suspense fallback={<Fallback />}>\n <Aria.Dialog aria-labelledby={ariaLabelledBy}>\n {children}\n </Aria.Dialog>\n </Suspense>\n </Aria.Modal>\n </DivView>\n </Aria.ModalOverlay>\n </UNSAFE_PortalProvider>\n );\n};\n\nexport default OverlayContent;\n"],"mappings":";;;;;;;;;;;;AAaA,IAAM,4BAA4B;;;;;;;;AASlC,IAAM,0BAA0B,cAAoC;CAClE,KACE,IAAI,OAAO,UAAU,oBACrB,MACA,OAAO,KAAK,oBAEZ,IAAI,CAAC,uBAAuB,IAAI,GAC9B,OAAO;CAGX,OAAO;AACT;;;;;;;;;AAUA,IAAM,aAAa,cAAiC;CAClD,MAAM,OAAO,SAAS;CACtB,MAAM,UAAU,SAAS;CACzB,MAAM,WAAW,MAAM,KAAK,UAAU,iBAA8B,GAAG,CAAC,CAAC,CACtE,QAAQ,YAAY,QAAQ,cAAc,KAAK,QAAQ,eAAe,CAAC,CAAC,CACxE,KACE,YAAY;EAAC;EAAS,QAAQ;EAAW,QAAQ;CAAU,CAC9D;CAEF,IAAI,OAAO,KAAK,eAAe,YAC7B,KAAK,WAAW,WAAW,IAAI;MAE/B,KAAK,OAAO,SAAS;CAGvB,KAAK,MAAM,CAAC,SAAS,WAAW,eAAe,UAAU;EACvD,QAAQ,YAAY;EACpB,QAAQ,aAAa;CACvB;CACA,IACE,mBAAmB,eACnB,UAAU,SAAS,OAAO,KAC1B,SAAS,kBAAkB,SAE3B,QAAQ,MAAM,EAAE,eAAe,KAAK,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;AAoBA,IAAM,4BAAgD;CACpD,IAAI,OAAO,aAAa,aACtB,OAAO;CAGT,MAAM,oBAAoB,SAAS,cACjC,WAAW,0BAA0B,EACvC;CAEA,IAAI,mBAAmB;EACrB,IAAI,uBAAuB,iBAAiB,GAC1C,UAAU,iBAAiB;EAE7B,OAAO;CACT;CAEA,MAAM,YAAY,SAAS,cAAc,KAAK;CAC9C,UAAU,aAAa,2BAA2B,EAAE;CACpD,SAAS,KAAK,OAAO,SAAS;CAE9B,OAAO;AACT;;AAcA,IAAa,kBAA2C,UAAU;CAChE,MAAM,EACJ,UACA,WACA,mBAAmB,gBACnB,GAAG,cACD;CAEJ,MAAM,mBAAmB,oBAAoB;CAE7C,oCAAoC,UAAU,UAAU,KAAK;CAE7D,MAAM,iBAAiB;EACrB,OACE,oBAAC,SAAD;GAAS,WAAW,uBAAO;GACzB,UAAA,oBAAC,yBAAD,EAAyB,GAAI,UAAY,CAAA;EAClC,CAAA;CAEb;CAEA,IAAI,CAAC,kBACH,OAAO;CAGT,OACE,oBAAC,uBAAD;EAAuB,cAAc;EACnC,UAAA,oBAAC,KAAK,cAAN;GAAmB,GAAI;GAAsB;GAI3C,UAAA,oBAAC,SAAD;IAAS,WAAW,uBAAO;IACzB,UAAA,oBAAC,KAAK,OAAN,EAAA,UACE,oBAAC,UAAD;KAAU,UAAU,oBAAC,UAAD,CAAW,CAAA;KAC7B,UAAA,oBAAC,KAAK,QAAN;MAAa,mBAAiB;MAC3B;KACU,CAAA;IACL,CAAA,EACA,CAAA;GACL,CAAA;EACQ,CAAA;CACE,CAAA;AAE3B"}
@@ -21,6 +21,7 @@ import { TogglePasswordVisibilityButton } from "./components/TogglePasswordVisib
21
21
  import { ValidationResultButton } from "./components/ValidationResultButton/ValidationResultButton.mjs";
22
22
  import { PasswordGenerateButton } from "./components/PasswordGenerateButton/PasswordGenerateButton.mjs";
23
23
  import { usePolicyValidationResult } from "./lib/usePolicyValidationResult.mjs";
24
+ import { useAriaAnnouncePasswordVisibility, useAriaAnnounceValidationState } from "./lib/ariaAnnounce.mjs";
24
25
  import clsx from "clsx";
25
26
  import { useMemo, useRef, useState } from "react";
26
27
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -29,7 +30,6 @@ import * as Aria from "react-aria-components";
29
30
  /** @flr-generate all */
30
31
  var PasswordCreationField = flowComponent("PasswordCreationField", (props) => {
31
32
  const { children, className, ref, isDisabled, onValidationResult, isInvalid: invalidFromProps, validationPolicy: validationPolicyFromProps = defaultPasswordCreationPolicy, isRequired, value, onChange, ...rest } = useControlledHostValueProps(props, "");
32
- const { FieldErrorView, FieldErrorCaptureContext, fieldProps, fieldPropsContext } = useFieldComponent(props, "PasswordCreationField");
33
33
  const translate = useLocalizedStringFormatter(__locale_default, "PasswordCreationField");
34
34
  const validationPolicy = useMemo(() => Policy.fromDeclaration(validationPolicyFromProps), [validationPolicyFromProps]);
35
35
  const [isPasswordRevealed, setIsPasswordRevealed] = useState(false);
@@ -77,6 +77,12 @@ var PasswordCreationField = flowComponent("PasswordCreationField", (props) => {
77
77
  const isValidFromValidationResult = !isEmptyValue && stateFromValidationResult?.isValid;
78
78
  const isInvalidFromValidationResult = !isEmptyValue && stateFromValidationResult?.isValid === false;
79
79
  const isInvalid = invalidFromProps || isInvalidFromValidationResult;
80
+ const { FieldErrorView, FieldErrorCaptureContext, fieldProps, fieldPropsContext } = useFieldComponent({
81
+ ...props,
82
+ isInvalid
83
+ }, "PasswordCreationField");
84
+ useAriaAnnounceValidationState(latestValidationErrorText, !isEmptyValue && policyValidationResult.isValid !== "indeterminate");
85
+ useAriaAnnouncePasswordVisibility(isPasswordRevealed);
80
86
  const setOptimisticPolicyValidationResult = (state = {}) => {
81
87
  setPolicyValidationResult((currentState) => ({
82
88
  ...currentState,
@@ -149,6 +155,7 @@ var PasswordCreationField = flowComponent("PasswordCreationField", (props) => {
149
155
  };
150
156
  return /* @__PURE__ */ jsxs(Aria.TextField, {
151
157
  ...rest,
158
+ ...fieldProps,
152
159
  value,
153
160
  type: isPasswordRevealed ? "text" : "password",
154
161
  onChange,