Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use stylesheets to give Editable components default styles #5206

Merged
merged 7 commits into from
Dec 4, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/strange-pens-lie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'slate': minor
'slate-react': minor
---

Use stylesheet for default styles on Editable components
1 change: 1 addition & 0 deletions packages/slate-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@types/react": "^16.9.13",
"@types/react-dom": "^16.9.4",
"@types/react-test-renderer": "^16.8.0",
"@types/resize-observer-browser": "^0.1.7",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
"react-test-renderer": ">=16.8.0",
Expand Down
58 changes: 46 additions & 12 deletions packages/slate-react/src/components/editable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import {
EDITOR_TO_ELEMENT,
EDITOR_TO_FORCE_RENDER,
EDITOR_TO_PENDING_INSERTION_MARKS,
EDITOR_TO_STYLE_ELEMENT,
EDITOR_TO_USER_MARKS,
EDITOR_TO_USER_SELECTION,
EDITOR_TO_WINDOW,
Expand All @@ -76,6 +77,9 @@ const Children = (props: Parameters<typeof useChildren>[0]) => (
<React.Fragment>{useChildren(props)}</React.Fragment>
)

// The number of Editable components currently mounted.
let mountedCount = 0

/**
* `RenderElementProps` are passed to the `renderElement` handler.
*/
Expand Down Expand Up @@ -802,6 +806,46 @@ export const Editable = (props: EditableProps) => {
})
})

useEffect(() => {
mountedCount++

if (mountedCount === 1) {
// Set global default styles for editors.
const defaultStylesElement = document.createElement('style')
defaultStylesElement.setAttribute('data-slate-default-styles', 'true')
defaultStylesElement.innerHTML =
// :where is used to give these rules lower specificity so user stylesheets can override them.
`:where([data-slate-editor]) {` +
// Allow positioning relative to the editable element.
`position: relative;` +
// Prevent the default outline styles.
`outline: none;` +
// Preserve adjacent whitespace and new lines.
`white-space: pre-wrap;` +
// Allow words to break if they are too long.
`word-wrap: break-word;` +
`}`
document.head.appendChild(defaultStylesElement)
}

return () => {
mountedCount--

if (mountedCount <= 0)
document.querySelector('style[data-slate-default-styles]')?.remove()
}
}, [])

useEffect(() => {
const styleElement = document.createElement('style')
document.head.appendChild(styleElement)
EDITOR_TO_STYLE_ELEMENT.set(editor, styleElement)
return () => {
styleElement.remove()
EDITOR_TO_STYLE_ELEMENT.delete(editor)
}
}, [])

return (
<ReadOnlyContext.Provider value={readOnly}>
<DecorateContext.Provider value={decorate}>
Expand Down Expand Up @@ -831,6 +875,7 @@ export const Editable = (props: EditableProps) => {
: 'false'
}
data-slate-editor
data-slate-editor-id={editor.id}
data-slate-node="value"
// explicitly set this
contentEditable={!readOnly}
Expand All @@ -840,18 +885,7 @@ export const Editable = (props: EditableProps) => {
zindex={-1}
suppressContentEditableWarning
ref={ref}
style={{
// Allow positioning relative to the editable element.
position: 'relative',
// Prevent the default outline styles.
outline: 'none',
// Preserve adjacent whitespace and new lines.
whiteSpace: 'pre-wrap',
// Allow words to break if they are too long.
wordWrap: 'break-word',
// Allow for passed-in styles to override anything.
...style,
}}
style={style}
onBeforeInput={useCallback(
(event: React.FormEvent<HTMLDivElement>) => {
// COMPAT: Certain browsers don't support the `beforeinput` event, so we
Expand Down
49 changes: 42 additions & 7 deletions packages/slate-react/src/components/leaf.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import String from './string'
import {
PLACEHOLDER_SYMBOL,
EDITOR_TO_PLACEHOLDER_ELEMENT,
EDITOR_TO_STYLE_ELEMENT,
} from '../utils/weak-maps'
import { RenderLeafProps, RenderPlaceholderProps } from './editable'
import { useSlateStatic } from '../hooks/use-slate-static'
import { ReactEditor } from '..'

/**
* Individual leaves in a text node with unique formatting.
Expand All @@ -33,19 +33,54 @@ const Leaf = (props: {
const placeholderRef = useRef<HTMLSpanElement | null>(null)
const editor = useSlateStatic()

const placeholderResizeObserver = useRef<ResizeObserver | null>(null)

useEffect(() => {
return () => {
if (placeholderResizeObserver.current) {
placeholderResizeObserver.current.disconnect()
}
}
}, [])

useEffect(() => {
const placeholderEl = placeholderRef?.current
const editorEl = ReactEditor.toDOMNode(editor, editor)

if (!placeholderEl || !editorEl) {
return
if (placeholderEl) {
EDITOR_TO_PLACEHOLDER_ELEMENT.set(editor, placeholderEl)
} else {
EDITOR_TO_PLACEHOLDER_ELEMENT.delete(editor)
}

editorEl.style.minHeight = `${placeholderEl.clientHeight}px`
EDITOR_TO_PLACEHOLDER_ELEMENT.set(editor, placeholderEl)
if (placeholderResizeObserver.current) {
// Update existing observer.
placeholderResizeObserver.current.disconnect()
if (placeholderEl)
placeholderResizeObserver.current.observe(placeholderEl)
} else if (placeholderEl) {
// Create a new observer and observe the placeholder element.
placeholderResizeObserver.current = new ResizeObserver(([{ target }]) => {
const styleElement = EDITOR_TO_STYLE_ELEMENT.get(editor)
if (styleElement) {
// Make the min-height the height of the placeholder.
const minHeight = `${target.clientHeight}px`
styleElement.innerHTML = `:where([data-slate-editor-id="${editor.id}"]) { min-height: ${minHeight}; }`
}
})

placeholderResizeObserver.current.observe(placeholderEl)
}

if (!placeholderEl) {
// No placeholder element, so no need for a resize observer.
const styleElement = EDITOR_TO_STYLE_ELEMENT.get(editor)
if (styleElement) {
// No min-height if there is no placeholder.
styleElement.innerHTML = ''
}
}

return () => {
editorEl.style.minHeight = 'auto'
EDITOR_TO_PLACEHOLDER_ELEMENT.delete(editor)
}
}, [placeholderRef, leaf])
Expand Down
4 changes: 4 additions & 0 deletions packages/slate-react/src/utils/weak-maps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ export const EDITOR_TO_KEY_TO_ELEMENT: WeakMap<
Editor,
WeakMap<Key, HTMLElement>
> = new WeakMap()
export const EDITOR_TO_STYLE_ELEMENT: WeakMap<
Editor,
HTMLStyleElement
> = new WeakMap()

/**
* Weak maps for storing editor-related state.
Expand Down
8 changes: 8 additions & 0 deletions packages/slate-react/test/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@ const createNodeMock = () => ({
getRootNode: () => global.document,
})

class MockResizeObserver {
observe() {}
unobserve() {}
disconnect() {}
}

describe('slate-react', () => {
window.ResizeObserver = MockResizeObserver as any

describe('Editable', () => {
describe('NODE_TO_KEY logic', () => {
it('should not unmount the node that gets split on a split_node operation', async () => {
Expand Down
3 changes: 3 additions & 0 deletions packages/slate/src/create-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
import { DIRTY_PATHS, DIRTY_PATH_KEYS, FLUSHING } from './utils/weak-maps'
import { TextUnit } from './interfaces/types'

let nextEditorId = 0

/**
* Create a new Slate `Editor` object.
*/
Expand All @@ -26,6 +28,7 @@ export const createEditor = (): Editor => {
operations: [],
selection: null,
marks: null,
id: nextEditorId++,
isInline: () => false,
isVoid: () => false,
markableVoid: () => false,
Expand Down
1 change: 1 addition & 0 deletions packages/slate/src/interfaces/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export interface BaseEditor {
selection: Selection
operations: Operation[]
marks: EditorMarks | null
readonly id: number

// Schema-specific node behaviors.
isInline: (element: Element) => boolean
Expand Down