svelte-comp 1.2.7 → 1.3.5
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/README.md +12 -11
- package/dist/App.svelte +497 -2
- package/dist/app.css +2 -3
- package/dist/app.d.ts +10 -0
- package/dist/lang.d.ts +3 -3
- package/dist/lang.js +3 -3
- package/dist/lib/Accordion.svelte +14 -14
- package/dist/lib/Badge.svelte +44 -0
- package/dist/lib/Badge.svelte.d.ts +10 -0
- package/dist/lib/Button.svelte +23 -8
- package/dist/lib/Calendar.svelte +384 -377
- package/dist/lib/Card.svelte +6 -6
- package/dist/lib/Carousel.svelte +16 -16
- package/dist/lib/Carousel.svelte.d.ts +1 -1
- package/dist/lib/CheckBox.svelte +2 -2
- package/dist/lib/CodeView.svelte +6 -5
- package/dist/lib/ColorPicker.svelte +6 -6
- package/dist/lib/ContextMenu.svelte +328 -0
- package/dist/lib/ContextMenu.svelte.d.ts +14 -0
- package/dist/lib/DatePicker.svelte +161 -161
- package/dist/lib/Dialog.svelte +10 -10
- package/dist/lib/Field.svelte +1 -1
- package/dist/lib/FilePicker.svelte +127 -74
- package/dist/lib/FilePicker.svelte.d.ts +6 -3
- package/dist/lib/Hamburger.svelte +27 -21
- package/dist/lib/InstallPWA.svelte +94 -0
- package/dist/lib/InstallPWA.svelte.d.ts +8 -0
- package/dist/lib/Menu.svelte +18 -18
- package/dist/lib/NoticeBase.svelte +140 -0
- package/dist/lib/NoticeBase.svelte.d.ts +43 -0
- package/dist/lib/PrimaryColorSelect.svelte +6 -6
- package/dist/lib/ProgressCircle.svelte +7 -9
- package/dist/lib/ProgressCircle.svelte.d.ts +7 -9
- package/dist/lib/SearchInput.svelte +6 -6
- package/dist/lib/Select.svelte +2 -2
- package/dist/lib/Slider.svelte +1 -1
- package/dist/lib/Splitter.svelte +15 -6
- package/dist/lib/Switch.svelte +5 -4
- package/dist/lib/Tabs.svelte +6 -6
- package/dist/lib/ThemeToggle.svelte +1 -0
- package/dist/lib/TimePicker.svelte +103 -95
- package/dist/lib/TimePickerNew.svelte +634 -0
- package/dist/lib/TimePickerNew.svelte.d.ts +49 -0
- package/dist/lib/Toast.svelte +17 -120
- package/dist/lib/Tooltip.svelte +7 -7
- package/dist/lib/Topbar.svelte +112 -0
- package/dist/lib/Topbar.svelte.d.ts +44 -0
- package/dist/lib/__tests__/Accordion.test.d.ts +1 -0
- package/dist/lib/__tests__/Accordion.test.js +171 -0
- package/dist/lib/__tests__/Badge.test.d.ts +1 -0
- package/dist/lib/__tests__/Badge.test.js +41 -0
- package/dist/lib/__tests__/Button.test.d.ts +1 -0
- package/dist/lib/__tests__/Button.test.js +269 -0
- package/dist/lib/__tests__/Calendar.test.d.ts +1 -0
- package/dist/lib/__tests__/Calendar.test.js +171 -0
- package/dist/lib/__tests__/Card.test.d.ts +1 -0
- package/dist/lib/__tests__/Card.test.js +148 -0
- package/dist/lib/__tests__/Carousel.test.d.ts +1 -0
- package/dist/lib/__tests__/Carousel.test.js +439 -0
- package/dist/lib/__tests__/CheckBox.test.d.ts +1 -0
- package/dist/lib/__tests__/CheckBox.test.js +152 -0
- package/dist/lib/__tests__/CodeView.test.d.ts +1 -0
- package/dist/lib/__tests__/CodeView.test.js +157 -0
- package/dist/lib/__tests__/ColorPicker.test.d.ts +1 -0
- package/dist/lib/__tests__/ColorPicker.test.js +93 -0
- package/dist/lib/__tests__/ContextMenu.test.d.ts +1 -0
- package/dist/lib/__tests__/ContextMenu.test.js +67 -0
- package/dist/lib/__tests__/DatePicker.test.d.ts +1 -0
- package/dist/lib/__tests__/DatePicker.test.js +108 -0
- package/dist/lib/__tests__/Dialog.test.d.ts +1 -0
- package/dist/lib/__tests__/Dialog.test.js +183 -0
- package/dist/lib/__tests__/Field.test.d.ts +1 -0
- package/dist/lib/__tests__/Field.test.js +190 -0
- package/dist/lib/__tests__/FilePicker.test.d.ts +1 -0
- package/dist/lib/__tests__/FilePicker.test.js +179 -0
- package/dist/lib/__tests__/Form.integration.test.d.ts +1 -0
- package/dist/lib/__tests__/Form.integration.test.js +158 -0
- package/dist/lib/__tests__/Form.test.d.ts +1 -0
- package/dist/lib/__tests__/Form.test.js +463 -0
- package/dist/lib/__tests__/Hamburger.test.d.ts +1 -0
- package/dist/lib/__tests__/Hamburger.test.js +161 -0
- package/dist/lib/__tests__/InstallPWA.test.d.ts +1 -0
- package/dist/lib/__tests__/InstallPWA.test.js +15 -0
- package/dist/lib/__tests__/Menu.test.d.ts +1 -0
- package/dist/lib/__tests__/Menu.test.js +285 -0
- package/dist/lib/__tests__/NoticeBase.test.d.ts +1 -0
- package/dist/lib/__tests__/NoticeBase.test.js +60 -0
- package/dist/lib/__tests__/PaginatedCard.test.d.ts +1 -0
- package/dist/lib/__tests__/PaginatedCard.test.js +89 -0
- package/dist/lib/__tests__/Pagination.test.d.ts +1 -0
- package/dist/lib/__tests__/Pagination.test.js +168 -0
- package/dist/lib/__tests__/PrimaryColorSelect.test.d.ts +1 -0
- package/dist/lib/__tests__/PrimaryColorSelect.test.js +92 -0
- package/dist/lib/__tests__/ProgressBar.test.d.ts +1 -0
- package/dist/lib/__tests__/ProgressBar.test.js +69 -0
- package/dist/lib/__tests__/ProgressCircle.test.d.ts +1 -0
- package/dist/lib/__tests__/ProgressCircle.test.js +71 -0
- package/dist/lib/__tests__/Radio.test.d.ts +1 -0
- package/dist/lib/__tests__/Radio.test.js +127 -0
- package/dist/lib/__tests__/SearchInput.test.d.ts +1 -0
- package/dist/lib/__tests__/SearchInput.test.js +80 -0
- package/dist/lib/__tests__/Select.test.d.ts +1 -0
- package/dist/lib/__tests__/Select.test.js +408 -0
- package/dist/lib/__tests__/Slider.test.d.ts +1 -0
- package/dist/lib/__tests__/Slider.test.js +213 -0
- package/dist/lib/__tests__/Splitter.test.d.ts +1 -0
- package/dist/lib/__tests__/Splitter.test.js +87 -0
- package/dist/lib/__tests__/Switch.test.d.ts +1 -0
- package/dist/lib/__tests__/Switch.test.js +97 -0
- package/dist/lib/__tests__/Table.test.d.ts +1 -0
- package/dist/lib/__tests__/Table.test.js +349 -0
- package/dist/lib/__tests__/Tabs.test.d.ts +1 -0
- package/dist/lib/__tests__/Tabs.test.js +262 -0
- package/dist/lib/__tests__/ThemeToggle.test.d.ts +1 -0
- package/dist/lib/__tests__/ThemeToggle.test.js +84 -0
- package/dist/lib/__tests__/TimePicker.test.d.ts +1 -0
- package/dist/lib/__tests__/TimePicker.test.js +146 -0
- package/dist/lib/__tests__/TimePickerNew.test.d.ts +1 -0
- package/dist/lib/__tests__/TimePickerNew.test.js +322 -0
- package/dist/lib/__tests__/Toast.test.d.ts +1 -0
- package/dist/lib/__tests__/Toast.test.js +135 -0
- package/dist/lib/__tests__/Tooltip.test.d.ts +1 -0
- package/dist/lib/__tests__/Tooltip.test.js +171 -0
- package/dist/lib/__tests__/Topbar.test.d.ts +1 -0
- package/dist/lib/__tests__/Topbar.test.js +25 -0
- package/dist/lib/__tests__/setupLangContext.d.ts +1 -0
- package/dist/lib/__tests__/setupLangContext.js +65 -0
- package/dist/lib/__tests__/storage.test.d.ts +1 -0
- package/dist/lib/__tests__/storage.test.js +124 -0
- package/dist/lib/__tests__/utils.test.d.ts +1 -0
- package/dist/lib/__tests__/utils.test.js +11 -0
- package/dist/lib/index.d.ts +5 -0
- package/dist/lib/index.js +5 -0
- package/dist/lib/lang.d.ts +64 -0
- package/dist/lib/lang.js +64 -0
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/styles.css +2 -0
- package/dist/utils/index.js +15 -4
- package/package.json +12 -12
package/dist/lib/Card.svelte
CHANGED
|
@@ -53,16 +53,16 @@
|
|
|
53
53
|
}: Props = $props();
|
|
54
54
|
|
|
55
55
|
const paddingSizes: Record<SizeKey, string> = {
|
|
56
|
-
xs: "px-
|
|
57
|
-
sm: "px-
|
|
58
|
-
md: "px-
|
|
59
|
-
lg: "px-
|
|
60
|
-
xl: "px-
|
|
56
|
+
xs: "px-[calc(var(--spacing-sm)+var(--spacing-xs))] py-[var(--spacing-sm)]",
|
|
57
|
+
sm: "px-[var(--spacing-md)] py-[var(--spacing-sm)]",
|
|
58
|
+
md: "px-[calc(var(--spacing-md)+var(--spacing-xs))] py-[calc(var(--spacing-sm)+var(--spacing-xs))]",
|
|
59
|
+
lg: "px-[calc(var(--spacing-md)+var(--spacing-sm))] py-[var(--spacing-md)]",
|
|
60
|
+
xl: "px-[calc(var(--spacing-md)+var(--spacing-sm)+var(--spacing-xs))] py-[calc(var(--spacing-md)+var(--spacing-xs))]",
|
|
61
61
|
};
|
|
62
62
|
|
|
63
63
|
const cardClass = $derived(
|
|
64
64
|
cx(
|
|
65
|
-
"bg-[var(--color-bg-surface)] border border-[var(--border-color-default)] rounded-xl shadow-
|
|
65
|
+
"bg-[var(--color-bg-surface)] border border-[var(--border-color-default)] rounded-[var(--radius-xl)] shadow-[0_1px_2px_var(--shadow-color)] overflow-hidden",
|
|
66
66
|
TEXT[sz],
|
|
67
67
|
"flex flex-col",
|
|
68
68
|
externalClass
|
package/dist/lib/Carousel.svelte
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
*/
|
|
35
35
|
import Card from "./Card.svelte";
|
|
36
36
|
import type { HTMLAttributes } from "svelte/elements";
|
|
37
|
-
import type
|
|
37
|
+
import { TEXT, type SizeKey, type CarouselItem } from "./types";
|
|
38
38
|
import { cx } from "../utils";
|
|
39
39
|
|
|
40
40
|
type Props = HTMLAttributes<HTMLDivElement> & {
|
|
@@ -66,19 +66,19 @@
|
|
|
66
66
|
"relative w-full overflow-hidden rounded-[var(--radius-lg)] bg-[var(--color-bg-surface)]";
|
|
67
67
|
|
|
68
68
|
const sizes: Record<SizeKey, string> = {
|
|
69
|
-
xs: "rounded-[var(--radius-md)]
|
|
70
|
-
sm: "rounded-[var(--radius-md)]
|
|
71
|
-
md: "rounded-[var(--radius-lg)]
|
|
72
|
-
lg: "rounded-[var(--radius-lg)]
|
|
73
|
-
xl: "rounded-[var(--radius-xl)]
|
|
69
|
+
xs: cx("rounded-[var(--radius-md)]", TEXT.xs),
|
|
70
|
+
sm: cx("rounded-[var(--radius-md)]", TEXT.sm),
|
|
71
|
+
md: cx("rounded-[var(--radius-lg)]", TEXT.md),
|
|
72
|
+
lg: cx("rounded-[var(--radius-lg)]", TEXT.lg),
|
|
73
|
+
xl: cx("rounded-[var(--radius-xl)]", TEXT.xl),
|
|
74
74
|
};
|
|
75
75
|
|
|
76
76
|
const contentSize: Record<SizeKey, string> = {
|
|
77
|
-
xs: "p-
|
|
78
|
-
sm: "p-
|
|
79
|
-
md: "p-
|
|
80
|
-
lg: "p-
|
|
81
|
-
xl: "p-
|
|
77
|
+
xs: "p-[calc(var(--spacing-sm)+var(--spacing-xs))] gap-[var(--spacing-sm)]",
|
|
78
|
+
sm: "p-[var(--spacing-md)] gap-[calc(var(--spacing-sm)+var(--spacing-xs)/2)]",
|
|
79
|
+
md: "p-[calc(var(--spacing-md)+var(--spacing-xs))] gap-[calc(var(--spacing-sm)+var(--spacing-xs))]",
|
|
80
|
+
lg: "p-[calc(var(--spacing-md)+var(--spacing-sm))] gap-[var(--spacing-md)]",
|
|
81
|
+
xl: "p-[var(--spacing-xl)] gap-[calc(var(--spacing-md)+var(--spacing-xs))]",
|
|
82
82
|
};
|
|
83
83
|
|
|
84
84
|
const arrowSize: Record<SizeKey, string> = {
|
|
@@ -123,14 +123,14 @@
|
|
|
123
123
|
const arrowClass = $derived(
|
|
124
124
|
cx(
|
|
125
125
|
arrowSize[sz],
|
|
126
|
-
"rounded-full bg-[var(--color-bg-surface)] shadow-
|
|
126
|
+
"rounded-full bg-[var(--color-bg-surface)] shadow-[0_8px_16px_var(--shadow-color)] flex items-center justify-center [color:var(--color-text-default)] hover:bg-[var(--color-bg-hover)] transition-colors focus-visible:ring-2 focus-visible:ring-[var(--border-color-focus)] focus:outline-none"
|
|
127
127
|
)
|
|
128
128
|
);
|
|
129
129
|
|
|
130
130
|
const dotClass = $derived(
|
|
131
131
|
cx(
|
|
132
132
|
dotSize[sz],
|
|
133
|
-
"rounded-full transition-all duration-
|
|
133
|
+
"rounded-full transition-all duration-[var(--transition-fast)] focus-visible:ring-2 focus-visible:ring-[var(--border-color-focus)] focus:outline-none"
|
|
134
134
|
)
|
|
135
135
|
);
|
|
136
136
|
|
|
@@ -156,7 +156,7 @@
|
|
|
156
156
|
|
|
157
157
|
$effect(() => {
|
|
158
158
|
if (autoplay && hasItems && items.length > 1) {
|
|
159
|
-
autoplayTimer = setInterval(next, interval);
|
|
159
|
+
autoplayTimer = setInterval(next, Math.max(1000, interval));
|
|
160
160
|
}
|
|
161
161
|
return () => {
|
|
162
162
|
if (autoplayTimer) {
|
|
@@ -203,7 +203,7 @@
|
|
|
203
203
|
{/snippet}
|
|
204
204
|
|
|
205
205
|
<div
|
|
206
|
-
class="transition-opacity duration-
|
|
206
|
+
class="transition-opacity duration-[var(--transition-normal)] ease-in-out"
|
|
207
207
|
class:opacity-100={i === current}
|
|
208
208
|
class:opacity-0={i !== current}
|
|
209
209
|
class:block={i === current}
|
|
@@ -275,7 +275,7 @@
|
|
|
275
275
|
</div>
|
|
276
276
|
|
|
277
277
|
{#if showDots && hasItems && items.length > 1}
|
|
278
|
-
<div class="flex justify-center gap-
|
|
278
|
+
<div class="flex justify-center gap-[var(--spacing-sm)] p-[var(--spacing-md)]">
|
|
279
279
|
{#each items as item, i (item.id ?? i)}
|
|
280
280
|
<button
|
|
281
281
|
type="button"
|
package/dist/lib/CheckBox.svelte
CHANGED
|
@@ -116,12 +116,12 @@
|
|
|
116
116
|
? state === "checked" || state === "mixed"
|
|
117
117
|
? "var(--border-color-strong)"
|
|
118
118
|
: "var(--border-color-default)"
|
|
119
|
-
: "
|
|
119
|
+
: "var(--color-text-inverse,#fff)"
|
|
120
120
|
);
|
|
121
121
|
|
|
122
122
|
const rootClass = $derived(
|
|
123
123
|
cx(
|
|
124
|
-
"inline-flex items-center cursor-pointer select-none",
|
|
124
|
+
"inline-flex items-center cursor-pointer select-none [@media(pointer:coarse)]:min-h-11",
|
|
125
125
|
gapBySize[sz],
|
|
126
126
|
externalClass
|
|
127
127
|
)
|
package/dist/lib/CodeView.svelte
CHANGED
|
@@ -139,6 +139,7 @@
|
|
|
139
139
|
});
|
|
140
140
|
|
|
141
141
|
async function copyToClipboard() {
|
|
142
|
+
if (typeof navigator === "undefined" || !navigator.clipboard) return;
|
|
142
143
|
await navigator.clipboard.writeText(code);
|
|
143
144
|
copied = true;
|
|
144
145
|
setTimeout(() => (copied = false), 1200);
|
|
@@ -155,7 +156,7 @@
|
|
|
155
156
|
{#if title}
|
|
156
157
|
<div
|
|
157
158
|
class={cx(
|
|
158
|
-
|
|
159
|
+
"px-[calc(var(--spacing-sm)+var(--spacing-xs))] py-[var(--spacing-xs)] bg-[var(--color-bg-muted)] font-semibold uppercase flex items-center justify-between",
|
|
159
160
|
TEXT[sz]
|
|
160
161
|
)}
|
|
161
162
|
>
|
|
@@ -165,7 +166,7 @@
|
|
|
165
166
|
<button
|
|
166
167
|
onclick={copyToClipboard}
|
|
167
168
|
class={cx(
|
|
168
|
-
"px-
|
|
169
|
+
"px-[calc(var(--spacing-sm)+var(--spacing-xs))] py-[calc(var(--spacing-xs)/2)] [font-size:var(--text-xs)] rounded-[var(--radius-sm)] bg-[var(--color-primary)] text-[var(--color-text-inverse,#fff)] hover:opacity-[var(--opacity-hover)]",
|
|
169
170
|
"transition focus-visible:ring-2 focus-visible:ring-[var(--border-color-focus)] focus:outline-none"
|
|
170
171
|
)}
|
|
171
172
|
class:!bg-green-600={copied}
|
|
@@ -187,7 +188,7 @@
|
|
|
187
188
|
<div
|
|
188
189
|
bind:this={gutterEl}
|
|
189
190
|
class={cx(
|
|
190
|
-
"select-none px-
|
|
191
|
+
"select-none px-[calc(var(--spacing-sm)+var(--spacing-xs))] py-[calc(var(--spacing-sm)+var(--spacing-xs))] border-r border-[var(--border-color-default)]",
|
|
191
192
|
"text-[var(--color-text-muted)] text-right overflow-hidden",
|
|
192
193
|
"cv-gutter bg-[var(--color-bg-surface)] tabular-nums h-full min-h-0"
|
|
193
194
|
)}
|
|
@@ -232,8 +233,8 @@
|
|
|
232
233
|
<style>
|
|
233
234
|
.cv-layer {
|
|
234
235
|
position: absolute;
|
|
235
|
-
padding:
|
|
236
|
-
white-space: pre;
|
|
236
|
+
padding: calc(var(--spacing-sm) + var(--spacing-xs));
|
|
237
|
+
white-space: var(--code-white-space, pre);
|
|
237
238
|
box-sizing: border-box;
|
|
238
239
|
font: inherit;
|
|
239
240
|
line-height: inherit;
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
* @note `clearable=false` hides the clear button; when `disabled`, pointer/keyboard handlers are skipped.
|
|
29
29
|
*/
|
|
30
30
|
import type { HTMLAttributes } from "svelte/elements";
|
|
31
|
-
import Button from "./Button.svelte";
|
|
32
|
-
import { cx } from "../utils";
|
|
33
|
-
import { getComponentText, getLangContext, getLangKey } from "./lang-context";
|
|
31
|
+
import Button from "./Button.svelte";
|
|
32
|
+
import { cx } from "../utils";
|
|
33
|
+
import { getComponentText, getLangContext, getLangKey } from "./lang-context";
|
|
34
34
|
|
|
35
35
|
type Props = HTMLAttributes<HTMLDivElement> & {
|
|
36
36
|
value?: string | null;
|
|
@@ -53,9 +53,9 @@
|
|
|
53
53
|
...rest
|
|
54
54
|
}: Props = $props();
|
|
55
55
|
|
|
56
|
-
const langCtx = getLangContext();
|
|
57
|
-
const langKey = $derived(getLangKey(langCtx));
|
|
58
|
-
const L = $derived(getComponentText("colorPicker", langKey));
|
|
56
|
+
const langCtx = getLangContext();
|
|
57
|
+
const langKey = $derived(getLangKey(langCtx));
|
|
58
|
+
const L = $derived(getComponentText("colorPicker", langKey));
|
|
59
59
|
|
|
60
60
|
const labelFinal = $derived(label ?? L.text);
|
|
61
61
|
const placeholderFinal = $derived(placeholder ?? L.placeholder);
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
<!-- src/lib/ContextMenu.svelte -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* @component ContextMenu
|
|
5
|
+
* @description Right-click context menu for editor actions.
|
|
6
|
+
*
|
|
7
|
+
* @prop onUndo {() => void} - Fired when Undo is selected
|
|
8
|
+
* @prop onRedo {() => void} - Fired when Redo is selected
|
|
9
|
+
* @prop onCopy {() => void} - Fired when Copy is selected
|
|
10
|
+
* @prop onCut {() => void} - Fired when Cut is selected
|
|
11
|
+
* @prop onPaste {() => void} - Fired when Paste is selected
|
|
12
|
+
* @prop onDelete {() => void} - Fired when Delete is selected
|
|
13
|
+
*
|
|
14
|
+
* @note Call `openAt(event)` to show the menu at the pointer.
|
|
15
|
+
* @note Uses lang-context for localization.
|
|
16
|
+
*/
|
|
17
|
+
import {
|
|
18
|
+
getComponentText,
|
|
19
|
+
getLangContext,
|
|
20
|
+
getLangKey,
|
|
21
|
+
} from "./lang-context";
|
|
22
|
+
|
|
23
|
+
interface Props {
|
|
24
|
+
onUndo?: () => void;
|
|
25
|
+
onRedo?: () => void;
|
|
26
|
+
onCopy?: () => void;
|
|
27
|
+
onCut?: () => void;
|
|
28
|
+
onPaste?: () => void;
|
|
29
|
+
onDelete?: () => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let {
|
|
33
|
+
onUndo = () => {},
|
|
34
|
+
onRedo = () => {},
|
|
35
|
+
onCopy = () => {},
|
|
36
|
+
onCut = () => {},
|
|
37
|
+
onPaste = () => {},
|
|
38
|
+
onDelete = () => {},
|
|
39
|
+
}: Props = $props();
|
|
40
|
+
const langCtx = getLangContext();
|
|
41
|
+
const langKey = $derived(getLangKey(langCtx));
|
|
42
|
+
const L = $derived(getComponentText("contextMenu", langKey));
|
|
43
|
+
|
|
44
|
+
let visible = $state(false);
|
|
45
|
+
let x = $state(0);
|
|
46
|
+
let y = $state(0);
|
|
47
|
+
|
|
48
|
+
function clampToViewport(): void {
|
|
49
|
+
requestAnimationFrame(() => {
|
|
50
|
+
const menu = document.getElementById("ctx-menu");
|
|
51
|
+
if (!menu) return;
|
|
52
|
+
const rect = menu.getBoundingClientRect();
|
|
53
|
+
const pad = 8;
|
|
54
|
+
let nextX = x;
|
|
55
|
+
let nextY = y;
|
|
56
|
+
if (rect.right > window.innerWidth - pad) {
|
|
57
|
+
nextX = Math.max(
|
|
58
|
+
pad,
|
|
59
|
+
x - (rect.right - (window.innerWidth - pad))
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
if (rect.bottom > window.innerHeight - pad) {
|
|
63
|
+
nextY = Math.max(
|
|
64
|
+
pad,
|
|
65
|
+
y - (rect.bottom - (window.innerHeight - pad))
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
if (nextX !== x) x = nextX;
|
|
69
|
+
if (nextY !== y) y = nextY;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function openAt(event: MouseEvent): void {
|
|
74
|
+
event?.preventDefault?.();
|
|
75
|
+
const hasClient =
|
|
76
|
+
Number.isFinite(event?.clientX) && Number.isFinite(event?.clientY);
|
|
77
|
+
const hasPage =
|
|
78
|
+
Number.isFinite(event?.pageX) && Number.isFinite(event?.pageY);
|
|
79
|
+
if (hasClient && (event.clientX !== 0 || event.clientY !== 0)) {
|
|
80
|
+
x = event.clientX;
|
|
81
|
+
y = event.clientY;
|
|
82
|
+
} else if (hasPage) {
|
|
83
|
+
x = event.pageX - window.scrollX;
|
|
84
|
+
y = event.pageY - window.scrollY;
|
|
85
|
+
} else {
|
|
86
|
+
x = 0;
|
|
87
|
+
y = 0;
|
|
88
|
+
}
|
|
89
|
+
visible = true;
|
|
90
|
+
clampToViewport();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function close(): void {
|
|
94
|
+
visible = false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function handleDocClick(e: MouseEvent): void {
|
|
98
|
+
if (!visible) return;
|
|
99
|
+
const menu = document.getElementById("ctx-menu");
|
|
100
|
+
if (!menu) return;
|
|
101
|
+
if (!menu.contains(e.target as Node)) close();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function handleEsc(e: KeyboardEvent): void {
|
|
105
|
+
if (e.key === "Escape" && visible) close();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
$effect(() => {
|
|
109
|
+
document.addEventListener("click", handleDocClick);
|
|
110
|
+
document.addEventListener("keydown", handleEsc);
|
|
111
|
+
return () => {
|
|
112
|
+
document.removeEventListener("click", handleDocClick);
|
|
113
|
+
document.removeEventListener("keydown", handleEsc);
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const doUndo = () => (onUndo(), close());
|
|
118
|
+
const doRedo = () => (onRedo(), close());
|
|
119
|
+
const doCopy = () => (onCopy(), close());
|
|
120
|
+
const doCut = () => (onCut(), close());
|
|
121
|
+
const doPaste = () => (onPaste(), close());
|
|
122
|
+
const doDelete = () => (onDelete(), close());
|
|
123
|
+
|
|
124
|
+
const menuPanelClass =
|
|
125
|
+
"fixed bg-[var(--color-bg-surface)] border border-[var(--border-color-default)] rounded-[var(--radius-md)] min-w-[160px] max-w-[260px] py-[var(--spacing-xs)] z-[9999] box-border text-[var(--text-sm)] shadow-[0_2px_4px_var(--shadow-color)] font-[var(--font-sans)] text-[var(--color-text-default)] m-0 scale-90 origin-top-left";
|
|
126
|
+
const itemClass =
|
|
127
|
+
"w-full flex items-center justify-between bg-transparent border-0 text-[var(--color-text-default)] px-[calc(var(--spacing-sm)+var(--spacing-xs))] py-[var(--spacing-sm)] m-0 font-inherit cursor-pointer rounded-[var(--radius-sm)] whitespace-nowrap leading-[var(--line-height-normal)] gap-[calc(var(--spacing-sm)+var(--spacing-xs))] outline-none shadow-none relative hover:bg-[var(--color-bg-hover)] hover:text-[var(--color-text-default)] active:bg-[color-mix(in_srgb,var(--color-primary)_12%,var(--color-bg-hover)_88%)] active:text-[var(--color-text-default)] transition-colors duration-[var(--transition-fast)]";
|
|
128
|
+
const itemContentClass = "flex items-center gap-[calc(var(--spacing-sm)+var(--spacing-xs)/2)]";
|
|
129
|
+
</script>
|
|
130
|
+
|
|
131
|
+
{#if visible}
|
|
132
|
+
<div
|
|
133
|
+
id="ctx-menu"
|
|
134
|
+
class={menuPanelClass}
|
|
135
|
+
style="top: {y}px; left: {x}px;"
|
|
136
|
+
role="menu"
|
|
137
|
+
tabindex="-1"
|
|
138
|
+
>
|
|
139
|
+
<button
|
|
140
|
+
class={itemClass}
|
|
141
|
+
onclick={(e) => {
|
|
142
|
+
e.stopPropagation();
|
|
143
|
+
doUndo();
|
|
144
|
+
}}
|
|
145
|
+
title={L.hotkeys.undo}
|
|
146
|
+
>
|
|
147
|
+
<div class={itemContentClass}>
|
|
148
|
+
<svg
|
|
149
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
150
|
+
width="24"
|
|
151
|
+
height="24"
|
|
152
|
+
viewBox="0 0 24 24"
|
|
153
|
+
fill="none"
|
|
154
|
+
stroke="currentColor"
|
|
155
|
+
stroke-width="2"
|
|
156
|
+
stroke-linecap="round"
|
|
157
|
+
stroke-linejoin="round"
|
|
158
|
+
class="w-4 h-4 opacity-80"
|
|
159
|
+
aria-hidden="true"
|
|
160
|
+
>
|
|
161
|
+
<path d="M3 7v6h6" />
|
|
162
|
+
<path d="M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13" />
|
|
163
|
+
</svg>
|
|
164
|
+
<span>{L.contextMenu.undo.replace(/↩️\s*/, "")}</span>
|
|
165
|
+
</div>
|
|
166
|
+
<span class="text-[var(--color-text-muted)] text-[0.7rem]">{L.hotkeys.undo}</span>
|
|
167
|
+
</button>
|
|
168
|
+
|
|
169
|
+
<button
|
|
170
|
+
class={itemClass}
|
|
171
|
+
onclick={(e) => {
|
|
172
|
+
e.stopPropagation();
|
|
173
|
+
doRedo();
|
|
174
|
+
}}
|
|
175
|
+
title={L.hotkeys.redo}
|
|
176
|
+
>
|
|
177
|
+
<div class={itemContentClass}>
|
|
178
|
+
<svg
|
|
179
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
180
|
+
width="24"
|
|
181
|
+
height="24"
|
|
182
|
+
viewBox="0 0 24 24"
|
|
183
|
+
fill="none"
|
|
184
|
+
stroke="currentColor"
|
|
185
|
+
stroke-width="2"
|
|
186
|
+
stroke-linecap="round"
|
|
187
|
+
stroke-linejoin="round"
|
|
188
|
+
class="w-4 h-4 opacity-80"
|
|
189
|
+
aria-hidden="true"
|
|
190
|
+
>
|
|
191
|
+
<path d="M21 7v6h-6" />
|
|
192
|
+
<path d="M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3l3 2.7" />
|
|
193
|
+
</svg>
|
|
194
|
+
<span>{L.contextMenu.redo.replace(/↪️\s*/, "")}</span>
|
|
195
|
+
</div>
|
|
196
|
+
<span class="text-[var(--color-text-muted)] text-[0.7rem]">{L.hotkeys.redo}</span>
|
|
197
|
+
</button>
|
|
198
|
+
|
|
199
|
+
<button
|
|
200
|
+
class={itemClass}
|
|
201
|
+
onclick={(e) => {
|
|
202
|
+
e.stopPropagation();
|
|
203
|
+
doCopy();
|
|
204
|
+
}}
|
|
205
|
+
title={L.hotkeys.copy}
|
|
206
|
+
>
|
|
207
|
+
<div class={itemContentClass}>
|
|
208
|
+
<svg
|
|
209
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
210
|
+
width="24"
|
|
211
|
+
height="24"
|
|
212
|
+
viewBox="0 0 24 24"
|
|
213
|
+
fill="none"
|
|
214
|
+
stroke="currentColor"
|
|
215
|
+
stroke-width="2"
|
|
216
|
+
stroke-linecap="round"
|
|
217
|
+
stroke-linejoin="round"
|
|
218
|
+
class="w-4 h-4 opacity-80"
|
|
219
|
+
aria-hidden="true"
|
|
220
|
+
>
|
|
221
|
+
<rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
|
|
222
|
+
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
|
|
223
|
+
</svg>
|
|
224
|
+
<span>{L.contextMenu.copy.replace(/📑\s*/, "")}</span>
|
|
225
|
+
</div>
|
|
226
|
+
<span class="text-[var(--color-text-muted)] text-[0.7rem]">{L.hotkeys.copy}</span>
|
|
227
|
+
</button>
|
|
228
|
+
|
|
229
|
+
<button
|
|
230
|
+
class={itemClass}
|
|
231
|
+
onclick={(e) => {
|
|
232
|
+
e.stopPropagation();
|
|
233
|
+
doCut();
|
|
234
|
+
}}
|
|
235
|
+
title={L.hotkeys.cut}
|
|
236
|
+
>
|
|
237
|
+
<div class={itemContentClass}>
|
|
238
|
+
<svg
|
|
239
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
240
|
+
width="24"
|
|
241
|
+
height="24"
|
|
242
|
+
viewBox="0 0 24 24"
|
|
243
|
+
fill="none"
|
|
244
|
+
stroke="currentColor"
|
|
245
|
+
stroke-width="2"
|
|
246
|
+
stroke-linecap="round"
|
|
247
|
+
stroke-linejoin="round"
|
|
248
|
+
class="w-4 h-4 opacity-80"
|
|
249
|
+
aria-hidden="true"
|
|
250
|
+
>
|
|
251
|
+
<circle cx="6" cy="6" r="3" />
|
|
252
|
+
<path d="M8.12 8.12 12 12" />
|
|
253
|
+
<path d="M20 4 8.12 15.88" />
|
|
254
|
+
<circle cx="6" cy="18" r="3" />
|
|
255
|
+
<path d="M14.8 14.8 20 20" />
|
|
256
|
+
</svg>
|
|
257
|
+
<span>{L.contextMenu.cut.replace(/✂️\s*/, "")}</span>
|
|
258
|
+
</div>
|
|
259
|
+
<span class="text-[var(--color-text-muted)] text-[0.7rem]">{L.hotkeys.cut}</span>
|
|
260
|
+
</button>
|
|
261
|
+
|
|
262
|
+
<button
|
|
263
|
+
class={itemClass}
|
|
264
|
+
onclick={(e) => {
|
|
265
|
+
e.stopPropagation();
|
|
266
|
+
doPaste();
|
|
267
|
+
}}
|
|
268
|
+
title={L.hotkeys.paste}
|
|
269
|
+
>
|
|
270
|
+
<div class={itemContentClass}>
|
|
271
|
+
<svg
|
|
272
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
273
|
+
width="24"
|
|
274
|
+
height="24"
|
|
275
|
+
viewBox="0 0 24 24"
|
|
276
|
+
fill="none"
|
|
277
|
+
stroke="currentColor"
|
|
278
|
+
stroke-width="2"
|
|
279
|
+
stroke-linecap="round"
|
|
280
|
+
stroke-linejoin="round"
|
|
281
|
+
class="w-4 h-4 opacity-80"
|
|
282
|
+
aria-hidden="true"
|
|
283
|
+
>
|
|
284
|
+
<path d="M11 14h10" />
|
|
285
|
+
<path d="M16 4h2a2 2 0 0 1 2 2v1.344" />
|
|
286
|
+
<path d="m17 18 4-4-4-4" />
|
|
287
|
+
<path d="M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 1.793-1.113" />
|
|
288
|
+
<rect x="8" y="2" width="8" height="4" rx="1" />
|
|
289
|
+
</svg>
|
|
290
|
+
<span>{L.contextMenu.paste.replace(/📋\s*/, "")}</span>
|
|
291
|
+
</div>
|
|
292
|
+
<span class="text-[var(--color-text-muted)] text-[0.7rem]">{L.hotkeys.paste}</span>
|
|
293
|
+
</button>
|
|
294
|
+
|
|
295
|
+
<button
|
|
296
|
+
class={itemClass}
|
|
297
|
+
onclick={(e) => {
|
|
298
|
+
e.stopPropagation();
|
|
299
|
+
doDelete();
|
|
300
|
+
}}
|
|
301
|
+
title={L.hotkeys.delete}
|
|
302
|
+
>
|
|
303
|
+
<div class={itemContentClass}>
|
|
304
|
+
<svg
|
|
305
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
306
|
+
width="24"
|
|
307
|
+
height="24"
|
|
308
|
+
viewBox="0 0 24 24"
|
|
309
|
+
fill="none"
|
|
310
|
+
stroke="currentColor"
|
|
311
|
+
stroke-width="2"
|
|
312
|
+
stroke-linecap="round"
|
|
313
|
+
stroke-linejoin="round"
|
|
314
|
+
class="w-4 h-4 opacity-80"
|
|
315
|
+
aria-hidden="true"
|
|
316
|
+
>
|
|
317
|
+
<path d="M10 11v6" />
|
|
318
|
+
<path d="M14 11v6" />
|
|
319
|
+
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" />
|
|
320
|
+
<path d="M3 6h18" />
|
|
321
|
+
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
|
322
|
+
</svg>
|
|
323
|
+
<span>{L.contextMenu.delete}</span>
|
|
324
|
+
</div>
|
|
325
|
+
<span class="text-[var(--color-text-muted)] text-[0.7rem]">{L.hotkeys.delete}</span>
|
|
326
|
+
</button>
|
|
327
|
+
</div>
|
|
328
|
+
{/if}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
onUndo?: () => void;
|
|
3
|
+
onRedo?: () => void;
|
|
4
|
+
onCopy?: () => void;
|
|
5
|
+
onCut?: () => void;
|
|
6
|
+
onPaste?: () => void;
|
|
7
|
+
onDelete?: () => void;
|
|
8
|
+
}
|
|
9
|
+
declare const ContextMenu: import("svelte").Component<Props, {
|
|
10
|
+
openAt: (event: MouseEvent) => void;
|
|
11
|
+
close: () => void;
|
|
12
|
+
}, "">;
|
|
13
|
+
type ContextMenu = ReturnType<typeof ContextMenu>;
|
|
14
|
+
export default ContextMenu;
|