@fuzdev/fuz_ui 0.169.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.
- package/LICENSE +21 -0
- package/README.md +93 -0
- package/dist/Alert.svelte +108 -0
- package/dist/Alert.svelte.d.ts +16 -0
- package/dist/Alert.svelte.d.ts.map +1 -0
- package/dist/ApiDeclarationList.svelte +35 -0
- package/dist/ApiDeclarationList.svelte.d.ts +9 -0
- package/dist/ApiDeclarationList.svelte.d.ts.map +1 -0
- package/dist/ApiIndex.svelte +65 -0
- package/dist/ApiIndex.svelte.d.ts +23 -0
- package/dist/ApiIndex.svelte.d.ts.map +1 -0
- package/dist/ApiModule.svelte +124 -0
- package/dist/ApiModule.svelte.d.ts +22 -0
- package/dist/ApiModule.svelte.d.ts.map +1 -0
- package/dist/Breadcrumb.svelte +83 -0
- package/dist/Breadcrumb.svelte.d.ts +23 -0
- package/dist/Breadcrumb.svelte.d.ts.map +1 -0
- package/dist/Card.svelte +157 -0
- package/dist/Card.svelte.d.ts +13 -0
- package/dist/Card.svelte.d.ts.map +1 -0
- package/dist/ColorSchemeInput.svelte +65 -0
- package/dist/ColorSchemeInput.svelte.d.ts +11 -0
- package/dist/ColorSchemeInput.svelte.d.ts.map +1 -0
- package/dist/Contextmenu.svelte +30 -0
- package/dist/Contextmenu.svelte.d.ts +32 -0
- package/dist/Contextmenu.svelte.d.ts.map +1 -0
- package/dist/ContextmenuEntry.svelte +74 -0
- package/dist/ContextmenuEntry.svelte.d.ts +12 -0
- package/dist/ContextmenuEntry.svelte.d.ts.map +1 -0
- package/dist/ContextmenuLinkEntry.svelte +112 -0
- package/dist/ContextmenuLinkEntry.svelte.d.ts +12 -0
- package/dist/ContextmenuLinkEntry.svelte.d.ts.map +1 -0
- package/dist/ContextmenuRoot.svelte +372 -0
- package/dist/ContextmenuRoot.svelte.d.ts +71 -0
- package/dist/ContextmenuRoot.svelte.d.ts.map +1 -0
- package/dist/ContextmenuRootForSafariCompatibility.svelte +541 -0
- package/dist/ContextmenuRootForSafariCompatibility.svelte.d.ts +79 -0
- package/dist/ContextmenuRootForSafariCompatibility.svelte.d.ts.map +1 -0
- package/dist/ContextmenuSeparator.svelte +16 -0
- package/dist/ContextmenuSeparator.svelte.d.ts +4 -0
- package/dist/ContextmenuSeparator.svelte.d.ts.map +1 -0
- package/dist/ContextmenuSubmenu.svelte +116 -0
- package/dist/ContextmenuSubmenu.svelte.d.ts +10 -0
- package/dist/ContextmenuSubmenu.svelte.d.ts.map +1 -0
- package/dist/ContextmenuTextEntry.svelte +21 -0
- package/dist/ContextmenuTextEntry.svelte.d.ts +10 -0
- package/dist/ContextmenuTextEntry.svelte.d.ts.map +1 -0
- package/dist/CopyToClipboard.svelte +81 -0
- package/dist/CopyToClipboard.svelte.d.ts +18 -0
- package/dist/CopyToClipboard.svelte.d.ts.map +1 -0
- package/dist/DeclarationDetail.svelte +340 -0
- package/dist/DeclarationDetail.svelte.d.ts +8 -0
- package/dist/DeclarationDetail.svelte.d.ts.map +1 -0
- package/dist/DeclarationLink.svelte +50 -0
- package/dist/DeclarationLink.svelte.d.ts +8 -0
- package/dist/DeclarationLink.svelte.d.ts.map +1 -0
- package/dist/Details.svelte +51 -0
- package/dist/Details.svelte.d.ts +20 -0
- package/dist/Details.svelte.d.ts.map +1 -0
- package/dist/Dialog.svelte +217 -0
- package/dist/Dialog.svelte.d.ts +30 -0
- package/dist/Dialog.svelte.d.ts.map +1 -0
- package/dist/Dialogs.svelte +28 -0
- package/dist/Dialogs.svelte.d.ts +11 -0
- package/dist/Dialogs.svelte.d.ts.map +1 -0
- package/dist/Docs.svelte +179 -0
- package/dist/Docs.svelte.d.ts +13 -0
- package/dist/Docs.svelte.d.ts.map +1 -0
- package/dist/DocsContent.svelte +40 -0
- package/dist/DocsContent.svelte.d.ts +14 -0
- package/dist/DocsContent.svelte.d.ts.map +1 -0
- package/dist/DocsFooter.svelte +64 -0
- package/dist/DocsFooter.svelte.d.ts +15 -0
- package/dist/DocsFooter.svelte.d.ts.map +1 -0
- package/dist/DocsLink.svelte +41 -0
- package/dist/DocsLink.svelte.d.ts +12 -0
- package/dist/DocsLink.svelte.d.ts.map +1 -0
- package/dist/DocsList.svelte +44 -0
- package/dist/DocsList.svelte.d.ts +11 -0
- package/dist/DocsList.svelte.d.ts.map +1 -0
- package/dist/DocsMenu.svelte +55 -0
- package/dist/DocsMenu.svelte.d.ts +11 -0
- package/dist/DocsMenu.svelte.d.ts.map +1 -0
- package/dist/DocsMenuHeader.svelte +15 -0
- package/dist/DocsMenuHeader.svelte.d.ts +9 -0
- package/dist/DocsMenuHeader.svelte.d.ts.map +1 -0
- package/dist/DocsModulesList.svelte +32 -0
- package/dist/DocsModulesList.svelte.d.ts +7 -0
- package/dist/DocsModulesList.svelte.d.ts.map +1 -0
- package/dist/DocsPageLinks.svelte +61 -0
- package/dist/DocsPageLinks.svelte.d.ts +8 -0
- package/dist/DocsPageLinks.svelte.d.ts.map +1 -0
- package/dist/DocsPrimaryNav.svelte +93 -0
- package/dist/DocsPrimaryNav.svelte.d.ts +11 -0
- package/dist/DocsPrimaryNav.svelte.d.ts.map +1 -0
- package/dist/DocsSearch.svelte +48 -0
- package/dist/DocsSearch.svelte.d.ts +11 -0
- package/dist/DocsSearch.svelte.d.ts.map +1 -0
- package/dist/DocsSecondaryNav.svelte +63 -0
- package/dist/DocsSecondaryNav.svelte.d.ts +9 -0
- package/dist/DocsSecondaryNav.svelte.d.ts.map +1 -0
- package/dist/DocsTertiaryNav.svelte +118 -0
- package/dist/DocsTertiaryNav.svelte.d.ts +10 -0
- package/dist/DocsTertiaryNav.svelte.d.ts.map +1 -0
- package/dist/EcosystemLinks.svelte +53 -0
- package/dist/EcosystemLinks.svelte.d.ts +7 -0
- package/dist/EcosystemLinks.svelte.d.ts.map +1 -0
- package/dist/EcosystemLinksPanel.svelte +22 -0
- package/dist/EcosystemLinksPanel.svelte.d.ts +8 -0
- package/dist/EcosystemLinksPanel.svelte.d.ts.map +1 -0
- package/dist/GithubLink.svelte +75 -0
- package/dist/GithubLink.svelte.d.ts +14 -0
- package/dist/GithubLink.svelte.d.ts.map +1 -0
- package/dist/Glyph.svelte +28 -0
- package/dist/Glyph.svelte.d.ts +9 -0
- package/dist/Glyph.svelte.d.ts.map +1 -0
- package/dist/Hashlink.svelte +41 -0
- package/dist/Hashlink.svelte.d.ts +8 -0
- package/dist/Hashlink.svelte.d.ts.map +1 -0
- package/dist/HiddenPersonalLinks.svelte +6 -0
- package/dist/HiddenPersonalLinks.svelte.d.ts +27 -0
- package/dist/HiddenPersonalLinks.svelte.d.ts.map +1 -0
- package/dist/HueInput.svelte +127 -0
- package/dist/HueInput.svelte.d.ts +11 -0
- package/dist/HueInput.svelte.d.ts.map +1 -0
- package/dist/ImgOrSvg.svelte +58 -0
- package/dist/ImgOrSvg.svelte.d.ts +25 -0
- package/dist/ImgOrSvg.svelte.d.ts.map +1 -0
- package/dist/LibraryDetail.svelte +297 -0
- package/dist/LibraryDetail.svelte.d.ts +15 -0
- package/dist/LibraryDetail.svelte.d.ts.map +1 -0
- package/dist/LibrarySummary.svelte +151 -0
- package/dist/LibrarySummary.svelte.d.ts +16 -0
- package/dist/LibrarySummary.svelte.d.ts.map +1 -0
- package/dist/MdnLink.svelte +40 -0
- package/dist/MdnLink.svelte.d.ts +8 -0
- package/dist/MdnLink.svelte.d.ts.map +1 -0
- package/dist/Mdz.svelte +30 -0
- package/dist/Mdz.svelte.d.ts +10 -0
- package/dist/Mdz.svelte.d.ts.map +1 -0
- package/dist/MdzNodeView.svelte +93 -0
- package/dist/MdzNodeView.svelte.d.ts +9 -0
- package/dist/MdzNodeView.svelte.d.ts.map +1 -0
- package/dist/ModuleLink.svelte +48 -0
- package/dist/ModuleLink.svelte.d.ts +8 -0
- package/dist/ModuleLink.svelte.d.ts.map +1 -0
- package/dist/PasteFromClipboard.svelte +35 -0
- package/dist/PasteFromClipboard.svelte.d.ts +9 -0
- package/dist/PasteFromClipboard.svelte.d.ts.map +1 -0
- package/dist/PendingAnimation.svelte +62 -0
- package/dist/PendingAnimation.svelte.d.ts +13 -0
- package/dist/PendingAnimation.svelte.d.ts.map +1 -0
- package/dist/PendingButton.svelte +75 -0
- package/dist/PendingButton.svelte.d.ts +17 -0
- package/dist/PendingButton.svelte.d.ts.map +1 -0
- package/dist/ProjectLinks.svelte +54 -0
- package/dist/ProjectLinks.svelte.d.ts +19 -0
- package/dist/ProjectLinks.svelte.d.ts.map +1 -0
- package/dist/Redirect.svelte +44 -0
- package/dist/Redirect.svelte.d.ts +23 -0
- package/dist/Redirect.svelte.d.ts.map +1 -0
- package/dist/Spiders.svelte +57 -0
- package/dist/Spiders.svelte.d.ts +9 -0
- package/dist/Spiders.svelte.d.ts.map +1 -0
- package/dist/Svg.svelte +99 -0
- package/dist/Svg.svelte.d.ts +54 -0
- package/dist/Svg.svelte.d.ts.map +1 -0
- package/dist/Teleport.svelte +48 -0
- package/dist/Teleport.svelte.d.ts +15 -0
- package/dist/Teleport.svelte.d.ts.map +1 -0
- package/dist/ThemeInput.svelte +75 -0
- package/dist/ThemeInput.svelte.d.ts +15 -0
- package/dist/ThemeInput.svelte.d.ts.map +1 -0
- package/dist/Themed.svelte +101 -0
- package/dist/Themed.svelte.d.ts +24 -0
- package/dist/Themed.svelte.d.ts.map +1 -0
- package/dist/TomeContent.svelte +67 -0
- package/dist/TomeContent.svelte.d.ts +12 -0
- package/dist/TomeContent.svelte.d.ts.map +1 -0
- package/dist/TomeHeader.svelte +56 -0
- package/dist/TomeHeader.svelte.d.ts +4 -0
- package/dist/TomeHeader.svelte.d.ts.map +1 -0
- package/dist/TomeLink.svelte +29 -0
- package/dist/TomeLink.svelte.d.ts +10 -0
- package/dist/TomeLink.svelte.d.ts.map +1 -0
- package/dist/TomeSection.svelte +65 -0
- package/dist/TomeSection.svelte.d.ts +24 -0
- package/dist/TomeSection.svelte.d.ts.map +1 -0
- package/dist/TomeSectionHeader.svelte +90 -0
- package/dist/TomeSectionHeader.svelte.d.ts +13 -0
- package/dist/TomeSectionHeader.svelte.d.ts.map +1 -0
- package/dist/TypeLink.svelte +19 -0
- package/dist/TypeLink.svelte.d.ts +7 -0
- package/dist/TypeLink.svelte.d.ts.map +1 -0
- package/dist/alert.d.ts +7 -0
- package/dist/alert.d.ts.map +1 -0
- package/dist/alert.js +6 -0
- package/dist/api_search.svelte.d.ts +16 -0
- package/dist/api_search.svelte.d.ts.map +1 -0
- package/dist/api_search.svelte.js +61 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +3 -0
- package/dist/context_helpers.d.ts +17 -0
- package/dist/context_helpers.d.ts.map +1 -0
- package/dist/context_helpers.js +19 -0
- package/dist/contextmenu_helpers.d.ts +16 -0
- package/dist/contextmenu_helpers.d.ts.map +1 -0
- package/dist/contextmenu_helpers.js +39 -0
- package/dist/contextmenu_state.svelte.d.ts +152 -0
- package/dist/contextmenu_state.svelte.d.ts.map +1 -0
- package/dist/contextmenu_state.svelte.js +424 -0
- package/dist/csp.d.ts +160 -0
- package/dist/csp.d.ts.map +1 -0
- package/dist/csp.js +354 -0
- package/dist/csp_of_ryanatkn.d.ts +6 -0
- package/dist/csp_of_ryanatkn.d.ts.map +1 -0
- package/dist/csp_of_ryanatkn.js +14 -0
- package/dist/declaration.svelte.d.ts +84 -0
- package/dist/declaration.svelte.d.ts.map +1 -0
- package/dist/declaration.svelte.js +66 -0
- package/dist/declaration_contextmenu.d.ts +4 -0
- package/dist/declaration_contextmenu.d.ts.map +1 -0
- package/dist/declaration_contextmenu.js +14 -0
- package/dist/dialog.d.ts +24 -0
- package/dist/dialog.d.ts.map +1 -0
- package/dist/dialog.js +12 -0
- package/dist/dimensions.svelte.d.ts +5 -0
- package/dist/dimensions.svelte.d.ts.map +1 -0
- package/dist/dimensions.svelte.js +4 -0
- package/dist/docs_helpers.svelte.d.ts +48 -0
- package/dist/docs_helpers.svelte.d.ts.map +1 -0
- package/dist/docs_helpers.svelte.js +99 -0
- package/dist/helpers.d.ts +2 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +16 -0
- package/dist/intersect.svelte.d.ts +47 -0
- package/dist/intersect.svelte.d.ts.map +1 -0
- package/dist/intersect.svelte.js +92 -0
- package/dist/library.svelte.d.ts +197 -0
- package/dist/library.svelte.d.ts.map +1 -0
- package/dist/library.svelte.js +130 -0
- package/dist/library_gen.d.ts +34 -0
- package/dist/library_gen.d.ts.map +1 -0
- package/dist/library_gen.js +123 -0
- package/dist/library_gen_helpers.d.ts +85 -0
- package/dist/library_gen_helpers.d.ts.map +1 -0
- package/dist/library_gen_helpers.js +188 -0
- package/dist/library_helpers.d.ts +54 -0
- package/dist/library_helpers.d.ts.map +1 -0
- package/dist/library_helpers.js +102 -0
- package/dist/logos.d.ts +134 -0
- package/dist/logos.d.ts.map +1 -0
- package/dist/logos.js +281 -0
- package/dist/mdz.d.ts +106 -0
- package/dist/mdz.d.ts.map +1 -0
- package/dist/mdz.js +1481 -0
- package/dist/mdz_components.d.ts +37 -0
- package/dist/mdz_components.d.ts.map +1 -0
- package/dist/mdz_components.js +12 -0
- package/dist/module.svelte.d.ts +47 -0
- package/dist/module.svelte.d.ts.map +1 -0
- package/dist/module.svelte.js +56 -0
- package/dist/module_contextmenu.d.ts +4 -0
- package/dist/module_contextmenu.d.ts.map +1 -0
- package/dist/module_contextmenu.js +14 -0
- package/dist/module_helpers.d.ts +69 -0
- package/dist/module_helpers.d.ts.map +1 -0
- package/dist/module_helpers.js +87 -0
- package/dist/rune_helpers.svelte.d.ts +6 -0
- package/dist/rune_helpers.svelte.d.ts.map +1 -0
- package/dist/rune_helpers.svelte.js +10 -0
- package/dist/storage.d.ts +13 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +43 -0
- package/dist/svelte_helpers.d.ts +37 -0
- package/dist/svelte_helpers.d.ts.map +1 -0
- package/dist/svelte_helpers.js +245 -0
- package/dist/themer.svelte.d.ts +24 -0
- package/dist/themer.svelte.d.ts.map +1 -0
- package/dist/themer.svelte.js +43 -0
- package/dist/tome.d.ts +80 -0
- package/dist/tome.d.ts.map +1 -0
- package/dist/tome.js +27 -0
- package/dist/ts_helpers.d.ts +110 -0
- package/dist/ts_helpers.d.ts.map +1 -0
- package/dist/ts_helpers.js +533 -0
- package/dist/tsdoc_helpers.d.ts +98 -0
- package/dist/tsdoc_helpers.d.ts.map +1 -0
- package/dist/tsdoc_helpers.js +221 -0
- package/package.json +128 -0
- package/src/lib/alert.ts +14 -0
- package/src/lib/api_search.svelte.ts +85 -0
- package/src/lib/constants.ts +3 -0
- package/src/lib/context_helpers.ts +47 -0
- package/src/lib/contextmenu_helpers.ts +63 -0
- package/src/lib/contextmenu_state.svelte.ts +515 -0
- package/src/lib/csp.ts +576 -0
- package/src/lib/csp_of_ryanatkn.ts +16 -0
- package/src/lib/declaration.svelte.ts +102 -0
- package/src/lib/declaration_contextmenu.ts +22 -0
- package/src/lib/dialog.ts +35 -0
- package/src/lib/dimensions.svelte.ts +4 -0
- package/src/lib/docs_helpers.svelte.ts +149 -0
- package/src/lib/helpers.ts +10 -0
- package/src/lib/intersect.svelte.ts +152 -0
- package/src/lib/library.svelte.ts +162 -0
- package/src/lib/library_gen.ts +160 -0
- package/src/lib/library_gen_helpers.ts +262 -0
- package/src/lib/library_helpers.ts +123 -0
- package/src/lib/logos.ts +302 -0
- package/src/lib/mdz.ts +1819 -0
- package/src/lib/mdz_components.ts +34 -0
- package/src/lib/module.svelte.ts +78 -0
- package/src/lib/module_contextmenu.ts +20 -0
- package/src/lib/module_helpers.ts +113 -0
- package/src/lib/rune_helpers.svelte.ts +10 -0
- package/src/lib/storage.ts +48 -0
- package/src/lib/svelte_helpers.ts +303 -0
- package/src/lib/themer.svelte.ts +68 -0
- package/src/lib/tome.ts +38 -0
- package/src/lib/ts_helpers.ts +662 -0
- package/src/lib/tsdoc_helpers.ts +259 -0
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* This alternative to `ContextmenuRoot`
|
|
4
|
+
* implements custom touch event handlers and "longpress" detection to work
|
|
5
|
+
* around iOS Safari not firing the standard `contextmenu` event -- see
|
|
6
|
+
* https://bugs.webkit.org/show_bug.cgi?id=213953.
|
|
7
|
+
*
|
|
8
|
+
* Prefer `ContextmenuRoot.svelte` unless you specifically need iOS Safari support.
|
|
9
|
+
*
|
|
10
|
+
* This is a complex implementation with many iOS-specific hacks and edge cases:
|
|
11
|
+
*
|
|
12
|
+
* - Custom touch event handlers (touchstart, touchmove, touchend)
|
|
13
|
+
* - Longpress detection with configurable timing and movement tolerance
|
|
14
|
+
* - Tap-then-longpress bypass gesture for accessing system contextmenu
|
|
15
|
+
* - Calls navigator.vibrate() for haptic feedback, but browsers block it due to longpress timeout workaround
|
|
16
|
+
*
|
|
17
|
+
* Otherwise, use the default `ContextmenuRoot.svelte` which is much simpler
|
|
18
|
+
* and relies on the standard `contextmenu` event.
|
|
19
|
+
*/
|
|
20
|
+
import {swallow} from '@fuzdev/fuz_util/dom.js';
|
|
21
|
+
import {DEV} from 'esm-env';
|
|
22
|
+
import {on} from 'svelte/events';
|
|
23
|
+
import type {ComponentProps, Snippet} from 'svelte';
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
contextmenu_context,
|
|
27
|
+
contextmenu_dimensions_context,
|
|
28
|
+
ContextmenuState,
|
|
29
|
+
contextmenu_open,
|
|
30
|
+
contextmenu_check_global_root,
|
|
31
|
+
} from './contextmenu_state.svelte.js';
|
|
32
|
+
import ContextmenuLinkEntry from './ContextmenuLinkEntry.svelte';
|
|
33
|
+
import ContextmenuTextEntry from './ContextmenuTextEntry.svelte';
|
|
34
|
+
import ContextmenuSeparator from './ContextmenuSeparator.svelte';
|
|
35
|
+
import {
|
|
36
|
+
CONTEXTMENU_DEFAULT_OPEN_OFFSET_X,
|
|
37
|
+
CONTEXTMENU_DEFAULT_OPEN_OFFSET_Y,
|
|
38
|
+
CONTEXTMENU_DEFAULT_BYPASS_WINDOW,
|
|
39
|
+
CONTEXTMENU_DEFAULT_BYPASS_MOVE_TOLERANCE,
|
|
40
|
+
CONTEXTMENU_DEFAULT_LONGPRESS_DURATION,
|
|
41
|
+
CONTEXTMENU_DEFAULT_LONGPRESS_MOVE_TOLERANCE,
|
|
42
|
+
contextmenu_is_valid_target,
|
|
43
|
+
contextmenu_create_keyboard_handlers,
|
|
44
|
+
contextmenu_create_keydown_handler,
|
|
45
|
+
contextmenu_calculate_constrained_x,
|
|
46
|
+
contextmenu_calculate_constrained_y,
|
|
47
|
+
} from './contextmenu_helpers.js';
|
|
48
|
+
|
|
49
|
+
const {
|
|
50
|
+
contextmenu = new ContextmenuState(),
|
|
51
|
+
longpress_move_tolerance = CONTEXTMENU_DEFAULT_LONGPRESS_MOVE_TOLERANCE,
|
|
52
|
+
longpress_duration = CONTEXTMENU_DEFAULT_LONGPRESS_DURATION,
|
|
53
|
+
bypass_with_tap_then_longpress = true,
|
|
54
|
+
bypass_window = CONTEXTMENU_DEFAULT_BYPASS_WINDOW,
|
|
55
|
+
bypass_move_tolerance = CONTEXTMENU_DEFAULT_BYPASS_MOVE_TOLERANCE,
|
|
56
|
+
open_offset_x = CONTEXTMENU_DEFAULT_OPEN_OFFSET_X,
|
|
57
|
+
open_offset_y = CONTEXTMENU_DEFAULT_OPEN_OFFSET_Y,
|
|
58
|
+
scoped = false,
|
|
59
|
+
link_entry = link_entry_default,
|
|
60
|
+
text_entry = text_entry_default,
|
|
61
|
+
separator_entry = separator_entry_default,
|
|
62
|
+
children,
|
|
63
|
+
}: {
|
|
64
|
+
/**
|
|
65
|
+
* The `contextmenu` prop is not reactive because that's a rare corner case and
|
|
66
|
+
* it's easier to put the `contextmenu` directly in the context
|
|
67
|
+
* rather than wrapping with a store or other reactivity.
|
|
68
|
+
* If you need to change the contextmenu prop for some reason, use a `{#key contextmenu}` block:
|
|
69
|
+
* https://svelte.dev/docs#template-syntax-key
|
|
70
|
+
* @nonreactive
|
|
71
|
+
*/
|
|
72
|
+
contextmenu?: ContextmenuState;
|
|
73
|
+
/**
|
|
74
|
+
* The number of pixels the pointer can be moved without canceling `longpress`.
|
|
75
|
+
*/
|
|
76
|
+
longpress_move_tolerance?: number;
|
|
77
|
+
/**
|
|
78
|
+
* The number of milliseconds after a touch starts before opening the Fuz contextmenu.
|
|
79
|
+
*/
|
|
80
|
+
longpress_duration?: number;
|
|
81
|
+
/**
|
|
82
|
+
* Whether to detect tap-then-longpress to bypass the Fuz contextmenu.
|
|
83
|
+
* This allows access to the system contextmenu by tapping once then long-pressing.
|
|
84
|
+
* Setting to `false` disables the gesture.
|
|
85
|
+
*/
|
|
86
|
+
bypass_with_tap_then_longpress?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* The number of milliseconds between taps to detect a gesture that bypasses the Fuz contextmenu.
|
|
89
|
+
* Used only when `bypass_with_tap_then_longpress` is true.
|
|
90
|
+
* If the duration is too long, it'll detect more false positives and interrupt normal usage,
|
|
91
|
+
* but too short and some people will have difficulty performing the gesture.
|
|
92
|
+
*/
|
|
93
|
+
bypass_window?: number;
|
|
94
|
+
/**
|
|
95
|
+
* The number of pixels the pointer can be moved between taps to detect a tap-then-longpress.
|
|
96
|
+
* Used only when `bypass_with_tap_then_longpress` is true.
|
|
97
|
+
*/
|
|
98
|
+
bypass_move_tolerance?: number;
|
|
99
|
+
/**
|
|
100
|
+
* The number of pixels to offset from the pointer X position when opened.
|
|
101
|
+
* Useful to ensure the first menu item is immediately under the pointer.
|
|
102
|
+
*/
|
|
103
|
+
open_offset_x?: number;
|
|
104
|
+
/**
|
|
105
|
+
* The number of pixels to offset from the pointer Y position when opened.
|
|
106
|
+
* Useful to ensure the first menu item is immediately under the pointer.
|
|
107
|
+
*/
|
|
108
|
+
open_offset_y?: number;
|
|
109
|
+
/**
|
|
110
|
+
* If `true`, wraps `children` with a div and listens to events on it instead of the window.
|
|
111
|
+
*/
|
|
112
|
+
scoped?: boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Snippet for rendering link entries.
|
|
115
|
+
* Set to `null` to disable automatic link detection.
|
|
116
|
+
* Defaults to `link_entry_default` which renders `ContextmenuLinkEntry`.
|
|
117
|
+
*/
|
|
118
|
+
link_entry?: Snippet<[ComponentProps<typeof ContextmenuLinkEntry>]> | null;
|
|
119
|
+
/**
|
|
120
|
+
* Snippet for rendering copy text entries.
|
|
121
|
+
* Set to `null` to disable automatic copy text detection.
|
|
122
|
+
* Defaults to `text_entry_default` which renders `ContextmenuTextEntry`.
|
|
123
|
+
*/
|
|
124
|
+
text_entry?: Snippet<[ComponentProps<typeof ContextmenuTextEntry>]> | null;
|
|
125
|
+
/**
|
|
126
|
+
* Snippet for rendering separator entries.
|
|
127
|
+
* Set to `null` to disable automatic separator rendering.
|
|
128
|
+
* Defaults to `separator_entry_default` which renders `ContextmenuSeparator`.
|
|
129
|
+
*/
|
|
130
|
+
separator_entry?: Snippet<[ComponentProps<typeof ContextmenuSeparator>]> | null;
|
|
131
|
+
children: Snippet;
|
|
132
|
+
} = $props();
|
|
133
|
+
|
|
134
|
+
contextmenu_context.set(contextmenu);
|
|
135
|
+
|
|
136
|
+
if (DEV) contextmenu_check_global_root(() => scoped); // TODO @many is this import tree-shaken?
|
|
137
|
+
|
|
138
|
+
let el: HTMLElement | undefined = $state();
|
|
139
|
+
|
|
140
|
+
const {layout} = $derived(contextmenu);
|
|
141
|
+
|
|
142
|
+
const dimensions = contextmenu_dimensions_context.set();
|
|
143
|
+
|
|
144
|
+
const x = $derived(
|
|
145
|
+
contextmenu_calculate_constrained_x(contextmenu.x, dimensions.width, layout.width),
|
|
146
|
+
);
|
|
147
|
+
const y = $derived(
|
|
148
|
+
contextmenu_calculate_constrained_y(contextmenu.y, dimensions.height, layout.height),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// TODO maybe show an indicator fade in at these coordinates
|
|
152
|
+
|
|
153
|
+
// State for tap-then-longpress bypass detection.
|
|
154
|
+
// These values are `undefined` when unused, and `null` after being reset.
|
|
155
|
+
let touch_x: number | undefined | null = $state();
|
|
156
|
+
let touch_y: number | undefined | null = $state();
|
|
157
|
+
let first_tap_time: number | undefined | null = $state();
|
|
158
|
+
let longpress_timeout: NodeJS.Timeout | undefined | null = $state();
|
|
159
|
+
let longpress_opened: boolean | undefined = $state();
|
|
160
|
+
let longpress_bypass: boolean | undefined = $state();
|
|
161
|
+
let tap_tracking_timeout: NodeJS.Timeout | undefined | null = $state();
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Blocks the next click event. Set to true when a longpress completes to prevent
|
|
165
|
+
* iOS's synthesized click from activating the first menu item.
|
|
166
|
+
*/
|
|
167
|
+
let block_next_click = $state(false);
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Adds contextmenu_pending class to body during longpress tracking.
|
|
171
|
+
* This applies aggressive user-select/touch-callout blocking via CSS
|
|
172
|
+
* to give iOS the earliest possible signal to not show selection UI.
|
|
173
|
+
*/
|
|
174
|
+
const add_contextmenu_pending_class = (): void => {
|
|
175
|
+
document.body.classList.add('contextmenu_pending');
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Removes contextmenu_pending class from body when tracking ends.
|
|
180
|
+
*/
|
|
181
|
+
const remove_contextmenu_pending_class = (): void => {
|
|
182
|
+
document.body.classList.remove('contextmenu_pending');
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Resets only the longpress timeout state, preserving tap tracking for bypass detection.
|
|
187
|
+
* This is called when a touch ends before longpress completes.
|
|
188
|
+
*/
|
|
189
|
+
const reset_longpress_timeout = (): void => {
|
|
190
|
+
longpress_opened = false;
|
|
191
|
+
if (longpress_timeout != null) {
|
|
192
|
+
clearTimeout(longpress_timeout);
|
|
193
|
+
longpress_timeout = null;
|
|
194
|
+
}
|
|
195
|
+
remove_contextmenu_pending_class();
|
|
196
|
+
// Don't clear tap tracking state here - we need it for tap-then-longpress detection
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Clears tap tracking state and bypass flag used for bypass detection.
|
|
201
|
+
*/
|
|
202
|
+
const reset_tap_tracking = (): void => {
|
|
203
|
+
first_tap_time = null;
|
|
204
|
+
touch_x = null;
|
|
205
|
+
touch_y = null;
|
|
206
|
+
longpress_bypass = false;
|
|
207
|
+
if (tap_tracking_timeout != null) {
|
|
208
|
+
clearTimeout(tap_tracking_timeout);
|
|
209
|
+
tap_tracking_timeout = null;
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Resets all state - both longpress and tap tracking.
|
|
215
|
+
*/
|
|
216
|
+
const reset_all = (): void => {
|
|
217
|
+
reset_longpress_timeout(); // Also removes contextmenu_pending class
|
|
218
|
+
reset_tap_tracking();
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const on_window_contextmenu = (e: MouseEvent) => {
|
|
222
|
+
// handle the tap-then-longpress bypass gesture
|
|
223
|
+
if (longpress_bypass) {
|
|
224
|
+
reset_tap_tracking(); // Clear bypass state after using it
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const {target} = e;
|
|
228
|
+
// handle touch devices that trigger `'contextmenu'` slower than our longpress
|
|
229
|
+
if (longpress_opened) {
|
|
230
|
+
// Don't prevent contextmenu events on elements inside our own contextmenu
|
|
231
|
+
// This allows the browser's native contextmenu (useful for dev tools, inspecting elements, etc.)
|
|
232
|
+
if (el?.contains(target as Node)) {
|
|
233
|
+
return; // Let the event pass through
|
|
234
|
+
}
|
|
235
|
+
reset_all();
|
|
236
|
+
swallow(e);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (!contextmenu_is_valid_target(target, e.shiftKey)) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// Don't open contextmenu when clicking on the menu itself
|
|
243
|
+
if (el?.contains(target)) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (
|
|
247
|
+
contextmenu_open(target, e.clientX + open_offset_x, e.clientY + open_offset_y, contextmenu, {
|
|
248
|
+
link_enabled: link_entry !== null,
|
|
249
|
+
text_enabled: text_entry !== null,
|
|
250
|
+
separator_enabled: separator_entry !== null,
|
|
251
|
+
})
|
|
252
|
+
) {
|
|
253
|
+
swallow(e);
|
|
254
|
+
reset_all(); // handle touch devices that trigger `'contextmenu'` faster than our longpress
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// Needed for the iOS workaround. Registered with { passive: false } via $effect (window) or attachment (scoped).
|
|
259
|
+
const touchstart = (e: TouchEvent): void => {
|
|
260
|
+
longpress_opened = false;
|
|
261
|
+
block_next_click = false; // Clear any stale click blocking flag
|
|
262
|
+
const {touches, target} = e;
|
|
263
|
+
if (
|
|
264
|
+
contextmenu.opened ||
|
|
265
|
+
touches.length !== 1 ||
|
|
266
|
+
!contextmenu_is_valid_target(target, e.shiftKey)
|
|
267
|
+
) {
|
|
268
|
+
// Reset all state when conditions aren't met
|
|
269
|
+
reset_all();
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const {clientX, clientY} = touches[0]!;
|
|
274
|
+
|
|
275
|
+
// Bypass the contextmenu behavior in certain conditions including a tap-and-longpress gesture.
|
|
276
|
+
// To handle tap-then-longpress we need to see if `first_tap_time`
|
|
277
|
+
// is less than `bypass_window`, and also allow a small amount
|
|
278
|
+
// of pointer movement, `bypass_move_tolerance`.
|
|
279
|
+
// The builtin `'contextmenu'` event will still fire for non-iOS browsers,
|
|
280
|
+
// so `longpress_bypass` is used to tell the handler `on_window_contextmenu` to exit early.
|
|
281
|
+
if (bypass_with_tap_then_longpress) {
|
|
282
|
+
if (
|
|
283
|
+
first_tap_time != null &&
|
|
284
|
+
performance.now() - first_tap_time < bypass_window &&
|
|
285
|
+
Math.hypot(clientX - touch_x!, clientY - touch_y!) < bypass_move_tolerance
|
|
286
|
+
) {
|
|
287
|
+
// Tap-then-longpress detected! Set bypass and clear tap tracking state.
|
|
288
|
+
// Must manually clear state (not call reset_tap_tracking) to preserve bypass flag.
|
|
289
|
+
longpress_bypass = true;
|
|
290
|
+
first_tap_time = null;
|
|
291
|
+
touch_x = null;
|
|
292
|
+
touch_y = null;
|
|
293
|
+
if (tap_tracking_timeout != null) {
|
|
294
|
+
clearTimeout(tap_tracking_timeout); // Prevent timeout from clearing bypass
|
|
295
|
+
tap_tracking_timeout = null;
|
|
296
|
+
}
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
// Record this tap for potential future bypass detection
|
|
300
|
+
first_tap_time = performance.now();
|
|
301
|
+
// Set timeout to clear stale tap tracking after the detection window expires
|
|
302
|
+
if (tap_tracking_timeout != null) {
|
|
303
|
+
clearTimeout(tap_tracking_timeout);
|
|
304
|
+
}
|
|
305
|
+
tap_tracking_timeout = setTimeout(() => {
|
|
306
|
+
reset_tap_tracking();
|
|
307
|
+
}, bypass_window);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
touch_x = clientX;
|
|
311
|
+
touch_y = clientY;
|
|
312
|
+
|
|
313
|
+
// Add pending class to enable aggressive iOS blocking via CSS during tracking
|
|
314
|
+
add_contextmenu_pending_class();
|
|
315
|
+
|
|
316
|
+
if (longpress_timeout != null) reset_longpress_timeout();
|
|
317
|
+
longpress_timeout = setTimeout(() => {
|
|
318
|
+
longpress_opened = true;
|
|
319
|
+
remove_contextmenu_pending_class(); // Tracking complete, menu opening
|
|
320
|
+
contextmenu_open(target, touch_x! + open_offset_x, touch_y! + open_offset_y, contextmenu, {
|
|
321
|
+
link_enabled: link_entry !== null,
|
|
322
|
+
text_enabled: text_entry !== null,
|
|
323
|
+
separator_enabled: separator_entry !== null,
|
|
324
|
+
});
|
|
325
|
+
}, longpress_duration);
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
// Needed for the iOS workaround. Registered with { passive: false } via $effect (window) or attachment (scoped).
|
|
329
|
+
const touchmove = (e: TouchEvent): void => {
|
|
330
|
+
// Exit early if no pending longpress or menu is already open
|
|
331
|
+
if (longpress_timeout == null || contextmenu.opened) return;
|
|
332
|
+
const {touches} = e;
|
|
333
|
+
if (touches.length !== 1) return;
|
|
334
|
+
const {clientX, clientY} = touches[0]!;
|
|
335
|
+
const distance = Math.hypot(clientX - touch_x!, clientY - touch_y!);
|
|
336
|
+
if (distance > longpress_move_tolerance) {
|
|
337
|
+
// User is scrolling - cancel longpress but DON'T preventDefault
|
|
338
|
+
reset_longpress_timeout();
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
// Still within threshold - this is a longpress
|
|
342
|
+
// CRITICAL: Prevent iOS from showing magnifier, text selection, and link callouts
|
|
343
|
+
e.preventDefault();
|
|
344
|
+
};
|
|
345
|
+
// Needed for the iOS workaround. Registered with { passive: false } via $effect.
|
|
346
|
+
const touchend = (e: TouchEvent): void => {
|
|
347
|
+
// Clear longpress timeout if it exists
|
|
348
|
+
if (longpress_timeout != null) {
|
|
349
|
+
if (longpress_opened) {
|
|
350
|
+
swallow(e);
|
|
351
|
+
// Block the next click to prevent iOS's synthesized click from activating the first menu item
|
|
352
|
+
block_next_click = true;
|
|
353
|
+
}
|
|
354
|
+
reset_longpress_timeout();
|
|
355
|
+
}
|
|
356
|
+
// Clear bypass flag if it was set but the contextmenu event hasn't fired yet
|
|
357
|
+
// This handles the case where bypass is detected but user lifts finger before native menu opens
|
|
358
|
+
if (longpress_bypass) {
|
|
359
|
+
reset_tap_tracking();
|
|
360
|
+
}
|
|
361
|
+
// Note: We don't clear tap tracking state here - we preserve it for tap-then-longpress detection
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Handle touchcancel - this should reset all state since the gesture was interrupted.
|
|
366
|
+
*/
|
|
367
|
+
const touchcancel = (): void => {
|
|
368
|
+
reset_all();
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// Passive listener that runs during the event's `capture` phase
|
|
372
|
+
// so that things like the Dialog don't eat the events and prevent the contextmenu from closing.
|
|
373
|
+
const mousedown = (e: MouseEvent) => {
|
|
374
|
+
if (el && !el.contains(e.target as any)) {
|
|
375
|
+
contextmenu.close();
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
const keyboard_handlers = contextmenu_create_keyboard_handlers(contextmenu);
|
|
380
|
+
const keydown = contextmenu_create_keydown_handler(keyboard_handlers);
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Creates an attachment that registers touch event listeners with { passive: false }
|
|
384
|
+
* to enable preventDefault() on iOS Safari. Works for both window (non-scoped mode) and
|
|
385
|
+
* HTMLElement (scoped mode). Svelte's declarative touch handlers create passive listeners
|
|
386
|
+
* by default, which prevents blocking iOS's default longpress behaviors (magnifier, text
|
|
387
|
+
* selection, callouts). Using `on()` from svelte/events ensures proper event ordering
|
|
388
|
+
* with Svelte's event delegation system.
|
|
389
|
+
*
|
|
390
|
+
* The critical fix is calling preventDefault() in the touchmove handler when tracking
|
|
391
|
+
* a longpress with movement below threshold.
|
|
392
|
+
*
|
|
393
|
+
* @param el The Window or HTMLElement to attach touch listeners to
|
|
394
|
+
* @returns Cleanup function to remove all event listeners
|
|
395
|
+
*/
|
|
396
|
+
const touch_event_attachment = (el: HTMLElement | Window) => {
|
|
397
|
+
// touchstart and touchcancel don't call preventDefault, so they can be passive for better performance
|
|
398
|
+
const passive_options: AddEventListenerOptions = {passive: true, capture: true};
|
|
399
|
+
// touchmove and touchend need to call preventDefault to block iOS behaviors, so they must be non-passive
|
|
400
|
+
const nonpassive_options: AddEventListenerOptions = {passive: false, capture: true};
|
|
401
|
+
|
|
402
|
+
const cleanup_touchstart = on(el, 'touchstart', touchstart as EventListener, passive_options);
|
|
403
|
+
const cleanup_touchmove = on(el, 'touchmove', touchmove as EventListener, nonpassive_options);
|
|
404
|
+
const cleanup_touchend = on(el, 'touchend', touchend as EventListener, nonpassive_options);
|
|
405
|
+
const cleanup_touchcancel = on(
|
|
406
|
+
el,
|
|
407
|
+
'touchcancel',
|
|
408
|
+
touchcancel as EventListener,
|
|
409
|
+
passive_options,
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
return () => {
|
|
413
|
+
cleanup_touchstart();
|
|
414
|
+
cleanup_touchmove();
|
|
415
|
+
cleanup_touchend();
|
|
416
|
+
cleanup_touchcancel();
|
|
417
|
+
};
|
|
418
|
+
};
|
|
419
|
+
</script>
|
|
420
|
+
|
|
421
|
+
<svelte:window
|
|
422
|
+
oncontextmenu={scoped ? undefined : on_window_contextmenu}
|
|
423
|
+
onmousedown={!contextmenu.opened ? undefined : mousedown}
|
|
424
|
+
onkeydown={!contextmenu.opened ? undefined : keydown}
|
|
425
|
+
{@attach scoped ? undefined : touch_event_attachment}
|
|
426
|
+
/>
|
|
427
|
+
|
|
428
|
+
{#if scoped}
|
|
429
|
+
<div
|
|
430
|
+
class="contextmenu_root"
|
|
431
|
+
role="region"
|
|
432
|
+
oncontextmenu={on_window_contextmenu}
|
|
433
|
+
{@attach touch_event_attachment}
|
|
434
|
+
>
|
|
435
|
+
{@render children()}
|
|
436
|
+
</div>
|
|
437
|
+
{:else}
|
|
438
|
+
{@render children()}
|
|
439
|
+
{/if}
|
|
440
|
+
|
|
441
|
+
{#if !contextmenu.has_custom_layout}
|
|
442
|
+
<div
|
|
443
|
+
class="contextmenu_layout"
|
|
444
|
+
bind:clientWidth={layout.width}
|
|
445
|
+
bind:clientHeight={layout.height}
|
|
446
|
+
aria-hidden="true"
|
|
447
|
+
></div>
|
|
448
|
+
{/if}
|
|
449
|
+
|
|
450
|
+
<!-- TODO Maybe animate a subtle highlight around the contextmenu as it appears? -->
|
|
451
|
+
{#if contextmenu.opened}
|
|
452
|
+
<ul
|
|
453
|
+
class="contextmenu unstyled pane"
|
|
454
|
+
role="menu"
|
|
455
|
+
aria-label="context menu"
|
|
456
|
+
tabindex="-1"
|
|
457
|
+
bind:this={el}
|
|
458
|
+
bind:offsetWidth={dimensions.width}
|
|
459
|
+
bind:offsetHeight={dimensions.height}
|
|
460
|
+
style:transform="translate3d({x}px, {y}px, 0)"
|
|
461
|
+
onclickcapture={block_next_click
|
|
462
|
+
? (e) => {
|
|
463
|
+
// iOS synthesizes a click after touchend which
|
|
464
|
+
// can unintentionally activate the first menu item. This blocks it.
|
|
465
|
+
block_next_click = false;
|
|
466
|
+
swallow(e);
|
|
467
|
+
}
|
|
468
|
+
: undefined}
|
|
469
|
+
>
|
|
470
|
+
<!-- TODO maybe this should be generic? -->
|
|
471
|
+
{#each contextmenu.params as p (p)}
|
|
472
|
+
{#if typeof p === 'function'}
|
|
473
|
+
{@render p()}
|
|
474
|
+
{:else if p.snippet === 'link'}
|
|
475
|
+
{@render link_entry?.(p.props)}
|
|
476
|
+
{:else if p.snippet === 'text'}
|
|
477
|
+
{@render text_entry?.(p.props)}
|
|
478
|
+
{:else if p.snippet === 'separator'}
|
|
479
|
+
{@render separator_entry?.(p.props)}
|
|
480
|
+
{/if}
|
|
481
|
+
{/each}
|
|
482
|
+
</ul>
|
|
483
|
+
{/if}
|
|
484
|
+
|
|
485
|
+
{#snippet link_entry_default(props: ComponentProps<typeof ContextmenuLinkEntry>)}
|
|
486
|
+
<ContextmenuLinkEntry {...props} />
|
|
487
|
+
{/snippet}
|
|
488
|
+
|
|
489
|
+
{#snippet text_entry_default(props: ComponentProps<typeof ContextmenuTextEntry>)}
|
|
490
|
+
<ContextmenuTextEntry {...props} />
|
|
491
|
+
{/snippet}
|
|
492
|
+
|
|
493
|
+
{#snippet separator_entry_default(props: ComponentProps<typeof ContextmenuSeparator>)}
|
|
494
|
+
<ContextmenuSeparator {...props} />
|
|
495
|
+
{/snippet}
|
|
496
|
+
|
|
497
|
+
<style>
|
|
498
|
+
:global(body.contextmenu_pending) {
|
|
499
|
+
/* Applied during active longpress tracking.
|
|
500
|
+
Aggressive blocking to give iOS earliest possible signal to not show selection UI.
|
|
501
|
+
Combined with preventDefault() in touchmove for defense in depth. */
|
|
502
|
+
-webkit-user-select: none !important;
|
|
503
|
+
user-select: none !important;
|
|
504
|
+
-webkit-touch-callout: none !important;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.contextmenu_root {
|
|
508
|
+
display: contents;
|
|
509
|
+
}
|
|
510
|
+
.contextmenu {
|
|
511
|
+
--icon_size: var(--icon_size_xs);
|
|
512
|
+
/* TODO maybe make this responsive or a max of the page width
|
|
513
|
+
minus some space to tap items covered by the menu on the side,
|
|
514
|
+
or consider a totally different design for small screens (more dialog-like) */
|
|
515
|
+
--contextmenu_width: 320px;
|
|
516
|
+
position: fixed;
|
|
517
|
+
left: 0;
|
|
518
|
+
top: 0;
|
|
519
|
+
z-index: var(--contextmenu_z_index, 200);
|
|
520
|
+
max-width: var(--contextmenu_width);
|
|
521
|
+
width: 100%;
|
|
522
|
+
/* Re-enable callouts on the menu itself to allow native contextmenu (for dev tools).
|
|
523
|
+
Resets the global body blocking. Prevents the menu from being selected. */
|
|
524
|
+
-webkit-touch-callout: initial !important;
|
|
525
|
+
}
|
|
526
|
+
/* TODO hacky */
|
|
527
|
+
.contextmenu,
|
|
528
|
+
.contextmenu :global(menu.pane) {
|
|
529
|
+
border: var(--contextmenu_border_width, var(--border_width))
|
|
530
|
+
var(--contextmenu_border_style, var(--border_style))
|
|
531
|
+
var(--contextmenu_border_color, var(--border_color));
|
|
532
|
+
border-radius: var(--contextmenu_border_radius, var(--border_radius_xs));
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/* TODO better way to do this? */
|
|
536
|
+
.contextmenu_layout {
|
|
537
|
+
z-index: -200;
|
|
538
|
+
position: fixed;
|
|
539
|
+
inset: 0;
|
|
540
|
+
}
|
|
541
|
+
</style>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { ComponentProps, Snippet } from 'svelte';
|
|
2
|
+
import { ContextmenuState } from './contextmenu_state.svelte.js';
|
|
3
|
+
import ContextmenuLinkEntry from './ContextmenuLinkEntry.svelte';
|
|
4
|
+
import ContextmenuTextEntry from './ContextmenuTextEntry.svelte';
|
|
5
|
+
import ContextmenuSeparator from './ContextmenuSeparator.svelte';
|
|
6
|
+
type $$ComponentProps = {
|
|
7
|
+
/**
|
|
8
|
+
* The `contextmenu` prop is not reactive because that's a rare corner case and
|
|
9
|
+
* it's easier to put the `contextmenu` directly in the context
|
|
10
|
+
* rather than wrapping with a store or other reactivity.
|
|
11
|
+
* If you need to change the contextmenu prop for some reason, use a `{#key contextmenu}` block:
|
|
12
|
+
* https://svelte.dev/docs#template-syntax-key
|
|
13
|
+
* @nonreactive
|
|
14
|
+
*/
|
|
15
|
+
contextmenu?: ContextmenuState;
|
|
16
|
+
/**
|
|
17
|
+
* The number of pixels the pointer can be moved without canceling `longpress`.
|
|
18
|
+
*/
|
|
19
|
+
longpress_move_tolerance?: number;
|
|
20
|
+
/**
|
|
21
|
+
* The number of milliseconds after a touch starts before opening the Fuz contextmenu.
|
|
22
|
+
*/
|
|
23
|
+
longpress_duration?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Whether to detect tap-then-longpress to bypass the Fuz contextmenu.
|
|
26
|
+
* This allows access to the system contextmenu by tapping once then long-pressing.
|
|
27
|
+
* Setting to `false` disables the gesture.
|
|
28
|
+
*/
|
|
29
|
+
bypass_with_tap_then_longpress?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* The number of milliseconds between taps to detect a gesture that bypasses the Fuz contextmenu.
|
|
32
|
+
* Used only when `bypass_with_tap_then_longpress` is true.
|
|
33
|
+
* If the duration is too long, it'll detect more false positives and interrupt normal usage,
|
|
34
|
+
* but too short and some people will have difficulty performing the gesture.
|
|
35
|
+
*/
|
|
36
|
+
bypass_window?: number;
|
|
37
|
+
/**
|
|
38
|
+
* The number of pixels the pointer can be moved between taps to detect a tap-then-longpress.
|
|
39
|
+
* Used only when `bypass_with_tap_then_longpress` is true.
|
|
40
|
+
*/
|
|
41
|
+
bypass_move_tolerance?: number;
|
|
42
|
+
/**
|
|
43
|
+
* The number of pixels to offset from the pointer X position when opened.
|
|
44
|
+
* Useful to ensure the first menu item is immediately under the pointer.
|
|
45
|
+
*/
|
|
46
|
+
open_offset_x?: number;
|
|
47
|
+
/**
|
|
48
|
+
* The number of pixels to offset from the pointer Y position when opened.
|
|
49
|
+
* Useful to ensure the first menu item is immediately under the pointer.
|
|
50
|
+
*/
|
|
51
|
+
open_offset_y?: number;
|
|
52
|
+
/**
|
|
53
|
+
* If `true`, wraps `children` with a div and listens to events on it instead of the window.
|
|
54
|
+
*/
|
|
55
|
+
scoped?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Snippet for rendering link entries.
|
|
58
|
+
* Set to `null` to disable automatic link detection.
|
|
59
|
+
* Defaults to `link_entry_default` which renders `ContextmenuLinkEntry`.
|
|
60
|
+
*/
|
|
61
|
+
link_entry?: Snippet<[ComponentProps<typeof ContextmenuLinkEntry>]> | null;
|
|
62
|
+
/**
|
|
63
|
+
* Snippet for rendering copy text entries.
|
|
64
|
+
* Set to `null` to disable automatic copy text detection.
|
|
65
|
+
* Defaults to `text_entry_default` which renders `ContextmenuTextEntry`.
|
|
66
|
+
*/
|
|
67
|
+
text_entry?: Snippet<[ComponentProps<typeof ContextmenuTextEntry>]> | null;
|
|
68
|
+
/**
|
|
69
|
+
* Snippet for rendering separator entries.
|
|
70
|
+
* Set to `null` to disable automatic separator rendering.
|
|
71
|
+
* Defaults to `separator_entry_default` which renders `ContextmenuSeparator`.
|
|
72
|
+
*/
|
|
73
|
+
separator_entry?: Snippet<[ComponentProps<typeof ContextmenuSeparator>]> | null;
|
|
74
|
+
children: Snippet;
|
|
75
|
+
};
|
|
76
|
+
declare const ContextmenuRootForSafariCompatibility: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
77
|
+
type ContextmenuRootForSafariCompatibility = ReturnType<typeof ContextmenuRootForSafariCompatibility>;
|
|
78
|
+
export default ContextmenuRootForSafariCompatibility;
|
|
79
|
+
//# sourceMappingURL=ContextmenuRootForSafariCompatibility.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContextmenuRootForSafariCompatibility.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/ContextmenuRootForSafariCompatibility.svelte"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAC,cAAc,EAAE,OAAO,EAAC,MAAM,QAAQ,CAAC;AAEpD,OAAO,EAGL,gBAAgB,EAGhB,MAAM,+BAA+B,CAAC;AACxC,OAAO,oBAAoB,MAAM,+BAA+B,CAAC;AACjE,OAAO,oBAAoB,MAAM,+BAA+B,CAAC;AACjE,OAAO,oBAAoB,MAAM,+BAA+B,CAAC;AAehE,KAAK,gBAAgB,GAAI;IACxB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B;;OAEG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC;;OAEG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,8BAA8B,CAAC,EAAE,OAAO,CAAC;IACzC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC,cAAc,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAC3E;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC,cAAc,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAC3E;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC,CAAC,cAAc,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAChF,QAAQ,EAAE,OAAO,CAAC;CAClB,CAAC;AAoXH,QAAA,MAAM,qCAAqC,sDAAwC,CAAC;AACpF,KAAK,qCAAqC,GAAG,UAAU,CAAC,OAAO,qCAAqC,CAAC,CAAC;AACtG,eAAe,qCAAqC,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type {SvelteHTMLElements} from 'svelte/elements';
|
|
3
|
+
|
|
4
|
+
const {...rest}: SvelteHTMLElements['li'] = $props();
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<li role="separator" aria-orientation="vertical" {...rest} class:contextmenu_separator={true}></li>
|
|
8
|
+
|
|
9
|
+
<style>
|
|
10
|
+
.contextmenu_separator {
|
|
11
|
+
height: var(--border_width);
|
|
12
|
+
margin: var(--spacing_xs) var(--spacing_sm);
|
|
13
|
+
background-color: var(--border_color_1);
|
|
14
|
+
list-style: none;
|
|
15
|
+
}
|
|
16
|
+
</style>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
declare const ContextmenuSeparator: import("svelte").Component<import("svelte/elements").HTMLLiAttributes, {}, "">;
|
|
2
|
+
type ContextmenuSeparator = ReturnType<typeof ContextmenuSeparator>;
|
|
3
|
+
export default ContextmenuSeparator;
|
|
4
|
+
//# sourceMappingURL=ContextmenuSeparator.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContextmenuSeparator.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/ContextmenuSeparator.svelte"],"names":[],"mappings":"AAkBA,QAAA,MAAM,oBAAoB,gFAAwC,CAAC;AACnE,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACpE,eAAe,oBAAoB,CAAC"}
|