@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
@@ -26,10 +26,13 @@ export { iconLucideArrowLeft as iconArrowLeft } from "@marianmeres/icons-fns/luc
26
26
  export { iconLucideArrowRight as iconArrowRight } from "@marianmeres/icons-fns/lucide/iconLucideArrowRight.js";
27
27
  export { iconLucideArrowUp as iconArrowUp } from "@marianmeres/icons-fns/lucide/iconLucideArrowUp.js";
28
28
  export { iconLucideArrowUpToLine as iconArrowUpToLine } from "@marianmeres/icons-fns/lucide/iconLucideArrowUpToLine.js";
29
+ export { iconLucideCopy as iconCopy } from "@marianmeres/icons-fns/lucide/iconLucideCopy.js";
29
30
  export { iconLucideDownload as iconDownload } from "@marianmeres/icons-fns/lucide/iconLucideDownload.js";
30
31
  export { iconLucideMinus as iconMinus } from "@marianmeres/icons-fns/lucide/iconLucideMinus.js";
31
32
  export { iconLucidePlus as iconPlus } from "@marianmeres/icons-fns/lucide/iconLucidePlus.js";
32
33
  export { iconLucideTrash2 as iconTrash } from "@marianmeres/icons-fns/lucide/iconLucideTrash2.js";
34
+ export { iconLucideTrendingDown as iconTrendingDown } from "@marianmeres/icons-fns/lucide/iconLucideTrendingDown.js";
35
+ export { iconLucideTrendingUp as iconTrendingUp } from "@marianmeres/icons-fns/lucide/iconLucideTrendingUp.js";
33
36
  export { iconLucideZoomIn as iconZoomIn } from "@marianmeres/icons-fns/lucide/iconLucideZoomIn.js";
34
37
  export { iconLucideZoomOut as iconZoomOut } from "@marianmeres/icons-fns/lucide/iconLucideZoomOut.js";
35
38
  // UI Control Icons (Lucide)
@@ -45,6 +48,7 @@ export { iconLucideEllipsisVertical as iconEllipsisVertical } from "@marianmeres
45
48
  export { iconLucideGrip as iconGrip } from "@marianmeres/icons-fns/lucide/iconLucideGrip.js";
46
49
  export { iconLucideGripHorizontal as iconGripHorizontal } from "@marianmeres/icons-fns/lucide/iconLucideGripHorizontal.js";
47
50
  export { iconLucideGripVertical as iconGripVertical } from "@marianmeres/icons-fns/lucide/iconLucideGripVertical.js";
51
+ export { iconLucideInbox as iconInbox } from "@marianmeres/icons-fns/lucide/iconLucideInbox.js";
48
52
  export { iconLucideLanguages as iconLanguages } from "@marianmeres/icons-fns/lucide/iconLucideLanguages.js";
49
53
  export { iconLucideList as iconList } from "@marianmeres/icons-fns/lucide/iconLucideList.js";
50
54
  export { iconLucideLogOut as iconLogOut } from "@marianmeres/icons-fns/lucide/iconLucideLogOut.js";
package/dist/index.css CHANGED
@@ -60,6 +60,7 @@ In practice:
60
60
  @import "./components/Avatar/index.css";
61
61
  @import "./components/Backdrop/index.css";
62
62
  @import "./components/Book/index.css";
63
+ @import "./components/Breadcrumbs/index.css";
63
64
  @import "./components/Button/index.css";
64
65
  @import "./components/ButtonGroupRadio/index.css";
65
66
  @import "./components/Collapsible/index.css";
@@ -75,11 +76,14 @@ In practice:
75
76
  MarkdownEditor's. It is a subpath-export component and imports its own CSS
76
77
  locally (see components/CommentInput/index.css). Do not re-add. */
77
78
  @import "./components/ContactUsForm/index.css";
79
+ @import "./components/ContextMenu/index.css";
80
+ @import "./components/CopyButton/index.css";
78
81
  @import "./components/CronInput/index.css";
79
82
  @import "./components/DataTable/index.css";
80
83
  @import "./components/DismissibleMessage/index.css";
81
84
  @import "./components/DropdownMenu/index.css";
82
85
  @import "./components/EmailVerifyForm/index.css";
86
+ @import "./components/EmptyState/index.css";
83
87
  @import "./components/FieldsBuilder/index.css";
84
88
  @import "./components/Float/index.css";
85
89
  @import "./components/H/index.css";
@@ -94,6 +98,7 @@ In practice:
94
98
  @import "./components/Nav/index.css";
95
99
  @import "./components/Notifications/index.css";
96
100
  @import "./components/OtpInput/index.css";
101
+ @import "./components/Pagination/index.css";
97
102
  @import "./components/Pill/index.css";
98
103
  @import "./components/PricingTable/index.css";
99
104
  @import "./components/Progress/index.css";
@@ -102,6 +107,8 @@ In practice:
102
107
  @import "./components/Slider/index.css";
103
108
  @import "./components/Spinner/index.css";
104
109
  @import "./components/SplitButton/index.css";
110
+ @import "./components/Stat/index.css";
111
+ @import "./components/Stepper/index.css";
105
112
  @import "./components/Switch/index.css";
106
113
  @import "./components/TabbedMenu/index.css";
107
114
  @import "./components/ThemePreview/index.css";
package/dist/index.d.ts CHANGED
@@ -29,6 +29,7 @@ export * from "./components/AssetsPreview/index.js";
29
29
  export * from "./components/Avatar/index.js";
30
30
  export * from "./components/Backdrop/index.js";
31
31
  export * from "./components/Book/index.js";
32
+ export * from "./components/Breadcrumbs/index.js";
32
33
  export * from "./components/Button/index.js";
33
34
  export * from "./components/ButtonGroupRadio/index.js";
34
35
  export * from "./components/Cart/index.js";
@@ -40,12 +41,15 @@ export * from "./components/Collapsible/index.js";
40
41
  export * from "./components/ColorScheme/index.js";
41
42
  export * from "./components/CommandMenu/index.js";
42
43
  export * from "./components/ContactUsForm/index.js";
44
+ export * from "./components/ContextMenu/index.js";
45
+ export * from "./components/CopyButton/index.js";
43
46
  export * from "./components/CronInput/index.js";
44
47
  export * from "./components/DataTable/index.js";
45
48
  export * from "./components/DismissibleMessage/index.js";
46
49
  export * from "./components/Drawer/index.js";
47
50
  export * from "./components/DropdownMenu/index.js";
48
51
  export * from "./components/EmailVerifyForm/index.js";
52
+ export * from "./components/EmptyState/index.js";
49
53
  export * from "./components/FieldsBuilder/index.js";
50
54
  export * from "./components/Float/index.js";
51
55
  export * from "./components/H/index.js";
@@ -64,6 +68,7 @@ export * from "./components/ModalDialog/index.js";
64
68
  export * from "./components/Nav/index.js";
65
69
  export * from "./components/Notifications/index.js";
66
70
  export * from "./components/OtpInput/index.js";
71
+ export * from "./components/Pagination/index.js";
67
72
  export * from "./components/Pill/index.js";
68
73
  export * from "./components/PricingTable/index.js";
69
74
  export * from "./components/Progress/index.js";
@@ -73,6 +78,8 @@ export * from "./components/Slider/index.js";
73
78
  export * from "./components/SlidingPanels/index.js";
74
79
  export * from "./components/Spinner/index.js";
75
80
  export * from "./components/SplitButton/index.js";
81
+ export * from "./components/Stat/index.js";
82
+ export * from "./components/Stepper/index.js";
76
83
  export * from "./components/Switch/index.js";
77
84
  export * from "./components/TabbedMenu/index.js";
78
85
  export * from "./components/Thc/index.js";
package/dist/index.js CHANGED
@@ -30,6 +30,7 @@ export * from "./components/AssetsPreview/index.js";
30
30
  export * from "./components/Avatar/index.js";
31
31
  export * from "./components/Backdrop/index.js";
32
32
  export * from "./components/Book/index.js";
33
+ export * from "./components/Breadcrumbs/index.js";
33
34
  export * from "./components/Button/index.js";
34
35
  export * from "./components/ButtonGroupRadio/index.js";
35
36
  export * from "./components/Cart/index.js";
@@ -46,12 +47,15 @@ export * from "./components/CommandMenu/index.js";
46
47
  // subpath export instead, mirroring `@marianmeres/stuic/markdown-editor`.
47
48
  // Guarded by src/lib/barrel-optional-peers.test.ts — do not re-add.
48
49
  export * from "./components/ContactUsForm/index.js";
50
+ export * from "./components/ContextMenu/index.js";
51
+ export * from "./components/CopyButton/index.js";
49
52
  export * from "./components/CronInput/index.js";
50
53
  export * from "./components/DataTable/index.js";
51
54
  export * from "./components/DismissibleMessage/index.js";
52
55
  export * from "./components/Drawer/index.js";
53
56
  export * from "./components/DropdownMenu/index.js";
54
57
  export * from "./components/EmailVerifyForm/index.js";
58
+ export * from "./components/EmptyState/index.js";
55
59
  export * from "./components/FieldsBuilder/index.js";
56
60
  export * from "./components/Float/index.js";
57
61
  export * from "./components/H/index.js";
@@ -70,6 +74,7 @@ export * from "./components/ModalDialog/index.js";
70
74
  export * from "./components/Nav/index.js";
71
75
  export * from "./components/Notifications/index.js";
72
76
  export * from "./components/OtpInput/index.js";
77
+ export * from "./components/Pagination/index.js";
73
78
  export * from "./components/Pill/index.js";
74
79
  export * from "./components/PricingTable/index.js";
75
80
  export * from "./components/Progress/index.js";
@@ -79,10 +84,17 @@ export * from "./components/Slider/index.js";
79
84
  export * from "./components/SlidingPanels/index.js";
80
85
  export * from "./components/Spinner/index.js";
81
86
  export * from "./components/SplitButton/index.js";
87
+ export * from "./components/Stat/index.js";
88
+ export * from "./components/Stepper/index.js";
82
89
  export * from "./components/Switch/index.js";
83
90
  export * from "./components/TabbedMenu/index.js";
84
91
  export * from "./components/Thc/index.js";
85
92
  export * from "./components/ThemePreview/index.js";
93
+ // NOTE: TrendChart is deliberately NOT exported here. It statically imports the
94
+ // optional @marianmeres/trend-chart peer, which would then be pulled into the
95
+ // root entry graph of every consumer. It ships as the `@marianmeres/stuic/trend-chart`
96
+ // subpath export instead, mirroring `@marianmeres/stuic/markdown-editor`.
97
+ // Guarded by src/lib/barrel-optional-peers.test.ts — do not re-add.
86
98
  export * from "./components/Tree/index.js";
87
99
  export * from "./components/TwCheck/index.js";
88
100
  export * from "./components/TypeaheadInput/index.js";
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Copies `text` to the system clipboard.
3
+ *
4
+ * Uses the async Clipboard API (`navigator.clipboard.writeText`) and, when that is
5
+ * unavailable (insecure context, older browser) or rejects (permission policy,
6
+ * unfocused document), falls back to the legacy `document.execCommand("copy")` path
7
+ * via an off-screen textarea. Rejects only when both paths fail — with the async
8
+ * API's own error when it was the one that failed.
9
+ *
10
+ * Call it from a user gesture (a click handler): browsers gate clipboard writes on
11
+ * transient user activation, and Safari in particular expects the write to happen
12
+ * promptly after the gesture. Any async work (resolving the text) should be kept
13
+ * short.
14
+ *
15
+ * @param text - The text to place on the clipboard
16
+ * @returns Resolves once the text is on the clipboard
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * import { copyToClipboard } from "@marianmeres/stuic";
21
+ *
22
+ * try {
23
+ * await copyToClipboard(apiKey);
24
+ * notifications.success("Copied");
25
+ * } catch (e) {
26
+ * notifications.error("Copy failed");
27
+ * }
28
+ * ```
29
+ */
30
+ export declare function copyToClipboard(text: string): Promise<void>;
31
+ /**
32
+ * Whether any clipboard write path is available in the current environment:
33
+ * the async Clipboard API or the legacy `execCommand("copy")`. Always `false` outside
34
+ * a browser (SSR). Useful to decide whether to render a copy control at all.
35
+ *
36
+ * @example
37
+ * ```svelte
38
+ * {#if isCopyToClipboardSupported()}
39
+ * <CopyButton text={url} />
40
+ * {/if}
41
+ * ```
42
+ */
43
+ export declare function isCopyToClipboardSupported(): boolean;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Copies `text` to the system clipboard.
3
+ *
4
+ * Uses the async Clipboard API (`navigator.clipboard.writeText`) and, when that is
5
+ * unavailable (insecure context, older browser) or rejects (permission policy,
6
+ * unfocused document), falls back to the legacy `document.execCommand("copy")` path
7
+ * via an off-screen textarea. Rejects only when both paths fail — with the async
8
+ * API's own error when it was the one that failed.
9
+ *
10
+ * Call it from a user gesture (a click handler): browsers gate clipboard writes on
11
+ * transient user activation, and Safari in particular expects the write to happen
12
+ * promptly after the gesture. Any async work (resolving the text) should be kept
13
+ * short.
14
+ *
15
+ * @param text - The text to place on the clipboard
16
+ * @returns Resolves once the text is on the clipboard
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * import { copyToClipboard } from "@marianmeres/stuic";
21
+ *
22
+ * try {
23
+ * await copyToClipboard(apiKey);
24
+ * notifications.success("Copied");
25
+ * } catch (e) {
26
+ * notifications.error("Copy failed");
27
+ * }
28
+ * ```
29
+ */
30
+ export async function copyToClipboard(text) {
31
+ let asyncError;
32
+ if (typeof navigator !== "undefined" &&
33
+ typeof navigator.clipboard?.writeText === "function") {
34
+ try {
35
+ await navigator.clipboard.writeText(text);
36
+ return;
37
+ }
38
+ catch (e) {
39
+ asyncError = e;
40
+ }
41
+ }
42
+ if (!legacyCopy(text)) {
43
+ throw asyncError ?? new Error("Clipboard is not available");
44
+ }
45
+ }
46
+ /**
47
+ * Whether any clipboard write path is available in the current environment:
48
+ * the async Clipboard API or the legacy `execCommand("copy")`. Always `false` outside
49
+ * a browser (SSR). Useful to decide whether to render a copy control at all.
50
+ *
51
+ * @example
52
+ * ```svelte
53
+ * {#if isCopyToClipboardSupported()}
54
+ * <CopyButton text={url} />
55
+ * {/if}
56
+ * ```
57
+ */
58
+ export function isCopyToClipboardSupported() {
59
+ if (typeof navigator !== "undefined" &&
60
+ typeof navigator.clipboard?.writeText === "function") {
61
+ return true;
62
+ }
63
+ return (typeof document !== "undefined" &&
64
+ typeof document.queryCommandSupported === "function" &&
65
+ document.queryCommandSupported("copy"));
66
+ }
67
+ /** The pre-Clipboard-API path: select the text in an off-screen textarea and `copy`. */
68
+ function legacyCopy(text) {
69
+ if (typeof document === "undefined" || typeof document.execCommand !== "function") {
70
+ return false;
71
+ }
72
+ const active = document.activeElement;
73
+ const ta = document.createElement("textarea");
74
+ ta.value = text;
75
+ ta.setAttribute("readonly", "");
76
+ ta.setAttribute("aria-hidden", "true");
77
+ ta.tabIndex = -1;
78
+ // off-screen and out of the way, but NOT display:none — hidden elements can't be selected
79
+ ta.style.position = "fixed";
80
+ ta.style.top = "0";
81
+ ta.style.left = "0";
82
+ ta.style.width = "1px";
83
+ ta.style.height = "1px";
84
+ ta.style.opacity = "0";
85
+ ta.style.pointerEvents = "none";
86
+ document.body.appendChild(ta);
87
+ ta.focus({ preventScroll: true });
88
+ ta.select();
89
+ ta.setSelectionRange(0, text.length);
90
+ let ok = false;
91
+ try {
92
+ ok = document.execCommand("copy");
93
+ }
94
+ catch (_e) {
95
+ ok = false;
96
+ }
97
+ ta.remove();
98
+ // the textarea stole focus — hand it back
99
+ if (active && typeof active.focus === "function")
100
+ active.focus({ preventScroll: true });
101
+ return ok;
102
+ }
@@ -2,6 +2,7 @@ export * from "./avatar-colors.js";
2
2
  export * from "./body-scroll-locker.js";
3
3
  export * from "./breakpoint.svelte.js";
4
4
  export * from "./colors.js";
5
+ export * from "./copy-to-clipboard.js";
5
6
  export * from "./debounce.js";
6
7
  export * from "./design-tokens.js";
7
8
  export * from "./device-pointer.svelte.js";
@@ -2,6 +2,7 @@ export * from "./avatar-colors.js";
2
2
  export * from "./body-scroll-locker.js";
3
3
  export * from "./breakpoint.svelte.js";
4
4
  export * from "./colors.js";
5
+ export * from "./copy-to-clipboard.js";
5
6
  export * from "./debounce.js";
6
7
  export * from "./design-tokens.js";
7
8
  export * from "./device-pointer.svelte.js";
@@ -48,7 +48,7 @@ src/lib/
48
48
  │ ├── *.ts # Traditional actions
49
49
  │ └── index.ts # Barrel export
50
50
  │
51
- ├── utils/ # 44 utility modules
51
+ ├── utils/ # 45 utility modules
52
52
  │ ├── *.svelte.ts # Reactive utilities
53
53
  │ ├── *.ts # Pure functions
54
54
  │ └── index.ts # Barrel export
@@ -18,6 +18,7 @@ re-exported from the package root (`src/lib/index.ts`).
18
18
  | Attachment | Purpose | File |
19
19
  | ------------ | ------------------------------------------------------------ | ---------------- |
20
20
  | `autoHeight` | Animate a host's height to its single child's natural height | `auto-height.ts` |
21
+ | `longPress` | Detect a touch/pen long-press on the host (factory) | `long-press.ts` |
21
22
 
22
23
  ---
23
24
 
@@ -105,6 +106,41 @@ inline `height` and `overflow`):
105
106
 
106
107
  ---
107
108
 
109
+ ## `longPress`
110
+
111
+ Factory returning an attachment that calls `onLongPress(pointerdownEvent)` when a primary
112
+ touch/pen pointer stays down for `duration` ms (default 500) without moving beyond
113
+ `moveTolerance` px (default 10). Used by `ContextMenu` for its touch trigger; reusable for
114
+ any press-and-hold interaction.
115
+
116
+ ```svelte
117
+ <script>
118
+ import { longPress } from "@marianmeres/stuic";
119
+ </script>
120
+
121
+ <div {@attach longPress({ onLongPress: (e) => openAt(e.clientX, e.clientY) })}>...</div>
122
+ ```
123
+
124
+ Semantics worth knowing:
125
+
126
+ - **Disarms itself** on pointer up/cancel, movement beyond the tolerance (a scroll), and on
127
+ a native `contextmenu` event on the host — Android synthesizes one on long-press by
128
+ itself, so a host handling `contextmenu` never gets both paths for one gesture.
129
+ - **Swallows the follow-up click** some platforms fire when the finger lifts after a fired
130
+ long-press (`suppressClick: false` opts out), so it can't activate whatever sits under
131
+ it. The suppress window closes right after the gesture ends — a later genuine click is
132
+ never eaten.
133
+ - **Does not prevent the platform's own long-press UI** — that's CSS the consumer owns on
134
+ the host: `-webkit-touch-callout: none` (iOS link/image callout) and `user-select: none`
135
+ (text-selection long-press; scope it to `@media (pointer: coarse)` to keep desktop text
136
+ selection working — see `ContextMenu/index.css` for the pattern).
137
+ - `pointerTypes` defaults to `["touch", "pen"]` — mouse users have right-click.
138
+
139
+ Options: `onLongPress` (required), `duration`, `moveTolerance`, `pointerTypes`,
140
+ `suppressClick`.
141
+
142
+ ---
143
+
108
144
  ## Attachment File Pattern
109
145
 
110
146
  An attachment is a function `(node) => cleanup?`, typed `Attachment<T>` from `svelte/attachments`.
@@ -135,7 +171,8 @@ it needs `$state` / `$derived` / a nested `$effect`.
135
171
 
136
172
  ## Key Files
137
173
 
138
- | File | Purpose |
139
- | ---------------------------------- | --------------------------- |
140
- | src/lib/attachments/index.ts | All attachment exports |
141
- | src/lib/attachments/auto-height.ts | Height-animation attachment |
174
+ | File | Purpose |
175
+ | ---------------------------------- | ------------------------------ |
176
+ | src/lib/attachments/index.ts | All attachment exports |
177
+ | src/lib/attachments/auto-height.ts | Height-animation attachment |
178
+ | src/lib/attachments/long-press.ts | Long-press detection (factory) |