quasar-ui-danx 0.5.0 → 0.5.2
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/.claude/settings.local.json +8 -0
- package/dist/danx.es.js +16119 -10641
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +202 -123
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +8 -1
- package/src/components/Utility/Buttons/ActionButton.vue +15 -5
- package/src/components/Utility/Code/CodeViewer.vue +41 -16
- package/src/components/Utility/Code/CodeViewerCollapsed.vue +2 -0
- package/src/components/Utility/Code/CodeViewerFooter.vue +3 -1
- package/src/components/Utility/Code/LanguageBadge.vue +278 -5
- package/src/components/Utility/Code/MarkdownContent.vue +31 -163
- package/src/components/Utility/Code/index.ts +3 -0
- package/src/components/Utility/Markdown/ContextMenu.vue +314 -0
- package/src/components/Utility/Markdown/HotkeyHelpPopover.vue +259 -0
- package/src/components/Utility/Markdown/LineTypeMenu.vue +226 -0
- package/src/components/Utility/Markdown/LinkPopover.vue +331 -0
- package/src/components/Utility/Markdown/MarkdownEditor.vue +233 -0
- package/src/components/Utility/Markdown/MarkdownEditorContent.vue +296 -0
- package/src/components/Utility/Markdown/MarkdownEditorFooter.vue +50 -0
- package/src/components/Utility/Markdown/TablePopover.vue +420 -0
- package/src/components/Utility/Markdown/index.ts +11 -0
- package/src/components/Utility/Markdown/types.ts +27 -0
- package/src/components/Utility/Widgets/LabelPillWidget.vue +20 -0
- package/src/components/Utility/index.ts +1 -0
- package/src/composables/index.ts +1 -0
- package/src/composables/markdown/features/useBlockquotes.spec.ts +428 -0
- package/src/composables/markdown/features/useBlockquotes.ts +248 -0
- package/src/composables/markdown/features/useCodeBlockManager.ts +369 -0
- package/src/composables/markdown/features/useCodeBlocks.spec.ts +805 -0
- package/src/composables/markdown/features/useCodeBlocks.ts +774 -0
- package/src/composables/markdown/features/useContextMenu.ts +444 -0
- package/src/composables/markdown/features/useFocusTracking.ts +116 -0
- package/src/composables/markdown/features/useHeadings.spec.ts +834 -0
- package/src/composables/markdown/features/useHeadings.ts +290 -0
- package/src/composables/markdown/features/useInlineFormatting.spec.ts +705 -0
- package/src/composables/markdown/features/useInlineFormatting.ts +402 -0
- package/src/composables/markdown/features/useLineTypeMenu.ts +285 -0
- package/src/composables/markdown/features/useLinks.spec.ts +388 -0
- package/src/composables/markdown/features/useLinks.ts +374 -0
- package/src/composables/markdown/features/useLists.spec.ts +834 -0
- package/src/composables/markdown/features/useLists.ts +747 -0
- package/src/composables/markdown/features/usePopoverManager.ts +181 -0
- package/src/composables/markdown/features/useTables.spec.ts +1601 -0
- package/src/composables/markdown/features/useTables.ts +1107 -0
- package/src/composables/markdown/index.ts +16 -0
- package/src/composables/markdown/useMarkdownEditor.spec.ts +332 -0
- package/src/composables/markdown/useMarkdownEditor.ts +1077 -0
- package/src/composables/markdown/useMarkdownHotkeys.spec.ts +791 -0
- package/src/composables/markdown/useMarkdownHotkeys.ts +266 -0
- package/src/composables/markdown/useMarkdownSelection.ts +219 -0
- package/src/composables/markdown/useMarkdownSync.ts +549 -0
- package/src/composables/useCodeFormat.ts +17 -10
- package/src/composables/useCodeViewerEditor.spec.ts +655 -0
- package/src/composables/useCodeViewerEditor.ts +174 -20
- package/src/helpers/formats/highlightCSS.ts +236 -0
- package/src/helpers/formats/highlightHTML.ts +483 -0
- package/src/helpers/formats/highlightJavaScript.ts +346 -0
- package/src/helpers/formats/highlightSyntax.ts +15 -4
- package/src/helpers/formats/index.ts +3 -0
- package/src/helpers/formats/markdown/htmlToMarkdown/convertHeadings.ts +41 -0
- package/src/helpers/formats/markdown/htmlToMarkdown/index.spec.ts +489 -0
- package/src/helpers/formats/markdown/htmlToMarkdown/index.ts +425 -0
- package/src/helpers/formats/markdown/index.ts +7 -0
- package/src/helpers/formats/markdown/linePatterns.spec.ts +498 -0
- package/src/helpers/formats/markdown/linePatterns.ts +172 -0
- package/src/styles/danx.scss +3 -3
- package/src/styles/index.scss +5 -5
- package/src/styles/themes/danx/code.scss +257 -1
- package/src/styles/themes/danx/index.scss +10 -10
- package/src/styles/themes/danx/markdown.scss +59 -0
- package/src/test/helpers/editorTestUtils.spec.ts +296 -0
- package/src/test/helpers/editorTestUtils.ts +253 -0
- package/src/test/helpers/index.ts +1 -0
- package/src/test/highlighters.test.ts +153 -0
- package/src/test/setup.test.ts +12 -0
- package/src/test/setup.ts +12 -0
- package/src/types/widgets.d.ts +2 -2
- package/vite.config.js +5 -1
- package/vitest.config.ts +19 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
ref="overlayRef"
|
|
4
|
+
class="dx-hotkey-help-overlay"
|
|
5
|
+
tabindex="-1"
|
|
6
|
+
@click.self="$emit('close')"
|
|
7
|
+
@keydown.escape="$emit('close')"
|
|
8
|
+
>
|
|
9
|
+
<div class="dx-hotkey-help-popover">
|
|
10
|
+
<div class="popover-header">
|
|
11
|
+
<h3>Keyboard Shortcuts</h3>
|
|
12
|
+
<button
|
|
13
|
+
class="close-btn"
|
|
14
|
+
type="button"
|
|
15
|
+
aria-label="Close"
|
|
16
|
+
@click="$emit('close')"
|
|
17
|
+
>
|
|
18
|
+
<CloseIcon class="w-4 h-4" />
|
|
19
|
+
</button>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="popover-content">
|
|
23
|
+
<div class="hotkey-groups-grid">
|
|
24
|
+
<div
|
|
25
|
+
v-for="group in groupedHotkeys"
|
|
26
|
+
:key="group.name"
|
|
27
|
+
class="hotkey-group"
|
|
28
|
+
>
|
|
29
|
+
<h4>{{ group.label }}</h4>
|
|
30
|
+
<div class="hotkey-list">
|
|
31
|
+
<div
|
|
32
|
+
v-for="hotkey in group.hotkeys"
|
|
33
|
+
:key="hotkey.key"
|
|
34
|
+
class="hotkey-item"
|
|
35
|
+
>
|
|
36
|
+
<span class="hotkey-description">{{ hotkey.description }}</span>
|
|
37
|
+
<kbd class="hotkey-key">{{ formatKey(hotkey.key) }}</kbd>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<script setup lang="ts">
|
|
48
|
+
import { FaSolidXmark as CloseIcon } from "danx-icon";
|
|
49
|
+
import { computed, onMounted, ref } from "vue";
|
|
50
|
+
import { HotkeyDefinition, HotkeyGroup } from "../../../composables/markdown/useMarkdownHotkeys";
|
|
51
|
+
|
|
52
|
+
const overlayRef = ref<HTMLDivElement | null>(null);
|
|
53
|
+
|
|
54
|
+
onMounted(() => {
|
|
55
|
+
// Focus the overlay so it can receive keyboard events
|
|
56
|
+
overlayRef.value?.focus();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export interface HotkeyHelpPopoverProps {
|
|
60
|
+
hotkeys: HotkeyDefinition[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface HotkeyGroupDisplay {
|
|
64
|
+
name: HotkeyGroup;
|
|
65
|
+
label: string;
|
|
66
|
+
hotkeys: HotkeyDefinition[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const GROUP_LABELS: Record<HotkeyGroup, string> = {
|
|
70
|
+
headings: "Headings",
|
|
71
|
+
formatting: "Formatting",
|
|
72
|
+
lists: "Lists",
|
|
73
|
+
blocks: "Blocks",
|
|
74
|
+
tables: "Tables",
|
|
75
|
+
other: "Other"
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const GROUP_ORDER: HotkeyGroup[] = ["headings", "formatting", "lists", "blocks", "tables", "other"];
|
|
79
|
+
|
|
80
|
+
const props = defineProps<HotkeyHelpPopoverProps>();
|
|
81
|
+
|
|
82
|
+
defineEmits<{
|
|
83
|
+
close: [];
|
|
84
|
+
}>();
|
|
85
|
+
|
|
86
|
+
const groupedHotkeys = computed<HotkeyGroupDisplay[]>(() => {
|
|
87
|
+
const groups = new Map<HotkeyGroup, HotkeyDefinition[]>();
|
|
88
|
+
|
|
89
|
+
// Initialize groups
|
|
90
|
+
for (const group of GROUP_ORDER) {
|
|
91
|
+
groups.set(group, []);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Distribute hotkeys into groups
|
|
95
|
+
for (const hotkey of props.hotkeys) {
|
|
96
|
+
const group = groups.get(hotkey.group);
|
|
97
|
+
if (group) {
|
|
98
|
+
group.push(hotkey);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Convert to display format, filtering empty groups
|
|
103
|
+
return GROUP_ORDER
|
|
104
|
+
.filter(name => (groups.get(name)?.length || 0) > 0)
|
|
105
|
+
.map(name => ({
|
|
106
|
+
name,
|
|
107
|
+
label: GROUP_LABELS[name],
|
|
108
|
+
hotkeys: groups.get(name) || []
|
|
109
|
+
}));
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Format a key combination for display
|
|
114
|
+
* Converts 'ctrl+1' to 'Ctrl + 1'
|
|
115
|
+
*/
|
|
116
|
+
function formatKey(key: string): string {
|
|
117
|
+
return key
|
|
118
|
+
.split("+")
|
|
119
|
+
.map(part => {
|
|
120
|
+
const lower = part.toLowerCase();
|
|
121
|
+
switch (lower) {
|
|
122
|
+
case "ctrl":
|
|
123
|
+
case "control":
|
|
124
|
+
return "Ctrl";
|
|
125
|
+
case "shift":
|
|
126
|
+
return "Shift";
|
|
127
|
+
case "alt":
|
|
128
|
+
case "option":
|
|
129
|
+
return "Alt";
|
|
130
|
+
case "meta":
|
|
131
|
+
case "cmd":
|
|
132
|
+
case "command":
|
|
133
|
+
return "Cmd";
|
|
134
|
+
default:
|
|
135
|
+
return part.toUpperCase();
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
.join(" + ");
|
|
139
|
+
}
|
|
140
|
+
</script>
|
|
141
|
+
|
|
142
|
+
<style lang="scss">
|
|
143
|
+
.dx-hotkey-help-overlay {
|
|
144
|
+
position: fixed;
|
|
145
|
+
inset: 0;
|
|
146
|
+
z-index: 1000;
|
|
147
|
+
display: flex;
|
|
148
|
+
align-items: center;
|
|
149
|
+
justify-content: center;
|
|
150
|
+
background: rgba(0, 0, 0, 0.5);
|
|
151
|
+
backdrop-filter: blur(2px);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.dx-hotkey-help-popover {
|
|
155
|
+
background: #2d2d2d;
|
|
156
|
+
border: 1px solid #404040;
|
|
157
|
+
border-radius: 0.5rem;
|
|
158
|
+
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
|
|
159
|
+
min-width: 320px;
|
|
160
|
+
max-width: 90vw;
|
|
161
|
+
max-height: 80vh;
|
|
162
|
+
overflow: hidden;
|
|
163
|
+
display: flex;
|
|
164
|
+
flex-direction: column;
|
|
165
|
+
|
|
166
|
+
.popover-header {
|
|
167
|
+
display: flex;
|
|
168
|
+
align-items: center;
|
|
169
|
+
justify-content: space-between;
|
|
170
|
+
padding: 1rem 1.25rem;
|
|
171
|
+
border-bottom: 1px solid #404040;
|
|
172
|
+
|
|
173
|
+
h3 {
|
|
174
|
+
margin: 0;
|
|
175
|
+
font-size: 1rem;
|
|
176
|
+
font-weight: 600;
|
|
177
|
+
color: #f3f4f6;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.close-btn {
|
|
181
|
+
display: flex;
|
|
182
|
+
align-items: center;
|
|
183
|
+
justify-content: center;
|
|
184
|
+
width: 1.75rem;
|
|
185
|
+
height: 1.75rem;
|
|
186
|
+
padding: 0;
|
|
187
|
+
background: transparent;
|
|
188
|
+
border: none;
|
|
189
|
+
border-radius: 0.25rem;
|
|
190
|
+
color: #9ca3af;
|
|
191
|
+
cursor: pointer;
|
|
192
|
+
transition: all 0.15s ease;
|
|
193
|
+
|
|
194
|
+
&:hover {
|
|
195
|
+
background: rgba(255, 255, 255, 0.1);
|
|
196
|
+
color: #f3f4f6;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.popover-content {
|
|
202
|
+
padding: 1rem 1.25rem;
|
|
203
|
+
overflow-y: auto;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.hotkey-groups-grid {
|
|
207
|
+
display: grid;
|
|
208
|
+
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
209
|
+
gap: 1.5rem 2rem;
|
|
210
|
+
|
|
211
|
+
// For wider screens, limit to 3 columns max
|
|
212
|
+
@media (min-width: 800px) {
|
|
213
|
+
grid-template-columns: repeat(3, minmax(200px, 1fr));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.hotkey-group {
|
|
218
|
+
h4 {
|
|
219
|
+
margin: 0 0 0.75rem;
|
|
220
|
+
font-size: 0.75rem;
|
|
221
|
+
font-weight: 600;
|
|
222
|
+
text-transform: uppercase;
|
|
223
|
+
letter-spacing: 0.05em;
|
|
224
|
+
color: #9ca3af;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.hotkey-list {
|
|
229
|
+
display: flex;
|
|
230
|
+
flex-direction: column;
|
|
231
|
+
gap: 0.5rem;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.hotkey-item {
|
|
235
|
+
display: flex;
|
|
236
|
+
align-items: center;
|
|
237
|
+
justify-content: space-between;
|
|
238
|
+
gap: 1rem;
|
|
239
|
+
|
|
240
|
+
.hotkey-description {
|
|
241
|
+
flex: 1;
|
|
242
|
+
color: #d4d4d4;
|
|
243
|
+
font-size: 0.875rem;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.hotkey-key {
|
|
247
|
+
flex-shrink: 0;
|
|
248
|
+
padding: 0.25rem 0.5rem;
|
|
249
|
+
background: #1e1e1e;
|
|
250
|
+
border: 1px solid #404040;
|
|
251
|
+
border-radius: 0.25rem;
|
|
252
|
+
font-family: 'Consolas', 'Monaco', monospace;
|
|
253
|
+
font-size: 0.75rem;
|
|
254
|
+
color: #9ca3af;
|
|
255
|
+
white-space: nowrap;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
</style>
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="menuRef" class="dx-line-type-menu" :class="{ 'is-open': isOpen }">
|
|
3
|
+
<button
|
|
4
|
+
class="line-type-trigger"
|
|
5
|
+
:title="currentTypeLabel"
|
|
6
|
+
type="button"
|
|
7
|
+
@mousedown.prevent="toggleMenu"
|
|
8
|
+
>
|
|
9
|
+
<span class="type-icon">{{ typeIcon }}</span>
|
|
10
|
+
</button>
|
|
11
|
+
|
|
12
|
+
<div v-if="isOpen" ref="dropdownRef" class="line-type-dropdown" :class="{ 'open-upward': openUpward }">
|
|
13
|
+
<button
|
|
14
|
+
v-for="option in LINE_TYPE_OPTIONS"
|
|
15
|
+
:key="option.value"
|
|
16
|
+
class="line-type-option"
|
|
17
|
+
:class="{ active: option.value === currentType }"
|
|
18
|
+
type="button"
|
|
19
|
+
@mousedown.prevent="selectType(option.value)"
|
|
20
|
+
>
|
|
21
|
+
<span class="option-icon">{{ option.icon }}</span>
|
|
22
|
+
<span class="option-label">{{ option.label }}</span>
|
|
23
|
+
<span class="option-shortcut">{{ option.shortcut }}</span>
|
|
24
|
+
</button>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script setup lang="ts">
|
|
30
|
+
import { computed, nextTick, onUnmounted, ref, watch } from "vue";
|
|
31
|
+
import type { LineType, LineTypeOption } from "./types";
|
|
32
|
+
|
|
33
|
+
export interface LineTypeMenuProps {
|
|
34
|
+
currentType: LineType;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const LINE_TYPE_OPTIONS: LineTypeOption[] = [
|
|
38
|
+
{ value: "paragraph", label: "Paragraph", icon: "\u00B6", shortcut: "Ctrl+0" },
|
|
39
|
+
{ value: "h1", label: "Heading 1", icon: "H1", shortcut: "Ctrl+1" },
|
|
40
|
+
{ value: "h2", label: "Heading 2", icon: "H2", shortcut: "Ctrl+2" },
|
|
41
|
+
{ value: "h3", label: "Heading 3", icon: "H3", shortcut: "Ctrl+3" },
|
|
42
|
+
{ value: "h4", label: "Heading 4", icon: "H4", shortcut: "Ctrl+4" },
|
|
43
|
+
{ value: "h5", label: "Heading 5", icon: "H5", shortcut: "Ctrl+5" },
|
|
44
|
+
{ value: "h6", label: "Heading 6", icon: "H6", shortcut: "Ctrl+6" },
|
|
45
|
+
{ value: "ul", label: "Bullet List", icon: "\u2022", shortcut: "Ctrl+Shift+[" },
|
|
46
|
+
{ value: "ol", label: "Numbered List", icon: "1.", shortcut: "Ctrl+Shift+]" },
|
|
47
|
+
{ value: "code", label: "Code Block", icon: "</>", shortcut: "Ctrl+Shift+K" },
|
|
48
|
+
{ value: "blockquote", label: "Blockquote", icon: ">", shortcut: "Ctrl+Shift+Q" }
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const props = defineProps<LineTypeMenuProps>();
|
|
52
|
+
|
|
53
|
+
const emit = defineEmits<{
|
|
54
|
+
change: [type: LineType];
|
|
55
|
+
}>();
|
|
56
|
+
|
|
57
|
+
const isOpen = ref(false);
|
|
58
|
+
const menuRef = ref<HTMLElement | null>(null);
|
|
59
|
+
const dropdownRef = ref<HTMLElement | null>(null);
|
|
60
|
+
const openUpward = ref(false);
|
|
61
|
+
|
|
62
|
+
const currentOption = computed(() => {
|
|
63
|
+
return LINE_TYPE_OPTIONS.find(o => o.value === props.currentType) || LINE_TYPE_OPTIONS[0];
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const currentTypeLabel = computed(() => currentOption.value.label);
|
|
67
|
+
const typeIcon = computed(() => currentOption.value.icon);
|
|
68
|
+
|
|
69
|
+
function toggleMenu() {
|
|
70
|
+
isOpen.value = !isOpen.value;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function selectType(type: LineType) {
|
|
74
|
+
emit("change", type);
|
|
75
|
+
isOpen.value = false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check if dropdown would overflow the editor container and adjust positioning
|
|
80
|
+
*/
|
|
81
|
+
function checkDropdownPosition() {
|
|
82
|
+
if (!dropdownRef.value || !menuRef.value) return;
|
|
83
|
+
|
|
84
|
+
// Find the editor container (walk up to find .dx-markdown-editor or similar scrollable container)
|
|
85
|
+
const editor = menuRef.value.closest(".dx-markdown-editor") || menuRef.value.closest("[class*='editor']");
|
|
86
|
+
if (!editor) {
|
|
87
|
+
openUpward.value = false;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const editorRect = editor.getBoundingClientRect();
|
|
92
|
+
const dropdownRect = dropdownRef.value.getBoundingClientRect();
|
|
93
|
+
|
|
94
|
+
// Check if dropdown extends below editor
|
|
95
|
+
if (dropdownRect.bottom > editorRect.bottom) {
|
|
96
|
+
openUpward.value = true;
|
|
97
|
+
} else {
|
|
98
|
+
openUpward.value = false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Close menu on click outside
|
|
103
|
+
function handleClickOutside(event: MouseEvent) {
|
|
104
|
+
const target = event.target as Node;
|
|
105
|
+
if (menuRef.value && !menuRef.value.contains(target)) {
|
|
106
|
+
isOpen.value = false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Close menu on Escape key
|
|
111
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
112
|
+
if (event.key === "Escape") {
|
|
113
|
+
isOpen.value = false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Add/remove event listeners when menu opens/closes
|
|
118
|
+
watch(isOpen, async (open) => {
|
|
119
|
+
if (open) {
|
|
120
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
121
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
122
|
+
// Wait for dropdown to render, then check position
|
|
123
|
+
await nextTick();
|
|
124
|
+
checkDropdownPosition();
|
|
125
|
+
} else {
|
|
126
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
127
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
128
|
+
// Reset position when closed
|
|
129
|
+
openUpward.value = false;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Cleanup on unmount
|
|
134
|
+
onUnmounted(() => {
|
|
135
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
136
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
137
|
+
});
|
|
138
|
+
</script>
|
|
139
|
+
|
|
140
|
+
<style lang="scss">
|
|
141
|
+
.dx-line-type-menu {
|
|
142
|
+
position: relative;
|
|
143
|
+
display: inline-block;
|
|
144
|
+
|
|
145
|
+
.line-type-trigger {
|
|
146
|
+
display: flex;
|
|
147
|
+
align-items: center;
|
|
148
|
+
justify-content: center;
|
|
149
|
+
width: 1.25rem;
|
|
150
|
+
height: 1.25rem;
|
|
151
|
+
background: rgba(255, 255, 255, 0.05);
|
|
152
|
+
border: none;
|
|
153
|
+
border-radius: 0.2rem;
|
|
154
|
+
color: #6b7280;
|
|
155
|
+
font-size: 0.625rem;
|
|
156
|
+
font-weight: 600;
|
|
157
|
+
cursor: pointer;
|
|
158
|
+
transition: all 0.15s ease;
|
|
159
|
+
|
|
160
|
+
&:hover {
|
|
161
|
+
background: rgba(255, 255, 255, 0.15);
|
|
162
|
+
color: #9ca3af;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.line-type-dropdown {
|
|
167
|
+
position: absolute;
|
|
168
|
+
top: 0;
|
|
169
|
+
left: 100%;
|
|
170
|
+
z-index: 100;
|
|
171
|
+
min-width: 240px;
|
|
172
|
+
margin-left: 0.25rem;
|
|
173
|
+
background: #2d2d2d;
|
|
174
|
+
border: 1px solid #404040;
|
|
175
|
+
border-radius: 0.375rem;
|
|
176
|
+
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4);
|
|
177
|
+
overflow: hidden;
|
|
178
|
+
|
|
179
|
+
// When dropdown would overflow editor bottom, open upward instead
|
|
180
|
+
&.open-upward {
|
|
181
|
+
top: auto;
|
|
182
|
+
bottom: 0;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.line-type-option {
|
|
187
|
+
display: flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
width: 100%;
|
|
190
|
+
padding: 0.5rem 0.75rem;
|
|
191
|
+
background: transparent;
|
|
192
|
+
border: none;
|
|
193
|
+
color: #d4d4d4;
|
|
194
|
+
font-size: 0.875rem;
|
|
195
|
+
text-align: left;
|
|
196
|
+
cursor: pointer;
|
|
197
|
+
transition: background-color 0.15s ease;
|
|
198
|
+
|
|
199
|
+
&:hover {
|
|
200
|
+
background: rgba(255, 255, 255, 0.1);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
&.active {
|
|
204
|
+
background: rgba(56, 139, 253, 0.2);
|
|
205
|
+
color: #58a6ff;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.option-icon {
|
|
209
|
+
width: 1.5rem;
|
|
210
|
+
font-weight: 700;
|
|
211
|
+
font-size: 0.75rem;
|
|
212
|
+
color: #9ca3af;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.option-label {
|
|
216
|
+
flex: 1;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.option-shortcut {
|
|
220
|
+
font-size: 0.75rem;
|
|
221
|
+
color: #6b7280;
|
|
222
|
+
font-family: 'Consolas', 'Monaco', monospace;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
</style>
|