@bigmistqke/solid-contenteditable
v0.0.14
Published
textual contenteditable build with solid-js
Readme
@bigmistqke/solid-contenteditable
textual contenteditable 🔥 by solid-js
Installation
npm i @bigmistqke/solid-contenteditableyarn add @bigmistqke/solid-contenteditablepnpm add @bigmistqke/solid-contenteditableProps
<ContentEditable/> accepts the following props:
editable: A boolean that controls whether the content is editable. Defaults totrue.historyStrategy: A function that determines whether two consecutive history entries should be merged. (more info)onPatch: A function that can return a (custom) patch based on a keyboard event. ReturnPatchornull.onTextContent: A callback that is triggered whenever the text-content is updated.render: A function that receives an accessor totextContentand returnsJSX.Element. This render-prop allows for adding markup around the textContent, but must keep the resulting textContent unchanged. (more info)singleline: A boolean that indicates whether the component should accept only single-line input. When set totrue, pasted newlines are replaced with spaces, and pressing the return key will be ignored. Defaults tofalse.textContent: The text-content of the component.
interface ContentEditableProps<T extends string | never = never>
extends Omit<
ComponentProps<'div'>,
'children' | 'contenteditable' | 'onBeforeInput' | 'textContent' | 'onInput' | 'style'
> {
editable?: boolean
historyStrategy?(currentPatch: Patch<T>, nextPatch: Patch<T>): boolean
onPatch?(event: KeyboardEvent & { currentTarget: HTMLElement }): Patch<T> | null
onTextContent?: (value: string) => void
render?(textContent: Accessor<string>): JSX.Element
singleline?: boolean
style?: JSX.CSSProperties
textContent: string
}Simple Example
import { ContentEditable } from '@bigmistqke/solid-contenteditable'
import { createSignal } from 'solid-js'
function ControlledContentEditable {
const [text, setText] = createSignal('Editable content here...')
return <ContentEditable textContent={text()} onTextContent={setText} />
}
function UncontrolledContentEditable {
return <ContentEditable textContent='Editable content here...' />
}Advanced Example
import { ContentEditable } from '@bigmistqke/solid-contenteditable'
import { createSignal, For, Show } from 'solid-js'
function HashTagHighlighter() {
const [text, setText] = createSignal('this is a #hashtag')
return (
<ContentEditable
textContent={text}
onTextContent={setText}
singleline
render={textContent => (
<For each={textContent().split(' ')}>
{(word, wordIndex) => (
<>
<Show when={word.startsWith('#')} fallback={word}>
<button onClick={() => console.log('clicked!')}>{word}</button>
</Show>
<Show when={textContent.split(' ').length - 1 !== wordIndex()} children=" " />
</>
)}
</For>
)}
/>
)
}History Strategy
historyStrategy is a function that determines whether two consecutive history entries (patches) should be merged during undo/redo operations. This feature allows for customizing the behavior of the history stack based on the nature of the changes.
Default Strategy
The default historyStrategy implementation in <ContentEditable/> behaves as follows:
- It only merges consecutive text insertions (
insertText). - It will concatenate patches when current character is a whitespace and the following is a non-whitespace.
function(currentPatch: Patch, nextPatch: Patch) {
if (
currentPatch.kind === 'deleteContentBackward' &&
nextPatch.kind === 'deleteContentForward'
) {
return false
}
if (
currentPatch.kind === 'deleteContentForward' &&
nextPatch.kind === 'deleteContentBackward'
) {
return false
}
return !(
(currentPatch.kind !== 'insertText' &&
currentPatch.kind !== 'deleteContentBackward' &&
currentPatch.kind !== 'deleteContentForward') ||
(nextPatch.kind !== 'insertText' &&
nextPatch.kind !== 'deleteContentBackward' &&
nextPatch.kind !== 'deleteContentForward') ||
(currentPatch.data === ' ' && nextPatch.data !== ' ')
)
}Custom Strategy Example
This custom strategy mirrors the behavior typically seen in default browser <input/> and <textarea/>, where subsequent text insertions and new paragraphs are merged automatically.
<ContentEditable
textContent="Start typing here..."
historyStrategy={(currentPatch, nextPatch) => {
return (
(currentPatch.kind === 'insertText' || currentPatch.kind === 'insertParagraph') &&
(nextPatch.kind === 'insertText' || nextPatch.kind === 'insertParagraph')
)
}}
/>Limitations with Render Prop
The <ContentEditable/> component supports a render-prop that accepts the textContent as its argument, enabling you to enhance the displayed content with additional markup. It's important to adhere to the following guidelines when using this feature:
- Consistency Requirement: The textContent of the JSX element returned by the render-prop must remain identical to the provided argument. This ensures that the element's functional behavior aligns with its displayed content.
- Behavioral Caution: Deviations in the textContent between the input and output can lead to undefined behavior, potentially affecting the stability and predictability of the component.
- Markup Flexibility: While you are free to add decorative or structural HTML around the text, these modifications should not alter the resulting textContent.
If the resulting textContent deviates from the given input, a warning will be logged in the console.
