@placeholderco/placeholder-ui 1.0.12 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +134 -130
  3. package/dist/app.css +178 -0
  4. package/dist/bootstrap.min.css +3600 -0
  5. package/dist/cards/GitCard.svelte +115 -0
  6. package/dist/cards/GitCard.svelte.d.ts +22 -0
  7. package/dist/display/Avatar.svelte +29 -17
  8. package/dist/display/Avatar.svelte.d.ts +12 -2
  9. package/dist/display/LinkCollection.svelte +40 -18
  10. package/dist/display/LinkCollection.svelte.d.ts +9 -2
  11. package/dist/display/Paper.svelte +31 -19
  12. package/dist/display/Paper.svelte.d.ts +12 -3
  13. package/dist/display/alert/Alert.svelte +129 -0
  14. package/dist/display/alert/Alert.svelte.d.ts +15 -0
  15. package/dist/display/alert/AlertVariant.d.ts +1 -0
  16. package/dist/display/alert/AlertVariant.js +1 -0
  17. package/dist/display/filetree/FileTree.svelte +259 -0
  18. package/dist/display/filetree/FileTree.svelte.d.ts +76 -0
  19. package/dist/display/filetree/FileTreeItem.svelte +396 -0
  20. package/dist/display/filetree/FileTreeItem.svelte.d.ts +40 -0
  21. package/dist/display/filetree/FileTreeNode.d.ts +46 -0
  22. package/dist/display/filetree/FileTreeNode.js +115 -0
  23. package/dist/display/filetree/index.d.ts +5 -0
  24. package/dist/display/filetree/index.js +3 -0
  25. package/dist/editors/CodeMirror.svelte +306 -0
  26. package/dist/editors/CodeMirror.svelte.d.ts +31 -0
  27. package/dist/editors/tiptap/LinkActionMenu.svelte +307 -0
  28. package/dist/editors/tiptap/LinkActionMenu.svelte.d.ts +8 -0
  29. package/dist/editors/tiptap/TipTap.svelte +856 -0
  30. package/dist/editors/tiptap/TipTap.svelte.d.ts +54 -0
  31. package/dist/editors/tiptap/TipTapBubble.svelte +790 -0
  32. package/dist/editors/tiptap/TipTapBubble.svelte.d.ts +44 -0
  33. package/dist/editors/tiptap/collaboration.d.ts +55 -0
  34. package/dist/editors/tiptap/collaboration.js +1 -0
  35. package/dist/editors/tiptap/extensions/Alert.d.ts +18 -0
  36. package/dist/editors/tiptap/extensions/Alert.js +72 -0
  37. package/dist/editors/tiptap/extensions/CodeBlockEditor.d.ts +21 -0
  38. package/dist/editors/tiptap/extensions/CodeBlockEditor.js +326 -0
  39. package/dist/editors/tiptap/extensions/Div.d.ts +19 -0
  40. package/dist/editors/tiptap/extensions/Div.js +89 -0
  41. package/dist/editors/tiptap/extensions/Span.d.ts +5 -0
  42. package/dist/editors/tiptap/extensions/Span.js +56 -0
  43. package/dist/editors/tiptap/extensions/codeBlockEditor.css +236 -0
  44. package/dist/editors/tiptap/extensions/codeBlockLanguages.d.ts +17 -0
  45. package/dist/editors/tiptap/extensions/codeBlockLanguages.js +124 -0
  46. package/dist/editors/tiptap/extensions/index.d.ts +7 -0
  47. package/dist/editors/tiptap/extensions/index.js +5 -0
  48. package/dist/editors/tiptap/toolbar/AlertButton.svelte +261 -0
  49. package/dist/editors/tiptap/toolbar/AlertButton.svelte.d.ts +7 -0
  50. package/dist/editors/tiptap/toolbar/AlignCenterButton.svelte +18 -0
  51. package/dist/editors/tiptap/toolbar/AlignCenterButton.svelte.d.ts +7 -0
  52. package/dist/editors/tiptap/toolbar/AlignJustifyButton.svelte +18 -0
  53. package/dist/editors/tiptap/toolbar/AlignJustifyButton.svelte.d.ts +7 -0
  54. package/dist/editors/tiptap/toolbar/AlignLeftButton.svelte +18 -0
  55. package/dist/editors/tiptap/toolbar/AlignLeftButton.svelte.d.ts +7 -0
  56. package/dist/editors/tiptap/toolbar/AlignRightButton.svelte +18 -0
  57. package/dist/editors/tiptap/toolbar/AlignRightButton.svelte.d.ts +7 -0
  58. package/dist/editors/tiptap/toolbar/BoldButton.svelte +18 -0
  59. package/dist/editors/tiptap/toolbar/BoldButton.svelte.d.ts +7 -0
  60. package/dist/editors/tiptap/toolbar/BulletListButton.svelte +18 -0
  61. package/dist/editors/tiptap/toolbar/BulletListButton.svelte.d.ts +7 -0
  62. package/dist/editors/tiptap/toolbar/CodeBlockButton.svelte +18 -0
  63. package/dist/editors/tiptap/toolbar/CodeBlockButton.svelte.d.ts +7 -0
  64. package/dist/editors/tiptap/toolbar/CodeButton.svelte +18 -0
  65. package/dist/editors/tiptap/toolbar/CodeButton.svelte.d.ts +7 -0
  66. package/dist/editors/tiptap/toolbar/ColorButton.svelte +299 -0
  67. package/dist/editors/tiptap/toolbar/ColorButton.svelte.d.ts +20 -0
  68. package/dist/editors/tiptap/toolbar/DocumentButton.svelte +58 -0
  69. package/dist/editors/tiptap/toolbar/DocumentButton.svelte.d.ts +9 -0
  70. package/dist/editors/tiptap/toolbar/HeadingButton.svelte +30 -0
  71. package/dist/editors/tiptap/toolbar/HeadingButton.svelte.d.ts +8 -0
  72. package/dist/editors/tiptap/toolbar/HtmlButton.svelte +68 -0
  73. package/dist/editors/tiptap/toolbar/HtmlButton.svelte.d.ts +7 -0
  74. package/dist/editors/tiptap/toolbar/ImageButton.svelte +54 -0
  75. package/dist/editors/tiptap/toolbar/ImageButton.svelte.d.ts +8 -0
  76. package/dist/editors/tiptap/toolbar/ItalicButton.svelte +18 -0
  77. package/dist/editors/tiptap/toolbar/ItalicButton.svelte.d.ts +7 -0
  78. package/dist/editors/tiptap/toolbar/LinkButton.svelte +257 -0
  79. package/dist/editors/tiptap/toolbar/LinkButton.svelte.d.ts +7 -0
  80. package/dist/editors/tiptap/toolbar/OrderedListButton.svelte +18 -0
  81. package/dist/editors/tiptap/toolbar/OrderedListButton.svelte.d.ts +7 -0
  82. package/dist/editors/tiptap/toolbar/RedoButton.svelte +18 -0
  83. package/dist/editors/tiptap/toolbar/RedoButton.svelte.d.ts +7 -0
  84. package/dist/editors/tiptap/toolbar/StrikethroughButton.svelte +18 -0
  85. package/dist/editors/tiptap/toolbar/StrikethroughButton.svelte.d.ts +7 -0
  86. package/dist/editors/tiptap/toolbar/SubscriptButton.svelte +18 -0
  87. package/dist/editors/tiptap/toolbar/SubscriptButton.svelte.d.ts +7 -0
  88. package/dist/editors/tiptap/toolbar/SuperscriptButton.svelte +18 -0
  89. package/dist/editors/tiptap/toolbar/SuperscriptButton.svelte.d.ts +7 -0
  90. package/dist/editors/tiptap/toolbar/TableButton.svelte +258 -0
  91. package/dist/editors/tiptap/toolbar/TableButton.svelte.d.ts +7 -0
  92. package/dist/editors/tiptap/toolbar/ToolbarButton.svelte +84 -0
  93. package/dist/editors/tiptap/toolbar/ToolbarButton.svelte.d.ts +10 -0
  94. package/dist/editors/tiptap/toolbar/UnderlineButton.svelte +18 -0
  95. package/dist/editors/tiptap/toolbar/UnderlineButton.svelte.d.ts +7 -0
  96. package/dist/editors/tiptap/toolbar/UndoButton.svelte +18 -0
  97. package/dist/editors/tiptap/toolbar/UndoButton.svelte.d.ts +7 -0
  98. package/dist/form/Autocomplete.svelte +86 -135
  99. package/dist/form/Autocomplete.svelte.d.ts +6 -8
  100. package/dist/form/AutocompleteMulti.svelte +385 -227
  101. package/dist/form/AutocompleteMulti.svelte.d.ts +59 -10
  102. package/dist/form/Checkbox.svelte +59 -54
  103. package/dist/form/Checkbox.svelte.d.ts +14 -5
  104. package/dist/form/Chips.svelte +29 -13
  105. package/dist/form/Chips.svelte.d.ts +16 -2
  106. package/dist/form/ColorPicker.svelte +957 -0
  107. package/dist/form/ColorPicker.svelte.d.ts +78 -0
  108. package/dist/form/ComboBox.svelte +30 -20
  109. package/dist/form/ComboBox.svelte.d.ts +1 -1
  110. package/dist/form/ComboBoxItemBuilder.svelte +161 -188
  111. package/dist/form/ComboBoxItemBuilder.svelte.d.ts +19 -2
  112. package/dist/form/ComboBoxMulti.svelte +38 -24
  113. package/dist/form/ComboBoxMulti.svelte.d.ts +1 -1
  114. package/dist/form/CronBuilder.svelte +84 -67
  115. package/dist/form/CronBuilder.svelte.d.ts +16 -3
  116. package/dist/form/DatePicker.svelte +148 -195
  117. package/dist/form/DateRangePicker.svelte +101 -98
  118. package/dist/form/DateTimePicker.svelte +167 -208
  119. package/dist/form/DateTimePicker.svelte.d.ts +10 -2
  120. package/dist/form/FormGroup.svelte +33 -10
  121. package/dist/form/FormGroup.svelte.d.ts +12 -2
  122. package/dist/form/Number.svelte +70 -47
  123. package/dist/form/Number.svelte.d.ts +36 -6
  124. package/dist/form/Radio.svelte +18 -18
  125. package/dist/form/RadioGroup.svelte +23 -12
  126. package/dist/form/RadioGroup.svelte.d.ts +13 -2
  127. package/dist/form/SegmentedControl.svelte +76 -56
  128. package/dist/form/SegmentedControl.svelte.d.ts +9 -4
  129. package/dist/form/Select.svelte +150 -233
  130. package/dist/form/Select.svelte.d.ts +29 -5
  131. package/dist/form/SelectMulti.svelte +163 -277
  132. package/dist/form/SelectMulti.svelte.d.ts +36 -7
  133. package/dist/form/Slider.svelte +183 -269
  134. package/dist/form/Slider.svelte.d.ts +32 -17
  135. package/dist/form/StringArrayBuilder.svelte +249 -0
  136. package/dist/form/StringArrayBuilder.svelte.d.ts +15 -0
  137. package/dist/form/Switch.svelte +164 -106
  138. package/dist/form/Switch.svelte.d.ts +22 -3
  139. package/dist/form/TextArea.svelte +49 -29
  140. package/dist/form/TextArea.svelte.d.ts +23 -3
  141. package/dist/form/Textbox.svelte +69 -68
  142. package/dist/form/Textbox.svelte.d.ts +32 -4
  143. package/dist/form/TimePicker.svelte +42 -47
  144. package/dist/form/TimePicker.svelte.d.ts +12 -2
  145. package/dist/form/comboBoxCore.svelte.d.ts +99 -0
  146. package/dist/form/comboBoxCore.svelte.js +243 -0
  147. package/dist/icon/Icon.svelte +8 -3
  148. package/dist/icon/Icon.svelte.d.ts +8 -3
  149. package/dist/icon/file-type-bmp.svg +1 -0
  150. package/dist/icon/file-type-csv.svg +1 -0
  151. package/dist/icon/file-type-doc.svg +1 -0
  152. package/dist/icon/file-type-docx.svg +1 -0
  153. package/dist/icon/file-type-jpg.svg +1 -0
  154. package/dist/icon/file-type-pdf.svg +1 -0
  155. package/dist/icon/file-type-png.svg +1 -0
  156. package/dist/icon/file-type-ppt.svg +1 -0
  157. package/dist/icon/file-type-svg.svg +1 -0
  158. package/dist/icon/file-type-txt.svg +1 -0
  159. package/dist/icon/file-type-xls.svg +1 -0
  160. package/dist/icon/file-type-zip.svg +1 -0
  161. package/dist/icon/index.d.ts +166 -151
  162. package/dist/icon/index.js +166 -152
  163. package/dist/icon/logo-placeholder.svg +6 -0
  164. package/dist/icon/source-code.svg +1 -0
  165. package/dist/icon/sun-moon.svg +1 -0
  166. package/dist/icon/table.svg +6 -0
  167. package/dist/index.d.ts +146 -82
  168. package/dist/index.js +77 -78
  169. package/dist/layout/AppShell.svelte +12 -162
  170. package/dist/layout/AppShell.svelte.d.ts +6 -27
  171. package/dist/layout/CustomNavbar.svelte +9 -7
  172. package/dist/layout/CustomNavbar.svelte.d.ts +4 -2
  173. package/dist/layout/Navbar.svelte +161 -129
  174. package/dist/layout/Navbar.svelte.d.ts +29 -15
  175. package/dist/layout/NavbarItemDisplay.svelte +2 -2
  176. package/dist/layout/NavbarItemView.svelte +182 -0
  177. package/dist/layout/NavbarItemView.svelte.d.ts +10 -0
  178. package/dist/layout/Sidenav.svelte +280 -341
  179. package/dist/layout/Sidenav.svelte.d.ts +36 -4
  180. package/dist/models/ComboBoxItem.d.ts +2 -0
  181. package/dist/models/Employee.d.ts +53 -0
  182. package/dist/models/Employee.js +23 -0
  183. package/dist/models/Hyperlink.d.ts +3 -1
  184. package/dist/models/LinkCollectionItem.d.ts +3 -1
  185. package/dist/models/NavbarItem.d.ts +6 -2
  186. package/dist/theme.svelte.d.ts +0 -1
  187. package/dist/theme.svelte.js +46 -58
  188. package/dist/ui/Accordion.svelte +81 -242
  189. package/dist/ui/Accordion.svelte.d.ts +37 -14
  190. package/dist/ui/AccordionItem.svelte +303 -0
  191. package/dist/ui/AccordionItem.svelte.d.ts +21 -0
  192. package/dist/ui/ActionIcon.svelte +25 -21
  193. package/dist/ui/ActionIcon.svelte.d.ts +15 -2
  194. package/dist/ui/Badge.svelte +163 -171
  195. package/dist/ui/Badge.svelte.d.ts +13 -5
  196. package/dist/ui/Button.svelte +178 -205
  197. package/dist/ui/Button.svelte.d.ts +19 -2
  198. package/dist/ui/ButtonVariant.d.ts +15 -1
  199. package/dist/ui/ContextMenu.svelte +146 -0
  200. package/dist/ui/ContextMenu.svelte.d.ts +37 -0
  201. package/dist/ui/ContextMenuList.svelte +188 -0
  202. package/dist/ui/ContextMenuList.svelte.d.ts +12 -0
  203. package/dist/ui/Dialog.svelte +183 -111
  204. package/dist/ui/Dialog.svelte.d.ts +21 -4
  205. package/dist/ui/Drawer.svelte +237 -184
  206. package/dist/ui/Drawer.svelte.d.ts +20 -2
  207. package/dist/ui/Dropdown.svelte +24 -14
  208. package/dist/ui/Dropdown.svelte.d.ts +11 -2
  209. package/dist/ui/Dropzone.svelte +159 -57
  210. package/dist/ui/Dropzone.svelte.d.ts +29 -2
  211. package/dist/ui/Link.svelte +12 -4
  212. package/dist/ui/Link.svelte.d.ts +8 -2
  213. package/dist/ui/Loader.svelte +23 -18
  214. package/dist/ui/Loader.svelte.d.ts +5 -2
  215. package/dist/ui/Logo.svelte +37 -0
  216. package/dist/ui/Logo.svelte.d.ts +16 -0
  217. package/dist/ui/MultiSortable.svelte +287 -0
  218. package/dist/ui/MultiSortable.svelte.d.ts +50 -0
  219. package/dist/ui/Pagination.svelte +15 -9
  220. package/dist/ui/Pagination.svelte.d.ts +6 -2
  221. package/dist/ui/Popover.svelte +43 -65
  222. package/dist/ui/Popover.svelte.d.ts +14 -5
  223. package/dist/ui/ProgressBar.svelte +28 -30
  224. package/dist/ui/ProgressBarVariant.d.ts +7 -1
  225. package/dist/ui/SegmentedControl.svelte +0 -0
  226. package/dist/ui/SegmentedControl.svelte.d.ts +26 -0
  227. package/dist/ui/Sortable.svelte +163 -0
  228. package/dist/ui/Sortable.svelte.d.ts +47 -0
  229. package/dist/ui/Table.svelte +383 -80
  230. package/dist/ui/Table.svelte.d.ts +43 -2
  231. package/dist/ui/Tabs.svelte +22 -18
  232. package/dist/ui/Tabs.svelte.d.ts +6 -2
  233. package/dist/ui/ThemeSwitcher.svelte +26 -7
  234. package/dist/ui/ThemeSwitcher.svelte.d.ts +4 -2
  235. package/dist/ui/Tooltip.svelte +312 -180
  236. package/dist/ui/Tooltip.svelte.d.ts +4 -3
  237. package/dist/uiTheme.svelte.d.ts +86 -0
  238. package/dist/uiTheme.svelte.js +214 -0
  239. package/dist/util/DateFunctions.js +1 -1
  240. package/dist/util/DragDrop.d.ts +50 -0
  241. package/dist/util/DragDrop.js +154 -0
  242. package/dist/util/NavigateTo.js +2 -2
  243. package/dist/util/dayjs.d.ts +4 -0
  244. package/dist/util/dayjs.js +15 -0
  245. package/package.json +66 -26
  246. package/dist/display/Alert.svelte +0 -179
  247. package/dist/display/Alert.svelte.d.ts +0 -13
  248. package/dist/form/FileInput.svelte +0 -235
  249. package/dist/form/FileInput.svelte.d.ts +0 -23
  250. package/dist/form/PasswordInput.svelte +0 -253
  251. package/dist/form/PasswordInput.svelte.d.ts +0 -34
  252. package/dist/form/Rating.svelte +0 -235
  253. package/dist/form/Rating.svelte.d.ts +0 -21
  254. package/dist/styles/components.css +0 -199
  255. package/dist/styles/dark.css +0 -146
  256. package/dist/styles/index.css +0 -116
  257. package/dist/styles/reset.css +0 -110
  258. package/dist/styles/semantic.css +0 -86
  259. package/dist/styles/tokens.css +0 -203
  260. package/dist/styles/utilities.css +0 -523
  261. package/dist/ui/Breadcrumbs.svelte +0 -131
  262. package/dist/ui/Breadcrumbs.svelte.d.ts +0 -16
  263. package/dist/ui/LoadingOverlay.svelte +0 -53
  264. package/dist/ui/LoadingOverlay.svelte.d.ts +0 -11
  265. package/dist/ui/Progress.svelte +0 -191
  266. package/dist/ui/Progress.svelte.d.ts +0 -15
  267. package/dist/ui/RingProgress.svelte +0 -141
  268. package/dist/ui/RingProgress.svelte.d.ts +0 -18
  269. package/dist/ui/Skeleton.svelte +0 -85
  270. package/dist/ui/Skeleton.svelte.d.ts +0 -12
  271. package/dist/ui/Stepper.svelte +0 -355
  272. package/dist/ui/Stepper.svelte.d.ts +0 -24
  273. package/dist/ui/Timeline.svelte +0 -225
  274. package/dist/ui/Timeline.svelte.d.ts +0 -23
  275. /package/dist/{actions → util}/ClickOutside.d.ts +0 -0
  276. /package/dist/{actions → util}/ClickOutside.js +0 -0
  277. /package/dist/{transitions → util}/Transitions.d.ts +0 -0
  278. /package/dist/{transitions → util}/Transitions.js +0 -0
@@ -0,0 +1,146 @@
1
+ <script lang="ts" module>
2
+ export interface ContextMenuItem {
3
+ /** Text label. Omit when `separator` is set. */
4
+ label?: string;
5
+ /** Optional leading icon (raw SVG string, e.g. `iconEdit`). */
6
+ icon?: string;
7
+ /** Invoked when the item is clicked. Ignored for separators/parents. */
8
+ action?: () => void;
9
+ /** Non-interactive, dimmed. */
10
+ disabled?: boolean;
11
+ /** Render in the destructive/danger colour. */
12
+ danger?: boolean;
13
+ /** Render a divider instead of a clickable row. */
14
+ separator?: boolean;
15
+ /** Right-aligned shortcut hint (e.g. `⌘C`, `Del`). Purely visual. */
16
+ shortcut?: string;
17
+ /** Nested submenu items — opens on hover. */
18
+ children?: ContextMenuItem[];
19
+ }
20
+
21
+ export interface ContextMenuProps {
22
+ /** Whether the menu is open (bindable). */
23
+ open?: boolean;
24
+ /** Viewport X coordinate to anchor at (usually `event.clientX`). */
25
+ x?: number;
26
+ /** Viewport Y coordinate to anchor at (usually `event.clientY`). */
27
+ y?: number;
28
+ /** The items to display. */
29
+ items?: ContextMenuItem[];
30
+ /** Minimum width of the (top-level) menu. */
31
+ minWidth?: string;
32
+ /** Additional CSS classes on the menu container. */
33
+ class?: string;
34
+ /** Fires whenever the menu closes (dismissed or item chosen). */
35
+ onClose?: () => void;
36
+ }
37
+ </script>
38
+
39
+ <script lang="ts">
40
+ import ContextMenuList from './ContextMenuList.svelte';
41
+
42
+ let {
43
+ open = $bindable(false),
44
+ x = 0,
45
+ y = 0,
46
+ items = [],
47
+ minWidth = '180px',
48
+ class: classes = '',
49
+ onClose
50
+ }: ContextMenuProps = $props();
51
+
52
+ let menuEl = $state<HTMLElement | null>(null);
53
+ let posX = $state(0);
54
+ let posY = $state(0);
55
+ let placed = $state(false);
56
+
57
+ /** Move the menu to <body> so it can't be clipped by overflow/transform ancestors. */
58
+ function portal(node: HTMLElement) {
59
+ document.body.appendChild(node);
60
+ return {
61
+ destroy() {
62
+ node.remove();
63
+ }
64
+ };
65
+ }
66
+
67
+ function close() {
68
+ if (!open) return;
69
+ open = false;
70
+ onClose?.();
71
+ }
72
+
73
+ function handleSelect(item: ContextMenuItem) {
74
+ item.action?.();
75
+ close();
76
+ }
77
+
78
+ // Clamp the menu into the viewport once it's measured. Hidden until placed to avoid a flash.
79
+ $effect(() => {
80
+ if (!open) {
81
+ placed = false;
82
+ return;
83
+ }
84
+ if (!menuEl) return;
85
+ const anchorX = x;
86
+ const anchorY = y;
87
+ const rect = menuEl.getBoundingClientRect();
88
+ const vw = window.innerWidth;
89
+ const vh = window.innerHeight;
90
+ let nx = anchorX;
91
+ let ny = anchorY;
92
+ if (nx + rect.width > vw - 8) nx = Math.max(8, vw - rect.width - 8);
93
+ if (ny + rect.height > vh - 8) ny = Math.max(8, vh - rect.height - 8);
94
+ posX = nx;
95
+ posY = ny;
96
+ placed = true;
97
+ });
98
+
99
+ // Dismiss the menu on outside interaction while it's open.
100
+ $effect(() => {
101
+ if (!open) return;
102
+ const onDown = (e: MouseEvent) => {
103
+ if (menuEl && !menuEl.contains(e.target as Node)) close();
104
+ };
105
+ const onKey = (e: KeyboardEvent) => {
106
+ if (e.key === 'Escape') {
107
+ e.preventDefault();
108
+ close();
109
+ }
110
+ };
111
+ const onScroll = (e: Event) => {
112
+ if (!menuEl || !menuEl.contains(e.target as Node)) close();
113
+ };
114
+ const onResize = () => close();
115
+ document.addEventListener('mousedown', onDown, true);
116
+ document.addEventListener('keydown', onKey, true);
117
+ window.addEventListener('scroll', onScroll, true);
118
+ window.addEventListener('resize', onResize);
119
+ return () => {
120
+ document.removeEventListener('mousedown', onDown, true);
121
+ document.removeEventListener('keydown', onKey, true);
122
+ window.removeEventListener('scroll', onScroll, true);
123
+ window.removeEventListener('resize', onResize);
124
+ };
125
+ });
126
+ </script>
127
+
128
+ {#if open}
129
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
130
+ <div
131
+ use:portal
132
+ bind:this={menuEl}
133
+ class="pui-ctxmenu {classes}"
134
+ style="left: {posX}px; top: {posY}px; visibility: {placed ? 'visible' : 'hidden'};"
135
+ oncontextmenu={(e) => e.preventDefault()}
136
+ >
137
+ <ContextMenuList {items} onSelect={handleSelect} {minWidth} depth={0} />
138
+ </div>
139
+ {/if}
140
+
141
+ <style>
142
+ .pui-ctxmenu {
143
+ position: fixed;
144
+ z-index: 2000;
145
+ }
146
+ </style>
@@ -0,0 +1,37 @@
1
+ export interface ContextMenuItem {
2
+ /** Text label. Omit when `separator` is set. */
3
+ label?: string;
4
+ /** Optional leading icon (raw SVG string, e.g. `iconEdit`). */
5
+ icon?: string;
6
+ /** Invoked when the item is clicked. Ignored for separators/parents. */
7
+ action?: () => void;
8
+ /** Non-interactive, dimmed. */
9
+ disabled?: boolean;
10
+ /** Render in the destructive/danger colour. */
11
+ danger?: boolean;
12
+ /** Render a divider instead of a clickable row. */
13
+ separator?: boolean;
14
+ /** Right-aligned shortcut hint (e.g. `⌘C`, `Del`). Purely visual. */
15
+ shortcut?: string;
16
+ /** Nested submenu items — opens on hover. */
17
+ children?: ContextMenuItem[];
18
+ }
19
+ export interface ContextMenuProps {
20
+ /** Whether the menu is open (bindable). */
21
+ open?: boolean;
22
+ /** Viewport X coordinate to anchor at (usually `event.clientX`). */
23
+ x?: number;
24
+ /** Viewport Y coordinate to anchor at (usually `event.clientY`). */
25
+ y?: number;
26
+ /** The items to display. */
27
+ items?: ContextMenuItem[];
28
+ /** Minimum width of the (top-level) menu. */
29
+ minWidth?: string;
30
+ /** Additional CSS classes on the menu container. */
31
+ class?: string;
32
+ /** Fires whenever the menu closes (dismissed or item chosen). */
33
+ onClose?: () => void;
34
+ }
35
+ declare const ContextMenu: import("svelte").Component<ContextMenuProps, {}, "open">;
36
+ type ContextMenu = ReturnType<typeof ContextMenu>;
37
+ export default ContextMenu;
@@ -0,0 +1,188 @@
1
+ <script lang="ts" module>
2
+ import type { ContextMenuItem } from './ContextMenu.svelte';
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ import Icon from '../icon/Icon.svelte';
7
+ import { iconChevronRight } from '../icon/index.js';
8
+ import Self from './ContextMenuList.svelte';
9
+
10
+ interface Props {
11
+ items: ContextMenuItem[];
12
+ /** Bubbles a chosen (leaf) item up to the root ContextMenu. */
13
+ onSelect: (item: ContextMenuItem) => void;
14
+ /** Nesting level — only the top level applies `minWidth`. */
15
+ depth?: number;
16
+ minWidth?: string;
17
+ }
18
+
19
+ let { items, onSelect, depth = 0, minWidth = '' }: Props = $props();
20
+
21
+ // Only one submenu per list can be open at a time (the hovered item).
22
+ let hoverIndex = $state<number | null>(null);
23
+ let submenuEl = $state<HTMLElement | null>(null);
24
+ let flipLeft = $state(false);
25
+
26
+ function onItemEnter(i: number, item: ContextMenuItem) {
27
+ hoverIndex = item.children?.length ? i : null;
28
+ }
29
+
30
+ function handleClick(item: ContextMenuItem) {
31
+ if (item.disabled || item.separator || item.children?.length) return;
32
+ onSelect(item);
33
+ }
34
+
35
+ // Decide whether the open submenu should flip to the left edge of its parent.
36
+ // Measured from the parent <li> right edge so the result is stable once flipped.
37
+ $effect(() => {
38
+ if (hoverIndex === null || !submenuEl) return;
39
+ const parent = submenuEl.parentElement;
40
+ if (!parent) return;
41
+ const parentRect = parent.getBoundingClientRect();
42
+ const width = submenuEl.offsetWidth;
43
+ flipLeft = parentRect.right + width > window.innerWidth - 4;
44
+ });
45
+ </script>
46
+
47
+ <ul
48
+ class="pui-ctxmenu-list"
49
+ role="menu"
50
+ style={depth === 0 && minWidth ? `min-width: ${minWidth};` : ''}
51
+ >
52
+ {#each items as item, i (i)}
53
+ {#if item.separator}
54
+ <li class="pui-ctxmenu-sep" role="separator"></li>
55
+ {:else}
56
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
57
+ <li class="pui-ctxmenu-li" role="none" onmouseenter={() => onItemEnter(i, item)}>
58
+ <button
59
+ type="button"
60
+ role="menuitem"
61
+ class="pui-ctxmenu-item"
62
+ class:danger={item.danger}
63
+ disabled={item.disabled}
64
+ onclick={() => handleClick(item)}
65
+ >
66
+ <span class="pui-ctxmenu-ico">
67
+ {#if item.icon}<Icon svg={item.icon} size="16px" />{/if}
68
+ </span>
69
+ <span class="pui-ctxmenu-label">{item.label}</span>
70
+ {#if item.shortcut}
71
+ <span class="pui-ctxmenu-shortcut">{item.shortcut}</span>
72
+ {/if}
73
+ {#if item.children?.length}
74
+ <span class="pui-ctxmenu-caret"><Icon svg={iconChevronRight} size="14px" /></span>
75
+ {/if}
76
+ </button>
77
+
78
+ {#if item.children?.length && hoverIndex === i}
79
+ <div class="pui-ctxmenu-submenu" class:flip-left={flipLeft} bind:this={submenuEl}>
80
+ <Self items={item.children} {onSelect} depth={depth + 1} />
81
+ </div>
82
+ {/if}
83
+ </li>
84
+ {/if}
85
+ {/each}
86
+ </ul>
87
+
88
+ <style>
89
+ .pui-ctxmenu-list {
90
+ list-style: none;
91
+ margin: 0;
92
+ padding: 0.25rem;
93
+ background: var(--paper-body-bg, #fff);
94
+ border: 1px solid var(--border-color);
95
+ border-radius: 6px;
96
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
97
+ color: var(--text-color);
98
+ font-size: 0.875rem;
99
+ user-select: none;
100
+ }
101
+
102
+ .pui-ctxmenu-li {
103
+ position: relative;
104
+ }
105
+
106
+ .pui-ctxmenu-item {
107
+ display: flex;
108
+ align-items: center;
109
+ gap: 0.5rem;
110
+ width: 100%;
111
+ padding: 0.375rem 0.6rem;
112
+ background: none;
113
+ border: 0;
114
+ border-radius: 4px;
115
+ color: inherit;
116
+ font: inherit;
117
+ text-align: left;
118
+ white-space: nowrap;
119
+ cursor: pointer;
120
+ }
121
+
122
+ .pui-ctxmenu-item:hover:not(:disabled),
123
+ .pui-ctxmenu-item:focus-visible {
124
+ background: rgba(var(--ui-primary-rgbc), 0.1);
125
+ outline: none;
126
+ }
127
+
128
+ :global(.dark) .pui-ctxmenu-item:hover:not(:disabled),
129
+ :global(.dark) .pui-ctxmenu-item:focus-visible {
130
+ background: rgba(var(--ui-primary-rgbc), 0.28);
131
+ }
132
+
133
+ .pui-ctxmenu-item:disabled {
134
+ opacity: 0.45;
135
+ cursor: default;
136
+ }
137
+
138
+ .pui-ctxmenu-item.danger {
139
+ color: var(--danger-text, #e03131);
140
+ }
141
+
142
+ .pui-ctxmenu-item.danger:hover:not(:disabled) {
143
+ background: var(--danger-bg-subtle, rgba(224, 49, 49, 0.12));
144
+ }
145
+
146
+ .pui-ctxmenu-ico {
147
+ display: inline-flex;
148
+ width: 16px;
149
+ flex-shrink: 0;
150
+ }
151
+
152
+ .pui-ctxmenu-label {
153
+ flex: 1;
154
+ }
155
+
156
+ .pui-ctxmenu-shortcut {
157
+ padding-left: 1.25rem;
158
+ font-size: 0.75rem;
159
+ color: var(--label-color, #888);
160
+ }
161
+
162
+ .pui-ctxmenu-caret {
163
+ display: inline-flex;
164
+ margin-left: auto;
165
+ opacity: 0.7;
166
+ }
167
+
168
+ .pui-ctxmenu-sep {
169
+ height: 1px;
170
+ margin: 0.25rem 0.3rem;
171
+ background: var(--border-color);
172
+ }
173
+
174
+ .pui-ctxmenu-submenu {
175
+ position: absolute;
176
+ top: -0.25rem;
177
+ left: 100%;
178
+ z-index: 1;
179
+ padding-left: 2px;
180
+ }
181
+
182
+ .pui-ctxmenu-submenu.flip-left {
183
+ left: auto;
184
+ right: 100%;
185
+ padding-left: 0;
186
+ padding-right: 2px;
187
+ }
188
+ </style>
@@ -0,0 +1,12 @@
1
+ import type { ContextMenuItem } from './ContextMenu.svelte';
2
+ interface Props {
3
+ items: ContextMenuItem[];
4
+ /** Bubbles a chosen (leaf) item up to the root ContextMenu. */
5
+ onSelect: (item: ContextMenuItem) => void;
6
+ /** Nesting level — only the top level applies `minWidth`. */
7
+ depth?: number;
8
+ minWidth?: string;
9
+ }
10
+ declare const ContextMenuList: import("svelte").Component<Props, {}, "">;
11
+ type ContextMenuList = ReturnType<typeof ContextMenuList>;
12
+ export default ContextMenuList;