@alliance-droid/status-feedback-system 1.3.6 → 1.3.8

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.
@@ -70,13 +70,46 @@
70
70
 
71
71
  let formOpen = $state(false);
72
72
  let formPopoverEl: HTMLDivElement | undefined = $state();
73
+ let formOriginalParent: HTMLElement | null = null;
74
+ let formPortalTarget: HTMLElement | null = null;
75
+
76
+ /**
77
+ * Find the topmost open modal dialog.
78
+ * showModal() makes everything outside the dialog inert — including
79
+ * top-layer popovers that aren't descendants of the dialog.
80
+ */
81
+ function findOpenModalDialog(): HTMLDialogElement | null {
82
+ const dialogs = document.querySelectorAll<HTMLDialogElement>('dialog[open]');
83
+ for (const d of dialogs) {
84
+ if (d.open) return d;
85
+ }
86
+ return null;
87
+ }
73
88
 
74
89
  // Promote form modal to the browser's top layer when opened
90
+ // Portal into any open modal dialog to escape inertness
75
91
  $effect(() => {
76
- if (formOpen && formPopoverEl && typeof formPopoverEl.showPopover === 'function') {
77
- try { formPopoverEl.showPopover(); } catch { /* fallback to z-index */ }
78
- } else if (!formOpen && formPopoverEl && typeof formPopoverEl.hidePopover === 'function') {
79
- try { formPopoverEl.hidePopover(); } catch { /* already hidden */ }
92
+ if (formOpen && formPopoverEl) {
93
+ // Portal into modal dialog if one is open
94
+ const modalDialog = findOpenModalDialog();
95
+ if (modalDialog && formPopoverEl.parentElement !== modalDialog) {
96
+ formOriginalParent = formPopoverEl.parentElement;
97
+ formPortalTarget = modalDialog;
98
+ modalDialog.appendChild(formPopoverEl);
99
+ }
100
+
101
+ if (typeof formPopoverEl.showPopover === 'function') {
102
+ try { formPopoverEl.showPopover(); } catch { /* fallback to z-index */ }
103
+ }
104
+ } else if (!formOpen && formPopoverEl) {
105
+ if (typeof formPopoverEl.hidePopover === 'function') {
106
+ try { formPopoverEl.hidePopover(); } catch { /* already hidden */ }
107
+ }
108
+ // Move back to original parent
109
+ if (formPortalTarget && formOriginalParent) {
110
+ try { formOriginalParent.appendChild(formPopoverEl); } catch { /* already moved */ }
111
+ formPortalTarget = null;
112
+ }
80
113
  }
81
114
  });
82
115
  let formType = $state<'bug' | 'feature'>('bug');
@@ -6,14 +6,16 @@
6
6
  board: FeedbackBoardState;
7
7
  /** Available category options for the dropdown */
8
8
  categories?: string[];
9
+ /** Pre-fill the email field (e.g. from the logged-in user's session) */
10
+ userEmail?: string;
9
11
  }
10
12
 
11
- let { board, categories = [] }: Props = $props();
13
+ let { board, categories = [], userEmail }: Props = $props();
12
14
 
13
15
  let title = $state('');
14
16
  let message = $state('');
15
17
  let category = $state('');
16
- let email = $state('');
18
+ let email = $state(userEmail ?? '');
17
19
  let submitting = $state(false);
18
20
  let success = $state(false);
19
21
  let error = $state('');
@@ -3,6 +3,8 @@ interface Props {
3
3
  board: FeedbackBoardState;
4
4
  /** Available category options for the dropdown */
5
5
  categories?: string[];
6
+ /** Pre-fill the email field (e.g. from the logged-in user's session) */
7
+ userEmail?: string;
6
8
  }
7
9
  declare const FeedbackSubmitForm: import("svelte").Component<Props, {}, "">;
8
10
  type FeedbackSubmitForm = ReturnType<typeof FeedbackSubmitForm>;
@@ -8,18 +8,53 @@
8
8
  onViewBoard?: () => void;
9
9
  }
10
10
 
11
- import { onMount } from 'svelte';
11
+ import { onMount, onDestroy } from 'svelte';
12
12
 
13
13
  let { x, y, boardUrl, onReportBug, onRequestFeature, onViewBoard }: Props = $props();
14
14
 
15
15
  let popoverEl: HTMLDivElement | undefined = $state();
16
+ let originalParent: HTMLElement | null = null;
17
+ let portalTarget: HTMLElement | null = null;
16
18
 
17
19
  let adjustedX = $derived(Math.min(x, (typeof window !== 'undefined' ? window.innerWidth : 800) - 200));
18
20
  let adjustedY = $derived(Math.min(y, (typeof window !== 'undefined' ? window.innerHeight : 600) - 160));
19
21
 
20
- // Show the popover on mount to promote it to the top layer
22
+ /**
23
+ * Find the topmost open modal dialog.
24
+ * showModal() makes everything outside the dialog inert — including
25
+ * top-layer popovers that aren't descendants of the dialog.
26
+ * By portalling into the dialog, our menu becomes a descendant and
27
+ * escapes the inertness.
28
+ */
29
+ function findOpenModalDialog(): HTMLDialogElement | null {
30
+ const dialogs = document.querySelectorAll<HTMLDialogElement>('dialog[open]');
31
+ // showModal() sets the `modal` property — filter for true modals
32
+ for (const d of dialogs) {
33
+ // A dialog opened with showModal() will have a non-null ::backdrop
34
+ // and will be in the top layer. We can detect this via the "open"
35
+ // attribute + checking if it has modal behavior (no direct API,
36
+ // but modal dialogs block interaction with the rest of the page).
37
+ // The simplest reliable check: modal dialogs have returnValue capability
38
+ // and are in the top layer. In practice, all open dialogs we care about
39
+ // are modal since our Modal component always uses showModal().
40
+ if (d.open) return d;
41
+ }
42
+ return null;
43
+ }
44
+
21
45
  onMount(() => {
22
- if (popoverEl && typeof popoverEl.showPopover === 'function') {
46
+ if (!popoverEl) return;
47
+
48
+ // If a modal dialog is open, portal into it to escape inertness
49
+ const modalDialog = findOpenModalDialog();
50
+ if (modalDialog) {
51
+ originalParent = popoverEl.parentElement;
52
+ portalTarget = modalDialog;
53
+ modalDialog.appendChild(popoverEl);
54
+ }
55
+
56
+ // Show the popover to promote it to the top layer
57
+ if (typeof popoverEl.showPopover === 'function') {
23
58
  try {
24
59
  popoverEl.showPopover();
25
60
  } catch {
@@ -28,6 +63,20 @@
28
63
  }
29
64
  });
30
65
 
66
+ onDestroy(() => {
67
+ // Move the element back before Svelte tries to clean up
68
+ if (popoverEl && portalTarget && originalParent) {
69
+ try {
70
+ if (typeof popoverEl.hidePopover === 'function') {
71
+ popoverEl.hidePopover();
72
+ }
73
+ originalParent.appendChild(popoverEl);
74
+ } catch {
75
+ // Element may already be removed
76
+ }
77
+ }
78
+ });
79
+
31
80
  function handleKeydown(e: KeyboardEvent) {
32
81
  const menu = (e.currentTarget as HTMLElement);
33
82
  const items = menu.querySelectorAll<HTMLElement>('[role="menuitem"]');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alliance-droid/status-feedback-system",
3
- "version": "1.3.6",
3
+ "version": "1.3.8",
4
4
  "type": "module",
5
5
  "description": "Drop-in system status pages and user feedback forms for SvelteKit apps",
6
6
  "svelte": "./dist/index.js",