Skip to content
This repository was archived by the owner on May 5, 2021. It is now read-only.

Commit 49f324f

Browse files
Gorashdmo-odoo
authored andcommitted
[IMP] DomLayout,Toolbar: can render select into block to apply css
1 parent 544b96f commit 49f324f

File tree

11 files changed

+172
-39
lines changed

11 files changed

+172
-39
lines changed

examples/layout/layout.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { ParagraphNode } from '../../packages/plugin-paragraph/src/ParagraphNode
2727
import { ThemeNode } from '../../packages/plugin-theme/src/ThemeNode';
2828
import { Iframe } from '../../packages/plugin-iframe/src/Iframe';
2929
import { Table } from '../../packages/plugin-table/src/Table';
30+
import { NativeSelect } from '../../packages/plugin-native-select/src/NativeSelect';
3031

3132
const target = document.getElementById('contentToEdit');
3233
jabberwocky.init(target);
@@ -169,6 +170,7 @@ editor.configure(DomLayout, {
169170
editor.configure(Table, {
170171
inlineUI: true,
171172
});
173+
editor.load(NativeSelect);
172174

173175
const toolbarConfig = editor.configuration.plugins.find(
174176
config => config[0] === Toolbar,

packages/plugin-dom-layout/src/ActionableDomObjectRenderer.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,18 @@ export class ActionableDomObjectRenderer extends NodeRenderer<DomObject> {
114114
const domVisible = element.style.display !== 'none';
115115
if (visible !== domVisible) {
116116
if (visible) {
117-
element.style.display = 'inline-block';
117+
element.style.display = '';
118118
} else {
119119
element.style.display = 'none';
120120
}
121121
}
122+
123+
if (select) {
124+
const domSelect = element.closest('jw-select');
125+
if (domSelect) {
126+
domSelect.querySelector('jw-button').innerHTML = element.innerHTML;
127+
}
128+
}
122129
}
123130
}
124131
}

packages/plugin-dom-layout/src/ActionableGroupDomObjectRenderer.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import {
55
import { NodeRenderer } from '../../plugin-renderer/src/NodeRenderer';
66
import { ActionableGroupNode } from '../../plugin-layout/src/ActionableGroupNode';
77
import { ActionableNode } from '../../plugin-layout/src/ActionableNode';
8+
import { Predicate } from '../../core/src/VNodes/VNode';
89

910
export class ActionableGroupDomObjectRenderer extends NodeRenderer<DomObject> {
1011
static id = DomObjectRenderingEngine.id;
1112
engine: DomObjectRenderingEngine;
12-
predicate = ActionableGroupNode;
13+
predicate: Predicate = ActionableGroupNode;
1314

1415
actionableGroupNodes = new Map<ActionableGroupNode, HTMLElement>();
1516

@@ -28,19 +29,41 @@ export class ActionableGroupDomObjectRenderer extends NodeRenderer<DomObject> {
2829
) {
2930
return { children: [] };
3031
} else if (group.ancestor(ActionableGroupNode)) {
31-
return this._renderSelect(group);
32+
return this._renderBlockSelect(group);
3233
} else {
3334
return this._renderGroup(group);
3435
}
3536
}
36-
private _renderSelect(group: ActionableGroupNode): DomObject {
37+
private _renderBlockSelect(group: ActionableGroupNode): DomObject {
38+
const mousedownHandler = (ev: MouseEvent): void => ev.preventDefault();
39+
40+
let clickHandler: (ev: MouseEvent) => void;
41+
let open = false;
3742
const objectSelect: DomObject = {
38-
tag: 'SELECT',
39-
children: [{ tag: 'OPTION' }, ...group.children()],
40-
attach: (el: HTMLSelectElement): void => {
43+
tag: 'JW-SELECT',
44+
children: [
45+
{ tag: 'JW-BUTTON', children: [{ text: '\u00A0' }] },
46+
{ tag: 'JW-GROUP', children: group.children() },
47+
],
48+
attach: (el: HTMLElement): void => {
49+
clickHandler = (ev: MouseEvent): void => {
50+
const inSelect =
51+
(ev.target as Node).nodeType === Node.ELEMENT_NODE &&
52+
(ev.target as Element).closest('jw-select') === el;
53+
if ((!inSelect && open) || ev.currentTarget !== document) {
54+
open = !open;
55+
el.setAttribute('aria-pressed', open.toString());
56+
}
57+
};
58+
el.addEventListener('mousedown', mousedownHandler);
59+
el.addEventListener('click', clickHandler);
60+
document.addEventListener('click', clickHandler);
4161
this.actionableGroupNodes.set(group, el);
4262
},
43-
detach: (): void => {
63+
detach: (el: HTMLElement): void => {
64+
el.removeEventListener('mousedown', mousedownHandler);
65+
el.removeEventListener('click', clickHandler);
66+
document.removeEventListener('click', clickHandler);
4467
this.actionableGroupNodes.delete(group);
4568
},
4669
};
@@ -72,7 +95,7 @@ export class ActionableGroupDomObjectRenderer extends NodeRenderer<DomObject> {
7295
if (invisible) {
7396
element.style.display = 'none';
7497
} else {
75-
element.style.display = 'inline-block';
98+
element.style.display = '';
7699
}
77100
}
78101
}

packages/plugin-dom-layout/src/DomLayout.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import { Keymap } from '../../plugin-keymap/src/Keymap';
1818
import { CommandIdentifier } from '../../core/src/Dispatcher';
1919
import { ActionableDomObjectRenderer } from './ActionableDomObjectRenderer';
2020
import { ActionableGroupDomObjectRenderer } from './ActionableGroupDomObjectRenderer';
21-
import { ActionableGroupSelectItemDomObjectRenderer } from './ActionableGroupSelectItemDomObjectRenderer';
2221
import { LabelDomObjectRenderer } from './LabelDomObjectRenderer';
2322
import { SeparatorDomObjectRenderer } from './SeparatorDomObjectRenderer';
2423
import { RuleProperty } from '../../core/src/Mode';
@@ -41,7 +40,6 @@ export class DomLayout<T extends DomLayoutConfig = DomLayoutConfig> extends JWPl
4140
renderers: [
4241
ZoneDomObjectRenderer,
4342
LayoutContainerDomObjectRenderer,
44-
ActionableGroupSelectItemDomObjectRenderer,
4543
ActionableGroupDomObjectRenderer,
4644
ActionableDomObjectRenderer,
4745
LabelDomObjectRenderer,

packages/plugin-dom-layout/test/DomLayout.test.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2674,7 +2674,7 @@ describe('DomLayout', () => {
26742674
await editor.execCommand('deleteForward');
26752675
expect(document.querySelector('jw-test').innerHTML).to.equal('<p>abc</p>');
26762676
expect(mutationNumber).to.equal(
2677-
3,
2677+
4,
26782678
'remove <p>, remove <br>, update toolbar history button',
26792679
);
26802680
},
@@ -2691,14 +2691,14 @@ describe('DomLayout', () => {
26912691
'<p>a<span style="background-color: red;">bc</span>d</p>',
26922692
);
26932693
expect(mutationNumber).to.equal(
2694-
6,
2694+
7,
26952695
'update text, add <span>, add text, add text, 2 update toolbar',
26962696
);
26972697
mutationNumber = 0;
26982698
await editor.execCommand('uncolorBackground');
26992699
expect(document.querySelector('jw-test').innerHTML).to.equal('<p>abcd</p>');
27002700
expect(mutationNumber).to.equal(
2701-
3,
2701+
4,
27022702
'remove <span>, add text, update toolbar',
27032703
);
27042704
},
@@ -2717,7 +2717,7 @@ describe('DomLayout', () => {
27172717
'<p>a<span>b</span>c<span style="color: green;">d</span>e</p>',
27182718
);
27192719
expect(mutationNumber).to.equal(
2720-
6,
2720+
7,
27212721
'remove 3 formats + remove 2 empty styles, update toolbar',
27222722
);
27232723
},
@@ -2888,8 +2888,8 @@ describe('DomLayout', () => {
28882888
triggerEvent(domEditable, 'mousedown', {
28892889
button: 2,
28902890
detail: 1,
2891-
clientX: 30,
2892-
clientY: 65,
2891+
clientX: 65,
2892+
clientY: 30,
28932893
});
28942894
const text = domEditable.lastChild as Text;
28952895
setDomSelection(
@@ -2898,6 +2898,7 @@ describe('DomLayout', () => {
28982898
text,
28992899
text.textContent.length - 2,
29002900
);
2901+
await nextTick();
29012902
setDomSelection(
29022903
text,
29032904
text.textContent.length - 2,
@@ -2908,7 +2909,7 @@ describe('DomLayout', () => {
29082909
triggerEvent(domEditable, 'click', {
29092910
button: 2,
29102911
detail: 0,
2911-
clientX: 65,
2912+
clientX: 80,
29122913
clientY: 30,
29132914
});
29142915
triggerEvent(domEditable, 'mouseup', {
@@ -2925,6 +2926,11 @@ describe('DomLayout', () => {
29252926

29262927
await nextTick();
29272928
await nextTick();
2929+
2930+
expect(domEditable.childNodes.length).to.equal(
2931+
2,
2932+
'at least keep the first text node',
2933+
);
29282934
},
29292935
contentAfter: 'aaabbbc[cc]',
29302936
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { JWPlugin, JWPluginConfig } from '../../core/src/JWPlugin';
2+
import { Renderer } from '../../plugin-renderer/src/Renderer';
3+
import { Loadables } from '../../core/src/JWEditor';
4+
import { NativeSelectVNodeDomObjectRenderer } from './NativeSelectVNodeDomObjectRenderer';
5+
import { NativeSelectActionableGroupDomObjectRenderer } from './NativeSelectActionableGroupDomObjectRenderer';
6+
7+
export class NativeSelect<T extends JWPluginConfig = JWPluginConfig> extends JWPlugin<T> {
8+
readonly loadables: Loadables<Renderer> = {
9+
renderers: [
10+
NativeSelectVNodeDomObjectRenderer,
11+
NativeSelectActionableGroupDomObjectRenderer,
12+
],
13+
};
14+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { DomObject } from '../../plugin-renderer-dom-object/src/DomObjectRenderingEngine';
2+
import { ActionableGroupNode } from '../../plugin-layout/src/ActionableGroupNode';
3+
import { ActionableGroupDomObjectRenderer } from '../../plugin-dom-layout/src/ActionableGroupDomObjectRenderer';
4+
import { VNode } from '../../core/src/VNodes/VNode';
5+
6+
export class NativeSelectActionableGroupDomObjectRenderer extends ActionableGroupDomObjectRenderer {
7+
predicate = (node: VNode): boolean =>
8+
node instanceof ActionableGroupNode && !!node.ancestor(ActionableGroupNode);
9+
10+
async render(group: ActionableGroupNode): Promise<DomObject> {
11+
if (
12+
!group.descendants(node => !(node instanceof ActionableGroupNode) && node.tangible)
13+
.length
14+
) {
15+
return { children: [] };
16+
} else {
17+
const objectSelect: DomObject = {
18+
tag: 'SELECT',
19+
children: [{ tag: 'OPTION' }, ...group.children()],
20+
attach: (el: HTMLSelectElement): void => {
21+
this.actionableGroupNodes.set(group, el);
22+
},
23+
detach: (): void => {
24+
this.actionableGroupNodes.delete(group);
25+
},
26+
};
27+
return objectSelect;
28+
}
29+
}
30+
}

packages/plugin-dom-layout/src/ActionableGroupSelectItemDomObjectRenderer.ts renamed to packages/plugin-native-select/src/NativeSelectVNodeDomObjectRenderer.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,16 @@ import { SeparatorNode } from '../../core/src/VNodes/SeparatorNode';
1010
import { VNode } from '../../core/src/VNodes/VNode';
1111
import { LabelNode } from '../../plugin-layout/src/LabelNode';
1212
import { ZoneNode } from '../../plugin-layout/src/ZoneNode';
13-
import { DomObjectActionable } from './ActionableDomObjectRenderer';
13+
import { DomObjectActionable } from '../../plugin-dom-layout/src/ActionableDomObjectRenderer';
1414
import { RenderingEngineWorker } from '../../plugin-renderer/src/RenderingEngineCache';
1515

16-
export class ActionableGroupSelectItemDomObjectRenderer extends NodeRenderer<DomObject> {
16+
export class NativeSelectVNodeDomObjectRenderer extends NodeRenderer<DomObject> {
1717
static id = DomObjectRenderingEngine.id;
1818
engine: DomObjectRenderingEngine;
1919
predicate = (node: VNode): boolean => node.ancestors(ActionableGroupNode).length >= 2;
2020

2121
actionableGroupSelectItemNodes = new Map<ActionableNode, HTMLElement>();
22+
useBlockIsteadOfSelect: boolean;
2223

2324
constructor(engine: DomObjectRenderingEngine) {
2425
super(engine);
@@ -126,7 +127,10 @@ export class ActionableGroupSelectItemDomObjectRenderer extends NodeRenderer<Dom
126127
if (select.toString() !== attrSelected) {
127128
if (select) {
128129
element.setAttribute('selected', 'true');
129-
element.closest('select').value = element.getAttribute('value');
130+
const domSelect = element.closest('select');
131+
if (domSelect) {
132+
domSelect.value = element.getAttribute('value');
133+
}
130134
} else {
131135
element.removeAttribute('selected');
132136
}

packages/plugin-template/test/Template.test.ts

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -308,14 +308,18 @@ describe('Template', () => {
308308
'<jw-editor>',
309309
'<jw-toolbar>',
310310
'<jw-group>',
311-
'<select>',
312-
'<option></option>',
313-
'<option value="template-template1">Good 1</option>',
314-
'</select>',
315-
'<select>',
316-
'<option></option>',
317-
'<option value="template-template2">Good 2</option>',
318-
'</select>',
311+
'<jw-select>',
312+
'<jw-button>&nbsp;</jw-button>',
313+
'<jw-group>',
314+
'<jw-button name="template-template1" aria-pressed="false">Good 1</jw-button>',
315+
'</jw-group>',
316+
'</jw-select>',
317+
'<jw-select>',
318+
'<jw-button>&nbsp;</jw-button>',
319+
'<jw-group>',
320+
'<jw-button name="template-template2" aria-pressed="false">Good 2</jw-button>',
321+
'</jw-group>',
322+
'</jw-select>',
319323
'</jw-group>',
320324
'</jw-toolbar>',
321325
'<article><br></article>',
@@ -383,28 +387,48 @@ describe('Template', () => {
383387
editor.configure(Toolbar, { layout: [['TemplateSelector']] });
384388
await editor.start();
385389

390+
/* eslint-disable prettier/prettier */
386391
expect(container.innerHTML).to.equal(
387392
[
388393
'<jw-editor>',
389-
'<jw-toolbar><jw-group><select><option></option><option value="template-template1">Good 1</option><option value="template-template2">Good 2</option></select></jw-group></jw-toolbar>',
390-
'<article><br></article>',
394+
'<jw-toolbar>'+
395+
'<jw-group>'+
396+
'<jw-select>'+
397+
'<jw-button>&nbsp;</jw-button>'+
398+
'<jw-group>',
399+
'<jw-button name="template-template1" aria-pressed="false">Good 1</jw-button>'+
400+
'<jw-button name="template-template2" aria-pressed="false">Good 2</jw-button>'+
401+
'</jw-group>'+
402+
'</jw-select>'+
403+
'</jw-group>'+
404+
'</jw-toolbar>',
405+
'<article><br></article>',
391406
'</jw-editor>',
392407
].join(''),
393408
);
409+
/* eslint-enable prettier/prettier */
410+
411+
await click(container.querySelector('jw-button[name="template-template1"]'));
394412

395-
const select = container.querySelector('select');
396-
select.value = 'template-template1';
397-
const dom = select.querySelector('option[value="template-template1"]');
398-
dom.setAttribute('selected', 'selected');
399-
select.dispatchEvent(new CustomEvent('change'));
400-
await nextTickFrame();
413+
/* eslint-disable prettier/prettier */
401414
expect(container.innerHTML).to.equal(
402415
[
403416
'<jw-editor>',
404-
'<jw-toolbar><jw-group><select><option></option><option value="template-template1">Good 1</option><option value="template-template2">Good 2</option></select></jw-group></jw-toolbar>',
405-
'<div class="row"><div class="col">text default</div></div>',
417+
'<jw-toolbar>'+
418+
'<jw-group>'+
419+
'<jw-select>'+
420+
'<jw-button>&nbsp;</jw-button>'+
421+
'<jw-group>',
422+
'<jw-button name="template-template1" aria-pressed="false">Good 1</jw-button>'+
423+
'<jw-button name="template-template2" aria-pressed="false">Good 2</jw-button>'+
424+
'</jw-group>'+
425+
'</jw-select>'+
426+
'</jw-group>'+
427+
'</jw-toolbar>',
428+
'<div class="row"><div class="col">text default</div></div>',
406429
'</jw-editor>',
407430
].join(''),
408431
);
432+
/* eslint-enable prettier/prettier */
409433
});
410434
});

packages/plugin-toolbar/assets/Toolbar.css

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,30 @@ jw-toolbar option.label {
6565
font-weight: bold;
6666
}
6767

68+
jw-toolbar jw-select > jw-group jw-button {
69+
margin: 0;
70+
display: block;
71+
width: auto !important;
72+
height: auto !important;
73+
text-align: left;
74+
border-bottom-width: 0;
75+
}
76+
jw-toolbar jw-select > jw-group jw-button:last-child {
77+
border-bottom-width: 1px;
78+
}
79+
jw-toolbar jw-select > jw-group {
80+
z-index: 1;
81+
display: none;
82+
position: absolute;
83+
margin-left: 0px;
84+
margin-top: 8px;
85+
}
86+
jw-toolbar jw-select[aria-pressed="true"] > jw-group {
87+
display: block;
88+
}
89+
6890
jw-toolbar jw-button {
91+
user-select: none;
6992
background-color: white;
7093
color: black;
7194
border: 1px solid #68465f;

0 commit comments

Comments
 (0)