|
1 | 1 | <script lang="ts"> |
2 | 2 | import { onMount, onDestroy, type Snippet } from 'svelte'; |
3 | 3 | import { BROWSER } from 'esm-env'; |
| 4 | + import { page } from '$app/state'; |
| 5 | + import { goto, pushState } from '$app/navigation'; |
4 | 6 | import CodeMirror, { |
5 | 7 | type ChangeCodeMirrorEvent, |
6 | 8 | type SelectCodeMirrorEvent, |
|
35 | 37 | getCellId: (rowIndex: number, cellIndex: number) => string | undefined; |
36 | 38 | isFocusedCell: (rowIndex: number, cellIndex: number) => boolean; |
37 | 39 | isFocusedRow: (rowIndex: number) => boolean; |
| 40 | + gotoAfterCollapse: (url: string | URL) => void; |
38 | 41 | }; |
39 | 42 |
|
40 | 43 | interface Props { |
|
69 | 72 | onclickSubmit: (event: MouseEvent) => void; |
70 | 73 | onclickClear: (event: MouseEvent) => void; |
71 | 74 | onclickClose: (event: MouseEvent) => void; |
| 75 | + gotoAfterCollapse?: (url: string | URL) => void; |
72 | 76 | } |
73 | 77 | ] |
74 | 78 | >; |
|
80 | 84 | getCellId: (cellIndex: number) => string; |
81 | 85 | isFocusedCell: (cellIndex: number) => boolean; |
82 | 86 | rowIndex: number; |
| 87 | + gotoAfterCollapse?: (url: string | URL) => void; |
83 | 88 | } |
84 | 89 | ] |
85 | 90 | >; |
|
89 | 94 | defaultResultCol?: number; |
90 | 95 | toggleWithKeyboardShortcut?: boolean; |
91 | 96 | wrappingArrowKeyNavigation?: boolean; |
| 97 | + shallowRouting?: boolean; |
92 | 98 | debouncedWait?: number; |
93 | 99 | getDebouncedWait?: DebouncedWaitFunction; |
94 | 100 | selection?: Selection; |
|
128 | 134 | loadingIndicator, |
129 | 135 | toggleWithKeyboardShortcut = false, |
130 | 136 | wrappingArrowKeyNavigation = false, |
| 137 | + shallowRouting = false, |
131 | 138 | defaultInputCol = -1, |
132 | 139 | defaultResultRow = 0, |
133 | 140 | defaultResultCol = 0, |
|
150 | 157 | let expanded = $state(false); |
151 | 158 | let activeRowIndex: number = $state(0); |
152 | 159 | let activeColIndex: number = $state(-1); |
| 160 | + let gotoAfterCollapseUrl: string | URL | undefined = $state(); |
153 | 161 | let prevValue: string = value; |
154 | 162 |
|
155 | 163 | let allowArrowKeyCursorHandling: { vertical: boolean; horizontal: boolean } = $state({ |
|
362 | 370 | }); |
363 | 371 | } |
364 | 372 |
|
| 373 | + export async function submit(form: HTMLFormElement) { |
| 374 | + hideExpandedSearch(); |
| 375 | + const formData = new FormData(form); |
| 376 | + const formParams = new URLSearchParams(formData as unknown as Record<string, string>); |
| 377 | + const actionUrl = new URL(form.action); |
| 378 | + gotoAfterCollapse(`${actionUrl.href}?${formParams.toString()}`); |
| 379 | + } |
| 380 | +
|
| 381 | + async function gotoAfterCollapse(url: string | URL) { |
| 382 | + gotoAfterCollapseUrl = url; |
| 383 | + history.back(); |
| 384 | + } |
| 385 | +
|
| 386 | + async function handlePopState(event: PopStateEvent) { |
| 387 | + if (dialog?.open) { |
| 388 | + hideExpandedSearch(); |
| 389 | + } |
| 390 | + if (gotoAfterCollapseUrl) { |
| 391 | + const url = gotoAfterCollapseUrl; |
| 392 | + gotoAfterCollapseUrl = undefined; |
| 393 | + await goto(url); |
| 394 | + collapsedEditorView?.focus(); |
| 395 | + } else if ( |
| 396 | + page.state.expandedSuperSearch && |
| 397 | + event.state['sveltekit:states']?.expandedSuperSearch // a little bit hacky way to ensure the dialog doesn't flicker when navigating forward |
| 398 | + ) { |
| 399 | + showExpandedSearch({ focusRow: 0, preventPushState: true }); |
| 400 | + } |
| 401 | + } |
| 402 | +
|
365 | 403 | export function showExpandedSearch(options?: ShowExpandedSearchOptions) { |
366 | 404 | if (!expanded) { |
367 | 405 | expandedEditorView?.dispatch({ |
|
373 | 411 | dialog?.showModal(); |
374 | 412 | expanded = true; |
375 | 413 | onexpand?.({ windowPageYOffset: window.pageYOffset }); |
| 414 | + if (shallowRouting) { |
| 415 | + if (!options?.preventPushState) { |
| 416 | + pushState('', { ...page.state, expandedSuperSearch: true }); |
| 417 | + } |
| 418 | + } |
376 | 419 | } |
377 | 420 | setDefaultRowAndCols({ focusRow: options?.focusRow }); |
378 | 421 | if (!options?.focusRow || options.focusRow < 1) { |
|
420 | 463 | : collapsedEditorView?.dom?.closest('form'); |
421 | 464 |
|
422 | 465 | if (formElement && formElement instanceof HTMLFormElement) { |
| 466 | + if (!shallowRouting) { |
| 467 | + hideExpandedSearch(); |
| 468 | + } |
423 | 469 | formElement.requestSubmit(); |
424 | | - hideExpandedSearch(); |
425 | 470 | } |
426 | 471 | } |
427 | 472 |
|
|
445 | 490 |
|
446 | 491 | function handleExpandedKeyDown(event: KeyboardEvent) { |
447 | 492 | if (event.key === 'Escape') { |
448 | | - hideExpandedSearch(); |
| 493 | + if (shallowRouting) { |
| 494 | + history.back(); |
| 495 | + } else { |
| 496 | + hideExpandedSearch(); |
| 497 | + } |
449 | 498 | } |
450 | 499 |
|
451 | 500 | if (event.key === 'Enter') { |
|
689 | 738 |
|
690 | 739 | function handleClickOutsideDialog(event: MouseEvent) { |
691 | 740 | if (event.target === dialog || event.target === event.currentTarget) { |
692 | | - hideExpandedSearch(); |
| 741 | + if (shallowRouting) { |
| 742 | + history.back(); |
| 743 | + } else { |
| 744 | + hideExpandedSearch(); |
| 745 | + } |
693 | 746 | } |
694 | 747 | } |
695 | 748 |
|
|
713 | 766 | // } |
714 | 767 | } |
715 | 768 |
|
| 769 | + function interceptExpandedLinks(event: MouseEvent) { |
| 770 | + const target = event.target as HTMLElement; |
| 771 | + const linkElement = target.tagName === 'A' ? target : target.closest('a'); |
| 772 | +
|
| 773 | + if (linkElement) { |
| 774 | + const href = linkElement.getAttribute('href'); |
| 775 | + if (href) { |
| 776 | + event.preventDefault(); |
| 777 | + gotoAfterCollapse(href); |
| 778 | + } |
| 779 | + } |
| 780 | + } |
| 781 | +
|
716 | 782 | function handleReset() { |
717 | 783 | collapsedEditorView?.dispatch({ |
718 | 784 | changes: { from: 0, to: value.length, insert: '' }, |
|
800 | 866 | collapsedEditorView.focus(); |
801 | 867 | } |
802 | 868 | }); |
| 869 | +
|
| 870 | + const handleClickClose = $derived(() => { |
| 871 | + if (shallowRouting) { |
| 872 | + return history.back(); |
| 873 | + } else { |
| 874 | + hideExpandedSearch(); |
| 875 | + } |
| 876 | + }); |
803 | 877 | </script> |
804 | 878 |
|
805 | 879 | {#snippet fallbackExpandedContent({ search }: { search: ReturnType<typeof useSearchRequest> })} |
|
863 | 937 | /> |
864 | 938 | {/snippet} |
865 | 939 |
|
| 940 | +<svelte:window onpopstate={handlePopState} /> |
866 | 941 | <div role="presentation" onkeydown={handleCollapsedKeyDown} {id}> |
867 | 942 | <div class="supersearch-combobox"> |
868 | 943 | {@render inputRow?.({ |
|
873 | 948 | isFocusedRow: () => activeRowIndex === -1, |
874 | 949 | onclickSubmit: handleClickSubmit, |
875 | 950 | onclickClear: handleReset, |
876 | | - onclickClose: hideExpandedSearch |
| 951 | + onclickClose: handleClickClose |
877 | 952 | })} |
878 | 953 | <textarea {value} {name} {form} hidden readonly></textarea> |
879 | 954 | </div> |
|
882 | 957 | class="supersearch-dialog" |
883 | 958 | id={`${id}-dialog`} |
884 | 959 | bind:this={dialog} |
885 | | - closedby="any" |
| 960 | + closedby="none" |
886 | 961 | tabindex="-1" |
887 | 962 | onclose={() => hideExpandedSearch()} |
888 | 963 | > |
|
893 | 968 | onclick={handleClickOutsideDialog} |
894 | 969 | > |
895 | 970 | <div class="supersearch-dialog-content"> |
896 | | - <div class="supersearch-combobox" bind:this={comboboxElement}> |
| 971 | + <!-- svelte-ignore a11y_click_events_have_key_events --> |
| 972 | + <!-- svelte-ignore a11y_no_static_element_interactions --> |
| 973 | + <div |
| 974 | + class="supersearch-combobox" |
| 975 | + bind:this={comboboxElement} |
| 976 | + onclick={interceptExpandedLinks} |
| 977 | + > |
897 | 978 | {@render inputRow?.({ |
898 | 979 | expanded: true, |
899 | 980 | inputField: expandedInputSnippet, |
|
902 | 983 | isFocusedRow: () => activeRowIndex === 0, |
903 | 984 | onclickSubmit: handleClickSubmit, |
904 | 985 | onclickClear: handleReset, |
905 | | - onclickClose: hideExpandedSearch |
| 986 | + onclickClose: handleClickClose, |
| 987 | + gotoAfterCollapse |
906 | 988 | })} |
907 | 989 | </div> |
908 | | - <div id={`${id}-grid`} role="grid"> |
| 990 | + <!-- svelte-ignore a11y_click_events_have_key_events --> |
| 991 | + <!-- svelte-ignore a11y_interactive_supports_focus --> |
| 992 | + <div id={`${id}-grid`} role="grid" onclick={interceptExpandedLinks}> |
909 | 993 | {@render expandedContent({ |
910 | 994 | search, |
911 | 995 | resultsSnippet, |
912 | 996 | resultsCount: resultItemRows?.length, |
913 | 997 | getCellId: (rowIndex: number, colIndex: number) => `${id}-item-${rowIndex}x${colIndex}`, |
914 | 998 | isFocusedCell: (rowIndex: number, colIndex: number) => |
915 | 999 | rowIndex === activeRowIndex && colIndex === activeColIndex, |
916 | | - isFocusedRow: (rowIndex: number) => rowIndex === activeRowIndex |
| 1000 | + isFocusedRow: (rowIndex: number) => rowIndex === activeRowIndex, |
| 1001 | + gotoAfterCollapse |
917 | 1002 | })} |
918 | 1003 | </div> |
919 | 1004 | </div> |
|
0 commit comments