@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.
- package/dist/ApiIndex.svelte +6 -3
- package/dist/ApiIndex.svelte.d.ts +1 -1
- package/dist/ApiIndex.svelte.d.ts.map +1 -1
- package/dist/ApiModule.svelte +72 -21
- package/dist/ApiModule.svelte.d.ts +1 -1
- package/dist/ApiModule.svelte.d.ts.map +1 -1
- package/dist/ContextmenuEntry.svelte +15 -16
- package/dist/ContextmenuEntry.svelte.d.ts +3 -2
- package/dist/ContextmenuEntry.svelte.d.ts.map +1 -1
- package/dist/ContextmenuIcon.svelte +30 -0
- package/dist/ContextmenuIcon.svelte.d.ts +9 -0
- package/dist/ContextmenuIcon.svelte.d.ts.map +1 -0
- package/dist/ContextmenuLinkEntry.svelte +23 -12
- package/dist/ContextmenuLinkEntry.svelte.d.ts +2 -1
- package/dist/ContextmenuLinkEntry.svelte.d.ts.map +1 -1
- package/dist/ContextmenuMenu.svelte +207 -0
- package/dist/ContextmenuMenu.svelte.d.ts +41 -0
- package/dist/ContextmenuMenu.svelte.d.ts.map +1 -0
- package/dist/ContextmenuRoot.svelte +58 -260
- package/dist/ContextmenuRoot.svelte.d.ts +2 -60
- package/dist/ContextmenuRoot.svelte.d.ts.map +1 -1
- package/dist/ContextmenuRootForSafariCompatibility.svelte +72 -283
- package/dist/ContextmenuRootForSafariCompatibility.svelte.d.ts +2 -58
- package/dist/ContextmenuRootForSafariCompatibility.svelte.d.ts.map +1 -1
- package/dist/ContextmenuSeparator.svelte +2 -1
- package/dist/ContextmenuSeparator.svelte.d.ts.map +1 -1
- package/dist/ContextmenuSubmenu.svelte +31 -25
- package/dist/ContextmenuSubmenu.svelte.d.ts +3 -2
- package/dist/ContextmenuSubmenu.svelte.d.ts.map +1 -1
- package/dist/ContextmenuTextEntry.svelte +6 -4
- package/dist/ContextmenuTextEntry.svelte.d.ts +3 -1
- package/dist/ContextmenuTextEntry.svelte.d.ts.map +1 -1
- package/dist/DeclarationLink.svelte +15 -2
- package/dist/DeclarationLink.svelte.d.ts +7 -0
- package/dist/DeclarationLink.svelte.d.ts.map +1 -1
- package/dist/DocsLink.svelte +3 -1
- package/dist/DocsLink.svelte.d.ts.map +1 -1
- package/dist/DocsTertiaryNav.svelte +2 -1
- package/dist/DocsTertiaryNav.svelte.d.ts.map +1 -1
- package/dist/LibraryDetail.svelte +9 -3
- package/dist/LibraryDetail.svelte.d.ts +1 -1
- package/dist/LibraryDetail.svelte.d.ts.map +1 -1
- package/dist/ModuleLink.svelte +2 -1
- package/dist/ModuleLink.svelte.d.ts.map +1 -1
- package/dist/TypeLink.svelte +2 -1
- package/dist/TypeLink.svelte.d.ts.map +1 -1
- package/dist/contextmenu_helpers.d.ts +324 -1
- package/dist/contextmenu_helpers.d.ts.map +1 -1
- package/dist/contextmenu_helpers.js +403 -2
- package/dist/contextmenu_state.svelte.d.ts +61 -15
- package/dist/contextmenu_state.svelte.d.ts.map +1 -1
- package/dist/contextmenu_state.svelte.js +206 -119
- package/dist/declaration.svelte.d.ts +1 -0
- package/dist/declaration.svelte.d.ts.map +1 -1
- package/dist/declaration.svelte.js +2 -1
- package/dist/icons.d.ts +15 -3
- package/dist/icons.d.ts.map +1 -1
- package/dist/icons.js +17 -3
- package/dist/library.svelte.d.ts +30 -3
- package/dist/library.svelte.d.ts.map +1 -1
- package/dist/library.svelte.js +38 -0
- package/dist/module.svelte.d.ts +62 -1
- package/dist/module.svelte.d.ts.map +1 -1
- package/dist/module.svelte.js +71 -1
- package/package.json +2 -2
- package/src/lib/contextmenu_helpers.ts +546 -3
- package/src/lib/contextmenu_state.svelte.ts +219 -119
- package/src/lib/declaration.svelte.ts +2 -1
- package/src/lib/icons.ts +19 -3
- package/src/lib/library.svelte.ts +43 -1
- package/src/lib/module.svelte.ts +97 -2
package/dist/ApiIndex.svelte
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import {set_library_context_with_fallback, type Library} from './library.svelte.js';
|
|
3
3
|
import {tome_get_by_slug, type Tome} from './tome.js';
|
|
4
4
|
import TomeContent from './TomeContent.svelte';
|
|
5
5
|
import TomeLink from './TomeLink.svelte';
|
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
import {create_api_search} from './api_search.svelte.js';
|
|
10
10
|
|
|
11
11
|
const {
|
|
12
|
-
library
|
|
12
|
+
library: library_prop,
|
|
13
13
|
tome = tome_get_by_slug('api'),
|
|
14
14
|
minimal = false,
|
|
15
15
|
}: {
|
|
16
16
|
/**
|
|
17
17
|
* The library instance to render API docs for.
|
|
18
|
-
* Defaults to getting from library_context
|
|
18
|
+
* Defaults to getting from `library_context`.
|
|
19
19
|
*/
|
|
20
20
|
library?: Library;
|
|
21
21
|
/**
|
|
@@ -30,6 +30,9 @@
|
|
|
30
30
|
minimal?: boolean;
|
|
31
31
|
} = $props();
|
|
32
32
|
|
|
33
|
+
const get_library = set_library_context_with_fallback(() => library_prop, 'ApiIndex');
|
|
34
|
+
const library = $derived(get_library());
|
|
35
|
+
|
|
33
36
|
const search = $derived(create_api_search(library));
|
|
34
37
|
</script>
|
|
35
38
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiIndex.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/ApiIndex.svelte"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"ApiIndex.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/ApiIndex.svelte"],"names":[],"mappings":"AAGA,OAAO,EAAoC,KAAK,OAAO,EAAC,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAAmB,KAAK,IAAI,EAAC,MAAM,WAAW,CAAC;AAQrD,KAAK,gBAAgB,GAAI;IACxB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAmDH,QAAA,MAAM,QAAQ,sDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|
package/dist/ApiModule.svelte
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import {set_library_context_with_fallback, type Library} from './library.svelte.js';
|
|
3
3
|
import {tome_get_by_slug, type Tome} from './tome.js';
|
|
4
4
|
import TomeContent from './TomeContent.svelte';
|
|
5
5
|
import TomeSection from './TomeSection.svelte';
|
|
6
6
|
import TomeSectionHeader from './TomeSectionHeader.svelte';
|
|
7
7
|
import DocsSearch from './DocsSearch.svelte';
|
|
8
8
|
import ModuleLink from './ModuleLink.svelte';
|
|
9
|
+
import DeclarationLink from './DeclarationLink.svelte';
|
|
9
10
|
import Mdz from './Mdz.svelte';
|
|
10
11
|
import ApiDeclarationList from './ApiDeclarationList.svelte';
|
|
11
12
|
import {create_module_declaration_search} from './api_search.svelte.js';
|
|
12
13
|
|
|
13
14
|
const {
|
|
14
15
|
module_path: module_path_param,
|
|
15
|
-
library
|
|
16
|
+
library: library_prop,
|
|
16
17
|
tome = tome_get_by_slug('api'),
|
|
17
18
|
}: {
|
|
18
19
|
/**
|
|
@@ -21,7 +22,7 @@
|
|
|
21
22
|
module_path: string | Array<string>;
|
|
22
23
|
/**
|
|
23
24
|
* The library instance to render API docs for.
|
|
24
|
-
* Defaults to getting from library_context
|
|
25
|
+
* Defaults to getting from `library_context`.
|
|
25
26
|
*/
|
|
26
27
|
library?: Library;
|
|
27
28
|
/**
|
|
@@ -31,6 +32,9 @@
|
|
|
31
32
|
tome?: Tome;
|
|
32
33
|
} = $props();
|
|
33
34
|
|
|
35
|
+
const get_library = set_library_context_with_fallback(() => library_prop, 'ApiModule');
|
|
36
|
+
const library = $derived(get_library());
|
|
37
|
+
|
|
34
38
|
// normalize module_path to string (could be array from [...module_path] route param)
|
|
35
39
|
const module_path = $derived(
|
|
36
40
|
Array.isArray(module_path_param) ? module_path_param.join('/') : module_path_param,
|
|
@@ -89,30 +93,77 @@
|
|
|
89
93
|
<Mdz content={module.module_comment} />
|
|
90
94
|
</section>
|
|
91
95
|
{/if}
|
|
96
|
+
{#if source_url}
|
|
97
|
+
<section>
|
|
98
|
+
<!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
|
|
99
|
+
<a href={source_url} class="chip" target="_blank" rel="noopener">view source</a>
|
|
100
|
+
</section>
|
|
101
|
+
{/if}
|
|
92
102
|
<!-- Declarations section -->
|
|
93
|
-
|
|
94
|
-
<
|
|
103
|
+
{#if module.has_declarations}
|
|
104
|
+
<TomeSection>
|
|
105
|
+
<TomeSectionHeader text="Declarations" />
|
|
95
106
|
|
|
96
|
-
<section>
|
|
97
107
|
{#if search.all.length > 1}
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
108
|
+
<section>
|
|
109
|
+
<DocsSearch
|
|
110
|
+
placeholder="search declarations in this module..."
|
|
111
|
+
declaration_count={search.all.length}
|
|
112
|
+
filtered_declaration_count={search.query.trim() ? search.filtered.length : undefined}
|
|
113
|
+
bind:search_query={search.query}
|
|
114
|
+
/>
|
|
115
|
+
</section>
|
|
104
116
|
{/if}
|
|
105
117
|
|
|
106
|
-
{
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
<a href={source_url} class="chip" target="_blank" rel="noopener">view source</a>
|
|
110
|
-
</p>
|
|
111
|
-
{/if}
|
|
112
|
-
</section>
|
|
118
|
+
<ApiDeclarationList declarations={search.filtered} search_query={search.query} />
|
|
119
|
+
</TomeSection>
|
|
120
|
+
{/if}
|
|
113
121
|
|
|
114
|
-
|
|
115
|
-
|
|
122
|
+
<!-- Re-exports section -->
|
|
123
|
+
{#if module.has_any_re_exports}
|
|
124
|
+
<TomeSection>
|
|
125
|
+
<TomeSectionHeader text="Re-exports" />
|
|
126
|
+
<ul class="unstyled">
|
|
127
|
+
{#each module.star_exports as star_path (star_path)}
|
|
128
|
+
<li class="mb_lg">
|
|
129
|
+
<p class="mb_sm">everything from <ModuleLink module_path={star_path} /></p>
|
|
130
|
+
</li>
|
|
131
|
+
{/each}
|
|
132
|
+
{#each module.external_star_exports as specifier (specifier)}
|
|
133
|
+
<li class="mb_lg">
|
|
134
|
+
<p class="mb_sm">everything from <code>{specifier}</code></p>
|
|
135
|
+
</li>
|
|
136
|
+
{/each}
|
|
137
|
+
{#each module.re_exports_by_module as [from_path, entries] (from_path)}
|
|
138
|
+
<li class="mb_lg">
|
|
139
|
+
<p class="mb_sm">from <ModuleLink module_path={from_path} /></p>
|
|
140
|
+
<div class="row gap_md flex-wrap:wrap">
|
|
141
|
+
{#each entries as entry (entry.name)}
|
|
142
|
+
<DeclarationLink name={entry.name} module_path={from_path}>
|
|
143
|
+
{#if entry.typeOnly}<small>type</small>
|
|
144
|
+
{/if}{entry.name}
|
|
145
|
+
</DeclarationLink>
|
|
146
|
+
{/each}
|
|
147
|
+
</div>
|
|
148
|
+
</li>
|
|
149
|
+
{/each}
|
|
150
|
+
{#each module.external_re_exports_by_specifier as [specifier, entries] (specifier)}
|
|
151
|
+
<li class="mb_lg">
|
|
152
|
+
<p class="mb_sm">from <code>{specifier}</code></p>
|
|
153
|
+
<div class="row gap_md flex-wrap:wrap">
|
|
154
|
+
{#each entries as entry (entry.name)}
|
|
155
|
+
<span class="chip">
|
|
156
|
+
{#if entry.typeOnly}<small>type</small>
|
|
157
|
+
{/if}{#if entry.originalName}<small>{entry.originalName} as</small>
|
|
158
|
+
{/if}{entry.name}
|
|
159
|
+
</span>
|
|
160
|
+
{/each}
|
|
161
|
+
</div>
|
|
162
|
+
</li>
|
|
163
|
+
{/each}
|
|
164
|
+
</ul>
|
|
165
|
+
</TomeSection>
|
|
166
|
+
{/if}
|
|
116
167
|
|
|
117
168
|
<!-- Depends on section -->
|
|
118
169
|
{#if module.has_dependencies}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiModule.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/ApiModule.svelte"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"ApiModule.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/ApiModule.svelte"],"names":[],"mappings":"AAGA,OAAO,EAAoC,KAAK,OAAO,EAAC,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAAmB,KAAK,IAAI,EAAC,MAAM,WAAW,CAAC;AAWrD,KAAK,gBAAgB,GAAI;IACxB;;OAEG;IACH,WAAW,EAAE,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IACpC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,IAAI,CAAC,EAAE,IAAI,CAAC;CACZ,CAAC;AAoLH,QAAA,MAAM,SAAS,sDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type
|
|
2
|
+
import {onDestroy, type Snippet} from 'svelte';
|
|
3
3
|
import {swallow} from '@fuzdev/fuz_util/dom.js';
|
|
4
4
|
|
|
5
5
|
import PendingAnimation from './PendingAnimation.svelte';
|
|
6
6
|
import {contextmenu_context, type ContextmenuRun} from './contextmenu_state.svelte.js';
|
|
7
|
+
import type {SvgData} from './svg.js';
|
|
8
|
+
import ContextmenuIcon from './ContextmenuIcon.svelte';
|
|
7
9
|
|
|
8
10
|
const {
|
|
9
11
|
run,
|
|
@@ -12,7 +14,7 @@
|
|
|
12
14
|
disabled: disabled_prop = false,
|
|
13
15
|
}: {
|
|
14
16
|
run: ContextmenuRun;
|
|
15
|
-
icon?: string | Snippet;
|
|
17
|
+
icon?: SvgData | string | Snippet;
|
|
16
18
|
children: Snippet;
|
|
17
19
|
disabled?: boolean;
|
|
18
20
|
} = $props();
|
|
@@ -28,6 +30,11 @@
|
|
|
28
30
|
|
|
29
31
|
const {selected, pending, error_message} = $derived(entry);
|
|
30
32
|
const disabled = $derived(entry.disabled());
|
|
33
|
+
|
|
34
|
+
let activate_timeout: NodeJS.Timeout | null = null;
|
|
35
|
+
onDestroy(() => {
|
|
36
|
+
if (activate_timeout !== null) clearTimeout(activate_timeout);
|
|
37
|
+
});
|
|
31
38
|
</script>
|
|
32
39
|
|
|
33
40
|
<!-- disabling the a11y warning because a parent element handles keyboard events -->
|
|
@@ -37,7 +44,6 @@
|
|
|
37
44
|
class:selected
|
|
38
45
|
class:disabled
|
|
39
46
|
role="menuitem"
|
|
40
|
-
aria-label="contextmenu entry"
|
|
41
47
|
aria-disabled={disabled}
|
|
42
48
|
tabindex="-1"
|
|
43
49
|
title={error_message ? `Error: ${error_message}` : undefined}
|
|
@@ -46,7 +52,11 @@
|
|
|
46
52
|
: () => {
|
|
47
53
|
// This timeout lets event handlers react to the current DOM
|
|
48
54
|
// before the item's changes are applied.
|
|
49
|
-
|
|
55
|
+
if (activate_timeout !== null) clearTimeout(activate_timeout);
|
|
56
|
+
activate_timeout = setTimeout(() => {
|
|
57
|
+
activate_timeout = null;
|
|
58
|
+
void contextmenu.activate(entry);
|
|
59
|
+
});
|
|
50
60
|
}}
|
|
51
61
|
onmouseenter={disabled
|
|
52
62
|
? undefined
|
|
@@ -57,20 +67,9 @@
|
|
|
57
67
|
>
|
|
58
68
|
<div class="content">
|
|
59
69
|
<div class="icon">
|
|
60
|
-
{
|
|
61
|
-
{icon}
|
|
62
|
-
{:else if icon}
|
|
63
|
-
{@render icon()}
|
|
64
|
-
{/if}
|
|
70
|
+
<ContextmenuIcon {icon} />
|
|
65
71
|
</div>
|
|
66
72
|
<div class="title">{@render children()}</div>
|
|
67
73
|
{#if pending}<PendingAnimation />{:else if error_message}⚠️{/if}
|
|
68
74
|
</div>
|
|
69
75
|
</li>
|
|
70
|
-
|
|
71
|
-
<style>
|
|
72
|
-
/* TODO hacky, needed because the base `.menuitem` added z-index */
|
|
73
|
-
.menuitem {
|
|
74
|
-
z-index: unset;
|
|
75
|
-
}
|
|
76
|
-
</style>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
2
2
|
import { type ContextmenuRun } from './contextmenu_state.svelte.js';
|
|
3
|
+
import type { SvgData } from './svg.js';
|
|
3
4
|
type $$ComponentProps = {
|
|
4
5
|
run: ContextmenuRun;
|
|
5
|
-
icon?: string | Snippet;
|
|
6
|
+
icon?: SvgData | string | Snippet;
|
|
6
7
|
children: Snippet;
|
|
7
8
|
disabled?: boolean;
|
|
8
9
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContextmenuEntry.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/ContextmenuEntry.svelte"],"names":[],"mappings":"AAGA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"ContextmenuEntry.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/ContextmenuEntry.svelte"],"names":[],"mappings":"AAGA,OAAO,EAAY,KAAK,OAAO,EAAC,MAAM,QAAQ,CAAC;AAI/C,OAAO,EAAsB,KAAK,cAAc,EAAC,MAAM,+BAA+B,CAAC;AACvF,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,UAAU,CAAC;AAGrC,KAAK,gBAAgB,GAAI;IACxB,GAAG,EAAE,cAAc,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;IAClC,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAgEH,QAAA,MAAM,gBAAgB,sDAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type {Snippet} from 'svelte';
|
|
3
|
+
|
|
4
|
+
import type {SvgData} from './svg.js';
|
|
5
|
+
import Svg from './Svg.svelte';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Renders a contextmenu entry's `icon` value, shared by `ContextmenuEntry`,
|
|
9
|
+
* `ContextmenuSubmenu`, and `ContextmenuLinkEntry`. Renders nothing when
|
|
10
|
+
* `icon` is `undefined`.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
icon,
|
|
17
|
+
}: {
|
|
18
|
+
icon?: SvgData | string | Snippet;
|
|
19
|
+
} = $props();
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
{#if typeof icon === 'string'}
|
|
23
|
+
{icon}
|
|
24
|
+
{:else if typeof icon === 'object'}
|
|
25
|
+
<!-- `SvgData` is a plain object; detect it positively by shape rather than
|
|
26
|
+
introspecting the snippet (whose runtime form is opaque/unsupported). -->
|
|
27
|
+
<Svg data={icon} />
|
|
28
|
+
{:else if icon}
|
|
29
|
+
{@render icon()}
|
|
30
|
+
{/if}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { SvgData } from './svg.js';
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
icon?: SvgData | string | Snippet;
|
|
5
|
+
};
|
|
6
|
+
declare const ContextmenuIcon: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type ContextmenuIcon = ReturnType<typeof ContextmenuIcon>;
|
|
8
|
+
export default ContextmenuIcon;
|
|
9
|
+
//# sourceMappingURL=ContextmenuIcon.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContextmenuIcon.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/ContextmenuIcon.svelte"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,QAAQ,CAAC;AAEpC,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,UAAU,CAAC;AAGrC,KAAK,gBAAgB,GAAI;IACxB,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;CAClC,CAAC;AA+BH,QAAA,MAAM,eAAe,sDAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
import {page} from '$app/state';
|
|
6
6
|
|
|
7
7
|
import {contextmenu_context} from './contextmenu_state.svelte.js';
|
|
8
|
+
import {icon_external_link, icon_link} from './icons.js';
|
|
9
|
+
import type {SvgData} from './svg.js';
|
|
10
|
+
import Svg from './Svg.svelte';
|
|
11
|
+
import ContextmenuIcon from './ContextmenuIcon.svelte';
|
|
8
12
|
|
|
9
13
|
const {
|
|
10
14
|
href,
|
|
@@ -14,7 +18,7 @@
|
|
|
14
18
|
external_rel = 'noreferrer', // TODO smarter defaults
|
|
15
19
|
}: {
|
|
16
20
|
href: string;
|
|
17
|
-
icon?: string | Snippet;
|
|
21
|
+
icon?: SvgData | string | Snippet;
|
|
18
22
|
children?: Snippet; // TODO @many rethink this API
|
|
19
23
|
disabled?: boolean;
|
|
20
24
|
external_rel?: string;
|
|
@@ -35,22 +39,27 @@
|
|
|
35
39
|
() => disabled_prop,
|
|
36
40
|
);
|
|
37
41
|
|
|
42
|
+
// keyboard selection, like `ContextmenuEntry` - distinct from `current_page` below
|
|
43
|
+
const {selected} = $derived(entry);
|
|
38
44
|
const disabled = $derived(entry.disabled());
|
|
39
45
|
|
|
40
|
-
// TODO move or upstream?
|
|
41
|
-
const
|
|
46
|
+
// TODO move or upstream?
|
|
47
|
+
const print_url = (url: string, host: string = location.host): string => {
|
|
42
48
|
const formatted = strip_start(strip_start(url, 'https://'), 'http://');
|
|
43
49
|
return formatted.startsWith(host + '/') ? strip_start(formatted, host) : formatted;
|
|
44
50
|
};
|
|
45
51
|
|
|
46
|
-
const text = $derived(
|
|
52
|
+
const text = $derived(print_url(href));
|
|
47
53
|
const external = $derived(!(text[0] === '.' || (text[0] === '/' && text[1] !== '/')));
|
|
48
54
|
const rel = $derived(external ? external_rel : undefined);
|
|
49
55
|
|
|
50
|
-
const
|
|
56
|
+
const current_page = $derived(page.url.pathname === href);
|
|
51
57
|
</script>
|
|
52
58
|
|
|
53
59
|
<li role="none">
|
|
60
|
+
<!-- `deselectable` is a hack to recover `cursor: pointer` when selected: these links always
|
|
61
|
+
navigate, but `.menuitem.selected` forces `cursor: default`. The real fix is a `.menuitem`
|
|
62
|
+
semantics cleanup (selected-as-active vs selected-as-highlight) - TODO @many -->
|
|
54
63
|
<!-- TODO -next-line doesnt work? -->
|
|
55
64
|
<!-- eslint-disable svelte/no-navigation-without-resolve -->
|
|
56
65
|
<a
|
|
@@ -58,8 +67,10 @@
|
|
|
58
67
|
class="menuitem plain"
|
|
59
68
|
class:selected
|
|
60
69
|
class:disabled
|
|
70
|
+
class:deselectable={!disabled}
|
|
61
71
|
role="menuitem"
|
|
62
72
|
aria-disabled={disabled}
|
|
73
|
+
aria-current={current_page ? 'page' : undefined}
|
|
63
74
|
tabindex="-1"
|
|
64
75
|
{href}
|
|
65
76
|
{rel}
|
|
@@ -81,10 +92,10 @@
|
|
|
81
92
|
>
|
|
82
93
|
<div class="content">
|
|
83
94
|
<div class="icon">
|
|
84
|
-
{#if
|
|
85
|
-
{icon}
|
|
86
|
-
{:else
|
|
87
|
-
{
|
|
95
|
+
{#if icon}
|
|
96
|
+
<ContextmenuIcon {icon} />
|
|
97
|
+
{:else}
|
|
98
|
+
<Svg data={external ? icon_external_link : icon_link} />
|
|
88
99
|
{/if}
|
|
89
100
|
</div>
|
|
90
101
|
<div class="title">
|
|
@@ -107,8 +118,8 @@
|
|
|
107
118
|
a:hover .text {
|
|
108
119
|
text-decoration: underline;
|
|
109
120
|
}
|
|
110
|
-
/*
|
|
111
|
-
.
|
|
112
|
-
|
|
121
|
+
/* Mark the current page distinctly from the keyboard selection highlight. */
|
|
122
|
+
a[aria-current='page'] .text {
|
|
123
|
+
font-weight: 700;
|
|
113
124
|
}
|
|
114
125
|
</style>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContextmenuLinkEntry.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/ContextmenuLinkEntry.svelte"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"ContextmenuLinkEntry.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/ContextmenuLinkEntry.svelte"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,QAAQ,CAAC;AAKpC,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,UAAU,CAAC;AAIrC,KAAK,gBAAgB,GAAI;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAyFH,QAAA,MAAM,oBAAoB,sDAAwC,CAAC;AACnE,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACpE,eAAe,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
// The default entry snippets are shared by `ContextmenuRoot.svelte`
|
|
3
|
+
// and `ContextmenuRootForSafariCompatibility.svelte`.
|
|
4
|
+
export {link_entry_default, text_entry_default, separator_entry_default};
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<script lang="ts">
|
|
8
|
+
/**
|
|
9
|
+
* The menu surface shared by `ContextmenuRoot.svelte` and
|
|
10
|
+
* `ContextmenuRootForSafariCompatibility.svelte`. Owns everything that happens
|
|
11
|
+
* while the menu is open - positioning, keyboard navigation, dismissal on
|
|
12
|
+
* outside presses, popover top-layer promotion, and entry rendering - while
|
|
13
|
+
* the roots own gesture detection, i.e. how the menu opens. Holds no gesture
|
|
14
|
+
* state of its own; the `open_guard` it consults is owned and armed by the root.
|
|
15
|
+
*/
|
|
16
|
+
import {swallow} from '@fuzdev/fuz_util/dom.js';
|
|
17
|
+
import type {ComponentProps, Snippet} from 'svelte';
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
contextmenu_dimensions_context,
|
|
21
|
+
type ContextmenuState,
|
|
22
|
+
} from './contextmenu_state.svelte.js';
|
|
23
|
+
import ContextmenuLinkEntry from './ContextmenuLinkEntry.svelte';
|
|
24
|
+
import ContextmenuTextEntry from './ContextmenuTextEntry.svelte';
|
|
25
|
+
import ContextmenuSeparator from './ContextmenuSeparator.svelte';
|
|
26
|
+
import {
|
|
27
|
+
contextmenu_create_keyboard_handlers,
|
|
28
|
+
contextmenu_create_keydown_handler,
|
|
29
|
+
contextmenu_create_mousedown_handler,
|
|
30
|
+
contextmenu_calculate_constrained_x,
|
|
31
|
+
contextmenu_calculate_constrained_y,
|
|
32
|
+
contextmenu_popover_attachment,
|
|
33
|
+
type ContextmenuOpenGuard,
|
|
34
|
+
} from './contextmenu_helpers.js';
|
|
35
|
+
|
|
36
|
+
let {
|
|
37
|
+
contextmenu,
|
|
38
|
+
open_guard,
|
|
39
|
+
link_entry,
|
|
40
|
+
text_entry,
|
|
41
|
+
separator_entry,
|
|
42
|
+
el = $bindable(),
|
|
43
|
+
}: {
|
|
44
|
+
/**
|
|
45
|
+
* The contextmenu state owned by the root.
|
|
46
|
+
*/
|
|
47
|
+
contextmenu: ContextmenuState;
|
|
48
|
+
/**
|
|
49
|
+
* Guards the menu from the residual events of the gesture that opened it.
|
|
50
|
+
* Owned by the root, which arms it when its gesture detection opens the menu.
|
|
51
|
+
*/
|
|
52
|
+
open_guard: ContextmenuOpenGuard;
|
|
53
|
+
/**
|
|
54
|
+
* Snippet for rendering link entries, or `null` to disable them.
|
|
55
|
+
*/
|
|
56
|
+
link_entry: Snippet<[ComponentProps<typeof ContextmenuLinkEntry>]> | null;
|
|
57
|
+
/**
|
|
58
|
+
* Snippet for rendering copy text entries, or `null` to disable them.
|
|
59
|
+
*/
|
|
60
|
+
text_entry: Snippet<[ComponentProps<typeof ContextmenuTextEntry>]> | null;
|
|
61
|
+
/**
|
|
62
|
+
* Snippet for rendering separator entries, or `null` to disable them.
|
|
63
|
+
*/
|
|
64
|
+
separator_entry: Snippet<[ComponentProps<typeof ContextmenuSeparator>]> | null;
|
|
65
|
+
/**
|
|
66
|
+
* The menu element while opened - bound by the roots for their gesture handlers.
|
|
67
|
+
*/
|
|
68
|
+
el?: HTMLElement;
|
|
69
|
+
} = $props();
|
|
70
|
+
|
|
71
|
+
const {layout} = $derived(contextmenu);
|
|
72
|
+
|
|
73
|
+
const dimensions = contextmenu_dimensions_context.set();
|
|
74
|
+
|
|
75
|
+
const x = $derived(
|
|
76
|
+
contextmenu_calculate_constrained_x(contextmenu.x, dimensions.width, layout.width),
|
|
77
|
+
);
|
|
78
|
+
const y = $derived(
|
|
79
|
+
contextmenu_calculate_constrained_y(contextmenu.y, dimensions.height, layout.height),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Closes the contextmenu on presses outside of it.
|
|
83
|
+
const mousedown = $derived(
|
|
84
|
+
contextmenu_create_mousedown_handler(contextmenu, () => el, open_guard),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const keyboard_handlers = $derived(contextmenu_create_keyboard_handlers(contextmenu));
|
|
88
|
+
const keydown = $derived(contextmenu_create_keydown_handler(keyboard_handlers));
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<svelte:window
|
|
92
|
+
onmousedown={!contextmenu.opened ? undefined : mousedown}
|
|
93
|
+
onkeydown={!contextmenu.opened ? undefined : keydown}
|
|
94
|
+
onmouseupcapture={(e) => open_guard.track_mouseup(e)}
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
{#if !contextmenu.has_custom_layout}
|
|
98
|
+
<div
|
|
99
|
+
class="contextmenu-layout"
|
|
100
|
+
bind:clientWidth={layout.width}
|
|
101
|
+
bind:clientHeight={layout.height}
|
|
102
|
+
aria-hidden="true"
|
|
103
|
+
></div>
|
|
104
|
+
{/if}
|
|
105
|
+
|
|
106
|
+
<!-- TODO animate the contextmenu as it appears somehow, maybe a subtle highlight -->
|
|
107
|
+
<!-- TODO implement focus management per APG: store `document.activeElement` when opening, focus menu on mount, restore focus on close if element `isConnected` -->
|
|
108
|
+
{#if contextmenu.opened}
|
|
109
|
+
<!-- Keyed on `params` (a fresh array every open) so reopening while open - which swaps
|
|
110
|
+
`params` without toggling `opened` - remounts the menu: entries register fresh state and
|
|
111
|
+
the popover attachment re-resolves its host (e.g. a modal dialog opened since the first
|
|
112
|
+
open). -->
|
|
113
|
+
{#key contextmenu.params}
|
|
114
|
+
<ul
|
|
115
|
+
class="contextmenu unstyled pane"
|
|
116
|
+
role="menu"
|
|
117
|
+
aria-label="context menu"
|
|
118
|
+
tabindex="-1"
|
|
119
|
+
popover="manual"
|
|
120
|
+
{@attach contextmenu_popover_attachment(contextmenu)}
|
|
121
|
+
bind:this={el}
|
|
122
|
+
bind:offsetWidth={dimensions.width}
|
|
123
|
+
bind:offsetHeight={dimensions.height}
|
|
124
|
+
style:transform="translate3d({x}px, {y}px, 0)"
|
|
125
|
+
onclickcapture={(e) => {
|
|
126
|
+
// blocks a click synthesized from the touch gesture that opened the menu,
|
|
127
|
+
// which would activate the first item (iOS can synthesize it even when
|
|
128
|
+
// the release was swallowed)
|
|
129
|
+
if (open_guard.consume_blocked_click()) swallow(e);
|
|
130
|
+
}}
|
|
131
|
+
>
|
|
132
|
+
<!-- TODO maybe this should be generic? -->
|
|
133
|
+
<!-- Index-keyed deliberately: `params` is immutable for the life of the key block
|
|
134
|
+
above, and identity keys would collide when the same snippet registers on nested
|
|
135
|
+
elements. -->
|
|
136
|
+
{#each contextmenu.params as p, i (i)}
|
|
137
|
+
{#if typeof p === 'function'}
|
|
138
|
+
{@render p()}
|
|
139
|
+
{:else if p.snippet === 'link'}
|
|
140
|
+
{@render link_entry?.(p.props)}
|
|
141
|
+
{:else if p.snippet === 'text'}
|
|
142
|
+
{@render text_entry?.(p.props)}
|
|
143
|
+
{:else if p.snippet === 'separator'}
|
|
144
|
+
{@render separator_entry?.(p.props)}
|
|
145
|
+
{/if}
|
|
146
|
+
{/each}
|
|
147
|
+
</ul>
|
|
148
|
+
{/key}
|
|
149
|
+
{/if}
|
|
150
|
+
|
|
151
|
+
{#snippet link_entry_default(props: ComponentProps<typeof ContextmenuLinkEntry>)}
|
|
152
|
+
<ContextmenuLinkEntry {...props} />
|
|
153
|
+
{/snippet}
|
|
154
|
+
|
|
155
|
+
{#snippet text_entry_default(props: ComponentProps<typeof ContextmenuTextEntry>)}
|
|
156
|
+
<ContextmenuTextEntry {...props} />
|
|
157
|
+
{/snippet}
|
|
158
|
+
|
|
159
|
+
{#snippet separator_entry_default(props: ComponentProps<typeof ContextmenuSeparator>)}
|
|
160
|
+
<ContextmenuSeparator {...props} />
|
|
161
|
+
{/snippet}
|
|
162
|
+
|
|
163
|
+
<style>
|
|
164
|
+
.contextmenu {
|
|
165
|
+
--icon_size: var(--icon_size_xs);
|
|
166
|
+
/* TODO maybe make this responsive or a max of the page width
|
|
167
|
+
minus some space to tap items covered by the menu on the side,
|
|
168
|
+
or consider a totally different design for small screens (more dialog-like) */
|
|
169
|
+
--contextmenu_width: 320px;
|
|
170
|
+
position: fixed;
|
|
171
|
+
left: 0;
|
|
172
|
+
top: 0;
|
|
173
|
+
/* the z-index is the fallback for browsers without the Popover API,
|
|
174
|
+
where the menu can't layer over modal dialogs */
|
|
175
|
+
z-index: var(--contextmenu_z_index, 200);
|
|
176
|
+
max-width: var(--contextmenu_width);
|
|
177
|
+
width: 100%;
|
|
178
|
+
/* reset the UA `[popover]` styles; positioning stays transform-based */
|
|
179
|
+
right: auto;
|
|
180
|
+
bottom: auto;
|
|
181
|
+
margin: 0;
|
|
182
|
+
padding: 0;
|
|
183
|
+
color: inherit;
|
|
184
|
+
/* the UA popover style sets `overflow: auto`, which would clip submenu flyouts */
|
|
185
|
+
overflow: visible;
|
|
186
|
+
/* Re-enable callouts on the menu itself to allow the native contextmenu (for dev
|
|
187
|
+
tools) and prevent the menu from being selected. Only meaningful under
|
|
188
|
+
`ContextmenuRootForSafariCompatibility`'s `body.contextmenu-pending` blocking -
|
|
189
|
+
inert under `ContextmenuRoot`, which never renders on iOS Safari. */
|
|
190
|
+
-webkit-touch-callout: initial !important;
|
|
191
|
+
}
|
|
192
|
+
/* TODO hacky */
|
|
193
|
+
.contextmenu,
|
|
194
|
+
.contextmenu :global(menu.pane) {
|
|
195
|
+
border: var(--contextmenu_border_width, var(--border_width))
|
|
196
|
+
var(--contextmenu_border_style, var(--border_style))
|
|
197
|
+
var(--contextmenu_border_color, var(--border_color));
|
|
198
|
+
border-radius: var(--contextmenu_border_radius, var(--border_radius_xs));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/* TODO better way to do this? */
|
|
202
|
+
.contextmenu-layout {
|
|
203
|
+
z-index: -200;
|
|
204
|
+
position: fixed;
|
|
205
|
+
inset: 0;
|
|
206
|
+
}
|
|
207
|
+
</style>
|