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

react-marks

v2.5.0

Published

React component for combine editable text with any component using annotated text

Downloads

6

Readme

Marked Input · npm version Storybook

A React component that lets you combine editable text with any component using annotated text.

Feature

  • Powerful annotation tool
  • TypeScript
  • Two ways to configure
  • Support any components
  • Props customization
  • Utils for annotate and denote text
  • Button handling (Left, Right, Delete, Backspace, Esc)
  • Overlay with default the suggestion component
  • Zero dependency

Installation

Install the package via npm:

npm install rc-marked-input

Examples

A lot of examples can be seen in the storybook. You can also try a template on CodeSandbox.

Here is some examples to get you started.

Static marks · sandbox

import {MarkedInput} from "rc-marked-input";

const Mark = (props) => <mark onClick={_ => alert(props.value)}>{props.label}</mark>

const Marked = () => {
    const [value, setValue] = useState("Hello, clickable marked @[world](Hello! Hello!)!")
    return <MarkedInput Mark={Mark} value={value} onChange={setValue}/>
}

Configured · sandbox

The library allows you to configure the MarkedInput component in two ways.

Let's declare markups and suggestions data:

const Data = ["First", "Second", "Third", "Fourth", "Fifth", "Sixth"]
const AnotherData = ["Seventh", "Eight", "Ninth"]
const Primary = "@[__label__](primary:__value__)"
const Default = "@[__label__](default)"

Using the components

import {MarkedInput, Option} from "rc-marked-input";

export const App = () => {
    const [value, setValue] = useState(
        "Enter the '@' for creating @[Primary Mark](primary:Hello!) or '/' for @[Default mark](default)!"
    )

    return (
        <MarkedInput Mark={Button} value={value} onChange={setValue}>
            <Option
                markup={Primary}
                data={Data}
                initMark={({label, value}) => ({label, primary: true, onClick: () => alert(value)})}
            />
            <Option
                markup={Default}
                trigger="/"
                data={AnotherData}
            />
        </MarkedInput>
    )
}

Using the createMarkedInput:

import {createMarkedInput} from "rc-marked-input";

const ConfiguredMarkedInput = createMarkedInput(Button, [{
    markup: Primary,
    data: Data,
    initMark: ({label, value}) => ({label, primary: true, onClick: () => alert(value)})
}, {
    trigger: '/',
    markup: Default,
    data: AnotherData
}])

const App = () => {
    const [value, setValue] = useState(
        "Enter the '@' for creating @[Primary Mark](primary:Hello!) or '/' for @[Default mark](default)!"
    )
    return <ConfiguredMarkedInput value={value} onChange={setValue}/>
}

Dynamic mark · sandbox

Marks can be dynamic: editable, removable, etc. via the useMark hook helper.

Editable

import {MarkedInput, useMark} from "rc-marked-input";

const Mark = () => {
    const {label, onChange} = useMark()

    const handleInput = (e) =>
        onChange({label: e.currentTarget.textContent ?? "", value: " "}, {silent: true})

    return <mark contentEditable onInput={handleInput} children={label}/>
}

export const Dynamic = () => {
    const [value, setValue] = useState("Hello, dynamical mark @[world]( )!")
    return <MarkedInput Mark={Mark} value={value} onChange={setValue}/>
}

Note: The silent option used to prevent re-rendering itself.

Removable

const RemovableMark = () => {
    const {label, onRemove} = useMark()
    return <mark onClick={onRemove} children={label}/>
}

export const Removable = () => {
    const [value, setValue] = useState("I @[contain]( ) @[removable]( ) by click @[marks]( )!")
    return <MarkedInput Mark={RemovableMark} value={value} onChange={setValue}/>
}

Focusable

If passed the reg prop of the useMark hook in ref of a component then it component can be focused by key operations.

Overlay

A default overlay is the suggestion component, but it can be easily replaced for any other.

Suggestions

export const DefaultOverlay = () => {
    const [value, setValue] = useState("Hello, default - suggestion overlay by trigger @!")
    return <MarkedInput Mark={Mark} value={value} onChange={setValue}>
        <Option data={['First', 'Second', 'Third']}/>
    </MarkedInput>
}

Custom overlay · sandbox

const Overlay = () => <h1>I am the overlay</h1>
export const CustomOverlay = () => {
    const [value, setValue] = useState("Hello, custom overlay by trigger @!")
    return <MarkedInput Mark={Mark} Overlay={Overlay} value={value} onChange={setValue}/>
}

Custom trigger

export const CustomTrigger = () => {
    const [value, setValue] = useState("Hello, custom overlay by trigger /!")
    return <MarkedInput Mark={Mark} Overlay={Overlay} value={value} onChange={setValue}>
        <Option trigger='/'/>
    </MarkedInput>
}

Positioned

The OverlayProps has a left and right absolute coordinate of a current caret position in the style prop.

const Tooltip = (props: OverlayProps) => <div style={{position: 'absolute', ...props.style}}>I am the overlay</div>
export const PositionedOverlay = () => {
    const [value, setValue] = useState("Hello, positioned overlay by trigger @!")
    return <MarkedInput Mark={Mark} Overlay={Tooltip} value={value} onChange={setValue}/>
}

Selectable

The OverlayProps provide some methods like onSelect for creating a new annotation.

const List = (props: OverlayProps) => <ul>
    <li onClick={() => props.onSelect({label: 'First'})}>Clickable First</li>
    <li onClick={() => props.onSelect({label: 'Second'})}>Clickable Second</li>
</ul>

export const SelectableOverlay = () => {
    const [value, setValue] = useState("Hello, suggest overlay by trigger @!")
    return <MarkedInput Mark={Mark} Overlay={List} value={value} onChange={setValue}/>
}

Note: Recommend to use the React.forwardRef for an overlay component. It used to detect outside click.

Event handlers

The onContainer prop allows to forward any of div events to a container of text.

<ConfiguredMarkedInput
    value={value} 
    onChange={setValue}
    onContainer={{
        onClick: (e) => console.log('onCLick'),
        onInput: (e) => console.log('onInput'),
        onBlur: (e) => console.log('onBlur'),
        //...
        onFocus: (e) => console.log('onFocus'),
        onKeyDown: (e) => console.log('onKeyDown'),
    }}
/>

Overall view

<MarkedInput Mark={Mark} Overlay={Overlay} value={value} onChange={setValue}>
    <Option
        trigger='@'
        markup='@[__label__](__value__)'
        data={Data}
        initMark={getCustomMarkProps}
        initOverlay={getCustomOverlayProps}
    />
    <Option
        trigger='/'
        markup='@(__label__)[__value__]'
        data={AnotherData}
        initMark={getAnotherCustomMarkProps}
        initOverlay={getAnotherCustomOverlayProps}
    />
</MarkedInput>

Or

const MarkedInput = createMarkedInput(Mark, Overlay, [{
    trigger: '@',
    markup: '@[__label__](__value__)',
    data: Data,
    initMark: getCustomMarkProps,
    initOverlay: getCustomOverlayProps,
}, {
    trigger: '/',
    markup: '@(__label__)[__value__]',
    data: AnotherData,
    initMark: getAnotherCustomMarkProps,
    initOverlay: getAnotherCustomOverlayProps,
}])

const App = () => <MarkedInput value={value} onChange={setValue}/>

API

MarkedInput

| Name | Type | Default | Description | |-------------|---------------------------------|---------------|--------------------------------------------| | value | string | | Annotated text with markups for mark | | onChange | (value: string) => void | | Change event | | Mark | ComponentType<T = MarkProps> | | Component that used for render markups | | Overlay | ComponentType<T = OverlayProps> | Suggestions | Component that is rendered by trigger | | readOnly | boolean | undefined | Prevents from changing the value | | onContainer | DivEvents | undefined | Forward any div events to a text container |

Option

| Name | Type | Default | Description | |-------------|-----------------------------|---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | markup | string | @[__label__](__value__) | Template string instead of which the mark is renderedMust contain placeholders: __label__ and optional __value__ __value__ For example: @[__label__](__value__) | | trigger | string | "@" | Sequence of symbols for calling the overlay. | | data | string[] | [] | Data for a overlay component. By default, it is suggestions. | | initMark | (props: MarkProps) => T | undefined | Function to initialize props for mark render. Gets arguments from found markup | | initOverlay | (props: OverlayProps) => T1 | undefined | Function to initialize overlay props to your requirements. If missing then passed overlay props directly. |

Helpers

| Name | Type | Description | |-------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------| | createMarkedInput | (Mark: ComponentType, options: OptionProps[]): ConfiguredMarkedInput (Mark: ComponentType, Overlay: ComponentType, options: OptionProps<T, T1>[]): ConfiguredMarkedInput | Create the configured MarkedInput component. | | annotate | (markup: Markup, label: string, value?: string) => string | Make annotation from the markup | | denote | (value: string, callback: (mark: Mark) => string, ...markups: Markup[]) => string | Transform the annotated text | | useMark | () => DynamicMark | Allow to use dynamic mark |

Types

interface MarkProps {
    label: string
    value?: string
}
interface OverlayProps {
    /**
     * Style with caret absolute position. Used for placing an overlay.
     */
    style: {
        left: number
        top: number
    }
    /**
     * Used for close overlay.
     */
    onClose: () => void
    /**
     * Used for insert an annotation instead a triggered value.
     */
    onSelect: (value: MarkProps) => void
    /**
     * Trigger details
     */
    trigger: Trigger
}
type Trigger = {
    /**
     * Found value via a trigger
     */
    value: string,
    /**
     * Triggered value
     */
    source: string,
    /**
     * Piece of text, in which was a trigger
     */
    span: string,
    /**
     * Html element, in which was a trigger
     */
    node: Node,
    /**
     * Start position of a trigger
     */
    index: number,
    /**
     * Trigger's option
     */
    option: OptionType
}

Contributing

If you want to contribute, you are welcome! Create an issue or start a discussion.