@marianmeres/stuic 3.167.0 → 3.168.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 (103) hide show
  1. package/AGENTS.md +6 -6
  2. package/dist/attachments/index.d.ts +1 -0
  3. package/dist/attachments/index.js +1 -0
  4. package/dist/attachments/long-press.d.ts +43 -0
  5. package/dist/attachments/long-press.js +107 -0
  6. package/dist/components/Breadcrumbs/Breadcrumbs.svelte +198 -0
  7. package/dist/components/Breadcrumbs/Breadcrumbs.svelte.d.ts +60 -0
  8. package/dist/components/Breadcrumbs/README.md +174 -0
  9. package/dist/components/Breadcrumbs/i18n-sk.d.ts +17 -0
  10. package/dist/components/Breadcrumbs/i18n-sk.js +19 -0
  11. package/dist/components/Breadcrumbs/i18n.d.ts +31 -0
  12. package/dist/components/Breadcrumbs/i18n.js +40 -0
  13. package/dist/components/Breadcrumbs/index.css +84 -0
  14. package/dist/components/Breadcrumbs/index.d.ts +4 -0
  15. package/dist/components/Breadcrumbs/index.js +4 -0
  16. package/dist/components/Breadcrumbs/json-ld.d.ts +58 -0
  17. package/dist/components/Breadcrumbs/json-ld.js +56 -0
  18. package/dist/components/Collapsible/Collapsible.svelte +84 -15
  19. package/dist/components/Collapsible/Collapsible.svelte.d.ts +29 -2
  20. package/dist/components/Collapsible/README.md +82 -23
  21. package/dist/components/Collapsible/index.css +38 -0
  22. package/dist/components/Collapsible/index.d.ts +1 -1
  23. package/dist/components/ContextMenu/ContextMenu.svelte +302 -0
  24. package/dist/components/ContextMenu/ContextMenu.svelte.d.ts +94 -0
  25. package/dist/components/ContextMenu/README.md +159 -0
  26. package/dist/components/ContextMenu/i18n-sk.d.ts +17 -0
  27. package/dist/components/ContextMenu/i18n-sk.js +18 -0
  28. package/dist/components/ContextMenu/i18n.d.ts +30 -0
  29. package/dist/components/ContextMenu/i18n.js +38 -0
  30. package/dist/components/ContextMenu/index.css +26 -0
  31. package/dist/components/ContextMenu/index.d.ts +3 -0
  32. package/dist/components/ContextMenu/index.js +3 -0
  33. package/dist/components/CopyButton/CopyButton.svelte +278 -0
  34. package/dist/components/CopyButton/CopyButton.svelte.d.ts +76 -0
  35. package/dist/components/CopyButton/README.md +241 -0
  36. package/dist/components/CopyButton/i18n-sk.d.ts +17 -0
  37. package/dist/components/CopyButton/i18n-sk.js +20 -0
  38. package/dist/components/CopyButton/i18n.d.ts +35 -0
  39. package/dist/components/CopyButton/i18n.js +43 -0
  40. package/dist/components/CopyButton/index.css +67 -0
  41. package/dist/components/CopyButton/index.d.ts +3 -0
  42. package/dist/components/CopyButton/index.js +3 -0
  43. package/dist/components/DataTable/DataTable.svelte +41 -34
  44. package/dist/components/DataTable/README.md +45 -0
  45. package/dist/components/DataTable/i18n-sk.js +1 -0
  46. package/dist/components/DataTable/i18n.d.ts +1 -0
  47. package/dist/components/DataTable/i18n.js +3 -0
  48. package/dist/components/DataTable/index.css +41 -3
  49. package/dist/components/EmptyState/EmptyState.svelte +121 -0
  50. package/dist/components/EmptyState/EmptyState.svelte.d.ts +40 -0
  51. package/dist/components/EmptyState/README.md +188 -0
  52. package/dist/components/EmptyState/index.css +154 -0
  53. package/dist/components/EmptyState/index.d.ts +1 -0
  54. package/dist/components/EmptyState/index.js +1 -0
  55. package/dist/components/Pagination/Pagination.svelte +250 -0
  56. package/dist/components/Pagination/Pagination.svelte.d.ts +69 -0
  57. package/dist/components/Pagination/README.md +152 -0
  58. package/dist/components/Pagination/i18n-sk.d.ts +17 -0
  59. package/dist/components/Pagination/i18n-sk.js +24 -0
  60. package/dist/components/Pagination/i18n.d.ts +41 -0
  61. package/dist/components/Pagination/i18n.js +49 -0
  62. package/dist/components/Pagination/index.css +50 -0
  63. package/dist/components/Pagination/index.d.ts +4 -0
  64. package/dist/components/Pagination/index.js +4 -0
  65. package/dist/components/Pagination/pagination-range.d.ts +21 -0
  66. package/dist/components/Pagination/pagination-range.js +41 -0
  67. package/dist/components/Stat/README.md +157 -0
  68. package/dist/components/Stat/Stat.svelte +226 -0
  69. package/dist/components/Stat/Stat.svelte.d.ts +54 -0
  70. package/dist/components/Stat/index.css +217 -0
  71. package/dist/components/Stat/index.d.ts +1 -0
  72. package/dist/components/Stat/index.js +1 -0
  73. package/dist/components/Stepper/README.md +181 -0
  74. package/dist/components/Stepper/Stepper.svelte +240 -0
  75. package/dist/components/Stepper/Stepper.svelte.d.ts +86 -0
  76. package/dist/components/Stepper/i18n-sk.d.ts +17 -0
  77. package/dist/components/Stepper/i18n-sk.js +21 -0
  78. package/dist/components/Stepper/i18n.d.ts +35 -0
  79. package/dist/components/Stepper/i18n.js +43 -0
  80. package/dist/components/Stepper/index.css +302 -0
  81. package/dist/components/Stepper/index.d.ts +3 -0
  82. package/dist/components/Stepper/index.js +3 -0
  83. package/dist/components/TrendChart/README.md +123 -0
  84. package/dist/components/TrendChart/TrendChart.svelte +114 -0
  85. package/dist/components/TrendChart/TrendChart.svelte.d.ts +36 -0
  86. package/dist/components/TrendChart/index.css +51 -0
  87. package/dist/components/TrendChart/index.d.ts +1 -0
  88. package/dist/components/TrendChart/index.js +1 -0
  89. package/dist/icons/index.d.ts +4 -0
  90. package/dist/icons/index.js +4 -0
  91. package/dist/index.css +7 -0
  92. package/dist/index.d.ts +7 -0
  93. package/dist/index.js +12 -0
  94. package/dist/utils/copy-to-clipboard.d.ts +43 -0
  95. package/dist/utils/copy-to-clipboard.js +102 -0
  96. package/dist/utils/index.d.ts +1 -0
  97. package/dist/utils/index.js +1 -0
  98. package/docs/architecture.md +1 -1
  99. package/docs/domains/attachments.md +41 -4
  100. package/docs/domains/components.md +280 -13
  101. package/docs/domains/utils.md +8 -6
  102. package/docs/maybe-todo.md +111 -0
  103. package/package.json +11 -1
@@ -0,0 +1,302 @@
1
+ <script lang="ts" module>
2
+ import type { Snippet } from "svelte";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+ import type { TranslateFn } from "../../types.js";
5
+ import type {
6
+ DropdownMenuActionItem,
7
+ DropdownMenuItem,
8
+ DropdownMenuPosition,
9
+ DropdownMenuSearchConfig,
10
+ } from "../DropdownMenu/DropdownMenu.svelte";
11
+
12
+ /** ContextMenu shares the DropdownMenu item model — aliases for consumer DX. */
13
+ export type ContextMenuItem = DropdownMenuItem;
14
+ export type ContextMenuActionItem = DropdownMenuActionItem;
15
+ export type ContextMenuPosition = DropdownMenuPosition;
16
+ export type ContextMenuSearchConfig = DropdownMenuSearchConfig;
17
+
18
+ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
19
+ /** Menu items to display (the DropdownMenu item model) */
20
+ items: ContextMenuItem[];
21
+ /** The right-clickable / long-pressable target area content */
22
+ children?: Snippet;
23
+ /** Controlled open state (bindable). Setting `true` programmatically with no
24
+ * recorded coordinates positions the menu at the target area's top-left. */
25
+ isOpen?: boolean;
26
+ /** When true, triggers are inert and the browser's native menu works again */
27
+ disabled?: boolean;
28
+ /** Long-press duration in ms for touch/pen pointers (default 500);
29
+ * `false` disables the long-press trigger entirely. */
30
+ longPress?: number | false;
31
+ /** Menu placement relative to the pointer (default "bottom-span-right" —
32
+ * below-right of the cursor, the native context menu convention) */
33
+ position?: ContextMenuPosition;
34
+ /** Offset of the menu from the pointer (CSS value, default "0px") */
35
+ offset?: string;
36
+ /** Max height of the menu (CSS value) */
37
+ maxHeight?: string;
38
+ /** Whether to close the menu when an action item is selected */
39
+ closeOnSelect?: boolean;
40
+ /** Close on click outside */
41
+ closeOnClickOutside?: boolean;
42
+ /** Close on Escape key */
43
+ closeOnEscape?: boolean;
44
+ /** Force fallback positioning mode (for testing) */
45
+ forceFallback?: boolean;
46
+ /** Enable search/filter functionality (true for defaults, or config object) */
47
+ search?: boolean | ContextMenuSearchConfig;
48
+ /** Show backdrop in fallback mode (default: true) */
49
+ showBackdrop?: boolean;
50
+ /** Reserve scrollbar space to prevent layout shift on open (see DropdownMenu) */
51
+ scrollbarGutter?: boolean;
52
+ /** Skip the body scroll lock in fallback mode */
53
+ noScrollLock?: boolean;
54
+ /** Called when the menu opens */
55
+ onOpen?: () => void;
56
+ /** Called when the menu closes */
57
+ onClose?: () => void;
58
+ /** Called when any action item is selected (fallback if item has no onSelect) */
59
+ onSelect?: (item: ContextMenuActionItem) => void | boolean | Promise<void | boolean>;
60
+ /** i18n translate function (see `createContextMenuT`) — localizes the
61
+ * screen-reader-only menu label */
62
+ t?: TranslateFn;
63
+ /** Opt out of stuic base classes for full styling control */
64
+ unstyled?: boolean;
65
+ /** Classes for the target area wrapper */
66
+ class?: string;
67
+ /** Classes for the menu container */
68
+ classDropdown?: string;
69
+ /** Classes for action items */
70
+ classItem?: string;
71
+ /** Classes for active/focused action item */
72
+ classItemActive?: string;
73
+ /** Classes for disabled items */
74
+ classItemDisabled?: string;
75
+ /** Classes for content before label */
76
+ classItemBefore?: string;
77
+ /** Classes for content after label */
78
+ classItemAfter?: string;
79
+ /** Classes for dividers */
80
+ classDivider?: string;
81
+ /** Classes for header items */
82
+ classHeader?: string;
83
+ /** Classes for expandable section header */
84
+ classExpandable?: string;
85
+ /** Classes for expandable section content */
86
+ classExpandableContent?: string;
87
+ /** Classes for backdrop (fallback mode only) */
88
+ classBackdrop?: string;
89
+ /** Classes for search input container */
90
+ classSearchContainer?: string;
91
+ /** Classes for search input */
92
+ classSearchInput?: string;
93
+ /** Reference to the target area wrapper element */
94
+ el?: HTMLDivElement;
95
+ /** Reference to the open menu element */
96
+ dropdownEl?: HTMLDivElement;
97
+ }
98
+
99
+ export const CONTEXT_MENU_BASE_CLASSES = `stuic-context-menu`;
100
+
101
+ export const CONTEXT_MENU_ANCHOR_CLASSES = `stuic-context-menu-anchor`;
102
+ </script>
103
+
104
+ <script lang="ts">
105
+ import { twMerge } from "../../utils/tw-merge.js";
106
+ import { fixedContainingBlockRect } from "../../utils/containing-block.js";
107
+ import { longPress as longPressAttachment } from "../../attachments/long-press.js";
108
+ import DropdownMenu from "../DropdownMenu/DropdownMenu.svelte";
109
+ import { t_default } from "./i18n.js";
110
+
111
+ let {
112
+ items,
113
+ children,
114
+ isOpen = $bindable(false),
115
+ disabled = false,
116
+ longPress = 500,
117
+ position = "bottom-span-right",
118
+ offset = "0px",
119
+ maxHeight,
120
+ closeOnSelect,
121
+ closeOnClickOutside,
122
+ closeOnEscape,
123
+ forceFallback,
124
+ search,
125
+ showBackdrop,
126
+ scrollbarGutter,
127
+ noScrollLock,
128
+ onOpen,
129
+ onClose,
130
+ onSelect,
131
+ t = t_default,
132
+ unstyled = false,
133
+ class: classProp,
134
+ classDropdown,
135
+ classItem,
136
+ classItemActive,
137
+ classItemDisabled,
138
+ classItemBefore,
139
+ classItemAfter,
140
+ classDivider,
141
+ classHeader,
142
+ classExpandable,
143
+ classExpandableContent,
144
+ classBackdrop,
145
+ classSearchContainer,
146
+ classSearchInput,
147
+ el = $bindable(),
148
+ dropdownEl = $bindable(),
149
+ ...rest
150
+ }: Props = $props();
151
+
152
+ // Anchor coordinates within the anchor's fixed containing block (viewport,
153
+ // unless a transformed/contained ancestor establishes one).
154
+ let x = $state(0);
155
+ let y = $state(0);
156
+ let hasPosition = $state(false);
157
+
158
+ // For keyboard users: what had focus when the menu opened, restored on close.
159
+ let prevFocused: HTMLElement | null = null;
160
+
161
+ /** Open the menu with its anchor at the given viewport coordinates. */
162
+ function openAt(clientX: number, clientY: number) {
163
+ if (disabled) return;
164
+ // clientX/Y are viewport coords, but the fixed anchor resolves against its
165
+ // containing block — translate when a transformed ancestor establishes one
166
+ const cb = el ? fixedContainingBlockRect(el) : undefined;
167
+ x = clientX - (cb?.left ?? 0);
168
+ y = clientY - (cb?.top ?? 0);
169
+ hasPosition = true;
170
+ if (!isOpen) {
171
+ const active = document.activeElement;
172
+ prevFocused =
173
+ active instanceof HTMLElement && active !== document.body ? active : null;
174
+ isOpen = true;
175
+ }
176
+ }
177
+
178
+ /** Open at an element's bottom-left corner (keyboard invocation convention). */
179
+ function openAtElement(target?: Element | null) {
180
+ const rect = (target ?? el)?.getBoundingClientRect();
181
+ if (!rect) return;
182
+ openAt(rect.left, rect.bottom);
183
+ }
184
+
185
+ function focusedElementWithin(): Element | null {
186
+ const active = document.activeElement;
187
+ return active && el?.contains(active) ? active : (el ?? null);
188
+ }
189
+
190
+ function _onContextMenu(e: MouseEvent) {
191
+ if (disabled) return;
192
+ e.preventDefault();
193
+ // nested context areas: the innermost one wins
194
+ e.stopPropagation();
195
+ if (e.clientX === 0 && e.clientY === 0) {
196
+ // keyboard-invoked (menu key synthesizes contextmenu with no coords)
197
+ openAtElement(focusedElementWithin());
198
+ } else {
199
+ openAt(e.clientX, e.clientY);
200
+ }
201
+ }
202
+
203
+ function _onKeydown(e: KeyboardEvent) {
204
+ if (disabled) return;
205
+ if (e.key === "ContextMenu" || (e.shiftKey && e.key === "F10")) {
206
+ e.preventDefault();
207
+ e.stopPropagation();
208
+ openAtElement(focusedElementWithin());
209
+ }
210
+ }
211
+
212
+ function _onClose() {
213
+ onClose?.();
214
+ if (prevFocused?.isConnected) prevFocused.focus();
215
+ prevFocused = null;
216
+ }
217
+
218
+ // Programmatic open (isOpen set true from outside) with no coords yet —
219
+ // anchor to the target area instead of the (0,0) default.
220
+ $effect(() => {
221
+ if (isOpen && !hasPosition && el) {
222
+ openAtElement(el);
223
+ }
224
+ });
225
+
226
+ const _longPressAttach = $derived(
227
+ longPress !== false &&
228
+ !disabled &&
229
+ longPressAttachment({
230
+ duration: longPress,
231
+ onLongPress: (e) => openAt(e.clientX, e.clientY),
232
+ })
233
+ );
234
+ </script>
235
+
236
+ <div
237
+ bind:this={el}
238
+ class={unstyled ? classProp : twMerge(CONTEXT_MENU_BASE_CLASSES, classProp)}
239
+ data-longpress={!unstyled && longPress !== false && !disabled ? "" : undefined}
240
+ data-open={!unstyled && isOpen ? "" : undefined}
241
+ aria-haspopup="menu"
242
+ {...rest}
243
+ oncontextmenu={_onContextMenu}
244
+ onkeydown={_onKeydown}
245
+ {@attach _longPressAttach}
246
+ >
247
+ {@render children?.()}
248
+ </div>
249
+
250
+ <!-- Invisible 0x0 anchor moved to the pointer — the DropdownMenu positions against it -->
251
+ <div
252
+ role="presentation"
253
+ class={unstyled ? undefined : CONTEXT_MENU_ANCHOR_CLASSES}
254
+ style:position="fixed"
255
+ style:left="{x}px"
256
+ style:top="{y}px"
257
+ style:width="0"
258
+ style:height="0"
259
+ oncontextmenu={(e) => e.preventDefault()}
260
+ >
261
+ <DropdownMenu
262
+ bind:isOpen
263
+ bind:dropdownEl
264
+ {items}
265
+ {position}
266
+ {offset}
267
+ {maxHeight}
268
+ {closeOnSelect}
269
+ {closeOnClickOutside}
270
+ {closeOnEscape}
271
+ {forceFallback}
272
+ {search}
273
+ {showBackdrop}
274
+ {scrollbarGutter}
275
+ {noScrollLock}
276
+ {onOpen}
277
+ onClose={_onClose}
278
+ {onSelect}
279
+ {unstyled}
280
+ {classDropdown}
281
+ {classItem}
282
+ {classItemActive}
283
+ {classItemDisabled}
284
+ {classItemBefore}
285
+ {classItemAfter}
286
+ {classDivider}
287
+ {classHeader}
288
+ {classExpandable}
289
+ {classExpandableContent}
290
+ {classBackdrop}
291
+ {classSearchContainer}
292
+ {classSearchInput}
293
+ >
294
+ {#snippet trigger({ triggerProps })}
295
+ <!-- No visible trigger — an sr-only label keeps the menu's
296
+ aria-labelledby reference valid and names it for AT -->
297
+ <span class="sr-only" id={triggerProps.id}>
298
+ {t("context_menu", null, "Context menu")}
299
+ </span>
300
+ {/snippet}
301
+ </DropdownMenu>
302
+ </div>
@@ -0,0 +1,94 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ import type { TranslateFn } from "../../types.js";
4
+ import type { DropdownMenuActionItem, DropdownMenuItem, DropdownMenuPosition, DropdownMenuSearchConfig } from "../DropdownMenu/DropdownMenu.svelte";
5
+ /** ContextMenu shares the DropdownMenu item model — aliases for consumer DX. */
6
+ export type ContextMenuItem = DropdownMenuItem;
7
+ export type ContextMenuActionItem = DropdownMenuActionItem;
8
+ export type ContextMenuPosition = DropdownMenuPosition;
9
+ export type ContextMenuSearchConfig = DropdownMenuSearchConfig;
10
+ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
11
+ /** Menu items to display (the DropdownMenu item model) */
12
+ items: ContextMenuItem[];
13
+ /** The right-clickable / long-pressable target area content */
14
+ children?: Snippet;
15
+ /** Controlled open state (bindable). Setting `true` programmatically with no
16
+ * recorded coordinates positions the menu at the target area's top-left. */
17
+ isOpen?: boolean;
18
+ /** When true, triggers are inert and the browser's native menu works again */
19
+ disabled?: boolean;
20
+ /** Long-press duration in ms for touch/pen pointers (default 500);
21
+ * `false` disables the long-press trigger entirely. */
22
+ longPress?: number | false;
23
+ /** Menu placement relative to the pointer (default "bottom-span-right" —
24
+ * below-right of the cursor, the native context menu convention) */
25
+ position?: ContextMenuPosition;
26
+ /** Offset of the menu from the pointer (CSS value, default "0px") */
27
+ offset?: string;
28
+ /** Max height of the menu (CSS value) */
29
+ maxHeight?: string;
30
+ /** Whether to close the menu when an action item is selected */
31
+ closeOnSelect?: boolean;
32
+ /** Close on click outside */
33
+ closeOnClickOutside?: boolean;
34
+ /** Close on Escape key */
35
+ closeOnEscape?: boolean;
36
+ /** Force fallback positioning mode (for testing) */
37
+ forceFallback?: boolean;
38
+ /** Enable search/filter functionality (true for defaults, or config object) */
39
+ search?: boolean | ContextMenuSearchConfig;
40
+ /** Show backdrop in fallback mode (default: true) */
41
+ showBackdrop?: boolean;
42
+ /** Reserve scrollbar space to prevent layout shift on open (see DropdownMenu) */
43
+ scrollbarGutter?: boolean;
44
+ /** Skip the body scroll lock in fallback mode */
45
+ noScrollLock?: boolean;
46
+ /** Called when the menu opens */
47
+ onOpen?: () => void;
48
+ /** Called when the menu closes */
49
+ onClose?: () => void;
50
+ /** Called when any action item is selected (fallback if item has no onSelect) */
51
+ onSelect?: (item: ContextMenuActionItem) => void | boolean | Promise<void | boolean>;
52
+ /** i18n translate function (see `createContextMenuT`) — localizes the
53
+ * screen-reader-only menu label */
54
+ t?: TranslateFn;
55
+ /** Opt out of stuic base classes for full styling control */
56
+ unstyled?: boolean;
57
+ /** Classes for the target area wrapper */
58
+ class?: string;
59
+ /** Classes for the menu container */
60
+ classDropdown?: string;
61
+ /** Classes for action items */
62
+ classItem?: string;
63
+ /** Classes for active/focused action item */
64
+ classItemActive?: string;
65
+ /** Classes for disabled items */
66
+ classItemDisabled?: string;
67
+ /** Classes for content before label */
68
+ classItemBefore?: string;
69
+ /** Classes for content after label */
70
+ classItemAfter?: string;
71
+ /** Classes for dividers */
72
+ classDivider?: string;
73
+ /** Classes for header items */
74
+ classHeader?: string;
75
+ /** Classes for expandable section header */
76
+ classExpandable?: string;
77
+ /** Classes for expandable section content */
78
+ classExpandableContent?: string;
79
+ /** Classes for backdrop (fallback mode only) */
80
+ classBackdrop?: string;
81
+ /** Classes for search input container */
82
+ classSearchContainer?: string;
83
+ /** Classes for search input */
84
+ classSearchInput?: string;
85
+ /** Reference to the target area wrapper element */
86
+ el?: HTMLDivElement;
87
+ /** Reference to the open menu element */
88
+ dropdownEl?: HTMLDivElement;
89
+ }
90
+ export declare const CONTEXT_MENU_BASE_CLASSES = "stuic-context-menu";
91
+ export declare const CONTEXT_MENU_ANCHOR_CLASSES = "stuic-context-menu-anchor";
92
+ declare const ContextMenu: import("svelte").Component<Props, {}, "el" | "isOpen" | "dropdownEl">;
93
+ type ContextMenu = ReturnType<typeof ContextMenu>;
94
+ export default ContextMenu;
@@ -0,0 +1,159 @@
1
+ # ContextMenu
2
+
3
+ Right-click / long-press triggered menu, positioned at the pointer. Wrap any content
4
+ in `<ContextMenu items={...}>` and the wrapped area becomes a context target:
5
+ a `contextmenu` event (right-click), a long-press (touch/pen pointers), or
6
+ Shift+F10 / the dedicated menu key (keyboard) opens the menu anchored at the
7
+ interaction point.
8
+
9
+ The menu panel itself **is** a [`DropdownMenu`](../DropdownMenu/README.md) — the full
10
+ item model (actions, dividers, headers, custom content, expandable sections), keyboard
11
+ navigation, optional search, viewport-overflow handling (CSS anchor positioning with a
12
+ centered-modal fallback), and theming all come from there. What ContextMenu adds is the
13
+ trigger semantics and the cursor anchoring.
14
+
15
+ ## Props
16
+
17
+ | Prop | Type | Default | Description |
18
+ | -------------------------------------------------- | -------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------------- |
19
+ | `items` | `ContextMenuItem[]` | required | Menu items (the DropdownMenu item model) |
20
+ | `children` | `Snippet` | - | The right-clickable / long-pressable target area content |
21
+ | `isOpen` | `boolean` | `false` | Controlled open state (bindable); setting `true` programmatically anchors at the target area |
22
+ | `disabled` | `boolean` | `false` | Triggers are inert — the browser's native context menu works again |
23
+ | `longPress` | `number \| false` | `500` | Long-press duration in ms (touch/pen); `false` disables the long-press trigger |
24
+ | `position` | `ContextMenuPosition` | `"bottom-span-right"` | Menu placement relative to the pointer (below-right = the native convention); flips near the edges |
25
+ | `offset` | `string` | `"0px"` | Offset of the menu from the pointer (CSS value) |
26
+ | `maxHeight` | `string` | `"300px"` | Max menu height (CSS value) |
27
+ | `closeOnSelect` | `boolean` | `true` | Close when an action item is selected |
28
+ | `closeOnClickOutside` | `boolean` | `true` | Close on click outside |
29
+ | `closeOnEscape` | `boolean` | `true` | Close on Escape |
30
+ | `search` | `boolean \| ContextMenuSearchConfig` | - | Search/filter input inside the menu (see DropdownMenu) |
31
+ | `showBackdrop` | `boolean` | `true` | Backdrop in fallback (centered modal) mode |
32
+ | `scrollbarGutter` | `boolean` | auto | Reserve scrollbar space (auto-enables at ≥ 7 items) |
33
+ | `noScrollLock` | `boolean` | - | Skip the body scroll lock in fallback mode |
34
+ | `forceFallback` | `boolean` | `false` | Force the centered-modal fallback (testing) |
35
+ | `onOpen` / `onClose` | `() => void` | - | Open/close callbacks |
36
+ | `onSelect` | `(item) => void \| boolean \| Promise` | - | Fallback select handler when an item has no own `onSelect` |
37
+ | `t` | `TranslateFn` | English | i18n — localizes the screen-reader-only menu label (see below) |
38
+ | `unstyled` | `boolean` | `false` | Skip all default styling |
39
+ | `class` | `string` | - | Classes for the target area wrapper |
40
+ | `classDropdown`, `classItem`, `classItemActive`, … | `string` | - | Menu part classes, passed to the underlying DropdownMenu |
41
+ | `el` | `HTMLDivElement` | - | Target area wrapper element (bindable) |
42
+ | `dropdownEl` | `HTMLDivElement` | - | Open menu element (bindable) |
43
+
44
+ `ContextMenuItem` / `ContextMenuActionItem` / `ContextMenuPosition` /
45
+ `ContextMenuSearchConfig` are aliases of the corresponding `DropdownMenu*` types —
46
+ see the [DropdownMenu README](../DropdownMenu/README.md) for the full item model
47
+ (action, divider, header, custom, expandable).
48
+
49
+ ## Usage
50
+
51
+ ### Basic
52
+
53
+ ```svelte
54
+ <script lang="ts">
55
+ import { ContextMenu, type ContextMenuItem } from "@marianmeres/stuic";
56
+
57
+ const items: ContextMenuItem[] = [
58
+ { type: "action", id: "copy", label: "Copy", onSelect: () => copy() },
59
+ { type: "action", id: "rename", label: "Rename", onSelect: () => rename() },
60
+ { type: "divider", id: "d" },
61
+ { type: "action", id: "delete", label: "Delete", onSelect: () => remove() },
62
+ ];
63
+ </script>
64
+
65
+ <ContextMenu {items}>
66
+ <div class="p-8">Right-click (or long-press) anywhere in here…</div>
67
+ </ContextMenu>
68
+ ```
69
+
70
+ ### Per-row menus (list/table)
71
+
72
+ Each `<ContextMenu>` instance is one target area — wrap each row and build the items
73
+ from the row's data:
74
+
75
+ ```svelte
76
+ {#each files as file (file.id)}
77
+ <ContextMenu items={itemsFor(file)}>
78
+ <div class="row">{file.name}</div>
79
+ </ContextMenu>
80
+ {/each}
81
+ ```
82
+
83
+ (Nested target areas are fine — the innermost one wins.)
84
+
85
+ ### Controlled / programmatic
86
+
87
+ ```svelte
88
+ <ContextMenu {items} bind:isOpen>...</ContextMenu>
89
+ ```
90
+
91
+ Setting `isOpen = true` programmatically (with no pointer interaction recorded yet)
92
+ anchors the menu at the target area's bottom-left corner.
93
+
94
+ ### i18n
95
+
96
+ Only the screen-reader-only menu label ("Context menu") is localized — the item
97
+ labels are yours. Built-in English; bundled Slovak (`CONTEXT_MENU_MESSAGES_SK`) is
98
+ opt-in:
99
+
100
+ ```svelte
101
+ <script lang="ts">
102
+ import {
103
+ ContextMenu,
104
+ createContextMenuT,
105
+ CONTEXT_MENU_MESSAGES_SK,
106
+ } from "@marianmeres/stuic";
107
+ const t = createContextMenuT(CONTEXT_MENU_MESSAGES_SK);
108
+ </script>
109
+
110
+ <ContextMenu {items} {t}>...</ContextMenu>
111
+ ```
112
+
113
+ ## Trigger semantics
114
+
115
+ - **Right-click** (`contextmenu` event): opens at the cursor, `preventDefault`s the
116
+ native menu, `stopPropagation`s so the innermost of nested context areas wins.
117
+ Right-clicking again while open just moves the menu.
118
+ - **Long-press** (touch/pen, via the exported [`longPress`](../../attachments/long-press.ts)
119
+ attachment): fires after `longPress` ms without moving beyond ~10px; the platform's
120
+ own long-press behaviors are suppressed (Android's native `contextmenu` synthesis is
121
+ deduplicated; iOS's callout/text-selection via CSS on the target area — coarse
122
+ pointers only, so desktop text selection inside the area stays intact). The click
123
+ that some platforms fire when the finger lifts is swallowed so it can't activate
124
+ whatever sits under it.
125
+ - **Keyboard**: Shift+F10 or the dedicated menu key while focus is inside the target
126
+ area opens the menu at the focused element's bottom-left corner. Note the wrapper
127
+ itself is not focusable — put focusable content inside (or add `tabindex="0"`
128
+ yourself) for keyboard invocation to have somewhere to happen. Focus returns to the
129
+ previously focused element on close.
130
+ - **`disabled`**: all triggers inert, the browser's native context menu works again.
131
+
132
+ ## Accessibility
133
+
134
+ - The target area wrapper carries `aria-haspopup="menu"`; the menu is a
135
+ `role="menu"` with `role="menuitem"` items (DropdownMenu's keyboard navigation:
136
+ arrows, Home/End, Enter/Space, Escape).
137
+ - The menu's `aria-labelledby` points at a visually hidden label ("Context menu",
138
+ localizable via `t`).
139
+ - Keyboard invocation (Shift+F10 / menu key) is supported, and focus is restored on
140
+ close.
141
+
142
+ ## Styling
143
+
144
+ The menu panel is a DropdownMenu — **theme it via the `--stuic-dropdown-menu-*`
145
+ tokens** (see the [DropdownMenu README](../DropdownMenu/README.md#css-variables)).
146
+ ContextMenu itself only styles the target area:
147
+
148
+ - `.stuic-context-menu` — the target area wrapper (`class` prop merges here)
149
+ - `.stuic-context-menu-anchor` — the invisible 0×0 fixed anchor the menu positions
150
+ against (inline-positioned; not a styling hook)
151
+
152
+ ## Data Attributes
153
+
154
+ On the target area wrapper:
155
+
156
+ - `data-open` — empty attribute while the menu is open (e.g. highlight the target:
157
+ `.stuic-context-menu[data-open] { background: ... }`)
158
+ - `data-longpress` — empty attribute when the long-press trigger is enabled; drives
159
+ the CSS that suppresses the platform's own long-press behaviors
@@ -0,0 +1,17 @@
1
+ import type { ContextMenuMessages } from "./i18n.js";
2
+ /**
3
+ * Slovak message catalog for `ContextMenu`. Opt-in — English stays the built-in
4
+ * default, and this module is only pulled into a bundle when it is actually imported
5
+ * (the component itself never references it).
6
+ *
7
+ * @example
8
+ * ```svelte
9
+ * <script>
10
+ * import { ContextMenu, createContextMenuT, CONTEXT_MENU_MESSAGES_SK } from "@marianmeres/stuic";
11
+ * const t = createContextMenuT(CONTEXT_MENU_MESSAGES_SK);
12
+ * </script>
13
+ *
14
+ * <ContextMenu {items} {t}>...</ContextMenu>
15
+ * ```
16
+ */
17
+ export declare const CONTEXT_MENU_MESSAGES_SK: ContextMenuMessages;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Slovak message catalog for `ContextMenu`. Opt-in — English stays the built-in
3
+ * default, and this module is only pulled into a bundle when it is actually imported
4
+ * (the component itself never references it).
5
+ *
6
+ * @example
7
+ * ```svelte
8
+ * <script>
9
+ * import { ContextMenu, createContextMenuT, CONTEXT_MENU_MESSAGES_SK } from "@marianmeres/stuic";
10
+ * const t = createContextMenuT(CONTEXT_MENU_MESSAGES_SK);
11
+ * </script>
12
+ *
13
+ * <ContextMenu {items} {t}>...</ContextMenu>
14
+ * ```
15
+ */
16
+ export const CONTEXT_MENU_MESSAGES_SK = {
17
+ context_menu: "Kontextové menu",
18
+ };
@@ -0,0 +1,30 @@
1
+ import type { TranslateFn } from "../../types.js";
2
+ /**
3
+ * The built-in (English) message catalog of `ContextMenu`. Also the fallback of every
4
+ * other bundled locale, so a locale missing a key still renders text.
5
+ */
6
+ export declare const CONTEXT_MENU_MESSAGES_EN: {
7
+ context_menu: string;
8
+ };
9
+ /** Every message key `ContextMenu` may look up. */
10
+ export type ContextMenuMessageKey = keyof typeof CONTEXT_MENU_MESSAGES_EN;
11
+ /** A (possibly partial) catalog for one locale. */
12
+ export type ContextMenuMessages = Record<ContextMenuMessageKey, string>;
13
+ /**
14
+ * Builds the `t` prop of `ContextMenu` from a message catalog. Unknown or untranslated
15
+ * keys fall back to `fallbackMessages` (English by default), so a catalog may safely be
16
+ * partial and never renders a raw key.
17
+ *
18
+ * @example
19
+ * ```svelte
20
+ * <script>
21
+ * import { ContextMenu, createContextMenuT, CONTEXT_MENU_MESSAGES_SK } from "@marianmeres/stuic";
22
+ * const t = createContextMenuT(CONTEXT_MENU_MESSAGES_SK);
23
+ * </script>
24
+ *
25
+ * <ContextMenu {items} {t}>...</ContextMenu>
26
+ * ```
27
+ */
28
+ export declare function createContextMenuT(messages: Partial<ContextMenuMessages> | Record<string, string>, fallbackMessages?: Partial<ContextMenuMessages> | Record<string, string>): TranslateFn;
29
+ /** The component's built-in English `t`. */
30
+ export declare const t_default: TranslateFn;
@@ -0,0 +1,38 @@
1
+ import { isPlainObject } from "../../utils/is-plain-object.js";
2
+ import { replaceMap } from "../../utils/replace-map.js";
3
+ /**
4
+ * The built-in (English) message catalog of `ContextMenu`. Also the fallback of every
5
+ * other bundled locale, so a locale missing a key still renders text.
6
+ */
7
+ export const CONTEXT_MENU_MESSAGES_EN = {
8
+ context_menu: "Context menu",
9
+ };
10
+ /**
11
+ * Builds the `t` prop of `ContextMenu` from a message catalog. Unknown or untranslated
12
+ * keys fall back to `fallbackMessages` (English by default), so a catalog may safely be
13
+ * partial and never renders a raw key.
14
+ *
15
+ * @example
16
+ * ```svelte
17
+ * <script>
18
+ * import { ContextMenu, createContextMenuT, CONTEXT_MENU_MESSAGES_SK } from "@marianmeres/stuic";
19
+ * const t = createContextMenuT(CONTEXT_MENU_MESSAGES_SK);
20
+ * </script>
21
+ *
22
+ * <ContextMenu {items} {t}>...</ContextMenu>
23
+ * ```
24
+ */
25
+ export function createContextMenuT(messages, fallbackMessages = CONTEXT_MENU_MESSAGES_EN) {
26
+ return (k, values = null, fallback = "") => {
27
+ const out = messages[k] ??
28
+ fallbackMessages[k] ??
29
+ (typeof fallback === "string" ? fallback : k);
30
+ return isPlainObject(values)
31
+ ? replaceMap(out, values, {
32
+ preSearchKeyTransform: (k) => `{${k}}`,
33
+ })
34
+ : out;
35
+ };
36
+ }
37
+ /** The component's built-in English `t`. */
38
+ export const t_default = createContextMenuT(CONTEXT_MENU_MESSAGES_EN);