Fix inputs and values attachments to be data-first#127
Fix inputs and values attachments to be data-first#127
Conversation
|
⏭️ No files to mutate for |
Coverage report for
|
St.❔ |
Category | Percentage | Covered / Total |
|---|---|---|---|
| 🟢 | Statements | 96.43% | 27/28 |
| 🟢 | Branches | 86.96% | 20/23 |
| 🟢 | Functions | 100% | 5/5 |
| 🟢 | Lines | 96.43% | 27/28 |
Test suite run success
11 tests passing in 2 suites.
Report generated by 🧪jest coverage report action from 7fe438f
|
⏭️ No files to mutate for |
Coverage report for
|
St.❔ |
Category | Percentage | Covered / Total |
|---|---|---|---|
| 🟢 | Statements | 100% (+0.11% 🔼) |
905/905 |
| 🟢 | Branches | 99.63% (+0.01% 🔼) |
266/267 |
| 🟢 | Functions | 97.75% (-0.42% 🔻) |
217/222 |
| 🟢 | Lines | 100% (+0.12% 🔼) |
869/869 |
Show files with reduced coverage 🔻
St.❔ |
File | Statements | Branches | Functions | Lines |
|---|---|---|---|---|---|
| 🟢 | ... / index.ts |
100% | 100% | 93.94% (-6.06% 🔻) |
100% |
Test suite run success
475 tests passing in 25 suites.
Report generated by 🧪jest coverage report action from 7fe438f
Coverage report for
|
St.❔ |
Category | Percentage | Covered / Total |
|---|---|---|---|
| 🟢 | Statements | 100% | 128/128 |
| 🟢 | Branches | 100% | 46/46 |
| 🟢 | Functions | 100% | 27/27 |
| 🟢 | Lines | 100% | 119/119 |
Show new covered files 🐣
St.❔ |
File | Statements | Branches | Functions | Lines |
|---|---|---|---|---|---|
| 🟢 | tokens.ts | 100% | 100% | 100% | 100% |
| 🟢 | ... / SelectionManager.ts |
100% | 100% | 100% | 100% |
| 🟢 | ... / BlockManager.ts |
100% | 100% | 100% | 100% |
| 🟢 | api/BlocksAPI.ts | 100% | 100% | 100% | 100% |
| 🟢 | ... / BlockRenderer.ts |
100% | 100% | 100% | 100% |
| 🟢 | api/SelectionAPI.ts | 100% | 100% | 100% | 100% |
| 🟢 | ... / DocumentAPI.ts |
100% | 100% | 100% | 100% |
Test suite run success
73 tests passing in 7 suites.
Report generated by 🧪jest coverage report action from 7fe438f
Coverage report for
|
St.❔ |
Category | Percentage | Covered / Total |
|---|---|---|---|
| 🟡 | Statements | 70.97% | 22/31 |
| 🔴 | Branches | 20% | 1/5 |
| 🟡 | Functions | 75% | 6/8 |
| 🟡 | Lines | 68.97% | 20/29 |
Test suite run success
4 tests passing in 1 suite.
Report generated by 🧪jest coverage report action from 7fe438f
Coverage report for
|
St.❔ |
Category | Percentage | Covered / Total |
|---|---|---|---|
| 🟢 | Statements | 92.51% | 358/387 |
| 🟢 | Branches | 85.51% | 118/138 |
| 🟢 | Functions | 98.15% | 53/54 |
| 🟢 | Lines | 92.41% | 353/382 |
Test suite run success
117 tests passing in 7 suites.
Report generated by 🧪jest coverage report action from 7fe438f
|
⏭️ No files to mutate for |
| } | ||
|
|
||
| wrapper.classList.add('editorjs-paragraph'); | ||
| onUpdate = (key: string, type: 'text' | 'value'): HTMLElement => { |
There was a problem hiding this comment.
| onUpdate = (key: string, type: 'text' | 'value'): HTMLElement => { | |
| onDataUpdated = (key: string, type: 'text' | 'value'): HTMLElement => { |
| this.attachInput(dataKey, input); | ||
| } | ||
|
|
||
| public registerKey(keyRaw: string, type: 'text' | 'value', initialData?: unknown): void { |
There was a problem hiding this comment.
| public registerKey(keyRaw: string, type: 'text' | 'value', initialData?: unknown): void { | |
| public registerDataKey(keyRaw: string, type: 'text' | 'value', initialData?: unknown): void { |
There was a problem hiding this comment.
Pull request overview
This PR refactors the editor’s adapter/tool integration to be “data-first” by introducing an abstract BlockToolAdapter in the SDK, moving DOM-specific interdependencies into a dedicated DOM adapter plugin, and migrating DI from typedi to inversify across core + dom-adapters. It also adjusts model event timing (sync vs microtask) to preserve event ordering guarantees.
Changes:
- Introduces an SDK
BlockToolAdapterbase class + new adapter-related events/types, and updates tool constructor typing to support adapter specialization. - Adds
DOMAdaptersas a core plugin that owns DOM adapter wiring via an Inversify container, including a sharedInputsRegistry. - Migrates core and dom-adapters DI from
typeditoinversify, and updates event dispatch ordering in the model (BlockAddedsync;BlockRemoved/data-node events microtasked).
Reviewed changes
Copilot reviewed 43 out of 45 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| yarn.lock | Adds Inversify dependency graph; removes typedi. |
| packages/sdk/src/tools/facades/InlineToolFacade.ts | Caches inline tool instance to enforce singleton behavior. |
| packages/sdk/src/tools/facades/BaseToolFacade.ts | Broadens BlockToolConstructor typing to allow generic adapters. |
| packages/sdk/src/entities/index.ts | Exports BlockToolAdapter as a runtime value; adds adapter plugin type export. |
| packages/sdk/src/entities/EventBus/events/index.ts | Re-exports adapter events barrel. |
| packages/sdk/src/entities/EventBus/events/adapter/index.ts | Adds adapter event barrel exports. |
| packages/sdk/src/entities/EventBus/events/adapter/ValueNodeChanged.ts | Adds adapter event for value updates. |
| packages/sdk/src/entities/EventBus/events/adapter/KeyRemoved.ts | Adds adapter event for key removal. |
| packages/sdk/src/entities/EventBus/events/adapter/KeyAdded.ts | Adds adapter event for key addition. |
| packages/sdk/src/entities/EventBus/events/adapter/AdapterEventType.ts | Defines adapter event type namespace. |
| packages/sdk/src/entities/EntityType.ts | Adds PluginType.Adapter for adapter plugins. |
| packages/sdk/src/entities/EditorjsPlugin.ts | Makes plugin constructor generic over params/instance. |
| packages/sdk/src/entities/EditorjsAdapterPlugin.ts | Introduces adapter plugin interfaces + constructor typing. |
| packages/sdk/src/entities/BlockToolAdapter.ts | Replaces interface with abstract base class that listens to model events and emits adapter events. |
| packages/sdk/src/entities/BlockTool.ts | Adds adapter generic to block tool constructor/options typing. |
| packages/playground/src/components/index.ts | Removes Input component export. |
| packages/playground/src/components/Input.vue | Removes old adapter-attached input component. |
| packages/model/src/entities/EditorDocument/index.ts | Adjusts block event timing; adds getDataNode. |
| packages/model/src/entities/BlockNode/index.ts | Adds getDataNode; microtasks data-node add/remove events; changes initialization path. |
| packages/model/src/EditorJSModel.ts | Adds getCaret and getDataNode passthroughs. |
| packages/dom-adapters/tsconfig.json | Enables emitDecoratorMetadata for DI. |
| packages/dom-adapters/src/tokens.ts | Adds Inversify token(s) for dom-adapters container. |
| packages/dom-adapters/src/index.ts | Adds DOMAdapters adapter plugin with Inversify container + adapter factory. |
| packages/dom-adapters/src/InputsRegistry/index.ts | Adds shared blockIndex/dataKey→HTMLElement registry. |
| packages/dom-adapters/src/FormattingAdapter/index.ts | Switches to Inversify DI and tool-loaded wiring via core EventBus. |
| packages/dom-adapters/src/CaretAdapter/index.ts | Switches to shared InputsRegistry and Inversify; removes block attachment tracking. |
| packages/dom-adapters/src/BlockToolAdapter/index.ts | Replaces prior adapter impl with DOMBlockToolAdapter extending SDK base adapter and using registry. |
| packages/dom-adapters/package.json | Adds inversify + reflect-metadata. |
| packages/core/src/tools/internal/block-tools/paragraph/index.ts | Updates Paragraph tool to use DOM adapter methods and adapter key events. |
| packages/core/src/tools/ToolsManager.ts | Migrates DI from typedi to Inversify token injection. |
| packages/core/src/tokens.ts | Adds core Inversify tokens (EditorConfig, Adapter). |
| packages/core/src/index.ts | Replaces typedi container with Inversify; introduces plugin container and adapter initialization. |
| packages/core/src/components/SelectionManager.ts | Removes reliance on FormattingAdapter for apply; formats directly via model using caret segments. |
| packages/core/src/components/SelectionManager.spec.ts | Updates mocks/tests to match new SelectionManager behavior. |
| packages/core/src/components/BlockRenderer.ts | New component: renders blocks from model block events using adapter plugin. |
| packages/core/src/components/BlockRenderer.spec.ts | Adds unit tests for BlockRenderer. |
| packages/core/src/components/BlockManager.ts | Removes model event/render responsibilities (delegated to BlockRenderer); updates caret lookup. |
| packages/core/src/components/BlockManager.spec.ts | Updates tests for BlocksManager refactor and caret lookup changes. |
| packages/core/src/api/index.ts | Migrates API DI to Inversify property injection. |
| packages/core/src/api/SelectionAPI.ts | Migrates SelectionAPI DI to Inversify. |
| packages/core/src/api/DocumentAPI/DocumentAPI.ts | Migrates DocumentAPI DI to Inversify. |
| packages/core/src/api/BlocksAPI.ts | Migrates BlocksAPI DI to Inversify token injection. |
| packages/core/src/api/BlocksAPI.integration.spec.ts | Updates for microtasked BlockRemovedEvent. |
| packages/core/package.json | Replaces typedi with inversify; keeps reflect-metadata. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Coverage report for
|
St.❔ |
Category | Percentage | Covered / Total |
|---|---|---|---|
| 🟢 | Statements | 100% | 129/129 |
| 🟢 | Branches | 100% | 46/46 |
| 🟢 | Functions | 100% | 27/27 |
| 🟢 | Lines | 100% | 120/120 |
Test suite run success
73 tests passing in 7 suites.
Report generated by 🧪jest coverage report action from ba5077d
| * - dispatches BlockAddedCoreEvent with the rendered UI element | ||
| * @param event - BlockAddedEvent | ||
| */ | ||
| async #handleBlockAddedEvent(event: BlockAddedEvent): Promise<void> { |
| case event instanceof BlockAddedEvent: | ||
| return this.#handleBlockAddedEvent(event); | ||
| case event instanceof BlockRemovedEvent: | ||
| this.#handleBlockRemovedEvent(event); | ||
| break; |
| #config: CoreConfig; | ||
| #config: CoreConfigValidated; | ||
|
|
||
| #toolsManager: ToolsManager; |
| #wrapper: HTMLDivElement | undefined; | ||
|
|
||
| #paragraph: HTMLDivElement | undefined; |
| * | ||
| */ | ||
| #data: ParagraphData; | ||
| private get wrapper(): HTMLDivElement { |
| if (dataKey === undefined) { | ||
| this.#inputs.splice(blockIndex, 1); | ||
|
|
||
| return; | ||
| } |
There was a problem hiding this comment.
seems like we should throw in this case, since we expose insertBlock method for registration of the block — insert of the input to the nonexistent block should be forbidden
There was a problem hiding this comment.
Not sure what's this comment about. This is unregister function
| Params extends EditorjsPluginParams = EditorjsPluginParams, | ||
| Instance extends EditorjsPlugin = EditorjsPlugin |
| * Cached instance of the inline tool | ||
| * Inline tools are singletons — the same instance is reused across all calls to create() | ||
| */ | ||
| #instance: IInlineTool | undefined; |
There was a problem hiding this comment.
can't see where actual InlineTool is used, maybe importing InlineTool as IInlineTool, is redundant?
| this.#toolsManager | ||
| .inlineTools | ||
| .entries() | ||
| .map(([name, facade]) => [createInlineToolName(name), facade.create()]) |
There was a problem hiding this comment.
is it ok to create a new tool instance for every caret/selection change?
There was a problem hiding this comment.
It's not created every time. InlineToolFacade returns the same instance for each create call
| #paragraph: HTMLDivElement | undefined; | ||
|
|
||
| /** | ||
| * Returns tool's wrapper, creates one if it doesn't exist yet |
There was a problem hiding this comment.
Write here why editor needs a wrapper for each tool
| } | ||
|
|
||
| wrapper.classList.add('editorjs-paragraph'); | ||
| #onUpdate = (event: KeyAddedEvent | KeyRemovedEvent): void => { |
| wrapper.contentEditable = 'true'; | ||
| wrapper.style.outline = 'none'; | ||
| wrapper.style.whiteSpace = 'pre-wrap'; | ||
| if (event instanceof KeyRemovedEvent) { |
| return; | ||
| } | ||
|
|
||
| const paragraph = document.createElement('div'); |
There was a problem hiding this comment.
it will recreate a div for each model update? and caret will be dropped and set again?
There was a problem hiding this comment.
No, only when a new key is being registered. For paragraph there's only one key — text
|
|
||
| /** | ||
| * Attaches or re-attaches input to the model using key | ||
| * It handles beforeinput events and updates model data |
There was a problem hiding this comment.
It handles beforeinput events and updates model data
where?
| } | ||
|
|
||
| /** | ||
| * Attaches or re-attaches input to the model using key |
There was a problem hiding this comment.
to the model using key
Explain what is Input Registry
| * @param keyRaw - key of the input to remove | ||
| * Returns the (dataKey → element) map for this block from the shared registry. | ||
| */ | ||
| public detachInput(keyRaw: string): void { |
There was a problem hiding this comment.
why detach input/value has been removed?
There was a problem hiding this comment.
Inputs get get removed if setInput called with input as undefined. Paragraph calls it when KeyRemovedEvent is dispatched on the adapter
Values now are not being 'attached'. Tool would just subscribe to adapter ValueChangedEvent to handle the updates. However, I'm going to return an update function from registerValueKey method so that tool can update value if needed
| const registry = this.#iocContainer.get(InputsRegistry); | ||
|
|
||
| model.addEventListener(EventType.Changed, (event) => { | ||
| if (event instanceof BlockRemovedEvent) { |
There was a problem hiding this comment.
why BlockRemovedEvent event is handled not in the BlockRenderer?
If you remove a Block form Registry, so it is expected to see adding a Block to Registry by BlockAddedEvent as well.
There was a problem hiding this comment.
It is handled here to remove adapter and the block from the registry. Additions are handled in createBlockToolAdapter method.
I'm going to implement block ids later which should remove the need to update the indexes
| .build(); | ||
|
|
||
| this.dispatchEvent(new DataNodeAddedEvent(index, data, getContext<string | number>()!)); | ||
| queueMicrotask(() => { |
There was a problem hiding this comment.
jsdoc explaining the reason of postponing should be added here
Notes: