prosekit-registry 0.0.15 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/r/lit-ui-button.json +1 -1
  2. package/dist/r/lit-ui-image-upload-popover.json +1 -1
  3. package/dist/r/lit-ui-slash-menu.json +1 -1
  4. package/dist/r/lit-ui-table-handle.json +1 -1
  5. package/dist/r/preact-example-text-color.json +1 -1
  6. package/dist/r/preact-ui-block-handle.json +1 -1
  7. package/dist/r/preact-ui-button.json +1 -1
  8. package/dist/r/preact-ui-image-upload-popover.json +1 -1
  9. package/dist/r/preact-ui-inline-menu.json +1 -1
  10. package/dist/r/preact-ui-slash-menu.json +1 -1
  11. package/dist/r/preact-ui-table-handle.json +1 -1
  12. package/dist/r/preact-ui-tag-menu.json +1 -1
  13. package/dist/r/preact-ui-user-menu.json +1 -1
  14. package/dist/r/react-example-notion.json +2 -2
  15. package/dist/r/react-example-text-color.json +1 -1
  16. package/dist/r/react-ui-block-handle.json +1 -1
  17. package/dist/r/react-ui-button.json +1 -1
  18. package/dist/r/react-ui-image-upload-popover.json +1 -1
  19. package/dist/r/react-ui-inline-menu.json +1 -1
  20. package/dist/r/react-ui-slash-menu.json +1 -1
  21. package/dist/r/react-ui-table-handle.json +1 -1
  22. package/dist/r/react-ui-tag-menu.json +1 -1
  23. package/dist/r/react-ui-user-menu.json +1 -1
  24. package/dist/r/solid-example-text-color.json +1 -1
  25. package/dist/r/solid-ui-block-handle.json +1 -1
  26. package/dist/r/solid-ui-button.json +1 -1
  27. package/dist/r/solid-ui-image-upload-popover.json +1 -1
  28. package/dist/r/solid-ui-inline-menu.json +1 -1
  29. package/dist/r/solid-ui-slash-menu.json +1 -1
  30. package/dist/r/solid-ui-table-handle.json +1 -1
  31. package/dist/r/solid-ui-tag-menu.json +1 -1
  32. package/dist/r/solid-ui-user-menu.json +1 -1
  33. package/dist/r/svelte-example-text-color.json +1 -1
  34. package/dist/r/svelte-ui-block-handle.json +1 -1
  35. package/dist/r/svelte-ui-button.json +1 -1
  36. package/dist/r/svelte-ui-image-upload-popover.json +1 -1
  37. package/dist/r/svelte-ui-inline-menu.json +1 -1
  38. package/dist/r/svelte-ui-slash-menu.json +1 -1
  39. package/dist/r/svelte-ui-table-handle.json +1 -1
  40. package/dist/r/svelte-ui-tag-menu.json +1 -1
  41. package/dist/r/svelte-ui-user-menu.json +1 -1
  42. package/dist/r/vanilla-ui-slash-menu.json +1 -1
  43. package/dist/r/vue-example-text-color.json +1 -1
  44. package/dist/r/vue-ui-block-handle.json +1 -1
  45. package/dist/r/vue-ui-button.json +1 -1
  46. package/dist/r/vue-ui-image-upload-popover.json +1 -1
  47. package/dist/r/vue-ui-inline-menu.json +1 -1
  48. package/dist/r/vue-ui-slash-menu.json +1 -1
  49. package/dist/r/vue-ui-table-handle.json +1 -1
  50. package/dist/r/vue-ui-tag-menu.json +1 -1
  51. package/dist/r/vue-ui-user-menu.json +1 -1
  52. package/package.json +3 -3
  53. package/dist/package.json +0 -7
@@ -12,7 +12,7 @@
12
12
  "path": "registry/src/lit/ui/button/button.ts",
13
13
  "type": "registry:component",
14
14
  "target": "components/editor/ui/button/button.ts",
15
- "content": "import 'prosekit/lit/tooltip'\n\nimport { html, LitElement, nothing, type PropertyDeclaration } from 'lit'\n\nclass LitButton extends LitElement {\n static override properties = {\n pressed: { type: Boolean },\n disabled: { type: Boolean },\n tooltip: { type: String },\n icon: { type: String },\n } satisfies Record<string, PropertyDeclaration>\n\n pressed = false\n disabled = false\n tooltip = ''\n icon = ''\n\n override createRenderRoot() {\n return this\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this.classList.add('contents')\n }\n\n private handleMouseDown = (event: MouseEvent) => {\n // Prevent the editor from being blurred when the button is clicked\n event.preventDefault()\n }\n\n override render() {\n const tooltip = this.tooltip\n\n return html`\n <prosekit-tooltip-root>\n <prosekit-tooltip-trigger class=\"block\">\n <button\n data-state=${this.pressed ? 'on' : 'off'}\n 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\"\n ?disabled=${this.disabled}\n @mousedown=${this.handleMouseDown}\n >\n ${this.icon ? html`<div class=\"${this.icon}\"></div>` : nothing}\n ${tooltip ? html`<span class=\"sr-only\">${tooltip}</span>` : nothing}\n </button>\n </prosekit-tooltip-trigger>\n ${tooltip\n ? html`\n <prosekit-tooltip-positioner class=\"block overflow-visible bg-transparent w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <prosekit-tooltip-popup class=\"flex box-border origin-(--transform-origin) transition 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\">\n ${tooltip}\n </prosekit-tooltip-popup>\n </prosekit-tooltip-positioner>\n `\n : nothing}\n </prosekit-tooltip-root>\n `\n }\n}\n\ncustomElements.define('lit-editor-button', LitButton)\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'lit-editor-button': LitButton\n }\n}\n"
15
+ "content": "import 'prosekit/lit/tooltip'\n\nimport { html, LitElement, nothing, type PropertyDeclaration } from 'lit'\n\nclass LitButton extends LitElement {\n static override properties = {\n pressed: { type: Boolean },\n disabled: { type: Boolean },\n tooltip: { type: String },\n icon: { type: String },\n } satisfies Record<string, PropertyDeclaration>\n\n pressed = false\n disabled = false\n tooltip = ''\n icon = ''\n\n override createRenderRoot() {\n return this\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this.classList.add('contents')\n }\n\n private handleMouseDown = (event: MouseEvent) => {\n // Prevent the editor from being blurred when the button is clicked\n event.preventDefault()\n }\n\n override render() {\n const tooltip = this.tooltip\n\n return html`\n <prosekit-tooltip-root>\n <prosekit-tooltip-trigger class=\"block\">\n <button\n data-state=${this.pressed ? 'on' : 'off'}\n 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\"\n ?disabled=${this.disabled}\n @mousedown=${this.handleMouseDown}\n >\n ${this.icon ? html`<div class=\"${this.icon}\"></div>` : nothing}\n ${tooltip ? html`<span class=\"sr-only\">${tooltip}</span>` : nothing}\n </button>\n </prosekit-tooltip-trigger>\n ${tooltip\n ? html`\n <prosekit-tooltip-positioner class=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <prosekit-tooltip-popup class=\"flex box-border origin-(--transform-origin) transition 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\">\n ${tooltip}\n </prosekit-tooltip-popup>\n </prosekit-tooltip-positioner>\n `\n : nothing}\n </prosekit-tooltip-root>\n `\n }\n}\n\ncustomElements.define('lit-editor-button', LitButton)\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'lit-editor-button': LitButton\n }\n}\n"
16
16
  },
17
17
  {
18
18
  "path": "registry/src/lit/ui/button/index.ts",
@@ -14,7 +14,7 @@
14
14
  "path": "registry/src/lit/ui/image-upload-popover/image-upload-popover.ts",
15
15
  "type": "registry:component",
16
16
  "target": "components/editor/ui/image-upload-popover/image-upload-popover.ts",
17
- "content": "import '../button/index'\n\nimport { html, LitElement, nothing, type PropertyDeclaration } from 'lit'\nimport type { Editor } from 'prosekit/core'\nimport type { Uploader } from 'prosekit/extensions/file'\nimport type { ImageExtension } from 'prosekit/extensions/image'\nimport type { OpenChangeEvent } from 'prosekit/lit/popover'\n\nlet imageUploadId = 0\n\nclass LitImageUploadPopover extends LitElement {\n static override properties = {\n editor: { attribute: false } satisfies PropertyDeclaration<Editor>,\n uploader: { attribute: false } satisfies PropertyDeclaration<Uploader<string>>,\n tooltip: { type: String },\n disabled: { type: Boolean },\n icon: { type: String },\n }\n\n editor?: Editor<ImageExtension>\n uploader?: Uploader<string>\n tooltip = ''\n disabled = false\n icon = ''\n\n private open = false\n private url = ''\n private file: File | null = null\n private ariaId = `lit-image-upload-${imageUploadId++}`\n\n override createRenderRoot() {\n return this\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this.classList.add('contents')\n }\n\n private handleOpenChange = (event: OpenChangeEvent) => {\n if (!event.detail) {\n this.deferResetState()\n }\n\n this.open = event.detail\n this.requestUpdate()\n }\n\n private handleFileChange = (event: Event) => {\n const target = event.target as HTMLInputElement\n const selectedFile = target.files?.[0]\n\n if (selectedFile) {\n this.file = selectedFile\n this.url = ''\n } else {\n this.file = null\n }\n\n this.requestUpdate()\n }\n\n private handleUrlChange = (event: Event) => {\n const target = event.target as HTMLInputElement\n const inputUrl = target.value\n\n if (inputUrl) {\n this.url = inputUrl\n this.file = null\n } else {\n this.url = ''\n }\n\n this.requestUpdate()\n }\n\n private deferResetState() {\n setTimeout(() => {\n this.url = ''\n this.file = null\n this.requestUpdate()\n }, 300)\n }\n\n private handleSubmit = () => {\n const editor = this.editor\n if (!editor) return\n\n if (this.url) {\n editor.commands.insertImage({ src: this.url })\n } else if (this.file && this.uploader) {\n editor.commands.uploadImage({ file: this.file, uploader: this.uploader })\n }\n\n this.open = false\n this.deferResetState()\n this.requestUpdate()\n }\n\n override render() {\n return html`\n <prosekit-popover-root .open=${this.open} @open-change=${this.handleOpenChange}>\n <prosekit-popover-trigger>\n <lit-editor-button\n .pressed=${this.open}\n .disabled=${this.disabled}\n .tooltip=${this.tooltip}\n .icon=${this.icon}\n ></lit-editor-button>\n </prosekit-popover-trigger>\n\n <prosekit-popover-positioner placement=\"bottom\" class=\"block overflow-visible bg-transparent w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <prosekit-popover-popup class=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg flex flex-col gap-y-4 p-6 text-sm w-sm\">\n ${!this.file\n ? html`\n <label for=\"id-link-${this.ariaId}\">Embed Link</label>\n <input\n id=\"id-link-${this.ariaId}\"\n class=\"flex h-9 rounded-md w-full bg-white dark:bg-gray-950 px-3 py-2 text-sm placeholder:text-gray-500 dark:placeholder:text-gray-500 transition border box-border border-gray-200 dark:border-gray-800 border-solid ring-0 ring-transparent focus-visible:ring-2 focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 focus-visible:ring-offset-0 outline-hidden focus-visible:outline-hidden file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:cursor-not-allowed disabled:opacity-50\"\n placeholder=\"Paste the image link...\"\n type=\"url\"\n .value=${this.url}\n @input=${this.handleUrlChange}\n />\n `\n : nothing}\n ${!this.url\n ? html`\n <label for=\"id-upload-${this.ariaId}\">Upload</label>\n <input\n id=\"id-upload-${this.ariaId}\"\n class=\"flex h-9 rounded-md w-full bg-white dark:bg-gray-950 px-3 py-2 text-sm placeholder:text-gray-500 dark:placeholder:text-gray-500 transition border box-border border-gray-200 dark:border-gray-800 border-solid ring-0 ring-transparent focus-visible:ring-2 focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 focus-visible:ring-offset-0 outline-hidden focus-visible:outline-hidden file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:cursor-not-allowed disabled:opacity-50\"\n accept=\"image/*\"\n type=\"file\"\n @change=${this.handleFileChange}\n />\n `\n : nothing}\n ${this.url\n ? html`\n <button class=\"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white dark:ring-offset-gray-950 transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border-0 bg-gray-900 dark:bg-gray-50 text-gray-50 dark:text-gray-900 hover:bg-gray-900/90 dark:hover:bg-gray-50/90 h-10 px-4 py-2 w-full\" @click=${this.handleSubmit}>\n Insert Image\n </button>\n `\n : nothing}\n ${this.file\n ? html`\n <button class=\"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white dark:ring-offset-gray-950 transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border-0 bg-gray-900 dark:bg-gray-50 text-gray-50 dark:text-gray-900 hover:bg-gray-900/90 dark:hover:bg-gray-50/90 h-10 px-4 py-2 w-full\" @click=${this.handleSubmit}>\n Upload Image\n </button>\n `\n : nothing}\n </prosekit-popover-popup>\n </prosekit-popover-positioner>\n </prosekit-popover-root>\n `\n }\n}\n\ncustomElements.define('lit-editor-image-upload-popover', LitImageUploadPopover)\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'lit-editor-image-upload-popover': LitImageUploadPopover\n }\n}\n"
17
+ "content": "import '../button/index'\n\nimport { html, LitElement, nothing, type PropertyDeclaration } from 'lit'\nimport type { Editor } from 'prosekit/core'\nimport type { Uploader } from 'prosekit/extensions/file'\nimport type { ImageExtension } from 'prosekit/extensions/image'\nimport type { OpenChangeEvent } from 'prosekit/lit/popover'\n\nlet imageUploadId = 0\n\nclass LitImageUploadPopover extends LitElement {\n static override properties = {\n editor: { attribute: false } satisfies PropertyDeclaration<Editor>,\n uploader: { attribute: false } satisfies PropertyDeclaration<Uploader<string>>,\n tooltip: { type: String },\n disabled: { type: Boolean },\n icon: { type: String },\n }\n\n editor?: Editor<ImageExtension>\n uploader?: Uploader<string>\n tooltip = ''\n disabled = false\n icon = ''\n\n private open = false\n private url = ''\n private file: File | null = null\n private ariaId = `lit-image-upload-${imageUploadId++}`\n\n override createRenderRoot() {\n return this\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this.classList.add('contents')\n }\n\n private handleOpenChange = (event: OpenChangeEvent) => {\n if (!event.detail) {\n this.deferResetState()\n }\n\n this.open = event.detail\n this.requestUpdate()\n }\n\n private handleFileChange = (event: Event) => {\n const target = event.target as HTMLInputElement\n const selectedFile = target.files?.[0]\n\n if (selectedFile) {\n this.file = selectedFile\n this.url = ''\n } else {\n this.file = null\n }\n\n this.requestUpdate()\n }\n\n private handleUrlChange = (event: Event) => {\n const target = event.target as HTMLInputElement\n const inputUrl = target.value\n\n if (inputUrl) {\n this.url = inputUrl\n this.file = null\n } else {\n this.url = ''\n }\n\n this.requestUpdate()\n }\n\n private deferResetState() {\n setTimeout(() => {\n this.url = ''\n this.file = null\n this.requestUpdate()\n }, 300)\n }\n\n private handleSubmit = () => {\n const editor = this.editor\n if (!editor) return\n\n if (this.url) {\n editor.commands.insertImage({ src: this.url })\n } else if (this.file && this.uploader) {\n editor.commands.uploadImage({ file: this.file, uploader: this.uploader })\n }\n\n this.open = false\n this.deferResetState()\n this.requestUpdate()\n }\n\n override render() {\n return html`\n <prosekit-popover-root .open=${this.open} @open-change=${this.handleOpenChange}>\n <prosekit-popover-trigger>\n <lit-editor-button\n .pressed=${this.open}\n .disabled=${this.disabled}\n .tooltip=${this.tooltip}\n .icon=${this.icon}\n ></lit-editor-button>\n </prosekit-popover-trigger>\n\n <prosekit-popover-positioner placement=\"bottom\" class=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <prosekit-popover-popup class=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg flex flex-col gap-y-4 p-6 text-sm w-sm\">\n ${!this.file\n ? html`\n <label for=\"id-link-${this.ariaId}\">Embed Link</label>\n <input\n id=\"id-link-${this.ariaId}\"\n class=\"flex h-9 rounded-md w-full bg-white dark:bg-gray-950 px-3 py-2 text-sm placeholder:text-gray-500 dark:placeholder:text-gray-500 transition border box-border border-gray-200 dark:border-gray-800 border-solid ring-0 ring-transparent focus-visible:ring-2 focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 focus-visible:ring-offset-0 outline-hidden focus-visible:outline-hidden file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:cursor-not-allowed disabled:opacity-50\"\n placeholder=\"Paste the image link...\"\n type=\"url\"\n .value=${this.url}\n @input=${this.handleUrlChange}\n />\n `\n : nothing}\n ${!this.url\n ? html`\n <label for=\"id-upload-${this.ariaId}\">Upload</label>\n <input\n id=\"id-upload-${this.ariaId}\"\n class=\"flex h-9 rounded-md w-full bg-white dark:bg-gray-950 px-3 py-2 text-sm placeholder:text-gray-500 dark:placeholder:text-gray-500 transition border box-border border-gray-200 dark:border-gray-800 border-solid ring-0 ring-transparent focus-visible:ring-2 focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 focus-visible:ring-offset-0 outline-hidden focus-visible:outline-hidden file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:cursor-not-allowed disabled:opacity-50\"\n accept=\"image/*\"\n type=\"file\"\n @change=${this.handleFileChange}\n />\n `\n : nothing}\n ${this.url\n ? html`\n <button class=\"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white dark:ring-offset-gray-950 transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border-0 bg-gray-900 dark:bg-gray-50 text-gray-50 dark:text-gray-900 hover:bg-gray-900/90 dark:hover:bg-gray-50/90 h-10 px-4 py-2 w-full\" @click=${this.handleSubmit}>\n Insert Image\n </button>\n `\n : nothing}\n ${this.file\n ? html`\n <button class=\"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white dark:ring-offset-gray-950 transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border-0 bg-gray-900 dark:bg-gray-50 text-gray-50 dark:text-gray-900 hover:bg-gray-900/90 dark:hover:bg-gray-50/90 h-10 px-4 py-2 w-full\" @click=${this.handleSubmit}>\n Upload Image\n </button>\n `\n : nothing}\n </prosekit-popover-popup>\n </prosekit-popover-positioner>\n </prosekit-popover-root>\n `\n }\n}\n\ncustomElements.define('lit-editor-image-upload-popover', LitImageUploadPopover)\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'lit-editor-image-upload-popover': LitImageUploadPopover\n }\n}\n"
18
18
  },
19
19
  {
20
20
  "path": "registry/src/lit/ui/image-upload-popover/index.ts",
@@ -33,7 +33,7 @@
33
33
  "path": "registry/src/lit/ui/slash-menu/slash-menu.ts",
34
34
  "type": "registry:component",
35
35
  "target": "components/editor/ui/slash-menu/slash-menu.ts",
36
- "content": "import 'prosekit/lit/autocomplete'\n\nimport { ContextConsumer } from '@lit/context'\nimport { html, LitElement } from 'lit'\nimport type { BasicExtension } from 'prosekit/basic'\nimport type { Editor } from 'prosekit/core'\nimport { canUseRegexLookbehind } from 'prosekit/core'\n\nimport { editorContext } from '../editor-context'\n\n// Match inputs like \"/\", \"/table\", \"/heading 1\" etc. Do not match \"/ heading\".\nconst regex = canUseRegexLookbehind() ? /(?<!\\S)\\/(\\S.*)?$/u : /\\/(\\S.*)?$/u\n\nclass SlashMenuElement extends LitElement {\n private editorConsumer = new ContextConsumer(this, {\n context: editorContext,\n subscribe: true,\n })\n\n override createRenderRoot() {\n return this\n }\n\n override render() {\n const editor = this.editorConsumer.value as Editor<BasicExtension> | undefined\n if (!editor) {\n return html``\n }\n\n return html`<prosekit-autocomplete-root .editor=${editor} .regex=${regex}>\n <prosekit-autocomplete-positioner class=\"block overflow-visible bg-transparent w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <prosekit-autocomplete-popup class=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg flex flex-col relative max-h-100 min-w-60 select-none overflow-auto whitespace-nowrap p-1\">\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Text\"\n @select=${() => editor.commands.setParagraph()}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Heading 1\"\n kbd=\"#\"\n @select=${() => editor.commands.setHeading({ level: 1 })}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Heading 2\"\n kbd=\"##\"\n @select=${() => editor.commands.setHeading({ level: 2 })}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Heading 3\"\n kbd=\"###\"\n @select=${() => editor.commands.setHeading({ level: 3 })}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Bullet list\"\n kbd=\"-\"\n @select=${() => editor.commands.wrapInList({ kind: 'bullet' })}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Ordered list\"\n kbd=\"1.\"\n @select=${() => editor.commands.wrapInList({ kind: 'ordered' })}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Task list\"\n kbd=\"[]\"\n @select=${() => editor.commands.wrapInList({ kind: 'task' })}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Toggle list\"\n kbd=\">>\"\n @select=${() => editor.commands.wrapInList({ kind: 'toggle' })}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Quote\"\n kbd=\">\"\n @select=${() => editor.commands.setBlockquote()}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Table\"\n @select=${() => editor.commands.insertTable({ row: 3, col: 3 })}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Divider\"\n kbd=\"---\"\n @select=${() => editor.commands.insertHorizontalRule()}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Code\"\n kbd=\"\\`\\`\\`\"\n @select=${() => editor.commands.setCodeBlock()}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-empty class=\"contents\"></lit-editor-slash-menu-empty>\n </prosekit-autocomplete-popup>\n </prosekit-autocomplete-positioner>\n </prosekit-autocomplete-root>`\n }\n}\n\ncustomElements.define('lit-editor-slash-menu', SlashMenuElement)\n"
36
+ "content": "import 'prosekit/lit/autocomplete'\n\nimport { ContextConsumer } from '@lit/context'\nimport { html, LitElement } from 'lit'\nimport type { BasicExtension } from 'prosekit/basic'\nimport type { Editor } from 'prosekit/core'\nimport { canUseRegexLookbehind } from 'prosekit/core'\n\nimport { editorContext } from '../editor-context'\n\n// Match inputs like \"/\", \"/table\", \"/heading 1\" etc. Do not match \"/ heading\".\nconst regex = canUseRegexLookbehind() ? /(?<!\\S)\\/(\\S.*)?$/u : /\\/(\\S.*)?$/u\n\nclass SlashMenuElement extends LitElement {\n private editorConsumer = new ContextConsumer(this, {\n context: editorContext,\n subscribe: true,\n })\n\n override createRenderRoot() {\n return this\n }\n\n override render() {\n const editor = this.editorConsumer.value as Editor<BasicExtension> | undefined\n if (!editor) {\n return html``\n }\n\n return html`<prosekit-autocomplete-root .editor=${editor} .regex=${regex}>\n <prosekit-autocomplete-positioner class=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <prosekit-autocomplete-popup class=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg flex flex-col relative max-h-100 min-w-60 select-none overflow-auto whitespace-nowrap p-1\">\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Text\"\n @select=${() => editor.commands.setParagraph()}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Heading 1\"\n kbd=\"#\"\n @select=${() => editor.commands.setHeading({ level: 1 })}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Heading 2\"\n kbd=\"##\"\n @select=${() => editor.commands.setHeading({ level: 2 })}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Heading 3\"\n kbd=\"###\"\n @select=${() => editor.commands.setHeading({ level: 3 })}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Bullet list\"\n kbd=\"-\"\n @select=${() => editor.commands.wrapInList({ kind: 'bullet' })}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Ordered list\"\n kbd=\"1.\"\n @select=${() => editor.commands.wrapInList({ kind: 'ordered' })}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Task list\"\n kbd=\"[]\"\n @select=${() => editor.commands.wrapInList({ kind: 'task' })}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Toggle list\"\n kbd=\">>\"\n @select=${() => editor.commands.wrapInList({ kind: 'toggle' })}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Quote\"\n kbd=\">\"\n @select=${() => editor.commands.setBlockquote()}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Table\"\n @select=${() => editor.commands.insertTable({ row: 3, col: 3 })}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Divider\"\n kbd=\"---\"\n @select=${() => editor.commands.insertHorizontalRule()}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-item\n class=\"contents\"\n label=\"Code\"\n kbd=\"\\`\\`\\`\"\n @select=${() => editor.commands.setCodeBlock()}\n ></lit-editor-slash-menu-item>\n <lit-editor-slash-menu-empty class=\"contents\"></lit-editor-slash-menu-empty>\n </prosekit-autocomplete-popup>\n </prosekit-autocomplete-positioner>\n </prosekit-autocomplete-root>`\n }\n}\n\ncustomElements.define('lit-editor-slash-menu', SlashMenuElement)\n"
37
37
  }
38
38
  ],
39
39
  "meta": {
@@ -23,7 +23,7 @@
23
23
  "path": "registry/src/lit/ui/table-handle/table-handle.ts",
24
24
  "type": "registry:component",
25
25
  "target": "components/editor/ui/table-handle/table-handle.ts",
26
- "content": "import 'prosekit/lit/table-handle'\nimport 'prosekit/web/menu'\n\nimport { ContextConsumer } from '@lit/context'\nimport { html, LitElement, nothing, type PropertyDeclaration, type PropertyValues } from 'lit'\nimport type { Editor } from 'prosekit/core'\nimport { defineUpdateHandler } from 'prosekit/core'\nimport type { TableExtension } from 'prosekit/extensions/table'\n\nimport { editorContext } from '../editor-context'\n\nfunction getTableHandleState(editor: Editor<TableExtension>) {\n return {\n addTableColumnBefore: {\n canExec: editor.commands.addTableColumnBefore.canExec(),\n command: () => editor.commands.addTableColumnBefore(),\n },\n addTableColumnAfter: {\n canExec: editor.commands.addTableColumnAfter.canExec(),\n command: () => editor.commands.addTableColumnAfter(),\n },\n deleteCellSelection: {\n canExec: editor.commands.deleteCellSelection.canExec(),\n command: () => editor.commands.deleteCellSelection(),\n },\n deleteTableColumn: {\n canExec: editor.commands.deleteTableColumn.canExec(),\n command: () => editor.commands.deleteTableColumn(),\n },\n addTableRowAbove: {\n canExec: editor.commands.addTableRowAbove.canExec(),\n command: () => editor.commands.addTableRowAbove(),\n },\n addTableRowBelow: {\n canExec: editor.commands.addTableRowBelow.canExec(),\n command: () => editor.commands.addTableRowBelow(),\n },\n deleteTableRow: {\n canExec: editor.commands.deleteTableRow.canExec(),\n command: () => editor.commands.deleteTableRow(),\n },\n deleteTable: {\n canExec: editor.commands.deleteTable.canExec(),\n command: () => editor.commands.deleteTable(),\n },\n }\n}\n\nclass LitTableHandle extends LitElement {\n static override properties = {\n dir: { type: String } satisfies PropertyDeclaration<'ltr' | 'rtl'>,\n }\n\n override dir: string = ''\n\n private editorConsumer = new ContextConsumer(this, {\n context: editorContext,\n subscribe: true,\n })\n\n private removeUpdateExtension?: VoidFunction\n\n override createRenderRoot() {\n return this\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this.classList.add('contents')\n this.attachEditorListener()\n }\n\n override disconnectedCallback() {\n this.detachEditorListener()\n super.disconnectedCallback()\n }\n\n override updated(changedProperties: PropertyValues) {\n super.updated(changedProperties)\n this.attachEditorListener()\n }\n\n private attachEditorListener() {\n this.detachEditorListener()\n\n const editor = this.editorConsumer.value\n if (!editor) return\n\n this.removeUpdateExtension = editor.use(defineUpdateHandler(() => this.requestUpdate()))\n }\n\n private detachEditorListener() {\n this.removeUpdateExtension?.()\n this.removeUpdateExtension = undefined\n }\n\n override render() {\n const editor = this.editorConsumer.value as Editor<TableExtension> | undefined\n if (!editor) {\n return nothing\n }\n\n const state = getTableHandleState(editor)\n const placement = this.dir === 'rtl' ? 'right' : 'left'\n\n return html`<prosekit-table-handle-root .editor=${editor}>\n <prosekit-table-handle-drag-preview .editor=${editor}></prosekit-table-handle-drag-preview>\n <prosekit-table-handle-drop-indicator\n .editor=${editor}\n ></prosekit-table-handle-drop-indicator>\n <prosekit-table-handle-column-positioner\n .editor=${editor}\n class=\"block overflow-visible bg-transparent w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\"\n >\n <prosekit-table-handle-column-popup class=\"translate-y-[50%] flex box-border origin-(--transform-origin) transition 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\">\n <prosekit-table-handle-column-menu-root>\n <prosekit-table-handle-column-menu-trigger\n .editor=${editor}\n class=\"h-4.5 w-6 flex items-center box-border justify-center bg-white dark:bg-gray-950 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-400/50 border border-gray-200 dark:border-gray-800 border-solid p-0 transition-colors overflow-clip\"\n >\n <div class=\"i-lucide-grip-horizontal size-5 min-h-5 min-w-5 block\"></div>\n </prosekit-table-handle-column-menu-trigger>\n <prosekit-menu-positioner class=\"overflow-visible bg-transparent\">\n <prosekit-menu-popup class=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg relative flex flex-col max-h-100 min-w-32 select-none overflow-auto whitespace-nowrap p-1 outline-none\">\n ${state.addTableColumnBefore.canExec\n ? html`<prosekit-menu-item\n class=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n @select=${state.addTableColumnBefore.command}\n >\n <span>Insert Left</span>\n </prosekit-menu-item>`\n : nothing}\n ${state.addTableColumnAfter.canExec\n ? html`<prosekit-menu-item\n class=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n @select=${state.addTableColumnAfter.command}\n >\n <span>Insert Right</span>\n </prosekit-menu-item>`\n : nothing}\n ${state.deleteCellSelection.canExec\n ? html`<prosekit-menu-item\n class=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n @select=${state.deleteCellSelection.command}\n >\n <span>Clear Contents</span>\n <span class=\"text-xs tracking-widest text-gray-500 dark:text-gray-500\">Del</span>\n </prosekit-menu-item>`\n : nothing}\n ${state.deleteTableColumn.canExec\n ? html`<prosekit-menu-item\n class=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n @select=${state.deleteTableColumn.command}\n >\n <span>Delete Column</span>\n </prosekit-menu-item>`\n : nothing}\n ${state.deleteTable.canExec\n ? html`<prosekit-menu-item\n class=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n data-danger=\"\"\n @select=${state.deleteTable.command}\n >\n <span>Delete Table</span>\n </prosekit-menu-item>`\n : nothing}\n </prosekit-menu-popup>\n </prosekit-menu-positioner>\n </prosekit-table-handle-column-menu-root>\n </prosekit-table-handle-column-popup>\n </prosekit-table-handle-column-positioner>\n <prosekit-table-handle-row-positioner\n .editor=${editor}\n .placement=${placement}\n class=\"block overflow-visible bg-transparent w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\"\n >\n <prosekit-table-handle-row-popup class=\"ltr:translate-x-[50%] rtl:translate-x-[-50%] flex box-border origin-(--transform-origin) transition 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\">\n <prosekit-table-handle-row-menu-root>\n <prosekit-table-handle-row-menu-trigger\n .editor=${editor}\n class=\"h-6 w-4.5 flex items-center box-border justify-center bg-white dark:bg-gray-950 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-400/50 border border-gray-200 dark:border-gray-800 border-solid p-0 transition-colors overflow-clip\"\n >\n <div class=\"i-lucide-grip-vertical size-5 min-h-5 min-w-5 block\"></div>\n </prosekit-table-handle-row-menu-trigger>\n <prosekit-menu-positioner class=\"overflow-visible bg-transparent\">\n <prosekit-menu-popup class=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg relative flex flex-col max-h-100 min-w-32 select-none overflow-auto whitespace-nowrap p-1 outline-none\">\n ${state.addTableRowAbove.canExec\n ? html`<prosekit-menu-item\n class=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n @select=${state.addTableRowAbove.command}\n >\n <span>Insert Above</span>\n </prosekit-menu-item>`\n : nothing}\n ${state.addTableRowBelow.canExec\n ? html`<prosekit-menu-item\n class=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n @select=${state.addTableRowBelow.command}\n >\n <span>Insert Below</span>\n </prosekit-menu-item>`\n : nothing}\n ${state.deleteCellSelection.canExec\n ? html`<prosekit-menu-item\n class=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n @select=${state.deleteCellSelection.command}\n >\n <span>Clear Contents</span>\n <span class=\"text-xs tracking-widest text-gray-500 dark:text-gray-500\">Del</span>\n </prosekit-menu-item>`\n : nothing}\n ${state.deleteTableRow.canExec\n ? html`<prosekit-menu-item\n class=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n @select=${state.deleteTableRow.command}\n >\n <span>Delete Row</span>\n </prosekit-menu-item>`\n : nothing}\n ${state.deleteTable.canExec\n ? html`<prosekit-menu-item\n class=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n data-danger=\"\"\n @select=${state.deleteTable.command}\n >\n <span>Delete Table</span>\n </prosekit-menu-item>`\n : nothing}\n </prosekit-menu-popup>\n </prosekit-menu-positioner>\n </prosekit-table-handle-row-menu-root>\n </prosekit-table-handle-row-popup>\n </prosekit-table-handle-row-positioner>\n </prosekit-table-handle-root>`\n }\n}\n\ncustomElements.define('lit-editor-table-handle', LitTableHandle)\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'lit-editor-table-handle': LitTableHandle\n }\n}\n"
26
+ "content": "import 'prosekit/lit/table-handle'\nimport 'prosekit/web/menu'\n\nimport { ContextConsumer } from '@lit/context'\nimport { html, LitElement, nothing, type PropertyDeclaration, type PropertyValues } from 'lit'\nimport type { Editor } from 'prosekit/core'\nimport { defineUpdateHandler } from 'prosekit/core'\nimport type { TableExtension } from 'prosekit/extensions/table'\n\nimport { editorContext } from '../editor-context'\n\nfunction getTableHandleState(editor: Editor<TableExtension>) {\n return {\n addTableColumnBefore: {\n canExec: editor.commands.addTableColumnBefore.canExec(),\n command: () => editor.commands.addTableColumnBefore(),\n },\n addTableColumnAfter: {\n canExec: editor.commands.addTableColumnAfter.canExec(),\n command: () => editor.commands.addTableColumnAfter(),\n },\n deleteCellSelection: {\n canExec: editor.commands.deleteCellSelection.canExec(),\n command: () => editor.commands.deleteCellSelection(),\n },\n deleteTableColumn: {\n canExec: editor.commands.deleteTableColumn.canExec(),\n command: () => editor.commands.deleteTableColumn(),\n },\n addTableRowAbove: {\n canExec: editor.commands.addTableRowAbove.canExec(),\n command: () => editor.commands.addTableRowAbove(),\n },\n addTableRowBelow: {\n canExec: editor.commands.addTableRowBelow.canExec(),\n command: () => editor.commands.addTableRowBelow(),\n },\n deleteTableRow: {\n canExec: editor.commands.deleteTableRow.canExec(),\n command: () => editor.commands.deleteTableRow(),\n },\n deleteTable: {\n canExec: editor.commands.deleteTable.canExec(),\n command: () => editor.commands.deleteTable(),\n },\n }\n}\n\nclass LitTableHandle extends LitElement {\n static override properties = {\n dir: { type: String } satisfies PropertyDeclaration<'ltr' | 'rtl'>,\n }\n\n override dir: string = ''\n\n private editorConsumer = new ContextConsumer(this, {\n context: editorContext,\n subscribe: true,\n })\n\n private removeUpdateExtension?: VoidFunction\n\n override createRenderRoot() {\n return this\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this.classList.add('contents')\n this.attachEditorListener()\n }\n\n override disconnectedCallback() {\n this.detachEditorListener()\n super.disconnectedCallback()\n }\n\n override updated(changedProperties: PropertyValues) {\n super.updated(changedProperties)\n this.attachEditorListener()\n }\n\n private attachEditorListener() {\n this.detachEditorListener()\n\n const editor = this.editorConsumer.value\n if (!editor) return\n\n this.removeUpdateExtension = editor.use(defineUpdateHandler(() => this.requestUpdate()))\n }\n\n private detachEditorListener() {\n this.removeUpdateExtension?.()\n this.removeUpdateExtension = undefined\n }\n\n override render() {\n const editor = this.editorConsumer.value as Editor<TableExtension> | undefined\n if (!editor) {\n return nothing\n }\n\n const state = getTableHandleState(editor)\n const placement = this.dir === 'rtl' ? 'right' : 'left'\n\n return html`<prosekit-table-handle-root .editor=${editor}>\n <prosekit-table-handle-drag-preview .editor=${editor}></prosekit-table-handle-drag-preview>\n <prosekit-table-handle-drop-indicator\n .editor=${editor}\n ></prosekit-table-handle-drop-indicator>\n <prosekit-table-handle-column-positioner\n .editor=${editor}\n class=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\"\n >\n <prosekit-table-handle-column-popup class=\"translate-y-[50%] flex box-border origin-(--transform-origin) transition 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\">\n <prosekit-table-handle-column-menu-root>\n <prosekit-table-handle-column-menu-trigger\n .editor=${editor}\n class=\"h-4.5 w-6 flex items-center box-border justify-center bg-white dark:bg-gray-950 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-400/50 border border-gray-200 dark:border-gray-800 border-solid p-0 transition-colors overflow-clip\"\n >\n <div class=\"i-lucide-grip-horizontal size-5 min-h-5 min-w-5 block\"></div>\n </prosekit-table-handle-column-menu-trigger>\n <prosekit-menu-positioner class=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <prosekit-menu-popup class=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg relative flex flex-col max-h-100 min-w-32 select-none overflow-auto whitespace-nowrap p-1 outline-none\">\n ${state.addTableColumnBefore.canExec\n ? html`<prosekit-menu-item\n class=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n @select=${state.addTableColumnBefore.command}\n >\n <span>Insert Left</span>\n </prosekit-menu-item>`\n : nothing}\n ${state.addTableColumnAfter.canExec\n ? html`<prosekit-menu-item\n class=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n @select=${state.addTableColumnAfter.command}\n >\n <span>Insert Right</span>\n </prosekit-menu-item>`\n : nothing}\n ${state.deleteCellSelection.canExec\n ? html`<prosekit-menu-item\n class=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n @select=${state.deleteCellSelection.command}\n >\n <span>Clear Contents</span>\n <span class=\"text-xs tracking-widest text-gray-500 dark:text-gray-500\">Del</span>\n </prosekit-menu-item>`\n : nothing}\n ${state.deleteTableColumn.canExec\n ? html`<prosekit-menu-item\n class=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n @select=${state.deleteTableColumn.command}\n >\n <span>Delete Column</span>\n </prosekit-menu-item>`\n : nothing}\n ${state.deleteTable.canExec\n ? html`<prosekit-menu-item\n class=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n data-danger=\"\"\n @select=${state.deleteTable.command}\n >\n <span>Delete Table</span>\n </prosekit-menu-item>`\n : nothing}\n </prosekit-menu-popup>\n </prosekit-menu-positioner>\n </prosekit-table-handle-column-menu-root>\n </prosekit-table-handle-column-popup>\n </prosekit-table-handle-column-positioner>\n <prosekit-table-handle-row-positioner\n .editor=${editor}\n .placement=${placement}\n class=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\"\n >\n <prosekit-table-handle-row-popup class=\"ltr:translate-x-[50%] rtl:translate-x-[-50%] flex box-border origin-(--transform-origin) transition 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\">\n <prosekit-table-handle-row-menu-root>\n <prosekit-table-handle-row-menu-trigger\n .editor=${editor}\n class=\"h-6 w-4.5 flex items-center box-border justify-center bg-white dark:bg-gray-950 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-400/50 border border-gray-200 dark:border-gray-800 border-solid p-0 transition-colors overflow-clip\"\n >\n <div class=\"i-lucide-grip-vertical size-5 min-h-5 min-w-5 block\"></div>\n </prosekit-table-handle-row-menu-trigger>\n <prosekit-menu-positioner class=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <prosekit-menu-popup class=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg relative flex flex-col max-h-100 min-w-32 select-none overflow-auto whitespace-nowrap p-1 outline-none\">\n ${state.addTableRowAbove.canExec\n ? html`<prosekit-menu-item\n class=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n @select=${state.addTableRowAbove.command}\n >\n <span>Insert Above</span>\n </prosekit-menu-item>`\n : nothing}\n ${state.addTableRowBelow.canExec\n ? html`<prosekit-menu-item\n class=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n @select=${state.addTableRowBelow.command}\n >\n <span>Insert Below</span>\n </prosekit-menu-item>`\n : nothing}\n ${state.deleteCellSelection.canExec\n ? html`<prosekit-menu-item\n class=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n @select=${state.deleteCellSelection.command}\n >\n <span>Clear Contents</span>\n <span class=\"text-xs tracking-widest text-gray-500 dark:text-gray-500\">Del</span>\n </prosekit-menu-item>`\n : nothing}\n ${state.deleteTableRow.canExec\n ? html`<prosekit-menu-item\n class=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n @select=${state.deleteTableRow.command}\n >\n <span>Delete Row</span>\n </prosekit-menu-item>`\n : nothing}\n ${state.deleteTable.canExec\n ? html`<prosekit-menu-item\n class=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n data-danger=\"\"\n @select=${state.deleteTable.command}\n >\n <span>Delete Table</span>\n </prosekit-menu-item>`\n : nothing}\n </prosekit-menu-popup>\n </prosekit-menu-positioner>\n </prosekit-table-handle-row-menu-root>\n </prosekit-table-handle-row-popup>\n </prosekit-table-handle-row-positioner>\n </prosekit-table-handle-root>`\n }\n}\n\ncustomElements.define('lit-editor-table-handle', LitTableHandle)\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'lit-editor-table-handle': LitTableHandle\n }\n}\n"
27
27
  }
28
28
  ],
29
29
  "meta": {
@@ -33,7 +33,7 @@
33
33
  "path": "registry/src/preact/examples/text-color/inline-menu.tsx",
34
34
  "type": "registry:component",
35
35
  "target": "components/editor/examples/text-color/inline-menu.tsx",
36
- "content": "import { useMemo, useState } from 'preact/hooks'\nimport type { Editor, Keymap } from 'prosekit/core'\nimport { useEditorDerivedValue, useKeymap } from 'prosekit/preact'\nimport { InlinePopoverPopup, InlinePopoverPositioner, InlinePopoverRoot } from 'prosekit/preact/inline-popover'\n\nimport { Button } from '../../ui/button'\n\nimport type { EditorExtension } from './extension'\n\nconst textColors = [\n { label: 'Gray', value: '#9ca3af' },\n { label: 'Brown', value: '#92400e' },\n { label: 'Orange', value: '#ea580c' },\n { label: 'Yellow', value: '#ca8a04' },\n { label: 'Green', value: '#16a34a' },\n { label: 'Blue', value: '#2563eb' },\n { label: 'Purple', value: '#9333ea' },\n { label: 'Magenta', value: '#c026d3' },\n { label: 'Red', value: '#dc2626' },\n]\n\nconst backgroundColors = [\n { label: 'Gray', value: '#f3f4f6' },\n { label: 'Brown', value: '#fef3c7' },\n { label: 'Orange', value: '#ffedd5' },\n { label: 'Yellow', value: '#fef9c3' },\n { label: 'Green', value: '#d1fae5' },\n { label: 'Blue', value: '#dbeafe' },\n { label: 'Purple', value: '#e9d5ff' },\n { label: 'Pink', value: '#fce7f3' },\n { label: 'Red', value: '#fecaca' },\n]\n\nfunction getTextColorState(editor: Editor<EditorExtension>) {\n return [{\n label: 'Default',\n value: 'currentColor',\n isActive: !editor.marks.textColor.isActive(),\n onClick: () => editor.commands.removeTextColor(),\n }].concat(textColors.map((color) => ({\n label: color.label,\n value: color.value,\n isActive: editor.marks.textColor.isActive({ color: color.value }),\n onClick: () => editor.commands.addTextColor({ color: color.value }),\n })))\n}\n\nfunction getBackgroundColorState(editor: Editor<EditorExtension>) {\n return [{\n label: 'Default',\n value: 'canvas',\n isActive: !editor.marks.backgroundColor.isActive(),\n onClick: () => editor.commands.removeBackgroundColor(),\n }].concat(\n backgroundColors.map((color) => ({\n label: color.label,\n value: color.value,\n isActive: editor.marks.backgroundColor.isActive({ color: color.value }),\n onClick: () => editor.commands.addBackgroundColor({ color: color.value }),\n })),\n )\n}\n\nexport default function InlineMenu() {\n const textColorState = useEditorDerivedValue(getTextColorState)\n const backgroundColorState = useEditorDerivedValue(getBackgroundColorState)\n const [open, setOpen] = useState(false)\n\n const keymap: Keymap = useMemo(() => ({\n Escape: () => {\n if (open) {\n setOpen(false)\n return true\n }\n return false\n },\n }), [open])\n\n useKeymap(keymap)\n\n return (\n <InlinePopoverRoot\n open={open}\n onOpenChange={(event) => setOpen(event.detail)}\n >\n <InlinePopoverPositioner className=\"block overflow-visible bg-transparent w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <InlinePopoverPopup className=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg relative flex min-w-32 space-x-1 overflow-auto whitespace-nowrap rounded-md p-1\">\n <div className=\"flex flex-col gap-4 p-4\">\n <div className=\"flex flex-col gap-2\">\n <div className=\"text-sm\">Text color</div>\n <div className=\"grid grid-cols-5 gap-1\">\n {textColorState.map((color) => (\n <Button\n key={color.label}\n pressed={color.isActive}\n tooltip={`Text: ${color.label}`}\n onClick={color.onClick}\n >\n <span\n className=\"text-base font-medium\"\n style={{ color: color.value }}\n >\n A\n </span>\n </Button>\n ))}\n </div>\n </div>\n <div className=\"flex flex-col gap-2\">\n <div className=\"text-sm\">Background color</div>\n <div className=\"grid grid-cols-5 gap-1\">\n {backgroundColorState.map((color) => (\n <Button\n key={color.label}\n pressed={color.isActive}\n tooltip={`Background: ${color.label}`}\n onClick={color.onClick}\n >\n <div\n className=\"w-6 h-6 rounded border border-gray-200 dark:border-gray-700\"\n style={{ backgroundColor: color.value }}\n />\n </Button>\n ))}\n </div>\n </div>\n </div>\n </InlinePopoverPopup>\n </InlinePopoverPositioner>\n </InlinePopoverRoot>\n )\n}\n"
36
+ "content": "import { useMemo, useState } from 'preact/hooks'\nimport type { Editor, Keymap } from 'prosekit/core'\nimport { useEditorDerivedValue, useKeymap } from 'prosekit/preact'\nimport { InlinePopoverPopup, InlinePopoverPositioner, InlinePopoverRoot } from 'prosekit/preact/inline-popover'\n\nimport { Button } from '../../ui/button'\n\nimport type { EditorExtension } from './extension'\n\nconst textColors = [\n { label: 'Gray', value: '#9ca3af' },\n { label: 'Brown', value: '#92400e' },\n { label: 'Orange', value: '#ea580c' },\n { label: 'Yellow', value: '#ca8a04' },\n { label: 'Green', value: '#16a34a' },\n { label: 'Blue', value: '#2563eb' },\n { label: 'Purple', value: '#9333ea' },\n { label: 'Magenta', value: '#c026d3' },\n { label: 'Red', value: '#dc2626' },\n]\n\nconst backgroundColors = [\n { label: 'Gray', value: '#f3f4f6' },\n { label: 'Brown', value: '#fef3c7' },\n { label: 'Orange', value: '#ffedd5' },\n { label: 'Yellow', value: '#fef9c3' },\n { label: 'Green', value: '#d1fae5' },\n { label: 'Blue', value: '#dbeafe' },\n { label: 'Purple', value: '#e9d5ff' },\n { label: 'Pink', value: '#fce7f3' },\n { label: 'Red', value: '#fecaca' },\n]\n\nfunction getTextColorState(editor: Editor<EditorExtension>) {\n return [{\n label: 'Default',\n value: 'currentColor',\n isActive: !editor.marks.textColor.isActive(),\n onClick: () => editor.commands.removeTextColor(),\n }].concat(textColors.map((color) => ({\n label: color.label,\n value: color.value,\n isActive: editor.marks.textColor.isActive({ color: color.value }),\n onClick: () => editor.commands.addTextColor({ color: color.value }),\n })))\n}\n\nfunction getBackgroundColorState(editor: Editor<EditorExtension>) {\n return [{\n label: 'Default',\n value: 'canvas',\n isActive: !editor.marks.backgroundColor.isActive(),\n onClick: () => editor.commands.removeBackgroundColor(),\n }].concat(\n backgroundColors.map((color) => ({\n label: color.label,\n value: color.value,\n isActive: editor.marks.backgroundColor.isActive({ color: color.value }),\n onClick: () => editor.commands.addBackgroundColor({ color: color.value }),\n })),\n )\n}\n\nexport default function InlineMenu() {\n const textColorState = useEditorDerivedValue(getTextColorState)\n const backgroundColorState = useEditorDerivedValue(getBackgroundColorState)\n const [open, setOpen] = useState(false)\n\n const keymap: Keymap = useMemo(() => ({\n Escape: () => {\n if (open) {\n setOpen(false)\n return true\n }\n return false\n },\n }), [open])\n\n useKeymap(keymap)\n\n return (\n <InlinePopoverRoot\n open={open}\n onOpenChange={(event) => setOpen(event.detail)}\n >\n <InlinePopoverPositioner className=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <InlinePopoverPopup className=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg relative flex min-w-32 space-x-1 overflow-auto whitespace-nowrap rounded-md p-1\">\n <div className=\"flex flex-col gap-4 p-4\">\n <div className=\"flex flex-col gap-2\">\n <div className=\"text-sm\">Text color</div>\n <div className=\"grid grid-cols-5 gap-1\">\n {textColorState.map((color) => (\n <Button\n key={color.label}\n pressed={color.isActive}\n tooltip={`Text: ${color.label}`}\n onClick={color.onClick}\n >\n <span\n className=\"text-base font-medium\"\n style={{ color: color.value }}\n >\n A\n </span>\n </Button>\n ))}\n </div>\n </div>\n <div className=\"flex flex-col gap-2\">\n <div className=\"text-sm\">Background color</div>\n <div className=\"grid grid-cols-5 gap-1\">\n {backgroundColorState.map((color) => (\n <Button\n key={color.label}\n pressed={color.isActive}\n tooltip={`Background: ${color.label}`}\n onClick={color.onClick}\n >\n <div\n className=\"w-6 h-6 rounded border border-gray-200 dark:border-gray-700\"\n style={{ backgroundColor: color.value }}\n />\n </Button>\n ))}\n </div>\n </div>\n </div>\n </InlinePopoverPopup>\n </InlinePopoverPositioner>\n </InlinePopoverRoot>\n )\n}\n"
37
37
  }
38
38
  ],
39
39
  "meta": {
@@ -14,7 +14,7 @@
14
14
  "path": "registry/src/preact/ui/block-handle/block-handle.tsx",
15
15
  "type": "registry:component",
16
16
  "target": "components/editor/ui/block-handle/block-handle.tsx",
17
- "content": "import {\n BlockHandleAdd,\n BlockHandleDraggable,\n BlockHandlePopup,\n BlockHandlePositioner,\n BlockHandleRoot,\n} from 'prosekit/preact/block-handle'\n\ninterface Props {\n dir?: 'ltr' | 'rtl'\n}\n\nexport default function BlockHandle(props: Props) {\n return (\n <BlockHandleRoot>\n <BlockHandlePositioner\n placement={props.dir === 'rtl' ? 'right' : 'left'}\n className=\"block overflow-visible bg-transparent w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\"\n >\n <BlockHandlePopup className=\"flex box-border origin-(--transform-origin) transition 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\">\n <BlockHandleAdd className=\"h-6 w-6 cursor-pointer flex items-center box-border justify-center hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-400/50\">\n <div className=\"i-lucide-plus size-5 block\" />\n </BlockHandleAdd>\n <BlockHandleDraggable className=\"h-6 w-5 cursor-grab flex items-center box-border justify-center hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-400/50\">\n <div className=\"i-lucide-grip-vertical size-5 block\" />\n </BlockHandleDraggable>\n </BlockHandlePopup>\n </BlockHandlePositioner>\n </BlockHandleRoot>\n )\n}\n"
17
+ "content": "import {\n BlockHandleAdd,\n BlockHandleDraggable,\n BlockHandlePopup,\n BlockHandlePositioner,\n BlockHandleRoot,\n} from 'prosekit/preact/block-handle'\n\ninterface Props {\n dir?: 'ltr' | 'rtl'\n}\n\nexport default function BlockHandle(props: Props) {\n return (\n <BlockHandleRoot>\n <BlockHandlePositioner\n placement={props.dir === 'rtl' ? 'right' : 'left'}\n className=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\"\n >\n <BlockHandlePopup className=\"flex box-border origin-(--transform-origin) transition 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\">\n <BlockHandleAdd className=\"h-6 w-6 cursor-pointer flex items-center box-border justify-center hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-400/50\">\n <div className=\"i-lucide-plus size-5 block\" />\n </BlockHandleAdd>\n <BlockHandleDraggable className=\"h-6 w-5 cursor-grab flex items-center box-border justify-center hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-400/50\">\n <div className=\"i-lucide-grip-vertical size-5 block\" />\n </BlockHandleDraggable>\n </BlockHandlePopup>\n </BlockHandlePositioner>\n </BlockHandleRoot>\n )\n}\n"
18
18
  },
19
19
  {
20
20
  "path": "registry/src/preact/ui/block-handle/index.ts",
@@ -12,7 +12,7 @@
12
12
  "path": "registry/src/preact/ui/button/button.tsx",
13
13
  "type": "registry:component",
14
14
  "target": "components/editor/ui/button/button.tsx",
15
- "content": "import type { ComponentChild, MouseEventHandler } from 'preact'\nimport { TooltipPopup, TooltipPositioner, TooltipRoot, TooltipTrigger } from 'prosekit/preact/tooltip'\n\nexport default function Button(props: {\n pressed?: boolean\n disabled?: boolean\n onClick?: MouseEventHandler<HTMLButtonElement>\n tooltip?: string\n children: ComponentChild\n}) {\n return (\n <TooltipRoot>\n <TooltipTrigger className=\"block\">\n <button\n data-state={props.pressed ? 'on' : 'off'}\n disabled={props.disabled}\n onClick={props.onClick}\n onMouseDown={(event) => {\n // Prevent the editor from being blurred when the button is clicked\n event.preventDefault()\n }}\n className=\"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\"\n >\n {props.children}\n {props.tooltip ? <span className=\"sr-only\">{props.tooltip}</span> : null}\n </button>\n </TooltipTrigger>\n {props.tooltip\n ? (\n <TooltipPositioner className=\"block overflow-visible bg-transparent w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <TooltipPopup className=\"flex box-border origin-(--transform-origin) transition 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\">\n {props.tooltip}\n </TooltipPopup>\n </TooltipPositioner>\n )\n : null}\n </TooltipRoot>\n )\n}\n"
15
+ "content": "import type { ComponentChild, MouseEventHandler } from 'preact'\nimport { TooltipPopup, TooltipPositioner, TooltipRoot, TooltipTrigger } from 'prosekit/preact/tooltip'\n\nexport default function Button(props: {\n pressed?: boolean\n disabled?: boolean\n onClick?: MouseEventHandler<HTMLButtonElement>\n tooltip?: string\n children: ComponentChild\n}) {\n return (\n <TooltipRoot>\n <TooltipTrigger className=\"block\">\n <button\n data-state={props.pressed ? 'on' : 'off'}\n disabled={props.disabled}\n onClick={props.onClick}\n onMouseDown={(event) => {\n // Prevent the editor from being blurred when the button is clicked\n event.preventDefault()\n }}\n className=\"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\"\n >\n {props.children}\n {props.tooltip ? <span className=\"sr-only\">{props.tooltip}</span> : null}\n </button>\n </TooltipTrigger>\n {props.tooltip\n ? (\n <TooltipPositioner className=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <TooltipPopup className=\"flex box-border origin-(--transform-origin) transition 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\">\n {props.tooltip}\n </TooltipPopup>\n </TooltipPositioner>\n )\n : null}\n </TooltipRoot>\n )\n}\n"
16
16
  },
17
17
  {
18
18
  "path": "registry/src/preact/ui/button/index.ts",
@@ -14,7 +14,7 @@
14
14
  "path": "registry/src/preact/ui/image-upload-popover/image-upload-popover.tsx",
15
15
  "type": "registry:component",
16
16
  "target": "components/editor/ui/image-upload-popover/image-upload-popover.tsx",
17
- "content": "import type { ComponentChild, JSX } from 'preact'\nimport { useId, useState } from 'preact/hooks'\nimport type { Uploader } from 'prosekit/extensions/file'\nimport type { ImageExtension } from 'prosekit/extensions/image'\nimport { useEditor } from 'prosekit/preact'\nimport { PopoverPopup, PopoverPositioner, PopoverRoot, PopoverTrigger } from 'prosekit/preact/popover'\nimport type { OpenChangeEvent } from 'prosekit/web/popover'\n\nimport { Button } from '../button'\n\nexport default function ImageUploadPopover(props: {\n uploader: Uploader<string>\n tooltip: string\n disabled: boolean\n children: ComponentChild\n}) {\n const [open, setOpen] = useState(false)\n const [url, setUrl] = useState('')\n const [file, setFile] = useState<File | null>(null)\n const ariaId = useId()\n\n const editor = useEditor<ImageExtension>()\n\n const handleFileChange = (\n event: JSX.TargetedEvent<HTMLInputElement, Event>,\n ) => {\n const file = event.currentTarget.files?.[0]\n\n if (file) {\n setFile(file)\n setUrl('')\n } else {\n setFile(null)\n }\n }\n\n const handleUrlChange = (\n event: JSX.TargetedEvent<HTMLInputElement, Event>,\n ) => {\n const url = event.currentTarget.value\n\n if (url) {\n setUrl(url)\n setFile(null)\n } else {\n setUrl('')\n }\n }\n\n const deferResetState = () => {\n setTimeout(() => {\n setUrl('')\n setFile(null)\n }, 300)\n }\n\n const handleSubmit = () => {\n if (url) {\n editor.commands.insertImage({ src: url })\n } else if (file) {\n editor.commands.uploadImage({ file, uploader: props.uploader })\n }\n setOpen(false)\n deferResetState()\n }\n\n const handleOpenChange = (event: OpenChangeEvent) => {\n if (!event.detail) {\n deferResetState()\n }\n setOpen(event.detail)\n }\n\n return (\n <PopoverRoot open={open} onOpenChange={handleOpenChange}>\n <PopoverTrigger>\n <Button pressed={open} disabled={props.disabled} tooltip={props.tooltip}>\n {props.children}\n </Button>\n </PopoverTrigger>\n\n <PopoverPositioner placement=\"bottom\" className=\"block overflow-visible bg-transparent w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <PopoverPopup className=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg flex flex-col gap-y-4 p-6 text-sm w-sm\">\n {file ? null : (\n <>\n <label htmlFor={`id-link-${ariaId}`}>Embed Link</label>\n <input\n id={`id-link-${ariaId}`}\n className=\"flex h-9 rounded-md w-full bg-white dark:bg-gray-950 px-3 py-2 text-sm placeholder:text-gray-500 dark:placeholder:text-gray-500 transition border box-border border-gray-200 dark:border-gray-800 border-solid ring-0 ring-transparent focus-visible:ring-2 focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 focus-visible:ring-offset-0 outline-hidden focus-visible:outline-hidden file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:cursor-not-allowed disabled:opacity-50\"\n placeholder=\"Paste the image link...\"\n type=\"url\"\n value={url}\n onChange={handleUrlChange}\n />\n </>\n )}\n\n {url ? null : (\n <>\n <label htmlFor={`id-upload-${ariaId}`}>Upload</label>\n <input\n id={`id-upload-${ariaId}`}\n className=\"flex h-9 rounded-md w-full bg-white dark:bg-gray-950 px-3 py-2 text-sm placeholder:text-gray-500 dark:placeholder:text-gray-500 transition border box-border border-gray-200 dark:border-gray-800 border-solid ring-0 ring-transparent focus-visible:ring-2 focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 focus-visible:ring-offset-0 outline-hidden focus-visible:outline-hidden file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:cursor-not-allowed disabled:opacity-50\"\n accept=\"image/*\"\n type=\"file\"\n onChange={handleFileChange}\n />\n </>\n )}\n\n {url\n ? (\n <button className=\"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white dark:ring-offset-gray-950 transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border-0 bg-gray-900 dark:bg-gray-50 text-gray-50 dark:text-gray-900 hover:bg-gray-900/90 dark:hover:bg-gray-50/90 h-10 px-4 py-2 w-full\" onClick={handleSubmit}>\n Insert Image\n </button>\n )\n : null}\n\n {file\n ? (\n <button className=\"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white dark:ring-offset-gray-950 transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border-0 bg-gray-900 dark:bg-gray-50 text-gray-50 dark:text-gray-900 hover:bg-gray-900/90 dark:hover:bg-gray-50/90 h-10 px-4 py-2 w-full\" onClick={handleSubmit}>\n Upload Image\n </button>\n )\n : null}\n </PopoverPopup>\n </PopoverPositioner>\n </PopoverRoot>\n )\n}\n"
17
+ "content": "import type { ComponentChild, JSX } from 'preact'\nimport { useId, useState } from 'preact/hooks'\nimport type { Uploader } from 'prosekit/extensions/file'\nimport type { ImageExtension } from 'prosekit/extensions/image'\nimport { useEditor } from 'prosekit/preact'\nimport { PopoverPopup, PopoverPositioner, PopoverRoot, PopoverTrigger } from 'prosekit/preact/popover'\nimport type { OpenChangeEvent } from 'prosekit/web/popover'\n\nimport { Button } from '../button'\n\nexport default function ImageUploadPopover(props: {\n uploader: Uploader<string>\n tooltip: string\n disabled: boolean\n children: ComponentChild\n}) {\n const [open, setOpen] = useState(false)\n const [url, setUrl] = useState('')\n const [file, setFile] = useState<File | null>(null)\n const ariaId = useId()\n\n const editor = useEditor<ImageExtension>()\n\n const handleFileChange = (\n event: JSX.TargetedEvent<HTMLInputElement, Event>,\n ) => {\n const file = event.currentTarget.files?.[0]\n\n if (file) {\n setFile(file)\n setUrl('')\n } else {\n setFile(null)\n }\n }\n\n const handleUrlChange = (\n event: JSX.TargetedEvent<HTMLInputElement, Event>,\n ) => {\n const url = event.currentTarget.value\n\n if (url) {\n setUrl(url)\n setFile(null)\n } else {\n setUrl('')\n }\n }\n\n const deferResetState = () => {\n setTimeout(() => {\n setUrl('')\n setFile(null)\n }, 300)\n }\n\n const handleSubmit = () => {\n if (url) {\n editor.commands.insertImage({ src: url })\n } else if (file) {\n editor.commands.uploadImage({ file, uploader: props.uploader })\n }\n setOpen(false)\n deferResetState()\n }\n\n const handleOpenChange = (event: OpenChangeEvent) => {\n if (!event.detail) {\n deferResetState()\n }\n setOpen(event.detail)\n }\n\n return (\n <PopoverRoot open={open} onOpenChange={handleOpenChange}>\n <PopoverTrigger>\n <Button pressed={open} disabled={props.disabled} tooltip={props.tooltip}>\n {props.children}\n </Button>\n </PopoverTrigger>\n\n <PopoverPositioner placement=\"bottom\" className=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <PopoverPopup className=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg flex flex-col gap-y-4 p-6 text-sm w-sm\">\n {file ? null : (\n <>\n <label htmlFor={`id-link-${ariaId}`}>Embed Link</label>\n <input\n id={`id-link-${ariaId}`}\n className=\"flex h-9 rounded-md w-full bg-white dark:bg-gray-950 px-3 py-2 text-sm placeholder:text-gray-500 dark:placeholder:text-gray-500 transition border box-border border-gray-200 dark:border-gray-800 border-solid ring-0 ring-transparent focus-visible:ring-2 focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 focus-visible:ring-offset-0 outline-hidden focus-visible:outline-hidden file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:cursor-not-allowed disabled:opacity-50\"\n placeholder=\"Paste the image link...\"\n type=\"url\"\n value={url}\n onChange={handleUrlChange}\n />\n </>\n )}\n\n {url ? null : (\n <>\n <label htmlFor={`id-upload-${ariaId}`}>Upload</label>\n <input\n id={`id-upload-${ariaId}`}\n className=\"flex h-9 rounded-md w-full bg-white dark:bg-gray-950 px-3 py-2 text-sm placeholder:text-gray-500 dark:placeholder:text-gray-500 transition border box-border border-gray-200 dark:border-gray-800 border-solid ring-0 ring-transparent focus-visible:ring-2 focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 focus-visible:ring-offset-0 outline-hidden focus-visible:outline-hidden file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:cursor-not-allowed disabled:opacity-50\"\n accept=\"image/*\"\n type=\"file\"\n onChange={handleFileChange}\n />\n </>\n )}\n\n {url\n ? (\n <button className=\"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white dark:ring-offset-gray-950 transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border-0 bg-gray-900 dark:bg-gray-50 text-gray-50 dark:text-gray-900 hover:bg-gray-900/90 dark:hover:bg-gray-50/90 h-10 px-4 py-2 w-full\" onClick={handleSubmit}>\n Insert Image\n </button>\n )\n : null}\n\n {file\n ? (\n <button className=\"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white dark:ring-offset-gray-950 transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border-0 bg-gray-900 dark:bg-gray-50 text-gray-50 dark:text-gray-900 hover:bg-gray-900/90 dark:hover:bg-gray-50/90 h-10 px-4 py-2 w-full\" onClick={handleSubmit}>\n Upload Image\n </button>\n )\n : null}\n </PopoverPopup>\n </PopoverPositioner>\n </PopoverRoot>\n )\n}\n"
18
18
  },
19
19
  {
20
20
  "path": "registry/src/preact/ui/image-upload-popover/index.ts",
@@ -22,7 +22,7 @@
22
22
  "path": "registry/src/preact/ui/inline-menu/inline-menu.tsx",
23
23
  "type": "registry:component",
24
24
  "target": "components/editor/ui/inline-menu/inline-menu.tsx",
25
- "content": "import type { JSX } from 'preact'\nimport { useState } from 'preact/hooks'\nimport type { BasicExtension } from 'prosekit/basic'\nimport type { Editor } from 'prosekit/core'\nimport type { LinkAttrs } from 'prosekit/extensions/link'\nimport type { EditorState } from 'prosekit/pm/state'\nimport { useEditor, useEditorDerivedValue } from 'prosekit/preact'\nimport { InlinePopoverPopup, InlinePopoverPositioner, InlinePopoverRoot } from 'prosekit/preact/inline-popover'\n\nimport { Button } from '../button'\n\nfunction getInlineMenuItems(editor: Editor<BasicExtension>) {\n return {\n bold: editor.commands.toggleBold\n ? {\n isActive: editor.marks.bold.isActive(),\n canExec: editor.commands.toggleBold.canExec(),\n command: () => editor.commands.toggleBold(),\n }\n : undefined,\n italic: editor.commands.toggleItalic\n ? {\n isActive: editor.marks.italic.isActive(),\n canExec: editor.commands.toggleItalic.canExec(),\n command: () => editor.commands.toggleItalic(),\n }\n : undefined,\n underline: editor.commands.toggleUnderline\n ? {\n isActive: editor.marks.underline.isActive(),\n canExec: editor.commands.toggleUnderline.canExec(),\n command: () => editor.commands.toggleUnderline(),\n }\n : undefined,\n strike: editor.commands.toggleStrike\n ? {\n isActive: editor.marks.strike.isActive(),\n canExec: editor.commands.toggleStrike.canExec(),\n command: () => editor.commands.toggleStrike(),\n }\n : undefined,\n code: editor.commands.toggleCode\n ? {\n isActive: editor.marks.code.isActive(),\n canExec: editor.commands.toggleCode.canExec(),\n command: () => editor.commands.toggleCode(),\n }\n : undefined,\n link: editor.commands.addLink\n ? {\n isActive: editor.marks.link.isActive(),\n canExec: editor.commands.addLink.canExec({ href: '' }),\n command: () => editor.commands.expandLink(),\n currentLink: getCurrentLink(editor.state) || '',\n }\n : undefined,\n }\n}\n\nfunction getCurrentLink(state: EditorState): string | undefined {\n const { $from } = state.selection\n const marks = $from.marksAcross($from)\n if (!marks) {\n return\n }\n for (const mark of marks) {\n if (mark.type.name === 'link') {\n return (mark.attrs as LinkAttrs).href\n }\n }\n}\n\nexport default function InlineMenu() {\n const editor = useEditor<BasicExtension>()\n const items = useEditorDerivedValue(getInlineMenuItems)\n\n const [linkMenuOpen, setLinkMenuOpen] = useState(false)\n const toggleLinkMenuOpen = () => setLinkMenuOpen((open) => !open)\n\n const handleLinkUpdate = (href?: string) => {\n if (href) {\n editor.commands.addLink({ href })\n } else {\n editor.commands.removeLink()\n }\n\n setLinkMenuOpen(false)\n editor.focus()\n }\n\n const handleSubmit = (\n event: JSX.TargetedEvent<HTMLFormElement, SubmitEvent>,\n ) => {\n event.preventDefault()\n const href = event.currentTarget.querySelector('input')?.value?.trim()\n handleLinkUpdate(href)\n }\n\n return (\n <>\n <InlinePopoverRoot\n onOpenChange={(event) => {\n if (!event.detail) {\n setLinkMenuOpen(false)\n }\n }}\n >\n <InlinePopoverPositioner className=\"block overflow-visible bg-transparent w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <InlinePopoverPopup\n data-testid=\"inline-menu-main\"\n className=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg relative flex min-w-32 space-x-1 overflow-auto whitespace-nowrap rounded-md p-1\"\n >\n {items.bold && (\n <Button\n pressed={items.bold.isActive}\n disabled={!items.bold.canExec}\n onClick={items.bold.command}\n tooltip=\"Bold\"\n >\n <div className=\"i-lucide-bold size-5 block\"></div>\n </Button>\n )}\n {items.italic && (\n <Button\n pressed={items.italic.isActive}\n disabled={!items.italic.canExec}\n onClick={items.italic.command}\n tooltip=\"Italic\"\n >\n <div className=\"i-lucide-italic size-5 block\"></div>\n </Button>\n )}\n {items.underline && (\n <Button\n pressed={items.underline.isActive}\n disabled={!items.underline.canExec}\n onClick={items.underline.command}\n tooltip=\"Underline\"\n >\n <div className=\"i-lucide-underline size-5 block\"></div>\n </Button>\n )}\n {items.strike && (\n <Button\n pressed={items.strike.isActive}\n disabled={!items.strike.canExec}\n onClick={items.strike.command}\n tooltip=\"Strikethrough\"\n >\n <div className=\"i-lucide-strikethrough size-5 block\"></div>\n </Button>\n )}\n {items.code && (\n <Button\n pressed={items.code.isActive}\n disabled={!items.code.canExec}\n onClick={items.code.command}\n tooltip=\"Code\"\n >\n <div className=\"i-lucide-code size-5 block\"></div>\n </Button>\n )}\n {items.link && items.link.canExec && (\n <Button\n pressed={items.link.isActive}\n onClick={() => {\n items.link?.command?.()\n toggleLinkMenuOpen()\n }}\n tooltip=\"Link\"\n >\n <div className=\"i-lucide-link size-5 block\"></div>\n </Button>\n )}\n </InlinePopoverPopup>\n </InlinePopoverPositioner>\n </InlinePopoverRoot>\n\n {items.link && (\n <InlinePopoverRoot\n defaultOpen={false}\n open={linkMenuOpen}\n onOpenChange={(event) => setLinkMenuOpen(event.detail)}\n >\n <InlinePopoverPositioner placement=\"bottom\" className=\"block overflow-visible bg-transparent w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <InlinePopoverPopup\n data-testid=\"inline-menu-link\"\n className=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg relative flex flex-col w-xs rounded-lg p-4 gap-y-2 items-stretch\"\n >\n {linkMenuOpen && (\n <form onSubmit={handleSubmit}>\n <input\n placeholder=\"Paste the link...\"\n defaultValue={items.link.currentLink}\n className=\"flex h-9 rounded-md w-full bg-white dark:bg-gray-950 px-3 py-2 text-sm placeholder:text-gray-500 dark:placeholder:text-gray-500 transition border box-border border-gray-200 dark:border-gray-800 border-solid ring-0 ring-transparent focus-visible:ring-2 focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 focus-visible:ring-offset-0 outline-hidden focus-visible:outline-hidden file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:cursor-not-allowed disabled:opacity-50\"\n />\n </form>\n )}\n {items.link.isActive && (\n <button\n onClick={() => handleLinkUpdate()}\n onMouseDown={(event) => event.preventDefault()}\n className=\"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white dark:ring-offset-gray-950 transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border-0 bg-gray-900 dark:bg-gray-50 text-gray-50 dark:text-gray-900 hover:bg-gray-900/90 dark:hover:bg-gray-50/90 h-9 px-3\"\n >\n Remove link\n </button>\n )}\n </InlinePopoverPopup>\n </InlinePopoverPositioner>\n </InlinePopoverRoot>\n )}\n </>\n )\n}\n"
25
+ "content": "import type { JSX } from 'preact'\nimport { useState } from 'preact/hooks'\nimport type { BasicExtension } from 'prosekit/basic'\nimport type { Editor } from 'prosekit/core'\nimport type { LinkAttrs } from 'prosekit/extensions/link'\nimport type { EditorState } from 'prosekit/pm/state'\nimport { useEditor, useEditorDerivedValue } from 'prosekit/preact'\nimport { InlinePopoverPopup, InlinePopoverPositioner, InlinePopoverRoot } from 'prosekit/preact/inline-popover'\n\nimport { Button } from '../button'\n\nfunction getInlineMenuItems(editor: Editor<BasicExtension>) {\n return {\n bold: editor.commands.toggleBold\n ? {\n isActive: editor.marks.bold.isActive(),\n canExec: editor.commands.toggleBold.canExec(),\n command: () => editor.commands.toggleBold(),\n }\n : undefined,\n italic: editor.commands.toggleItalic\n ? {\n isActive: editor.marks.italic.isActive(),\n canExec: editor.commands.toggleItalic.canExec(),\n command: () => editor.commands.toggleItalic(),\n }\n : undefined,\n underline: editor.commands.toggleUnderline\n ? {\n isActive: editor.marks.underline.isActive(),\n canExec: editor.commands.toggleUnderline.canExec(),\n command: () => editor.commands.toggleUnderline(),\n }\n : undefined,\n strike: editor.commands.toggleStrike\n ? {\n isActive: editor.marks.strike.isActive(),\n canExec: editor.commands.toggleStrike.canExec(),\n command: () => editor.commands.toggleStrike(),\n }\n : undefined,\n code: editor.commands.toggleCode\n ? {\n isActive: editor.marks.code.isActive(),\n canExec: editor.commands.toggleCode.canExec(),\n command: () => editor.commands.toggleCode(),\n }\n : undefined,\n link: editor.commands.addLink\n ? {\n isActive: editor.marks.link.isActive(),\n canExec: editor.commands.addLink.canExec({ href: '' }),\n command: () => editor.commands.expandLink(),\n currentLink: getCurrentLink(editor.state) || '',\n }\n : undefined,\n }\n}\n\nfunction getCurrentLink(state: EditorState): string | undefined {\n const { $from } = state.selection\n const marks = $from.marksAcross($from)\n if (!marks) {\n return\n }\n for (const mark of marks) {\n if (mark.type.name === 'link') {\n return (mark.attrs as LinkAttrs).href\n }\n }\n}\n\nexport default function InlineMenu() {\n const editor = useEditor<BasicExtension>()\n const items = useEditorDerivedValue(getInlineMenuItems)\n\n const [linkMenuOpen, setLinkMenuOpen] = useState(false)\n const toggleLinkMenuOpen = () => setLinkMenuOpen((open) => !open)\n\n const handleLinkUpdate = (href?: string) => {\n if (href) {\n editor.commands.addLink({ href })\n } else {\n editor.commands.removeLink()\n }\n\n setLinkMenuOpen(false)\n editor.focus()\n }\n\n const handleSubmit = (\n event: JSX.TargetedEvent<HTMLFormElement, SubmitEvent>,\n ) => {\n event.preventDefault()\n const href = event.currentTarget.querySelector('input')?.value?.trim()\n handleLinkUpdate(href)\n }\n\n return (\n <>\n <InlinePopoverRoot\n onOpenChange={(event) => {\n if (!event.detail) {\n setLinkMenuOpen(false)\n }\n }}\n >\n <InlinePopoverPositioner className=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <InlinePopoverPopup\n data-testid=\"inline-menu-main\"\n className=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg relative flex min-w-32 space-x-1 overflow-auto whitespace-nowrap rounded-md p-1\"\n >\n {items.bold && (\n <Button\n pressed={items.bold.isActive}\n disabled={!items.bold.canExec}\n onClick={items.bold.command}\n tooltip=\"Bold\"\n >\n <div className=\"i-lucide-bold size-5 block\"></div>\n </Button>\n )}\n {items.italic && (\n <Button\n pressed={items.italic.isActive}\n disabled={!items.italic.canExec}\n onClick={items.italic.command}\n tooltip=\"Italic\"\n >\n <div className=\"i-lucide-italic size-5 block\"></div>\n </Button>\n )}\n {items.underline && (\n <Button\n pressed={items.underline.isActive}\n disabled={!items.underline.canExec}\n onClick={items.underline.command}\n tooltip=\"Underline\"\n >\n <div className=\"i-lucide-underline size-5 block\"></div>\n </Button>\n )}\n {items.strike && (\n <Button\n pressed={items.strike.isActive}\n disabled={!items.strike.canExec}\n onClick={items.strike.command}\n tooltip=\"Strikethrough\"\n >\n <div className=\"i-lucide-strikethrough size-5 block\"></div>\n </Button>\n )}\n {items.code && (\n <Button\n pressed={items.code.isActive}\n disabled={!items.code.canExec}\n onClick={items.code.command}\n tooltip=\"Code\"\n >\n <div className=\"i-lucide-code size-5 block\"></div>\n </Button>\n )}\n {items.link && items.link.canExec && (\n <Button\n pressed={items.link.isActive}\n onClick={() => {\n items.link?.command?.()\n toggleLinkMenuOpen()\n }}\n tooltip=\"Link\"\n >\n <div className=\"i-lucide-link size-5 block\"></div>\n </Button>\n )}\n </InlinePopoverPopup>\n </InlinePopoverPositioner>\n </InlinePopoverRoot>\n\n {items.link && (\n <InlinePopoverRoot\n defaultOpen={false}\n open={linkMenuOpen}\n onOpenChange={(event) => setLinkMenuOpen(event.detail)}\n >\n <InlinePopoverPositioner placement=\"bottom\" className=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <InlinePopoverPopup\n data-testid=\"inline-menu-link\"\n className=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg relative flex flex-col w-xs rounded-lg p-4 gap-y-2 items-stretch\"\n >\n {linkMenuOpen && (\n <form onSubmit={handleSubmit}>\n <input\n placeholder=\"Paste the link...\"\n defaultValue={items.link.currentLink}\n className=\"flex h-9 rounded-md w-full bg-white dark:bg-gray-950 px-3 py-2 text-sm placeholder:text-gray-500 dark:placeholder:text-gray-500 transition border box-border border-gray-200 dark:border-gray-800 border-solid ring-0 ring-transparent focus-visible:ring-2 focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 focus-visible:ring-offset-0 outline-hidden focus-visible:outline-hidden file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:cursor-not-allowed disabled:opacity-50\"\n />\n </form>\n )}\n {items.link.isActive && (\n <button\n onClick={() => handleLinkUpdate()}\n onMouseDown={(event) => event.preventDefault()}\n className=\"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white dark:ring-offset-gray-950 transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border-0 bg-gray-900 dark:bg-gray-50 text-gray-50 dark:text-gray-900 hover:bg-gray-900/90 dark:hover:bg-gray-50/90 h-9 px-3\"\n >\n Remove link\n </button>\n )}\n </InlinePopoverPopup>\n </InlinePopoverPositioner>\n </InlinePopoverRoot>\n )}\n </>\n )\n}\n"
26
26
  }
27
27
  ],
28
28
  "meta": {
@@ -30,7 +30,7 @@
30
30
  "path": "registry/src/preact/ui/slash-menu/slash-menu.tsx",
31
31
  "type": "registry:component",
32
32
  "target": "components/editor/ui/slash-menu/slash-menu.tsx",
33
- "content": "import type { BasicExtension } from 'prosekit/basic'\nimport { canUseRegexLookbehind } from 'prosekit/core'\nimport { useEditor } from 'prosekit/preact'\nimport { AutocompletePopup, AutocompletePositioner, AutocompleteRoot } from 'prosekit/preact/autocomplete'\n\nimport SlashMenuEmpty from './slash-menu-empty'\nimport SlashMenuItem from './slash-menu-item'\n\n// Match inputs like \"/\", \"/table\", \"/heading 1\" etc. Do not match \"/ heading\".\nconst regex = canUseRegexLookbehind() ? /(?<!\\S)\\/(\\S.*)?$/u : /\\/(\\S.*)?$/u\n\nexport default function SlashMenu() {\n const editor = useEditor<BasicExtension>()\n\n return (\n <AutocompleteRoot regex={regex}>\n <AutocompletePositioner className=\"block overflow-visible bg-transparent w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <AutocompletePopup className=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg flex flex-col relative max-h-100 min-w-60 select-none overflow-auto whitespace-nowrap p-1\">\n <SlashMenuItem\n label=\"Text\"\n onSelect={() => editor.commands.setParagraph()}\n />\n\n <SlashMenuItem\n label=\"Heading 1\"\n kbd=\"#\"\n onSelect={() => editor.commands.setHeading({ level: 1 })}\n />\n\n <SlashMenuItem\n label=\"Heading 2\"\n kbd=\"##\"\n onSelect={() => editor.commands.setHeading({ level: 2 })}\n />\n\n <SlashMenuItem\n label=\"Heading 3\"\n kbd=\"###\"\n onSelect={() => editor.commands.setHeading({ level: 3 })}\n />\n\n <SlashMenuItem\n label=\"Bullet list\"\n kbd=\"-\"\n onSelect={() => editor.commands.wrapInList({ kind: 'bullet' })}\n />\n\n <SlashMenuItem\n label=\"Ordered list\"\n kbd=\"1.\"\n onSelect={() => editor.commands.wrapInList({ kind: 'ordered' })}\n />\n\n <SlashMenuItem\n label=\"Task list\"\n kbd=\"[]\"\n onSelect={() => editor.commands.wrapInList({ kind: 'task' })}\n />\n\n <SlashMenuItem\n label=\"Toggle list\"\n kbd=\">>\"\n onSelect={() => editor.commands.wrapInList({ kind: 'toggle' })}\n />\n\n <SlashMenuItem\n label=\"Quote\"\n kbd=\">\"\n onSelect={() => editor.commands.setBlockquote()}\n />\n\n <SlashMenuItem\n label=\"Table\"\n onSelect={() => editor.commands.insertTable({ row: 3, col: 3 })}\n />\n\n <SlashMenuItem\n label=\"Divider\"\n kbd=\"---\"\n onSelect={() => editor.commands.insertHorizontalRule()}\n />\n\n <SlashMenuItem\n label=\"Code\"\n kbd=\"```\"\n onSelect={() => editor.commands.setCodeBlock()}\n />\n\n <SlashMenuEmpty />\n </AutocompletePopup>\n </AutocompletePositioner>\n </AutocompleteRoot>\n )\n}\n"
33
+ "content": "import type { BasicExtension } from 'prosekit/basic'\nimport { canUseRegexLookbehind } from 'prosekit/core'\nimport { useEditor } from 'prosekit/preact'\nimport { AutocompletePopup, AutocompletePositioner, AutocompleteRoot } from 'prosekit/preact/autocomplete'\n\nimport SlashMenuEmpty from './slash-menu-empty'\nimport SlashMenuItem from './slash-menu-item'\n\n// Match inputs like \"/\", \"/table\", \"/heading 1\" etc. Do not match \"/ heading\".\nconst regex = canUseRegexLookbehind() ? /(?<!\\S)\\/(\\S.*)?$/u : /\\/(\\S.*)?$/u\n\nexport default function SlashMenu() {\n const editor = useEditor<BasicExtension>()\n\n return (\n <AutocompleteRoot regex={regex}>\n <AutocompletePositioner className=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <AutocompletePopup className=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg flex flex-col relative max-h-100 min-w-60 select-none overflow-auto whitespace-nowrap p-1\">\n <SlashMenuItem\n label=\"Text\"\n onSelect={() => editor.commands.setParagraph()}\n />\n\n <SlashMenuItem\n label=\"Heading 1\"\n kbd=\"#\"\n onSelect={() => editor.commands.setHeading({ level: 1 })}\n />\n\n <SlashMenuItem\n label=\"Heading 2\"\n kbd=\"##\"\n onSelect={() => editor.commands.setHeading({ level: 2 })}\n />\n\n <SlashMenuItem\n label=\"Heading 3\"\n kbd=\"###\"\n onSelect={() => editor.commands.setHeading({ level: 3 })}\n />\n\n <SlashMenuItem\n label=\"Bullet list\"\n kbd=\"-\"\n onSelect={() => editor.commands.wrapInList({ kind: 'bullet' })}\n />\n\n <SlashMenuItem\n label=\"Ordered list\"\n kbd=\"1.\"\n onSelect={() => editor.commands.wrapInList({ kind: 'ordered' })}\n />\n\n <SlashMenuItem\n label=\"Task list\"\n kbd=\"[]\"\n onSelect={() => editor.commands.wrapInList({ kind: 'task' })}\n />\n\n <SlashMenuItem\n label=\"Toggle list\"\n kbd=\">>\"\n onSelect={() => editor.commands.wrapInList({ kind: 'toggle' })}\n />\n\n <SlashMenuItem\n label=\"Quote\"\n kbd=\">\"\n onSelect={() => editor.commands.setBlockquote()}\n />\n\n <SlashMenuItem\n label=\"Table\"\n onSelect={() => editor.commands.insertTable({ row: 3, col: 3 })}\n />\n\n <SlashMenuItem\n label=\"Divider\"\n kbd=\"---\"\n onSelect={() => editor.commands.insertHorizontalRule()}\n />\n\n <SlashMenuItem\n label=\"Code\"\n kbd=\"```\"\n onSelect={() => editor.commands.setCodeBlock()}\n />\n\n <SlashMenuEmpty />\n </AutocompletePopup>\n </AutocompletePositioner>\n </AutocompleteRoot>\n )\n}\n"
34
34
  }
35
35
  ],
36
36
  "meta": {
@@ -20,7 +20,7 @@
20
20
  "path": "registry/src/preact/ui/table-handle/table-handle.tsx",
21
21
  "type": "registry:component",
22
22
  "target": "components/editor/ui/table-handle/table-handle.tsx",
23
- "content": "import type { Editor } from 'prosekit/core'\nimport type { TableExtension } from 'prosekit/extensions/table'\nimport { useEditorDerivedValue } from 'prosekit/preact'\nimport { MenuItem, MenuPopup, MenuPositioner } from 'prosekit/preact/menu'\nimport {\n TableHandleColumnMenuRoot,\n TableHandleColumnMenuTrigger,\n TableHandleColumnPopup,\n TableHandleColumnPositioner,\n TableHandleDragPreview,\n TableHandleDropIndicator,\n TableHandleRoot,\n TableHandleRowMenuRoot,\n TableHandleRowMenuTrigger,\n TableHandleRowPopup,\n TableHandleRowPositioner,\n} from 'prosekit/preact/table-handle'\n\nfunction getTableHandleState(editor: Editor<TableExtension>) {\n return {\n addTableColumnBefore: {\n canExec: editor.commands.addTableColumnBefore.canExec(),\n command: () => editor.commands.addTableColumnBefore(),\n },\n addTableColumnAfter: {\n canExec: editor.commands.addTableColumnAfter.canExec(),\n command: () => editor.commands.addTableColumnAfter(),\n },\n deleteCellSelection: {\n canExec: editor.commands.deleteCellSelection.canExec(),\n command: () => editor.commands.deleteCellSelection(),\n },\n deleteTableColumn: {\n canExec: editor.commands.deleteTableColumn.canExec(),\n command: () => editor.commands.deleteTableColumn(),\n },\n addTableRowAbove: {\n canExec: editor.commands.addTableRowAbove.canExec(),\n command: () => editor.commands.addTableRowAbove(),\n },\n addTableRowBelow: {\n canExec: editor.commands.addTableRowBelow.canExec(),\n command: () => editor.commands.addTableRowBelow(),\n },\n deleteTableRow: {\n canExec: editor.commands.deleteTableRow.canExec(),\n command: () => editor.commands.deleteTableRow(),\n },\n deleteTable: {\n canExec: editor.commands.deleteTable.canExec(),\n command: () => editor.commands.deleteTable(),\n },\n }\n}\n\ninterface Props {\n dir?: 'ltr' | 'rtl'\n}\n\nexport default function TableHandle(props: Props) {\n const state = useEditorDerivedValue(getTableHandleState)\n\n return (\n <TableHandleRoot>\n <TableHandleDragPreview />\n <TableHandleDropIndicator />\n <TableHandleColumnPositioner className=\"block overflow-visible bg-transparent w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <TableHandleColumnPopup className=\"translate-y-[50%] flex box-border origin-(--transform-origin) transition 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\">\n <TableHandleColumnMenuRoot>\n <TableHandleColumnMenuTrigger className=\"h-4.5 w-6 flex items-center box-border justify-center bg-white dark:bg-gray-950 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-400/50 border border-gray-200 dark:border-gray-800 border-solid p-0 transition-colors overflow-clip\">\n <div className=\"i-lucide-grip-horizontal size-5 min-h-5 min-w-5 block\"></div>\n </TableHandleColumnMenuTrigger>\n <MenuPositioner className=\"overflow-visible bg-transparent\">\n <MenuPopup className=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg relative flex flex-col max-h-100 min-w-32 select-none overflow-auto whitespace-nowrap p-1 outline-none\">\n {state.addTableColumnBefore.canExec && (\n <MenuItem\n className=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n onSelect={state.addTableColumnBefore.command}\n >\n <span>Insert Left</span>\n </MenuItem>\n )}\n {state.addTableColumnAfter.canExec && (\n <MenuItem\n className=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n onSelect={state.addTableColumnAfter.command}\n >\n <span>Insert Right</span>\n </MenuItem>\n )}\n {state.deleteCellSelection.canExec && (\n <MenuItem\n className=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n onSelect={state.deleteCellSelection.command}\n >\n <span>Clear Contents</span>\n <span className=\"text-xs tracking-widest text-gray-500 dark:text-gray-500\">Del</span>\n </MenuItem>\n )}\n {state.deleteTableColumn.canExec && (\n <MenuItem\n className=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n onSelect={state.deleteTableColumn.command}\n >\n <span>Delete Column</span>\n </MenuItem>\n )}\n {state.deleteTable.canExec && (\n <MenuItem\n className=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n data-danger=\"\"\n onSelect={state.deleteTable.command}\n >\n <span>Delete Table</span>\n </MenuItem>\n )}\n </MenuPopup>\n </MenuPositioner>\n </TableHandleColumnMenuRoot>\n </TableHandleColumnPopup>\n </TableHandleColumnPositioner>\n <TableHandleRowPositioner\n placement={props.dir === 'rtl' ? 'right' : 'left'}\n className=\"block overflow-visible bg-transparent w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\"\n >\n <TableHandleRowPopup className=\"ltr:translate-x-[50%] rtl:translate-x-[-50%] flex box-border origin-(--transform-origin) transition 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\">\n <TableHandleRowMenuRoot>\n <TableHandleRowMenuTrigger className=\"h-6 w-4.5 flex items-center box-border justify-center bg-white dark:bg-gray-950 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-400/50 border border-gray-200 dark:border-gray-800 border-solid p-0 transition-colors overflow-clip\">\n <div className=\"i-lucide-grip-vertical size-5 min-h-5 min-w-5 block\"></div>\n </TableHandleRowMenuTrigger>\n <MenuPositioner className=\"overflow-visible bg-transparent\">\n <MenuPopup className=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg relative flex flex-col max-h-100 min-w-32 select-none overflow-auto whitespace-nowrap p-1 outline-none\">\n {state.addTableRowAbove.canExec && (\n <MenuItem\n className=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n onSelect={state.addTableRowAbove.command}\n >\n <span>Insert Above</span>\n </MenuItem>\n )}\n {state.addTableRowBelow.canExec && (\n <MenuItem\n className=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n onSelect={state.addTableRowBelow.command}\n >\n <span>Insert Below</span>\n </MenuItem>\n )}\n {state.deleteCellSelection.canExec && (\n <MenuItem\n className=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n onSelect={state.deleteCellSelection.command}\n >\n <span>Clear Contents</span>\n <span className=\"text-xs tracking-widest text-gray-500 dark:text-gray-500\">Del</span>\n </MenuItem>\n )}\n {state.deleteTableRow.canExec && (\n <MenuItem\n className=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n onSelect={state.deleteTableRow.command}\n >\n <span>Delete Row</span>\n </MenuItem>\n )}\n {state.deleteTable.canExec && (\n <MenuItem\n className=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n data-danger=\"\"\n onSelect={state.deleteTable.command}\n >\n <span>Delete Table</span>\n </MenuItem>\n )}\n </MenuPopup>\n </MenuPositioner>\n </TableHandleRowMenuRoot>\n </TableHandleRowPopup>\n </TableHandleRowPositioner>\n </TableHandleRoot>\n )\n}\n"
23
+ "content": "import type { Editor } from 'prosekit/core'\nimport type { TableExtension } from 'prosekit/extensions/table'\nimport { useEditorDerivedValue } from 'prosekit/preact'\nimport { MenuItem, MenuPopup, MenuPositioner } from 'prosekit/preact/menu'\nimport {\n TableHandleColumnMenuRoot,\n TableHandleColumnMenuTrigger,\n TableHandleColumnPopup,\n TableHandleColumnPositioner,\n TableHandleDragPreview,\n TableHandleDropIndicator,\n TableHandleRoot,\n TableHandleRowMenuRoot,\n TableHandleRowMenuTrigger,\n TableHandleRowPopup,\n TableHandleRowPositioner,\n} from 'prosekit/preact/table-handle'\n\nfunction getTableHandleState(editor: Editor<TableExtension>) {\n return {\n addTableColumnBefore: {\n canExec: editor.commands.addTableColumnBefore.canExec(),\n command: () => editor.commands.addTableColumnBefore(),\n },\n addTableColumnAfter: {\n canExec: editor.commands.addTableColumnAfter.canExec(),\n command: () => editor.commands.addTableColumnAfter(),\n },\n deleteCellSelection: {\n canExec: editor.commands.deleteCellSelection.canExec(),\n command: () => editor.commands.deleteCellSelection(),\n },\n deleteTableColumn: {\n canExec: editor.commands.deleteTableColumn.canExec(),\n command: () => editor.commands.deleteTableColumn(),\n },\n addTableRowAbove: {\n canExec: editor.commands.addTableRowAbove.canExec(),\n command: () => editor.commands.addTableRowAbove(),\n },\n addTableRowBelow: {\n canExec: editor.commands.addTableRowBelow.canExec(),\n command: () => editor.commands.addTableRowBelow(),\n },\n deleteTableRow: {\n canExec: editor.commands.deleteTableRow.canExec(),\n command: () => editor.commands.deleteTableRow(),\n },\n deleteTable: {\n canExec: editor.commands.deleteTable.canExec(),\n command: () => editor.commands.deleteTable(),\n },\n }\n}\n\ninterface Props {\n dir?: 'ltr' | 'rtl'\n}\n\nexport default function TableHandle(props: Props) {\n const state = useEditorDerivedValue(getTableHandleState)\n\n return (\n <TableHandleRoot>\n <TableHandleDragPreview />\n <TableHandleDropIndicator />\n <TableHandleColumnPositioner className=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <TableHandleColumnPopup className=\"translate-y-[50%] flex box-border origin-(--transform-origin) transition 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\">\n <TableHandleColumnMenuRoot>\n <TableHandleColumnMenuTrigger className=\"h-4.5 w-6 flex items-center box-border justify-center bg-white dark:bg-gray-950 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-400/50 border border-gray-200 dark:border-gray-800 border-solid p-0 transition-colors overflow-clip\">\n <div className=\"i-lucide-grip-horizontal size-5 min-h-5 min-w-5 block\"></div>\n </TableHandleColumnMenuTrigger>\n <MenuPositioner className=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <MenuPopup className=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg relative flex flex-col max-h-100 min-w-32 select-none overflow-auto whitespace-nowrap p-1 outline-none\">\n {state.addTableColumnBefore.canExec && (\n <MenuItem\n className=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n onSelect={state.addTableColumnBefore.command}\n >\n <span>Insert Left</span>\n </MenuItem>\n )}\n {state.addTableColumnAfter.canExec && (\n <MenuItem\n className=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n onSelect={state.addTableColumnAfter.command}\n >\n <span>Insert Right</span>\n </MenuItem>\n )}\n {state.deleteCellSelection.canExec && (\n <MenuItem\n className=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n onSelect={state.deleteCellSelection.command}\n >\n <span>Clear Contents</span>\n <span className=\"text-xs tracking-widest text-gray-500 dark:text-gray-500\">Del</span>\n </MenuItem>\n )}\n {state.deleteTableColumn.canExec && (\n <MenuItem\n className=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n onSelect={state.deleteTableColumn.command}\n >\n <span>Delete Column</span>\n </MenuItem>\n )}\n {state.deleteTable.canExec && (\n <MenuItem\n className=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n data-danger=\"\"\n onSelect={state.deleteTable.command}\n >\n <span>Delete Table</span>\n </MenuItem>\n )}\n </MenuPopup>\n </MenuPositioner>\n </TableHandleColumnMenuRoot>\n </TableHandleColumnPopup>\n </TableHandleColumnPositioner>\n <TableHandleRowPositioner\n placement={props.dir === 'rtl' ? 'right' : 'left'}\n className=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\"\n >\n <TableHandleRowPopup className=\"ltr:translate-x-[50%] rtl:translate-x-[-50%] flex box-border origin-(--transform-origin) transition 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\">\n <TableHandleRowMenuRoot>\n <TableHandleRowMenuTrigger className=\"h-6 w-4.5 flex items-center box-border justify-center bg-white dark:bg-gray-950 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-400/50 border border-gray-200 dark:border-gray-800 border-solid p-0 transition-colors overflow-clip\">\n <div className=\"i-lucide-grip-vertical size-5 min-h-5 min-w-5 block\"></div>\n </TableHandleRowMenuTrigger>\n <MenuPositioner className=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <MenuPopup className=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg relative flex flex-col max-h-100 min-w-32 select-none overflow-auto whitespace-nowrap p-1 outline-none\">\n {state.addTableRowAbove.canExec && (\n <MenuItem\n className=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n onSelect={state.addTableRowAbove.command}\n >\n <span>Insert Above</span>\n </MenuItem>\n )}\n {state.addTableRowBelow.canExec && (\n <MenuItem\n className=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n onSelect={state.addTableRowBelow.command}\n >\n <span>Insert Below</span>\n </MenuItem>\n )}\n {state.deleteCellSelection.canExec && (\n <MenuItem\n className=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n onSelect={state.deleteCellSelection.command}\n >\n <span>Clear Contents</span>\n <span className=\"text-xs tracking-widest text-gray-500 dark:text-gray-500\">Del</span>\n </MenuItem>\n )}\n {state.deleteTableRow.canExec && (\n <MenuItem\n className=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n onSelect={state.deleteTableRow.command}\n >\n <span>Delete Row</span>\n </MenuItem>\n )}\n {state.deleteTable.canExec && (\n <MenuItem\n className=\"relative min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 flex items-center justify-between gap-8 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 hover:data-[disabled=true]:opacity-50 data-danger:text-red-500 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n data-danger=\"\"\n onSelect={state.deleteTable.command}\n >\n <span>Delete Table</span>\n </MenuItem>\n )}\n </MenuPopup>\n </MenuPositioner>\n </TableHandleRowMenuRoot>\n </TableHandleRowPopup>\n </TableHandleRowPositioner>\n </TableHandleRoot>\n )\n}\n"
24
24
  }
25
25
  ],
26
26
  "meta": {
@@ -18,7 +18,7 @@
18
18
  "path": "registry/src/preact/ui/tag-menu/tag-menu.tsx",
19
19
  "type": "registry:component",
20
20
  "target": "components/editor/ui/tag-menu/tag-menu.tsx",
21
- "content": "import type { BasicExtension } from 'prosekit/basic'\nimport type { Union } from 'prosekit/core'\nimport type { MentionExtension } from 'prosekit/extensions/mention'\nimport { useEditor } from 'prosekit/preact'\nimport {\n AutocompleteEmpty,\n AutocompleteItem,\n AutocompletePopup,\n AutocompletePositioner,\n AutocompleteRoot,\n} from 'prosekit/preact/autocomplete'\n\nconst regex = /#[\\da-z]*$/i\n\nexport default function TagMenu(props: { tags: { id: number; label: string }[] }) {\n const editor = useEditor<Union<[MentionExtension, BasicExtension]>>()\n\n const handleTagInsert = (id: number, label: string) => {\n editor.commands.insertMention({\n id: id.toString(),\n value: '#' + label,\n kind: 'tag',\n })\n editor.commands.insertText({ text: ' ' })\n }\n\n return (\n <AutocompleteRoot regex={regex}>\n <AutocompletePositioner className=\"block overflow-visible bg-transparent w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <AutocompletePopup className=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg flex flex-col relative max-h-100 min-w-60 select-none overflow-auto whitespace-nowrap p-1\">\n <AutocompleteEmpty className=\"relative flex items-center justify-between min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\">\n No results\n </AutocompleteEmpty>\n\n {props.tags.map((tag) => (\n <AutocompleteItem\n key={tag.id}\n className=\"relative flex items-center justify-between min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n onSelect={() => handleTagInsert(tag.id, tag.label)}\n >\n #{tag.label}\n </AutocompleteItem>\n ))}\n </AutocompletePopup>\n </AutocompletePositioner>\n </AutocompleteRoot>\n )\n}\n"
21
+ "content": "import type { BasicExtension } from 'prosekit/basic'\nimport type { Union } from 'prosekit/core'\nimport type { MentionExtension } from 'prosekit/extensions/mention'\nimport { useEditor } from 'prosekit/preact'\nimport {\n AutocompleteEmpty,\n AutocompleteItem,\n AutocompletePopup,\n AutocompletePositioner,\n AutocompleteRoot,\n} from 'prosekit/preact/autocomplete'\n\nconst regex = /#[\\da-z]*$/i\n\nexport default function TagMenu(props: { tags: { id: number; label: string }[] }) {\n const editor = useEditor<Union<[MentionExtension, BasicExtension]>>()\n\n const handleTagInsert = (id: number, label: string) => {\n editor.commands.insertMention({\n id: id.toString(),\n value: '#' + label,\n kind: 'tag',\n })\n editor.commands.insertText({ text: ' ' })\n }\n\n return (\n <AutocompleteRoot regex={regex}>\n <AutocompletePositioner className=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <AutocompletePopup className=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg flex flex-col relative max-h-100 min-w-60 select-none overflow-auto whitespace-nowrap p-1\">\n <AutocompleteEmpty className=\"relative flex items-center justify-between min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\">\n No results\n </AutocompleteEmpty>\n\n {props.tags.map((tag) => (\n <AutocompleteItem\n key={tag.id}\n className=\"relative flex items-center justify-between min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n onSelect={() => handleTagInsert(tag.id, tag.label)}\n >\n #{tag.label}\n </AutocompleteItem>\n ))}\n </AutocompletePopup>\n </AutocompletePositioner>\n </AutocompleteRoot>\n )\n}\n"
22
22
  }
23
23
  ],
24
24
  "meta": {
@@ -18,7 +18,7 @@
18
18
  "path": "registry/src/preact/ui/user-menu/user-menu.tsx",
19
19
  "type": "registry:component",
20
20
  "target": "components/editor/ui/user-menu/user-menu.tsx",
21
- "content": "import type { BasicExtension } from 'prosekit/basic'\nimport { canUseRegexLookbehind, type Union } from 'prosekit/core'\nimport type { MentionExtension } from 'prosekit/extensions/mention'\nimport { useEditor } from 'prosekit/preact'\nimport {\n AutocompleteEmpty,\n AutocompleteItem,\n AutocompletePopup,\n AutocompletePositioner,\n AutocompleteRoot,\n} from 'prosekit/preact/autocomplete'\n\n// Match inputs like \"@\", \"@foo\", \"@foo bar\" etc. Do not match \"@ foo\".\nconst regex = canUseRegexLookbehind() ? /(?<!\\S)@(\\S.*)?$/u : /@(\\S.*)?$/u\n\nexport default function UserMenu(props: {\n users: { id: number; name: string }[]\n loading?: boolean\n onQueryChange?: (query: string) => void\n onOpenChange?: (open: boolean) => void\n}) {\n const editor = useEditor<Union<[MentionExtension, BasicExtension]>>()\n\n const handleUserInsert = (id: number, username: string) => {\n editor.commands.insertMention({\n id: id.toString(),\n value: '@' + username,\n kind: 'user',\n })\n editor.commands.insertText({ text: ' ' })\n }\n\n return (\n <AutocompleteRoot\n regex={regex}\n onQueryChange={(event) => props.onQueryChange?.(event.detail)}\n onOpenChange={(event) => props.onOpenChange?.(event.detail)}\n >\n <AutocompletePositioner className=\"block overflow-visible bg-transparent w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <AutocompletePopup className=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg flex flex-col relative max-h-100 min-w-60 select-none overflow-auto whitespace-nowrap p-1\">\n <AutocompleteEmpty className=\"relative flex items-center justify-between min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\">\n {props.loading ? 'Loading...' : 'No results'}\n </AutocompleteEmpty>\n\n {props.users.map((user) => (\n <AutocompleteItem\n key={user.id}\n className=\"relative flex items-center justify-between min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n onSelect={() => handleUserInsert(user.id, user.name)}\n >\n <span className={props.loading ? 'opacity-50' : undefined}>\n {user.name}\n </span>\n </AutocompleteItem>\n ))}\n </AutocompletePopup>\n </AutocompletePositioner>\n </AutocompleteRoot>\n )\n}\n"
21
+ "content": "import type { BasicExtension } from 'prosekit/basic'\nimport { canUseRegexLookbehind, type Union } from 'prosekit/core'\nimport type { MentionExtension } from 'prosekit/extensions/mention'\nimport { useEditor } from 'prosekit/preact'\nimport {\n AutocompleteEmpty,\n AutocompleteItem,\n AutocompletePopup,\n AutocompletePositioner,\n AutocompleteRoot,\n} from 'prosekit/preact/autocomplete'\n\n// Match inputs like \"@\", \"@foo\", \"@foo bar\" etc. Do not match \"@ foo\".\nconst regex = canUseRegexLookbehind() ? /(?<!\\S)@(\\S.*)?$/u : /@(\\S.*)?$/u\n\nexport default function UserMenu(props: {\n users: { id: number; name: string }[]\n loading?: boolean\n onQueryChange?: (query: string) => void\n onOpenChange?: (open: boolean) => void\n}) {\n const editor = useEditor<Union<[MentionExtension, BasicExtension]>>()\n\n const handleUserInsert = (id: number, username: string) => {\n editor.commands.insertMention({\n id: id.toString(),\n value: '@' + username,\n kind: 'user',\n })\n editor.commands.insertText({ text: ' ' })\n }\n\n return (\n <AutocompleteRoot\n regex={regex}\n onQueryChange={(event) => props.onQueryChange?.(event.detail)}\n onOpenChange={(event) => props.onOpenChange?.(event.detail)}\n >\n <AutocompletePositioner className=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <AutocompletePopup className=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg flex flex-col relative max-h-100 min-w-60 select-none overflow-auto whitespace-nowrap p-1\">\n <AutocompleteEmpty className=\"relative flex items-center justify-between min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\">\n {props.loading ? 'Loading...' : 'No results'}\n </AutocompleteEmpty>\n\n {props.users.map((user) => (\n <AutocompleteItem\n key={user.id}\n className=\"relative flex items-center justify-between min-w-32 scroll-my-1 rounded-sm px-3 py-1.5 box-border cursor-default select-none whitespace-nowrap outline-hidden data-highlighted:bg-gray-100 dark:data-highlighted:bg-gray-800\"\n onSelect={() => handleUserInsert(user.id, user.name)}\n >\n <span className={props.loading ? 'opacity-50' : undefined}>\n {user.name}\n </span>\n </AutocompleteItem>\n ))}\n </AutocompletePopup>\n </AutocompletePositioner>\n </AutocompleteRoot>\n )\n}\n"
22
22
  }
23
23
  ],
24
24
  "meta": {
@@ -32,7 +32,7 @@
32
32
  "path": "registry/src/react/examples/notion/block-handle.tsx",
33
33
  "type": "registry:component",
34
34
  "target": "components/editor/examples/notion/block-handle.tsx",
35
- "content": "'use client'\n\nimport { Tooltip } from '@base-ui/react/tooltip'\nimport { BlockHandleAdd, BlockHandleDraggable, BlockHandlePopup, BlockHandlePositioner, BlockHandleRoot } from 'prosekit/react/block-handle'\n\nimport BlockHandleMenu from './block-handle-menu'\n\ninterface Props {\n enabled: boolean\n dir?: 'ltr' | 'rtl'\n}\n\nexport default function BlockHandle(props: Props) {\n if (!props.enabled) {\n return null\n }\n\n return (\n <Tooltip.Provider>\n <BlockHandleRoot>\n <BlockHandlePositioner\n placement={props.dir === 'rtl' ? 'right' : 'left'}\n className=\"block overflow-visible bg-transparent w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\"\n >\n <BlockHandlePopup className=\"flex box-border origin-(--transform-origin) transition 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\">\n <Tooltip.Root>\n <Tooltip.Trigger className=\"m-0 p-0\">\n <BlockHandleAdd className=\"h-6 w-6 cursor-pointer flex items-center box-border justify-center hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-400/50\">\n <div className=\"i-lucide-plus size-5 block\" />\n </BlockHandleAdd>\n </Tooltip.Trigger>\n <Tooltip.Portal>\n <Tooltip.Positioner sideOffset={10} side=\"bottom\">\n <Tooltip.Popup className=\"\n flex flex-col justify-center items-center\n px-2 py-1\n rounded-md\n bg-[canvas]\n text-sm\n z-100\n origin-(--transform-origin)\n shadow-lg shadow-gray-200 outline-1 outline-gray-200\n transition-[transform,scale,opacity]\n data-ending-style:opacity-0 data-ending-style:scale-90\n data-instant:transition-none\n data-starting-style:opacity-0 data-starting-style:scale-90\n dark:shadow-none dark:outline-gray-300 dark:-outline-offset-1\">\n <span>\n <span>Click{' '}</span>\n <span className=\"opacity-50\">to add below</span>\n </span>\n <span>\n <span>Option-click{' '}</span>\n <span className=\"opacity-50\">to add above</span>\n </span>\n </Tooltip.Popup>\n </Tooltip.Positioner>\n </Tooltip.Portal>\n </Tooltip.Root>\n <Tooltip.Root>\n <Tooltip.Trigger className=\"m-0 p-0\">\n <BlockHandleMenu>\n <BlockHandleDraggable className=\"h-6 w-5 cursor-grab flex items-center box-border justify-center hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-400/50\">\n <div className=\"i-lucide-grip-vertical size-5 block\" />\n </BlockHandleDraggable>\n </BlockHandleMenu>\n </Tooltip.Trigger>\n <Tooltip.Portal>\n <Tooltip.Positioner sideOffset={10} side=\"bottom\">\n <Tooltip.Popup className=\"\n flex flex-col justify-center items-center\n px-2 py-1\n rounded-md\n bg-[canvas]\n text-sm\n z-100\n origin-(--transform-origin)\n shadow-lg shadow-gray-200 outline-1 outline-gray-200\n transition-[transform,scale,opacity]\n data-ending-style:opacity-0 data-ending-style:scale-90\n data-instant:transition-none\n data-starting-style:opacity-0 data-starting-style:scale-90\n dark:shadow-none dark:outline-gray-300 dark:-outline-offset-1\">\n <span>\n <span>Drag{' '}</span>\n <span className=\"opacity-50\">to move</span>\n </span>\n <span>\n <span>Click{' '}</span>\n <span className=\"opacity-50\">to open menu</span>\n </span>\n </Tooltip.Popup>\n </Tooltip.Positioner>\n </Tooltip.Portal>\n </Tooltip.Root>\n </BlockHandlePopup>\n </BlockHandlePositioner>\n </BlockHandleRoot>\n </Tooltip.Provider>\n )\n}\n"
35
+ "content": "'use client'\n\nimport { Tooltip } from '@base-ui/react/tooltip'\nimport { BlockHandleAdd, BlockHandleDraggable, BlockHandlePopup, BlockHandlePositioner, BlockHandleRoot } from 'prosekit/react/block-handle'\n\nimport BlockHandleMenu from './block-handle-menu'\n\ninterface Props {\n enabled: boolean\n dir?: 'ltr' | 'rtl'\n}\n\nexport default function BlockHandle(props: Props) {\n if (!props.enabled) {\n return null\n }\n\n return (\n <Tooltip.Provider>\n <BlockHandleRoot>\n <BlockHandlePositioner\n placement={props.dir === 'rtl' ? 'right' : 'left'}\n className=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\"\n >\n <BlockHandlePopup className=\"flex box-border origin-(--transform-origin) transition 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\">\n <Tooltip.Root>\n <Tooltip.Trigger className=\"m-0 p-0\">\n <BlockHandleAdd className=\"h-6 w-6 cursor-pointer flex items-center box-border justify-center hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-400/50\">\n <div className=\"i-lucide-plus size-5 block\" />\n </BlockHandleAdd>\n </Tooltip.Trigger>\n <Tooltip.Portal>\n <Tooltip.Positioner sideOffset={10} side=\"bottom\">\n <Tooltip.Popup className=\"\n flex flex-col justify-center items-center\n px-2 py-1\n rounded-md\n bg-[canvas]\n text-sm\n z-100\n origin-(--transform-origin)\n shadow-lg shadow-gray-200 outline-1 outline-gray-200\n transition-[transform,scale,opacity]\n data-ending-style:opacity-0 data-ending-style:scale-90\n data-instant:transition-none\n data-starting-style:opacity-0 data-starting-style:scale-90\n dark:shadow-none dark:outline-gray-300 dark:-outline-offset-1\">\n <span>\n <span>Click{' '}</span>\n <span className=\"opacity-50\">to add below</span>\n </span>\n <span>\n <span>Option-click{' '}</span>\n <span className=\"opacity-50\">to add above</span>\n </span>\n </Tooltip.Popup>\n </Tooltip.Positioner>\n </Tooltip.Portal>\n </Tooltip.Root>\n <Tooltip.Root>\n <Tooltip.Trigger className=\"m-0 p-0\">\n <BlockHandleMenu>\n <BlockHandleDraggable className=\"h-6 w-5 cursor-grab flex items-center box-border justify-center hover:bg-gray-100 dark:hover:bg-gray-800 rounded-sm text-gray-500/50 dark:text-gray-400/50\">\n <div className=\"i-lucide-grip-vertical size-5 block\" />\n </BlockHandleDraggable>\n </BlockHandleMenu>\n </Tooltip.Trigger>\n <Tooltip.Portal>\n <Tooltip.Positioner sideOffset={10} side=\"bottom\">\n <Tooltip.Popup className=\"\n flex flex-col justify-center items-center\n px-2 py-1\n rounded-md\n bg-[canvas]\n text-sm\n z-100\n origin-(--transform-origin)\n shadow-lg shadow-gray-200 outline-1 outline-gray-200\n transition-[transform,scale,opacity]\n data-ending-style:opacity-0 data-ending-style:scale-90\n data-instant:transition-none\n data-starting-style:opacity-0 data-starting-style:scale-90\n dark:shadow-none dark:outline-gray-300 dark:-outline-offset-1\">\n <span>\n <span>Drag{' '}</span>\n <span className=\"opacity-50\">to move</span>\n </span>\n <span>\n <span>Click{' '}</span>\n <span className=\"opacity-50\">to open menu</span>\n </span>\n </Tooltip.Popup>\n </Tooltip.Positioner>\n </Tooltip.Portal>\n </Tooltip.Root>\n </BlockHandlePopup>\n </BlockHandlePositioner>\n </BlockHandleRoot>\n </Tooltip.Provider>\n )\n}\n"
36
36
  },
37
37
  {
38
38
  "path": "registry/src/react/examples/notion/editor.tsx",
@@ -98,7 +98,7 @@
98
98
  "path": "registry/src/react/examples/notion/slash-menu/slash-menu.tsx",
99
99
  "type": "registry:component",
100
100
  "target": "components/editor/examples/notion/slash-menu/slash-menu.tsx",
101
- "content": "'use client'\n\nimport type { BasicExtension } from 'prosekit/basic'\nimport { canUseRegexLookbehind } from 'prosekit/core'\nimport { useEditor } from 'prosekit/react'\nimport { AutocompletePopup, AutocompletePositioner, AutocompleteRoot } from 'prosekit/react/autocomplete'\n\nimport SlashMenuEmpty from './slash-menu-empty'\nimport SlashMenuItem from './slash-menu-item'\n\n// Match inputs like \"/\", \"/table\", \"/heading 1\" etc. Do not match \"/ heading\".\nconst regex = canUseRegexLookbehind() ? /(?<!\\S)\\/(\\S.*)?$/u : /\\/(\\S.*)?$/u\n\ninterface Props {\n onOpenChange: (open: boolean) => void\n}\n\nexport default function SlashMenu(props: Props) {\n const editor = useEditor<BasicExtension>()\n\n return (\n <AutocompleteRoot\n regex={regex}\n onOpenChange={(event) => {\n props.onOpenChange(event.detail)\n }}\n >\n <AutocompletePositioner className=\"block overflow-visible bg-transparent w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <AutocompletePopup className=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg flex flex-col relative max-h-100 min-w-60 select-none overflow-auto whitespace-nowrap p-1\">\n <SlashMenuItem\n label=\"Text\"\n onSelect={() => editor.commands.setParagraph()}\n />\n\n <SlashMenuItem\n label=\"Heading 1\"\n kbd=\"#\"\n onSelect={() => editor.commands.setHeading({ level: 1 })}\n />\n\n <SlashMenuItem\n label=\"Heading 2\"\n kbd=\"##\"\n onSelect={() => editor.commands.setHeading({ level: 2 })}\n />\n\n <SlashMenuItem\n label=\"Heading 3\"\n kbd=\"###\"\n onSelect={() => editor.commands.setHeading({ level: 3 })}\n />\n\n <SlashMenuItem\n label=\"Bullet list\"\n kbd=\"-\"\n onSelect={() => editor.commands.wrapInList({ kind: 'bullet' })}\n />\n\n <SlashMenuItem\n label=\"Ordered list\"\n kbd=\"1.\"\n onSelect={() => editor.commands.wrapInList({ kind: 'ordered' })}\n />\n\n <SlashMenuItem\n label=\"Task list\"\n kbd=\"[]\"\n onSelect={() => editor.commands.wrapInList({ kind: 'task' })}\n />\n\n <SlashMenuItem\n label=\"Toggle list\"\n kbd=\">>\"\n onSelect={() => editor.commands.wrapInList({ kind: 'toggle' })}\n />\n\n <SlashMenuItem\n label=\"Quote\"\n kbd=\">\"\n onSelect={() => editor.commands.setBlockquote()}\n />\n\n <SlashMenuItem\n label=\"Table\"\n onSelect={() => editor.commands.insertTable({ row: 3, col: 3 })}\n />\n\n <SlashMenuItem\n label=\"Divider\"\n kbd=\"---\"\n onSelect={() => editor.commands.insertHorizontalRule()}\n />\n\n <SlashMenuItem\n label=\"Code\"\n kbd=\"```\"\n onSelect={() => editor.commands.setCodeBlock()}\n />\n\n <SlashMenuItem\n label=\"Image\"\n onSelect={() => editor.commands.insertImage({ src: '' })}\n />\n\n <SlashMenuEmpty />\n </AutocompletePopup>\n </AutocompletePositioner>\n </AutocompleteRoot>\n )\n}\n"
101
+ "content": "'use client'\n\nimport type { BasicExtension } from 'prosekit/basic'\nimport { canUseRegexLookbehind } from 'prosekit/core'\nimport { useEditor } from 'prosekit/react'\nimport { AutocompletePopup, AutocompletePositioner, AutocompleteRoot } from 'prosekit/react/autocomplete'\n\nimport SlashMenuEmpty from './slash-menu-empty'\nimport SlashMenuItem from './slash-menu-item'\n\n// Match inputs like \"/\", \"/table\", \"/heading 1\" etc. Do not match \"/ heading\".\nconst regex = canUseRegexLookbehind() ? /(?<!\\S)\\/(\\S.*)?$/u : /\\/(\\S.*)?$/u\n\ninterface Props {\n onOpenChange: (open: boolean) => void\n}\n\nexport default function SlashMenu(props: Props) {\n const editor = useEditor<BasicExtension>()\n\n return (\n <AutocompleteRoot\n regex={regex}\n onOpenChange={(event) => {\n props.onOpenChange(event.detail)\n }}\n >\n <AutocompletePositioner className=\"block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none\">\n <AutocompletePopup className=\"box-border origin-(--transform-origin) transition transition-discrete motion-reduce:transition-none data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 duration-40 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg flex flex-col relative max-h-100 min-w-60 select-none overflow-auto whitespace-nowrap p-1\">\n <SlashMenuItem\n label=\"Text\"\n onSelect={() => editor.commands.setParagraph()}\n />\n\n <SlashMenuItem\n label=\"Heading 1\"\n kbd=\"#\"\n onSelect={() => editor.commands.setHeading({ level: 1 })}\n />\n\n <SlashMenuItem\n label=\"Heading 2\"\n kbd=\"##\"\n onSelect={() => editor.commands.setHeading({ level: 2 })}\n />\n\n <SlashMenuItem\n label=\"Heading 3\"\n kbd=\"###\"\n onSelect={() => editor.commands.setHeading({ level: 3 })}\n />\n\n <SlashMenuItem\n label=\"Bullet list\"\n kbd=\"-\"\n onSelect={() => editor.commands.wrapInList({ kind: 'bullet' })}\n />\n\n <SlashMenuItem\n label=\"Ordered list\"\n kbd=\"1.\"\n onSelect={() => editor.commands.wrapInList({ kind: 'ordered' })}\n />\n\n <SlashMenuItem\n label=\"Task list\"\n kbd=\"[]\"\n onSelect={() => editor.commands.wrapInList({ kind: 'task' })}\n />\n\n <SlashMenuItem\n label=\"Toggle list\"\n kbd=\">>\"\n onSelect={() => editor.commands.wrapInList({ kind: 'toggle' })}\n />\n\n <SlashMenuItem\n label=\"Quote\"\n kbd=\">\"\n onSelect={() => editor.commands.setBlockquote()}\n />\n\n <SlashMenuItem\n label=\"Table\"\n onSelect={() => editor.commands.insertTable({ row: 3, col: 3 })}\n />\n\n <SlashMenuItem\n label=\"Divider\"\n kbd=\"---\"\n onSelect={() => editor.commands.insertHorizontalRule()}\n />\n\n <SlashMenuItem\n label=\"Code\"\n kbd=\"```\"\n onSelect={() => editor.commands.setCodeBlock()}\n />\n\n <SlashMenuItem\n label=\"Image\"\n onSelect={() => editor.commands.insertImage({ src: '' })}\n />\n\n <SlashMenuEmpty />\n </AutocompletePopup>\n </AutocompletePositioner>\n </AutocompleteRoot>\n )\n}\n"
102
102
  },
103
103
  {
104
104
  "path": "registry/src/react/examples/notion/style.css",