Socket
Socket
Sign inDemoInstall

rc-marked-input

Package Overview
Dependencies
1
Maintainers
1
Versions
25
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

    rc-marked-input

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


Version published
Weekly downloads
155
decreased by-25.48%
Maintainers
1
Install size
99.8 kB
Created
Weekly downloads
 

Readme

Source

Marked Input · npm version min zipped size Storybook

image

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

Feature

  • Powerful annotations tool: add, edit, remove, visualize
  • TypeScript
  • Support for any components
  • Flexible and customizable
  • Two ways to configure
  • Helpers for processing text
  • Hooks for advanced components
  • Button handling (Left, Right, Delete, Backspace, Esc)
  • Overlay with the suggestions component by default
  • Zero dependencies
  • Cross selection

Installation

You can install the package via npm:

npm install rc-marked-input

Usage

There are many examples available in the Storybook. You can also try a template on CodeSandbox.

Here are a few 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} 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} options={[{
            markup: Primary,
            data: Data,
            initMark: ({label, value}) => ({label, primary: true, onClick: () => alert(value)})
        }, {
            trigger: '/',
            markup: Default,
            data: AnotherData
        }]}/>
    )
}

Using the createMarkedInput:

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

const ConfiguredMarkedInput = createMarkedInput({
    Mark: Button,
    options: [{
        markup: Primary,
        data: ['First', 'Second', 'Third', 'Fourth', 'Fifth', 'Sixth'],
        initMark: ({label, value}) => ({label, primary: true, onClick: () => alert(value)})
    }, {
        markup: Default,
        trigger: '/',
        data: ['Seventh', 'Eight', 'Ninth'],
        initMark: ({label}) => ({label})
    }],
})

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, change} = useMark()

    const handleInput = (e) =>
        change({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, remove} = useMark()
    return <mark onClick={remove} 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 ref 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} options={[{data:['First', 'Second', 'Third']}]}/>
}
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={() => null} Overlay={Overlay} value={value} onChange={setValue} options={[{trigger: '/'}]}/>
}
Positioned

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

const Tooltip = () => {
    const {style} = useOverlay()
    return <div style={{position: 'absolute', ...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 useOverlay hook provide some methods like select for creating a new annotation.

const List = () => {
    const {select} = useOverlay()
    return <ul>
        <li onClick={() => select({label: 'First'})}>Clickable First</li>
        <li onClick={() => select({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 pass the ref for an overlay component. It used to detect outside click.

Overriding internal components

The children prop allows to pass elements to overrides internal components. The div tag for container. The span tag for text cell.

<ConfiguredMarkedInput value={value} onChange={setValue}>
    <div
        onClick = {(e) => console.log('onCLick')}
        onInput = {(e) => console.log('onInput')}
        /* other props */
        onBlur = {(e) => console.log('onBlur')}
        onFocus = {(e) => console.log('onFocus')}
        onKeyDown={(e) => console.log('onKeyDown')}
    />
    <span className='span-class'/>
</>

Overall view

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

Or

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

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

API

MarkedInput

NameTypeDefaultDescription
valuestringAnnotated text with markups for mark
onChange(value: string) => voidChange event
MarkComponentType<T = MarkProps>undefinedComponent that used for render markups
OverlayComponentTypeSuggestionsComponent that is rendered by trigger
readOnlybooleanundefinedPrevents from changing the value
optionsOptionProps[][{}]Passed options for configure
triggerOverlayTriggerchangeTriggering events for overlay

Helpers

NameTypeDescription
createMarkedInput<T = MarkStruct>(configs: MarkedInputProps): ConfiguredMarkedInputCreate the configured MarkedInput component.
annotate(markup: Markup, label: string, value?: string) => stringMake annotation from the markup
denote(value: string, callback: (mark: Mark) => string, ...markups: Markup[]) => stringTransform the annotated text
useMark() => MarkHandlerAllow to use dynamic mark
useOverlay() => OverlayHandlerUse overlay props
useListener(type, listener, deps) => voidEvent listener

Types

type OverlayTrigger =
	| Array<'change' | 'selectionChange'>
	| 'change'
	| 'selectionChange'
	| 'none';
interface MarkStruct {
    label: string
    value?: string
}
interface OverlayHandler {
    /**
     * Style with caret absolute position. Used for placing an overlay.
     */
    style: {
        left: number;
        top: number;
    };
    /**
     * Used for close overlay.
     */
    close: () => void;
    /**
     * Used for insert an annotation instead a triggered value.
     */
    select: (value: MarkStruct) => void;
    /**
     * Overlay match details
     */
    match: OverlayMatch;
    ref: RefObject<HTMLElement>;
}
interface MarkHandler<T> extends MarkStruct {
    /**
     * MarkStruct ref. Used for focusing and key handling operations.
     */
    ref: RefObject<T>
    /**
     * Change mark.
     * @options.silent doesn't change itself label and value, only pass change event.
     */
    change: (props: MarkStruct, options?: { silent: boolean }) => void
    /**
     * Remove itself.
     */
    remove: () => void
    /**
     * Passed the readOnly prop value
     */
    readOnly?: boolean
}
type OverlayMatch = {
    /**
     * Found value via a overlayMatch
     */
    value: string;
    /**
     * Triggered value
     */
    source: string;
    /**
     * Piece of text, in which was a overlayMatch
     */
    span: string;
    /**
     * Html element, in which was a overlayMatch
     */
    node: Node;
    /**
     * Start position of a overlayMatch
     */
    index: number;
    /**
     * OverlayMatch's option
     */
    option: Option;
}
export interface Option<T = Record<string, any>> {
    /**
     * Template string instead of which the mark is rendered.
     * Must contain placeholders: `__label__` and optional `__value__`
     * @default "@[__label__](__value__)"
     */
    markup?: Markup;
    /**
     * Sequence of symbols for calling the overlay.
     * @default "@"
     */
    trigger?: string;
    /**
     * Data for an overlay component. By default, it is suggestions.
     */
    data?: string[];
    /**
     * Function to initialize props for the mark component. Gets arguments from found markup
     */
    initMark?: (props: MarkStruct) => T;
}

Contributing

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

Keywords

FAQs

Last updated on 30 Mar 2023

Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc