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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@lmarcel/highlight

v2.7.1

Published

Editable highlight component for react with support for many languages ​​and custom themes.

Downloads

106

Readme

Editable highlight component for react with support for many languages ​​and custom themes.

It is now possible to create the definition of languages ​​that do not exist! That's right, you can make your own! See the demonstration. Imports, language definitions and the documentation have also been updated. All the changes I wanted have already been made, any problems, please create an issue.

Summary

Features

  • Support for many languages
  • Loads the dependencies of each language alone
  • Editable content support
  • Mobile editable content support
  • Customizable existing or created language definitions
  • Customizable theming
  • Tab navigation support
  • Included styles
  • With built-in line number viewer
  • Deep integration with TypeScript
  • Plug and play! You don't need an external configuration to use it

Installation

To install you need to run in your project:

//Using pnpm
pnpm add @lmarcel/highlight

//Using npm
npm install @lmarcel/highlight

//Using yarn
yarn add @lmarcel/highlight

Basic usage

It's very simple to use!

<Highlight
  theme="oneDark"
  language="ts"
  code={`import path from "path";
console.log(path.resolve(__dirname, "test"));`}
/>

Advanced usage

You can edit! That's right, like a normal textarea! See the demo.

import { useState } from "react";
import { Highlight, EditEvent } from "@lmarcel/highlight";

export default function Home() {
  const [code, setCode] = useState(`const a = "red";\nconsole.log(a);`);

  function handleOnEdit(e: EditEvent) {
    setCode(e.currentTarget.value);
  };

  return (
    <Highlight
      placeholder="Put your code here..."
      style={{
        minWidth: 800
      }}
      editable={true}
      onEdit={handleOnEdit}
      code={code}
      language="javascript"
    />
  );
};

Demonstrations

See some demos at storybook. It has an editable demonstration!

Edit mode

If the editable property is true, the user will be able to enter the edit mode, where it is possible to change the content inside the component.

Tab navigation

In edit mode, the tab key inserts a tab space (as in a normal editor). To exit edit mode, just press esc key or click outside the component (triggering a blur event).

However, if the user chooses to leave using the esc key, a focus event will be triggered in the component, so that the component does not harm navigation by tab navigation.

When the component is focused, it is possible to enter edit mode by pressing the enter key.

I thought of showing the available keys in a kind of menu, but this could harm your design and the current structure makes it very difficult to do this within the component itself, so I left it free for you to choose how you want to indicate the navigation possibilities to the user.

I left two functions that can be passed for this purpose: onEnterEditMode and onExitEditMode.

Theming

You can define pre-existing or custom themes for the component.

Available themes

I left some predefined themes, some with more extended support for some languages.

Extensive themes (I made several changes):

  • oneDark
  • oneLight
  • laserwave
  • dracula

Updated themes (I made small changes):

  • vsDark
  • vsLight

Old themes (not changed):

  • byverduDracula
  • duotoneDark
  • duotoneLight
  • github
  • nightOwl
  • nightOwlLight
  • oceanicNext
  • palenight
  • okaidia
  • shadesOfPurple
  • synthwave84
  • ultramin

Custom themes

You can also edit existing themes in a very simple way using HighlightCustomTheme. Or even create your own themes.

import { HighlightCustomTheme, themes } from "@lmarcel/highlight";

export const myTheme = new HighlightCustomTheme(/*...*/);

export const anotherTheme = new HighlightCustomTheme.extends(myTheme, /*...*/);

export const storybookTheme = HighlightCustomTheme.extends(themes.oneDark, {
  numbersBorderColor: "#1ea7fd",
  backgroundColor: "#272727",
  numbersBackgroundColor: "#2b2a2a",
  numbersColor: "#cfcfcf"
});

Languages

This library uses Prism.js to generate the tokens for each language component, to avoid ambiguity I call these components languages definitions.

Available languages

This library currently supports ALL Prism.js languages ​​dynamically. Because it was too big and not feasible to do manually, I migrated this list to storybook in available languages.

Custom languages definitions

It is possible, but quite complex, to edit language definitions using the library. You can make your own (it inevitably requires extensive knowledge of regex, see the demonstration):

//imports
import { Highlight, HighlightCustomLanguage } from "@lmarcel/highlight";

//my custom language definitions
const banner = new HighlightCustomLanguage(
  "myBanner",
  ["banner"],
  {
    grammar: {
      "banners": [{
        pattern: /\btitle\b/g,
        alias: "banner-title"
      }, {
        pattern: /\bsubtitle\b/g,
        alias: "banner-subtitle"
      }, {
        pattern: /\bend\b/g,
        alias: "banner-end"
      }]
    },
  }
);

//my javascript language definitions
const javascript = new HighlightCustomLanguage(
  "javascript",
  [],
  {
    grammar: "javascript",
  }
);

javascript.replaceTokenRule(
  "keyword",
  "control-flow",
  (oldToken) => {
    return {
      ...oldToken,
      pattern:
        /\b(?<!\.)(?:(await(?= |\()|break(?=\b)|catch(?=[\s]*\()|continue(?=\b)|do(?=\b)|else(?=\b)|finally(?=\b)|for(?=\b)|if(?=\b)|return(?=\b)|switch(?=\b)|throw(?=\b)|try(?=\b)|while(?=\b)|yield(?=\b)))/,
      alias: "control-flow",
    };
  }
);

export { javascript, banner };

//component
<Highlight
  externalLanguages={[banner, javascript]}
  language="banner"
  code={code}
/>

The definitions are available in the grammar property of the new language instance.

When creating a new instance it is possible to pass the raw value of this property, another instance of the class or even the name (but not an alias) of a pre-existing language (inheriting the language definitions).

This is what happens in this section:

const javascript = new HighlightCustomLanguage(
  "javascript",
  [],
  {
    grammar: "javascript",
  }
);

Within grammar, the mapping of tokens is done, each token has one or more rules within it.

These rules can have an alias, which is the value that can be passed after the token to be used in styling.

I left some functions available in the instance to manipulate these tokens. But it is a very complex resource and I may have missed something.

javascript.replaceTokenRule(
  "keyword", //token
  "control-flow", //token rule alias
  (oldToken) => { //function to return the new token rule
    return {
      ...oldToken,

      //regex
      pattern: 
        /\b(?<!\.)(?:(await(?= |\()|break(?=\b)|catch(?=[\s]*\()|continue(?=\b)|do(?=\b)|else(?=\b)|finally(?=\b)|for(?=\b)|if(?=\b)|return(?=\b)|switch(?=\b)|throw(?=\b)|try(?=\b)|while(?=\b)|yield(?=\b)))/,

      //token rule alias
      alias: "control-flow", 
    };
  }
);

Plugins

One of the most complex and most useful parts. A single plugin can make changes to code, theme, tokens, property of rendered lines and other things. The library itself already comes with a very simple plugin that I created for testing, the corePlugin.

Currently its only function is to style spaces generated by tabs.

See how to use:

import { corePlugin, Highlight } from "@lmarcel/highlight";

export function Code(/* ... */) {
  return (
    <Highlight
      plugins={[
        corePlugin()
      ]}
      /* ... */
    />
  );
}

Plugins are executed in the same order they are passed to the component.

The plugin must always be passed as a function, because it can receive specific settings from it. Avoid using too many plugins simultaneously, it can hurt performance and some can conflict.

Here is an example of the structure of a plugin:

//packages/highlight/src/plugins/custom/corePlugin.ts
export const corePlugin = HighlightPlugin.create<CorePluginSettings>(
  {
    codeLine: (settings, tokens, core) => {
      const spaces = core.tabSize ?? 2;

      if (
        settings?.showTabulations &&
        tokensStartWithTabulation(tokens, spaces)
      ) {
        /* ... */
      }

      return tokens;
    },
    theme: (settings, theme, core) => {
      if (settings?.showTabulations) {
        /* ... */
      }

      return theme;
    },
    code: (settings, code, core) => {
      let numberOfTabsLastLine = 0;

      if (settings?.showTabulations) {
        /* ... */
      }

      return code;
    },
  },
  {
    showTabulations: true,
  }
);

The functions follow the same pattern. The first parameter is the plugin's settings, the second is the value it wants to change and return and the last is the main properties passed to the component.

Does not support async functions!

I left a demo of this plugin. I made something very simple, so take it easy if you find fault!

For authors

For those who are interested, I left a page on storybook containing some examples of how it is possible to create plugins, language definitions and themes.

See the codes.

I also left a lot of helpful comments on the properties I typed within the code. If you are going to use TypeScript you will see occasionally. I apologize if this part is not very well documented.

Recommendation

A library that can be very useful for building complex regex is magic regex. It is a new library with a lot of potential.