@widgetic/design-system 0.5.22 → 0.5.24

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 (258) hide show
  1. package/README.md +2 -0
  2. package/dist/components/CopyableHeader.svelte +260 -448
  3. package/dist/components/CopyableHeader.svelte.d.ts +8 -5
  4. package/dist/components/EmptyState.svelte +100 -128
  5. package/dist/components/EmptyState.svelte.d.ts +16 -19
  6. package/dist/components/GenerateLoader.svelte +17 -10
  7. package/dist/components/PasswordInput.svelte +41 -44
  8. package/dist/components/PasswordInput.svelte.d.ts +2 -0
  9. package/dist/components/ThumbHashImage.svelte +117 -119
  10. package/dist/components/WScrollArea.svelte +47 -41
  11. package/dist/components/chat/ActivityRow.svelte +115 -0
  12. package/dist/components/chat/ActivityRow.svelte.d.ts +18 -0
  13. package/dist/components/chat/ChatHeader.svelte +114 -0
  14. package/dist/components/chat/ChatHeader.svelte.d.ts +26 -0
  15. package/dist/components/chat/Composer.svelte +172 -0
  16. package/dist/components/chat/Composer.svelte.d.ts +27 -0
  17. package/dist/components/chat/Message.svelte +158 -0
  18. package/dist/components/chat/Message.svelte.d.ts +28 -0
  19. package/dist/components/chat/OptionRow.svelte +59 -0
  20. package/dist/components/chat/OptionRow.svelte.d.ts +14 -0
  21. package/dist/components/chat/PlanCard.svelte +220 -0
  22. package/dist/components/chat/PlanCard.svelte.d.ts +34 -0
  23. package/dist/components/chat/QuestionCard.svelte +265 -0
  24. package/dist/components/chat/QuestionCard.svelte.d.ts +38 -0
  25. package/dist/components/chat/ScrollToBottom.svelte +51 -0
  26. package/dist/components/chat/ScrollToBottom.svelte.d.ts +11 -0
  27. package/dist/components/chat/SuggestionCard.svelte +79 -0
  28. package/dist/components/chat/SuggestionCard.svelte.d.ts +20 -0
  29. package/dist/components/chat/TaskRow.svelte +59 -0
  30. package/dist/components/chat/TaskRow.svelte.d.ts +11 -0
  31. package/dist/components/chat/composerLogic.d.ts +16 -0
  32. package/dist/components/chat/composerLogic.js +29 -0
  33. package/dist/components/chat/index.d.ts +16 -0
  34. package/dist/components/chat/index.js +16 -0
  35. package/dist/components/chat/questionCardLogic.d.ts +11 -0
  36. package/dist/components/chat/questionCardLogic.js +39 -0
  37. package/dist/components/index.d.ts +10 -1
  38. package/dist/components/index.js +10 -0
  39. package/dist/components/modals/ActionModal.svelte +186 -180
  40. package/dist/components/modals/BaseModal.svelte +495 -401
  41. package/dist/components/modals/BaseModal.svelte.d.ts +4 -0
  42. package/dist/components/modals/NewItemDialog.svelte +198 -243
  43. package/dist/components/modals/Portal.svelte +109 -105
  44. package/dist/components/modals/README.md +16 -2
  45. package/dist/components/modals/RenameDialog.svelte +232 -292
  46. package/dist/components/modals/modalStore.d.ts +9 -1
  47. package/dist/components/modals/modalStore.js +44 -11
  48. package/dist/components/navigation/Breadcrumbs.svelte +421 -377
  49. package/dist/components/navigation/Breadcrumbs.svelte.d.ts +3 -2
  50. package/dist/components/navigation/breadcrumbsAvailableWidth.d.ts +10 -0
  51. package/dist/components/navigation/breadcrumbsAvailableWidth.js +10 -0
  52. package/dist/components/navigation/types.d.ts +2 -0
  53. package/dist/components/pdf/PDFViewer.svelte +1 -1
  54. package/dist/components/search-input/SearchInput.svelte +63 -104
  55. package/dist/components/search-input/SearchInput.svelte.d.ts +13 -27
  56. package/dist/components/search-input/index2.d.ts +14 -9
  57. package/dist/components/shad-editor/editor.css +1 -1
  58. package/dist/components/shad-editor/i18n/messages.d.ts +1 -1
  59. package/dist/components/shad-editor/i18n/messages.js +1 -1
  60. package/dist/components/shell/AppShell.svelte +52 -0
  61. package/dist/components/shell/AppShell.svelte.d.ts +13 -0
  62. package/dist/components/shell/Banner.svelte +90 -0
  63. package/dist/components/shell/Banner.svelte.d.ts +61 -0
  64. package/dist/components/shell/ErrorState.svelte +60 -0
  65. package/dist/components/shell/ErrorState.svelte.d.ts +13 -0
  66. package/dist/components/shell/Link.svelte +63 -0
  67. package/dist/components/shell/Link.svelte.d.ts +53 -0
  68. package/dist/components/shell/NavDrawer.svelte +117 -0
  69. package/dist/components/shell/NavDrawer.svelte.d.ts +10 -0
  70. package/dist/components/shell/NavRow.svelte +128 -0
  71. package/dist/components/shell/NavRow.svelte.d.ts +65 -0
  72. package/dist/components/shell/NavSectionLabel.svelte +27 -0
  73. package/dist/components/shell/NavSectionLabel.svelte.d.ts +9 -0
  74. package/dist/components/shell/NotificationRow.svelte +76 -0
  75. package/dist/components/shell/NotificationRow.svelte.d.ts +16 -0
  76. package/dist/components/shell/PageHeader.svelte +42 -0
  77. package/dist/components/shell/PageHeader.svelte.d.ts +12 -0
  78. package/dist/components/shell/ProfileRow.svelte +95 -0
  79. package/dist/components/shell/ProfileRow.svelte.d.ts +54 -0
  80. package/dist/components/shell/SettingsDialog.svelte +163 -0
  81. package/dist/components/shell/SettingsDialog.svelte.d.ts +19 -0
  82. package/dist/components/shell/SettingsNav.svelte +46 -0
  83. package/dist/components/shell/SettingsNav.svelte.d.ts +11 -0
  84. package/dist/components/shell/Sidebar.svelte +82 -0
  85. package/dist/components/shell/Sidebar.svelte.d.ts +25 -0
  86. package/dist/components/shell/Stat.svelte +63 -0
  87. package/dist/components/shell/Stat.svelte.d.ts +58 -0
  88. package/dist/components/shell/TopBar.svelte +37 -0
  89. package/dist/components/shell/TopBar.svelte.d.ts +11 -0
  90. package/dist/components/shell/WidgetRow.svelte +126 -0
  91. package/dist/components/shell/WidgetRow.svelte.d.ts +78 -0
  92. package/dist/components/shell/error-state-messages.d.ts +7 -0
  93. package/dist/components/shell/error-state-messages.js +9 -0
  94. package/dist/components/shell/index.d.ts +22 -0
  95. package/dist/components/shell/index.js +25 -0
  96. package/dist/components/shell/link-external.d.ts +2 -0
  97. package/dist/components/shell/link-external.js +7 -0
  98. package/dist/components/shell/messages.d.ts +7 -0
  99. package/dist/components/shell/messages.js +5 -0
  100. package/dist/components/shell/nested-modal-guard.d.ts +25 -0
  101. package/dist/components/shell/nested-modal-guard.js +33 -0
  102. package/dist/components/shell/settings-nav-types.d.ts +12 -0
  103. package/dist/components/shell/settings-nav-types.js +3 -0
  104. package/dist/components/shell/shell-utils.d.ts +7 -0
  105. package/dist/components/shell/shell-utils.js +27 -0
  106. package/dist/components/shell/stat-tone.d.ts +3 -0
  107. package/dist/components/shell/stat-tone.js +11 -0
  108. package/dist/components/toast/ToasterModal.svelte +102 -168
  109. package/dist/components/toast/ToasterModal.svelte.d.ts +2 -12
  110. package/dist/components/ui/accordion/accordion-content.svelte +3 -2
  111. package/dist/components/ui/accordion/accordion-item.svelte +10 -1
  112. package/dist/components/ui/accordion/accordion-trigger.svelte +4 -3
  113. package/dist/components/ui/alert/alert-description.svelte +5 -1
  114. package/dist/components/ui/alert/alert-title.svelte +2 -1
  115. package/dist/components/ui/alert/index.d.ts +13 -11
  116. package/dist/components/ui/alert/index.js +4 -4
  117. package/dist/components/ui/avatar/avatar-utils.d.ts +2 -0
  118. package/dist/components/ui/avatar/avatar-utils.js +9 -0
  119. package/dist/components/ui/avatar/avatar.svelte +63 -0
  120. package/dist/components/ui/avatar/avatar.svelte.d.ts +80 -0
  121. package/dist/components/ui/avatar/index.d.ts +4 -0
  122. package/dist/components/ui/avatar/index.js +4 -0
  123. package/dist/components/ui/badge/badge.svelte +22 -10
  124. package/dist/components/ui/badge/badge.svelte.d.ts +14 -15
  125. package/dist/components/ui/breadcrumb/breadcrumb-ellipsis.svelte +2 -2
  126. package/dist/components/ui/breadcrumb/breadcrumb-link.svelte +1 -1
  127. package/dist/components/ui/breadcrumb/breadcrumb-page.svelte +1 -1
  128. package/dist/components/ui/breadcrumb/breadcrumb-separator.svelte +2 -2
  129. package/dist/components/ui/button/button-padding.d.ts +9 -0
  130. package/dist/components/ui/button/button-padding.js +15 -0
  131. package/dist/components/ui/button/button.svelte +81 -31
  132. package/dist/components/ui/button/button.svelte.d.ts +48 -29
  133. package/dist/components/ui/calendar/calendar-cell.svelte +1 -1
  134. package/dist/components/ui/calendar/calendar-head-cell.svelte +1 -1
  135. package/dist/components/ui/calendar/calendar-next-button.svelte +2 -2
  136. package/dist/components/ui/calendar/calendar-prev-button.svelte +2 -2
  137. package/dist/components/ui/calendar/calendar.svelte.d.ts +1 -1
  138. package/dist/components/ui/card/card-description.svelte +1 -1
  139. package/dist/components/ui/card/card-title.svelte +1 -1
  140. package/dist/components/ui/card/card.svelte +1 -1
  141. package/dist/components/ui/checkbox/checkbox.svelte +9 -5
  142. package/dist/components/ui/chip/chip.svelte +48 -0
  143. package/dist/components/ui/chip/chip.svelte.d.ts +13 -0
  144. package/dist/components/ui/chip/index.d.ts +2 -0
  145. package/dist/components/ui/chip/index.js +2 -0
  146. package/dist/components/ui/command/command-dialog.svelte +1 -1
  147. package/dist/components/ui/command/command-input.svelte +4 -4
  148. package/dist/components/ui/command/command-item.svelte +1 -1
  149. package/dist/components/ui/command/command-link-item.svelte +1 -1
  150. package/dist/components/ui/command/command.svelte +1 -1
  151. package/dist/components/ui/dialog/dialog-content.svelte +11 -4
  152. package/dist/components/ui/dialog/dialog-description.svelte +1 -1
  153. package/dist/components/ui/dialog/dialog-overlay.svelte +8 -1
  154. package/dist/components/ui/dialog/dialog-title.svelte +1 -1
  155. package/dist/components/ui/drawer/drawer-content.svelte +1 -1
  156. package/dist/components/ui/drawer/drawer-overlay.svelte +1 -1
  157. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +5 -5
  158. package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte +1 -1
  159. package/dist/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +1 -1
  160. package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte +20 -2
  161. package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte.d.ts +4 -0
  162. package/dist/components/ui/dropdown-menu/dropdown-menu-label.svelte +5 -1
  163. package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +3 -3
  164. package/dist/components/ui/dropdown-menu/dropdown-menu-separator.svelte +2 -1
  165. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
  166. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +3 -3
  167. package/dist/components/ui/field/field-ids.d.ts +7 -0
  168. package/dist/components/ui/field/field-ids.js +15 -0
  169. package/dist/components/ui/field/field.svelte +62 -0
  170. package/dist/components/ui/field/field.svelte.d.ts +21 -0
  171. package/dist/components/ui/field/index.d.ts +3 -0
  172. package/dist/components/ui/field/index.js +3 -0
  173. package/dist/components/ui/hover-card/hover-card-content.svelte +1 -1
  174. package/dist/components/ui/icon-button/icon-button.svelte +72 -0
  175. package/dist/components/ui/icon-button/icon-button.svelte.d.ts +90 -0
  176. package/dist/components/ui/icon-button/index.d.ts +2 -0
  177. package/dist/components/ui/icon-button/index.js +4 -0
  178. package/dist/components/ui/input/index.d.ts +1 -0
  179. package/dist/components/ui/input/input.svelte +11 -4
  180. package/dist/components/ui/input/input.svelte.d.ts +4 -1
  181. package/dist/components/ui/input-otp/input-otp-separator.svelte +2 -2
  182. package/dist/components/ui/input-otp/input-otp-slot.svelte +5 -4
  183. package/dist/components/ui/input-otp/input-otp-slot.svelte.d.ts +1 -1
  184. package/dist/components/ui/input-otp/input-otp.svelte +1 -1
  185. package/dist/components/ui/label/label.svelte +3 -2
  186. package/dist/components/ui/popover/popover-content.svelte +1 -1
  187. package/dist/components/ui/progress/progress.svelte +19 -5
  188. package/dist/components/ui/progress/progress.svelte.d.ts +4 -2
  189. package/dist/components/ui/radio-group/radio-group-item.svelte +4 -4
  190. package/dist/components/ui/range-calendar/range-calendar-head-cell.svelte +1 -1
  191. package/dist/components/ui/range-calendar/range-calendar-next-button.svelte +2 -2
  192. package/dist/components/ui/range-calendar/range-calendar-prev-button.svelte +2 -2
  193. package/dist/components/ui/range-calendar/range-calendar.svelte.d.ts +1 -1
  194. package/dist/components/ui/scroll-area/scroll-area-scrollbar.svelte +4 -1
  195. package/dist/components/ui/scroll-area/scroll-area.svelte +1 -1
  196. package/dist/components/ui/select/select-content.svelte +1 -3
  197. package/dist/components/ui/select/select-item.svelte +5 -4
  198. package/dist/components/ui/select/select-scroll-down-button.svelte +2 -2
  199. package/dist/components/ui/select/select-scroll-up-button.svelte +2 -2
  200. package/dist/components/ui/select/select-trigger.svelte +4 -3
  201. package/dist/components/ui/skeleton/index.d.ts +2 -0
  202. package/dist/components/ui/skeleton/index.js +2 -0
  203. package/dist/components/ui/skeleton/skeleton.svelte +24 -0
  204. package/dist/components/ui/skeleton/skeleton.svelte.d.ts +8 -0
  205. package/dist/components/ui/slider/slider.svelte +5 -10
  206. package/dist/components/ui/sonner/sonner.svelte +19 -13
  207. package/dist/components/ui/stepper-button/index.d.ts +3 -0
  208. package/dist/components/ui/stepper-button/index.js +2 -0
  209. package/dist/components/ui/stepper-button/stepper-button.svelte +37 -0
  210. package/dist/components/ui/stepper-button/stepper-button.svelte.d.ts +9 -0
  211. package/dist/components/ui/switch/switch.svelte +8 -3
  212. package/dist/components/ui/table/table-cell.svelte +1 -1
  213. package/dist/components/ui/table/table-head.svelte +1 -1
  214. package/dist/components/ui/table/table-row.svelte +1 -1
  215. package/dist/components/ui/tabs/tabs-list.svelte +1 -1
  216. package/dist/components/ui/tabs/tabs-trigger.svelte +1 -1
  217. package/dist/components/ui/tag/index.d.ts +2 -0
  218. package/dist/components/ui/tag/index.js +2 -0
  219. package/dist/components/ui/tag/tag.svelte +42 -0
  220. package/dist/components/ui/tag/tag.svelte.d.ts +11 -0
  221. package/dist/components/ui/textarea/textarea.svelte +4 -3
  222. package/dist/components/ui/textarea/textarea.svelte.d.ts +2 -2
  223. package/dist/components/ui/toggle/toggle.svelte +9 -9
  224. package/dist/components/ui/toggle/toggle.svelte.d.ts +14 -21
  225. package/dist/components/ui/tooltip/tooltip-content.svelte +1 -1
  226. package/dist/fonts/Inter/Inter-Black.woff2 +0 -0
  227. package/dist/fonts/Inter/Inter-BlackItalic.woff2 +0 -0
  228. package/dist/fonts/Inter/Inter-Bold.woff2 +0 -0
  229. package/dist/fonts/Inter/Inter-BoldItalic.woff2 +0 -0
  230. package/dist/fonts/Inter/Inter-ExtraBold.woff2 +0 -0
  231. package/dist/fonts/Inter/Inter-ExtraBoldItalic.woff2 +0 -0
  232. package/dist/fonts/Inter/Inter-ExtraLight-BETA.woff2 +0 -0
  233. package/dist/fonts/Inter/Inter-ExtraLightItalic-BETA.woff2 +0 -0
  234. package/dist/fonts/Inter/Inter-Italic.woff2 +0 -0
  235. package/dist/fonts/Inter/Inter-Light-BETA.woff2 +0 -0
  236. package/dist/fonts/Inter/Inter-LightItalic-BETA.woff2 +0 -0
  237. package/dist/fonts/Inter/Inter-Medium.woff2 +0 -0
  238. package/dist/fonts/Inter/Inter-MediumItalic.woff2 +0 -0
  239. package/dist/fonts/Inter/Inter-Regular.woff2 +0 -0
  240. package/dist/fonts/Inter/Inter-SemiBold.woff2 +0 -0
  241. package/dist/fonts/Inter/Inter-SemiBoldItalic.woff2 +0 -0
  242. package/dist/fonts/Inter/Inter-Thin-BETA.woff2 +0 -0
  243. package/dist/fonts/Inter/Inter-ThinItalic-BETA.woff2 +0 -0
  244. package/dist/fonts/Inter/Inter-italic.var.woff2 +0 -0
  245. package/dist/fonts/Inter/Inter-upright.var.woff2 +0 -0
  246. package/dist/fonts/Inter/Inter.var.woff2 +0 -0
  247. package/dist/fonts/JetBrainsMono/JetBrainsMono-Regular.woff2 +0 -0
  248. package/dist/i18n/audit/designSystemWords.d.ts +1 -1
  249. package/dist/i18n/audit/designSystemWords.js +1 -1
  250. package/dist/styles/fonts.css +18 -14
  251. package/dist/styles/global.css +10 -21
  252. package/dist/styles/tokens.css +332 -0
  253. package/dist/utils/overlayMotion.d.ts +49 -0
  254. package/dist/utils/overlayMotion.js +71 -0
  255. package/dist/utils.d.ts +27 -0
  256. package/dist/utils.js +33 -2
  257. package/package.json +2 -1
  258. package/tailwind.config.ts +7 -47
@@ -1,419 +1,513 @@
1
1
  <script lang="ts">
2
- // Svelte imports
3
- import { fade, scale } from 'svelte/transition';
4
- import { cubicOut } from 'svelte/easing';
5
- import { createEventDispatcher, onMount } from 'svelte';
6
- const dispatch = createEventDispatcher();
7
-
8
- // Design system componentsimports
9
- import { Button, WScrollArea, Portal } from '..';
10
- import { IconX } from '@tabler/icons-svelte';
11
-
12
- // Modal store
13
- import { modalState, openModal, closeModal } from './modalStore';
14
-
15
- /* COMPONENT PROPS */
16
- // class and style props
17
- let className = '';
18
- export { className as class };
19
- export let style: string = '';
20
- // modal dimensions
21
- export let width: string = '808px';
22
- export let height: string = '500px';
23
- // modal props
24
- export let modalType: string | undefined = undefined;
25
- export let modalData: any = undefined;
26
- // portal target
27
- export let target: string = '';
28
- // title props
29
- export let title: string | undefined = undefined;
30
- export let titlePadding: 'small' | 'large' = 'large';
31
- // modal state props
32
- export let open = false;
33
- export let enableSpaceKey = false;
34
- export let isSelected = false;
35
- export let showScroll = false;
36
- // An image preview stays hidden until its real size is known, so the card does not open large and then shrink.
37
- export let shown = true;
38
- // Width and height ease to the content, instead of jumping.
39
- export let animateSize = false;
40
- // The words a screen reader says for the close button. A page that shows the modal in another language passes its own.
41
- export let closeLabel: string = 'Close modal';
42
-
43
- // children prop(svelte 5) to replace the traditional slot(svelte 4)
44
- export let children: any;
45
-
46
- /* CONSTANTS */
47
- // Modal transition and style variables
48
- const transitionDuration = 200;
49
- const backdropOpacity = '0.4';
50
- const backdropBlur = '4px';
51
- const easing = cubicOut;
52
-
53
- // Update constants for modal constraints
54
- const PADDING = 24; // Consistent padding for sides and bottom
55
-
56
- /* STATE VARIABLES */
57
- // Add viewport dimension state
58
- let viewportWidth = 0;
59
- let viewportHeight = 0;
60
-
61
- // Modal vars
62
- let modalHeight = 0; // Height of the modal including borders that's why binded to offsetHeight
63
-
64
-
65
- // All Content Container vars
66
- const contentPadding = 16;
67
- let allContentHeight = 0;
68
- $: allContentHeight = 2 * contentPadding + titleHeight + contentHolderHeight;
69
-
70
- // Title vars
71
- let titleHeight = 0;
72
-
73
- // Content only vars
74
- let contentHolderHeight = 0;
75
- $: contentAvailableHeight = modalHeight - (2 * contentPadding + titleHeight);
76
- let updateContentDimInterval:any;
77
-
78
- // Show logs
79
- let showLogs = false;
80
-
81
-
82
- /* METHODS */
83
- function toggleUpdateContentDimensions(flag: boolean = true) {
84
- if (!flag) {
85
- clearInterval(updateContentDimInterval);
86
- updateContentDimInterval = undefined;
87
- } else {
88
- // updateContentDimInterval = setInterval(() => {updateViewportDimensions()}, 200);
89
- }
90
- }
91
-
92
- /**
93
- * LIFECYCLE HOOKS
94
- * */
95
- // Check stored state on mount and reopen if needed
96
- onMount(() => {
97
- if ($modalState.isOpen && !open) {
98
- // if(showLogs) console.log('Checking modal restoration:', {
99
- // currentType: modalType,
100
- // storedType: $modalState.modalType,
101
- // storedData: $modalState.data
102
- // });
103
-
104
- // Only reopen if this is the correct modal type
105
- if ($modalState.modalType === modalType) {
106
- // if(showLogs) console.log('Restoring modal state for:', modalType);
107
- open = true;
108
- modalData = $modalState.data;
109
- dispatch('restore', $modalState.data);
110
- }
111
- }
112
-
113
- // Initialize update content dimensions
114
- // updateViewportDimensions();
115
-
116
- // Add keydown listener
117
- window.addEventListener('keydown', handleKeydown);
118
-
119
- // Add resize listener
120
- // window.addEventListener('resize', updateViewportDimensions);
121
-
122
- return () => {
123
- // window.removeEventListener('resize', updateViewportDimensions);
124
- window.removeEventListener('keydown', handleKeydown);
125
-
126
- // Close the modal if it is open
127
- if (open) {
128
- closeModal();
129
- toggleUpdateContentDimensions(false);
130
- }
131
- };
132
- });
133
-
134
- // Function to update viewport dimensions
135
- function updateViewportDimensions() {// not used anymore
136
- // read window width and height
137
- // viewportWidth = window.innerWidth;
138
- // viewportHeight = window.innerHeight;
139
-
140
- // update the conetent holder height
141
- // setTimeout(() => {
142
- allContentHeight = 2 * contentPadding + titleHeight + contentHolderHeight;
143
- // if(showLogs) console.log('Modal current height:', allContentHeight);
144
- // let availableHeight = modalHeight - 2 * contentPadding;
145
- // if(showLogs) console.log('Modal current height:', allContentHeight, availableHeight);
146
- // contentHolderHeight = allContentHeight > availableHeight ? (availableHeight - titleHeight) + 'px' : 'auto';
147
- // modalFinalHeight = titleHeight + contentHeight + 2 * PADDING;
148
- // if(showLogs) console.log('Modal current height:', modalHeight, modalFinalHeight);
149
- // modalFinalHeight = Math.min(modalHeight, contentCurrentHeight);
150
- // }, 10);
151
- // if(showLogs) console.log('Updating viewport dimensions:', viewportWidth, viewportHeight);
152
- }
153
-
154
- // Reactive Statement to Calculate dynamic modal position and dimensions
155
- $: modalStyle = (() => {
156
- if (viewportWidth === 0 || viewportHeight === 0) {
157
- // Return default styles during SSR
158
- return {
159
- width: width,
160
- height: height,
161
- maxWidth: `calc(100vw - ${2 * PADDING}px)`,
162
- maxHeight: `calc(100vh - ${2 * PADDING}px)`,
163
- top: '50%',
164
- transform: 'translateY(-50%)'
165
- };
166
- }
167
-
168
- // Parse provided dimensions
169
- const providedWidth = parseInt(width);
170
- const providedHeight = parseInt(height);
171
-
172
- // Calculate maximum dimensions
173
- const maxWidth = viewportWidth - (2 * PADDING);
174
- const maxHeight = viewportHeight - (2 * PADDING);
175
-
176
- // Calculate actual dimensions
177
- const actualWidth = Math.min(providedWidth, maxWidth);
178
- const actualHeight = Math.min(providedHeight, maxHeight);
179
-
180
- // If the actual height is less than maxHeight, center it vertically(but ensure it never goes above PADDING
181
- const centeredTop = Math.max(PADDING, (viewportHeight - actualHeight) / 2);
182
-
183
- // Return the calculated styles
184
- return {
185
- width: `${actualWidth}px`,
186
- height: `${actualHeight}px`,
187
- maxWidth: `${maxWidth}px`,
188
- maxHeight: `${maxHeight}px`,
189
- top: `${centeredTop}px`,
190
- transform: 'none'
191
- };
192
- })();
193
-
194
- function handleKeydown(event: KeyboardEvent) {
195
- if (!open) return;
196
-
197
- // Only log keyboard events that we actually handle
198
- if (['Backspace', 'Escape', 'Space'].includes(event.key)) {
199
- // if(showLogs) console.log('Modal handling key:', event.key, 'Target:', (event.target as HTMLElement).tagName);
200
- }
201
-
202
- const target = event.target as HTMLElement;
203
- const isInput = target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable;
204
-
205
- // Let input fields handle their own events
206
- if (isInput) {
207
- return;
208
- }
209
-
210
- // Prevent backspace navigation when not in input
211
- if (event.key === 'Backspace') {
212
- event.preventDefault();
213
- event.stopPropagation();
214
- return;
215
- }
216
-
217
- if (event.key === 'Escape') {
218
- handleClose();
219
- }
220
-
221
- if (enableSpaceKey && open && event.code === 'Space') {
222
- event.preventDefault();
223
- event.stopPropagation();
224
- dispatch('spaceKey');
225
- }
226
- }
227
-
228
- function handleClose() {
229
- // if(showLogs) console.log('handleClose');
230
-
231
- // change the open state to false
232
- open = false;
233
-
234
- // Close the modal
235
- closeModal();
236
-
237
- // Stop updating the content dimensions
238
- toggleUpdateContentDimensions(false);
239
-
240
- // Dispatch close event
241
- dispatch('close');
242
- }
243
-
244
- // Only update the modal store when the open state actually changes
245
- let previousOpen = open;
246
- $: if (open !== previousOpen) {
247
- previousOpen = open;
248
-
249
- // if(showLogs) console.log('Modal open state changed:', { open, modalType, modalData });
250
-
251
- // Open the modal if the open state is true
252
- if (open) {
253
- openModal(modalType, modalData);
254
- toggleUpdateContentDimensions(true);
255
- } else {
256
- closeModal();
257
- toggleUpdateContentDimensions(false);
258
- }
259
- }
260
-
261
- // Improved backdrop click handler
262
- function handleBackdropClick(event: MouseEvent) {
263
- // Prevent any propagation
264
- event.preventDefault();
265
- event.stopPropagation();
266
-
267
- // Only handle clicks directly on the backdrop
268
- if (event.target === event.currentTarget) {
269
- // if(showLogs) console.log('Modal backdrop clicked!', event.type);
270
- handleClose();
271
- }
272
- }
2
+ // Svelte imports
3
+ import { fade, scale } from 'svelte/transition';
4
+ import { createEventDispatcher, onDestroy, onMount } from 'svelte';
5
+ const dispatch = createEventDispatcher();
6
+
7
+ // Design system componentsimports
8
+ import { IconButton, WScrollArea, Portal } from '..';
9
+ import { IconX } from '@tabler/icons-svelte';
10
+
11
+ // Modal store
12
+ import {
13
+ modalState,
14
+ openModal,
15
+ closeModal,
16
+ registerModalInstance,
17
+ unregisterModalInstance,
18
+ isTopModalInstance
19
+ } from './modalStore';
20
+ import {
21
+ overlayBackdropBlurPx,
22
+ overlayContentScaleStart,
23
+ overlayTransitionDurationMs,
24
+ overlayTransitionEasing
25
+ } from '../../utils/overlayMotion.js';
26
+
27
+ /* COMPONENT PROPS */
28
+ // class and style props
29
+ let className = '';
30
+ export { className as class };
31
+ export let style: string = '';
32
+ // modal dimensions
33
+ export let width: string = '808px';
34
+ export let height: string = '500px';
35
+ // modal props
36
+ export let modalType: string | undefined = undefined;
37
+ export let modalData: any = undefined;
38
+ // portal target
39
+ export let target: string = '';
40
+ // title props
41
+ export let title: string | undefined = undefined;
42
+ export let titlePadding: 'small' | 'large' = 'large';
43
+ // modal state props
44
+ export let open = false;
45
+ export let enableSpaceKey = false;
46
+ export let isSelected = false;
47
+ export let showScroll = false;
48
+ // An image preview stays hidden until its real size is known, so the card does not open large and then shrink.
49
+ export let shown = true;
50
+ // Width and height ease to the content, instead of jumping.
51
+ export let animateSize = false;
52
+ // The words a screen reader says for the close button. A page that shows the modal in another language passes its own.
53
+ export let closeLabel: string = 'Close modal';
54
+ // A bare modal has no title row, no close button and no padding: its content draws all of it (the File Browser's
55
+ // search dialog is one: the search field is the top row). Close it with Escape or a click on the backdrop.
56
+ export let bare: boolean = false;
57
+ /** When false, clicks and taps on the scrim do not close (destructive confirms can set this). Default matches Figma: scrim dismisses. */
58
+ export let closeOnBackdropClick = true;
59
+ /** When false, Escape does not close. Default: Escape dismisses unless a nested control already handled the key. */
60
+ export let closeOnEscape = true;
61
+
62
+ // children prop(svelte 5) to replace the traditional slot(svelte 4)
63
+ export let children: any;
64
+
65
+ /* CONSTANTS */
66
+ const transitionDuration = overlayTransitionDurationMs;
67
+ const backdropBlur = `${overlayBackdropBlurPx}px`;
68
+ const easing = overlayTransitionEasing;
69
+ const transitionOpts = { duration: transitionDuration, easing };
70
+
71
+ const focusRingGutterPx = 4;
72
+
73
+ // Update constants for modal constraints
74
+ const PADDING = 24; // Consistent padding for sides and bottom
75
+
76
+ /* STATE VARIABLES */
77
+ // Add viewport dimension state
78
+ let viewportWidth = 0;
79
+ let viewportHeight = 0;
80
+
81
+ // Modal vars
82
+ let modalHeight = 0; // Height of the modal including borders that's why binded to offsetHeight
83
+
84
+ // All Content Container vars
85
+ const contentPadding = 16;
86
+ let allContentHeight = 0;
87
+ // The card draws a 1px border and 24px of padding (a bare modal has none) around the title row and the content.
88
+ const CARD_BORDER = 1;
89
+ const CARD_PADDING = 24;
90
+ $: cardChromeHeight = 2 * CARD_BORDER + (bare ? 0 : 2 * CARD_PADDING);
91
+ // The title row (a bare modal has none) and the gap under it: 8px for a small title padding, 16px for a large one.
92
+ $: titleRowHeight = bare ? 0 : titleHeight + (titlePadding === 'small' ? 8 : 16);
93
+ // The card is exactly as tall as its content, the title row and what the card draws around them.
94
+ $: allContentHeight = cardChromeHeight + titleRowHeight + contentHolderHeight;
95
+
96
+ // Title vars
97
+ let titleHeight = 0;
98
+
99
+ // Content only vars
100
+ let contentHolderHeight = 0;
101
+ $: contentAvailableHeight = Math.max(0, modalHeight - cardChromeHeight - titleRowHeight);
102
+ let updateContentDimInterval: any;
103
+
104
+ /** Stays true until exit transitions finish or the instance is destroyed. */
105
+ let keepPortalMounted = false;
106
+ /** True while exit `out:` runs after `open` became false (parent `{#if}` or bind). */
107
+ let exiting = false;
108
+ let prevOpenForExit = open;
109
+ /** `openModal` pushed and this instance registered — popped only after outro or destroy. */
110
+ let storePushed = false;
111
+ let instanceToken: symbol | undefined;
112
+
113
+ let reduceMotion = false;
114
+
115
+ // Show logs
116
+ let showLogs = false;
117
+
118
+ /* METHODS */
119
+ function toggleUpdateContentDimensions(flag: boolean = true) {
120
+ if (!flag) {
121
+ clearInterval(updateContentDimInterval);
122
+ updateContentDimInterval = undefined;
123
+ } else {
124
+ // updateContentDimInterval = setInterval(() => {updateViewportDimensions()}, 200);
125
+ }
126
+ }
127
+
128
+ function pushModalStore() {
129
+ if (!storePushed) {
130
+ openModal(modalType, modalData);
131
+ instanceToken = registerModalInstance();
132
+ storePushed = true;
133
+ }
134
+ }
135
+
136
+ function popModalStore() {
137
+ if (storePushed) {
138
+ closeModal();
139
+ if (instanceToken !== undefined) {
140
+ unregisterModalInstance(instanceToken);
141
+ instanceToken = undefined;
142
+ }
143
+ storePushed = false;
144
+ }
145
+ }
146
+
147
+ function isTopModal(): boolean {
148
+ return instanceToken !== undefined && isTopModalInstance(instanceToken);
149
+ }
150
+
151
+ /**
152
+ * LIFECYCLE HOOKS
153
+ * */
154
+ // Check stored state on mount and reopen if needed
155
+ onMount(() => {
156
+ try {
157
+ reduceMotion =
158
+ typeof window !== 'undefined' &&
159
+ window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches === true;
160
+ } catch {
161
+ reduceMotion = false;
162
+ }
163
+
164
+ if ($modalState.isOpen && !open) {
165
+ // Only reopen if this is the correct modal type
166
+ if ($modalState.modalType === modalType) {
167
+ open = true;
168
+ modalData = $modalState.data;
169
+ dispatch('restore', $modalState.data);
170
+ }
171
+ }
172
+
173
+ window.addEventListener('keydown', handleKeydown);
174
+
175
+ return () => {
176
+ window.removeEventListener('keydown', handleKeydown);
177
+ popModalStore();
178
+ toggleUpdateContentDimensions(false);
179
+ };
180
+ });
181
+
182
+ onDestroy(() => {
183
+ popModalStore();
184
+ });
185
+
186
+ // Reactive Statement to Calculate dynamic modal position and dimensions
187
+ $: modalStyle = (() => {
188
+ if (viewportWidth === 0 || viewportHeight === 0) {
189
+ return {
190
+ width: width,
191
+ height: height,
192
+ maxWidth: `calc(100vw - ${2 * PADDING}px)`,
193
+ maxHeight: `calc(100vh - ${2 * PADDING}px)`,
194
+ top: '50%',
195
+ transform: 'translateY(-50%)'
196
+ };
197
+ }
198
+
199
+ const providedWidth = parseInt(width);
200
+ const providedHeight = parseInt(height);
201
+
202
+ const maxWidth = viewportWidth - 2 * PADDING;
203
+ const maxHeight = viewportHeight - 2 * PADDING;
204
+
205
+ const actualWidth = Math.min(providedWidth, maxWidth);
206
+ const actualHeight = Math.min(providedHeight, maxHeight);
207
+
208
+ const centeredTop = Math.max(PADDING, (viewportHeight - actualHeight) / 2);
209
+
210
+ return {
211
+ width: `${actualWidth}px`,
212
+ height: `${actualHeight}px`,
213
+ maxWidth: `${maxWidth}px`,
214
+ maxHeight: `${maxHeight}px`,
215
+ top: `${centeredTop}px`,
216
+ transform: 'none'
217
+ };
218
+ })();
219
+
220
+ function handleKeydown(event: KeyboardEvent) {
221
+ if (!isTopModal()) return;
222
+ if (!open) return;
223
+
224
+ if (['Backspace', 'Escape', 'Space'].includes(event.key)) {
225
+ // if(showLogs) console.log('Modal handling key:', event.key, 'Target:', (event.target as HTMLElement).tagName);
226
+ }
227
+
228
+ const target = event.target as HTMLElement;
229
+ const isInput =
230
+ target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable;
231
+
232
+ if (event.key === 'Escape' && closeOnEscape) {
233
+ // Escape: close from inputs too unless a nested listbox/combobox already called preventDefault.
234
+ if (!event.defaultPrevented) {
235
+ event.preventDefault();
236
+ event.stopPropagation();
237
+ handleClose();
238
+ }
239
+ return;
240
+ }
241
+
242
+ if (isInput) {
243
+ return;
244
+ }
245
+
246
+ if (event.key === 'Backspace') {
247
+ event.preventDefault();
248
+ event.stopPropagation();
249
+ return;
250
+ }
251
+
252
+ if (enableSpaceKey && open && event.code === 'Space') {
253
+ event.preventDefault();
254
+ event.stopPropagation();
255
+ dispatch('spaceKey');
256
+ }
257
+ }
258
+
259
+ function handleClose() {
260
+ open = false;
261
+ toggleUpdateContentDimensions(false);
262
+ dispatch('close');
263
+ }
264
+
265
+ function handleBackdropPointerDown(event: PointerEvent) {
266
+ if (!open || !closeOnBackdropClick || !isTopModal()) return;
267
+ // Only a press that *starts* on the scrim closes; starting inside the card and releasing outside does not.
268
+ if (event.target !== event.currentTarget) return;
269
+ event.preventDefault();
270
+ event.stopPropagation();
271
+ handleClose();
272
+ }
273
+
274
+ function handleBackdropClick(event: MouseEvent) {
275
+ if (!open || !closeOnBackdropClick || !isTopModal()) return;
276
+ // Only a press that *starts* on the scrim closes; starting inside the card and releasing outside does not.
277
+ if (event.target !== event.currentTarget) return;
278
+ event.preventDefault();
279
+ event.stopPropagation();
280
+ }
281
+
282
+ function handlePanelOutroEnd() {
283
+ exiting = false;
284
+ popModalStore();
285
+ if (!open) {
286
+ keepPortalMounted = false;
287
+ }
288
+ }
289
+
290
+ // Only update the modal store when `open` is true (push once per open); closing is handled in outro / destroy.
291
+ $: if (open) {
292
+ pushModalStore();
293
+ keepPortalMounted = true;
294
+ exiting = false;
295
+ }
296
+
297
+ $: if (prevOpenForExit && !open) {
298
+ exiting = true;
299
+ keepPortalMounted = true;
300
+ }
301
+ $: prevOpenForExit = open;
302
+
303
+ $: if (open) {
304
+ toggleUpdateContentDimensions(true);
305
+ } else if (!exiting) {
306
+ toggleUpdateContentDimensions(false);
307
+ }
273
308
  </script>
274
309
 
275
- {#if open}
276
- <Portal class="{className ? className + ' ' : ''}base-modal-portal"
277
- target={target || `modal-${modalType || 'default'}`}
278
- style={style}
279
- bind:clientWidth={viewportWidth}
280
- bind:clientHeight={viewportHeight}
281
- >
282
- <!-- Backdrop Container with blur restricted to file browser -->
283
- <!-- svelte-ignore a11y_no_static_element_interactions -->
284
- <!-- svelte-ignore a11y_click_events_have_key_events -->
285
- <div
286
- class="modal-backdrop-container absolute inset-0 pointer-events-auto"
287
- style="background-color: rgba(15, 32, 16, {backdropOpacity}); backdrop-filter: blur({backdropBlur}); z-index: 999;"
288
-
289
- transition:fade={{ duration: transitionDuration, easing }}
290
- on:mousedown={handleBackdropClick}
291
- on:click={handleBackdropClick}
292
- ></div>
293
-
294
- <!-- Close button -->
295
- <div class="modal-close-bt-container absolute top-4 end-4 z-1004" transition:fade={{ duration: transitionDuration, easing }}>
296
- <!-- svelte-ignore a11y-click-events-have-key-events -->
297
- <!-- svelte-ignore a11y-no-static-element-interactions -->
298
- <div
299
- class="close-button widgetic-colors p-0 w-10 h-10 bg-[hsl(var(--dark))] hover:bg-[hsl(var(--grey6))] transition-colors duration-200 rounded-full flex items-center justify-center cursor-pointer"
300
- on:click|stopPropagation|preventDefault={handleClose}
301
- aria-label={closeLabel}
302
- role="button"
303
- tabindex="0"
304
- >
305
- <IconX class="!w-[24px] !h-[24px] text-white" />
306
- </div>
307
- </div>
308
-
309
- <!-- Modal Main Container -->
310
- <div
311
- class="modal-root-container absolute w-full h-full inset-x-0 flex justify-center pointer-events-none items-center"
312
- style="z-index: 1000;"
313
- >
314
- <!-- Modal Content Container -->
315
- <div
316
- class="modal-content-inner-container fixed pointer-events-auto mx-6 overflow-hidden
317
- bg-white modal-shadow widgetic-colors rounded-medium widgetic-radius
318
- border select-text {isSelected && modalType === 'filePreview' ? 'border-[hsl(var(--grey1))]' : 'border-[hsl(var(--grey1))]'}"
319
- style="
310
+ {#if keepPortalMounted}
311
+ <Portal
312
+ class="{className ? className + ' ' : ''}base-modal-portal"
313
+ target={target || `modal-${modalType || 'default'}`}
314
+ {style}
315
+ bind:clientWidth={viewportWidth}
316
+ bind:clientHeight={viewportHeight}
317
+ >
318
+ {#if open || exiting}
319
+ <!-- Host-scoped scrim (absolute fills the portal host, not the viewport). -->
320
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
321
+ <div class="modal-dismiss-layer pointer-events-none absolute inset-0 z-[1000]">
322
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
323
+ <div
324
+ role="presentation"
325
+ data-widgetic-overlay="base-modal-backdrop"
326
+ class="modal-backdrop-container bg-muted-foreground/80 pointer-events-auto absolute inset-0"
327
+ style="backdrop-filter: blur({backdropBlur}); z-index: 0;"
328
+ in:fade|global={reduceMotion ? { duration: 0 } : transitionOpts}
329
+ out:fade|global={reduceMotion ? { duration: 0 } : transitionOpts}
330
+ on:pointerdown={handleBackdropPointerDown}
331
+ on:click={handleBackdropClick}
332
+ ></div>
333
+ <div
334
+ class="modal-root-container pointer-events-none absolute inset-0 z-[1] flex items-center justify-center px-6"
335
+ >
336
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
337
+ <div
338
+ data-widgetic-overlay="base-modal-panel"
339
+ class="modal-content-inner-container bg-background border-border rounded-panel pointer-events-auto relative max-h-[calc(100vh-3rem)] select-text overflow-hidden border {bare
340
+ ? 'p-0'
341
+ : 'p-6'}"
342
+ style="
320
343
  width: {modalStyle.width};
321
344
  height: {allContentHeight}px;
322
- min-width: {2 * contentPadding + 100}px;
323
- min-height: {2 * contentPadding + 50}px;
345
+ min-width: {bare ? 100 : 2 * contentPadding + 100}px;
346
+ min-height: {bare ? 50 : 2 * contentPadding + 50}px;
324
347
  max-width: {modalStyle.maxWidth};
325
348
  max-height: {modalStyle.maxHeight};
326
349
  visibility: {viewportWidth === 0 || !shown ? 'hidden' : 'visible'};
327
350
  transition: {animateSize ? 'width 200ms ease, height 200ms ease' : 'none'};
328
- padding: {contentPadding}px 0;
329
351
  user-select: text;
330
352
  -webkit-user-select: text;
331
353
  -moz-user-select: text;
332
354
  -ms-user-select: text;
333
- z-index: 1003;
355
+ z-index: 2;
334
356
  "
335
- bind:offsetHeight={modalHeight}
336
- in:scale={{ duration: transitionDuration, start: 0.95, opacity: 0, easing }}
337
- out:scale={{ duration: transitionDuration, start: 0.95, opacity: 0, easing }}
338
- on:click|stopPropagation
339
- >
340
- <!-- Content Title -->
341
- {#if title}
342
- <div bind:clientHeight={titleHeight}
343
- class="modal-title-container h5 text-[hsl(var(--grey7))] overflow-hidden
344
- text-center px-4 {titlePadding === 'small' ? 'pb-2' : 'pb-4'}">
345
- {title}
346
- </div>
347
- {/if}
348
-
349
- <!-- Content Slot Container -->
350
- {#snippet contentSlot()}
351
- <div class="modal-content-slot-container w-full" bind:clientHeight={contentHolderHeight}>
352
- {@render children()}
353
- </div>
354
- {/snippet}
355
-
356
- <!-- Content with Scroll Area -->
357
- {#if showScroll}
358
- <WScrollArea class="modal-content-container scroll-container" type="always" scrollOutside={true}
359
- style="height: {contentAvailableHeight}px"
360
- >
361
- {@render contentSlot()}
362
- </WScrollArea>
363
- {:else}
364
- <div class="modal-content-container"
365
- style="height: {contentAvailableHeight}px;"
366
- >
367
- {@render contentSlot()}
368
- </div>
369
- {/if}
370
-
371
- </div>
372
- </div>
373
- </Portal>
357
+ bind:offsetHeight={modalHeight}
358
+ in:scale|global={reduceMotion
359
+ ? { duration: 0 }
360
+ : {
361
+ duration: transitionDuration,
362
+ start: overlayContentScaleStart,
363
+ opacity: 0,
364
+ easing
365
+ }}
366
+ out:scale|global={reduceMotion
367
+ ? { duration: 0 }
368
+ : {
369
+ duration: transitionDuration,
370
+ start: overlayContentScaleStart,
371
+ opacity: 0,
372
+ easing
373
+ }}
374
+ on:outroend={handlePanelOutroEnd}
375
+ on:pointerdown|stopPropagation
376
+ on:click|stopPropagation
377
+ >
378
+ {#if !bare}
379
+ <div
380
+ bind:clientHeight={titleHeight}
381
+ class="modal-title-container flex items-start justify-between gap-2 {titlePadding === 'small'
382
+ ? 'mb-2'
383
+ : 'mb-4'}"
384
+ aria-selected={isSelected}
385
+ >
386
+ {#if title}
387
+ <h2 class="text-heading-page text-foreground min-w-0 flex-1 break-words text-start">
388
+ {title}
389
+ </h2>
390
+ {:else}
391
+ <div class="min-w-0 flex-1"></div>
392
+ {/if}
393
+ <IconButton variant="ghost" label={closeLabel} onclick={handleClose}>
394
+ <IconX class="size-4 stroke-[1.333]" />
395
+ </IconButton>
396
+ </div>
397
+ {/if}
398
+
399
+ {#snippet contentSlot()}
400
+ <div
401
+ class="modal-content-slot-container w-full"
402
+ bind:clientHeight={contentHolderHeight}
403
+ >
404
+ {@render children()}
405
+ </div>
406
+ {/snippet}
407
+
408
+ {#if bare}
409
+ {#if showScroll}
410
+ <WScrollArea
411
+ class="modal-content-container scroll-container"
412
+ type="always"
413
+ scrollOutside={true}
414
+ style="height: {contentAvailableHeight}px"
415
+ >
416
+ {@render contentSlot()}
417
+ </WScrollArea>
418
+ {:else}
419
+ <div class="modal-content-container" style="height: {contentAvailableHeight}px;">
420
+ {@render contentSlot()}
421
+ </div>
422
+ {/if}
423
+ {:else}
424
+ <!--
425
+ Focus ring gutter: the clip wrapper uses margin -4px / padding +4px so the overflow clip edge sits
426
+ outside the content box; the inner flow/scroll root keeps the same footprint as before (no inner
427
+ padding), so layout parity stays 25px from the card border. For showScroll, the scroll root is
428
+ extended by 8px and an inner wrapper provides the 4px pad for rings without changing measured height.
429
+ -->
430
+ <div
431
+ class="modal-focus-ring-clip min-h-0 overflow-hidden"
432
+ style="margin: -{focusRingGutterPx}px; padding: {focusRingGutterPx}px; border-radius: inherit;"
433
+ >
434
+ {#if showScroll}
435
+ <WScrollArea
436
+ class="modal-scroll-gutter-host scroll-container max-h-full min-h-0"
437
+ type="always"
438
+ scrollOutside={true}
439
+ style="margin: -{focusRingGutterPx}px; width: calc(100% + {2 *
440
+ focusRingGutterPx}px); height: {contentAvailableHeight + 2 * focusRingGutterPx}px;"
441
+ >
442
+ <div class="modal-scroll-focus-gutter box-border min-h-0 p-1">
443
+ {@render contentSlot()}
444
+ </div>
445
+ </WScrollArea>
446
+ {:else}
447
+ <div
448
+ class="modal-content-flow max-h-full min-h-0"
449
+ style="height: {contentAvailableHeight}px;"
450
+ >
451
+ {@render contentSlot()}
452
+ </div>
453
+ {/if}
454
+ </div>
455
+ {/if}
456
+ </div>
457
+ </div>
458
+ </div>
459
+ {/if}
460
+ </Portal>
374
461
  {/if}
375
462
 
376
463
  <style>
377
- .modal-content-container {
378
- width: 100%;
379
- height: 100%;
380
- overflow: hidden;
381
- }
382
-
383
- /* Add specific styling for the backdrop to ensure it captures all clicks */
384
- .modal-backdrop-container {
385
- pointer-events: all !important;
386
- }
387
-
388
- :global(.ScrollAreaViewport) {
389
- width: 100%;
390
- height: 100%;
391
- border-radius: inherit;
392
- }
393
-
394
- :global(.ScrollAreaScrollbar[data-state='hidden']) {
395
- display: none;
396
- }
397
-
398
- /* Ensure text selection works in modals */
399
- :global(.base-modal-portal),
400
- :global(.modal-content-inner-container),
401
- :global(.modal-content-root-container) {
402
- user-select: text !important;
403
- -webkit-user-select: text !important;
404
- -moz-user-select: text !important;
405
- -ms-user-select: text !important;
406
- }
407
-
408
- /* Ensure text selection works in input fields */
409
- :global(.base-modal-portal input),
410
- :global(.base-modal-portal textarea),
411
- :global(.base-modal-portal select),
412
- :global(.base-modal-portal [contenteditable="true"]) {
413
- user-select: text !important;
414
- -webkit-user-select: text !important;
415
- -moz-user-select: text !important;
416
- -ms-user-select: text !important;
417
- cursor: text !important;
418
- }
464
+ .modal-content-container {
465
+ width: 100%;
466
+ height: 100%;
467
+ overflow: hidden;
468
+ }
469
+
470
+ /* Non-scroll body: no overflow:hidden here — the outer card + clip wrapper handle clipping; avoids cutting focus rings. */
471
+ .modal-content-flow {
472
+ width: 100%;
473
+ overflow: visible;
474
+ }
475
+
476
+ .modal-dismiss-layer {
477
+ pointer-events: none;
478
+ }
479
+
480
+ .modal-backdrop-container {
481
+ pointer-events: auto;
482
+ }
483
+
484
+ :global(.ScrollAreaViewport) {
485
+ width: 100%;
486
+ height: 100%;
487
+ border-radius: inherit;
488
+ }
489
+
490
+ :global(.ScrollAreaScrollbar[data-state='hidden']) {
491
+ display: none;
492
+ }
493
+
494
+ :global(.base-modal-portal),
495
+ :global(.modal-content-inner-container),
496
+ :global(.modal-content-root-container) {
497
+ user-select: text !important;
498
+ -webkit-user-select: text !important;
499
+ -moz-user-select: text !important;
500
+ -ms-user-select: text !important;
501
+ }
502
+
503
+ :global(.base-modal-portal input),
504
+ :global(.base-modal-portal textarea),
505
+ :global(.base-modal-portal select),
506
+ :global(.base-modal-portal [contenteditable='true']) {
507
+ user-select: text !important;
508
+ -webkit-user-select: text !important;
509
+ -moz-user-select: text !important;
510
+ -ms-user-select: text !important;
511
+ cursor: text !important;
512
+ }
419
513
  </style>