@fuzdev/fuz_ui 0.202.0 → 0.203.1

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 (71) hide show
  1. package/dist/ApiIndex.svelte +6 -3
  2. package/dist/ApiIndex.svelte.d.ts +1 -1
  3. package/dist/ApiIndex.svelte.d.ts.map +1 -1
  4. package/dist/ApiModule.svelte +72 -21
  5. package/dist/ApiModule.svelte.d.ts +1 -1
  6. package/dist/ApiModule.svelte.d.ts.map +1 -1
  7. package/dist/ContextmenuEntry.svelte +15 -16
  8. package/dist/ContextmenuEntry.svelte.d.ts +3 -2
  9. package/dist/ContextmenuEntry.svelte.d.ts.map +1 -1
  10. package/dist/ContextmenuIcon.svelte +30 -0
  11. package/dist/ContextmenuIcon.svelte.d.ts +9 -0
  12. package/dist/ContextmenuIcon.svelte.d.ts.map +1 -0
  13. package/dist/ContextmenuLinkEntry.svelte +23 -12
  14. package/dist/ContextmenuLinkEntry.svelte.d.ts +2 -1
  15. package/dist/ContextmenuLinkEntry.svelte.d.ts.map +1 -1
  16. package/dist/ContextmenuMenu.svelte +207 -0
  17. package/dist/ContextmenuMenu.svelte.d.ts +41 -0
  18. package/dist/ContextmenuMenu.svelte.d.ts.map +1 -0
  19. package/dist/ContextmenuRoot.svelte +58 -260
  20. package/dist/ContextmenuRoot.svelte.d.ts +2 -60
  21. package/dist/ContextmenuRoot.svelte.d.ts.map +1 -1
  22. package/dist/ContextmenuRootForSafariCompatibility.svelte +72 -283
  23. package/dist/ContextmenuRootForSafariCompatibility.svelte.d.ts +2 -58
  24. package/dist/ContextmenuRootForSafariCompatibility.svelte.d.ts.map +1 -1
  25. package/dist/ContextmenuSeparator.svelte +2 -1
  26. package/dist/ContextmenuSeparator.svelte.d.ts.map +1 -1
  27. package/dist/ContextmenuSubmenu.svelte +31 -25
  28. package/dist/ContextmenuSubmenu.svelte.d.ts +3 -2
  29. package/dist/ContextmenuSubmenu.svelte.d.ts.map +1 -1
  30. package/dist/ContextmenuTextEntry.svelte +6 -4
  31. package/dist/ContextmenuTextEntry.svelte.d.ts +3 -1
  32. package/dist/ContextmenuTextEntry.svelte.d.ts.map +1 -1
  33. package/dist/DeclarationLink.svelte +15 -2
  34. package/dist/DeclarationLink.svelte.d.ts +7 -0
  35. package/dist/DeclarationLink.svelte.d.ts.map +1 -1
  36. package/dist/DocsLink.svelte +3 -1
  37. package/dist/DocsLink.svelte.d.ts.map +1 -1
  38. package/dist/DocsTertiaryNav.svelte +2 -1
  39. package/dist/DocsTertiaryNav.svelte.d.ts.map +1 -1
  40. package/dist/LibraryDetail.svelte +9 -3
  41. package/dist/LibraryDetail.svelte.d.ts +1 -1
  42. package/dist/LibraryDetail.svelte.d.ts.map +1 -1
  43. package/dist/ModuleLink.svelte +2 -1
  44. package/dist/ModuleLink.svelte.d.ts.map +1 -1
  45. package/dist/TypeLink.svelte +2 -1
  46. package/dist/TypeLink.svelte.d.ts.map +1 -1
  47. package/dist/contextmenu_helpers.d.ts +324 -1
  48. package/dist/contextmenu_helpers.d.ts.map +1 -1
  49. package/dist/contextmenu_helpers.js +403 -2
  50. package/dist/contextmenu_state.svelte.d.ts +61 -15
  51. package/dist/contextmenu_state.svelte.d.ts.map +1 -1
  52. package/dist/contextmenu_state.svelte.js +206 -119
  53. package/dist/declaration.svelte.d.ts +1 -0
  54. package/dist/declaration.svelte.d.ts.map +1 -1
  55. package/dist/declaration.svelte.js +2 -1
  56. package/dist/icons.d.ts +15 -3
  57. package/dist/icons.d.ts.map +1 -1
  58. package/dist/icons.js +17 -3
  59. package/dist/library.svelte.d.ts +30 -3
  60. package/dist/library.svelte.d.ts.map +1 -1
  61. package/dist/library.svelte.js +38 -0
  62. package/dist/module.svelte.d.ts +62 -1
  63. package/dist/module.svelte.d.ts.map +1 -1
  64. package/dist/module.svelte.js +71 -1
  65. package/package.json +2 -2
  66. package/src/lib/contextmenu_helpers.ts +546 -3
  67. package/src/lib/contextmenu_state.svelte.ts +219 -119
  68. package/src/lib/declaration.svelte.ts +2 -1
  69. package/src/lib/icons.ts +19 -3
  70. package/src/lib/library.svelte.ts +43 -1
  71. package/src/lib/module.svelte.ts +97 -2
@@ -0,0 +1,41 @@
1
+ export { link_entry_default, text_entry_default, separator_entry_default };
2
+ import type { ComponentProps, Snippet } from 'svelte';
3
+ import { type ContextmenuState } from './contextmenu_state.svelte.js';
4
+ import ContextmenuLinkEntry from './ContextmenuLinkEntry.svelte';
5
+ import ContextmenuTextEntry from './ContextmenuTextEntry.svelte';
6
+ import ContextmenuSeparator from './ContextmenuSeparator.svelte';
7
+ import { type ContextmenuOpenGuard } from './contextmenu_helpers.js';
8
+ type $$ComponentProps = {
9
+ /**
10
+ * The contextmenu state owned by the root.
11
+ */
12
+ contextmenu: ContextmenuState;
13
+ /**
14
+ * Guards the menu from the residual events of the gesture that opened it.
15
+ * Owned by the root, which arms it when its gesture detection opens the menu.
16
+ */
17
+ open_guard: ContextmenuOpenGuard;
18
+ /**
19
+ * Snippet for rendering link entries, or `null` to disable them.
20
+ */
21
+ link_entry: Snippet<[ComponentProps<typeof ContextmenuLinkEntry>]> | null;
22
+ /**
23
+ * Snippet for rendering copy text entries, or `null` to disable them.
24
+ */
25
+ text_entry: Snippet<[ComponentProps<typeof ContextmenuTextEntry>]> | null;
26
+ /**
27
+ * Snippet for rendering separator entries, or `null` to disable them.
28
+ */
29
+ separator_entry: Snippet<[ComponentProps<typeof ContextmenuSeparator>]> | null;
30
+ /**
31
+ * The menu element while opened - bound by the roots for their gesture handlers.
32
+ */
33
+ el?: HTMLElement;
34
+ };
35
+ declare const link_entry_default: (props: ComponentProps<typeof ContextmenuLinkEntry>) => ReturnType<import("svelte").Snippet>;
36
+ declare const text_entry_default: (props: ComponentProps<typeof ContextmenuTextEntry>) => ReturnType<import("svelte").Snippet>;
37
+ declare const separator_entry_default: (props: ComponentProps<typeof ContextmenuSeparator>) => ReturnType<import("svelte").Snippet>;
38
+ declare const ContextmenuMenu: import("svelte").Component<$$ComponentProps, {}, "el">;
39
+ type ContextmenuMenu = ReturnType<typeof ContextmenuMenu>;
40
+ export default ContextmenuMenu;
41
+ //# sourceMappingURL=ContextmenuMenu.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContextmenuMenu.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/ContextmenuMenu.svelte"],"names":[],"mappings":"AAKC,OAAO,EAAC,kBAAkB,EAAE,kBAAkB,EAAE,uBAAuB,EAAC,CAAC;AAY1E,OAAO,KAAK,EAAC,cAAc,EAAE,OAAO,EAAC,MAAM,QAAQ,CAAC;AAEpD,OAAO,EAEL,KAAK,gBAAgB,EACrB,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;AACjE,OAAO,EAOL,KAAK,oBAAoB,EACzB,MAAM,0BAA0B,CAAC;AAElC,KAAK,gBAAgB,GAAI;IACxB;;OAEG;IACH,WAAW,EAAE,gBAAgB,CAAC;IAC9B;;;OAGG;IACH,UAAU,EAAE,oBAAoB,CAAC;IACjC;;OAEG;IACH,UAAU,EAAE,OAAO,CAAC,CAAC,cAAc,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAC1E;;OAEG;IACH,UAAU,EAAE,OAAO,CAAC,CAAC,cAAc,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAC1E;;OAEG;IACH,eAAe,EAAE,OAAO,CAAC,CAAC,cAAc,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAC/E;;OAEG;IACH,EAAE,CAAC,EAAE,WAAW,CAAC;CACjB,CAAC;AAAC,QAAA,MAAM,kBAAkB,GAAyB,OAAO,cAAc,CAAC,OAAO,oBAAoB,CAAC,KAAqB,UAAU,CAAC,OAAO,QAAQ,EAAE,OAAO,CAElI,CAAC;AAAC,QAAA,MAAM,kBAAkB,GAAyB,OAAO,cAAc,CAAC,OAAO,oBAAoB,CAAC,KAAqB,UAAU,CAAC,OAAO,QAAQ,EAAE,OAAO,CAE7J,CAAC;AAAC,QAAA,MAAM,uBAAuB,GAAyB,OAAO,cAAc,CAAC,OAAO,oBAAoB,CAAC,KAAqB,UAAU,CAAC,OAAO,QAAQ,EAAE,OAAO,CAElK,CAAC;AAuF9B,QAAA,MAAM,eAAe,wDAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
@@ -16,30 +16,26 @@
16
16
  * instead. That version implements custom touch handlers and longpress detection at the
17
17
  * cost of significantly more complexity and no vibrate support.
18
18
  */
19
- import {swallow} from '@fuzdev/fuz_util/dom.js';
20
- import {DEV} from 'esm-env';
21
- import type {ComponentProps, Snippet} from 'svelte';
22
-
23
19
  import {
24
20
  contextmenu_context,
25
- contextmenu_dimensions_context,
26
21
  ContextmenuState,
27
- contextmenu_open,
28
22
  contextmenu_check_global_root,
29
23
  } from './contextmenu_state.svelte.js';
30
- import ContextmenuLinkEntry from './ContextmenuLinkEntry.svelte';
31
- import ContextmenuTextEntry from './ContextmenuTextEntry.svelte';
32
- import ContextmenuSeparator from './ContextmenuSeparator.svelte';
24
+ import ContextmenuMenu, {
25
+ link_entry_default,
26
+ text_entry_default,
27
+ separator_entry_default,
28
+ } from './ContextmenuMenu.svelte';
33
29
  import {
34
30
  CONTEXTMENU_DEFAULT_OPEN_OFFSET_X,
35
31
  CONTEXTMENU_DEFAULT_OPEN_OFFSET_Y,
36
32
  CONTEXTMENU_DEFAULT_BYPASS_WINDOW,
37
33
  CONTEXTMENU_DEFAULT_BYPASS_MOVE_TOLERANCE,
38
34
  contextmenu_is_valid_target,
39
- contextmenu_create_keyboard_handlers,
40
- contextmenu_create_keydown_handler,
41
- contextmenu_calculate_constrained_x,
42
- contextmenu_calculate_constrained_y,
35
+ contextmenu_resolve_contextmenu_event,
36
+ ContextmenuBypassTracker,
37
+ ContextmenuOpenGuard,
38
+ type ContextmenuRootBaseProps,
43
39
  } from './contextmenu_helpers.js';
44
40
 
45
41
  const {
@@ -54,131 +50,47 @@
54
50
  text_entry = text_entry_default,
55
51
  separator_entry = separator_entry_default,
56
52
  children,
57
- }: {
58
- contextmenu?: ContextmenuState;
59
- /**
60
- * The number of pixels to offset from the pointer X position when opened.
61
- * Useful to ensure the first menu item is immediately under the pointer.
62
- */
63
- open_offset_x?: number;
64
- /**
65
- * The number of pixels to offset from the pointer Y position when opened.
66
- * Useful to ensure the first menu item is immediately under the pointer.
67
- */
68
- open_offset_y?: number;
69
- /**
70
- * Whether to detect tap-then-longpress to bypass the Fuz contextmenu.
71
- * This allows access to the system contextmenu by tapping once then rightclicking/long-pressing.
72
- * Setting to `false` disables the gesture.
73
- */
74
- bypass_with_tap_then_longpress?: boolean;
75
- /**
76
- * The number of milliseconds between taps to detect a gesture that bypasses the Fuz contextmenu.
77
- * Used only when `bypass_with_tap_then_longpress` is true.
78
- * If the duration is too long, it'll detect more false positives and interrupt normal usage,
79
- * but too short and some people will have difficulty performing the gesture.
80
- */
81
- bypass_window?: number;
82
- /**
83
- * The number of pixels the pointer can be moved between taps to detect a tap-then-longpress.
84
- * Used only when `bypass_with_tap_then_longpress` is true.
85
- */
86
- bypass_move_tolerance?: number;
87
- /**
88
- * If `true`, wraps `children` with a div and listens to events on it instead of the window.
89
- */
90
- scoped?: boolean;
91
- /**
92
- * Snippet for rendering link entries.
93
- * Set to `null` to disable automatic link detection.
94
- * Defaults to `link_entry_default` which renders `ContextmenuLinkEntry`.
95
- */
96
- link_entry?: Snippet<[ComponentProps<typeof ContextmenuLinkEntry>]> | null;
97
- /**
98
- * Snippet for rendering copy text entries.
99
- * Set to `null` to disable automatic copy text detection.
100
- * Defaults to `text_entry_default` which renders `ContextmenuTextEntry`.
101
- */
102
- text_entry?: Snippet<[ComponentProps<typeof ContextmenuTextEntry>]> | null;
103
- /**
104
- * Snippet for rendering separator entries.
105
- * Set to `null` to disable automatic separator rendering.
106
- * Defaults to `separator_entry_default` which renders `ContextmenuSeparator`.
107
- */
108
- separator_entry?: Snippet<[ComponentProps<typeof ContextmenuSeparator>]> | null;
109
- children: Snippet;
110
- } = $props();
53
+ }: ContextmenuRootBaseProps = $props();
111
54
 
112
55
  contextmenu_context.set(() => contextmenu);
113
56
 
114
- if (DEV) contextmenu_check_global_root(() => scoped); // TODO @many is this import tree-shaken?
57
+ contextmenu_check_global_root(() => scoped); // DEV-only internally, eliminated from production bundles
115
58
 
59
+ // The menu element while opened, bound from `ContextmenuMenu.svelte`.
116
60
  let el: HTMLElement | undefined = $state.raw();
117
61
 
118
- const {layout} = $derived(contextmenu);
119
-
120
- const dimensions = contextmenu_dimensions_context.set();
121
-
122
- const x = $derived(
123
- contextmenu_calculate_constrained_x(contextmenu.x, dimensions.width, layout.width),
124
- );
125
- const y = $derived(
126
- contextmenu_calculate_constrained_y(contextmenu.y, dimensions.height, layout.height),
127
- );
62
+ const bypass_tracker = new ContextmenuBypassTracker();
63
+ const open_guard = new ContextmenuOpenGuard();
128
64
 
129
- // State for tap-then-longpress bypass detection.
130
- // These values are `undefined` when unused, and `null` after being reset.
131
- let touch_x: number | undefined | null = $state.raw();
132
- let touch_y: number | undefined | null = $state.raw();
133
- let first_tap_time: number | undefined | null = $state.raw();
134
- let longpress_bypass: boolean | undefined = $state.raw();
135
- let tap_tracking_timeout: NodeJS.Timeout | undefined | null = $state.raw();
65
+ const open_options = $derived({
66
+ open_offset_x,
67
+ open_offset_y,
68
+ link_enabled: link_entry !== null,
69
+ text_enabled: text_entry !== null,
70
+ separator_enabled: separator_entry !== null,
71
+ });
136
72
 
137
73
  const on_window_contextmenu = (e: MouseEvent) => {
138
- // Handle the tap-then-longpress bypass gesture
139
- if (longpress_bypass) {
140
- // Clear all tap tracking state after consuming the bypass
141
- longpress_bypass = false;
142
- first_tap_time = null;
143
- touch_x = null;
144
- touch_y = null;
145
- if (tap_tracking_timeout != null) {
146
- clearTimeout(tap_tracking_timeout);
147
- tap_tracking_timeout = null;
148
- }
149
- return;
150
- }
151
- const {target} = e;
152
- if (!contextmenu_is_valid_target(target, e.shiftKey)) {
153
- return;
154
- }
155
- // Don't open contextmenu when clicking on the menu itself
156
- if (el?.contains(target)) {
157
- return;
158
- }
159
- if (
160
- contextmenu_open(target, e.clientX + open_offset_x, e.clientY + open_offset_y, contextmenu, {
161
- link_enabled: link_entry !== null,
162
- text_enabled: text_entry !== null,
163
- separator_enabled: separator_entry !== null,
164
- })
165
- ) {
166
- swallow(e);
167
- }
74
+ // let the system contextmenu through after a tap-then-longpress bypass gesture
75
+ if (bypass_tracker.consume()) return;
76
+ contextmenu_resolve_contextmenu_event(e, contextmenu, el, open_guard, open_options);
168
77
  };
169
78
 
170
79
  /**
171
- * Touch event handler for tap-then-longpress bypass detection.
80
+ * Touch event handler for tap-then-longpress bypass detection
81
+ * and touch-open release guarding.
172
82
  *
173
- * This allows users to access the native context menu by performing a tap
174
- * followed by a longpress/rightclick within a specified time window.
175
- * The bypass gesture is useful for accessing browser features like text selection
176
- * or the native context menu when the Fuz contextmenu would normally override it.
83
+ * The bypass gesture allows users to access the native context menu by performing
84
+ * a tap followed by a longpress/rightclick within a specified time window,
85
+ * useful for browser features like text selection when the Fuz contextmenu
86
+ * would normally override it.
177
87
  *
178
88
  * Note: preventDefault is not called as we're only observing touch patterns,
179
89
  * not intercepting them. The actual bypass happens in on_window_contextmenu.
180
90
  */
181
91
  const touchstart = (e: TouchEvent): void => {
92
+ open_guard.touchstart();
93
+
182
94
  if (!bypass_with_tap_then_longpress) return;
183
95
 
184
96
  const {touches, target} = e;
@@ -187,97 +99,53 @@
187
99
  touches.length !== 1 ||
188
100
  !contextmenu_is_valid_target(target, e.shiftKey)
189
101
  ) {
190
- // Reset all state if conditions aren't met
191
- first_tap_time = null;
192
- touch_x = null;
193
- touch_y = null;
194
- longpress_bypass = false;
195
- if (tap_tracking_timeout != null) {
196
- clearTimeout(tap_tracking_timeout);
197
- tap_tracking_timeout = null;
198
- }
102
+ bypass_tracker.reset();
199
103
  return;
200
104
  }
201
105
 
202
106
  const {clientX, clientY} = touches[0]!;
107
+ bypass_tracker.track(clientX, clientY, bypass_window, bypass_move_tolerance);
108
+ };
203
109
 
204
- // Check if this is a tap-then-longpress gesture
205
- if (
206
- first_tap_time != null &&
207
- performance.now() - first_tap_time < bypass_window &&
208
- Math.hypot(clientX - touch_x!, clientY - touch_y!) < bypass_move_tolerance
209
- ) {
210
- // This is a tap-then-longpress - set bypass flag and clear tap tracking
211
- longpress_bypass = true;
212
- first_tap_time = null;
213
- touch_x = null;
214
- touch_y = null;
215
- if (tap_tracking_timeout != null) {
216
- clearTimeout(tap_tracking_timeout);
217
- tap_tracking_timeout = null;
218
- }
219
- return;
220
- }
221
-
222
- // Record this tap for potential future bypass detection
223
- first_tap_time = performance.now();
224
- touch_x = clientX;
225
- touch_y = clientY;
226
-
227
- // Set timeout to clear stale tap tracking after the detection window expires
228
- if (tap_tracking_timeout != null) {
229
- clearTimeout(tap_tracking_timeout);
230
- }
231
- tap_tracking_timeout = setTimeout(() => {
232
- first_tap_time = null;
233
- touch_x = null;
234
- touch_y = null;
235
- tap_tracking_timeout = null;
236
- }, bypass_window);
110
+ /**
111
+ * Swallows the release of a gesture that opened the menu,
112
+ * stopping the browser from synthesizing mouse events at the touch point
113
+ * (the open offsets place the first menu item there).
114
+ */
115
+ const touchend = (e: TouchEvent): void => {
116
+ open_guard.touchend(e);
117
+ // Clear a pending bypass when the user lifts before the native contextmenu fires -
118
+ // no native menu appeared, so the bypass must not linger to eat a future right-click.
119
+ // Tap tracking is otherwise preserved for tap-then-longpress detection.
120
+ bypass_tracker.consume();
237
121
  };
238
122
 
239
123
  /**
240
124
  * Reset state when touch is cancelled (e.g., when scrolling starts).
241
125
  */
242
126
  const touchcancel = (): void => {
243
- // Reset all bypass detection state
244
- first_tap_time = null;
245
- touch_x = null;
246
- touch_y = null;
247
- longpress_bypass = false;
248
- if (tap_tracking_timeout != null) {
249
- clearTimeout(tap_tracking_timeout);
250
- tap_tracking_timeout = null;
251
- }
252
- };
253
-
254
- // Passive listener that runs during the event's `capture` phase
255
- // so that things like the Dialog don't eat the events and prevent the contextmenu from closing.
256
- const mousedown = (e: MouseEvent) => {
257
- if (el && !el.contains(e.target as any)) {
258
- contextmenu.close();
259
- }
127
+ open_guard.reset();
128
+ bypass_tracker.reset();
260
129
  };
261
-
262
- const keyboard_handlers = $derived(contextmenu_create_keyboard_handlers(contextmenu));
263
- const keydown = $derived(contextmenu_create_keydown_handler(keyboard_handlers));
264
130
  </script>
265
131
 
266
132
  <svelte:window
267
133
  oncontextmenu={scoped ? undefined : on_window_contextmenu}
268
- onmousedown={!contextmenu.opened ? undefined : mousedown}
269
- onkeydown={!contextmenu.opened ? undefined : keydown}
270
- ontouchstartcapture={scoped || !bypass_with_tap_then_longpress ? undefined : touchstart}
271
- ontouchcancelcapture={scoped || !bypass_with_tap_then_longpress ? undefined : touchcancel}
134
+ ontouchstartcapture={scoped ? undefined : touchstart}
135
+ ontouchendcapture={scoped ? undefined : touchend}
136
+ ontouchcancelcapture={scoped ? undefined : touchcancel}
272
137
  />
273
138
 
274
139
  {#if scoped}
140
+ <!-- A transparent (`display: contents`) event-delegation wrapper. `role="group"` rather
141
+ than a landmark role - it satisfies the a11y lint without requiring an accessible name. -->
275
142
  <div
276
143
  class="contextmenu-root"
277
- role="region"
144
+ role="group"
278
145
  oncontextmenu={on_window_contextmenu}
279
- ontouchstartcapture={!bypass_with_tap_then_longpress ? undefined : touchstart}
280
- ontouchcancelcapture={!bypass_with_tap_then_longpress ? undefined : touchcancel}
146
+ ontouchstartcapture={touchstart}
147
+ ontouchendcapture={touchend}
148
+ ontouchcancelcapture={touchcancel}
281
149
  >
282
150
  {@render children()}
283
151
  </div>
@@ -285,80 +153,10 @@
285
153
  {@render children()}
286
154
  {/if}
287
155
 
288
- {#if !contextmenu.has_custom_layout}
289
- <div
290
- class="contextmenu-layout"
291
- bind:clientWidth={layout.width}
292
- bind:clientHeight={layout.height}
293
- aria-hidden="true"
294
- ></div>
295
- {/if}
296
-
297
- <!-- TODO animate the contextmenu as it appears somehow -->
298
- <!-- TODO implement focus management per APG: store `document.activeElement` when opening, focus menu on mount, restore focus on close if element `isConnected` -->
299
- {#if contextmenu.opened}
300
- <ul
301
- class="contextmenu unstyled pane"
302
- role="menu"
303
- aria-label="context menu"
304
- tabindex="-1"
305
- bind:this={el}
306
- bind:offsetWidth={dimensions.width}
307
- bind:offsetHeight={dimensions.height}
308
- style:transform="translate3d({x}px, {y}px, 0)"
309
- >
310
- <!-- TODO maybe this should be generic? -->
311
- {#each contextmenu.params as p (p)}
312
- {#if typeof p === 'function'}
313
- {@render p()}
314
- {:else if p.snippet === 'link'}
315
- {@render link_entry?.(p.props)}
316
- {:else if p.snippet === 'text'}
317
- {@render text_entry?.(p.props)}
318
- {:else if p.snippet === 'separator'}
319
- {@render separator_entry?.(p.props)}
320
- {/if}
321
- {/each}
322
- </ul>
323
- {/if}
324
-
325
- {#snippet link_entry_default(props: ComponentProps<typeof ContextmenuLinkEntry>)}
326
- <ContextmenuLinkEntry {...props} />
327
- {/snippet}
328
-
329
- {#snippet text_entry_default(props: ComponentProps<typeof ContextmenuTextEntry>)}
330
- <ContextmenuTextEntry {...props} />
331
- {/snippet}
332
-
333
- {#snippet separator_entry_default(props: ComponentProps<typeof ContextmenuSeparator>)}
334
- <ContextmenuSeparator {...props} />
335
- {/snippet}
156
+ <ContextmenuMenu bind:el {contextmenu} {open_guard} {link_entry} {text_entry} {separator_entry} />
336
157
 
337
158
  <style>
338
159
  .contextmenu-root {
339
160
  display: contents;
340
161
  }
341
- .contextmenu {
342
- --icon_size: var(--icon_size_xs);
343
- --contextmenu_width: 320px;
344
- position: fixed;
345
- left: 0;
346
- top: 0;
347
- z-index: var(--contextmenu_z_index, 200);
348
- max-width: var(--contextmenu_width);
349
- width: 100%;
350
- }
351
- .contextmenu,
352
- .contextmenu :global(menu.pane) {
353
- border: var(--contextmenu_border_width, var(--border_width))
354
- var(--contextmenu_border_style, var(--border_style))
355
- var(--contextmenu_border_color, var(--border_color));
356
- border-radius: var(--contextmenu_border_radius, var(--border_radius_xs));
357
- }
358
-
359
- .contextmenu-layout {
360
- z-index: -200;
361
- position: fixed;
362
- inset: 0;
363
- }
364
162
  </style>
@@ -1,63 +1,5 @@
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
- contextmenu?: ContextmenuState;
8
- /**
9
- * The number of pixels to offset from the pointer X position when opened.
10
- * Useful to ensure the first menu item is immediately under the pointer.
11
- */
12
- open_offset_x?: number;
13
- /**
14
- * The number of pixels to offset from the pointer Y position when opened.
15
- * Useful to ensure the first menu item is immediately under the pointer.
16
- */
17
- open_offset_y?: number;
18
- /**
19
- * Whether to detect tap-then-longpress to bypass the Fuz contextmenu.
20
- * This allows access to the system contextmenu by tapping once then rightclicking/long-pressing.
21
- * Setting to `false` disables the gesture.
22
- */
23
- bypass_with_tap_then_longpress?: boolean;
24
- /**
25
- * The number of milliseconds between taps to detect a gesture that bypasses the Fuz contextmenu.
26
- * Used only when `bypass_with_tap_then_longpress` is true.
27
- * If the duration is too long, it'll detect more false positives and interrupt normal usage,
28
- * but too short and some people will have difficulty performing the gesture.
29
- */
30
- bypass_window?: number;
31
- /**
32
- * The number of pixels the pointer can be moved between taps to detect a tap-then-longpress.
33
- * Used only when `bypass_with_tap_then_longpress` is true.
34
- */
35
- bypass_move_tolerance?: number;
36
- /**
37
- * If `true`, wraps `children` with a div and listens to events on it instead of the window.
38
- */
39
- scoped?: boolean;
40
- /**
41
- * Snippet for rendering link entries.
42
- * Set to `null` to disable automatic link detection.
43
- * Defaults to `link_entry_default` which renders `ContextmenuLinkEntry`.
44
- */
45
- link_entry?: Snippet<[ComponentProps<typeof ContextmenuLinkEntry>]> | null;
46
- /**
47
- * Snippet for rendering copy text entries.
48
- * Set to `null` to disable automatic copy text detection.
49
- * Defaults to `text_entry_default` which renders `ContextmenuTextEntry`.
50
- */
51
- text_entry?: Snippet<[ComponentProps<typeof ContextmenuTextEntry>]> | null;
52
- /**
53
- * Snippet for rendering separator entries.
54
- * Set to `null` to disable automatic separator rendering.
55
- * Defaults to `separator_entry_default` which renders `ContextmenuSeparator`.
56
- */
57
- separator_entry?: Snippet<[ComponentProps<typeof ContextmenuSeparator>]> | null;
58
- children: Snippet;
59
- };
60
- declare const ContextmenuRoot: import("svelte").Component<$$ComponentProps, {}, "">;
1
+ import { type ContextmenuRootBaseProps } from './contextmenu_helpers.js';
2
+ declare const ContextmenuRoot: import("svelte").Component<ContextmenuRootBaseProps, {}, "">;
61
3
  type ContextmenuRoot = ReturnType<typeof ContextmenuRoot>;
62
4
  export default ContextmenuRoot;
63
5
  //# sourceMappingURL=ContextmenuRoot.svelte.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ContextmenuRoot.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/ContextmenuRoot.svelte"],"names":[],"mappings":"AAsBA,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;AAahE,KAAK,gBAAgB,GAAI;IACxB,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;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;;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;AAsOH,QAAA,MAAM,eAAe,sDAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"ContextmenuRoot.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/ContextmenuRoot.svelte"],"names":[],"mappings":"AA8BA,OAAO,EASL,KAAK,wBAAwB,EAC7B,MAAM,0BAA0B,CAAC;AAoHnC,QAAA,MAAM,eAAe,8DAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}