mirror of
https://github.com/nuxt/ui.git
synced 2026-09-14 19:51:10 +08:00
d09668d75e
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
113 lines
5.3 KiB
TypeScript
113 lines
5.3 KiB
TypeScript
import type { ModuleOptions } from '../module'
|
|
|
|
// Re-scope each `[&_…]` descendant selector to `[&_:where(.ProseMirror_…)]`; other tokens (and
|
|
// falsy entries) pass through. The `(?=:)` lookahead matches the arbitrary-variant's closing `]`
|
|
// only, so `[…]` inside a utility value (e.g. `content-[attr()]`) is left untouched.
|
|
const prose = (...classes: Array<string | false | undefined>) => classes.map(cls => cls && cls.replace(/\[&_(.+?)\](?=:)/g, '[&_:where(.ProseMirror_$1)]'))
|
|
|
|
export default (options: Required<ModuleOptions>) => {
|
|
// Typography descendant utilities; used verbatim on `base` and re-scoped onto `content` via `prose()`.
|
|
const typography = [
|
|
// Paragraph
|
|
'[&_p]:leading-7',
|
|
// Links
|
|
'[&_a]:text-primary [&_a]:border-b [&_a]:border-transparent [&_a]:hover:border-primary [&_a]:font-medium',
|
|
options.theme?.transitions && '[&_a]:transition-colors',
|
|
// Code inside links
|
|
'[&_a>code]:border-dashed [&_a:hover>code]:border-primary [&_a:hover>code]:text-primary',
|
|
options.theme?.transitions && '[&_a>code]:transition-colors',
|
|
// Mentions
|
|
'[&_.mention]:text-primary [&_.mention]:font-medium',
|
|
// Headings - shared styles
|
|
'[&_:is(h1,h2,h3,h4,h5,h6)]:text-highlighted [&_:is(h1,h2,h3,h4,h5,h6)]:font-bold',
|
|
// Headings - unique styles
|
|
'[&_h1]:text-3xl',
|
|
'[&_h2]:text-2xl',
|
|
'[&_h3]:text-xl',
|
|
'[&_h4]:text-lg',
|
|
'[&_h5]:text-base',
|
|
'[&_h6]:text-base',
|
|
// Code inside headings
|
|
'[&_:is(h1,h2,h3,h4,h5,h6)>code]:border-dashed [&_:is(h1,h2,h3,h4,h5,h6)>code]:font-bold',
|
|
'[&_h2>code]:text-xl/6',
|
|
'[&_h3>code]:text-lg/5',
|
|
// Blockquote & HR
|
|
'[&_blockquote]:border-s-4 [&_blockquote]:border-accented [&_blockquote]:ps-4 [&_blockquote]:italic',
|
|
'[&_[data-type=horizontalRule]]:my-8 [&_[data-type=horizontalRule]]:py-2',
|
|
'[&_hr]:border-t [&_hr]:border-default',
|
|
// Code blocks
|
|
'[&_pre]:text-sm/6 [&_pre]:border [&_pre]:border-muted [&_pre]:bg-muted [&_pre]:rounded-md [&_pre]:px-4 [&_pre]:py-3 [&_pre]:whitespace-pre-wrap [&_pre]:break-words [&_pre]:overflow-x-auto',
|
|
'[&_pre_code]:p-0 [&_pre_code]:text-inherit [&_pre_code]:font-inherit [&_pre_code]:rounded-none [&_pre_code]:inline [&_pre_code]:border-0 [&_pre_code]:bg-transparent',
|
|
// Inline code
|
|
'[&_code]:px-1.5 [&_code]:py-0.5 [&_code]:text-sm [&_code]:font-mono [&_code]:font-medium [&_code]:rounded-md [&_code]:inline-block [&_code]:border [&_code]:border-muted [&_code]:text-highlighted [&_code]:bg-muted',
|
|
// Lists
|
|
'[&_:is(ul,ol)]:ps-6',
|
|
'[&_ul]:list-disc [&_ul]:marker:text-(--ui-border-accented)',
|
|
'[&_ol]:list-decimal [&_ol]:marker:text-muted',
|
|
'[&_li]:my-1.5 [&_li]:ps-1.5',
|
|
// Images
|
|
'[&_img]:rounded-md [&_img]:block [&_img]:max-w-full [&_img.ProseMirror-selectednode]:outline-2 [&_img.ProseMirror-selectednode]:outline-primary',
|
|
// Selected nodes
|
|
'[&_.ProseMirror-selectednode:not(img):not(pre):not([data-node-view-wrapper])]:bg-primary/20'
|
|
]
|
|
|
|
// Placeholder decorations; the `.is-editor-empty` / `.is-empty` / `data-placeholder` hooks come from
|
|
// the Placeholder extension. Used verbatim on `base` and re-scoped onto `content` via `prose()`.
|
|
const placeholder = {
|
|
firstLine: '[&_:is(p,h1,h2,h3,h4,h5,h6).is-editor-empty:first-child]:before:content-[attr(data-placeholder)] [&_:is(p,h1,h2,h3,h4,h5,h6).is-editor-empty:first-child]:before:text-dimmed [&_:is(p,h1,h2,h3,h4,h5,h6).is-editor-empty:first-child]:before:float-start [&_:is(p,h1,h2,h3,h4,h5,h6).is-editor-empty:first-child]:before:h-0 [&_:is(p,h1,h2,h3,h4,h5,h6).is-editor-empty:first-child]:before:pointer-events-none',
|
|
everyLine: '[&_:is(p,h1,h2,h3,h4,h5,h6).is-empty]:before:content-[attr(data-placeholder)] [&_:is(p,h1,h2,h3,h4,h5,h6).is-empty]:before:text-dimmed [&_:is(p,h1,h2,h3,h4,h5,h6).is-empty]:before:float-start [&_:is(p,h1,h2,h3,h4,h5,h6).is-empty]:before:h-0 [&_:is(p,h1,h2,h3,h4,h5,h6).is-empty]:before:pointer-events-none'
|
|
}
|
|
|
|
return {
|
|
slots: {
|
|
root: '',
|
|
// Classes of the editable element itself, injected via `editorProps` (built-in editor only).
|
|
base: [
|
|
'w-full outline-none *:my-5 *:first:mt-0 *:last:mb-0 sm:px-8 selection:bg-primary/20',
|
|
...typography
|
|
],
|
|
content: 'relative size-full flex-1'
|
|
},
|
|
variants: {
|
|
// `content`-wrapper copy of the typography — scoped to `.ProseMirror` (excludes the appended
|
|
// menus), `:where()`-wrapped (zero specificity). Enabled by the component for external editors
|
|
// only; the built-in editor keeps the un-scoped copy on `base`.
|
|
prose: {
|
|
true: {
|
|
content: prose(...typography)
|
|
}
|
|
},
|
|
placeholderMode: {
|
|
firstLine: {
|
|
base: placeholder.firstLine
|
|
},
|
|
everyLine: {
|
|
base: placeholder.everyLine
|
|
}
|
|
}
|
|
},
|
|
// Placeholder re-scoped onto the content wrapper for external editors (which don't get `base` via
|
|
// `editorProps`); gated on `prose` so it follows the same opt-out.
|
|
compoundVariants: [
|
|
{
|
|
prose: true,
|
|
placeholderMode: 'firstLine',
|
|
class: {
|
|
content: prose(placeholder.firstLine)
|
|
}
|
|
},
|
|
{
|
|
prose: true,
|
|
placeholderMode: 'everyLine',
|
|
class: {
|
|
content: prose(placeholder.everyLine)
|
|
}
|
|
}
|
|
],
|
|
defaultVariants: {
|
|
prose: true,
|
|
placeholderMode: 'everyLine'
|
|
}
|
|
}
|
|
}
|