npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cocoar/vue-script-editor

v2.5.1

Published

Monaco-based script editor component for Vue 3 with Cocoar Design System styling

Readme

@cocoar/vue-script-editor

Monaco-based script editor for Vue 3 with Cocoar Design System theming and locked-line protection for templates where signatures stay fixed and users fill in bodies.

Initial scope: TypeScript and JavaScript only, with support for user-supplied type definitions (extraLibs).

Install

pnpm add @cocoar/vue-script-editor monaco-editor

monaco-editor is a peer dependency — consumers install and configure it themselves (this keeps the library bundle small and avoids double-bundling Monaco's ~5 MB of language services).

Worker setup

Monaco needs its language services to run in Web Workers. You register them by assigning a getter to self.MonacoEnvironment before any editor mounts. Pick the pattern that matches your app's shape.

SPA (client-only, Vite)

The common case — a single-page app that runs only in the browser. Register workers once at application entry:

// src/main.ts
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import TsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';

self.MonacoEnvironment = {
  getWorker(_workerId, label) {
    if (label === 'typescript' || label === 'javascript') return new TsWorker();
    if (label === 'json') return new JsonWorker();
    return new EditorWorker();
  },
};

Drop the JsonWorker entries if you don't use JSON mode.

SSR / static-generation (VitePress, Nuxt, Astro)

Monaco touches window, document, and the DOM — it cannot run during server-side rendering. Defer both the worker registration AND the editor import to onMounted, and guard with <ClientOnly> in your template:

<template>
  <ClientOnly>
    <component :is="Editor" v-if="Editor" v-model="code" />
  </ClientOnly>
</template>

<script setup lang="ts">
import { onMounted, ref, shallowRef, type Component } from 'vue';

const Editor = shallowRef<Component | null>(null);
const code = ref('// ...');

onMounted(async () => {
  const [mod, editorWorkerMod, tsWorkerMod, jsonWorkerMod] = await Promise.all([
    import('@cocoar/vue-script-editor'),
    import('monaco-editor/esm/vs/editor/editor.worker?worker'),
    import('monaco-editor/esm/vs/language/typescript/ts.worker?worker'),
    import('monaco-editor/esm/vs/language/json/json.worker?worker'),
  ]);
  self.MonacoEnvironment = {
    getWorker(_workerId, label) {
      if (label === 'typescript' || label === 'javascript') return new tsWorkerMod.default();
      if (label === 'json') return new jsonWorkerMod.default();
      return new editorWorkerMod.default();
    },
  };
  Editor.value = mod.CoarScriptEditor;
});
</script>

<ClientOnly> is registered globally by VitePress and Nuxt. In a plain Vite SPA you don't need it — the SPA pattern above is simpler. In Astro use client:only="vue" on the component.

Other bundlers

monaco-editor's official docs cover Webpack, esbuild, Rollup, and plain CDN setups. The same self.MonacoEnvironment.getWorker contract applies — only the worker import syntax changes.

Basic usage

<script setup lang="ts">
import { ref } from 'vue';
import { CoarScriptEditor } from '@cocoar/vue-script-editor';

const code = ref(`function greet(name: string) {\n  return \`Hello, \${name}\`;\n}\n`);
</script>

<template>
  <CoarScriptEditor v-model="code" language="typescript" />
</template>

Constrained mode — // @locked lines

Any line of the source that contains // @locked is protected: users cannot edit, merge, or delete it. Everything else is freely editable — including the file top, so TypeScript's Auto-Import quickfix works as in any normal .ts file.

<script setup lang="ts">
import { ref } from 'vue';
import { CoarScriptEditor, type CoarScriptEditorRejectEvent } from '@cocoar/vue-script-editor';

const code = ref(`function describeOrder(order: Order): string { // @locked
  return \`Order \${order.id}\`;
} // @locked
`);

function onReject(event: CoarScriptEditorRejectEvent) {
  // Reject reason is 'edit-overlaps-locked-line'. Surface a toast, shake, etc.
  console.log('Edit rejected on line', event.range?.startLineNumber);
}
</script>

<template>
  <CoarScriptEditor v-model="code" language="typescript" @reject="onReject" />
</template>

The v-model string includes the // @locked markers, so it round-trips through persistence unchanged — save and reload the exact same value.

Authoring mode

Template authors need to edit the protected parts too. Pass :authoring="true" to suspend enforcement — locked lines become editable, markers render at full size with a warm accent colour, and the author can add or remove markers. Toggle back to false and enforcement resumes with whatever markers are currently in the text.

<CoarScriptEditor v-model="code" :authoring="isAuthor" />

Custom type definitions (extraLibs)

Inject .d.ts contents so user code can reference your domain types with full IntelliSense:

import type { CoarScriptEditorExtraLib } from '@cocoar/vue-script-editor';

const extraLibs: CoarScriptEditorExtraLib[] = [
  {
    content: `declare interface AppContext { user: { id: string; name: string } };`,
    // IMPORTANT: must start with `file:///` — Monaco's TypeScript service keys on path
    // and silently ignores non-file URIs. A dev-mode console.warn flags this in dev.
    filePath: 'file:///types/app-context.d.ts',
  },
];

Pure helpers (no editor mount required)

For server-side validation, submit-gating, or tests:

import {
  hasLockedMarkers,
  getEditableSegments,
  getSlots,
  getSlot,
  isEverySegmentNonEmpty,
  validateSource,
  countLockedLines,
  SLOT_MARKER_PATTERN,
} from '@cocoar/vue-script-editor';

if (isEverySegmentNonEmpty(code)) submit(code);

const v = validateSource(code);
// v.ok, v.lockedLineCount, v.segmentCount, v.warnings: string[]

Named slots

Mark an editable region with @slot:NAME on a // @locked line to name it. getSlots(source) returns a { slotName: bodyContent } dictionary; getSlot(source, name) returns a single body (or undefined if the slot is not declared). The slot marker sits on a locked line so the user cannot delete it.

const template = `function fn1(x) { // @locked @slot:fn1
  return x + 1;
} // @locked

function fn2(x) { // @locked @slot:fn2
} // @locked`;

const slots = getSlots(template);
// { fn1: '  return x + 1;', fn2: '' }

Empty string = slot exists but body is whitespace-only. Server-side parsers (e.g. a C# Jint host) can mirror the same regex via SLOT_MARKER_PATTERN. See the full docs for the C# port.

See the full docs page for the deeper helpers (scanLockedLines, computeProtectedRanges, editIsProtected, snapOffsetAwayFromLocked).

Props

| Prop | Type | Default | Description | | -------------- | ----------------------------------------- | -------------- | -------------------------------------------------------------------------- | | modelValue | string | '' | Editor content (use with v-model). // @locked lines protected. | | authoring | boolean | false | Authoring mode — suspends enforcement for template authors. | | language | 'typescript' \| 'javascript' \| 'json' | 'typescript' | Language mode. | | readonly | boolean | false | Viewer mode — selection / copy still work. | | disabled | boolean | false | Non-interactive form state. Picked up from CoarFormField. | | error | boolean | false | Error state — red border. Auto-picked up from CoarFormField. | | placeholder | string | '' | Placeholder shown when empty and not focused. | | required | boolean | false | Sets aria-required="true". | | autofocus | boolean | false | Focus the editor after mount. | | id | string | '' | HTML id (auto-generated if omitted; CoarFormField.id wins). | | name | string | '' | Informational data-name (the editor is not a native form control). | | height | string \| number | undefined | CSS string ("160px", "40vh") or pixels as number. | | variant | 'editor' \| 'inline' | 'editor' | UI preset: full chrome vs compact form-field look. | | lineNumbers | boolean | undefined | Explicit toggle for the line-number gutter. Overrides the variant default. | | scriptMode | boolean | false | Suppress TS/JS diagnostics for script-body code. Global side-effect. | | preamble | string | '' | Hidden + locked per-editor type context (not in modelValue). | | minimap | boolean | false | Show the Monaco minimap gutter. | | theme | 'auto' \| 'light' \| 'dark' | 'auto' | auto follows .dark-mode class and prefers-color-scheme. | | extraLibs | CoarScriptEditorExtraLib[] | [] | TypeScript declarations for IntelliSense. |

Events

| Event | Payload | Description | | ------------------- | ------------------------------ | --------------------------------------------------------------------------------- | | update:modelValue | string | Full editor text. Markers stay; preamble is stripped before emit. | | reject | CoarScriptEditorRejectEvent | Emitted when an edit was rolled back by the constrained guards. | | focused | void | Editor widget gained focus (including suggestion popups). | | blurred | void | Editor widget lost focus — use for form-touched state. |

interface CoarScriptEditorRejectEvent {
  reason: 'edit-overlaps-locked-line';
  range?: { startLineNumber: number; endLineNumber: number };
}

Exposed methods

The component exposes focus() as a convenience, plus getEditor() and getModel() for access to the raw Monaco instance when you need APIs beyond the declarative props (markers, custom commands, folding, etc.):

<script setup lang="ts">
import { ref } from 'vue';
import { CoarScriptEditor } from '@cocoar/vue-script-editor';

const editorRef = ref<InstanceType<typeof CoarScriptEditor> | null>(null);

function focusEditor() {
  editorRef.value?.focus();
}
</script>

<template>
  <CoarScriptEditor ref="editorRef" v-model="code" />
</template>

Forms

CoarScriptEditor integrates with CoarFormField the same way CoarTextInput does — label, error state, aria-describedby, required marker, and disabled state all wire up automatically:

<CoarFormField label="Handler script" :error="scriptError" required>
  <CoarScriptEditor
    v-model="form.script"
    variant="inline"
    height="180px"
    placeholder="// return query.filter(...)"
    script-mode
    preamble="declare const query: TodoQuery;"
    :extra-libs="[{ filePath: 'file:///types/query.d.ts', content: queryTypes }]"
  />
</CoarFormField>
  • preamble — hidden + locked type-context lines, per-editor scope. Emitted modelValue is the user portion only.
  • script-mode — suppresses diagnostics for top-level return/await/export. Global side-effect across all TS/JS editors.
  • variant="inline" — compact form-field chrome: no line numbers, no gutter, tight padding.
  • height — CSS string or pixels. The editor fills its parent if omitted.

Full documentation

See the Cocoar UI Vue docs for the complete guide, including styling, @reject payload details, a live playground, and the full list of pure helpers.