Skip to content

Commit c214076

Browse files
authored
fix(Checkbox): correct handling of checkbox click (#510)
1 parent d2fd709 commit c214076

File tree

7 files changed

+108
-70
lines changed

7 files changed

+108
-70
lines changed

src/extensions/yfm/Checkbox/CheckboxSpecs/const.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ export enum CheckboxNode {
66
Label = 'checkbox_label',
77
}
88

9+
export const CheckboxAttr = {
10+
Class: 'class',
11+
Type: 'type',
12+
Id: 'id',
13+
Checked: 'checked',
14+
For: 'for',
15+
} as const;
16+
917
export const idPrefix = 'yfm-editor-checkbox';
1018

1119
export const b = cn('checkbox');

src/extensions/yfm/Checkbox/CheckboxSpecs/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {parserTokens} from './parser';
99
import {getSchemaSpecs} from './schema';
1010
import {serializerTokens} from './serializer';
1111

12-
export {CheckboxNode} from './const';
12+
export {CheckboxAttr, CheckboxNode} from './const';
1313
export const checkboxType = nodeTypeFactory(CheckboxNode.Checkbox);
1414
export const checkboxLabelType = nodeTypeFactory(CheckboxNode.Label);
1515
export const checkboxInputType = nodeTypeFactory(CheckboxNode.Input);

src/extensions/yfm/Checkbox/CheckboxSpecs/schema.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type {NodeSpec} from 'prosemirror-model';
22

33
import {PlaceholderOptions} from '../../../../utils/placeholder';
4-
import {CheckboxNode, b} from '../const';
4+
5+
import {CheckboxAttr, CheckboxNode, b} from './const';
56

67
import type {CheckboxSpecsOptions} from './index';
78

@@ -18,7 +19,7 @@ export const getSchemaSpecs = (
1819
allowSelection: false,
1920
parseDOM: [],
2021
attrs: {
21-
class: {default: b()},
22+
[CheckboxAttr.Class]: {default: b()},
2223
},
2324
toDOM(node) {
2425
return ['div', node.attrs, 0];
@@ -30,9 +31,9 @@ export const getSchemaSpecs = (
3031
group: 'block',
3132
parseDOM: [],
3233
attrs: {
33-
type: {default: 'checkbox'},
34-
id: {default: null},
35-
checked: {default: null},
34+
[CheckboxAttr.Type]: {default: 'checkbox'},
35+
[CheckboxAttr.Id]: {default: null},
36+
[CheckboxAttr.Checked]: {default: null},
3637
},
3738
toDOM(node) {
3839
return ['div', node.attrs];
@@ -49,12 +50,12 @@ export const getSchemaSpecs = (
4950
{
5051
tag: `span[class="${b('label')}"]`,
5152
getAttrs: (node) => ({
52-
for: (node as Element).getAttribute('for') || '',
53+
[CheckboxAttr.For]: (node as Element).getAttribute(CheckboxAttr.For) || '',
5354
}),
5455
},
5556
],
5657
attrs: {
57-
for: {default: null},
58+
[CheckboxAttr.For]: {default: null},
5859
},
5960
escapeText: false,
6061
placeholder: {

src/extensions/yfm/Checkbox/CheckboxSpecs/serializer.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {SerializerNodeToken} from '../../../../core';
22
import {getPlaceholderContent} from '../../../../utils/placeholder';
3-
import {CheckboxNode} from '../const';
3+
4+
import {CheckboxAttr, CheckboxNode} from './const';
45

56
export const serializerTokens: Record<CheckboxNode, SerializerNodeToken> = {
67
[CheckboxNode.Checkbox]: (state, node) => {
@@ -9,7 +10,7 @@ export const serializerTokens: Record<CheckboxNode, SerializerNodeToken> = {
910
},
1011

1112
[CheckboxNode.Input]: (state, node) => {
12-
const checked = node.attrs.checked === 'true';
13+
const checked = node.attrs[CheckboxAttr.Checked] === 'true';
1314
state.write(`[${checked ? 'X' : ' '}] `);
1415
},
1516

src/extensions/yfm/Checkbox/const.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export {CheckboxNode, b} from './CheckboxSpecs/const';
1+
export {CheckboxAttr, CheckboxNode, b} from './CheckboxSpecs/const';

src/extensions/yfm/Checkbox/index.ts

Lines changed: 10 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,30 @@
1-
import {replaceParentNodeOfType} from 'prosemirror-utils';
2-
31
import type {Action, ExtensionAuto} from '../../../core';
42
import {nodeInputRule} from '../../../utils/inputrules';
5-
import {pType} from '../../base/BaseSchema';
63

7-
import {CheckboxSpecs, CheckboxSpecsOptions} from './CheckboxSpecs';
4+
import {CheckboxSpecs, type CheckboxSpecsOptions} from './CheckboxSpecs';
85
import {addCheckbox} from './actions';
9-
import {CheckboxNode, b} from './const';
6+
import {CheckboxInputView} from './nodeviews';
107
import {keymapPlugin} from './plugin';
118
import {checkboxInputType, checkboxType} from './utils';
129

1310
import './index.scss';
1411

1512
const checkboxAction = 'addCheckbox';
1613

17-
export {CheckboxNode, checkboxType, checkboxLabelType, checkboxInputType} from './CheckboxSpecs';
14+
export {
15+
CheckboxAttr,
16+
CheckboxNode,
17+
checkboxType,
18+
checkboxLabelType,
19+
checkboxInputType,
20+
} from './CheckboxSpecs';
1821

1922
export type CheckboxOptions = Pick<CheckboxSpecsOptions, 'checkboxLabelPlaceholder'> & {};
2023

2124
export const Checkbox: ExtensionAuto<CheckboxOptions> = (builder, opts) => {
2225
builder.use(CheckboxSpecs, {
2326
...opts,
24-
inputView: () => (node, view, getPos) => {
25-
const dom = document.createElement('input');
26-
27-
for (const attr in node.attrs) {
28-
if (node.attrs[attr]) dom.setAttribute(attr, node.attrs[attr]);
29-
}
30-
31-
dom.setAttribute('class', b('input'));
32-
33-
dom.addEventListener('click', (e) => {
34-
const elem = e.target as HTMLElement;
35-
const checkedAttr = elem.getAttribute('checked');
36-
const checked = checkedAttr ? '' : 'true';
37-
const pos = getPos();
38-
39-
if (pos !== undefined) {
40-
view.dispatch(
41-
view.state.tr.setNodeMarkup(pos, undefined, {
42-
...node.attrs,
43-
checked,
44-
}),
45-
);
46-
}
47-
48-
elem.setAttribute('checked', checked);
49-
});
50-
51-
return {
52-
dom,
53-
ignoreMutation: () => true,
54-
update: () => true,
55-
destroy() {
56-
const pos = getPos();
57-
if (pos !== undefined) {
58-
const resolved = view.state.doc.resolve(pos);
59-
if (
60-
resolved.parent.type.name === CheckboxNode.Checkbox &&
61-
resolved.parent.lastChild
62-
) {
63-
view.dispatch(
64-
replaceParentNodeOfType(
65-
resolved.parent.type,
66-
pType(view.state.schema).create(
67-
resolved.parent.lastChild.content,
68-
),
69-
)(view.state.tr),
70-
);
71-
}
72-
}
73-
dom.remove();
74-
},
75-
};
76-
},
27+
inputView: () => CheckboxInputView.create,
7728
});
7829

7930
builder
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import type {Node} from 'prosemirror-model';
2+
import type {EditorView, NodeView, NodeViewConstructor} from 'prosemirror-view';
3+
4+
import {CheckboxAttr, b} from './const';
5+
6+
export class CheckboxInputView implements NodeView {
7+
static create: NodeViewConstructor = (node, view, getPos) => new this(node, view, getPos);
8+
9+
dom: HTMLInputElement;
10+
11+
private _node: Node;
12+
private _view: EditorView;
13+
private _getPos: () => number | undefined;
14+
15+
private constructor(node: Node, view: EditorView, getPos: () => number | undefined) {
16+
this._node = node;
17+
this._view = view;
18+
this._getPos = getPos;
19+
20+
this.dom = this._createDomElem();
21+
this._applyNodeAttrsToDomElem();
22+
}
23+
24+
ignoreMutation(): boolean {
25+
return true;
26+
}
27+
28+
update(node: Node): boolean {
29+
if (node.type !== this._node.type) return false;
30+
31+
this._node = node;
32+
this._applyNodeAttrsToDomElem();
33+
34+
return true;
35+
}
36+
37+
destroy(): void {
38+
this.dom.removeEventListener('click', this._onInputClick);
39+
}
40+
41+
private _createDomElem(): HTMLInputElement {
42+
const dom = document.createElement('input');
43+
dom.setAttribute('class', b('input'));
44+
dom.addEventListener('click', this._onInputClick);
45+
return dom;
46+
}
47+
48+
private _applyNodeAttrsToDomElem(): void {
49+
const {dom, _node: node} = this;
50+
51+
for (const [key, value] of Object.entries(node.attrs)) {
52+
if (value) dom.setAttribute(key, value);
53+
else dom.removeAttribute(key);
54+
}
55+
56+
const checked = node.attrs[CheckboxAttr.Checked] === 'true';
57+
this.dom.checked = checked;
58+
}
59+
60+
private _onInputClick = (event: MouseEvent): void => {
61+
if (event.target instanceof HTMLInputElement) {
62+
const {checked} = event.target;
63+
const pos = this._getPos();
64+
65+
if (pos !== undefined)
66+
this._view.dispatch(
67+
this._view.state.tr.setNodeAttribute(
68+
pos,
69+
CheckboxAttr.Checked,
70+
checked ? 'true' : null,
71+
),
72+
);
73+
}
74+
75+
this._view.focus();
76+
};
77+
}

0 commit comments

Comments
 (0)