Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(adapters): caret adapter basic implementation #58

Merged
merged 9 commits into from
Nov 20, 2023
Merged

Conversation

neSpecc
Copy link
Contributor

@neSpecc neSpecc commented Nov 18, 2023

Basic implementation of the Caret DOM Adapter

  • Binds "selectionchange" event on document using "useSelectionChange" util providing "on" and "off" methods.
  • "useSelectionChange" creates a singleton and delegates listeners
  • Caret Adapter provides attachInput and detachInput methods
  • Caret Adapter extends EventTarget and emits the change event — used for playground purposes. Could be removed later
  • Playground contains Input components with Caret Adapter attached. Caret index is displayed at the right side of the input
image

Copy link

github-actions bot commented Nov 18, 2023

✅ Mutation testing passed

Report: https://dashboard.stryker-mutator.io/reports/github.com/editor-js/document-model/PR-58

Mutated files
src/utils/EventBus/index.ts
src/utils/index.ts
src/index.ts

Copy link

github-actions bot commented Nov 18, 2023

Coverage report for ./packages/model

St.
Category Percentage Covered / Total
🟢 Statements 100% 589/589
🟢 Branches 99.27% 136/137
🟢 Functions 99.33% 149/150
🟢 Lines 100% 564/564

Test suite run success

347 tests passing in 20 suites.

Report generated by 🧪jest coverage report action from 4c73832

/**
* Caret index is a tuple of start and end offset of a caret
*/
export type CaretIndex = [number, number];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might re-use type from model

/**
* Index stores start and end offset of a caret depending on a root of input
*/
#index: CaretIndex = [0, 0];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If caret is not set, what index it would be?

*
* @param selection - changed document selection
*/
#onDocumentSelectionChange = (selection: Selection | null): void => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could try to optimise memory usage with a method (not a property). And pass the context from outside

* @param model - EditorJSModel instance
* @param blockIndex - index of a block that contains input
*/
constructor(private readonly model: EditorJSModel, private readonly blockIndex: number) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be better to use # modifier. It would add more lines, but properties would be "really" private

* @param input - input to watch caret change
* @param dataKey - key of data property in block's data that contains input's value
*/
public attachInput(input: HTMLElement, dataKey: string): void {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need an index of the block as well. Also dataKey is not used

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

block index passed to the constructor. It is not used right now as well as model and dataKey. I'll implement a working with model later

* Unsubscribes from input caret change
*/
public detachInput(): void {
this.#offSelectionChange(this.#onDocumentSelectionChange);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could set null to #input


const range = selection.getRangeAt(0);

return this.#input?.contains(range.startContainer) ?? false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok for now, but we should think of cross-block selection case in the future

*
* @param node - node to check
*/
function isLineBreak(node: Node): boolean {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you move it below the exported members

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There will be ES Lint error: using of method before it declared

export const useSelectionChange = createSingleton(() => {
const subscribers = new Set<Subscriber>();

document.addEventListener('selectionchange', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need an API to remove the listener?

/**
* Returns absolute caret index related to input
*/
public get index(): TextRange {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be above private methods

/**
* Used to save context of the callback.
*/
context: CaretAdapter;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be not only CaretAdapter in the future I suppose. Could be just unknown


const range = selection.getRangeAt(0);

return input.contains(range.startContainer) ?? false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you just add todo to think of cross-block selection later. To know where to look once we'll start working on it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can actually use range.intersectsNode(input)

*/
export const useSelectionChange = createSingleton(() => {
/**
* User to iterate over all inputs and check if selection is related to them.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* User to iterate over all inputs and check if selection is related to them.
* Used to iterate over all inputs and check if selection is related to them.

/**
* WeakMap that stores subscribers for each input.
*/
const subscribers = new WeakMap<InputWithCaret, Subscriber>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could there be more than one subscriber for an input?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no

return {
on,
off,
destroy,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once destroyed, there'll be no way to re-init as createSingletone will always return the same instance

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've exported init method as well. The destroy flow is not designed yet, so it will be implemented once we can understand it

@neSpecc neSpecc requested a review from gohabereg November 19, 2023 00:36
@neSpecc neSpecc requested a review from ilyamore88 November 19, 2023 00:42
@neSpecc neSpecc added this pull request to the merge queue Nov 20, 2023
Merged via the queue into main with commit 38cfaa1 Nov 20, 2023
5 checks passed
@neSpecc neSpecc deleted the feat/caret-adapter branch November 20, 2023 20:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants