@codemonster-ru/vue-codeblock 1.0.0

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) 2026-present Kirill Kolesnikov
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 ADDED
@@ -0,0 +1,150 @@
1
+ # @codemonster-ru/vue-codeblock
2
+
3
+ [![npm version](https://img.shields.io/npm/v/%40codemonster-ru%2Fvue-codeblock)](https://www.npmjs.com/package/@codemonster-ru/vue-codeblock)
4
+ [![npm downloads](https://img.shields.io/npm/dw/%40codemonster-ru%2Fvue-codeblock)](https://www.npmjs.com/package/@codemonster-ru/vue-codeblock)
5
+ [![license](https://img.shields.io/npm/l/%40codemonster-ru%2Fvue-codeblock)](https://github.com/codemonster-ru/vue-codeblock/blob/main/LICENSE)
6
+
7
+ Standalone Vue 3 code block component with built-in syntax highlighting, light/dark theme support, copy actions, line numbers, and a small shared highlighting runtime you can also use outside the component.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm i @codemonster-ru/vue-codeblock
13
+ ```
14
+
15
+ ## Use Cases
16
+
17
+ - package documentation
18
+ - design system docs
19
+ - internal developer portals
20
+ - settings/admin panels that need to render code examples
21
+
22
+ ## Component Usage
23
+
24
+ Register the plugin:
25
+
26
+ ```ts
27
+ import { createApp } from "vue";
28
+ import App from "./App.vue";
29
+ import VueCodeBlock from "@codemonster-ru/vue-codeblock";
30
+
31
+ createApp(App).use(VueCodeBlock).mount("#app");
32
+ ```
33
+
34
+ Or import the component directly:
35
+
36
+ ```vue
37
+ <script setup lang="ts">
38
+ import { CodeBlock } from "@codemonster-ru/vue-codeblock";
39
+ </script>
40
+
41
+ <template>
42
+ <CodeBlock
43
+ language="vue"
44
+ filename="ButtonExample.vue"
45
+ :show-line-numbers="true"
46
+ :code="`<Button label=&quot;Save&quot; />`"
47
+ />
48
+ </template>
49
+ ```
50
+
51
+ If you want the package CSS explicitly:
52
+
53
+ ```ts
54
+ import "@codemonster-ru/vue-codeblock/style.css";
55
+ ```
56
+
57
+ ## Runtime Usage
58
+
59
+ The package also exports the shared highlighter:
60
+
61
+ ```ts
62
+ import {
63
+ highlightCodeBlock,
64
+ escapeCodeHtml,
65
+ } from "@codemonster-ru/vue-codeblock";
66
+
67
+ const html = highlightCodeBlock("ts", "const answer = 42;");
68
+ const escaped = escapeCodeHtml("<Button />");
69
+ ```
70
+
71
+ ## Props
72
+
73
+ | Prop | Type | Default | Purpose |
74
+ | ----------------- | -------------------------------- | -------------- | ------------------------------------ |
75
+ | `code` | `string` | `''` | Raw source code |
76
+ | `language` | `CodeBlockLanguage` | `'plaintext'` | Language hint for highlighting |
77
+ | `filename` | `string` | `''` | Optional filename in header |
78
+ | `showHeader` | `boolean` | `true` | Shows the top meta/action bar |
79
+ | `showLineNumbers` | `boolean` | `false` | Renders line numbers |
80
+ | `copyable` | `boolean` | `true` | Shows copy action |
81
+ | `copyLabel` | `string` | `'Copy'` | Copy button text |
82
+ | `copiedLabel` | `string` | `'Copied'` | Temporary copied state label |
83
+ | `copiedDuration` | `number` | `1200` | Copied state timeout in ms |
84
+ | `languageLabel` | `string` | `'Language'` | Header label before language |
85
+ | `disabled` | `boolean` | `false` | Disables actions |
86
+ | `wrap` | `boolean` | `false` | Enables wrapped lines |
87
+ | `highlight` | `boolean` | `true` | Turns highlighting on/off |
88
+ | `maxHeight` | `string` | `''` | Optional scroll container max height |
89
+ | `ariaLabel` | `string` | `'Code block'` | Accessibility label |
90
+ | `theme` | `'inherit' \| 'light' \| 'dark'` | `'inherit'` | Theme mode override |
91
+
92
+ ## Events
93
+
94
+ | Event | Payload |
95
+ | ------ | ------------------ |
96
+ | `copy` | `{ text: string }` |
97
+
98
+ ## Slots
99
+
100
+ | Slot | Purpose |
101
+ | --------- | ------------------------------------------ |
102
+ | `actions` | Add custom actions next to the copy button |
103
+
104
+ ## Supported Languages
105
+
106
+ Canonical built-in values:
107
+
108
+ `plaintext`, `text`, `txt`, `js`, `javascript`, `ts`, `typescript`, `vue`, `html`, `json`, `bash`, `shell`, `sh`, `css`, `scss`, `sass`
109
+
110
+ You can import them as `SUPPORTED_CODE_BLOCK_LANGUAGES`.
111
+
112
+ ## Theming
113
+
114
+ The component ships with light and dark defaults. You can override it with:
115
+
116
+ - `theme="light"`
117
+ - `theme="dark"`
118
+ - `theme="inherit"` and an ancestor `data-theme="dark"`
119
+
120
+ Main CSS custom properties:
121
+
122
+ - `--vcb-background-color`
123
+ - `--vcb-text-color`
124
+ - `--vcb-border-color`
125
+ - `--vcb-padding`
126
+ - `--vcb-font-size`
127
+ - `--vcb-line-height`
128
+ - `--vcb-token-keyword-color`
129
+ - `--vcb-token-string-color`
130
+ - `--vcb-token-number-color`
131
+ - `--vcb-token-variable-color`
132
+ - `--vcb-token-function-color`
133
+ - `--vcb-token-property-color`
134
+ - `--vcb-token-directive-color`
135
+
136
+ Example:
137
+
138
+ ```css
139
+ .docs-surface {
140
+ --vcb-background-color: #081224;
141
+ --vcb-border-color: rgba(96, 165, 250, 0.28);
142
+ --vcb-token-keyword-color: #d8b4fe;
143
+ }
144
+ ```
145
+
146
+ ## Notes
147
+
148
+ - The built-in highlighter is lightweight and regex-based by design.
149
+ - It is tuned for documentation and UI examples, not for full IDE-grade parsing.
150
+ - If you need more languages later, extend the highlighter runtime rather than patching rendered HTML.
@@ -0,0 +1,39 @@
1
+ import { CodeBlockCopyPayload, CodeBlockProps } from '../types';
2
+ declare function __VLS_template(): {
3
+ attrs: Partial<{}>;
4
+ slots: {
5
+ actions?(_: {}): any;
6
+ };
7
+ refs: {};
8
+ rootEl: HTMLElement;
9
+ };
10
+ type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
11
+ declare const __VLS_component: import('vue').DefineComponent<CodeBlockProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
12
+ copy: (payload: CodeBlockCopyPayload) => any;
13
+ }, string, import('vue').PublicProps, Readonly<CodeBlockProps> & Readonly<{
14
+ onCopy?: ((payload: CodeBlockCopyPayload) => any) | undefined;
15
+ }>, {
16
+ code: string;
17
+ language: import('..').CodeBlockLanguage;
18
+ filename: string;
19
+ showHeader: boolean;
20
+ showLineNumbers: boolean;
21
+ copyable: boolean;
22
+ copyLabel: string;
23
+ copiedLabel: string;
24
+ copiedDuration: number;
25
+ languageLabel: string;
26
+ disabled: boolean;
27
+ wrap: boolean;
28
+ highlight: boolean;
29
+ maxHeight: string;
30
+ ariaLabel: string;
31
+ theme: import('..').CodeBlockTheme;
32
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLElement>;
33
+ declare const _default: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
34
+ export default _default;
35
+ type __VLS_WithTemplateSlots<T, S> = T & {
36
+ new (): {
37
+ $slots: S;
38
+ };
39
+ };
package/dist/index.css ADDED
@@ -0,0 +1 @@
1
+ .vcb{display:grid;gap:var(--vcb-gap, 0);border:1px solid var(--vcb-border-color, #cbd5e1);border-radius:var(--vcb-border-radius, .625rem);background:var(--vcb-background-color, #f8fbff);color:var(--vcb-text-color, #0f172a);font-family:var(--vcb-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-size:var(--vcb-font-size, .8125rem);line-height:var(--vcb-line-height, 1.5)}.vcb__header{display:flex;align-items:center;justify-content:space-between;gap:var(--vcb-header-gap, .75rem);padding:var(--vcb-header-padding, .55rem .75rem);border-bottom:1px solid var(--vcb-header-border-color, rgba(148, 163, 184, .28))}.vcb__meta{display:inline-flex;align-items:center;gap:var(--vcb-meta-gap, .55rem);color:var(--vcb-meta-color, #475569);font-size:var(--vcb-meta-font-size, .75rem)}.vcb__filename{color:var(--vcb-filename-color, #0f172a);font-weight:var(--vcb-filename-font-weight, 600)}.vcb__actions{display:inline-flex;align-items:center;gap:var(--vcb-actions-gap, .45rem)}.vcb__copy{border:1px solid var(--vcb-action-border-color, rgba(148, 163, 184, .38));border-radius:var(--vcb-action-border-radius, .375rem);background:var(--vcb-action-background-color, #ffffff);color:var(--vcb-action-text-color, #0f172a);padding:var(--vcb-action-padding, .2rem .55rem);font-size:var(--vcb-action-font-size, .75rem)}.vcb__pre{margin:0;padding:var(--vcb-padding, .75rem .9rem);overflow:auto;white-space:pre}.vcb__pre_wrap{white-space:pre-wrap;word-break:break-word}.vcb__line{display:flex;align-items:baseline;gap:var(--vcb-line-gap, .7rem)}.vcb__line-number{text-align:right;color:var(--vcb-line-number-color, #64748b);min-width:var(--vcb-line-number-min-width, 2.1rem);-webkit-user-select:none;user-select:none}.vcb__line-content{white-space:inherit}.vcb__token_keyword{color:var(--vcb-token-keyword-color, #8b2fc9)}.vcb__token_string{color:var(--vcb-token-string-color, #0f7b45)}.vcb__token_number{color:var(--vcb-token-number-color, #c2410c)}.vcb__token_comment{color:var(--vcb-token-comment-color, #64748b)}.vcb__token_variable{color:var(--vcb-token-variable-color, #0f4c81)}.vcb__token_identifier{color:var(--vcb-token-identifier-color, #0369a1)}.vcb__token_function{color:var(--vcb-token-function-color, #1d4ed8)}.vcb__token_property{color:var(--vcb-token-property-color, #6d28d9)}.vcb__token_operator{color:var(--vcb-token-operator-color, #be185d)}.vcb__token_tag{color:var(--vcb-token-tag-color, #0f4c81)}.vcb__token_selector{color:var(--vcb-token-selector-color, #0f4c81)}.vcb__token_component{color:var(--vcb-token-component-color, #0369a1)}.vcb__token_attribute{color:var(--vcb-token-attribute-color, #7c3aed)}.vcb__token_directive{color:var(--vcb-token-directive-color, #a21caf)}.vcb[data-theme=dark],:root[data-theme=dark] .vcb:not([data-theme=light]),[data-theme=dark] .vcb:not([data-theme=light]){--vcb-background-color: var( --vcb-dark-background-color, #0a1425 );--vcb-text-color: var(--vcb-dark-text-color, #e2e8f0);--vcb-border-color: var( --vcb-dark-border-color, rgba(148, 163, 184, .35) );--vcb-header-border-color: var( --vcb-dark-header-border-color, rgba(148, 163, 184, .35) );--vcb-meta-color: var(--vcb-dark-meta-color, #93a4bf);--vcb-filename-color: var( --vcb-dark-filename-color, #e2e8f0 );--vcb-action-border-color: var( --vcb-dark-action-border-color, rgba(148, 163, 184, .45) );--vcb-action-background-color: var( --vcb-dark-action-background-color, rgba(15, 23, 42, .7) );--vcb-action-text-color: var( --vcb-dark-action-text-color, #e2e8f0 );--vcb-line-number-color: var( --vcb-dark-line-number-color, #64748b );--vcb-token-keyword-color: var( --vcb-dark-token-keyword-color, #c084fc );--vcb-token-string-color: var( --vcb-dark-token-string-color, #86efac );--vcb-token-number-color: var( --vcb-dark-token-number-color, #fca5a5 );--vcb-token-comment-color: var( --vcb-dark-token-comment-color, #94a3b8 );--vcb-token-variable-color: var( --vcb-dark-token-variable-color, #bfdbfe );--vcb-token-tag-color: var( --vcb-dark-token-tag-color, #93c5fd );--vcb-token-selector-color: var( --vcb-dark-token-selector-color, #93c5fd );--vcb-token-component-color: var( --vcb-dark-token-component-color, #67e8f9 );--vcb-token-attribute-color: var( --vcb-dark-token-attribute-color, #c4b5fd );--vcb-token-directive-color: var( --vcb-dark-token-directive-color, #f0abfc );--vcb-token-identifier-color: var( --vcb-dark-token-identifier-color, #7dd3fc );--vcb-token-function-color: var( --vcb-dark-token-function-color, #93c5fd );--vcb-token-property-color: var( --vcb-dark-token-property-color, #a5b4fc );--vcb-token-operator-color: var( --vcb-dark-token-operator-color, #f0abfc )}.vcb_disabled{opacity:var(--vcb-disabled-opacity, .6)}
@@ -0,0 +1,6 @@
1
+ import { Plugin } from 'vue';
2
+ export { default as CodeBlock } from './components/CodeBlock.vue';
3
+ export { escapeCodeHtml, highlightCodeBlock, highlightCodeLine, } from './services/code-highlight';
4
+ export { SUPPORTED_CODE_BLOCK_LANGUAGES, type CodeBlockCopyPayload, type CodeBlockLanguage, type CodeBlockProps, type CodeBlockTheme, type SupportedCodeBlockLanguage, } from './types';
5
+ declare const plugin: Plugin;
6
+ export default plugin;
package/dist/index.js ADDED
@@ -0,0 +1,560 @@
1
+ import { defineComponent as R, ref as F, computed as y, onBeforeUnmount as U, openBlock as m, createElementBlock as b, normalizeClass as q, toDisplayString as w, createCommentVNode as x, createElementVNode as C, renderSlot as G, normalizeStyle as K, Fragment as X, renderList as Y } from "vue";
2
+ const n = (t) => t.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;"), I = (t) => t.replaceAll("&quot;", '"').replaceAll("&#39;", "'").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&"), B = (t) => n(t), l = (t, o) => `<span class="vcb__token ${t}">${o}</span>`, tt = "\\b\\d(?:[\\d_]*\\d)?(?:\\.\\d(?:[\\d_]*\\d)?)?\\b", et = "\\b-?\\d(?:[\\d_]*\\d)?(?:\\.\\d(?:[\\d_]*\\d)?)?(?:px|rem|em|%|vh|vw|fr|s|ms|deg)?\\b", E = (t) => t.startsWith(":") || t.startsWith("@") || t.startsWith("#") || t.startsWith("v-"), V = (t) => /^[A-Z]/.test(t), nt = (t) => {
3
+ let o = 0, s = l("vcb__token_string", n("`"));
4
+ for (; o < t.length - 1; ) {
5
+ const e = t.indexOf("${", o), i = e === -1 ? t.length - 1 : e, c = t.slice(o, i);
6
+ if (c && (s += l("vcb__token_string", n(c))), e === -1)
7
+ break;
8
+ s += n("${");
9
+ let a = e + 2, r = 1;
10
+ for (; a < t.length && r > 0; ) {
11
+ const u = t[a];
12
+ u === "{" ? r += 1 : u === "}" && (r -= 1), a += 1;
13
+ }
14
+ const _ = t.slice(
15
+ e + 2,
16
+ a - 1
17
+ );
18
+ s += M(_), s += n("}"), o = a;
19
+ }
20
+ return s += l("vcb__token_string", n("`")), s;
21
+ }, M = (t) => H(t), st = (t) => {
22
+ if (t.length < 2)
23
+ return l("vcb__token_string", t);
24
+ const o = t[0], s = I(t.slice(1, -1)), e = M(s), i = o === '"' ? "&quot;" : "&#39;";
25
+ return `${i}${e}${i}`;
26
+ }, L = (t, o, s, e, i) => {
27
+ const c = s ? M(I(t)) : l("vcb__token_string", n(t)), a = o === '"' ? "&quot;" : "&#39;";
28
+ return `${e ? a : ""}${c}${i ? a : ""}`;
29
+ }, O = (t) => t.replaceAll(/{{\s*([\s\S]*?)\s*}}/g, (o, s) => {
30
+ const e = M(s);
31
+ return `${n("{{ ")}${e}${n(" }}")}`;
32
+ }), T = (t) => t.replaceAll(
33
+ /(\s+)([:@#]?[\w-]+(?::[\w-]+)?)(\s*=\s*)?("[^"]*"|'[^']*')?/g,
34
+ (o, s, e, i = "", c = "") => `${n(s)}${l(
35
+ E(e) ? "vcb__token_directive" : "vcb__token_attribute",
36
+ n(e)
37
+ )}${n(i)}${c ? E(e) ? st(c) : l(
38
+ "vcb__token_string",
39
+ n(c)
40
+ ) : ""}`
41
+ ), z = (t) => {
42
+ const o = t.match(/^(\s*)<([/\w-]+)([\s\S]*)$/);
43
+ if (!o)
44
+ return n(t);
45
+ const [, s, e, i] = o, c = e.startsWith("/"), a = c ? e.slice(1) : e, r = V(a) ? "vcb__token_component" : "vcb__token_tag";
46
+ return `${n(s)}${n(c ? "</" : "<")}${l(
47
+ r,
48
+ n(a)
49
+ )}${T(String(i))}`;
50
+ }, ct = (t) => {
51
+ if (t.startsWith("<!--") && t.endsWith("-->"))
52
+ return l("vcb__token_comment", n(t));
53
+ const o = t.startsWith("</"), s = t.endsWith("/>"), i = t.slice(
54
+ o ? 2 : 1,
55
+ t.length - (s ? 2 : 1)
56
+ ).match(/^([\w-]+)([\s\S]*)$/);
57
+ if (!i)
58
+ return n(t);
59
+ const [, c, a] = i, r = V(c) ? "vcb__token_component" : "vcb__token_tag";
60
+ return `${n(o ? "</" : "<")}${l(
61
+ r,
62
+ n(c)
63
+ )}${T(a)}${n(s ? "/>" : ">")}`;
64
+ }, ot = (t) => n(t).replaceAll(
65
+ /"(?:[^"\\]|\\.)*"(?=\s*:)?/g,
66
+ (s) => l("vcb__token_string", s)
67
+ ).replaceAll(
68
+ /\b(true|false|null)\b/g,
69
+ (s) => l("vcb__token_keyword", s)
70
+ ).replaceAll(
71
+ /\b-?\d(?:[\d_]*\d)?(?:\.\d(?:[\d_]*\d)?)?(?:[eE][+-]?\d(?:[\d_]*\d)?)?\b/g,
72
+ (s) => l("vcb__token_number", s)
73
+ ), it = (t, o) => {
74
+ for (let s = o - 1; s >= 0; s -= 1) {
75
+ const e = t[s];
76
+ if (!/\s/.test(e))
77
+ return e;
78
+ }
79
+ return "";
80
+ }, J = (t, o) => {
81
+ for (let s = o; s < t.length; s += 1) {
82
+ const e = t[s];
83
+ if (!/\s/.test(e))
84
+ return e;
85
+ }
86
+ return "";
87
+ }, rt = (t, o) => {
88
+ let s = o;
89
+ for (; s > 0 && /\s/.test(t[s - 1]); )
90
+ s -= 1;
91
+ return t.slice(0, s).match(/([A-Za-z_$][\w$]*)\s*$/)?.[1] ?? "";
92
+ }, lt = (t, o, s) => {
93
+ const e = it(t, s), i = J(t, s + o.length), c = rt(t, s), a = t.slice(0, s);
94
+ return e === "." || /\?\.\s*$/.test(a) || i === ":" && ["{", ",", ""].includes(e) ? "vcb__token_property" : i === "(" ? "vcb__token_function" : /^[A-Z]/.test(o) || ["import", "from", "new", "class", "extends"].includes(c) ? "vcb__token_identifier" : "vcb__token_variable";
95
+ }, H = (t) => {
96
+ const o = new RegExp(
97
+ `(//.*$)|((["'\`])(?:\\\\.|(?!\\3).)*\\3)|\\b(const|let|var|function|return|if|else|for|while|class|new|import|export|from|await|async|try|catch|throw|true|false|null|undefined)\\b|(=>|\\?\\?|\\?|:|\\.|[{}()[\\],])|${tt}|\\b[A-Za-z_$][\\w$]*\\b`,
98
+ "g"
99
+ );
100
+ let s = 0, e = "";
101
+ for (const i of t.matchAll(o)) {
102
+ const c = i[0] ?? "", a = i.index ?? 0;
103
+ e += n(t.slice(s, a)), i[1] ? e += l(
104
+ "vcb__token_comment",
105
+ n(c)
106
+ ) : i[2] ? e += c.startsWith("`") && c.includes("${") ? nt(c) : l("vcb__token_string", n(c)) : i[4] ? e += l(
107
+ "vcb__token_keyword",
108
+ n(c)
109
+ ) : i[5] ? e += l(
110
+ "vcb__token_operator",
111
+ n(c)
112
+ ) : /^\d/.test(c) ? e += l(
113
+ "vcb__token_number",
114
+ n(c)
115
+ ) : e += l(
116
+ lt(t, c, a),
117
+ n(c)
118
+ ), s = a + c.length;
119
+ }
120
+ return e += n(t.slice(s)), e;
121
+ }, at = (t, o) => {
122
+ let s = null;
123
+ for (let e = o + 1; e < t.length; e += 1) {
124
+ const i = t[e];
125
+ if (s) {
126
+ i === s && t[e - 1] !== "\\" && (s = null);
127
+ continue;
128
+ }
129
+ if (i === '"' || i === "'") {
130
+ s = i;
131
+ continue;
132
+ }
133
+ if (i === ">")
134
+ return e;
135
+ }
136
+ return -1;
137
+ }, k = (t) => {
138
+ if (!t.trim())
139
+ return n(t);
140
+ let o = 0, s = "";
141
+ for (; o < t.length; ) {
142
+ const e = t.indexOf("<", o);
143
+ if (e === -1) {
144
+ s += O(n(t.slice(o)));
145
+ break;
146
+ }
147
+ s += O(
148
+ n(t.slice(o, e))
149
+ );
150
+ const i = at(t, e);
151
+ if (i === -1) {
152
+ s += n(t.slice(e));
153
+ break;
154
+ }
155
+ s += ct(t.slice(e, i + 1)), o = i + 1;
156
+ }
157
+ return s;
158
+ }, ut = (t) => {
159
+ const o = t.match(/^(\s*)(npm)(\s+)(run)(\s+)([^\s].*)$/);
160
+ if (o) {
161
+ const [
162
+ ,
163
+ r,
164
+ _,
165
+ u,
166
+ f,
167
+ h,
168
+ d
169
+ ] = o;
170
+ return `${n(r)}${l(
171
+ "vcb__token_keyword",
172
+ n(_)
173
+ )}${n(u)}${l(
174
+ "vcb__token_function",
175
+ n(f)
176
+ )}${n(h)}${l(
177
+ "vcb__token_variable",
178
+ n(d)
179
+ )}`;
180
+ }
181
+ const s = (r) => {
182
+ const _ = /(\s+)|((["'])(?:\\.|(?!\3).)*\3)|(\$[A-Za-z_][A-Za-z0-9_]*)|(@[A-Za-z0-9._/-]+|[A-Za-z0-9._-]+\/[A-Za-z0-9._/@-]+)|(-{1,2}[A-Za-z][\w-]*)(?:=(\S+))?|([^\s]+)/g;
183
+ let u = 0, f = "";
184
+ for (const h of r.matchAll(_)) {
185
+ const d = h[0] ?? "", A = h.index ?? 0;
186
+ f += n(r.slice(u, A)), h[1] ? f += n(d) : h[2] ? f += l(
187
+ "vcb__token_string",
188
+ n(d)
189
+ ) : h[4] ? f += l(
190
+ "vcb__token_variable",
191
+ n(d)
192
+ ) : h[5] ? f += l(
193
+ "vcb__token_string",
194
+ n(d)
195
+ ) : h[6] ? (f += l(
196
+ "vcb__token_directive",
197
+ n(h[6])
198
+ ), h[7] && (f += `${n("=")}${l(
199
+ h[7].startsWith("@") || h[7].includes("/") ? "vcb__token_string" : "vcb__token_variable",
200
+ n(h[7])
201
+ )}`)) : h[8] ? f += l(
202
+ "vcb__token_variable",
203
+ n(d)
204
+ ) : f += n(d), u = A + d.length;
205
+ }
206
+ return f += n(r.slice(u)), f;
207
+ }, e = t.match(
208
+ /^(\s*)(npm|pnpm|yarn|bun)(\s+)(i|install|add|remove|rm|uninstall|create|dlx|exec|x)(\s+)(.+)$/
209
+ );
210
+ if (e) {
211
+ const [
212
+ ,
213
+ r,
214
+ _,
215
+ u,
216
+ f,
217
+ h,
218
+ d
219
+ ] = e;
220
+ return `${n(r)}${l(
221
+ "vcb__token_keyword",
222
+ n(_)
223
+ )}${n(u)}${l(
224
+ "vcb__token_function",
225
+ n(f)
226
+ )}${n(h)}${s(d)}`;
227
+ }
228
+ const i = /(#[^\n]*$)|((["'])(?:\\.|(?!\3).)*\3)|(\$[A-Za-z_][A-Za-z0-9_]*)|(@[A-Za-z0-9._/-]+)|(^\s*[A-Za-z][\w-]*\b)|(\s-[A-Za-z-]+\b|\s--[A-Za-z-]+\b)/g;
229
+ let c = 0, a = "";
230
+ for (const r of t.matchAll(i)) {
231
+ const _ = r[0] ?? "", u = r.index ?? 0;
232
+ if (a += n(t.slice(c, u)), r[1])
233
+ a += l(
234
+ "vcb__token_comment",
235
+ n(_)
236
+ );
237
+ else if (r[2])
238
+ a += l(
239
+ "vcb__token_string",
240
+ n(_)
241
+ );
242
+ else if (r[4])
243
+ a += l(
244
+ "vcb__token_variable",
245
+ n(_)
246
+ );
247
+ else if (r[5])
248
+ a += l(
249
+ "vcb__token_string",
250
+ n(_)
251
+ );
252
+ else if (r[6])
253
+ a += l(
254
+ "vcb__token_keyword",
255
+ n(_.trimStart())
256
+ );
257
+ else if (r[7]) {
258
+ const f = _.match(/^\s*/)?.[0] ?? "";
259
+ a += `${n(f)}${l(
260
+ "vcb__token_directive",
261
+ n(_.trimStart())
262
+ )}`;
263
+ } else
264
+ a += n(_);
265
+ c = u + _.length;
266
+ }
267
+ return a += n(t.slice(c)), a;
268
+ }, j = (t) => {
269
+ const o = new RegExp(
270
+ `(\\/\\*.*?\\*\\/)|((["'])(?:\\\\.|(?!\\3).)*\\3)|(#[0-9A-Fa-f]{3,8})|(@[A-Za-z-]+)|([A-Za-z_-][\\w-]*)(?=\\s*\\()|${et}|\\b[A-Za-z_-][\\w-]*\\b`,
271
+ "g"
272
+ );
273
+ let s = 0, e = "";
274
+ for (const i of t.matchAll(o)) {
275
+ const c = i[0] ?? "", a = i.index ?? 0;
276
+ e += n(t.slice(s, a)), i[1] ? e += l(
277
+ "vcb__token_comment",
278
+ n(c)
279
+ ) : i[2] ? e += l(
280
+ "vcb__token_string",
281
+ n(c)
282
+ ) : /^#[0-9A-Fa-f]{3,8}$/.test(c) ? e += l(
283
+ "vcb__token_number",
284
+ n(c)
285
+ ) : c.startsWith("@") ? e += l(
286
+ "vcb__token_keyword",
287
+ n(c)
288
+ ) : J(t, a + c.length) === "(" ? e += l(
289
+ "vcb__token_function",
290
+ n(c)
291
+ ) : /^-?\d/.test(c) ? e += l(
292
+ "vcb__token_number",
293
+ n(c)
294
+ ) : e += l(
295
+ "vcb__token_variable",
296
+ n(c)
297
+ ), s = a + c.length;
298
+ }
299
+ return e += n(t.slice(s)), e;
300
+ }, _t = (t) => {
301
+ const o = t.match(/^(\s*)(\/\*.*\*\/)(\s*)$/);
302
+ if (o)
303
+ return `${n(o[1])}${l(
304
+ "vcb__token_comment",
305
+ n(o[2])
306
+ )}${n(o[3])}`;
307
+ const s = t.match(/^(\s*)([^{}][^{}]*?)(\s*)(\{|\})\s*$/);
308
+ if (s) {
309
+ const [, i, c, a, r] = s, _ = c.trim(), u = _ === ":root" || _.startsWith(".") || _.startsWith("#") || /^[A-Za-z_-][\w-]*$/.test(_) ? "vcb__token_selector" : "vcb__token_variable";
310
+ return `${n(i)}${l(u, n(_))}${n(
311
+ a
312
+ )}${n(r)}`;
313
+ }
314
+ const e = t.match(
315
+ /^(\s*)(--?[A-Za-z_][\w-]*|[A-Za-z_][\w-]*)(\s*:\s*)(.*)$/
316
+ );
317
+ if (e) {
318
+ const [, i, c, a, r] = e;
319
+ return `${n(i)}${l(
320
+ "vcb__token_property",
321
+ n(c)
322
+ )}${n(a)}${j(r)}`;
323
+ }
324
+ return j(t);
325
+ }, dt = (t, o, s = !0) => {
326
+ if (!s)
327
+ return B(o);
328
+ const e = t.toLowerCase();
329
+ return e.includes("json") ? ot(o) : e.includes("vue") || e.includes("html") ? k(o) : e.includes("js") || e.includes("ts") || e.includes("javascript") || e.includes("typescript") ? H(o) : e.includes("bash") || e.includes("shell") || e === "sh" ? ut(o) : e.includes("css") || e.includes("scss") || e.includes("sass") ? _t(o) : B(o);
330
+ }, ht = (t, o) => {
331
+ const s = (t || "").replace(/\r\n/g, `
332
+ `).split(`
333
+ `);
334
+ let e = !1, i = null, c = null;
335
+ return s.map((a) => {
336
+ const r = a;
337
+ if (!o)
338
+ return B(r);
339
+ const _ = n(r), u = _.trim();
340
+ if (!u)
341
+ return _;
342
+ if (/^&lt;script\b/i.test(u)) {
343
+ i = "script";
344
+ const d = u.includes("&gt;");
345
+ return e = !d, d ? k(r) : z(r);
346
+ }
347
+ if (/^&lt;template\b/i.test(u)) {
348
+ i = "template";
349
+ const d = u.includes("&gt;");
350
+ return e = !d, d ? k(r) : z(r);
351
+ }
352
+ if (/^&lt;style\b/i.test(u)) {
353
+ i = "style";
354
+ const d = u.includes("&gt;");
355
+ return e = !d, d ? k(r) : z(r);
356
+ }
357
+ if (/^&lt;\/script&gt;/i.test(u) || /^&lt;\/template&gt;/i.test(u) || /^&lt;\/style&gt;/i.test(u))
358
+ return i = null, e = !1, k(r);
359
+ const f = u.startsWith("&lt;") && !u.startsWith("&lt;/"), h = u.includes("&gt;");
360
+ if (f) {
361
+ const d = h ? k(r) : z(r);
362
+ return e = !h, d;
363
+ }
364
+ if (e) {
365
+ if (u === "&gt;" || u === "/&gt;")
366
+ return e = !1, c = null, _;
367
+ if (c) {
368
+ const p = c, g = r.indexOf(c.quote);
369
+ if (g === -1)
370
+ return L(
371
+ r,
372
+ p.quote,
373
+ p.directive,
374
+ !1,
375
+ !1
376
+ );
377
+ const Z = r.slice(0, g), v = r.slice(g + 1), $ = n(v);
378
+ return c = null, v.includes(">") && (e = !1), `${L(
379
+ Z,
380
+ p.quote,
381
+ p.directive,
382
+ !1,
383
+ !0
384
+ )}${$}`;
385
+ }
386
+ const d = r.match(
387
+ /^(\s+)([:@#]?[\w-]+(?::[\w-]+)?)(\s*=\s*)(["'])([\s\S]*)$/
388
+ );
389
+ if (d) {
390
+ const [, p, g, Z, v, $] = d, S = E(g), W = $.indexOf(v), N = `${p}${l(
391
+ S ? "vcb__token_directive" : "vcb__token_attribute",
392
+ g
393
+ )}${n(Z)}`;
394
+ if (W === -1)
395
+ return c = {
396
+ quote: v,
397
+ directive: S
398
+ }, `${N}${L(
399
+ $,
400
+ v,
401
+ S,
402
+ !0,
403
+ !1
404
+ )}`;
405
+ const Q = $.slice(0, W), P = $.slice(W + 1);
406
+ return P.includes(">") && (e = !1), `${N}${L(
407
+ Q,
408
+ v,
409
+ S,
410
+ !0,
411
+ !0
412
+ )}${n(P)}`;
413
+ }
414
+ const A = T(_);
415
+ return h && (e = !1), A;
416
+ }
417
+ return i === "script" ? H(r) : k(r);
418
+ }).join(`
419
+ `);
420
+ }, ft = (t, o, s = !0) => {
421
+ const e = t.toLowerCase();
422
+ return e.includes("vue") || e.includes("html") ? ht(o, s) : (o || "").replace(/\r\n/g, `
423
+ `).split(`
424
+ `).map((i) => dt(t, i, s)).join(`
425
+ `);
426
+ }, gt = ["data-theme", "aria-label"], mt = {
427
+ key: 0,
428
+ class: "vcb__header"
429
+ }, bt = {
430
+ key: 0,
431
+ class: "vcb__meta"
432
+ }, pt = {
433
+ key: 0,
434
+ class: "vcb__filename"
435
+ }, vt = { class: "vcb__language" }, kt = { class: "vcb__actions" }, $t = ["disabled"], At = {
436
+ key: 0,
437
+ class: "vcb__line-number"
438
+ }, yt = ["innerHTML"], D = /* @__PURE__ */ R({
439
+ name: "VcbCodeBlock",
440
+ __name: "CodeBlock",
441
+ props: {
442
+ code: { default: "" },
443
+ language: { default: "plaintext" },
444
+ filename: { default: "" },
445
+ showHeader: { type: Boolean, default: !0 },
446
+ showLineNumbers: { type: Boolean, default: !1 },
447
+ copyable: { type: Boolean, default: !0 },
448
+ copyLabel: { default: "Copy" },
449
+ copiedLabel: { default: "Copied" },
450
+ copiedDuration: { default: 1200 },
451
+ languageLabel: { default: "Language" },
452
+ disabled: { type: Boolean, default: !1 },
453
+ wrap: { type: Boolean, default: !1 },
454
+ highlight: { type: Boolean, default: !0 },
455
+ maxHeight: { default: "" },
456
+ ariaLabel: { default: "Code block" },
457
+ theme: { default: "inherit" }
458
+ },
459
+ emits: ["copy"],
460
+ setup(t, { emit: o }) {
461
+ const s = t, e = o, i = F(!1);
462
+ let c = null;
463
+ const a = y(() => s.code.replace(/\r\n/g, `
464
+ `)), r = y(() => a.value ? a.value.split(`
465
+ `) : [""]), _ = y(
466
+ () => ft(
467
+ s.language,
468
+ a.value,
469
+ s.highlight
470
+ ).split(`
471
+ `)
472
+ ), u = y(
473
+ () => s.theme === "inherit" ? void 0 : s.theme
474
+ ), f = y(() => {
475
+ if (s.maxHeight)
476
+ return {
477
+ maxHeight: s.maxHeight
478
+ };
479
+ }), h = async () => {
480
+ if (!s.disabled)
481
+ try {
482
+ typeof navigator < "u" && navigator.clipboard?.writeText && await navigator.clipboard.writeText(a.value);
483
+ } finally {
484
+ e("copy", { text: a.value }), i.value = !0, c && clearTimeout(c), c = setTimeout(() => {
485
+ i.value = !1, c = null;
486
+ }, s.copiedDuration);
487
+ }
488
+ };
489
+ return U(() => {
490
+ c && (clearTimeout(c), c = null);
491
+ }), (d, A) => (m(), b("section", {
492
+ class: q(["vcb", { vcb_disabled: t.disabled }]),
493
+ "data-theme": u.value,
494
+ "aria-label": t.ariaLabel
495
+ }, [
496
+ t.showHeader || d.$slots.actions ? (m(), b("header", mt, [
497
+ t.showHeader ? (m(), b("div", bt, [
498
+ t.filename ? (m(), b("span", pt, w(t.filename), 1)) : x("", !0),
499
+ C("span", vt, w(t.languageLabel) + ": " + w(t.language), 1)
500
+ ])) : x("", !0),
501
+ C("div", kt, [
502
+ G(d.$slots, "actions"),
503
+ t.copyable ? (m(), b("button", {
504
+ key: 0,
505
+ type: "button",
506
+ class: "vcb__copy",
507
+ disabled: t.disabled,
508
+ onClick: h
509
+ }, w(i.value ? t.copiedLabel : t.copyLabel), 9, $t)) : x("", !0)
510
+ ])
511
+ ])) : x("", !0),
512
+ C("pre", {
513
+ class: q(["vcb__pre", { vcb__pre_wrap: t.wrap }]),
514
+ style: K(f.value)
515
+ }, [
516
+ C("code", null, [
517
+ (m(!0), b(X, null, Y(r.value, (p, g) => (m(), b("span", {
518
+ key: g,
519
+ class: "vcb__line"
520
+ }, [
521
+ t.showLineNumbers ? (m(), b("span", At, w(g + 1), 1)) : x("", !0),
522
+ C("span", {
523
+ class: "vcb__line-content",
524
+ innerHTML: _.value[g] ?? ""
525
+ }, null, 8, yt)
526
+ ]))), 128))
527
+ ])
528
+ ], 6)
529
+ ], 10, gt));
530
+ }
531
+ }), xt = [
532
+ "plaintext",
533
+ "text",
534
+ "txt",
535
+ "js",
536
+ "javascript",
537
+ "ts",
538
+ "typescript",
539
+ "vue",
540
+ "html",
541
+ "json",
542
+ "bash",
543
+ "shell",
544
+ "sh",
545
+ "css",
546
+ "scss",
547
+ "sass"
548
+ ], Ct = {
549
+ install(t) {
550
+ t.component("CodeBlock", D), t.component("VueCodeBlock", D);
551
+ }
552
+ };
553
+ export {
554
+ D as CodeBlock,
555
+ xt as SUPPORTED_CODE_BLOCK_LANGUAGES,
556
+ Ct as default,
557
+ n as escapeCodeHtml,
558
+ ft as highlightCodeBlock,
559
+ dt as highlightCodeLine
560
+ };
@@ -0,0 +1,11 @@
1
+ (function(f,s){typeof exports=="object"&&typeof module<"u"?s(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],s):(f=typeof globalThis<"u"?globalThis:f||self,s(f.VueCodeBlock={},f.Vue))})(this,(function(f,s){"use strict";var Z=document.createElement("style");Z.textContent=`.vcb{display:grid;gap:var(--vcb-gap, 0);border:1px solid var(--vcb-border-color, #cbd5e1);border-radius:var(--vcb-border-radius, .625rem);background:var(--vcb-background-color, #f8fbff);color:var(--vcb-text-color, #0f172a);font-family:var(--vcb-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-size:var(--vcb-font-size, .8125rem);line-height:var(--vcb-line-height, 1.5)}.vcb__header{display:flex;align-items:center;justify-content:space-between;gap:var(--vcb-header-gap, .75rem);padding:var(--vcb-header-padding, .55rem .75rem);border-bottom:1px solid var(--vcb-header-border-color, rgba(148, 163, 184, .28))}.vcb__meta{display:inline-flex;align-items:center;gap:var(--vcb-meta-gap, .55rem);color:var(--vcb-meta-color, #475569);font-size:var(--vcb-meta-font-size, .75rem)}.vcb__filename{color:var(--vcb-filename-color, #0f172a);font-weight:var(--vcb-filename-font-weight, 600)}.vcb__actions{display:inline-flex;align-items:center;gap:var(--vcb-actions-gap, .45rem)}.vcb__copy{border:1px solid var(--vcb-action-border-color, rgba(148, 163, 184, .38));border-radius:var(--vcb-action-border-radius, .375rem);background:var(--vcb-action-background-color, #ffffff);color:var(--vcb-action-text-color, #0f172a);padding:var(--vcb-action-padding, .2rem .55rem);font-size:var(--vcb-action-font-size, .75rem)}.vcb__pre{margin:0;padding:var(--vcb-padding, .75rem .9rem);overflow:auto;white-space:pre}.vcb__pre_wrap{white-space:pre-wrap;word-break:break-word}.vcb__line{display:flex;align-items:baseline;gap:var(--vcb-line-gap, .7rem)}.vcb__line-number{text-align:right;color:var(--vcb-line-number-color, #64748b);min-width:var(--vcb-line-number-min-width, 2.1rem);-webkit-user-select:none;user-select:none}.vcb__line-content{white-space:inherit}.vcb__token_keyword{color:var(--vcb-token-keyword-color, #8b2fc9)}.vcb__token_string{color:var(--vcb-token-string-color, #0f7b45)}.vcb__token_number{color:var(--vcb-token-number-color, #c2410c)}.vcb__token_comment{color:var(--vcb-token-comment-color, #64748b)}.vcb__token_variable{color:var(--vcb-token-variable-color, #0f4c81)}.vcb__token_identifier{color:var(--vcb-token-identifier-color, #0369a1)}.vcb__token_function{color:var(--vcb-token-function-color, #1d4ed8)}.vcb__token_property{color:var(--vcb-token-property-color, #6d28d9)}.vcb__token_operator{color:var(--vcb-token-operator-color, #be185d)}.vcb__token_tag{color:var(--vcb-token-tag-color, #0f4c81)}.vcb__token_selector{color:var(--vcb-token-selector-color, #0f4c81)}.vcb__token_component{color:var(--vcb-token-component-color, #0369a1)}.vcb__token_attribute{color:var(--vcb-token-attribute-color, #7c3aed)}.vcb__token_directive{color:var(--vcb-token-directive-color, #a21caf)}.vcb[data-theme=dark],:root[data-theme=dark] .vcb:not([data-theme=light]),[data-theme=dark] .vcb:not([data-theme=light]){--vcb-background-color: var( --vcb-dark-background-color, #0a1425 );--vcb-text-color: var(--vcb-dark-text-color, #e2e8f0);--vcb-border-color: var( --vcb-dark-border-color, rgba(148, 163, 184, .35) );--vcb-header-border-color: var( --vcb-dark-header-border-color, rgba(148, 163, 184, .35) );--vcb-meta-color: var(--vcb-dark-meta-color, #93a4bf);--vcb-filename-color: var( --vcb-dark-filename-color, #e2e8f0 );--vcb-action-border-color: var( --vcb-dark-action-border-color, rgba(148, 163, 184, .45) );--vcb-action-background-color: var( --vcb-dark-action-background-color, rgba(15, 23, 42, .7) );--vcb-action-text-color: var( --vcb-dark-action-text-color, #e2e8f0 );--vcb-line-number-color: var( --vcb-dark-line-number-color, #64748b );--vcb-token-keyword-color: var( --vcb-dark-token-keyword-color, #c084fc );--vcb-token-string-color: var( --vcb-dark-token-string-color, #86efac );--vcb-token-number-color: var( --vcb-dark-token-number-color, #fca5a5 );--vcb-token-comment-color: var( --vcb-dark-token-comment-color, #94a3b8 );--vcb-token-variable-color: var( --vcb-dark-token-variable-color, #bfdbfe );--vcb-token-tag-color: var( --vcb-dark-token-tag-color, #93c5fd );--vcb-token-selector-color: var( --vcb-dark-token-selector-color, #93c5fd );--vcb-token-component-color: var( --vcb-dark-token-component-color, #67e8f9 );--vcb-token-attribute-color: var( --vcb-dark-token-attribute-color, #c4b5fd );--vcb-token-directive-color: var( --vcb-dark-token-directive-color, #f0abfc );--vcb-token-identifier-color: var( --vcb-dark-token-identifier-color, #7dd3fc );--vcb-token-function-color: var( --vcb-dark-token-function-color, #93c5fd );--vcb-token-property-color: var( --vcb-dark-token-property-color, #a5b4fc );--vcb-token-operator-color: var( --vcb-dark-token-operator-color, #f0abfc )}.vcb_disabled{opacity:var(--vcb-disabled-opacity, .6)}
2
+ /*$vite$:1*/`,document.head.appendChild(Z);const o=e=>e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;"),T=e=>e.replaceAll("&quot;",'"').replaceAll("&#39;","'").replaceAll("&lt;","<").replaceAll("&gt;",">").replaceAll("&amp;","&"),S=e=>o(e),i=(e,r)=>`<span class="vcb__token ${e}">${r}</span>`,I="\\b\\d(?:[\\d_]*\\d)?(?:\\.\\d(?:[\\d_]*\\d)?)?\\b",R="\\b-?\\d(?:[\\d_]*\\d)?(?:\\.\\d(?:[\\d_]*\\d)?)?(?:px|rem|em|%|vh|vw|fr|s|ms|deg)?\\b",B=e=>e.startsWith(":")||e.startsWith("@")||e.startsWith("#")||e.startsWith("v-"),W=e=>/^[A-Z]/.test(e),U=e=>{let r=0,n=i("vcb__token_string",o("`"));for(;r<e.length-1;){const t=e.indexOf("${",r),a=t===-1?e.length-1:t,c=e.slice(r,a);if(c&&(n+=i("vcb__token_string",o(c))),t===-1)break;n+=o("${");let d=t+2,l=1;for(;d<e.length&&l>0;){const b=e[d];b==="{"?l+=1:b==="}"&&(l-=1),d+=1}const _=e.slice(t+2,d-1);n+=w(_),n+=o("}"),r=d}return n+=i("vcb__token_string",o("`")),n},w=e=>z(e),F=e=>{if(e.length<2)return i("vcb__token_string",e);const r=e[0],n=T(e.slice(1,-1)),t=w(n),a=r==='"'?"&quot;":"&#39;";return`${a}${t}${a}`},A=(e,r,n,t,a)=>{const c=n?w(T(e)):i("vcb__token_string",o(e)),d=r==='"'?"&quot;":"&#39;";return`${t?d:""}${c}${a?d:""}`},V=e=>e.replaceAll(/{{\s*([\s\S]*?)\s*}}/g,(r,n)=>{const t=w(n);return`${o("{{ ")}${t}${o(" }}")}`}),E=e=>e.replaceAll(/(\s+)([:@#]?[\w-]+(?::[\w-]+)?)(\s*=\s*)?("[^"]*"|'[^']*')?/g,(r,n,t,a="",c="")=>`${o(n)}${i(B(t)?"vcb__token_directive":"vcb__token_attribute",o(t))}${o(a)}${c?B(t)?F(c):i("vcb__token_string",o(c)):""}`),x=e=>{const r=e.match(/^(\s*)<([/\w-]+)([\s\S]*)$/);if(!r)return o(e);const[,n,t,a]=r,c=t.startsWith("/"),d=c?t.slice(1):t,l=W(d)?"vcb__token_component":"vcb__token_tag";return`${o(n)}${o(c?"</":"<")}${i(l,o(d))}${E(String(a))}`},G=e=>{if(e.startsWith("<!--")&&e.endsWith("-->"))return i("vcb__token_comment",o(e));const r=e.startsWith("</"),n=e.endsWith("/>"),a=e.slice(r?2:1,e.length-(n?2:1)).match(/^([\w-]+)([\s\S]*)$/);if(!a)return o(e);const[,c,d]=a,l=W(c)?"vcb__token_component":"vcb__token_tag";return`${o(r?"</":"<")}${i(l,o(c))}${E(d)}${o(n?"/>":">")}`},J=e=>o(e).replaceAll(/"(?:[^"\\]|\\.)*"(?=\s*:)?/g,n=>i("vcb__token_string",n)).replaceAll(/\b(true|false|null)\b/g,n=>i("vcb__token_keyword",n)).replaceAll(/\b-?\d(?:[\d_]*\d)?(?:\.\d(?:[\d_]*\d)?)?(?:[eE][+-]?\d(?:[\d_]*\d)?)?\b/g,n=>i("vcb__token_number",n)),Q=(e,r)=>{for(let n=r-1;n>=0;n-=1){const t=e[n];if(!/\s/.test(t))return t}return""},O=(e,r)=>{for(let n=r;n<e.length;n+=1){const t=e[n];if(!/\s/.test(t))return t}return""},K=(e,r)=>{let n=r;for(;n>0&&/\s/.test(e[n-1]);)n-=1;return e.slice(0,n).match(/([A-Za-z_$][\w$]*)\s*$/)?.[1]??""},X=(e,r,n)=>{const t=Q(e,n),a=O(e,n+r.length),c=K(e,n),d=e.slice(0,n);return t==="."||/\?\.\s*$/.test(d)||a===":"&&["{",",",""].includes(t)?"vcb__token_property":a==="("?"vcb__token_function":/^[A-Z]/.test(r)||["import","from","new","class","extends"].includes(c)?"vcb__token_identifier":"vcb__token_variable"},z=e=>{const r=new RegExp(`(//.*$)|((["'\`])(?:\\\\.|(?!\\3).)*\\3)|\\b(const|let|var|function|return|if|else|for|while|class|new|import|export|from|await|async|try|catch|throw|true|false|null|undefined)\\b|(=>|\\?\\?|\\?|:|\\.|[{}()[\\],])|${I}|\\b[A-Za-z_$][\\w$]*\\b`,"g");let n=0,t="";for(const a of e.matchAll(r)){const c=a[0]??"",d=a.index??0;t+=o(e.slice(n,d)),a[1]?t+=i("vcb__token_comment",o(c)):a[2]?t+=c.startsWith("`")&&c.includes("${")?U(c):i("vcb__token_string",o(c)):a[4]?t+=i("vcb__token_keyword",o(c)):a[5]?t+=i("vcb__token_operator",o(c)):/^\d/.test(c)?t+=i("vcb__token_number",o(c)):t+=i(X(e,c,d),o(c)),n=d+c.length}return t+=o(e.slice(n)),t},Y=(e,r)=>{let n=null;for(let t=r+1;t<e.length;t+=1){const a=e[t];if(n){a===n&&e[t-1]!=="\\"&&(n=null);continue}if(a==='"'||a==="'"){n=a;continue}if(a===">")return t}return-1},g=e=>{if(!e.trim())return o(e);let r=0,n="";for(;r<e.length;){const t=e.indexOf("<",r);if(t===-1){n+=V(o(e.slice(r)));break}n+=V(o(e.slice(r,t)));const a=Y(e,t);if(a===-1){n+=o(e.slice(t));break}n+=G(e.slice(t,a+1)),r=a+1}return n},ee=e=>{const r=e.match(/^(\s*)(npm)(\s+)(run)(\s+)([^\s].*)$/);if(r){const[,l,_,b,m,u,v]=r;return`${o(l)}${i("vcb__token_keyword",o(_))}${o(b)}${i("vcb__token_function",o(m))}${o(u)}${i("vcb__token_variable",o(v))}`}const n=l=>{const _=/(\s+)|((["'])(?:\\.|(?!\3).)*\3)|(\$[A-Za-z_][A-Za-z0-9_]*)|(@[A-Za-z0-9._/-]+|[A-Za-z0-9._-]+\/[A-Za-z0-9._/@-]+)|(-{1,2}[A-Za-z][\w-]*)(?:=(\S+))?|([^\s]+)/g;let b=0,m="";for(const u of l.matchAll(_)){const v=u[0]??"",y=u.index??0;m+=o(l.slice(b,y)),u[1]?m+=o(v):u[2]?m+=i("vcb__token_string",o(v)):u[4]?m+=i("vcb__token_variable",o(v)):u[5]?m+=i("vcb__token_string",o(v)):u[6]?(m+=i("vcb__token_directive",o(u[6])),u[7]&&(m+=`${o("=")}${i(u[7].startsWith("@")||u[7].includes("/")?"vcb__token_string":"vcb__token_variable",o(u[7]))}`)):u[8]?m+=i("vcb__token_variable",o(v)):m+=o(v),b=y+v.length}return m+=o(l.slice(b)),m},t=e.match(/^(\s*)(npm|pnpm|yarn|bun)(\s+)(i|install|add|remove|rm|uninstall|create|dlx|exec|x)(\s+)(.+)$/);if(t){const[,l,_,b,m,u,v]=t;return`${o(l)}${i("vcb__token_keyword",o(_))}${o(b)}${i("vcb__token_function",o(m))}${o(u)}${n(v)}`}const a=/(#[^\n]*$)|((["'])(?:\\.|(?!\3).)*\3)|(\$[A-Za-z_][A-Za-z0-9_]*)|(@[A-Za-z0-9._/-]+)|(^\s*[A-Za-z][\w-]*\b)|(\s-[A-Za-z-]+\b|\s--[A-Za-z-]+\b)/g;let c=0,d="";for(const l of e.matchAll(a)){const _=l[0]??"",b=l.index??0;if(d+=o(e.slice(c,b)),l[1])d+=i("vcb__token_comment",o(_));else if(l[2])d+=i("vcb__token_string",o(_));else if(l[4])d+=i("vcb__token_variable",o(_));else if(l[5])d+=i("vcb__token_string",o(_));else if(l[6])d+=i("vcb__token_keyword",o(_.trimStart()));else if(l[7]){const m=_.match(/^\s*/)?.[0]??"";d+=`${o(m)}${i("vcb__token_directive",o(_.trimStart()))}`}else d+=o(_);c=b+_.length}return d+=o(e.slice(c)),d},P=e=>{const r=new RegExp(`(\\/\\*.*?\\*\\/)|((["'])(?:\\\\.|(?!\\3).)*\\3)|(#[0-9A-Fa-f]{3,8})|(@[A-Za-z-]+)|([A-Za-z_-][\\w-]*)(?=\\s*\\()|${R}|\\b[A-Za-z_-][\\w-]*\\b`,"g");let n=0,t="";for(const a of e.matchAll(r)){const c=a[0]??"",d=a.index??0;t+=o(e.slice(n,d)),a[1]?t+=i("vcb__token_comment",o(c)):a[2]?t+=i("vcb__token_string",o(c)):/^#[0-9A-Fa-f]{3,8}$/.test(c)?t+=i("vcb__token_number",o(c)):c.startsWith("@")?t+=i("vcb__token_keyword",o(c)):O(e,d+c.length)==="("?t+=i("vcb__token_function",o(c)):/^-?\d/.test(c)?t+=i("vcb__token_number",o(c)):t+=i("vcb__token_variable",o(c)),n=d+c.length}return t+=o(e.slice(n)),t},te=e=>{const r=e.match(/^(\s*)(\/\*.*\*\/)(\s*)$/);if(r)return`${o(r[1])}${i("vcb__token_comment",o(r[2]))}${o(r[3])}`;const n=e.match(/^(\s*)([^{}][^{}]*?)(\s*)(\{|\})\s*$/);if(n){const[,a,c,d,l]=n,_=c.trim(),b=_===":root"||_.startsWith(".")||_.startsWith("#")||/^[A-Za-z_-][\w-]*$/.test(_)?"vcb__token_selector":"vcb__token_variable";return`${o(a)}${i(b,o(_))}${o(d)}${o(l)}`}const t=e.match(/^(\s*)(--?[A-Za-z_][\w-]*|[A-Za-z_][\w-]*)(\s*:\s*)(.*)$/);if(t){const[,a,c,d,l]=t;return`${o(a)}${i("vcb__token_property",o(c))}${o(d)}${P(l)}`}return P(e)},D=(e,r,n=!0)=>{if(!n)return S(r);const t=e.toLowerCase();return t.includes("json")?J(r):t.includes("vue")||t.includes("html")?g(r):t.includes("js")||t.includes("ts")||t.includes("javascript")||t.includes("typescript")?z(r):t.includes("bash")||t.includes("shell")||t==="sh"?ee(r):t.includes("css")||t.includes("scss")||t.includes("sass")?te(r):S(r)},oe=(e,r)=>{const n=(e||"").replace(/\r\n/g,`
3
+ `).split(`
4
+ `);let t=!1,a=null,c=null;return n.map(d=>{const l=d;if(!r)return S(l);const _=o(l),b=_.trim();if(!b)return _;if(/^&lt;script\b/i.test(b)){a="script";const v=b.includes("&gt;");return t=!v,v?g(l):x(l)}if(/^&lt;template\b/i.test(b)){a="template";const v=b.includes("&gt;");return t=!v,v?g(l):x(l)}if(/^&lt;style\b/i.test(b)){a="style";const v=b.includes("&gt;");return t=!v,v?g(l):x(l)}if(/^&lt;\/script&gt;/i.test(b)||/^&lt;\/template&gt;/i.test(b)||/^&lt;\/style&gt;/i.test(b))return a=null,t=!1,g(l);const m=b.startsWith("&lt;")&&!b.startsWith("&lt;/"),u=b.includes("&gt;");if(m){const v=u?g(l):x(l);return t=!u,v}if(t){if(b==="&gt;"||b==="/&gt;")return t=!1,c=null,_;if(c){const k=c,h=l.indexOf(c.quote);if(h===-1)return A(l,k.quote,k.directive,!1,!1);const M=l.slice(0,h),p=l.slice(h+1),$=o(p);return c=null,p.includes(">")&&(t=!1),`${A(M,k.quote,k.directive,!1,!0)}${$}`}const v=l.match(/^(\s+)([:@#]?[\w-]+(?::[\w-]+)?)(\s*=\s*)(["'])([\s\S]*)$/);if(v){const[,k,h,M,p,$]=v,C=B(h),N=$.indexOf(p),j=`${k}${i(C?"vcb__token_directive":"vcb__token_attribute",h)}${o(M)}`;if(N===-1)return c={quote:p,directive:C},`${j}${A($,p,C,!0,!1)}`;const ue=$.slice(0,N),q=$.slice(N+1);return q.includes(">")&&(t=!1),`${j}${A(ue,p,C,!0,!0)}${o(q)}`}const y=E(_);return u&&(t=!1),y}return a==="script"?z(l):g(l)}).join(`
5
+ `)},H=(e,r,n=!0)=>{const t=e.toLowerCase();return t.includes("vue")||t.includes("html")?oe(r,n):(r||"").replace(/\r\n/g,`
6
+ `).split(`
7
+ `).map(a=>D(e,a,n)).join(`
8
+ `)},ne=["data-theme","aria-label"],ce={key:0,class:"vcb__header"},re={key:0,class:"vcb__meta"},ae={key:0,class:"vcb__filename"},se={class:"vcb__language"},le={class:"vcb__actions"},ie=["disabled"],de={key:0,class:"vcb__line-number"},be=["innerHTML"],L=s.defineComponent({name:"VcbCodeBlock",__name:"CodeBlock",props:{code:{default:""},language:{default:"plaintext"},filename:{default:""},showHeader:{type:Boolean,default:!0},showLineNumbers:{type:Boolean,default:!1},copyable:{type:Boolean,default:!0},copyLabel:{default:"Copy"},copiedLabel:{default:"Copied"},copiedDuration:{default:1200},languageLabel:{default:"Language"},disabled:{type:Boolean,default:!1},wrap:{type:Boolean,default:!1},highlight:{type:Boolean,default:!0},maxHeight:{default:""},ariaLabel:{default:"Code block"},theme:{default:"inherit"}},emits:["copy"],setup(e,{emit:r}){const n=e,t=r,a=s.ref(!1);let c=null;const d=s.computed(()=>n.code.replace(/\r\n/g,`
9
+ `)),l=s.computed(()=>d.value?d.value.split(`
10
+ `):[""]),_=s.computed(()=>H(n.language,d.value,n.highlight).split(`
11
+ `)),b=s.computed(()=>n.theme==="inherit"?void 0:n.theme),m=s.computed(()=>{if(n.maxHeight)return{maxHeight:n.maxHeight}}),u=async()=>{if(!n.disabled)try{typeof navigator<"u"&&navigator.clipboard?.writeText&&await navigator.clipboard.writeText(d.value)}finally{t("copy",{text:d.value}),a.value=!0,c&&clearTimeout(c),c=setTimeout(()=>{a.value=!1,c=null},n.copiedDuration)}};return s.onBeforeUnmount(()=>{c&&(clearTimeout(c),c=null)}),(v,y)=>(s.openBlock(),s.createElementBlock("section",{class:s.normalizeClass(["vcb",{vcb_disabled:e.disabled}]),"data-theme":b.value,"aria-label":e.ariaLabel},[e.showHeader||v.$slots.actions?(s.openBlock(),s.createElementBlock("header",ce,[e.showHeader?(s.openBlock(),s.createElementBlock("div",re,[e.filename?(s.openBlock(),s.createElementBlock("span",ae,s.toDisplayString(e.filename),1)):s.createCommentVNode("",!0),s.createElementVNode("span",se,s.toDisplayString(e.languageLabel)+": "+s.toDisplayString(e.language),1)])):s.createCommentVNode("",!0),s.createElementVNode("div",le,[s.renderSlot(v.$slots,"actions"),e.copyable?(s.openBlock(),s.createElementBlock("button",{key:0,type:"button",class:"vcb__copy",disabled:e.disabled,onClick:u},s.toDisplayString(a.value?e.copiedLabel:e.copyLabel),9,ie)):s.createCommentVNode("",!0)])])):s.createCommentVNode("",!0),s.createElementVNode("pre",{class:s.normalizeClass(["vcb__pre",{vcb__pre_wrap:e.wrap}]),style:s.normalizeStyle(m.value)},[s.createElementVNode("code",null,[(s.openBlock(!0),s.createElementBlock(s.Fragment,null,s.renderList(l.value,(k,h)=>(s.openBlock(),s.createElementBlock("span",{key:h,class:"vcb__line"},[e.showLineNumbers?(s.openBlock(),s.createElementBlock("span",de,s.toDisplayString(h+1),1)):s.createCommentVNode("",!0),s.createElementVNode("span",{class:"vcb__line-content",innerHTML:_.value[h]??""},null,8,be)]))),128))])],6)],10,ne))}}),_e=["plaintext","text","txt","js","javascript","ts","typescript","vue","html","json","bash","shell","sh","css","scss","sass"],ve={install(e){e.component("CodeBlock",L),e.component("VueCodeBlock",L)}};f.CodeBlock=L,f.SUPPORTED_CODE_BLOCK_LANGUAGES=_e,f.default=ve,f.escapeCodeHtml=o,f.highlightCodeBlock=H,f.highlightCodeLine=D,Object.defineProperties(f,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
@@ -0,0 +1,3 @@
1
+ export declare const escapeCodeHtml: (value: string) => string;
2
+ export declare const highlightCodeLine: (language: string, line: string, highlight?: boolean) => string;
3
+ export declare const highlightCodeBlock: (language: string, code: string, highlight?: boolean) => string;
@@ -0,0 +1,25 @@
1
+ export declare const SUPPORTED_CODE_BLOCK_LANGUAGES: readonly ["plaintext", "text", "txt", "js", "javascript", "ts", "typescript", "vue", "html", "json", "bash", "shell", "sh", "css", "scss", "sass"];
2
+ export type CodeBlockTheme = "inherit" | "light" | "dark";
3
+ export type SupportedCodeBlockLanguage = (typeof SUPPORTED_CODE_BLOCK_LANGUAGES)[number];
4
+ export type CodeBlockLanguage = SupportedCodeBlockLanguage | (string & {});
5
+ export interface CodeBlockCopyPayload {
6
+ text: string;
7
+ }
8
+ export interface CodeBlockProps {
9
+ code?: string;
10
+ language?: CodeBlockLanguage;
11
+ filename?: string;
12
+ showHeader?: boolean;
13
+ showLineNumbers?: boolean;
14
+ copyable?: boolean;
15
+ copyLabel?: string;
16
+ copiedLabel?: string;
17
+ copiedDuration?: number;
18
+ languageLabel?: string;
19
+ disabled?: boolean;
20
+ wrap?: boolean;
21
+ highlight?: boolean;
22
+ maxHeight?: string;
23
+ ariaLabel?: string;
24
+ theme?: CodeBlockTheme;
25
+ }
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@codemonster-ru/vue-codeblock",
3
+ "version": "1.0.0",
4
+ "description": "Standalone Vue code block component with syntax highlighting and light/dark theming.",
5
+ "license": "MIT",
6
+ "author": "Kirill Kolesnikov",
7
+ "keywords": [
8
+ "vue",
9
+ "vue3",
10
+ "code-block",
11
+ "syntax-highlighting",
12
+ "code",
13
+ "documentation"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/codemonster-ru/vue-codeblock.git"
18
+ },
19
+ "homepage": "https://github.com/codemonster-ru/vue-codeblock#readme",
20
+ "bugs": {
21
+ "url": "https://github.com/codemonster-ru/vue-codeblock/issues"
22
+ },
23
+ "type": "module",
24
+ "main": "./dist/index.umd.cjs",
25
+ "module": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "style": "./dist/index.css",
28
+ "sideEffects": [
29
+ "**/*.css"
30
+ ],
31
+ "exports": {
32
+ ".": {
33
+ "import": {
34
+ "types": "./dist/index.d.ts",
35
+ "default": "./dist/index.js"
36
+ },
37
+ "require": {
38
+ "types": "./dist/index.d.ts",
39
+ "default": "./dist/index.umd.cjs"
40
+ }
41
+ },
42
+ "./style.css": "./dist/index.css"
43
+ },
44
+ "files": [
45
+ "dist"
46
+ ],
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "scripts": {
51
+ "dev": "vite",
52
+ "build": "vite build",
53
+ "format": "prettier --write .",
54
+ "typecheck": "vue-tsc --noEmit",
55
+ "test": "vitest run"
56
+ },
57
+ "peerDependencies": {
58
+ "vue": "^3.4.0"
59
+ },
60
+ "devDependencies": {
61
+ "@types/node": "^24.3.1",
62
+ "@vitejs/plugin-vue": "^6.0.1",
63
+ "@vue/test-utils": "^2.4.6",
64
+ "jsdom": "^26.1.0",
65
+ "prettier": "^3.6.2",
66
+ "sass": "^1.77.8",
67
+ "typescript": "^5.5.3",
68
+ "vite": "^7.1.3",
69
+ "vite-plugin-dts": "^4.5.4",
70
+ "vitest": "^3.2.4",
71
+ "vue": "^3.5.21",
72
+ "vue-tsc": "^3.0.7"
73
+ }
74
+ }