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

@webselect/editor

v3.5.1

Published

A text editor UI built on top of TipTap

Downloads

7

Readme

Webselect text editor

The editor is a UI built on top of TipTap, which itself is a high-level abstraction of ProseMirror.

Installation

npm i @webselect/editor

Usage

ESM environments

This can be implemented natively in the browser:

<link rel="stylesheet" href="./path/to/editor.css">

<script type="module">
    import { Editor } from './path/to/editor.mjs';

    const editor = new Editor({ ... });
</script>

Or as part of a build pipeline / bundler:

// Optional depending on setup.
// Explained in the "Styling" section.
import './path/to/editor.css';

import { Editor } from '@webselect/editor';

const editor = new Editor({ ... });

Legacy environments

This works in a similar way to libraries such as jQuery, which expose a global variable. In this case that variable is WebselectEditor.

<script src="./path/to/editor.browser.js"></script>

<script>
    document.addEventListener("DOMContentLoaded", () => {
        const editor = new WebselectEditor.Editor({ ... });
    });
</script>

Creating a new Editor instance

const editor = new Editor({
    element: document.querySelector('.editor'),
});

The Editor constructor takes a single options object.

| Param | Type | Default | Description | | --- | --- | --- | --- | | element | Element | - | The Element that the Editor will be mounted to. | | [content] | string | - | Optional: Initial HTML content to populate the Editor with on instantiation. | | [editorClasses] | string | - | Optional: Additional classes to be added to the TipTap editor. | | [defaultColour] | string | '#000000' | Optional: The default text colour of the Editor. | | [swatches] | string[] | - | Optional: Swatches that will be shown in the colour picker. | | [fontFamilies] | string[] | - | Optional: Font families that can be used in the Editor. | | [headingLevels] | number[] | [1, 2, 3, 4, 5, 6] | Optional: Permitted heading levels in the editor. | | [elementBindings] | Element | Element[] | - | Optional: Any Element(s) that will be automatically bound to the value of the Editor. | | [onUpdate] | (editor: Editor) ⇒ void | - | Optional: Callback function that fires whenever the state of the Editor is changed. |

The Editor instance

The underlying TipTap instance is available as property tiptap.

const editor = new Editor({ ... });

// TipTap instance
editor.tiptap;

The following instance methods are also available. In some cases these simply proxy TipTap methods for ease of use.

destroy() ⇒ void

Destroy the editor along with its TipTap instance.

getHTML() ⇒ string

Get the editor contents as HTML.

isEmpty() ⇒ boolean

Check if the editor is empty or not.

setContent(content) ⇒ void

Replace the editor with new content.

| Param | Type | Description | | --- | --- | --- | | content | string | The new content. |

Example:

editor.setContent('<p>Example text</p>');

closeAllDialogs() ⇒ void

Close any open dialogs.

Styling

If your pipeline/setup allows it, for example using Webpack along with the css-loader plugin, you can import the CSS directly in the module:

import './path/to/editor.css';

Otherwise you'll want to copy editor.css from the package into your distribution folder and reference it in your HTML like normal.

The Editor comes with a basic, neutral layout that is based on CSS variables. These can be overridden to customise the editor as needed. The following table lists the available variables and their functions:

| Variable | Default | Description | | --- | --- | --- | | --wse-toolbar-bg-colour | transparent | The background colour of the toolbar. | | --wse-toolbar-text-colour | #000 | The text colour of the toolbar. | | --wse-toolbar-padding | 10px | The toolbar padding. | | --wse-toolbar-gap | 8px | The gap between controls in the toolbar. | | --wse-toolbar-border | 1px solid #999 | The border underneath the toolbar. | | --wse-control-border | none | The border around toolbar controls. | | --wse-control-radius | 5px | The border radius of toolbar controls. | | --wse-control-padding | 8px | The padding of toolbar controls. | | --wse-control-bg-colour | #eee | The background colour of toolbar controls. | | --wse-control-text-colour | #000 | The text colour of toolbar controls. | | --wse-control-hover-bg-colour | #ddd | The hover background colour of toolbar controls. | | --wse-control-hover-text-colour | #000 | The hover text colour of toolbar controls. | | --wse-control-highlight-bg-colour | #222 | The highlighted background colour of toolbar controls. | | --wse-control-highlight-text-colour | #fff | The highlighted text colour of toolbar controls. | | --wse-control-icon-size | 16px | The size of toolbar control icons. | | --wse-dialog-bg-colour | #222 | The background colour of control dialogs. | | --wse-dialog-text-colour | #fff | The text colour of control dialogs. | | --wse-dialog-padding | 15px | The padding of control dialogs. | | --wse-dialog-radius | 5px | The border radius of control dialogs. | | --wse-dialog-gap | 6px | The gap between elements inside control dialogs. | | --wse-dialog-shadow | none | The drop shadow underneath control dialogs. | | --wse-editor-bg-colour | #fff | The background colour of the editor. | | --wse-editor-min-height | 200px | The minimum height of the editor. | | --wse-editor-max-height | none | The maximum height of the editor. | | --wse-editor-padding | 20px | The padding of the editor. | | --wse-editor-border | none | The border around the editor. | | --wse-editor-radius | 0px | The border radius of the editor. |

Text colour component styles

| Variable | Default | Description | | --- | --- | --- | | --wse-default-swatch-radius | 50% | The radius of the swatches. | | --wse-default-swatch-highlight-outline | #fff | The highlight colour of the swatches. | | --wse-default-swatches-per-line | 8 | The number of swatches per row. |

Demo pages

Demo pages can be found under the demos directory. You'll need to spin up a server in the project root, and not under demos, so that the distribution files can be referenced properly. I recommend using serve.

Contributing

TipTap is headless and totally modular, so out of the box it does virtually nothing. You can intitialise it with "extensions", which enable features such as bold or italic text. In some cases this will include markdown functionality, but it does not include any UI. That's down to us to implement ourselves.

In most cases the requirements for a feature UI are as follows:

  • Have a button or some kind of interactive element that will invoke the functionality of an "extension".
  • Provide a subsequent UI if needed (eg. a colour picker)
  • The element will react to changes in TipTap's state (eg. a bold button will highlight if the user selects bold text in the editor).

To that end the editor is based on class inheritance, with common functionality delegated to abstract classes. These can be found under components/abstract/*.

The base class, Control<T>, does very little other than define methods that must be implemented by subclasses, and expose the current editor instance. This will probably never need to be touched.

Next in the heirarchy is ControlButton and ControlSelect. Hopefully these are self-explanatory; they contain common logic for button and select UI elements. This includes creating the corresponding elements in memory so that they can be appended to the toolbar later. These should be the only elements we need.

Above ControlButton is the optional ControlButtonWithDialog class, which handles the creation of a Dialog component. More on Dialog in the next section.

Lastly we have the top-level controls that implement either ControlButton, ControlButtonWithDialog or ControlSelect. These are found under components/Control{Name}.ts (note the Control prefix for consistency).

A simple example is the control for bold text:

import icon from '../../img/bold.svg';

import { highlightIf } from '../utils';
import { ControlButton } from './abstract/ControlButton';

class ControlBold extends ControlButton {
   constructor() {
       super(icon);
   }

   public override update(): void {
       highlightIf(this.tiptap.isActive('bold'), this.element);
   }

   protected override onClick(): void {
       this.tiptap.chain().focus().toggleBold().run();
   }
}

export { ControlBold };

Currently the icons are provided by Flaticon. There's a collection in our account named "Text editor". Any new icons should follow the same style if possible.

The update() method is mandatory and is called when TipTap fires its transaction event, which essentially means the state has changed somehow. This includes the user making selections or simply changing the caret position.

The highlightIf() method is a simple utility function that will apply some styles to the element if the condition is truthy. What's important is the condition itself: this.tiptap.isActive('bold'). If the caret is within the bounds of a block of bold text, or the current selection contains any bold markers, this will return true and our UI element will be highlighted.

The onClick() method is fired when the UI element is clicked. This then hooks directly into the TipTap API to enable bold text. This action will also trigger the transaction event mentioned previously.

Note that not all extensions have the same methods, so you'll need to reference the TipTap docs when implementing any new extensions. The concept, however, is the same.

For a more complex example, it's worth having a look at ControlTextColour.ts, as this requires an additional UI for displaying a colour picker.

In a nutshell, the process for adding a new UI control is as follows:

  • Install the corresponding TipTap extension.
  • Register the extension when initialising TipTap (in main.ts).
  • Find a new icon and add it to the img directory.
  • Create a new Control{Name}.ts that extends one of the abstract ControlButton, ControlButtonWithDialog or ControlSelect classes and implement according to the TipTap docs.
  • Import and add a new instance of the control to the controls array (in main.ts).

Dialog component

The Dialog component creates a floating dialog beneath a Control. You'll need to pass in the HTML to render in the dialog, but there are a couple of handy conventions to be aware of:

  • If your HTML contains a <form> element then a submit event will automatically be bound to it, which will call event.preventDefault() and then, if you have provided an onSubmit property, it'll be called.
  • If your HTML contains a <button> element with a data-type="clear" attribute (inside a <form> element) then a click event will be automatically bound to it, which will call onClear if you have provided it.

Development mode

Run watch to begin development mode.

Publishing

Before publishing a new version, please make sure to update CHANGELOG.md.

The prePublishOnly task runs automatically and builds the project for production (including code formatting and generating TypeScript types), so you shouldn't have to do any heavy lifting.

I recommend using np for publishing, but it's entirely up to you.

You can bump just the version number in package.json and package-lock.json by running the following command:

npm version --commit-hooks false --git-tag-version false <major|minor|patch>