From 168009a04285020b33cbaf9dbb4538b636fa0e7d Mon Sep 17 00:00:00 2001 From: paul_webyte Date: Mon, 29 Sep 2025 14:11:23 +0200 Subject: [PATCH 1/5] feat: Add unique id prop to support multiple editors --- README.md | 35 +++++++ src/lib/Editor.svelte | 96 ++++++++++--------- src/routes/test-multiple-editors/+page.svelte | 60 ++++++++++++ 3 files changed, 146 insertions(+), 45 deletions(-) create mode 100644 src/routes/test-multiple-editors/+page.svelte diff --git a/README.md b/README.md index 0849f2b..714f640 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,38 @@ This will install svelte-trix as well as the base Trix dependency. Both of the above examples will produce a simple rich text editor with buttons on the top that looks like this: ![Screenshot of a simple text editor created with svelte-trix](https://www.dropbox.com/scl/fi/45d3c6jsvvvwp659ejs74/Screenshot-2025-06-02-at-1.56.17-PM.png?rlkey=q2gwo2tw1904xukxl5msogfj0&raw=1) +### Using Multiple Editors on the Same Page + +When using multiple Trix editors on the same page, you should provide custom IDs to avoid conflicts: + +```svelte + + +
+
+

Editor 1

+ +
+ +
+

Editor 2

+ +
+
+``` + ## Props svelte-trix has typesafe support for all customizations and event listeners that the original Trix library supports, as well as a bindable `value` prop for svelte-ishness. @@ -59,6 +91,9 @@ svelte-trix has typesafe support for all customizations and event listeners that | `editor` | `Element` | Bindable prop that exposes the instance of the TrixEditor directly, should you want to perform any customizations or actions that aren't available through props. | Yes | | `label` | `string` | Optional label for the form. | No | | `disabled` | `boolean` | Disabled editors are not editable and cannot receive focus. | No | +| `wrapperId` | `string` | Custom ID for the main wrapper element. Defaults to 'svelte-trix-editor-wrapper'. | No | +| `hiddenInputId` | `string` | Custom ID for the hidden input element. Defaults to 'svelte-trix-hidden-input'. | No | +| `editorId` | `string` | Custom ID for the Trix editor element. Defaults to 'svelte-trix-editor'. | No | | `config` | `ITrixConfig` | Learn more about [Config](#config) | No | ### Event Listeners diff --git a/src/lib/Editor.svelte b/src/lib/Editor.svelte index c53d092..790073c 100644 --- a/src/lib/Editor.svelte +++ b/src/lib/Editor.svelte @@ -4,43 +4,49 @@ import { onMount } from 'svelte'; import type { ITrixConfig } from './types.js'; - interface IProps { - value?: string; - editor?: any; - config?: ITrixConfig; - onChange?: (value: string) => void; - onFileAccept?: (event: Event) => void; - onAttachmentAdd?: (event: Event) => void; - onAttachmentRemove?: (event: Event) => void; - onSelectionChange?: (event: Event) => void; - onFocus?: (event: Event) => void; - onBlur?: (event: Event) => void; - onPaste?: (event: Event) => void; - onActionInvoke?: (event: Event) => void; - label?: string; - disabled?: boolean; - required?: boolean; - hideAttachmentButton?: boolean; - } - - let { - value = $bindable(), - editor = $bindable(), - onChange = undefined, - onFileAccept = undefined, - onAttachmentAdd = undefined, - onAttachmentRemove = undefined, - onSelectionChange = undefined, - onFocus = undefined, - onBlur = undefined, - onPaste = undefined, - onActionInvoke = undefined, - label = '', - disabled = false, - required = false, - hideAttachmentButton = false, - config - }: IProps = $props(); + interface IProps { + value?: string; + editor?: any; + config?: ITrixConfig; + onChange?: (value: string) => void; + onFileAccept?: (event: Event) => void; + onAttachmentAdd?: (event: Event) => void; + onAttachmentRemove?: (event: Event) => void; + onSelectionChange?: (event: Event) => void; + onFocus?: (event: Event) => void; + onBlur?: (event: Event) => void; + onPaste?: (event: Event) => void; + onActionInvoke?: (event: Event) => void; + label?: string; + disabled?: boolean; + required?: boolean; + hideAttachmentButton?: boolean; + wrapperId?: string; + hiddenInputId?: string; + editorId?: string; + } + + let { + value = $bindable(), + editor = $bindable(), + onChange = undefined, + onFileAccept = undefined, + onAttachmentAdd = undefined, + onAttachmentRemove = undefined, + onSelectionChange = undefined, + onFocus = undefined, + onBlur = undefined, + onPaste = undefined, + onActionInvoke = undefined, + label = '', + disabled = false, + required = false, + hideAttachmentButton = false, + wrapperId = 'svelte-trix-editor-wrapper', + hiddenInputId = 'svelte-trix-hidden-input', + editorId = 'svelte-trix-editor', + config + }: IProps = $props(); let Trix: any; let el = $state(); @@ -355,8 +361,8 @@ document.addEventListener('trix-selection-change', _onSelectionChange); document.addEventListener('trix-action-invoke', _onActionInvoke); - _editor = document.querySelector('trix-editor'); - editor = document.querySelector('trix-editor'); + _editor = document.querySelector(`trix-editor#${editorId}`); + editor = document.querySelector(`trix-editor#${editorId}`); }; $effect(() => { @@ -378,24 +384,24 @@ {#if BROWSER} -
+
{#if label} - + {/if} - +
{/if} \ No newline at end of file From f29b048cc9589375020793a9c87b7ff6b706655b Mon Sep 17 00:00:00 2001 From: paul_webyte Date: Mon, 29 Sep 2025 14:55:41 +0200 Subject: [PATCH 2/5] chore: Update package version and dependencies, include `src` in published files --- package-lock.json | 8 ++++++-- package.json | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 01e3a55..f6ef764 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,16 @@ { "name": "svelte-trix", - "version": "0.0.1", + "version": "0.0.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "svelte-trix", - "version": "0.0.1", + "version": "0.0.6", + "license": "MIT", + "dependencies": { + "trix": "^2.1.15" + }, "devDependencies": { "@eslint/compat": "^1.2.5", "@eslint/js": "^9.18.0", diff --git a/package.json b/package.json index 3076c2b..a722ecf 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "lint": "prettier --check . && eslint ." }, "files": [ + "src", "dist", "!dist/**/*.test.*", "!dist/**/*.spec.*" From 087bb3278c0cd678b67d6e5d7bbe1094fe9b5038 Mon Sep 17 00:00:00 2001 From: paul_webyte Date: Mon, 29 Sep 2025 15:07:04 +0200 Subject: [PATCH 3/5] include dist --- .gitignore | 1 - dist/Editor.svelte | 409 ++++++++++++++++++++++++++++++++++++++++ dist/Editor.svelte.d.ts | 26 +++ dist/index.d.ts | 1 + dist/index.js | 2 + dist/types.d.ts | 184 ++++++++++++++++++ dist/types.js | 1 + 7 files changed, 623 insertions(+), 1 deletion(-) create mode 100644 dist/Editor.svelte create mode 100644 dist/Editor.svelte.d.ts create mode 100644 dist/index.d.ts create mode 100644 dist/index.js create mode 100644 dist/types.d.ts create mode 100644 dist/types.js diff --git a/.gitignore b/.gitignore index 294b385..3b462cb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ node_modules .wrangler /.svelte-kit /build -/dist # OS .DS_Store diff --git a/dist/Editor.svelte b/dist/Editor.svelte new file mode 100644 index 0000000..790073c --- /dev/null +++ b/dist/Editor.svelte @@ -0,0 +1,409 @@ + + +{#if BROWSER} +
+ {#if label} + + {/if} + + +
+{/if} + + diff --git a/dist/Editor.svelte.d.ts b/dist/Editor.svelte.d.ts new file mode 100644 index 0000000..c14c2c8 --- /dev/null +++ b/dist/Editor.svelte.d.ts @@ -0,0 +1,26 @@ +import 'trix/dist/trix.css'; +import type { ITrixConfig } from './types.js'; +interface IProps { + value?: string; + editor?: any; + config?: ITrixConfig; + onChange?: (value: string) => void; + onFileAccept?: (event: Event) => void; + onAttachmentAdd?: (event: Event) => void; + onAttachmentRemove?: (event: Event) => void; + onSelectionChange?: (event: Event) => void; + onFocus?: (event: Event) => void; + onBlur?: (event: Event) => void; + onPaste?: (event: Event) => void; + onActionInvoke?: (event: Event) => void; + label?: string; + disabled?: boolean; + required?: boolean; + hideAttachmentButton?: boolean; + wrapperId?: string; + hiddenInputId?: string; + editorId?: string; +} +declare const Editor: import("svelte").Component; +type Editor = ReturnType; +export default Editor; diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..1d8cc7c --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1 @@ +export { default as TrixEditor } from './Editor.svelte'; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..c0dd8e0 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,2 @@ +// Reexport your entry components here +export { default as TrixEditor } from './Editor.svelte'; diff --git a/dist/types.d.ts b/dist/types.d.ts new file mode 100644 index 0000000..e5d3cb4 --- /dev/null +++ b/dist/types.d.ts @@ -0,0 +1,184 @@ +export interface ITrixConfig { + attachments?: Attachments; + blockAttributes?: BlockAttributes; + browser?: Browser; + css?: Css; + dompurify?: Dompurify; + fileSize?: FileSize; + input?: Input; + keyNames?: KeyNames; + lang?: Lang; + parser?: Parser; + textAttributes?: TextAttributes; + toolbar?: Toolbar; + undo?: Undo; +} +interface Undo { + interval?: number; +} +interface Toolbar { + getDefaultHTML?: () => any; +} +interface TextAttributes { + bold?: Bold; + italic?: Bold; + href?: Href; + strike?: Bold; + frozen?: Frozen; +} +interface Frozen { + style?: Style; +} +interface Style { + backgroundColor?: string; +} +interface Href { + groupTagName?: string; +} +interface Bold { + tagName?: string; + inheritable?: boolean; +} +interface Parser { + removeBlankTableCells?: boolean; + tableCellSeparator?: string; + tableRowSeparator?: string; +} +interface Lang { + attachFiles?: string; + bold?: string; + bullets?: string; + byte?: string; + bytes?: string; + captionPlaceholder?: string; + code?: string; + heading1?: string; + indent?: string; + italic?: string; + link?: string; + numbers?: string; + outdent?: string; + quote?: string; + redo?: string; + remove?: string; + strike?: string; + undo?: string; + unlink?: string; + url?: string; + urlPlaceholder?: string; + GB?: string; + KB?: string; + MB?: string; + PB?: string; + TB?: string; +} +interface KeyNames { + '8'?: string; + '9'?: string; + '13'?: string; + '27'?: string; + '37'?: string; + '39'?: string; + '46'?: string; + '68'?: string; + '72'?: string; + '79'?: string; +} +interface Input { + level2Enabled?: boolean; +} +interface FileSize { + prefix?: string; + precision?: number; + formatter?: (t3: string) => string; +} +interface Dompurify { + ADD_ATTR?: string[]; + SAFE_FOR_XML?: boolean; + RETURN_DOM?: boolean; +} +interface Css { + attachment?: string; + attachmentCaption?: string; + attachmentCaptionEditor?: string; + attachmentMetadata?: string; + attachmentMetadataContainer?: string; + attachmentName?: string; + attachmentProgress?: string; + attachmentSize?: string; + attachmentToolbar?: string; + attachmentGallery?: string; +} +interface Browser { + composesExistingText?: boolean; + recentAndroid?: boolean; + samsungAndroid?: boolean; + forcesObjectResizing?: boolean; + supportsInputEvents?: boolean; +} +interface BlockAttributes { + default?: Default; + quote?: Quote; + heading1?: Heading1; + code?: Code; + bulletList?: Default; + bullet?: Bullet; + numberList?: Default; + number?: Bullet; + attachmentGallery?: AttachmentGallery; +} +interface AttachmentGallery { + tagName?: string; + exclusive?: boolean; + terminal?: boolean; + parse?: boolean; + group?: boolean; +} +interface Bullet { + tagName?: string; + listAttribute?: string; + group?: boolean; + nestable?: boolean; +} +interface Code { + tagName?: string; + terminal?: boolean; + htmlAttributes?: string[]; + text?: Text; +} +interface Text { + plaintext?: boolean; +} +interface Heading1 { + tagName?: string; + terminal?: boolean; + breakOnReturn?: boolean; + group?: boolean; +} +interface Quote { + tagName?: string; + nestable?: boolean; +} +interface Default { + tagName?: string; + parse?: boolean; +} +interface Attachments { + preview?: Preview; + file?: File; +} +interface File { + caption?: Caption2; +} +interface Caption2 { + size?: boolean; +} +interface Preview { + presentation?: string; + caption?: Caption; +} +interface Caption { + name?: boolean; + size?: boolean; +} +export {}; diff --git a/dist/types.js b/dist/types.js new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/dist/types.js @@ -0,0 +1 @@ +export {}; From 88b5863728356f8ba3aeb565bb2b8024e733f36f Mon Sep 17 00:00:00 2001 From: paul_webyte Date: Mon, 29 Sep 2025 15:11:58 +0200 Subject: [PATCH 4/5] include dist --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index a722ecf..3076c2b 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "lint": "prettier --check . && eslint ." }, "files": [ - "src", "dist", "!dist/**/*.test.*", "!dist/**/*.spec.*" From 308c3420df1f1928254afb9a88203fb01431c56a Mon Sep 17 00:00:00 2001 From: paul_webyte Date: Mon, 29 Sep 2025 15:48:36 +0200 Subject: [PATCH 5/5] fix: Ensure event handlers only trigger for the targeted editor element --- dist/Editor.svelte | 24 +++++++++++++----------- src/lib/Editor.svelte | 24 +++++++++++++----------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/dist/Editor.svelte b/dist/Editor.svelte index 790073c..e8a557b 100644 --- a/dist/Editor.svelte +++ b/dist/Editor.svelte @@ -295,56 +295,58 @@ }; const _onChange = (e: Event) => { - value = e?.target?.value; - if (onChange) { - onChange(e?.target?.value); + if (e.target === el) { + value = e?.target?.value; + if (onChange) { + onChange(e?.target?.value); + } } }; const _onPaste = (e: Event) => { - if (onPaste) { + if (e.target === el && onPaste) { onPaste(e); } }; const _onAttachmentAdd = (e: Event) => { - if (onAttachmentAdd) { + if (e.target === el && onAttachmentAdd) { onAttachmentAdd(e); } }; const _onAttachmentRemove = (e: Event) => { - if (onAttachmentRemove) { + if (e.target === el && onAttachmentRemove) { onAttachmentRemove(e); } }; const _onFileAccept = (e: Event) => { - if (onFileAccept) { + if (e.target === el && onFileAccept) { onFileAccept(e); } }; const _onSelectionChange = (e: Event) => { - if (onSelectionChange) { + if (e.target === el && onSelectionChange) { onSelectionChange(e); } }; const _onFocus = (e: Event) => { - if (onFocus) { + if (e.target === el && onFocus) { onFocus(e); } }; const _onBlur = (e: Event) => { - if (onBlur) { + if (e.target === el && onBlur) { onBlur(e); } }; const _onActionInvoke = (e: Event) => { - if (onActionInvoke) { + if (e.target === el && onActionInvoke) { onActionInvoke(e); } }; diff --git a/src/lib/Editor.svelte b/src/lib/Editor.svelte index 790073c..e8a557b 100644 --- a/src/lib/Editor.svelte +++ b/src/lib/Editor.svelte @@ -295,56 +295,58 @@ }; const _onChange = (e: Event) => { - value = e?.target?.value; - if (onChange) { - onChange(e?.target?.value); + if (e.target === el) { + value = e?.target?.value; + if (onChange) { + onChange(e?.target?.value); + } } }; const _onPaste = (e: Event) => { - if (onPaste) { + if (e.target === el && onPaste) { onPaste(e); } }; const _onAttachmentAdd = (e: Event) => { - if (onAttachmentAdd) { + if (e.target === el && onAttachmentAdd) { onAttachmentAdd(e); } }; const _onAttachmentRemove = (e: Event) => { - if (onAttachmentRemove) { + if (e.target === el && onAttachmentRemove) { onAttachmentRemove(e); } }; const _onFileAccept = (e: Event) => { - if (onFileAccept) { + if (e.target === el && onFileAccept) { onFileAccept(e); } }; const _onSelectionChange = (e: Event) => { - if (onSelectionChange) { + if (e.target === el && onSelectionChange) { onSelectionChange(e); } }; const _onFocus = (e: Event) => { - if (onFocus) { + if (e.target === el && onFocus) { onFocus(e); } }; const _onBlur = (e: Event) => { - if (onBlur) { + if (e.target === el && onBlur) { onBlur(e); } }; const _onActionInvoke = (e: Event) => { - if (onActionInvoke) { + if (e.target === el && onActionInvoke) { onActionInvoke(e); } };