@meistrari/tela-build 1.57.3 → 1.57.4

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.
@@ -2,8 +2,8 @@
2
2
  import type { HTMLAttributes } from 'vue'
3
3
 
4
4
  import type { BuiltinLanguage } from 'shiki'
5
- import { codeToHtml } from 'shiki'
6
5
 
6
+ import { highlightCode } from './highlighter'
7
7
  import { TelaTheme } from './tela-theme'
8
8
 
9
9
  const props = withDefaults(defineProps<{
@@ -33,24 +33,32 @@ const theme = computed(() => TelaTheme(props.background ?? 'transparent'))
33
33
  const lineCount = computed(() => props.code?.split('\n').length || 1)
34
34
  const isSingleLine = computed(() => lineCount.value === 1)
35
35
 
36
- watchEffect(() => {
37
- if (props.code && props.language) {
38
- isLoading.value = true
39
- codeToHtml(props.code, {
40
- lang: props.language,
41
- theme: theme.value,
42
- })
43
- .then((html) => {
44
- highlighted.value = html
45
- isLoading.value = false
46
- })
47
- .catch(() => {
48
- isLoading.value = false
49
- })
50
- }
51
- else {
36
+ watchEffect((onCleanup) => {
37
+ if (!props.code || !props.language) {
52
38
  isLoading.value = false
39
+ return
53
40
  }
41
+
42
+ let stale = false
43
+
44
+ onCleanup(() => {
45
+ stale = true
46
+ })
47
+
48
+ isLoading.value = true
49
+
50
+ highlightCode(props.code, props.language, theme.value)
51
+ .then((html) => {
52
+ if (stale)
53
+ return
54
+
55
+ highlighted.value = html
56
+ isLoading.value = false
57
+ })
58
+ .catch(() => {
59
+ if (!stale)
60
+ isLoading.value = false
61
+ })
54
62
  })
55
63
  </script>
56
64
 
@@ -0,0 +1,79 @@
1
+ import type { HighlighterCore, LanguageInput, ThemeRegistrationAny } from 'shiki/core'
2
+ import { createHighlighterCore } from 'shiki/core'
3
+ import { createJavaScriptRegexEngine } from 'shiki/engine/javascript'
4
+ import { bundledLanguages } from 'shiki/langs'
5
+
6
+ /**
7
+ * Shiki loaded through the fine-grained core API instead of the `shiki` bundle
8
+ * entry. The bundle entry pulls in the Oniguruma engine, whose WASM is
9
+ * base64-inlined into the JS chunk (~640KB parsed on first import) even when a
10
+ * page never renders a code block. The JavaScript regex engine covers every
11
+ * grammar we ship and costs nothing up front.
12
+ *
13
+ * Every bundled language stays available: `bundledLanguages` is a map of
14
+ * dynamic imports, so a grammar is only fetched when a code block asks for it.
15
+ * Grammars and themes are registered once and shared across every
16
+ * TelaCodeBlock instance, so a page with twenty code blocks loads each grammar
17
+ * a single time.
18
+ */
19
+
20
+ /** Names shiki treats as plain text — no grammar to load. */
21
+ const plainLanguages = new Set(['text', 'plaintext', 'txt', 'plain', 'ansi'])
22
+
23
+ let highlighterPromise: Promise<HighlighterCore> | undefined
24
+
25
+ const themeLoads = new Map<string, Promise<void>>()
26
+ const languageLoads = new Map<string, Promise<void>>()
27
+
28
+ async function getHighlighter() {
29
+ highlighterPromise ??= createHighlighterCore({
30
+ themes: [],
31
+ langs: [],
32
+ // `forgiving` downgrades the few Oniguruma-only patterns to a no-match
33
+ // instead of throwing, so an exotic grammar degrades to partial
34
+ // highlighting rather than failing the whole block
35
+ engine: createJavaScriptRegexEngine({ forgiving: true }),
36
+ })
37
+
38
+ return await highlighterPromise
39
+ }
40
+
41
+ async function loadTheme(highlighter: HighlighterCore, theme: ThemeRegistrationAny) {
42
+ const name = theme.name ?? 'default'
43
+
44
+ let pending = themeLoads.get(name)
45
+
46
+ if (!pending) {
47
+ pending = highlighter.loadTheme(theme).then(() => undefined)
48
+ themeLoads.set(name, pending)
49
+ }
50
+
51
+ return await pending
52
+ }
53
+
54
+ async function loadLanguage(highlighter: HighlighterCore, language: string) {
55
+ let pending = languageLoads.get(language)
56
+
57
+ if (!pending) {
58
+ const grammar = bundledLanguages[language as keyof typeof bundledLanguages] as LanguageInput
59
+
60
+ pending = highlighter.loadLanguage(grammar).then(() => undefined)
61
+ languageLoads.set(language, pending)
62
+ }
63
+
64
+ return await pending
65
+ }
66
+
67
+ export async function highlightCode(code: string, language: string, theme: ThemeRegistrationAny) {
68
+ const highlighter = await getHighlighter()
69
+
70
+ // an unknown language renders as plain text rather than throwing
71
+ const resolved = !plainLanguages.has(language) && Object.hasOwn(bundledLanguages, language) ? language : 'text'
72
+
73
+ await Promise.all([
74
+ loadTheme(highlighter, theme),
75
+ resolved === 'text' ? undefined : loadLanguage(highlighter, resolved),
76
+ ])
77
+
78
+ return highlighter.codeToHtml(code, { lang: resolved, theme: theme.name ?? 'default' })
79
+ }
@@ -2,7 +2,9 @@ import type { RawTheme } from 'shiki'
2
2
 
3
3
  export function TelaTheme(background = '#FAFAFB') {
4
4
  return ({
5
- name: 'tela-light',
5
+ // the background is baked into the theme, so it has to be part of the
6
+ // name — the highlighter registers and caches themes by name
7
+ name: `tela-light-${background}`,
6
8
  colors: {
7
9
  'editor.background': background,
8
10
  },
@@ -14,6 +14,16 @@ const props = withDefaults(defineProps<{
14
14
  isAnimated: true,
15
15
  })
16
16
 
17
+ const isMotionReady = ref(false)
18
+
19
+ onMounted(() => {
20
+ requestAnimationFrame(() => {
21
+ isMotionReady.value = true
22
+ })
23
+ })
24
+
25
+ const isMotionActive = computed(() => props.isAnimated && isMotionReady.value)
26
+
17
27
  const colorValue = computed(() => {
18
28
  if (props.color.startsWith('#')) {
19
29
  return props.color
@@ -38,7 +48,7 @@ const colorValue = computed(() => {
38
48
  <svg :width="size" :height="size" viewBox="0 0 12 12" fill="none" :style="{ color: colorValue }" xmlns="http://www.w3.org/2000/svg">
39
49
  <template v-if="name === 'circle'">
40
50
  <component
41
- :is="isAnimated ? Motion : 'path'"
51
+ :is="isMotionActive ? Motion : 'path'"
42
52
  as="path"
43
53
  d="M12 6C12 9.31371 9.31371 12 6 12C2.68629 12 0 9.31371 0 6C0 2.68629 2.68629 0 6 0C9.31371 0 12 2.68629 12 6ZM1.2 6C1.2 8.65097 3.34903 10.8 6 10.8C8.65097 10.8 10.8 8.65097 10.8 6C10.8 3.34903 8.65097 1.2 6 1.2C3.34903 1.2 1.2 3.34903 1.2 6Z"
44
54
  fill="currentColor"
@@ -59,7 +69,7 @@ const colorValue = computed(() => {
59
69
 
60
70
  <template v-if="name === 'queued'">
61
71
  <component
62
- :is="isAnimated ? Motion : 'path'"
72
+ :is="isMotionActive ? Motion : 'path'"
63
73
  as="path"
64
74
  d="M12 6C12 9.31371 9.31371 12 6 12C2.68629 12 0 9.31371 0 6C0 2.68629 2.68629 0 6 0C9.31371 0 12 2.68629 12 6ZM1.2 6C1.2 8.65097 3.34903 10.8 6 10.8C8.65097 10.8 10.8 8.65097 10.8 6C10.8 3.34903 8.65097 1.2 6 1.2C3.34903 1.2 1.2 3.34903 1.2 6Z"
65
75
  fill="currentColor"
@@ -68,7 +78,7 @@ const colorValue = computed(() => {
68
78
  :transition="{ duration: 0.8, ease: [0.4, 0, 0.2, 1] }"
69
79
  />
70
80
  <component
71
- :is="isAnimated ? Motion : 'rect'"
81
+ :is="isMotionActive ? Motion : 'rect'"
72
82
  as="rect"
73
83
  x="3.5"
74
84
  y="6.5"
@@ -81,7 +91,7 @@ const colorValue = computed(() => {
81
91
  :transition="{ duration: 0.8, ease: 'easeOut', delay: 0.17, repeat: Infinity, repeatDelay: 1 }"
82
92
  />
83
93
  <component
84
- :is="isAnimated ? Motion : 'rect'"
94
+ :is="isMotionActive ? Motion : 'rect'"
85
95
  as="rect"
86
96
  x="4"
87
97
  y="4.5"
@@ -94,7 +104,7 @@ const colorValue = computed(() => {
94
104
  :transition="{ duration: 0.8, ease: 'easeOut', delay: 0.12, repeat: Infinity, repeatDelay: 1 }"
95
105
  />
96
106
  <component
97
- :is="isAnimated ? Motion : 'rect'"
107
+ :is="isMotionActive ? Motion : 'rect'"
98
108
  as="rect"
99
109
  x="4.5"
100
110
  y="3"
@@ -124,7 +134,8 @@ const colorValue = computed(() => {
124
134
  </template>
125
135
 
126
136
  <template v-if="name === 'circle-notch'">
127
- <Motion
137
+ <component
138
+ :is="isMotionActive ? Motion : 'path'"
128
139
  as="path"
129
140
  d="M11.4 6C11.7314 6 12.0031 5.73069 11.97 5.40098C11.858 4.28448 11.4345 3.21734 10.7422 2.32414C9.92681 1.27227 8.78487 0.521339 7.49603 0.189501C6.20719 -0.142337 4.84459 -0.0362459 3.62264 0.491083C2.40069 1.01841 1.38875 1.93705 0.746034 3.10244C0.103318 4.26784 -0.133688 5.61385 0.0723077 6.92869C0.278303 8.24353 0.915607 9.45256 1.88394 10.3656C2.85228 11.2786 4.09668 11.8437 5.42136 11.972C6.5462 12.081 7.67471 11.8698 8.67877 11.3688C8.97528 11.2209 9.05806 10.8474 8.88125 10.5671C8.70444 10.2868 8.3351 10.2068 8.03498 10.3473C7.25926 10.7104 6.39684 10.8609 5.53709 10.7776C4.47735 10.6749 3.48182 10.2228 2.70715 9.49244C1.93249 8.76205 1.42264 7.79482 1.25785 6.74295C1.09305 5.69108 1.28265 4.61427 1.79683 3.68195C2.311 2.74964 3.12055 2.01473 4.09811 1.59287C5.07567 1.171 6.16575 1.08613 7.19682 1.3516C8.2279 1.61707 9.14145 2.21782 9.79373 3.05932C10.3229 3.74201 10.6558 4.55173 10.7626 5.40155C10.8039 5.73033 11.0686 6 11.4 6Z"
130
141
  fill="currentColor"
@@ -136,7 +147,7 @@ const colorValue = computed(() => {
136
147
 
137
148
  <template v-if="name === 'circle-minus'">
138
149
  <component
139
- :is="isAnimated ? Motion : 'path'"
150
+ :is="isMotionActive ? Motion : 'path'"
140
151
  as="path"
141
152
  d="M12 6C12 9.31371 9.31371 12 6 12C2.68629 12 0 9.31371 0 6C0 2.68629 2.68629 0 6 0C9.31371 0 12 2.68629 12 6ZM1.2 6C1.2 8.65097 3.34903 10.8 6 10.8C8.65097 10.8 10.8 8.65097 10.8 6C10.8 3.34903 8.65097 1.2 6 1.2C3.34903 1.2 1.2 3.34903 1.2 6Z"
142
153
  fill="currentColor"
@@ -145,7 +156,7 @@ const colorValue = computed(() => {
145
156
  :transition="{ duration: 2.2, ease: 'easeOut', repeat: Infinity }"
146
157
  />
147
158
  <component
148
- :is="isAnimated ? Motion : 'rect'"
159
+ :is="isMotionActive ? Motion : 'rect'"
149
160
  as="rect"
150
161
  x="3.5"
151
162
  y="5"
@@ -161,7 +172,7 @@ const colorValue = computed(() => {
161
172
 
162
173
  <template v-if="name === 'check'">
163
174
  <component
164
- :is="isAnimated ? Motion : 'g'"
175
+ :is="isMotionActive ? Motion : 'g'"
165
176
  as="g"
166
177
  :initial="{ opacity: 0.4 }"
167
178
  :animate="{ opacity: 1 }"
@@ -173,7 +184,7 @@ const colorValue = computed(() => {
173
184
  fill="currentColor"
174
185
  />
175
186
  <component
176
- :is="isAnimated ? Motion : 'path'"
187
+ :is="isMotionActive ? Motion : 'path'"
177
188
  as="path"
178
189
  d="M3.5 6 L5.5 8 L8.5 4"
179
190
  stroke="currentColor"
@@ -184,7 +195,7 @@ const colorValue = computed(() => {
184
195
  tabindex="-1"
185
196
  :initial="{ pathLength: 0 }"
186
197
  :animate="{ pathLength: 1 }"
187
- :style="{ rotate: isAnimated ? 4 : undefined, scale: 0.85, transformOrigin: '6px 6px', transformBox: 'view-box' }"
198
+ :style="{ rotate: isMotionActive ? 4 : undefined, scale: 0.85, transformOrigin: '6px 6px', transformBox: 'view-box' }"
188
199
  :transition="{ duration: 0.3, ease: [0.645, 0.045, 0.355, 1] }"
189
200
  />
190
201
  </component>
@@ -219,14 +230,14 @@ const colorValue = computed(() => {
219
230
 
220
231
  <template v-if="name === 'warning'">
221
232
  <component
222
- :is="isAnimated ? Motion : 'g'"
233
+ :is="isMotionActive ? Motion : 'g'"
223
234
  as="g"
224
235
  tabindex="-1"
225
236
  :animate="{ opacity: [0.5, 1, 0.5] }"
226
237
  :transition="{ duration: 1.4, ease: 'easeOut', repeat: Infinity }"
227
238
  >
228
239
  <component
229
- :is="isAnimated ? Motion : 'path'"
240
+ :is="isMotionActive ? Motion : 'path'"
230
241
  as="path"
231
242
  d="M11.4 6C11.7314 6 12.0031 5.73069 11.97 5.40098C11.858 4.28448 11.4345 3.21734 10.7422 2.32414C9.92681 1.27227 8.78487 0.521339 7.49603 0.189501C6.20719 -0.142337 4.84459 -0.0362459 3.62264 0.491083C2.40069 1.01841 1.38875 1.93705 0.746034 3.10244C0.103318 4.26784 -0.133688 5.61385 0.0723077 6.92869C0.278303 8.24353 0.915607 9.45256 1.88394 10.3656C2.85228 11.2786 4.09668 11.8437 5.42136 11.972C6.5462 12.081 7.67471 11.8698 8.67877 11.3688C8.97528 11.2209 9.05806 10.8474 8.88125 10.5671C8.70444 10.2868 8.3351 10.2068 8.03498 10.3473C7.25926 10.7104 6.39684 10.8609 5.53709 10.7776C4.47735 10.6749 3.48182 10.2228 2.70715 9.49244C1.93249 8.76205 1.42264 7.79482 1.25785 6.74295C1.09305 5.69108 1.28265 4.61427 1.79683 3.68195C2.311 2.74964 3.12055 2.01473 4.09811 1.59287C5.07567 1.171 6.16575 1.08613 7.19682 1.3516C8.2279 1.61707 9.14145 2.21782 9.79373 3.05932C10.3229 3.74201 10.6558 4.55173 10.7626 5.40155C10.8039 5.73033 11.0686 6 11.4 6Z"
232
243
  fill="currentColor"
@@ -235,7 +246,7 @@ const colorValue = computed(() => {
235
246
  :transition="{ duration: 0.7, ease: 'easeOut', repeat: Infinity, repeatDelay: 0.1 }"
236
247
  />
237
248
  <component
238
- :is="isAnimated ? Motion : 'rect'"
249
+ :is="isMotionActive ? Motion : 'rect'"
239
250
  as="rect"
240
251
  x="5.25"
241
252
  y="3"
@@ -259,7 +270,7 @@ const colorValue = computed(() => {
259
270
 
260
271
  <template v-if="name === 'check-dashed'">
261
272
  <component
262
- :is="isAnimated ? Motion : 'g'" as="g" clip-path="url(#clip0_5125_104)" tabindex="-1" :initial="{ opacity: 0.4 }" :animate="{ opacity: 1 }" :transition="{ duration: 0.3, ease: [0.645, 0.045, 0.355, 1] }"
273
+ :is="isMotionActive ? Motion : 'g'" as="g" clip-path="url(#clip0_5125_104)" tabindex="-1" :initial="{ opacity: 0.4 }" :animate="{ opacity: 1 }" :transition="{ duration: 0.3, ease: [0.645, 0.045, 0.355, 1] }"
263
274
  >
264
275
  <path d="M12 6C12 9.31371 9.31371 12 6 12C2.68629 12 0 9.31371 0 6C0 2.68629 2.68629 0 6 0C9.31371 0 12 2.68629 12 6ZM1.2 6C1.2 8.65097 3.34903 10.8 6 10.8C8.65097 10.8 10.8 8.65097 10.8 6C10.8 3.34903 8.65097 1.2 6 1.2C3.34903 1.2 1.2 3.34903 1.2 6Z" fill="currentColor" />
265
276
  <rect x="5.25" width="1.5" height="12" fill="#F0FDF6" />
@@ -267,7 +278,7 @@ const colorValue = computed(() => {
267
278
  <rect x="10.773" y="9.71228" width="1.5" height="12" transform="rotate(135 10.773 9.71228)" fill="#F0FDF6" />
268
279
  <rect x="2.28769" y="10.7729" width="1.5" height="12" transform="rotate(-135 2.28769 10.7729)" fill="#F0FDF6" />
269
280
  <component
270
- :is="isAnimated ? Motion : 'path'"
281
+ :is="isMotionActive ? Motion : 'path'"
271
282
  as="path"
272
283
  d="M3.5 6 L5.5 8 L8.5 4"
273
284
  stroke="currentColor"
@@ -307,7 +318,7 @@ const colorValue = computed(() => {
307
318
  <template v-if="name === 'arrow-down'">
308
319
  <path d="M12 6C12 9.31371 9.31371 12 6 12C2.68629 12 0 9.31371 0 6C0 2.68629 2.68629 0 6 0C9.31371 0 12 2.68629 12 6ZM1.2 6C1.2 8.65097 3.34903 10.8 6 10.8C8.65097 10.8 10.8 8.65097 10.8 6C10.8 3.34903 8.65097 1.2 6 1.2C3.34903 1.2 1.2 3.34903 1.2 6Z" fill="#A3A3A3" />
309
320
  <component
310
- :is="isAnimated ? Motion : 'path'"
321
+ :is="isMotionActive ? Motion : 'path'"
311
322
  as="path"
312
323
  d="M6 4V8M6 8L7.75 6.53682M6 8L4.25 6.53682"
313
324
  stroke="currentColor"
@@ -59,7 +59,6 @@ const value = ref('')
59
59
  <template>
60
60
  <TelaInput
61
61
  v-model="value"
62
- label="Username"
63
62
  placeholder="Enter your username"
64
63
  />
65
64
  </template>
@@ -70,7 +69,6 @@ const value = ref('')
70
69
  ```vue
71
70
  <TelaInput
72
71
  v-model="value"
73
- label="Email"
74
72
  placeholder="you@example.com"
75
73
  show-clear-button
76
74
  />
@@ -92,7 +90,6 @@ const value = ref('')
92
90
  ```vue
93
91
  <TelaInput
94
92
  v-model="email"
95
- label="Email"
96
93
  placeholder="you@example.com"
97
94
  error="Please enter a valid email address"
98
95
  />
@@ -102,10 +99,9 @@ const value = ref('')
102
99
 
103
100
  ```vue
104
101
  <TelaInput
105
- label="Disabled Input"
106
102
  placeholder="Cannot edit"
107
- disabled
108
103
  model-value="Read only value"
104
+ disabled
109
105
  />
110
106
  ```
111
107
 
@@ -113,7 +109,6 @@ const value = ref('')
113
109
  <TelaInput
114
110
  v-model="message"
115
111
  type="textarea"
116
- label="Message"
117
112
  placeholder="Enter your message..."
118
113
  />
119
114
  ```
@@ -123,7 +118,6 @@ const value = ref('')
123
118
  ```vue
124
119
  <TelaInput
125
120
  v-model="value"
126
- label="Custom Input"
127
121
  label-class="text-blue-600 font-bold"
128
122
  input-class="border-blue-400"
129
123
  />
@@ -134,7 +128,6 @@ const value = ref('')
134
128
  ```vue
135
129
  <TelaInput
136
130
  v-model="bio"
137
- label="Bio"
138
131
  placeholder="Tell us about yourself"
139
132
  :maxlength="100"
140
133
  />
@@ -150,10 +150,7 @@ defineExpose({
150
150
  <label
151
151
  v-if="label"
152
152
  :for="id"
153
- text="12px gray-400"
154
- body-12-semibold
155
- flex
156
- align-center
153
+ body-12-semibold text-tertiary flex align-center
157
154
  :class="[disabled && 'cursor-not-allowed', props.labelClass]"
158
155
  >
159
156
  <slot name="label-leading" />
@@ -39,7 +39,8 @@ One-time passcode input. Renders a row of single-character slots that share one
39
39
  const code = ref('')
40
40
 
41
41
  function onComplete(value: string) {
42
- submit(value)
42
+ // fires once every slot is filled — verify the code here
43
+ console.log(value)
43
44
  }
44
45
  </script>
45
46
 
@@ -102,7 +102,9 @@ const isLoading = ref(true)
102
102
  const data = ref(null)
103
103
 
104
104
  onMounted(async () => {
105
- data.value = await fetchData()
105
+ // swap this for your own request
106
+ data.value = await new Promise(resolve =>
107
+ setTimeout(() => resolve({ title: 'Loaded', content: 'Content is ready.' }), 1200))
106
108
  isLoading.value = false
107
109
  })
108
110
  </script>
package/nuxt.config.ts CHANGED
@@ -99,8 +99,25 @@ export default defineNuxtConfig({
99
99
  },
100
100
  },
101
101
  optimizeDeps: {
102
+ // Components here are pulled in by auto-imports from lazily-imported
103
+ // pages, so esbuild's dependency scanner never sees them from the app
104
+ // entry. Left undeclared, the first page that renders one of these
105
+ // components makes Vite discover the dep mid-session, re-optimize, and
106
+ // hard-reload the browser. Prebundling them up front keeps dev stable.
102
107
  include: [
103
- 'shiki',
108
+ '@number-flow/vue',
109
+ 'clsx',
110
+ 'date-fns',
111
+ 'radix-vue',
112
+ 'reka-ui',
113
+ 'shiki/core',
114
+ 'shiki/engine/javascript',
115
+ 'tailwind-merge',
116
+ 'vue-i18n',
117
+ 'vue-input-otp',
118
+ ],
119
+ exclude: [
120
+ 'shiki/langs',
104
121
  ],
105
122
  },
106
123
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/tela-build",
3
- "version": "1.57.3",
3
+ "version": "1.57.4",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "app.config.ts",