Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions lit-inline-menu/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
dist
.next
.svelte-kit
15 changes: 15 additions & 0 deletions lit-inline-menu/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# lit-inline-menu

A [ProseKit](https://prosekit.dev) example.

[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/prosekit/examples/tree/master/lit-inline-menu)
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/prosekit/examples/tree/master/lit-inline-menu)

Run the example locally with:

```bash
npx degit prosekit/examples/lit-inline-menu lit-inline-menu
cd lit-inline-menu
npm install
npm run dev
```
13 changes: 13 additions & 0 deletions lit-inline-menu/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ProseKit + Lit</title>
<script type="module" src="/src/app.ts"></script>
</head>

<body>
<my-app></my-app>
</body>
</html>
24 changes: 24 additions & 0 deletions lit-inline-menu/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "example-lit-inline-menu",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "tsc && vite build",
"dev": "vite",
"preview": "vite preview"
},
"dependencies": {
"@lit/context": "^1.1.6",
"lit": "^3.3.3",
"prosekit": "^0.21.1"
},
"devDependencies": {
"@egoist/tailwindcss-icons": "^1.9.2",
"@iconify-json/lucide": "^1.2.107",
"@tailwindcss/vite": "^4.3.0",
"tailwindcss": "^4.3.0",
"typescript": "^6.0.3",
"vite": "^8.0.13"
}
}
12 changes: 12 additions & 0 deletions lit-inline-menu/src/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@import 'tailwindcss';

@plugin "@egoist/tailwindcss-icons";

body {
height: 100svh;
display: grid;
max-width: 900px;
padding: 16px;
margin-left: auto;
margin-right: auto;
}
18 changes: 18 additions & 0 deletions lit-inline-menu/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import './app.css'
import './editor'

import { LitElement, html } from 'lit'
import { customElement } from 'lit/decorators.js'

@customElement('my-app')
export class MyApp extends LitElement {
createRenderRoot() {
return this
}

render() {
return html`
<my-editor></my-editor>
`
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import 'prosekit/basic/style.css'
import 'prosekit/basic/typography.css'

import { ContextProvider } from '@lit/context'
import {
html,
LitElement,
type PropertyDeclaration,
type PropertyValues,
} from 'lit'
import { createRef, ref, type Ref } from 'lit/directives/ref.js'
import type { Editor } from 'prosekit/core'
import { createEditor } from 'prosekit/core'

import { sampleContent } from '../../sample/sample-doc-inline-menu'
import { editorContext } from '../../ui/editor-context'
import { registerLitEditorInlineMenu } from '../../ui/inline-menu'

import { defineExtension } from './extension'

export class LitEditor extends LitElement {
static override properties = {
editor: {
state: true,
attribute: false,
} satisfies PropertyDeclaration<Editor>,
}

private editor: Editor
private ref: Ref<HTMLDivElement>
constructor() {
super()

const extension = defineExtension()
this.editor = createEditor({ extension, defaultContent: sampleContent })
this.ref = createRef<HTMLDivElement>()
new ContextProvider(this, {
context: editorContext,
initialValue: this.editor,
})
}

override createRenderRoot() {
return this
}

override disconnectedCallback() {
this.editor.unmount()
super.disconnectedCallback()
}

override updated(changedProperties: PropertyValues) {
super.updated(changedProperties)
this.editor.mount(this.ref.value)
}

override render() {
return html`
<div
class="box-border h-full w-full min-h-36 overflow-y-hidden overflow-x-hidden rounded-md border border-solid border-gray-200 dark:border-gray-700 shadow-sm flex flex-col bg-[canvas] text-black dark:text-white"
>
<div class="relative w-full flex-1 box-border overflow-y-auto">
<div
${ref(this.ref)}
class="ProseMirror box-border min-h-full px-[max(4rem,calc(50%-20rem))] py-8 outline-hidden outline-0 [&_span[data-mention=user]]:text-blue-500 [&_span[data-mention=tag]]:text-violet-500"
></div>
<lit-editor-inline-menu
style="display: contents;"
></lit-editor-inline-menu>
</div>
</div>
`
}
}

export function registerLitEditor() {
registerLitEditorInlineMenu()

if (customElements.get('lit-editor-example-inline-menu')) return
customElements.define('lit-editor-example-inline-menu', LitEditor)
}

declare global {
interface HTMLElementTagNameMap {
'lit-editor-example-inline-menu': LitEditor
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineBasicExtension } from 'prosekit/basic'

export function defineExtension() {
return defineBasicExtension()
}

export type EditorExtension = ReturnType<typeof defineExtension>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { LitEditor as ExampleEditor, registerLitEditor } from './editor'
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { NodeJSON } from 'prosekit/core'

const loremText =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Aliquet nec ullamcorper sit amet risus. Nam aliquam sem et tortor consequat id porta. Interdum posuere lorem ipsum dolor sit amet. Lectus sit amet est placerat in egestas erat. Egestas sed tempus urna et pharetra pharetra. Sit amet cursus sit amet dictum sit amet. Porttitor leo a diam sollicitudin. Tellus orci ac auctor augue. Tellus in hac habitasse platea dictumst vestibulum. At elementum eu facilisis sed odio morbi. Dolor magna eget est lorem ipsum. Et malesuada fames ac turpis egestas. Arcu risus quis varius quam quisque id diam. Purus viverra accumsan in nisl nisi scelerisque eu ultrices. Ut tortor pretium viverra suspendisse potenti nullam ac tortor vitae.'

export const sampleContent: NodeJSON = {
type: 'doc',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
marks: [
{
type: 'bold',
},
],
text: 'Try to select some text',
},
],
},
...Array.from({ length: 10 }, () => ({
type: 'paragraph' as const,
content: [
{
type: 'text' as const,
text: loremText,
},
],
})),
],
}
92 changes: 92 additions & 0 deletions lit-inline-menu/src/components/editor/ui/button/button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { html, LitElement, nothing, type PropertyDeclaration } from 'lit'
import {
registerTooltipPopupElement,
registerTooltipPositionerElement,
registerTooltipRootElement,
registerTooltipTriggerElement,
} from 'prosekit/lit/tooltip'

class LitButton extends LitElement {
static override properties = {
pressed: { type: Boolean },
disabled: { type: Boolean },
tooltip: { type: String },
icon: { type: String },
} satisfies Record<string, PropertyDeclaration>

pressed = false
disabled = false
tooltip = ''
icon = ''

override createRenderRoot() {
return this
}

override connectedCallback() {
super.connectedCallback()
this.classList.add('contents')
}

private handleMouseDown = (event: MouseEvent) => {
// Prevent the editor from being blurred when the button is clicked
event.preventDefault()
}

override render() {
const tooltip = this.tooltip

return html`
<prosekit-tooltip-root>
<prosekit-tooltip-trigger class="block">
<button
data-state=${this.pressed ? 'on' : 'off'}
class="outline-unset focus-visible:outline-unset flex items-center justify-center rounded-md p-2 font-medium transition focus-visible:ring-2 text-sm focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 disabled:pointer-events-none min-w-9 min-h-9 text-gray-900 dark:text-gray-50 disabled:text-gray-900/50 dark:disabled:text-gray-50/50 bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 data-[state=on]:bg-gray-200 dark:data-[state=on]:bg-gray-700"
?disabled=${this.disabled}
@mousedown=${this.handleMouseDown}
>
${this.icon
? html`
<div class="${this.icon}"></div>
`
: nothing}
${tooltip
? html`
<span class="sr-only">${tooltip}</span>
`
: nothing}
</button>
</prosekit-tooltip-trigger>
${tooltip
? html`
<prosekit-tooltip-positioner
class="block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none"
>
<prosekit-tooltip-popup
class="flex box-border origin-(--transform-origin) transition-[opacity,scale] transition-discrete motion-reduce:transition-none duration-100 data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 overflow-hidden rounded-md border border-solid bg-gray-900 dark:bg-gray-50 px-3 py-1.5 text-xs text-gray-50 dark:text-gray-900 shadow-xs text-nowrap"
>
${tooltip}
</prosekit-tooltip-popup>
</prosekit-tooltip-positioner>
`
: nothing}
</prosekit-tooltip-root>
`
}
}

export function registerLitEditorButton() {
registerTooltipPopupElement()
registerTooltipPositionerElement()
registerTooltipRootElement()
registerTooltipTriggerElement()

if (customElements.get('lit-editor-button')) return
customElements.define('lit-editor-button', LitButton)
}

declare global {
interface HTMLElementTagNameMap {
'lit-editor-button': LitButton
}
}
1 change: 1 addition & 0 deletions lit-inline-menu/src/components/editor/ui/button/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { registerLitEditorButton } from './button'
6 changes: 6 additions & 0 deletions lit-inline-menu/src/components/editor/ui/editor-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createContext } from '@lit/context'
import type { Editor } from 'prosekit/core'

export const editorContext = createContext<Editor | undefined>(
'prosekit-editor',
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { registerLitEditorInlineMenu } from './inline-menu'
Loading
Loading