@lapikit/repl 0.0.0-insiders.24a3260 → 0.0.0-insiders.2ac5230

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Nycolaide <https://github.com/Nycolaide>.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1 +1,87 @@
1
1
  # @Lapikit/repl
2
+
3
+ @lapikit/repl is a Svelte component for Lapikit. It's a add-on package for Lapikit library.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -D @lapikit/repl
9
+ ```
10
+
11
+ ```javascript
12
+ // svelte.config.js
13
+
14
+ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
15
+
16
+ const config = {
17
+ preprocess: [vitePreprocess(), lapikitPreprocess({ plugins: ['repl'] })],
18
+
19
+ ...
20
+ };
21
+
22
+ export default config;
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ```svelte
28
+ <kit:repl content="console.log('hello')" />
29
+ ```
30
+
31
+ ```svelte
32
+ <script lang="ts">
33
+ import sampleCounter from './samples/counter.svelte?raw';
34
+ import Counter from './samples/counter.svelte';
35
+ </script>
36
+
37
+ <kit:repl presentation content={sampleCounter}>
38
+ <Counter />
39
+ </kit:repl>
40
+ ```
41
+
42
+ ```svelte
43
+ <script lang="ts">
44
+ import sampleJson from './samples/json.json?raw';
45
+ import sampleCounter from './samples/counter.svelte?raw';
46
+ import sampleCss from './samples/css.css?raw';
47
+ import Counter from './samples/counter.svelte';
48
+ </script>
49
+
50
+ <kit:repl
51
+ presentation
52
+ content={{
53
+ 'Counter.svelte': { code: sampleCounter, lang: 'svelte' },
54
+ 'file.json': { code: sampleJson, lang: 'json' },
55
+ 'Styles.css': { code: sampleCss, lang: 'css' }
56
+ }}
57
+ >
58
+ <Counter />
59
+ </kit:repl>
60
+ ```
61
+
62
+ ```svelte
63
+ <script lang="ts">
64
+ import sampleJson from './samples/json.json?raw';
65
+ import sampleCounter from './samples/counter.svelte?raw';
66
+ import sampleCss from './samples/css.css?raw';
67
+ import Counter from './samples/counter.svelte';
68
+ </script>
69
+
70
+ <kit:repl
71
+ content={{
72
+ 'Counter.svelte': { code: sampleCounter, lang: 'svelte' },
73
+ 'file.json': { code: sampleJson, lang: 'json' },
74
+ 'Styles.css': { code: sampleCss, lang: 'css' }
75
+ }}
76
+ >
77
+ <Counter />
78
+ </kit:repl>
79
+ ```
80
+
81
+ ## Props
82
+
83
+ | Prop | Type | Default | Description |
84
+ | ------------ | --------------------------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------- |
85
+ | content | string \| Record<string, { code: string; lang?: string }> | '' | The code content to be displayed in the REPL. It can be a single string or an object representing multiple files. |
86
+ | presentation | boolean | false | If true, the REPL will be in presentation mode, showing only the output without the code editor. |
87
+ | tiltle | string | '' | The title displayed on the REPL toolbar. |
@@ -1,8 +1,17 @@
1
1
  <script lang="ts">
2
- let { children, ...rest } = $props();
2
+ import type { Snippet } from 'svelte';
3
+
4
+ let { children, type = 'button', disabled = false, ...rest }: PropsTypeButton = $props();
5
+
6
+ type PropsTypeButton = {
7
+ children?: Snippet;
8
+ type?: 'button' | 'submit' | 'reset';
9
+ disabled?: boolean;
10
+ [rest: string]: unknown;
11
+ };
3
12
  </script>
4
13
 
5
- <button {...rest}>
14
+ <button {type} {disabled} {...rest}>
6
15
  {@render children?.()}
7
16
  </button>
8
17
 
@@ -14,15 +23,15 @@
14
23
  display: flex;
15
24
  align-self: center;
16
25
  justify-content: center;
17
- font-size: 0.875rem;
18
- border-radius: 0.375rem;
26
+ font-size: var(--kit-repl-shiki-size);
27
+ border-radius: var(--kit-repl-radius);
19
28
  transition: background-color 0.2s ease;
20
29
  padding: 8px;
21
- color: var(--repl-secondary);
30
+ color: var(--kit-repl-secondary);
22
31
  }
23
32
 
24
33
  button:hover {
25
- color: var(--repl-primary);
34
+ color: var(--kit-repl-primary);
26
35
  }
27
36
 
28
37
  button :global(svg) {
@@ -1,5 +1,9 @@
1
+ import type { Snippet } from 'svelte';
1
2
  declare const Button: import("svelte").Component<{
2
- children: any;
3
- } & Record<string, any>, {}, "">;
3
+ [rest: string]: unknown;
4
+ children?: Snippet;
5
+ type?: "button" | "submit" | "reset";
6
+ disabled?: boolean;
7
+ }, {}, "">;
4
8
  type Button = ReturnType<typeof Button>;
5
9
  export default Button;
package/dist/Files.svelte CHANGED
@@ -1,67 +1,64 @@
1
1
  <script lang="ts">
2
2
  import type { FilesProps } from './types.js';
3
- import { dictionaryIcons } from './utils.js';
3
+ import { dictionaryIcons, dictionaryPkgIcons } from './utils.js';
4
4
 
5
- let { files, activeIndex = $bindable() }: FilesProps = $props();
5
+ let { files, activeIndex = $bindable(), modeState, viewState }: FilesProps = $props();
6
6
  </script>
7
7
 
8
- {#if files && files.length > 1}
9
- <div>
8
+ {#if modeState !== 'playground' && viewState === 'code' && files && files.length > 1}
9
+ <div role="tablist" aria-label="Files">
10
10
  {#each files as file, index (index)}
11
- <button class:active={activeIndex === index} onclick={() => (activeIndex = index)}>
12
- {#if file.lang && dictionaryIcons[file.lang]}
11
+ <button
12
+ type="button"
13
+ role="tab"
14
+ aria-selected={activeIndex === index}
15
+ aria-label="Select {file.name}"
16
+ class:active={activeIndex === index}
17
+ onclick={() => (activeIndex = index)}
18
+ >
19
+ {#if dictionaryPkgIcons[file.name.toLowerCase()]}
20
+ <img src={dictionaryPkgIcons[file.name.toLowerCase()]} alt="{file.name} icon" />
21
+ {:else if file.lang && dictionaryIcons[file.lang]}
13
22
  <img src={dictionaryIcons[file.lang]} alt="{file.lang} icon" />
14
23
  {/if}
15
24
  <span>{file.name}</span>
16
25
  </button>
17
26
  {/each}
18
27
  </div>
19
-
20
- <hr />
21
28
  {/if}
22
29
 
23
30
  <style>
24
31
  div {
25
32
  display: flex;
26
- gap: calc(var(--repl-spacing) * 2);
27
- padding-left: calc(5 * var(--repl-spacing));
28
- padding-right: calc(5 * var(--repl-spacing));
29
- padding-block: calc(var(--repl-spacing) * 2);
30
33
  overflow-x: auto;
31
34
  }
32
35
 
33
36
  button {
34
37
  display: flex;
35
38
  align-items: center;
36
- gap: calc(var(--repl-spacing) * 2);
37
- padding: calc(var(--repl-spacing) * 2) calc(var(--repl-spacing) * 3);
38
- border-radius: 0.375rem;
39
- font-size: 0.875rem;
39
+ padding: 9px 4px;
40
+ gap: var(--kit-repl-spacing);
41
+ font-size: var(--kit-repl-shiki-size);
40
42
  transition: all 0.2s ease;
41
- border: 1px solid transparent;
43
+ border: 0;
42
44
  white-space: nowrap;
45
+ background-color: transparent;
46
+ border-bottom: 2px solid transparent;
47
+ cursor: pointer;
43
48
  }
44
49
 
45
50
  button img {
46
51
  width: 16px;
47
52
  height: 16px;
53
+ border: 0;
48
54
  }
49
55
 
50
56
  button:hover {
51
- background-color: #f5f5f5;
57
+ border-color: var(--kit-color-accent, var(--kit-repl-secondary));
52
58
  }
53
59
 
54
60
  button.active {
55
- background-color: #f0f0f0;
56
- border-color: #d0d0d0;
57
- }
58
-
59
- hr {
60
- max-width: calc(100% - 4.5rem);
61
- margin-inline-start: calc(4.5rem / 2);
62
- display: block;
63
- border: thin solid var(--repl-border-color);
64
- margin-top: 0;
65
- margin-bottom: 0;
61
+ border-color: var(--kit-color-accent, var(--kit-repl-primary));
62
+ color: var(--kit-color-accent, var(--kit-repl-primary));
66
63
  }
67
64
  </style>
package/dist/Repl.svelte CHANGED
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { copyToClipboard } from './utils.js';
3
3
  import { getHighlighterSingleton } from './shiki.js';
4
+ import { createTheme } from 'lapikit/actions';
4
5
  import type { FileItem, ReplProps } from './types.js';
5
6
 
6
7
  // components
@@ -13,14 +14,25 @@
13
14
  let ref: null | HTMLElement = $state(null);
14
15
 
15
16
  // states
16
- let language = $state('javascript');
17
+ let language = $state('sh');
17
18
 
18
19
  let modeState: 'code' | 'playground' | 'mixed' = $state('code');
19
20
  let copyState = $state(false);
20
21
  let viewState: 'code' | 'preview' = $state('code');
21
- let themeState: 'light' | 'dark' = $state('light');
22
22
 
23
- let codeHTML = $state('');
23
+ // theme: mirrors the ambient lapikit theme (light/dark/system) until the
24
+ // toolbar toggle is used, at which point it becomes a local override
25
+ // scoped to this repl instance only.
26
+ const theme = createTheme();
27
+ let themeOverridden = $state(false);
28
+ let themeState = $derived<'light' | 'dark'>(theme.active === 'dark' ? 'dark' : 'light');
29
+
30
+ function toggleTheme() {
31
+ themeOverridden = true;
32
+ theme.set(themeState === 'dark' ? 'light' : 'dark');
33
+ }
34
+
35
+ let codeHTML = $state<string | null>(null);
24
36
  let activeFileIndex = $state(0);
25
37
 
26
38
  let files = $derived.by<FileItem[]>(() => {
@@ -29,7 +41,7 @@
29
41
  {
30
42
  name: title || 'code',
31
43
  content: content.code,
32
- lang: content.lang || 'javascript'
44
+ lang: content.lang || 'sh'
33
45
  }
34
46
  ];
35
47
  }
@@ -44,7 +56,7 @@
44
56
  lang:
45
57
  typeof fileContent === 'object'
46
58
  ? ((fileContent as Record<string, unknown>).lang as string)
47
- : 'javascript'
59
+ : 'sh'
48
60
  }));
49
61
  }
50
62
 
@@ -52,11 +64,11 @@
52
64
  return content.map((item) => ({
53
65
  name: item.name,
54
66
  content: item.content || item.code || '',
55
- lang: item.lang || 'javascript'
67
+ lang: item.lang || 'sh'
56
68
  }));
57
69
  }
58
70
 
59
- return [{ name: 'code', content: content || '', lang: 'javascript' }];
71
+ return [{ name: 'code', content: content || '', lang: 'sh' }];
60
72
  });
61
73
  let activeFile = $derived(files[activeFileIndex]);
62
74
 
@@ -91,105 +103,180 @@
91
103
 
92
104
  $effect(() => {
93
105
  const file = activeFile;
94
- const theme = themeState;
95
106
 
96
107
  if (file?.content) {
108
+ codeHTML = null;
109
+ language = file.lang || 'sh';
110
+
97
111
  (async () => {
98
112
  const highlighter = await getHighlighterSingleton();
99
-
100
- language = file.lang || 'javascript';
101
113
  const html = highlighter.codeToHtml(file.content, {
102
- theme: theme === 'light' ? 'github-light' : 'github-dark',
114
+ themes: { light: 'github-light', dark: 'github-dark' },
115
+ defaultColor: false,
103
116
  lang: file.lang || language
104
117
  });
105
-
106
118
  codeHTML = html;
107
119
  })();
108
120
  }
109
121
  });
110
122
  </script>
111
123
 
112
- <div class="repl-container">
113
- <Toolbar
114
- {title}
115
- {language}
116
- {presentation}
117
- bind:copyState
118
- bind:viewState
119
- bind:themeState
120
- bind:modeState
121
- />
122
-
123
- {#if modeState !== 'code'}
124
- <hr />
125
- {/if}
126
-
124
+ <div class="kit-repl" use:theme.action={{ overridden: themeOverridden }}>
127
125
  {#if presentation}
128
- <div class="repl-content">
129
- <div>{@render children?.()}</div>
126
+ <div class="kit-repl-content" class:kit-repl-content--playground={presentation}>
127
+ <div class="wrapper-playground">
128
+ {@render children?.()}
129
+ </div>
130
130
  </div>
131
-
132
- <hr />
133
131
  {/if}
134
132
 
135
- <Files {files} bind:activeIndex={activeFileIndex} />
133
+ <div class="kit-repl-container">
134
+ <Toolbar
135
+ {title}
136
+ {language}
137
+ {presentation}
138
+ {files}
139
+ {themeState}
140
+ onToggleTheme={toggleTheme}
141
+ bind:copyState
142
+ bind:viewState
143
+ bind:modeState
144
+ >
145
+ <Files {files} bind:activeIndex={activeFileIndex} {modeState} {viewState} />
146
+ </Toolbar>
147
+
148
+ {#if modeState !== 'code'}
149
+ <hr />
150
+ {/if}
136
151
 
137
- <div class="repl-content">
138
- {#if viewState === 'code'}
139
- <!-- eslint-disable-next-line svelte/no-at-html-tags -->
140
- <div class="wrapper-highlight" bind:this={ref}>{@html codeHTML}</div>
141
- {:else}
142
- <div>{@render children?.()}</div>
152
+ {#if title}
153
+ <Files {files} bind:activeIndex={activeFileIndex} {modeState} {viewState} />
143
154
  {/if}
155
+
156
+ <div
157
+ class="kit-repl-content"
158
+ class:kit-repl-content--code={viewState === 'code' && !presentation}
159
+ >
160
+ {#if viewState === 'code'}
161
+ <div class="kit-repl-wrapper-highlight" bind:this={ref}>
162
+ {#if codeHTML !== null}
163
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
164
+ {@html codeHTML}
165
+ {:else}
166
+ <pre class="kit-repl-raw"><code>{activeFile?.content ?? ''}</code></pre>
167
+ {/if}
168
+ </div>
169
+ {:else}
170
+ <div class="kit-repl-wrapper-playground">
171
+ {@render children?.()}
172
+ </div>
173
+ {/if}
174
+ </div>
144
175
  </div>
145
176
  </div>
146
177
 
147
178
  <style>
148
- div.repl-container {
149
- --repl-spacing: 0.25rem;
150
- --repl-radius: 1rem;
151
- --repl-shiki-size: 1rem;
152
- --repl-shiki-tab-size: 2;
153
-
154
- --repl-background: #f9f9f9;
155
- --repl-border-color: #ebebeb;
179
+ .kit-repl {
180
+ /* ui */
181
+ --kit-repl-spacing: var(--kit-space-default, 4px);
182
+ --kit-repl-radius: var(--kit-shape-md, 10px);
156
183
 
157
- --repl-primary: #0d0d34;
158
- --repl-secondary: #8f8f8f;
184
+ /* shiki override */
185
+ --kit-repl-shiki-size: var(--kit-font-xs, 13px);
186
+ --kit-repl-shiki-tab-size: 2;
159
187
 
160
- background-color: var(--repl-background);
161
- border-radius: var(--repl-radius);
162
- border: 2px solid var(--repl-border-color);
188
+ /* colors */
189
+ --kit-repl-background: var(--kit-color-surface-1, #f9f9f9);
190
+ --kit-repl-border-color: var(--kit-color-fill, #ebebeb);
191
+ --kit-repl-primary: var(--kit-color-text, #0d0d34);
192
+ --kit-repl-secondary: var(--kit-color-text-muted, #8f8f8f);
193
+ }
194
+ .kit-repl-container {
195
+ background-color: var(--kit-repl-background);
196
+ border-radius: var(--kit-repl-radius);
197
+ border: 1px solid var(--kit-repl-border-color);
163
198
  }
164
199
 
165
- div.repl-container :global(pre) {
166
- background-color: var(--repl-background) !important;
200
+ .kit-repl-container :global(pre) {
201
+ background-color: var(--kit-repl-background) !important;
202
+ border: 0 !important;
203
+ border-radius: 0 !important;
204
+ border-bottom-left-radius: var(--kit-repl-radius) !important;
205
+ border-bottom-right-radius: var(--kit-repl-radius) !important;
206
+ padding: 10px 0 !important;
207
+ margin-bottom: 0 !important;
208
+ margin-top: 0 !important;
209
+ font-size: var(--kit-repl-shiki-size) !important;
167
210
  }
168
211
 
169
- .repl-content {
212
+ .kit-repl-content {
170
213
  display: flow-root;
171
- margin-top: calc(var(--repl-spacing) * 0);
172
- padding-right: calc(10 * var(--repl-spacing));
173
- padding-left: calc(5 * var(--repl-spacing));
174
- padding-bottom: calc(4 * var(--repl-spacing));
175
- padding-top: calc(3 * var(--repl-spacing));
214
+ padding-left: calc(var(--kit-repl-spacing) * 2);
215
+ /* margin-top: calc(var(--kit-repl-spacing) * 0); */
216
+ /* padding-right: calc(10 * var(--kit-repl-spacing));
217
+ padding-left: calc(5 * var(--kit-repl-spacing));
218
+ padding-bottom: calc(4 * var(--kit-repl-spacing));
219
+ padding-top: calc(3 * var(--kit-repl-spacing)); */
176
220
  position: relative;
177
221
  }
178
222
 
223
+ .kit-repl-content--code {
224
+ padding-top: 0;
225
+ }
226
+
227
+ .kit-repl-content--playground {
228
+ padding-top: calc(4 * var(--kit-repl-spacing));
229
+ padding-bottom: calc(10 * var(--kit-repl-spacing));
230
+ }
231
+
179
232
  hr {
180
- max-width: calc(100% - 4.5rem);
181
- margin-inline-start: calc(4.5rem / 2);
233
+ max-width: calc(100% - 2.5rem);
234
+ margin-inline-start: calc(2.5rem / 2);
182
235
  display: block;
183
- border: thin solid var(--repl-border-color);
236
+ border: thin solid var(--kit-repl-border-color);
184
237
  margin-top: 0;
185
238
  margin-bottom: 0;
186
239
  }
187
240
 
188
- div.repl-container .wrapper-highlight :global(pre code) {
189
- font-size: var(--repl-shiki-size);
190
- -moz-tab-size: var(--repl-shiki-tab-size);
191
- tab-size: var(--repl-shiki-tab-size);
241
+ .kit-repl-raw {
242
+ font-size: var(--kit-repl-shiki-size);
243
+ -moz-tab-size: var(--kit-repl-shiki-tab-size);
244
+ tab-size: var(--kit-repl-shiki-tab-size);
245
+ white-space: pre-wrap;
246
+ word-break: break-word;
247
+ margin: 0;
248
+ padding: 0;
249
+ }
250
+
251
+ div.kit-repl-container .kit-repl-wrapper-highlight :global(pre code) {
252
+ font-size: var(--kit-repl-shiki-size);
253
+ -moz-tab-size: var(--kit-repl-shiki-tab-size);
254
+ tab-size: var(--kit-repl-shiki-tab-size);
192
255
  white-space: pre-wrap;
193
256
  word-break: break-word;
194
257
  }
258
+
259
+ /* shiki dual-theme: follows the ambient lapikit theme (light/dark/system) */
260
+ .kit-repl-wrapper-highlight :global(.shiki),
261
+ .kit-repl-wrapper-highlight :global(.shiki span) {
262
+ color: var(--shiki-light);
263
+ }
264
+
265
+ :global([data-kit-theme='dark']) .kit-repl-wrapper-highlight :global(.shiki),
266
+ :global([data-kit-theme='dark']) .kit-repl-wrapper-highlight :global(.shiki span) {
267
+ color: var(--shiki-dark);
268
+ }
269
+
270
+ @media (prefers-color-scheme: dark) {
271
+ :global([data-kit-theme='system']) .kit-repl-wrapper-highlight :global(.shiki),
272
+ :global([data-kit-theme='system']) .kit-repl-wrapper-highlight :global(.shiki span) {
273
+ color: var(--shiki-dark);
274
+ }
275
+ }
276
+
277
+ div.kit-repl-container .kit-repl-wrapper-playground {
278
+ background-color: var(--kit-repl-background);
279
+ border-radius: var(--kit-repl-radius);
280
+ padding: calc(4 * var(--kit-repl-spacing));
281
+ }
195
282
  </style>
@@ -5,12 +5,15 @@
5
5
  import { Copy, Check, Code, Codesandbox, Moon, Sun } from '@lucide/svelte';
6
6
 
7
7
  let {
8
+ children,
8
9
  title,
9
10
  language,
10
11
  presentation,
12
+ files,
11
13
  copyState = $bindable(),
12
14
  viewState = $bindable(),
13
- themeState = $bindable(),
15
+ themeState,
16
+ onToggleTheme,
14
17
  modeState = $bindable()
15
18
  }: ToolbarProps = $props();
16
19
 
@@ -19,18 +22,28 @@
19
22
  );
20
23
  </script>
21
24
 
22
- <div class="lpk-repl--toolbar">
23
- <div class="lpk-repl--toolbar-title">
24
- {#if title}
25
+ <div class="kit-repl--toolbar">
26
+ {#if title}
27
+ <div class="kit-repl--toolbar-title" class:kit-repl--toolbar-title--title={title}>
25
28
  <span>{title}</span>
26
- {:else if language}
29
+ </div>
30
+ {:else if files && files.length > 1}
31
+ <div class="kit-repl--toolbar-files">
32
+ {@render children?.()}
33
+ </div>
34
+ {:else if language}
35
+ <div class="kit-repl--toolbar-title" class:kit-repl--toolbar-title--language={language}>
27
36
  <span>{languageKey}</span>
28
- {/if}
29
- </div>
37
+ </div>
38
+ {/if}
30
39
 
31
- <div class="lpk-repl--toolbar-actions">
40
+ <div class="kit-repl--toolbar-actions">
32
41
  {#if (modeState !== 'code' && viewState === 'preview') || presentation}
33
- <Button onclick={() => (themeState = themeState === 'light' ? 'dark' : 'light')}>
42
+ <Button
43
+ aria-label={themeState === 'light' ? 'Switch to dark theme' : 'Switch to light theme'}
44
+ aria-pressed={themeState === 'dark'}
45
+ onclick={() => onToggleTheme?.()}
46
+ >
34
47
  {#if themeState === 'light'}
35
48
  <Moon />
36
49
  {:else}
@@ -40,7 +53,11 @@
40
53
  {/if}
41
54
 
42
55
  {#if modeState === 'mixed' && !presentation}
43
- <Button onclick={() => (viewState = viewState === 'code' ? 'preview' : 'code')}>
56
+ <Button
57
+ aria-label={viewState === 'code' ? 'Show preview' : 'Show code'}
58
+ aria-pressed={viewState === 'preview'}
59
+ onclick={() => (viewState = viewState === 'code' ? 'preview' : 'code')}
60
+ >
44
61
  {#if viewState === 'code'}
45
62
  <Code />
46
63
  {:else}
@@ -50,7 +67,10 @@
50
67
  {/if}
51
68
 
52
69
  {#if modeState !== 'playground'}
53
- <Button onclick={() => (copyState = true)}>
70
+ <Button
71
+ aria-label={copyState ? 'Code copied' : 'Copy code'}
72
+ onclick={() => (copyState = true)}
73
+ >
54
74
  {#if copyState}
55
75
  <Check />
56
76
  {:else}
@@ -62,32 +82,46 @@
62
82
  </div>
63
83
 
64
84
  <style>
65
- .lpk-repl--toolbar {
66
- display: flex;
85
+ .kit-repl--toolbar {
86
+ display: grid;
67
87
  align-items: center;
68
- justify-content: space-between;
69
- gap: calc(var(--repl-spacing) * 3);
70
- padding-left: calc(5 * var(--repl-spacing));
71
- padding-right: calc(var(--repl-spacing) * 2);
72
- padding-block: calc(var(--repl-spacing) * 1.5);
73
- border-top-left-radius: var(--repl-radius);
74
- border-top-right-radius: var(--repl-radius);
88
+ grid-template-columns: 1fr auto;
89
+ padding-left: calc(var(--kit-repl-spacing) * 2);
90
+ background-color: color-mix(in oklab, var(--kit-repl-background) 95%, black);
91
+ border-top-left-radius: var(--kit-repl-radius);
92
+ border-top-right-radius: var(--kit-repl-radius);
93
+ gap: var(--kit-repl-spacing);
75
94
  min-height: 36px;
76
95
  }
77
96
 
78
- .lpk-repl--toolbar .lpk-repl--toolbar-title {
97
+ .kit-repl--toolbar .kit-repl--toolbar-title {
79
98
  display: flex;
80
99
  align-items: center;
81
- gap: calc(var(--repl-spacing) * 2);
82
- font-weight: 600;
83
- color: var(--repl-secondary);
100
+ gap: calc(var(--kit-repl-spacing) * 2);
84
101
  max-width: 80%;
85
102
  min-width: 0;
86
103
  }
87
104
 
88
- .lpk-repl--toolbar .lpk-repl--toolbar-actions {
105
+ .kit-repl--toolbar-title--language {
106
+ font-size: var(--kit-repl-shiki-size);
107
+ line-height: 1;
108
+ color: var(--kit-color-text-muted);
109
+ }
110
+
111
+ .kit-repl--toolbar-title--title {
112
+ font-size: var(--kit-repl-shiki-size);
113
+ line-height: 1;
114
+ color: var(--kit-color-text-muted);
115
+ }
116
+
117
+ .kit-repl--toolbar .kit-repl--toolbar-actions {
89
118
  display: flex;
90
119
  align-items: center;
91
- gap: calc(var(--repl-spacing) * 2);
120
+ gap: calc(var(--kit-repl-spacing) * 2);
121
+ }
122
+
123
+ .kit-repl--toolbar .kit-repl--toolbar-files {
124
+ width: 100%;
125
+ overflow-x: auto;
92
126
  }
93
127
  </style>
@@ -1,4 +1,4 @@
1
1
  import type { ToolbarProps } from './types.js';
2
- declare const Toolbar: import("svelte").Component<ToolbarProps, {}, "copyState" | "viewState" | "themeState" | "modeState">;
2
+ declare const Toolbar: import("svelte").Component<ToolbarProps, {}, "modeState" | "viewState" | "copyState">;
3
3
  type Toolbar = ReturnType<typeof Toolbar>;
4
4
  export default Toolbar;
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { default as KitCode } from './Repl.svelte';
1
+ export { default as KitRepl } from './Repl.svelte';
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // components
2
- export { default as KitCode } from './Repl.svelte';
2
+ export { default as KitRepl } from './Repl.svelte';
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path d="M113.744 41.999a18.558 18.558 0 0 0-.8-.772c-.272-.246-.528-.524-.8-.771s-.528-.525-.8-.771c-.272-.247-.528-.525-.8-.772s-.528-.524-.8-.771-.528-.525-.8-.772-.528-.524-.8-.771c7.936 7.52 12.483 17.752 12.656 28.481 0 25.565-26.912 46.363-60 46.363-18.528 0-35.104-6.526-46.128-16.756l.8.772.8.771.8.772.8.771.8.772.8.771.8.771c11.008 10.662 27.952 17.527 46.928 17.527 33.088 0 60-20.797 60-46.285 0-10.893-4.864-21.215-13.456-29.33z"/><path fill="#fbf0df" d="M116.8 65.08c0 23.467-25.072 42.49-56 42.49s-56-19.023-56-42.49c0-14.55 9.6-27.401 24.352-35.023C43.904 22.435 53.088 14.628 60.8 14.628S75.104 21 92.448 30.058C107.2 37.677 116.8 50.53 116.8 65.08Z"/><path fill="#f6dece" d="M116.8 65.08a32.314 32.314 0 0 0-1.28-8.918c-4.368 51.377-69.36 53.846-94.912 38.48 11.486 8.584 25.66 13.144 40.192 12.928 30.88 0 56-19.054 56-42.49z"/><path fill="#fffefc" d="M39.248 27.234c7.152-4.135 16.656-11.896 26-11.911a15.372 15.372 0 0 0-4.448-.695c-3.872 0-8 1.93-13.2 4.83-1.808 1.018-3.68 2.144-5.664 3.317-3.728 2.222-8 4.736-12.8 7.251C13.904 37.972 4.8 51.071 4.8 65.08v1.836c9.696-33.033 27.312-35.547 34.448-39.682z"/><path fill="#ccbea7" d="M56.192 18.532A24.553 24.553 0 0 1 53.867 29.1a25.407 25.407 0 0 1-6.683 8.671c-.448.386-.096 1.127.48.91 5.392-2.02 12.672-8.068 9.6-20.272-.128-.695-1.072-.51-1.072.123zm3.632 0a24.474 24.474 0 0 1 3.646 10.12c.445 3.587.08 7.224-1.07 10.662-.192.54.496 1.003.88.556 3.504-4.32 6.56-12.899-2.592-22.156-.464-.4-1.184.216-.864.756zm4.416-.262a25.702 25.702 0 0 1 7.521 7.925A24.71 24.71 0 0 1 75.2 36.414c-.016.13.02.26.101.365a.543.543 0 0 0 .718.117.509.509 0 0 0 .221-.313c1.472-5.384.64-14.564-11.472-19.332-.64-.246-1.056.587-.528.957zM34.704 34.315a27.418 27.418 0 0 0 9.91-5.222 26.262 26.262 0 0 0 6.842-8.663c.288-.556 1.2-.34 1.056.277-2.768 12.343-12.032 14.92-17.792 14.58-.608.016-.592-.802-.016-.972z"/><path d="M60.8 111.443c-33.088 0-60-20.798-60-46.363 0-15.429 9.888-29.823 26.448-38.448 4.8-2.469 8.912-4.953 12.576-7.128 2.016-1.203 3.92-2.33 5.76-3.379C51.2 12.916 56 10.771 60.8 10.771c4.8 0 8.992 1.852 14.24 4.845 1.6.88 3.2 1.836 4.912 2.885 3.984 2.376 8.48 5.06 14.4 8.131 16.56 8.625 26.448 23.004 26.448 38.448 0 25.565-26.912 46.363-60 46.363zm0-96.814c-3.872 0-8 1.928-13.2 4.829-1.808 1.018-3.68 2.144-5.664 3.317-3.728 2.222-8 4.736-12.8 7.251C13.904 37.972 4.8 51.071 4.8 65.08c0 23.436 25.12 42.506 56 42.506s56-19.07 56-42.506c0-14.01-9.104-27.108-24.352-35.023-6.048-3.086-10.768-5.986-14.592-8.27-1.744-1.033-3.344-1.99-4.8-2.838-4.848-2.778-8.384-4.32-12.256-4.32z"/><path fill="#b71422" d="M72.08 76.343c-.719 2.839-2.355 5.383-4.672 7.267a11.07 11.07 0 0 1-6.4 2.9 11.13 11.13 0 0 1-6.608-2.9c-2.293-1.892-3.906-4.436-4.608-7.267a1.073 1.073 0 0 1 .05-.5 1.11 1.11 0 0 1 .272-.428 1.19 1.19 0 0 1 .958-.322h19.744a1.185 1.185 0 0 1 .947.33 1.073 1.073 0 0 1 .317.92z"/><path fill="#ff6164" d="M54.4 83.733a11.24 11.24 0 0 0 6.592 2.932 11.239 11.239 0 0 0 6.576-2.932 16.652 16.652 0 0 0 1.6-1.65 10.904 10.904 0 0 0-3.538-2.564 11.26 11.26 0 0 0-4.302-1 10.121 10.121 0 0 0-4.549 1.192 9.71 9.71 0 0 0-3.451 3.097c.368.323.688.632 1.072.925z"/><path d="M54.656 82.514a8.518 8.518 0 0 1 2.97-2.347 8.836 8.836 0 0 1 3.734-.862 9.78 9.78 0 0 1 6.4 2.608c.368-.386.72-.787 1.056-1.188-2.035-1.87-4.726-2.933-7.536-2.978a10.487 10.487 0 0 0-4.335.975 10.125 10.125 0 0 0-3.489 2.666c.378.396.779.772 1.2 1.126z"/><path d="M60.944 87.436a12.078 12.078 0 0 1-7.12-3.086c-2.477-2.02-4.22-4.75-4.976-7.791-.054-.27-.045-.55.027-.817a1.83 1.83 0 0 1 .389-.726 2.25 2.25 0 0 1 .81-.595 2.32 2.32 0 0 1 .998-.192h19.744c.343-.007.683.06.996.196a2.3 2.3 0 0 1 .812.591c.182.212.313.46.382.728.07.267.076.545.018.815-.756 3.042-2.5 5.771-4.976 7.791a12.078 12.078 0 0 1-7.104 3.086zm-9.872-11.417c-.256 0-.32.108-.336.139.676 2.638 2.206 4.999 4.368 6.742a10.122 10.122 0 0 0 5.84 2.7 10.207 10.207 0 0 0 5.84-2.67c2.155-1.745 3.679-4.106 4.352-6.741a.333.333 0 0 0-.14-.113.348.348 0 0 0-.18-.026z"/><path fill="#febbd0" d="M85.152 77.3c5.17 0 9.36-2.377 9.36-5.308s-4.19-5.307-9.36-5.307c-5.17 0-9.36 2.376-9.36 5.307 0 2.931 4.19 5.307 9.36 5.307zm-48.432 0c5.17 0 9.36-2.377 9.36-5.308s-4.19-5.307-9.36-5.307c-5.17 0-9.36 2.376-9.36 5.307 0 2.931 4.19 5.307 9.36 5.307z"/><path d="M41.12 69.863a9.052 9.052 0 0 0 4.902-1.425 8.578 8.578 0 0 0 3.254-3.812 8.22 8.22 0 0 0 .508-4.913 8.41 8.41 0 0 0-2.408-4.357 8.92 8.92 0 0 0-4.514-2.33 9.12 9.12 0 0 0-5.096.48 8.755 8.755 0 0 0-3.96 3.131 8.287 8.287 0 0 0-1.486 4.725c0 2.252.927 4.412 2.577 6.005 1.65 1.594 3.888 2.492 6.223 2.496zm39.632 0a9.054 9.054 0 0 0 4.915-1.403 8.582 8.582 0 0 0 3.275-3.802 8.22 8.22 0 0 0 .528-4.917 8.408 8.408 0 0 0-2.398-4.368 8.92 8.92 0 0 0-4.512-2.344 9.12 9.12 0 0 0-5.103.473 8.756 8.756 0 0 0-3.967 3.13 8.287 8.287 0 0 0-1.49 4.73c-.004 2.245.914 4.4 2.555 5.994 1.64 1.593 3.869 2.495 6.197 2.507z"/><path fill="#fff" d="M38.4 61.902a3.4 3.4 0 0 0 1.844-.531c.547-.35.974-.847 1.227-1.43a3.088 3.088 0 0 0 .195-1.847 3.16 3.16 0 0 0-.902-1.639 3.351 3.351 0 0 0-1.696-.878 3.426 3.426 0 0 0-1.916.179 3.29 3.29 0 0 0-1.489 1.176 3.113 3.113 0 0 0-.559 1.776c0 .844.347 1.654.964 2.253a3.374 3.374 0 0 0 2.332.94zm39.632 0a3.4 3.4 0 0 0 1.844-.531c.547-.35.974-.847 1.227-1.43a3.088 3.088 0 0 0 .195-1.847 3.16 3.16 0 0 0-.902-1.639 3.351 3.351 0 0 0-1.696-.878 3.426 3.426 0 0 0-1.916.179 3.29 3.29 0 0 0-1.489 1.176 3.113 3.113 0 0 0-.559 1.776c0 .84.342 1.644.953 2.242.61.598 1.44.94 2.311.952z"/></svg>
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
3
+ <svg width="800px" height="800px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
4
+ <g>
5
+ <polygon fill="#C12127" points="0 256 0 0 256 0 256 256">
6
+
7
+ <polygon fill="#FFFFFF" points="48 48 208 48 208 208 176 208 176 80 128 80 128 208 48 208">
8
+
9
+ </g>
10
+ </svg>
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
2
+ <svg width="800px" height="800px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><title>file_type_yarn</title><path d="M28.208,24.409a10.493,10.493,0,0,0-3.959,1.822,23.743,23.743,0,0,1-5.835,2.642,1.632,1.632,0,0,1-.983.55A62.228,62.228,0,0,1,10.984,30c-1.163.009-1.876-.3-2.074-.776a1.573,1.573,0,0,1,.866-2.074,3.759,3.759,0,0,1-.514-.379c-.171-.171-.352-.514-.406-.388-.225.55-.343,1.894-.947,2.5-.83.839-2.4.559-3.328.072-1.019-.541.072-1.813.072-1.813a.73.73,0,0,1-.992-.343,4.847,4.847,0,0,1-.667-2.949,5.374,5.374,0,0,1,1.749-2.895,9.334,9.334,0,0,1,.658-4.4,10.445,10.445,0,0,1,3.165-3.661S6.628,10.747,7.35,8.817c.469-1.262.658-1.253.812-1.308a3.633,3.633,0,0,0,1.452-.857,5.265,5.265,0,0,1,4.41-1.7S15.2,1.4,16.277,2.09a18.349,18.349,0,0,1,1.533,2.886s1.281-.748,1.425-.469a11.334,11.334,0,0,1,.523,6.132,14.01,14.01,0,0,1-2.6,5.411c-.135.225,1.551.938,2.615,3.887.983,2.7.108,4.96.262,5.212.027.045.036.063.036.063s1.127.09,3.391-1.308A8.5,8.5,0,0,1,27.739,22.3a1.081,1.081,0,0,1,.469,2.11Z" style="fill:#2188b6"/></svg>
package/dist/shiki.js CHANGED
@@ -1,11 +1,14 @@
1
1
  import { createHighlighter } from 'shiki';
2
- let highlighter;
2
+ let highlighter = null;
3
+ const highlighterPromise = createHighlighter({
4
+ themes: ['github-light', 'github-dark'],
5
+ langs: ['svelte', 'typescript', 'javascript', 'html', 'css', 'json', 'bash']
6
+ }).then((h) => {
7
+ highlighter = h;
8
+ return h;
9
+ });
3
10
  export async function getHighlighterSingleton() {
4
- if (!highlighter) {
5
- highlighter = await createHighlighter({
6
- themes: ['github-light', 'github-dark'],
7
- langs: ['svelte', 'typescript', 'javascript', 'html', 'css', 'json', 'bash']
8
- });
9
- }
10
- return highlighter;
11
+ if (highlighter)
12
+ return highlighter;
13
+ return highlighterPromise;
11
14
  }
package/dist/types.d.ts CHANGED
@@ -12,11 +12,16 @@ export interface ToolbarProps {
12
12
  presentation?: boolean;
13
13
  viewState?: 'code' | 'preview';
14
14
  themeState?: 'light' | 'dark';
15
+ onToggleTheme?: () => void;
15
16
  modeState?: 'code' | 'playground' | 'mixed';
17
+ children?: Snippet;
18
+ files?: FileItem[];
16
19
  }
17
20
  export interface FilesProps {
18
21
  files?: FileItem[];
19
22
  activeIndex: number;
23
+ viewState: 'code' | 'preview';
24
+ modeState: 'code' | 'playground' | 'mixed';
20
25
  }
21
26
  export interface FileItem {
22
27
  name: string;
package/dist/utils.d.ts CHANGED
@@ -5,3 +5,6 @@ export declare const dictionary: {
5
5
  export declare const dictionaryIcons: {
6
6
  [key: string]: string;
7
7
  };
8
+ export declare const dictionaryPkgIcons: {
9
+ [key: string]: string;
10
+ };
package/dist/utils.js CHANGED
@@ -4,6 +4,9 @@ import svelte from './languages/svelte.svg';
4
4
  import css from './languages/css.svg';
5
5
  import html from './languages/html.svg';
6
6
  import shell from './languages/shell.svg';
7
+ import npm from './pkg/npm.svg';
8
+ import yarn from './pkg/yarn.svg';
9
+ import bun from './pkg/bun.svg';
7
10
  export const copyToClipboard = (value) => {
8
11
  if (navigator.clipboard && window.isSecureContext) {
9
12
  navigator.clipboard
@@ -59,3 +62,8 @@ export const dictionaryIcons = {
59
62
  htm: html,
60
63
  json: shell
61
64
  };
65
+ export const dictionaryPkgIcons = {
66
+ npm,
67
+ yarn,
68
+ bun
69
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lapikit/repl",
3
- "version": "0.0.0-insiders.24a3260",
3
+ "version": "0.0.0-insiders.2ac5230",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -9,10 +9,12 @@
9
9
  "name": "Nycolaide",
10
10
  "email": "contact@lapikit.dev"
11
11
  },
12
+ "homepage": "https://lapikit.dev",
12
13
  "repository": {
13
14
  "type": "git",
14
- "url": "https://github.com/Nycolaide/lapikit.git"
15
+ "url": "git+https://github.com/lapikit/lapikit-repl.git"
15
16
  },
17
+ "funding": "https://buymeacoffee.com/nycolaide",
16
18
  "scripts": {
17
19
  "dev": "vite dev",
18
20
  "build": "vite build && npm run prepack",
@@ -45,7 +47,8 @@
45
47
  },
46
48
  "peerDependencies": {
47
49
  "@sveltejs/kit": "^2.0.0",
48
- "svelte": "^5.0.0"
50
+ "svelte": "^5.0.0",
51
+ "lapikit": "^0.6.7"
49
52
  },
50
53
  "devDependencies": {
51
54
  "@eslint/compat": "^1.4.0",