twintrinsic 0.0.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/LICENSE +674 -0
- package/README.md +150 -0
- package/dist/App/App.svelte +54 -0
- package/dist/App/App.svelte.d.ts +65 -0
- package/dist/Section.svelte +25 -0
- package/dist/Section.svelte.d.ts +34 -0
- package/dist/actions/clickOutside.d.ts +9 -0
- package/dist/actions/clickOutside.js +19 -0
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/components/Accordion/Accordion.svelte +75 -0
- package/dist/components/Accordion/Accordion.svelte.d.ts +39 -0
- package/dist/components/Accordion/AccordionItem.svelte +150 -0
- package/dist/components/Accordion/AccordionItem.svelte.d.ts +30 -0
- package/dist/components/App/App.story.md +8 -0
- package/dist/components/App/App.story.svelte +170 -0
- package/dist/components/App/App.story.svelte.d.ts +22 -0
- package/dist/components/App/App.svelte +77 -0
- package/dist/components/App/App.svelte.d.ts +66 -0
- package/dist/components/App/Split.svelte +346 -0
- package/dist/components/App/Split.svelte.d.ts +54 -0
- package/dist/components/App/index.d.ts +2 -0
- package/dist/components/App/index.js +3 -0
- package/dist/components/AppHeader/AppHeader.svelte +439 -0
- package/dist/components/AppHeader/AppHeader.svelte.d.ts +24 -0
- package/dist/components/Avatar/Avatar.svelte +300 -0
- package/dist/components/Avatar/Avatar.svelte.d.ts +48 -0
- package/dist/components/Avatar/AvatarGroup.svelte +185 -0
- package/dist/components/Avatar/AvatarGroup.svelte.d.ts +46 -0
- package/dist/components/Badge/Badge.svelte +186 -0
- package/dist/components/Badge/Badge.svelte.d.ts +51 -0
- package/dist/components/BottomBar/BottomBar.svelte +146 -0
- package/dist/components/BottomBar/BottomBar.svelte.d.ts +38 -0
- package/dist/components/Breadcrumb/Breadcrumb.svelte +77 -0
- package/dist/components/Breadcrumb/Breadcrumb.svelte.d.ts +42 -0
- package/dist/components/Breadcrumb/BreadcrumbItem.svelte +171 -0
- package/dist/components/Breadcrumb/BreadcrumbItem.svelte.d.ts +38 -0
- package/dist/components/Button/Button.svelte +252 -0
- package/dist/components/Button/Button.svelte.d.ts +80 -0
- package/dist/components/Button/ButtonGroup.svelte +127 -0
- package/dist/components/Button/ButtonGroup.svelte.d.ts +44 -0
- package/dist/components/Card/Card.svelte +152 -0
- package/dist/components/Card/Card.svelte.d.ts +55 -0
- package/dist/components/Carousel/Carousel.svelte +461 -0
- package/dist/components/Carousel/Carousel.svelte.d.ts +79 -0
- package/dist/components/Carousel/CarouselItem.svelte +149 -0
- package/dist/components/Carousel/CarouselItem.svelte.d.ts +35 -0
- package/dist/components/Chip/Chip.svelte +288 -0
- package/dist/components/Chip/Chip.svelte.d.ts +71 -0
- package/dist/components/Chip/ChipGroup.svelte +190 -0
- package/dist/components/Chip/ChipGroup.svelte.d.ts +71 -0
- package/dist/components/CodeBlock/CodeBlock.svelte +356 -0
- package/dist/components/CodeBlock/CodeBlock.svelte.d.ts +44 -0
- package/dist/components/CodeBlock/index.d.ts +1 -0
- package/dist/components/CodeBlock/index.js +1 -0
- package/dist/components/CodeBlockSpeed/CodeBlockSpeed.svelte +145 -0
- package/dist/components/CodeBlockSpeed/CodeBlockSpeed.svelte.d.ts +44 -0
- package/dist/components/CodeEditor/CodeEditor.svelte +229 -0
- package/dist/components/CodeEditor/CodeEditor.svelte.d.ts +23 -0
- package/dist/components/Combobox/Combobox.svelte +279 -0
- package/dist/components/Combobox/Combobox.svelte.d.ts +34 -0
- package/dist/components/Container/Container.svelte +45 -0
- package/dist/components/Container/Container.svelte.d.ts +36 -0
- package/dist/components/DataTable/DataTable.svelte +879 -0
- package/dist/components/DataTable/DataTable.svelte.d.ts +102 -0
- package/dist/components/Form/AutoComplete.svelte +357 -0
- package/dist/components/Form/AutoComplete.svelte.d.ts +73 -0
- package/dist/components/Form/Calendar.svelte +429 -0
- package/dist/components/Form/Calendar.svelte.d.ts +53 -0
- package/dist/components/Form/Checkbox.svelte +196 -0
- package/dist/components/Form/Checkbox.svelte.d.ts +50 -0
- package/dist/components/Form/ColorPicker.svelte +396 -0
- package/dist/components/Form/ColorPicker.svelte.d.ts +43 -0
- package/dist/components/Form/Combobox.svelte +645 -0
- package/dist/components/Form/Combobox.svelte.d.ts +93 -0
- package/dist/components/Form/Dropdown.svelte +773 -0
- package/dist/components/Form/Dropdown.svelte.d.ts +81 -0
- package/dist/components/Form/FileUpload.svelte +796 -0
- package/dist/components/Form/FileUpload.svelte.d.ts +78 -0
- package/dist/components/Form/FloatLabel.svelte +245 -0
- package/dist/components/Form/FloatLabel.svelte.d.ts +44 -0
- package/dist/components/Form/Form.svelte +281 -0
- package/dist/components/Form/Form.svelte.d.ts +54 -0
- package/dist/components/Form/FormField.svelte +218 -0
- package/dist/components/Form/FormField.svelte.d.ts +47 -0
- package/dist/components/Form/Input.svelte +340 -0
- package/dist/components/Form/Input.svelte.d.ts +79 -0
- package/dist/components/Form/InputSwitch.svelte +189 -0
- package/dist/components/Form/InputSwitch.svelte.d.ts +46 -0
- package/dist/components/Form/InvalidState.svelte +97 -0
- package/dist/components/Form/InvalidState.svelte.d.ts +37 -0
- package/dist/components/Form/Knob.svelte +537 -0
- package/dist/components/Form/Knob.svelte.d.ts +78 -0
- package/dist/components/Form/ListInput.svelte +469 -0
- package/dist/components/Form/ListInput.svelte.d.ts +70 -0
- package/dist/components/Form/Listbox.svelte +513 -0
- package/dist/components/Form/Listbox.svelte.d.ts +74 -0
- package/dist/components/Form/NumberInput.svelte +452 -0
- package/dist/components/Form/NumberInput.svelte.d.ts +82 -0
- package/dist/components/Form/Radio.svelte +192 -0
- package/dist/components/Form/Radio.svelte.d.ts +53 -0
- package/dist/components/Form/RadioGroup.svelte +155 -0
- package/dist/components/Form/RadioGroup.svelte.d.ts +48 -0
- package/dist/components/Form/Rating.svelte +380 -0
- package/dist/components/Form/Rating.svelte.d.ts +64 -0
- package/dist/components/Form/Select.svelte +436 -0
- package/dist/components/Form/Select.svelte.d.ts +49 -0
- package/dist/components/Form/SelectGroup.svelte +34 -0
- package/dist/components/Form/SelectGroup.svelte.d.ts +33 -0
- package/dist/components/Form/Slider.svelte +622 -0
- package/dist/components/Form/Slider.svelte.d.ts +73 -0
- package/dist/components/Form/Switch.svelte +192 -0
- package/dist/components/Form/Switch.svelte.d.ts +46 -0
- package/dist/components/Form/TextInput.svelte +274 -0
- package/dist/components/Form/TextInput.svelte.d.ts +74 -0
- package/dist/components/Form/Textarea.svelte +207 -0
- package/dist/components/Form/Textarea.svelte.d.ts +62 -0
- package/dist/components/Icon/Icon.svelte +140 -0
- package/dist/components/Icon/Icon.svelte.d.ts +25 -0
- package/dist/components/Icon/index.d.ts +1 -0
- package/dist/components/Icon/index.js +1 -0
- package/dist/components/Lazy/Lazy.svelte +158 -0
- package/dist/components/Lazy/Lazy.svelte.d.ts +42 -0
- package/dist/components/Masonry/Masonry.svelte +299 -0
- package/dist/components/Masonry/Masonry.svelte.d.ts +55 -0
- package/dist/components/Menu/Menu/Menu.svelte +65 -0
- package/dist/components/Menu/Menu/Menu.svelte.d.ts +17 -0
- package/dist/components/Menu/Menu/MenuItem.svelte +90 -0
- package/dist/components/Menu/Menu/MenuItem.svelte.d.ts +27 -0
- package/dist/components/Modal/Modal.svelte +334 -0
- package/dist/components/Modal/Modal.svelte.d.ts +55 -0
- package/dist/components/Panel/Card.svelte +141 -0
- package/dist/components/Panel/Card.svelte.d.ts +52 -0
- package/dist/components/Panel/Hero/Hero.story.md +9 -0
- package/dist/components/Panel/Hero/Hero.story.svelte +49 -0
- package/dist/components/Panel/Hero/Hero.story.svelte.d.ts +21 -0
- package/dist/components/Panel/Hero/Hero.svelte +24 -0
- package/dist/components/Panel/Hero/Hero.svelte.d.ts +32 -0
- package/dist/components/Panel/LazyPanel.svelte +110 -0
- package/dist/components/Panel/LazyPanel.svelte.d.ts +46 -0
- package/dist/components/Panel/Panel.svelte +205 -0
- package/dist/components/Panel/Panel.svelte.d.ts +23 -0
- package/dist/components/Progress/Progress.svelte +220 -0
- package/dist/components/Progress/Progress.svelte.d.ts +61 -0
- package/dist/components/Separator/Separator.svelte +109 -0
- package/dist/components/Separator/Separator.svelte.d.ts +35 -0
- package/dist/components/Sidebar/Sidebar.svelte +213 -0
- package/dist/components/Sidebar/Sidebar.svelte.d.ts +60 -0
- package/dist/components/Skeleton/Skeleton.svelte +170 -0
- package/dist/components/Skeleton/Skeleton.svelte.d.ts +48 -0
- package/dist/components/Stepper/Stepper.svelte +111 -0
- package/dist/components/Stepper/Stepper.svelte.d.ts +54 -0
- package/dist/components/Stepper/StepperStep.svelte +369 -0
- package/dist/components/Stepper/StepperStep.svelte.d.ts +63 -0
- package/dist/components/Table/Table.svelte +167 -0
- package/dist/components/Table/Table.svelte.d.ts +56 -0
- package/dist/components/Table/TableBody.svelte +41 -0
- package/dist/components/Table/TableBody.svelte.d.ts +33 -0
- package/dist/components/Table/TableCell.svelte +76 -0
- package/dist/components/Table/TableCell.svelte.d.ts +36 -0
- package/dist/components/Table/TableHead.svelte +41 -0
- package/dist/components/Table/TableHead.svelte.d.ts +32 -0
- package/dist/components/Table/TableHeader.svelte +148 -0
- package/dist/components/Table/TableHeader.svelte.d.ts +42 -0
- package/dist/components/Table/TableRow.svelte +99 -0
- package/dist/components/Table/TableRow.svelte.d.ts +40 -0
- package/dist/components/Tabs/Tab.svelte +145 -0
- package/dist/components/Tabs/Tab.svelte.d.ts +36 -0
- package/dist/components/Tabs/TabList.svelte +60 -0
- package/dist/components/Tabs/TabList.svelte.d.ts +32 -0
- package/dist/components/Tabs/TabPanel.svelte +118 -0
- package/dist/components/Tabs/TabPanel.svelte.d.ts +38 -0
- package/dist/components/Tabs/Tabs.svelte +287 -0
- package/dist/components/Tabs/Tabs.svelte.d.ts +50 -0
- package/dist/components/Tag/Tag.svelte +260 -0
- package/dist/components/Tag/Tag.svelte.d.ts +54 -0
- package/dist/components/Tag/TagGroup.svelte +147 -0
- package/dist/components/Tag/TagGroup.svelte.d.ts +62 -0
- package/dist/components/ThemeToggle/ThemeToggle.svelte +93 -0
- package/dist/components/ThemeToggle/ThemeToggle.svelte.d.ts +12 -0
- package/dist/components/Timeline/Timeline.svelte +144 -0
- package/dist/components/Timeline/Timeline.svelte.d.ts +48 -0
- package/dist/components/Timeline/TimelineItem.svelte +391 -0
- package/dist/components/Timeline/TimelineItem.svelte.d.ts +63 -0
- package/dist/components/Toast/Toast.svelte +313 -0
- package/dist/components/Toast/Toast.svelte.d.ts +44 -0
- package/dist/components/Toast/toastStore.d.ts +40 -0
- package/dist/components/Toast/toastStore.js +293 -0
- package/dist/components/Tooltip/Tooltip.svelte +282 -0
- package/dist/components/Tooltip/Tooltip.svelte.d.ts +55 -0
- package/dist/components/Tree/Tree.svelte +129 -0
- package/dist/components/Tree/Tree.svelte.d.ts +61 -0
- package/dist/components/Tree/TreeNode.svelte +332 -0
- package/dist/components/Tree/TreeNode.svelte.d.ts +55 -0
- package/dist/components/icons/TwintrinsicLogo.svelte +73 -0
- package/dist/components/icons/TwintrinsicLogo.svelte.d.ts +17 -0
- package/dist/components/icons/twintrinsic-source.svg +73 -0
- package/dist/components/icons/twintrinsic.svg +38 -0
- package/dist/docs/EventsTable.svelte +86 -0
- package/dist/docs/EventsTable.svelte.d.ts +27 -0
- package/dist/docs/PropsTable.svelte +103 -0
- package/dist/docs/PropsTable.svelte.d.ts +28 -0
- package/dist/docs/index.d.ts +2 -0
- package/dist/docs/index.js +2 -0
- package/dist/helpers/detectLanguage.d.ts +6 -0
- package/dist/helpers/detectLanguage.js +60 -0
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.js +1 -0
- package/dist/index.d.ts +86 -0
- package/dist/index.js +94 -0
- package/dist/twintrinsic.css +347 -0
- package/package.json +98 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { highlightElement } from '@speed-highlight/core';
|
|
3
|
+
import { detectLanguage } from '@speed-highlight/core/detect';
|
|
4
|
+
import { onDestroy, onMount } from 'svelte';
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
/** @type {string} - The language for syntax highlighting */
|
|
8
|
+
language = '',
|
|
9
|
+
/** @type {string} - Additional CSS classes */
|
|
10
|
+
class: className = '',
|
|
11
|
+
/** @type {boolean} - Whether to show rendering time */
|
|
12
|
+
showRenderTime = false,
|
|
13
|
+
} = $props();
|
|
14
|
+
|
|
15
|
+
// className is used in the template below
|
|
16
|
+
|
|
17
|
+
let code = $state('');
|
|
18
|
+
let copied = $state(false);
|
|
19
|
+
let copyTimeout = $state();
|
|
20
|
+
let codeElement = $state();
|
|
21
|
+
let renderTime = $state(0);
|
|
22
|
+
|
|
23
|
+
onMount(() => {
|
|
24
|
+
if (!codeElement) return;
|
|
25
|
+
|
|
26
|
+
code = codeElement.textContent || '';
|
|
27
|
+
|
|
28
|
+
const detectedLang = language || detectLanguage(code);
|
|
29
|
+
|
|
30
|
+
codeElement.className = `shj-lang-${detectedLang}`;
|
|
31
|
+
|
|
32
|
+
if (showRenderTime) {
|
|
33
|
+
const startTime = performance.now();
|
|
34
|
+
highlightElement(codeElement, detectedLang);
|
|
35
|
+
const endTime = performance.now();
|
|
36
|
+
renderTime = Math.round((endTime - startTime) * 100) / 100;
|
|
37
|
+
} else {
|
|
38
|
+
highlightElement(codeElement, detectedLang);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
onDestroy(() => {
|
|
43
|
+
if (copyTimeout) {
|
|
44
|
+
clearTimeout(copyTimeout);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Copy code to clipboard
|
|
50
|
+
*/
|
|
51
|
+
async function copyCode() {
|
|
52
|
+
if (copied) return;
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
await navigator.clipboard.writeText(code);
|
|
56
|
+
copied = true;
|
|
57
|
+
|
|
58
|
+
copyTimeout = setTimeout(() => {
|
|
59
|
+
copied = false;
|
|
60
|
+
}, 2000);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Failed to copy code:', error);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<div class="code-block-speed {className}">
|
|
68
|
+
<div class="code-header">
|
|
69
|
+
<div class="code-header-left">
|
|
70
|
+
{#if language}
|
|
71
|
+
<div class="code-language">{language}</div>
|
|
72
|
+
{/if}
|
|
73
|
+
{#if showRenderTime && renderTime > 0}
|
|
74
|
+
<div class="code-render-time">{renderTime}ms</div>
|
|
75
|
+
{/if}
|
|
76
|
+
</div>
|
|
77
|
+
<button
|
|
78
|
+
type="button"
|
|
79
|
+
class="code-copy"
|
|
80
|
+
onclick={copyCode}
|
|
81
|
+
aria-label={copied ? 'Copied!' : 'Copy code'}
|
|
82
|
+
>
|
|
83
|
+
{#if copied}
|
|
84
|
+
<svg viewBox="0 0 24 24" width="16" height="16">
|
|
85
|
+
<path
|
|
86
|
+
fill="currentColor"
|
|
87
|
+
d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"
|
|
88
|
+
/>
|
|
89
|
+
</svg>
|
|
90
|
+
<span>Copied!</span>
|
|
91
|
+
{:else}
|
|
92
|
+
<svg viewBox="0 0 24 24" width="16" height="16">
|
|
93
|
+
<path
|
|
94
|
+
fill="currentColor"
|
|
95
|
+
d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"
|
|
96
|
+
/>
|
|
97
|
+
</svg>
|
|
98
|
+
<span>Copy</span>
|
|
99
|
+
{/if}
|
|
100
|
+
</button>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<pre class="code-pre"><code bind:this={codeElement}><slot /></code></pre>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<style>
|
|
107
|
+
@reference '../../twintrinsic.css';
|
|
108
|
+
|
|
109
|
+
.code-block-speed {
|
|
110
|
+
@apply relative my-4 rounded-lg overflow-hidden;
|
|
111
|
+
@apply bg-surface border border-border;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.code-header {
|
|
115
|
+
@apply flex items-center justify-between px-4 py-2;
|
|
116
|
+
@apply bg-surface border-b border-border;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.code-header-left {
|
|
120
|
+
@apply flex items-center gap-3;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.code-language {
|
|
124
|
+
@apply text-xs font-mono text-muted;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.code-render-time {
|
|
128
|
+
@apply text-xs font-mono text-primary-600 dark:text-primary-400;
|
|
129
|
+
@apply px-2 py-1 rounded bg-primary-50 dark:bg-primary-900;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.code-copy {
|
|
133
|
+
@apply flex items-center gap-2 px-2 py-1;
|
|
134
|
+
@apply text-xs font-medium text-muted;
|
|
135
|
+
@apply rounded hover:bg-hover;
|
|
136
|
+
@apply transition-colors duration-150;
|
|
137
|
+
@apply focus:outline-none focus:ring-2 focus:ring-primary/50;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.code-pre {
|
|
141
|
+
@apply m-0 p-4 overflow-x-auto;
|
|
142
|
+
@apply font-mono text-sm;
|
|
143
|
+
@apply bg-surface dark:bg-surface;
|
|
144
|
+
}
|
|
145
|
+
</style>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export default CodeBlockSpeed;
|
|
2
|
+
type CodeBlockSpeed = SvelteComponent<$$__sveltets_2_PropsWithChildren<$$ComponentProps, {
|
|
3
|
+
default: {};
|
|
4
|
+
}>, {
|
|
5
|
+
[evt: string]: CustomEvent<any>;
|
|
6
|
+
}, {
|
|
7
|
+
default: {};
|
|
8
|
+
}> & {
|
|
9
|
+
$$bindings?: "" | undefined;
|
|
10
|
+
};
|
|
11
|
+
declare const CodeBlockSpeed: $$__sveltets_2_IsomorphicComponent<$$__sveltets_2_PropsWithChildren<{
|
|
12
|
+
language?: string;
|
|
13
|
+
class?: string;
|
|
14
|
+
showRenderTime?: boolean;
|
|
15
|
+
}, {
|
|
16
|
+
default: {};
|
|
17
|
+
}>, {
|
|
18
|
+
[evt: string]: CustomEvent<any>;
|
|
19
|
+
}, {
|
|
20
|
+
default: {};
|
|
21
|
+
}, {}, "">;
|
|
22
|
+
type $$ComponentProps = {
|
|
23
|
+
language?: string;
|
|
24
|
+
class?: string;
|
|
25
|
+
showRenderTime?: boolean;
|
|
26
|
+
};
|
|
27
|
+
type $$__sveltets_2_PropsWithChildren<Props, Slots> = Props & (Slots extends {
|
|
28
|
+
default: any;
|
|
29
|
+
} ? Props extends Record<string, never> ? any : {
|
|
30
|
+
children?: any;
|
|
31
|
+
} : {});
|
|
32
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
33
|
+
new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
|
|
34
|
+
$$bindings?: Bindings;
|
|
35
|
+
} & Exports;
|
|
36
|
+
(internal: unknown, props: Props & {
|
|
37
|
+
$$events?: Events;
|
|
38
|
+
$$slots?: Slots;
|
|
39
|
+
}): Exports & {
|
|
40
|
+
$set?: any;
|
|
41
|
+
$on?: any;
|
|
42
|
+
};
|
|
43
|
+
z_$$bindings?: Bindings;
|
|
44
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { EditorState } from '@codemirror/state';
|
|
3
|
+
import { EditorView } from '@codemirror/view';
|
|
4
|
+
import { onMount } from 'svelte';
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
/** @type {string} - Initial code content */
|
|
8
|
+
code = '',
|
|
9
|
+
/** @type {string} - Language to use for syntax highlighting */
|
|
10
|
+
language = 'javascript',
|
|
11
|
+
/** @type {string} - Theme name to apply */
|
|
12
|
+
theme = 'light',
|
|
13
|
+
/** @type {string[]} - Array of extension URLs to load dynamically */
|
|
14
|
+
extensions = [],
|
|
15
|
+
/** @type {'jsdelivr' | 'esm.sh' | 'unpkg'} - CDN source for loading extensions */
|
|
16
|
+
cdnSource = 'esm.sh',
|
|
17
|
+
/** @type {(event: CustomEvent<string>) => void | undefined} - Callback when code changes */
|
|
18
|
+
onchange = undefined,
|
|
19
|
+
/** @type {string} - Height of the editor */
|
|
20
|
+
height = '400px',
|
|
21
|
+
} = $props();
|
|
22
|
+
|
|
23
|
+
/** @type {HTMLDivElement | undefined} */
|
|
24
|
+
let container;
|
|
25
|
+
/** @type {any} */
|
|
26
|
+
let view;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Constructs the CDN URL for loading a package
|
|
30
|
+
* @param {string} packageName - Package name
|
|
31
|
+
* @param {string} version - Package version
|
|
32
|
+
* @returns {string} CDN URL
|
|
33
|
+
*/
|
|
34
|
+
function getCdnUrl(packageName, version = 'latest') {
|
|
35
|
+
switch (cdnSource) {
|
|
36
|
+
case 'jsdelivr':
|
|
37
|
+
return `https://cdn.jsdelivr.net/npm/${packageName}@${version}/+esm`;
|
|
38
|
+
case 'unpkg':
|
|
39
|
+
return `https://unpkg.com/${packageName}@${version}?module`;
|
|
40
|
+
case 'esm.sh':
|
|
41
|
+
default:
|
|
42
|
+
return `https://esm.sh/${packageName}@${version}`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Dynamically loads an extension from a CDN
|
|
48
|
+
* @param {string} extensionUrl - URL to load extension from
|
|
49
|
+
* @returns {Promise<any|null>} Loaded extension or null
|
|
50
|
+
*/
|
|
51
|
+
async function loadExtension(extensionUrl) {
|
|
52
|
+
try {
|
|
53
|
+
const module = await import(/* @vite-ignore */ extensionUrl);
|
|
54
|
+
const ext = module.default || Object.values(module)[0];
|
|
55
|
+
return ext;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(`Failed to load extension from ${extensionUrl}:`, error);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Loads language support dynamically
|
|
64
|
+
* @param {string} lang - Language name
|
|
65
|
+
* @returns {Promise<any|null>} Language extension or null
|
|
66
|
+
*/
|
|
67
|
+
async function loadLanguageSupport(lang) {
|
|
68
|
+
/** @type {Record<string, string>} */
|
|
69
|
+
const languageMap = {
|
|
70
|
+
javascript: '@codemirror/lang-javascript',
|
|
71
|
+
typescript: '@codemirror/lang-javascript',
|
|
72
|
+
python: '@codemirror/lang-python',
|
|
73
|
+
html: '@codemirror/lang-html',
|
|
74
|
+
css: '@codemirror/lang-css',
|
|
75
|
+
json: '@codemirror/lang-json',
|
|
76
|
+
xml: '@codemirror/lang-xml',
|
|
77
|
+
markdown: '@codemirror/lang-markdown',
|
|
78
|
+
sql: '@codemirror/lang-sql',
|
|
79
|
+
java: '@codemirror/lang-java',
|
|
80
|
+
cpp: '@codemirror/lang-cpp',
|
|
81
|
+
rust: '@codemirror/lang-rust',
|
|
82
|
+
go: '@codemirror/lang-go',
|
|
83
|
+
php: '@codemirror/lang-php',
|
|
84
|
+
vue: '@codemirror/lang-vue',
|
|
85
|
+
svelte: '@codemirror/lang-svelte',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const packageName = languageMap[lang.toLowerCase()];
|
|
89
|
+
if (!packageName) {
|
|
90
|
+
console.warn(`Language support for ${lang} not available`);
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const url = getCdnUrl(packageName);
|
|
96
|
+
const module = await import(/* @vite-ignore */ url);
|
|
97
|
+
const langFn = module[lang.toLowerCase()] || module.default;
|
|
98
|
+
return langFn?.() || null;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error(`Failed to load language support for ${lang}:`, error);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Loads theme dynamically
|
|
107
|
+
* @param {string} themeName - Theme name
|
|
108
|
+
* @returns {Promise<any|null>} Theme extension or null
|
|
109
|
+
*/
|
|
110
|
+
async function loadTheme(themeName) {
|
|
111
|
+
/** @type {Record<string, string>} */
|
|
112
|
+
const themeMap = {
|
|
113
|
+
'one-dark': '@codemirror/theme-one-dark',
|
|
114
|
+
'dracula': '@codemirror/theme-dracula',
|
|
115
|
+
'material-dark': '@codemirror/theme-material-dark',
|
|
116
|
+
'nord': '@codemirror/theme-nord',
|
|
117
|
+
'solarized-light': '@codemirror/theme-solarized-light',
|
|
118
|
+
'solarized-dark': '@codemirror/theme-solarized-dark',
|
|
119
|
+
'sublime': '@codemirror/theme-sublime',
|
|
120
|
+
'ayu-light': '@codemirror/theme-ayu-light',
|
|
121
|
+
'ayu-dark': '@codemirror/theme-ayu-dark',
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const packageName = themeMap[themeName.toLowerCase()];
|
|
125
|
+
if (!packageName) {
|
|
126
|
+
console.warn(`Theme ${themeName} not available`);
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const url = getCdnUrl(packageName);
|
|
132
|
+
const module = await import(/* @vite-ignore */ url);
|
|
133
|
+
const themeFn = module[themeName.toLowerCase().replace(/-/g, '')] || module.default;
|
|
134
|
+
return themeFn || null;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error(`Failed to load theme ${themeName}:`, error);
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Initializes the editor with all extensions
|
|
143
|
+
*/
|
|
144
|
+
async function initializeEditor() {
|
|
145
|
+
const exts = [];
|
|
146
|
+
|
|
147
|
+
const langExt = await loadLanguageSupport(language);
|
|
148
|
+
if (langExt) exts.push(langExt);
|
|
149
|
+
|
|
150
|
+
if (theme !== 'light') {
|
|
151
|
+
const themeExt = await loadTheme(theme);
|
|
152
|
+
if (themeExt) exts.push(themeExt);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for (const extUrl of extensions) {
|
|
156
|
+
const ext = await loadExtension(extUrl);
|
|
157
|
+
if (ext) exts.push(ext);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
exts.push(EditorView.lineNumbers());
|
|
161
|
+
exts.push(EditorView.highlightActiveLineGutter());
|
|
162
|
+
exts.push(EditorView.foldGutter());
|
|
163
|
+
|
|
164
|
+
const state = EditorState.create({
|
|
165
|
+
doc: code,
|
|
166
|
+
extensions: exts,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
view = new EditorView({
|
|
170
|
+
state,
|
|
171
|
+
parent: container,
|
|
172
|
+
dispatch: (/** @type {any} */ tr) => {
|
|
173
|
+
view.update([tr]);
|
|
174
|
+
if (tr.docChanged) {
|
|
175
|
+
const newCode = tr.newDoc.toString();
|
|
176
|
+
onchange?.(new CustomEvent('change', { detail: newCode }));
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Updates the code content
|
|
184
|
+
* @param {string} newCode - New code to set
|
|
185
|
+
*/
|
|
186
|
+
function setCode(newCode) {
|
|
187
|
+
if (view) {
|
|
188
|
+
const changes = {
|
|
189
|
+
from: 0,
|
|
190
|
+
to: view.state.doc.length,
|
|
191
|
+
insert: newCode,
|
|
192
|
+
};
|
|
193
|
+
view.dispatch({ changes });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Gets the current code content
|
|
199
|
+
* @returns {string} Current code
|
|
200
|
+
*/
|
|
201
|
+
function getCode() {
|
|
202
|
+
return view?.state.doc.toString() || code;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
onMount(() => {
|
|
206
|
+
initializeEditor();
|
|
207
|
+
|
|
208
|
+
return () => {
|
|
209
|
+
if (view) {
|
|
210
|
+
view.destroy();
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
});
|
|
214
|
+
</script>
|
|
215
|
+
|
|
216
|
+
<div
|
|
217
|
+
bind:this={container}
|
|
218
|
+
class="code-editor-wrapper"
|
|
219
|
+
style="height: {height}; overflow: hidden; border: 1px solid var(--color-border, #e5e7eb);"
|
|
220
|
+
></div>
|
|
221
|
+
|
|
222
|
+
<style>
|
|
223
|
+
@reference "../../twintrinsic.css";
|
|
224
|
+
|
|
225
|
+
.code-editor-wrapper {
|
|
226
|
+
font-family: 'Fira Code', 'Courier New', monospace;
|
|
227
|
+
font-size: 14px;
|
|
228
|
+
}
|
|
229
|
+
</style>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export default CodeEditor;
|
|
2
|
+
type CodeEditor = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
declare const CodeEditor: import("svelte").Component<{
|
|
7
|
+
code?: string;
|
|
8
|
+
language?: string;
|
|
9
|
+
theme?: string;
|
|
10
|
+
extensions?: any[];
|
|
11
|
+
cdnSource?: string;
|
|
12
|
+
onchange?: any;
|
|
13
|
+
height?: string;
|
|
14
|
+
}, {}, "">;
|
|
15
|
+
type $$ComponentProps = {
|
|
16
|
+
code?: string;
|
|
17
|
+
language?: string;
|
|
18
|
+
theme?: string;
|
|
19
|
+
extensions?: any[];
|
|
20
|
+
cdnSource?: string;
|
|
21
|
+
onchange?: any;
|
|
22
|
+
height?: string;
|
|
23
|
+
};
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { createEventDispatcher } from "svelte"
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
class: className = "",
|
|
6
|
+
id = crypto.randomUUID(),
|
|
7
|
+
name,
|
|
8
|
+
options = [],
|
|
9
|
+
value = null,
|
|
10
|
+
placeholder = "Select an option",
|
|
11
|
+
optionLabel = "label",
|
|
12
|
+
optionValue = "value",
|
|
13
|
+
disabled = false,
|
|
14
|
+
searchable = false,
|
|
15
|
+
clearable = false,
|
|
16
|
+
ariaLabel,
|
|
17
|
+
children,
|
|
18
|
+
} = $props()
|
|
19
|
+
|
|
20
|
+
const dispatch = createEventDispatcher()
|
|
21
|
+
|
|
22
|
+
let isOpen = $state(false)
|
|
23
|
+
let searchValue = $state("")
|
|
24
|
+
let highlightedIndex = $state(-1)
|
|
25
|
+
let selectedValue = $state(null)
|
|
26
|
+
let inputElement
|
|
27
|
+
|
|
28
|
+
$effect(() => {
|
|
29
|
+
selectedValue = value
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const getOptionLabel = (option) => {
|
|
33
|
+
if (typeof option === "object" && option !== null) {
|
|
34
|
+
return option[optionLabel]
|
|
35
|
+
}
|
|
36
|
+
return option
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const getOptionValue = (option) => {
|
|
40
|
+
if (typeof option === "object" && option !== null) {
|
|
41
|
+
return option[optionValue]
|
|
42
|
+
}
|
|
43
|
+
return option
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const filteredOptions = $derived.by(() => {
|
|
47
|
+
if (!searchable || !searchValue) {
|
|
48
|
+
return options
|
|
49
|
+
}
|
|
50
|
+
return options.filter((option) =>
|
|
51
|
+
getOptionLabel(option).toLowerCase().includes(searchValue.toLowerCase())
|
|
52
|
+
)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const handleInputFocus = () => {
|
|
56
|
+
isOpen = true
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const handleInputBlur = (e) => {
|
|
60
|
+
if (!e.relatedTarget?.closest(".combobox-dropdown")) {
|
|
61
|
+
isOpen = false
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const handleInputChange = (e) => {
|
|
66
|
+
if (searchable) {
|
|
67
|
+
searchValue = e.target.value
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const handleOptionClick = (option) => {
|
|
72
|
+
const newValue = getOptionValue(option)
|
|
73
|
+
selectedValue = newValue
|
|
74
|
+
isOpen = false
|
|
75
|
+
searchValue = ""
|
|
76
|
+
dispatch("change", { value: newValue, option })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const handleClear = (e) => {
|
|
80
|
+
e.stopPropagation()
|
|
81
|
+
selectedValue = null
|
|
82
|
+
searchValue = ""
|
|
83
|
+
dispatch("change", { value: null, option: null })
|
|
84
|
+
inputElement?.focus()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const handleKeydown = (e) => {
|
|
88
|
+
if (!isOpen) {
|
|
89
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
90
|
+
e.preventDefault()
|
|
91
|
+
isOpen = true
|
|
92
|
+
}
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
switch (e.key) {
|
|
97
|
+
case "ArrowDown":
|
|
98
|
+
e.preventDefault()
|
|
99
|
+
highlightedIndex = Math.min(highlightedIndex + 1, filteredOptions.length - 1)
|
|
100
|
+
break
|
|
101
|
+
case "ArrowUp":
|
|
102
|
+
e.preventDefault()
|
|
103
|
+
highlightedIndex = Math.max(highlightedIndex - 1, -1)
|
|
104
|
+
break
|
|
105
|
+
case "Enter":
|
|
106
|
+
e.preventDefault()
|
|
107
|
+
if (highlightedIndex >= 0) {
|
|
108
|
+
handleOptionClick(filteredOptions[highlightedIndex])
|
|
109
|
+
}
|
|
110
|
+
break
|
|
111
|
+
case "Escape":
|
|
112
|
+
e.preventDefault()
|
|
113
|
+
isOpen = false
|
|
114
|
+
break
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const selectedLabel = $derived.by(() => {
|
|
119
|
+
if (selectedValue === null || selectedValue === undefined) {
|
|
120
|
+
return placeholder
|
|
121
|
+
}
|
|
122
|
+
const selected = options.find((opt) => getOptionValue(opt) === selectedValue)
|
|
123
|
+
return selected ? getOptionLabel(selected) : placeholder
|
|
124
|
+
})
|
|
125
|
+
</script>
|
|
126
|
+
|
|
127
|
+
<style>
|
|
128
|
+
@reference '$lib/twintrinsic.css';
|
|
129
|
+
|
|
130
|
+
.combobox-wrapper {
|
|
131
|
+
position: relative;
|
|
132
|
+
display: inline-block;
|
|
133
|
+
width: 100%;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.combobox-input {
|
|
137
|
+
width: 100%;
|
|
138
|
+
padding: 0.5rem 0.75rem;
|
|
139
|
+
border: 1px solid var(--color-border);
|
|
140
|
+
border-radius: 0.375rem;
|
|
141
|
+
background-color: var(--color-background);
|
|
142
|
+
color: var(--color-text);
|
|
143
|
+
font-size: 1rem;
|
|
144
|
+
transition: all 0.2s ease;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.combobox-input:focus {
|
|
148
|
+
outline: none;
|
|
149
|
+
border-color: var(--color-primary);
|
|
150
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.combobox-input:disabled {
|
|
154
|
+
background-color: var(--color-disabled);
|
|
155
|
+
cursor: not-allowed;
|
|
156
|
+
opacity: 0.5;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.combobox-input-wrapper {
|
|
160
|
+
position: relative;
|
|
161
|
+
display: flex;
|
|
162
|
+
align-items: center;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.combobox-clear {
|
|
166
|
+
position: absolute;
|
|
167
|
+
right: 0.5rem;
|
|
168
|
+
background: none;
|
|
169
|
+
border: none;
|
|
170
|
+
cursor: pointer;
|
|
171
|
+
padding: 0.25rem;
|
|
172
|
+
color: var(--color-text-secondary);
|
|
173
|
+
transition: color 0.2s ease;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.combobox-clear:hover {
|
|
177
|
+
color: var(--color-text);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.combobox-dropdown {
|
|
181
|
+
position: absolute;
|
|
182
|
+
top: 100%;
|
|
183
|
+
left: 0;
|
|
184
|
+
right: 0;
|
|
185
|
+
margin-top: 0.25rem;
|
|
186
|
+
background-color: var(--color-background);
|
|
187
|
+
border: 1px solid var(--color-border);
|
|
188
|
+
border-radius: 0.375rem;
|
|
189
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
190
|
+
max-height: 300px;
|
|
191
|
+
overflow-y: auto;
|
|
192
|
+
z-index: 10;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.combobox-option {
|
|
196
|
+
padding: 0.5rem 0.75rem;
|
|
197
|
+
cursor: pointer;
|
|
198
|
+
transition: background-color 0.2s ease;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.combobox-option:hover,
|
|
202
|
+
.combobox-option.highlighted {
|
|
203
|
+
background-color: var(--color-hover);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.combobox-option.selected {
|
|
207
|
+
background-color: var(--color-primary);
|
|
208
|
+
color: white;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.combobox-empty {
|
|
212
|
+
padding: 1rem;
|
|
213
|
+
text-align: center;
|
|
214
|
+
color: var(--color-text-secondary);
|
|
215
|
+
}
|
|
216
|
+
</style>
|
|
217
|
+
|
|
218
|
+
<div class="combobox-wrapper {className}" {id}>
|
|
219
|
+
<div class="combobox-input-wrapper">
|
|
220
|
+
<input
|
|
221
|
+
bind:this={inputElement}
|
|
222
|
+
type="text"
|
|
223
|
+
class="combobox-input"
|
|
224
|
+
{placeholder}
|
|
225
|
+
{disabled}
|
|
226
|
+
value={searchable ? searchValue : selectedLabel}
|
|
227
|
+
on:focus={handleInputFocus}
|
|
228
|
+
on:blur={handleInputBlur}
|
|
229
|
+
on:change={handleInputChange}
|
|
230
|
+
on:keydown={handleKeydown}
|
|
231
|
+
aria-label={ariaLabel}
|
|
232
|
+
aria-description={ariaDescription}
|
|
233
|
+
aria-haspopup="listbox"
|
|
234
|
+
aria-expanded={isOpen}
|
|
235
|
+
{name}
|
|
236
|
+
/>
|
|
237
|
+
{#if clearable && value !== null && value !== undefined}
|
|
238
|
+
<button
|
|
239
|
+
class="combobox-clear"
|
|
240
|
+
on:click={handleClear}
|
|
241
|
+
aria-label="Clear selection"
|
|
242
|
+
tabindex="-1"
|
|
243
|
+
>
|
|
244
|
+
✕
|
|
245
|
+
</button>
|
|
246
|
+
{/if}
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
{#if isOpen && filteredOptions.length > 0}
|
|
250
|
+
<div class="combobox-dropdown" bind:this={dropdownElement} role="listbox">
|
|
251
|
+
{#each filteredOptions as option, index}
|
|
252
|
+
<div
|
|
253
|
+
class="combobox-option"
|
|
254
|
+
class:highlighted={index === highlightedIndex}
|
|
255
|
+
class:selected={getOptionValue(option) === value}
|
|
256
|
+
on:click={() => handleOptionClick(option)}
|
|
257
|
+
on:keydown={(e) => {
|
|
258
|
+
if (e.key === "Enter") {
|
|
259
|
+
handleOptionClick(option)
|
|
260
|
+
}
|
|
261
|
+
}}
|
|
262
|
+
role="option"
|
|
263
|
+
aria-selected={getOptionValue(option) === value}
|
|
264
|
+
tabindex="-1"
|
|
265
|
+
>
|
|
266
|
+
{#if children}
|
|
267
|
+
{@render children(option)}
|
|
268
|
+
{:else}
|
|
269
|
+
{getOptionLabel(option)}
|
|
270
|
+
{/if}
|
|
271
|
+
</div>
|
|
272
|
+
{/each}
|
|
273
|
+
</div>
|
|
274
|
+
{:else if isOpen}
|
|
275
|
+
<div class="combobox-dropdown">
|
|
276
|
+
<div class="combobox-empty">No options available</div>
|
|
277
|
+
</div>
|
|
278
|
+
{/if}
|
|
279
|
+
</div>
|