diff --git a/editors/demo/src/HHMI/layout/EditorElements.js b/editors/demo/src/HHMI/layout/EditorElements.js index 25b06611eb216a714fe7129137054be9be27d788..c2082c0f30d63d3c743386e34244886e05c0d387 100644 --- a/editors/demo/src/HHMI/layout/EditorElements.js +++ b/editors/demo/src/HHMI/layout/EditorElements.js @@ -12,5 +12,9 @@ const fontWriting = css` export default css` .ProseMirror { ${fontWriting} + + .ProseMirror-separator { + display: none !important; + } } `; diff --git a/wax-prosemirror-core/index.js b/wax-prosemirror-core/index.js index dff5e11ffd24a867a9308de89568e6ab06b0ae59..ba7f464dff6cdb110c5e96fce41cba97877b970d 100644 --- a/wax-prosemirror-core/index.js +++ b/wax-prosemirror-core/index.js @@ -47,3 +47,7 @@ export { default as MenuButton } from './src/components/ui/MenuButton'; export { default as icons } from './src/components/icons/icons'; export { default as Icon } from './src/components/icons/Icon'; export { default as Button } from './src/components/Button'; + +/* Plugins */ + +export { default as FakeCursorPlugin } from './src/config/plugins/FakeCursorPlugin'; diff --git a/wax-prosemirror-core/src/WaxOptions.js b/wax-prosemirror-core/src/WaxOptions.js index b8e8a9d0efe830ed6375617acd30af9a918aff6d..239f22d7315e299c50c95dc0a9537beaf12fa64d 100644 --- a/wax-prosemirror-core/src/WaxOptions.js +++ b/wax-prosemirror-core/src/WaxOptions.js @@ -1,6 +1,6 @@ import { DOMParser } from 'prosemirror-model'; -import Placeholder from './plugins/placeholder'; -import defaultPlugins from './plugins/defaultPlugins'; +import Placeholder from './config/plugins/placeholder'; +import defaultPlugins from './config/plugins/defaultPlugins'; const parser = schema => { const WaxParser = DOMParser.fromSchema(schema); diff --git a/wax-prosemirror-core/src/WaxView.js b/wax-prosemirror-core/src/WaxView.js index 40ad701925f13fd50c968ae6b3d12893b221e09a..48d9862593a1681089a2f4b22d00cd2ec940f689 100644 --- a/wax-prosemirror-core/src/WaxView.js +++ b/wax-prosemirror-core/src/WaxView.js @@ -84,13 +84,6 @@ const WaxView = forwardRef((props, ref) => { attributes: { spellcheck: browserSpellCheck ? 'true' : 'false', }, - handleDOMEvents: { - blur: (editorView, event) => { - if (view && event.relatedTarget === null) { - view.focus(); - } - }, - }, }, ); diff --git a/wax-prosemirror-core/src/components/icons/icons.js b/wax-prosemirror-core/src/components/icons/icons.js index dab653f106f245af96389059566205c7a270ceff..ce490d2a944ace590da6c574660dd4889f5e8a1d 100644 --- a/wax-prosemirror-core/src/components/icons/icons.js +++ b/wax-prosemirror-core/src/components/icons/icons.js @@ -484,4 +484,10 @@ export default { <path d="M12 2A10 10 0 1 0 22 12A10 10 0 0 0 12 2M18 11H13L14.81 9.19A3.94 3.94 0 0 0 12 8A4 4 0 1 0 15.86 13H17.91A6 6 0 1 1 12 6A5.91 5.91 0 0 1 16.22 7.78L18 6Z" />{' '} </Svg> ), + expand: ({ className }) => ( + <Svg className={className} fill="none" viewBox="0 0 24 24"> + <title> Expand</title> + <path d="M7 10l5 5 5-5z" />{' '} + </Svg> + ), }; diff --git a/wax-prosemirror-core/src/components/ui/MenuButton.js b/wax-prosemirror-core/src/components/ui/MenuButton.js index 958c458d4f910b314740eabdb6c79beb428fb20c..46a9680aacad20a876b19549e4463dc1c3a96b4f 100644 --- a/wax-prosemirror-core/src/components/ui/MenuButton.js +++ b/wax-prosemirror-core/src/components/ui/MenuButton.js @@ -91,6 +91,7 @@ const MenuButton = props => { return ( <Wrapper active={active} + aria-pressed={active || false} className={className} disabled={disabled} onMouseDown={onMouseDown} diff --git a/wax-prosemirror-core/src/config/defaultConfig.js b/wax-prosemirror-core/src/config/defaultConfig.js index 8b5f98f721ab61fd2f13294ee4d803b9a4c87463..457cb38f0798b7e1a552abe7a8861a7a0d0e2dd4 100644 --- a/wax-prosemirror-core/src/config/defaultConfig.js +++ b/wax-prosemirror-core/src/config/defaultConfig.js @@ -5,6 +5,7 @@ import LayoutService from './defaultServices/LayoutService/LayoutService'; import PortalService from './defaultServices/PortalService/PortalService'; import MenuService from './defaultServices/MenuService/MenuService'; import OverlayService from './defaultServices/OverlayService/OverlayService'; +import CorePluginsService from './defaultServices/CorePluginsService/CorePluginsService'; export default () => ({ services: [ @@ -15,5 +16,6 @@ export default () => ({ new PortalService(), new MenuService(), new OverlayService(), + new CorePluginsService(), ], }); diff --git a/wax-prosemirror-core/src/config/defaultServices/CorePluginsService/CorePluginsService.js b/wax-prosemirror-core/src/config/defaultServices/CorePluginsService/CorePluginsService.js new file mode 100644 index 0000000000000000000000000000000000000000..70935410d9dee519d24a42ae81482fabd431bb92 --- /dev/null +++ b/wax-prosemirror-core/src/config/defaultServices/CorePluginsService/CorePluginsService.js @@ -0,0 +1,11 @@ +import Service from '../../../Service'; +import FakeCursorPlugin from '../../plugins/FakeCursorPlugin'; + +export default class CorePluginsService extends Service { + boot() { + // this.app.PmPlugins.add( + // 'fakeCursorPlugin', + // FakeCursorPlugin('fakeCursorPlugin'), + // ); + } +} diff --git a/wax-prosemirror-core/src/config/plugins/FakeCursorPlugin.js b/wax-prosemirror-core/src/config/plugins/FakeCursorPlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..0cee39826c75e36c7885675d43e26b0c980e23ce --- /dev/null +++ b/wax-prosemirror-core/src/config/plugins/FakeCursorPlugin.js @@ -0,0 +1,59 @@ +/* eslint-disable */ + +import { Plugin, PluginKey } from 'prosemirror-state'; +import { Decoration, DecorationSet } from 'prosemirror-view'; + +const fakeCursorPlugin = new PluginKey('fakeCursorPlugin'); +export default props => { + return new Plugin({ + key: fakeCursorPlugin, + state: { + init: (_, state) => {}, + apply(tr, prev, _, newState) { + let createDecoration; + if (newState.selection.from === newState.selection.to) { + const widget = document.createElement('fakecursor'); + createDecoration = DecorationSet.create(newState.doc, [ + Decoration.widget(newState.selection.from, widget, { + key: 'fakecursor', + }), + ]); + } + return { + createDecoration, + }; + }, + }, + props: { + decorations: state => { + const fakeCursorPluginState = state && fakeCursorPlugin.getState(state); + if (fakeCursorPluginState) + return fakeCursorPluginState.createDecoration; + }, + handleDOMEvents: { + focus: (view, event) => { + event.preventDefault(); + const fakeCursor = document.getElementsByTagName('fakecursor'); + if (fakeCursor && fakeCursor[0]) { + for (let i = 0; i < fakeCursor.length; i++) { + fakeCursor[i].style.visibility = 'hidden'; + } + } + }, + blur: (view, event) => { + event.preventDefault(); + if (view && event.relatedTarget === null) { + view.focus(); + } else { + const fakeCursor = document.getElementsByTagName('fakecursor'); + if (fakeCursor && fakeCursor[0]) { + for (let i = 0; i < fakeCursor.length; i++) { + fakeCursor[i].style.visibility = 'visible'; + } + } + } + }, + }, + }, + }); +}; diff --git a/wax-prosemirror-core/src/plugins/defaultPlugins.js b/wax-prosemirror-core/src/config/plugins/defaultPlugins.js similarity index 54% rename from wax-prosemirror-core/src/plugins/defaultPlugins.js rename to wax-prosemirror-core/src/config/plugins/defaultPlugins.js index 88158c86b7cace5cf58ed3c872a94fc0fecc2f18..eb62279208e5fb8de8a9a2f915ad7457d3b941c7 100644 --- a/wax-prosemirror-core/src/plugins/defaultPlugins.js +++ b/wax-prosemirror-core/src/config/plugins/defaultPlugins.js @@ -1,5 +1,6 @@ import { history } from 'prosemirror-history'; import { dropCursor } from 'prosemirror-dropcursor'; import { gapCursor } from 'prosemirror-gapcursor'; +import FakeCursorPlugin from './FakeCursorPlugin'; -export default [dropCursor(), gapCursor(), history()]; +export default [dropCursor(), gapCursor(), history(), FakeCursorPlugin()]; diff --git a/wax-prosemirror-core/src/plugins/placeholder.js b/wax-prosemirror-core/src/config/plugins/placeholder.js similarity index 100% rename from wax-prosemirror-core/src/plugins/placeholder.js rename to wax-prosemirror-core/src/config/plugins/placeholder.js diff --git a/wax-prosemirror-core/src/styles/styles.css b/wax-prosemirror-core/src/styles/styles.css index 670f3b37ceafa83e815c951badd8010fa933cf10..9199a96e967284357ee58c1348b60b91200ec285 100644 --- a/wax-prosemirror-core/src/styles/styles.css +++ b/wax-prosemirror-core/src/styles/styles.css @@ -225,4 +225,14 @@ img.ProseMirror-separator { .ProseMirror *::selection{ background-color: #C5D7FE; color: #000; -} \ No newline at end of file +} + +fakecursor { + display: inline-flex; + border-right: 1px solid black; + height: 19px; + position: absolute; + width: 1px; + visibility: hidden; + z-index: 999; + } \ No newline at end of file diff --git a/wax-prosemirror-services/src/FillTheGapQuestionService/CreateGapService/CreateGapService.js b/wax-prosemirror-services/src/FillTheGapQuestionService/CreateGapService/CreateGapService.js index d7cfe9046c9e0c2202551b54975c7ba7f622bf73..7b142ac92100267bb39e2be4b714cea2147d9a67 100644 --- a/wax-prosemirror-services/src/FillTheGapQuestionService/CreateGapService/CreateGapService.js +++ b/wax-prosemirror-services/src/FillTheGapQuestionService/CreateGapService/CreateGapService.js @@ -1,6 +1,6 @@ import { Service } from 'wax-prosemirror-core'; import CreateGap from './CreateGap'; -import FillTheGapToolGroupService from '../../WaxToolGroups/FillTheGapToolGroupService/FillTheGapToolGroupService'; +import FillTheGapToolGroupService from '../FillTheGapToolGroupService/FillTheGapToolGroupService'; class FillTheGapQuestionService extends Service { register() { diff --git a/wax-prosemirror-services/src/WaxToolGroups/FillTheGapToolGroupService/FillTheGap.js b/wax-prosemirror-services/src/FillTheGapQuestionService/FillTheGapToolGroupService/FillTheGap.js similarity index 100% rename from wax-prosemirror-services/src/WaxToolGroups/FillTheGapToolGroupService/FillTheGap.js rename to wax-prosemirror-services/src/FillTheGapQuestionService/FillTheGapToolGroupService/FillTheGap.js diff --git a/wax-prosemirror-services/src/WaxToolGroups/FillTheGapToolGroupService/FillTheGapToolGroupService.js b/wax-prosemirror-services/src/FillTheGapQuestionService/FillTheGapToolGroupService/FillTheGapToolGroupService.js similarity index 100% rename from wax-prosemirror-services/src/WaxToolGroups/FillTheGapToolGroupService/FillTheGapToolGroupService.js rename to wax-prosemirror-services/src/FillTheGapQuestionService/FillTheGapToolGroupService/FillTheGapToolGroupService.js diff --git a/wax-prosemirror-services/src/FillTheGapQuestionService/components/InputComponent.js b/wax-prosemirror-services/src/FillTheGapQuestionService/components/InputComponent.js index 5ae80bcd524e0e333b795b82107f3ee45cc4c3c1..d2ca2d677cc8ba55469d2631fe02b559a3cf0074 100644 --- a/wax-prosemirror-services/src/FillTheGapQuestionService/components/InputComponent.js +++ b/wax-prosemirror-services/src/FillTheGapQuestionService/components/InputComponent.js @@ -52,6 +52,7 @@ export default () => { return ( <AnswerInput + aria-label="answer input" onBlur={saveAnswer} onChange={setAnswerInput} onFocus={onFocus} diff --git a/wax-prosemirror-services/src/MatchingService/MatchingContainerNodeView.js b/wax-prosemirror-services/src/MatchingService/MatchingContainerNodeView.js index a66c6e8646bb2f882583e4cb923e97a4e211058c..867fdda7a1a81f1f5ccd6ada0b2e5df45cc32500 100644 --- a/wax-prosemirror-services/src/MatchingService/MatchingContainerNodeView.js +++ b/wax-prosemirror-services/src/MatchingService/MatchingContainerNodeView.js @@ -30,7 +30,12 @@ export default class MatchingContainerNodeView extends QuestionsNodeView { } stopEvent(event) { - if (event.target.type === 'textarea' || event.target.type === 'text') { + if ( + event.target.type === 'textarea' || + event.target.type === 'text' || + event.target.type === 'button' || + !event.target.type + ) { return true; } const innerView = this.context.pmViews[this.node.attrs.id]; diff --git a/wax-prosemirror-services/src/MatchingService/MatchingQuestion.js b/wax-prosemirror-services/src/MatchingService/MatchingQuestion.js index 2fb469e76cede7ff2931763691f4be1cd8997b8c..57a60d7f828542808683b0c68dda6f0cdbd43e90 100644 --- a/wax-prosemirror-services/src/MatchingService/MatchingQuestion.js +++ b/wax-prosemirror-services/src/MatchingService/MatchingQuestion.js @@ -54,6 +54,10 @@ class MatchingQuestion extends Tools { Commands.isParentOfType( state, state.config.schema.nodes.matching_container, + ) || + Commands.isParentOfType( + state, + state.config.schema.nodes.matching_option, ) ) { return true; diff --git a/wax-prosemirror-services/src/MatchingService/components/DropDownComponent.js b/wax-prosemirror-services/src/MatchingService/components/DropDownComponent.js index 558ceb785b91efc11d2613ca336640235e3c0197..694e4b7625e9c79be39dc3cac4645decb9ee3f3b 100644 --- a/wax-prosemirror-services/src/MatchingService/components/DropDownComponent.js +++ b/wax-prosemirror-services/src/MatchingService/components/DropDownComponent.js @@ -1,61 +1,88 @@ +/* eslint-disable no-unused-vars */ /* eslint-disable no-underscore-dangle */ -import React, { useContext, useMemo, useState } from 'react'; +import React, { + useMemo, + useContext, + useState, + useEffect, + useRef, + createRef, +} from 'react'; import styled from 'styled-components'; import { WaxContext, DocumentHelpers, - ReactDropDownStyles, + Icon, + useOnClickOutside, } from 'wax-prosemirror-core'; -import Dropdown from 'react-dropdown'; -import { v4 as uuidv4 } from 'uuid'; -const Wrapper = styled.div` - ${ReactDropDownStyles}; -`; -const DropdownStyled = styled(Dropdown)` - cursor: not-allowed; - display: inline-flex; - margin-left: auto; - opacity: ${props => (props.select ? 1 : 0.4)}; - pointer-events: ${props => (props.select ? 'default' : 'none')}; +const Wrapper = styled.div``; - .Dropdown-control { - border: none; - padding: 8px 30px 8px 10px; +const DropDownButton = styled.button` + background: #fff; + border: none; + color: #000; + cursor: ${props => (props.disabled ? `cursor` : `pointer`)}; + display: flex; + position: relative; + width: 160px; - &:hover { - box-shadow: none; - } + span { + position: relative; + top: 2px; } +`; - .Dropdown-arrow { - top: 17px; +const DropDownMenu = styled.div` + visibility: ${props => (props.isOpen ? 'visible' : 'hidden')}; + background: #fff; + display: flex; + flex-direction: column; + border: 1px solid #ddd; + border-radius: 0.25rem; + box-shadow: 0 0.2rem 0.4rem rgb(0 0 0 / 10%); + margin: 10px auto auto; + position: absolute; + width: 170px; + max-height: 150px; + overflow-y: auto; + z-index: 2; + + span { + cursor: pointer; + padding: 8px 10px; } - .Dropdown-menu { - align-items: flex-start; - display: flex; - flex-direction: column; - width: 102%; - - .Dropdown-option { - width: 100%; - } + span:focus { + background: #f2f9fc; + outline: 2px solid #f2f9fc; } `; +const StyledIcon = styled(Icon)` + height: 18px; + width: 18px; + margin-left: auto; +`; + const DropComponent = ({ getPos, node, view }) => { const [selectedOption, setSelectedOption] = useState(node.attrs.correct); + const itemRefs = useRef([]); + const wrapperRef = useRef(); + const [isOpen, setIsOpen] = useState(false); const context = useContext(WaxContext); const { pmViews: { main }, + activeView, } = context; const isEditable = main.props.editable(editable => { return editable; }); + const isDisabled = !isEditable; + const onChange = option => { const allNodes = getNodes(main); allNodes.forEach(singleNode => { @@ -70,33 +97,105 @@ const DropComponent = ({ getPos, node, view }) => { ); } }); + openCloseMenu(); + setSelectedOption(option.value); + }; + + useOnClickOutside(wrapperRef, () => setIsOpen(false)); + + useEffect(() => { + if (isDisabled) setIsOpen(false); + }, [isDisabled]); + + const openCloseMenu = () => { + if (!isDisabled) setIsOpen(!isOpen); + if (isOpen) + setTimeout(() => { + console.log('here?', activeView); + activeView.focus(); + }); }; - // useEffect(() => { - // const found = find(node.attrs.options, { value: node.attrs.correct }); - - // if (found) { - // setSelectedOption(found); - // } - // }, [node.attrs.options]); - - const MultipleDropDown = useMemo( - () => ( - <Wrapper key={uuidv4()}> - <DropdownStyled - key={uuidv4()} - onChange={option => onChange(option)} - options={node.attrs.options} - placeholder="Select option" - select={isEditable} - value={ - selectedOption === 'undefined' ? 'Select Option' : selectedOption - } - /> + const onKeyDown = (e, index) => { + e.preventDefault(); + // arrow down + if (e.keyCode === 40) { + if (index === itemRefs.current.length - 1) { + itemRefs.current[0].current.focus(); + } else { + itemRefs.current[index + 1].current.focus(); + } + } + + // arrow up + if (e.keyCode === 38) { + if (index === 0) { + itemRefs.current[itemRefs.current.length - 1].current.focus(); + } else { + itemRefs.current[index - 1].current.focus(); + } + } + + // enter + if (e.keyCode === 13) { + itemRefs.current[index].current.click(); + } + + // ESC + if (e.keyCode === 27) { + openCloseMenu(); + } + }; + + const MultipleDropDown = useMemo(() => { + let selectedValue; + if (selectedOption) { + selectedValue = node.attrs.options.filter(option => { + return option.value === selectedOption; + }); + } + return ( + <Wrapper disabled={isDisabled} ref={wrapperRef}> + <DropDownButton + aria-expanded={isOpen} + aria-haspopup + disabled={isDisabled} + onKeyDown={e => { + if (e.keyCode === 40) { + itemRefs.current[0].current.focus(); + } + if (e.keyCode === 27) { + openCloseMenu(); + } + }} + onMouseDown={openCloseMenu} + type="button" + > + {selectedOption === null || !selectedOption + ? 'Select Option' + : selectedValue[0].label} + <StyledIcon name="expand" /> + </DropDownButton> + <DropDownMenu isOpen={isOpen} role="menu"> + {node.attrs.options.map((option, index) => { + itemRefs.current[index] = itemRefs.current[index] || createRef(); + return ( + <span + key={option.value} + onClick={() => onChange(option)} + onKeyDown={e => onKeyDown(e, index)} + ref={itemRefs.current[index]} + role="menuitem" + tabIndex="-1" + > + {option.label} + </span> + ); + })} + </DropDownMenu> </Wrapper> - ), - [node.attrs.options, selectedOption], - ); + ); + }, [node.attrs.options, selectedOption, isOpen]); return MultipleDropDown; }; diff --git a/wax-prosemirror-services/src/MatchingService/components/EditorComponent.js b/wax-prosemirror-services/src/MatchingService/components/EditorComponent.js index 6f2b08ca8d782e3b5292006b6b129d3cb72f35b2..4168ecc3847ecb81bf22e0544bc52d1855dca2d0 100644 --- a/wax-prosemirror-services/src/MatchingService/components/EditorComponent.js +++ b/wax-prosemirror-services/src/MatchingService/components/EditorComponent.js @@ -6,7 +6,7 @@ import { StepMap } from 'prosemirror-transform'; import { keymap } from 'prosemirror-keymap'; import { baseKeymap } from 'prosemirror-commands'; import { undo, redo } from 'prosemirror-history'; -import { WaxContext } from 'wax-prosemirror-core'; +import { WaxContext, FakeCursorPlugin } from 'wax-prosemirror-core'; import Placeholder from '../../MultipleChoiceQuestionService/plugins/placeholder'; const EditorWrapper = styled.div` @@ -68,7 +68,7 @@ const EditorComponent = ({ node, view, getPos }) => { return editable; }); - let finalPlugins = []; + let finalPlugins = [FakeCursorPlugin()]; const createKeyBindings = () => { const keys = getKeys(); @@ -125,9 +125,7 @@ const EditorComponent = ({ node, view, getPos }) => { .setSelection( new TextSelection( main.state.tr.doc.resolve( - getPos() + - 2 + - context.pmViews[questionId].state.selection.to, + getPos() + context.pmViews[questionId].state.selection.to, ), ), ), diff --git a/wax-prosemirror-services/src/MatchingService/components/MatchingContainerComponent.js b/wax-prosemirror-services/src/MatchingService/components/MatchingContainerComponent.js index 4d5afc9cd32965a0bed716ff68525e5afa8ac3c7..77c41edfef98c5d321efcd62b3ae86b41b59c3e5 100644 --- a/wax-prosemirror-services/src/MatchingService/components/MatchingContainerComponent.js +++ b/wax-prosemirror-services/src/MatchingService/components/MatchingContainerComponent.js @@ -264,11 +264,15 @@ export default ({ node, view, getPos }) => { {option.label} {!readOnly && ( <ActionButton + aria-label={`delete ${option.label}`} onClick={() => removeOption(option.value)} ref={setRef(option.value)} type="button" > - <StyledIconAction name="deleteOutlined" /> + <StyledIconAction + label={`delete ${option.label}`} + name="deleteOutlined" + /> </ActionButton> )} </span> @@ -289,7 +293,12 @@ export default ({ node, view, getPos }) => { type="text" value={optionText} /> - <button onClick={addOption} ref={addOptionBtnRef} type="button"> + <button + aria-label="add new option" + onClick={addOption} + ref={addOptionBtnRef} + type="button" + > Add Option </button> </AddOption> diff --git a/wax-prosemirror-services/src/MatchingService/components/MatchingOptionComponent.js b/wax-prosemirror-services/src/MatchingService/components/MatchingOptionComponent.js index ea42dc3a5f38f069af116a627e922ec98b6409a0..ae3f045aa8329655e07164b47c9222063e6b5d7c 100644 --- a/wax-prosemirror-services/src/MatchingService/components/MatchingOptionComponent.js +++ b/wax-prosemirror-services/src/MatchingService/components/MatchingOptionComponent.js @@ -120,11 +120,19 @@ export default ({ node, view, getPos }) => { <Option> {!readOnly && ( <ButtonsContainer> - <ActionButton onClick={addAnswer} type="button"> + <ActionButton + aria-label="add new option" + onClick={addAnswer} + type="button" + > <StyledIconAction name="plusSquare" /> </ActionButton> {!node.attrs.isfirst && ( - <ActionButton onClick={removeAnswer} type="button"> + <ActionButton + aria-label="delete this option" + onClick={removeAnswer} + type="button" + > <StyledIconAction name="deleteOutlined" /> </ActionButton> )} diff --git a/wax-prosemirror-services/src/MatchingService/components/TestModeDropDownComponent.js b/wax-prosemirror-services/src/MatchingService/components/TestModeDropDownComponent.js index 6bb5fbfba16e8d754a3f9bacd69d78d78c49c5d8..3d471faaf68d83a88861562d5e8aa32d6f7e90f0 100644 --- a/wax-prosemirror-services/src/MatchingService/components/TestModeDropDownComponent.js +++ b/wax-prosemirror-services/src/MatchingService/components/TestModeDropDownComponent.js @@ -1,3 +1,4 @@ +/* eslint-disable no-unused-vars */ /* eslint-disable no-underscore-dangle */ import React, { useContext, useMemo, useEffect, useState } from 'react'; import styled from 'styled-components'; diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceContainerNodeView.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceContainerNodeView.js index e914da795cc19e327328316c3990a2c6181326a1..36ec4664ce83c2c5ba3cdf2dbca207c6d2c1f5b9 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceContainerNodeView.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceContainerNodeView.js @@ -23,7 +23,11 @@ export default class MultipleChoiceContainerNodeView extends QuestionsNodeView { } stopEvent(event) { - if (event.target.type === 'text') { + if ( + !event.target.type || + event.target.type === 'button' || + event.target.type === 'text' + ) { return true; } const innerView = this.context.pmViews[this.node.attrs.id]; diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceNodeView.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceNodeView.js index 99176684f90b789a134e0cda1444595bcf996adf..58040e5e208f65aec61206c3b8af0b079e153379 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceNodeView.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceNodeView.js @@ -23,7 +23,11 @@ export default class MultipleChoiceNodeView extends QuestionsNodeView { } stopEvent(event) { - if (event.target.type === 'textarea') { + if ( + !event.target.type || + event.target.type === 'button' || + event.target.type === 'textarea' + ) { return true; } const innerView = this.context.pmViews[this.node.attrs.id]; diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceQuestionService.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceQuestionService.js index 8b36b99246ba9f64719f536dbf8801b8982f7721..06e1ef7818728bf130e901b3f74a11104b203ead 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceQuestionService.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceQuestionService.js @@ -16,10 +16,10 @@ import MoveCursorPlugin from './plugins/MoveCursorPlugin'; class MultipleChoiceQuestionService extends Service { boot() { - this.app.PmPlugins.add( - 'moveCursorPlugin', - MoveCursorPlugin('moveCursorPlugin'), - ); + // this.app.PmPlugins.add( + // 'moveCursorPlugin', + // MoveCursorPlugin('moveCursorPlugin'), + // ); } register() { diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceSingleCorrectQuestionService/MultipleChoiceSingleCorrectNodeView.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceSingleCorrectQuestionService/MultipleChoiceSingleCorrectNodeView.js index e6ca50f410343c2c2d3e03d88236a92a26b817a3..ce3ed262fc3fba2a9156050158b813b6db5fd7ac 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceSingleCorrectQuestionService/MultipleChoiceSingleCorrectNodeView.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceSingleCorrectQuestionService/MultipleChoiceSingleCorrectNodeView.js @@ -23,7 +23,11 @@ export default class MultipleChoiceSingleCorrectNodeView extends QuestionsNodeVi } stopEvent(event) { - if (event.target.type === 'textarea') { + if ( + !event.target.type || + event.target.type === 'button' || + event.target.type === 'textarea' + ) { return true; } const innerView = this.context.pmViews[this.node.attrs.id]; diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceSingleCorrectQuestionService/MultipleChoiceSingleCorrectQuestion.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceSingleCorrectQuestionService/MultipleChoiceSingleCorrectQuestion.js index 0861b08bd6061989e6193fa71e4992c6b2b92c3c..1815d024e6fd67e254ff640d9768d32314aff2b9 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceSingleCorrectQuestionService/MultipleChoiceSingleCorrectQuestion.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceSingleCorrectQuestionService/MultipleChoiceSingleCorrectQuestion.js @@ -28,6 +28,10 @@ class MultipleChoiceSingleCorrectQuestion extends Tools { get active() { return state => { if ( + Commands.isParentOfType( + state, + state.config.schema.nodes.multiple_choice_single_correct_container, + ) || Commands.isParentOfType( state, state.config.schema.nodes.multiple_choice_single_correct, diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceSingleCorrectQuestionService/QuestionMultipleSingleNodeView.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceSingleCorrectQuestionService/QuestionMultipleSingleNodeView.js index 4a69d56140712ffd4fb20c66d869231f932e7c90..dcf0bc7b77291bf2a45eb630065f5efd951275d2 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceSingleCorrectQuestionService/QuestionMultipleSingleNodeView.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceSingleCorrectQuestionService/QuestionMultipleSingleNodeView.js @@ -23,7 +23,11 @@ export default class QuestionMultipleSingleNodeView extends QuestionsNodeView { } stopEvent(event) { - if (event.target.type === 'text') { + if ( + !event.target.type || + event.target.type === 'button' || + event.target.type === 'text' + ) { return true; } const innerView = this.context.pmViews[this.node.attrs.id]; diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceSingleCorrectQuestionService/components/AnswerComponent.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceSingleCorrectQuestionService/components/AnswerComponent.js index 1210e87c4b439fac88f218e75534666e896c7c56..f72dee0818ae44d19f9945c53e24b0ee9cf91da7 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceSingleCorrectQuestionService/components/AnswerComponent.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/MultipleChoiceSingleCorrectQuestionService/components/AnswerComponent.js @@ -230,6 +230,7 @@ export default ({ node, view, getPos }) => { <IconsWrapper> {!readOnly && ( <ActionButton + aria-label="add new option" onClick={() => addOption(node.attrs.id)} ref={addOptionBtnRef} type="button" @@ -239,6 +240,7 @@ export default ({ node, view, getPos }) => { )} {!readOnly && ( <ActionButton + aria-label="delete this option" onClick={removeOption} ref={removeOptionBtnRef} type="button" diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseQuestionService/TrueFalseContainerNodeView.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseQuestionService/TrueFalseContainerNodeView.js index 6e01a409bad651715cf1170cae465692d118fe61..2bc71a7ff30181f370905209142adbf729a827d5 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseQuestionService/TrueFalseContainerNodeView.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseQuestionService/TrueFalseContainerNodeView.js @@ -23,7 +23,11 @@ export default class TrueFalseContainerNodeView extends QuestionsNodeView { } stopEvent(event) { - if (event.target.type === 'text') { + if ( + !event.target.type || + event.target.type === 'button' || + event.target.type === 'text' + ) { return true; } const innerView = this.context.pmViews[this.node.attrs.id]; diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseQuestionService/TrueFalseNodeView.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseQuestionService/TrueFalseNodeView.js index e663e615620a62a34b975098ad71f3bab4b4976d..527a485f0b8aac04f698706f5f1c83e19faf4bd5 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseQuestionService/TrueFalseNodeView.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseQuestionService/TrueFalseNodeView.js @@ -23,7 +23,11 @@ export default class TrueFalseNodeView extends QuestionsNodeView { } stopEvent(event) { - if (event.target.type === 'textarea') { + if ( + !event.target.type || + event.target.type === 'button' || + event.target.type === 'textarea' + ) { return true; } const innerView = this.context.pmViews[this.node.attrs.id]; diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseQuestionService/TrueFalseQuestion.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseQuestionService/TrueFalseQuestion.js index d2dc43cfbc53bfcae8d1c230815c9e3a716d289b..74d502fada983d25bb4772c5ed42074f618808c9 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseQuestionService/TrueFalseQuestion.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseQuestionService/TrueFalseQuestion.js @@ -28,6 +28,10 @@ class TrueFalseQuestion extends Tools { get active() { return state => { if ( + Commands.isParentOfType( + state, + state.config.schema.nodes.true_false_container, + ) || Commands.isParentOfType(state, state.config.schema.nodes.true_false) || Commands.isParentOfType( state, diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseQuestionService/components/AnswerComponent.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseQuestionService/components/AnswerComponent.js index c96a6251f7560067298c4f390545f72ba4c6cc71..ced1bc08f53c27b6fb692eea20d5b241b735c7a1 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseQuestionService/components/AnswerComponent.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseQuestionService/components/AnswerComponent.js @@ -230,6 +230,7 @@ export default ({ node, view, getPos }) => { <IconsWrapper> {!readOnly && ( <ActionButton + aria-label="add new option" onClick={() => addOption(node.attrs.id)} ref={addOptionBtnRef} type="button" @@ -239,6 +240,7 @@ export default ({ node, view, getPos }) => { )} {!readOnly && ( <ActionButton + aria-label="delete this option" onClick={removeOption} ref={removeOptionBtnRef} type="button" diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/TrueFalseSingleCorrectContainerNodeView.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/TrueFalseSingleCorrectContainerNodeView.js index 4a815e5b39644bd4afe1ad973345947ad8465c7c..51b31391d372abf39af2ab344c4356b384c484e9 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/TrueFalseSingleCorrectContainerNodeView.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/TrueFalseSingleCorrectContainerNodeView.js @@ -23,7 +23,11 @@ export default class TrueFalseSingleCorrectContainerNodeView extends QuestionsNo } stopEvent(event) { - if (event.target.type === 'text') { + if ( + !event.target.type || + event.target.type === 'button' || + event.target.type === 'text' + ) { return true; } const innerView = this.context.pmViews[this.node.attrs.id]; diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/TrueFalseSingleCorrectNodeView.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/TrueFalseSingleCorrectNodeView.js index 1bcba67e646a4549256db469c99ef32b906b9be7..c94c3fc49461630a8f4cfaa09f69d047cf0a6899 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/TrueFalseSingleCorrectNodeView.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/TrueFalseSingleCorrectNodeView.js @@ -23,7 +23,11 @@ export default class TrueFalseSingleCorrectNodeView extends QuestionsNodeView { } stopEvent(event) { - if (event.target.type === 'textarea') { + if ( + !event.target.type || + event.target.type === 'button' || + event.target.type === 'textarea' + ) { return true; } const innerView = this.context.pmViews[this.node.attrs.id]; diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/TrueFalseSingleCorrectQuestion.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/TrueFalseSingleCorrectQuestion.js index ce39f1988a081d0aea143e10a8fc947b22c50963..6eea810093353686b644ccb0bd371d523243ffdf 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/TrueFalseSingleCorrectQuestion.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/TrueFalseSingleCorrectQuestion.js @@ -28,6 +28,10 @@ class TrueFalseSingleCorrectQuestion extends Tools { get active() { return state => { if ( + Commands.isParentOfType( + state, + state.config.schema.nodes.true_false_single_correct_container, + ) || Commands.isParentOfType( state, state.config.schema.nodes.true_false_single_correct, diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/components/AnswerComponent.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/components/AnswerComponent.js index 707d0eaf585ca185c4e1ab91cf5e1e88366f7915..175601897bc33178c23b28f3f6f4e8113cef2e80 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/components/AnswerComponent.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/TrueFalseSingleCorrectQuestionService/components/AnswerComponent.js @@ -230,6 +230,7 @@ export default ({ node, view, getPos }) => { <IconsWrapper> {!readOnly && ( <ActionButton + aria-label="add new option" onClick={() => addOption(node.attrs.id)} ref={addOptionBtnRef} type="button" @@ -239,6 +240,7 @@ export default ({ node, view, getPos }) => { )} {!readOnly && ( <ActionButton + aria-label="delete this option" onClick={removeOption} ref={removeOptionBtnRef} type="button" diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/AnswerComponent.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/AnswerComponent.js index bd696b59e21cc48e6ed37e435b9efb76d4c3d17e..952db530481893825b54319e73556dd96c3296a5 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/AnswerComponent.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/AnswerComponent.js @@ -228,6 +228,7 @@ export default ({ node, view, getPos }) => { <IconsWrapper> {!readOnly && ( <ActionButton + aria-label="add new option" onClick={() => addOption(node.attrs.id)} ref={addOptionBtnRef} type="button" @@ -237,6 +238,7 @@ export default ({ node, view, getPos }) => { )} {!readOnly && ( <ActionButton + aria-label="delete this option" onClick={removeOption} ref={removeOptionBtnRef} type="button" diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/EditorComponent.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/EditorComponent.js index 59f95977f4001754d838af4479cd3948dd57d0ac..31141b319006e89ea7613332d6d0612f81c04691 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/EditorComponent.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/EditorComponent.js @@ -8,6 +8,7 @@ import { baseKeymap } from 'prosemirror-commands'; import { undo, redo } from 'prosemirror-history'; import { WaxContext } from 'wax-prosemirror-core'; import Placeholder from '../plugins/placeholder'; +import FakeCursorPlugin from '../../MultipleDropDownService/plugins/FakeCursorPlugin'; const EditorWrapper = styled.div` border: none; @@ -57,7 +58,6 @@ const EditorComponent = ({ node, view, getPos }) => { const { app, pmViews: { main }, - activeViewId, } = context; let questionView; const questionId = node.attrs.id; @@ -65,7 +65,7 @@ const EditorComponent = ({ node, view, getPos }) => { return editable; }); - let finalPlugins = []; + let finalPlugins = [FakeCursorPlugin()]; const createKeyBindings = () => { const keys = getKeys(); diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/FeedbackComponent.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/FeedbackComponent.js index ade5c99e78e6d798eb9038de820a360f59f7c097..2cf40ab1e318a9d423175d9bffdec70aae3b1fbb 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/FeedbackComponent.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/FeedbackComponent.js @@ -95,7 +95,6 @@ export default ({ node, getPos, readOnly }) => { <FeedBack> <FeedBackLabel>Feedback</FeedBackLabel> <FeedBackInput - autoFocus="autoFocus" onChange={feedBackInput} onFocus={onFocus} placeholder="Insert feedback" diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/QuestionEditorComponent.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/QuestionEditorComponent.js index bd653c9b74ade6fbef96b0e0123c896d6cee71a7..2e79dfc2e2f5e440a3164ca86590e90376f6a61b 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/QuestionEditorComponent.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/QuestionEditorComponent.js @@ -13,6 +13,7 @@ import { sinkListItem, } from 'prosemirror-schema-list'; import Placeholder from '../plugins/placeholder'; +import FakeCursorPlugin from '../../MultipleDropDownService/plugins/FakeCursorPlugin'; const EditorWrapper = styled.div` border: none; @@ -71,7 +72,7 @@ const QuestionEditorComponent = ({ node, view, getPos }) => { return editable; }); - let finalPlugins = []; + let finalPlugins = [FakeCursorPlugin()]; const createKeyBindings = () => { const keys = getKeys(); diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/Switch.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/Switch.js index 151b91cdfb667011d7f774af24101f4c53c19cf7..1896a291388f8db837ccf5b6587d0d77b3b01e17 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/Switch.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/Switch.js @@ -27,7 +27,7 @@ const Wrapper = styled.span` } `; -const Label = styled.span` +const Label = styled.label` ${props => props.labelPosition === 'left' && css` @@ -39,21 +39,26 @@ const Label = styled.span` css` margin-left: ${grid(2)}; `} + cursor: pointer; `; const SwitchComponent = props => { - const { className, label, labelPosition, ...rest } = props; + const { className, label, labelPosition, onChange, ...rest } = props; return ( <Wrapper className={className}> {label && labelPosition === 'left' && ( - <Label labelPosition={labelPosition}>{label}</Label> + <Label labelPosition={labelPosition} onClick={onChange}> + {label} + </Label> )} - <Switch {...rest} /> + <Switch aria-label="Is it correct" onChange={onChange} {...rest} /> {label && labelPosition === 'right' && ( - <Label labelPosition={labelPosition}>{label}</Label> + <Label labelPosition={labelPosition} onClick={onChange}> + {label} + </Label> )} </Wrapper> ); @@ -62,11 +67,13 @@ const SwitchComponent = props => { SwitchComponent.propTypes = { label: PropTypes.string, labelPosition: PropTypes.string, + onChange: PropTypes.func, }; SwitchComponent.defaultProps = { label: null, labelPosition: 'right', + onChange: () => true, }; export default SwitchComponent; diff --git a/wax-prosemirror-services/src/MultipleDropDownService/CreateDropDownService/CreateDropDown.js b/wax-prosemirror-services/src/MultipleDropDownService/CreateDropDownService/CreateDropDown.js index 817627ecbccb2181254d7b50fb93b934fa682f94..c0e6860ea3fcc852bbbe0cd131d0961bc361a754 100644 --- a/wax-prosemirror-services/src/MultipleDropDownService/CreateDropDownService/CreateDropDown.js +++ b/wax-prosemirror-services/src/MultipleDropDownService/CreateDropDownService/CreateDropDown.js @@ -32,7 +32,7 @@ class CreateDropDown extends Tools { select = (state, activeViewId, activeView) => { if ( activeView.props.type && - activeView.props.type === 'MultipleDropDownContaier' + activeView.props.type === 'MultipleDropDownContainer' ) return true; diff --git a/wax-prosemirror-services/src/MultipleDropDownService/CreateDropDownService/CreateDropDownService.js b/wax-prosemirror-services/src/MultipleDropDownService/CreateDropDownService/CreateDropDownService.js index ff481070123a1574059bac55bbaa8e3b1587ce46..b4499195462491758a587c43d375397ec1c6aef3 100644 --- a/wax-prosemirror-services/src/MultipleDropDownService/CreateDropDownService/CreateDropDownService.js +++ b/wax-prosemirror-services/src/MultipleDropDownService/CreateDropDownService/CreateDropDownService.js @@ -4,7 +4,7 @@ import multipleDropDownOptionNode from '../schema/multipleDropDownOptionNode'; import MultipleDropDownNodeView from './MultipleDropDownNodeView'; import MultipleDropDownComponent from '../components/MultipleDropDownComponent'; import DropDownComponent from '../components/DropDownComponent'; -import MultipleDropDownToolGroupService from '../../WaxToolGroups/MultipleDropDownToolGroupService/MultipleDropDownToolGroupService'; +import MultipleDropDownToolGroupService from '../MultipleDropDownToolGroupService/MultipleDropDownToolGroupService'; class CreateDropDownService extends Service { name = 'CreateDropDownService'; diff --git a/wax-prosemirror-services/src/WaxToolGroups/MultipleDropDownToolGroupService/MultipleDropDown.js b/wax-prosemirror-services/src/MultipleDropDownService/MultipleDropDownToolGroupService/MultipleDropDown.js similarity index 100% rename from wax-prosemirror-services/src/WaxToolGroups/MultipleDropDownToolGroupService/MultipleDropDown.js rename to wax-prosemirror-services/src/MultipleDropDownService/MultipleDropDownToolGroupService/MultipleDropDown.js diff --git a/wax-prosemirror-services/src/WaxToolGroups/MultipleDropDownToolGroupService/MultipleDropDownToolGroupService.js b/wax-prosemirror-services/src/MultipleDropDownService/MultipleDropDownToolGroupService/MultipleDropDownToolGroupService.js similarity index 100% rename from wax-prosemirror-services/src/WaxToolGroups/MultipleDropDownToolGroupService/MultipleDropDownToolGroupService.js rename to wax-prosemirror-services/src/MultipleDropDownService/MultipleDropDownToolGroupService/MultipleDropDownToolGroupService.js diff --git a/wax-prosemirror-services/src/MultipleDropDownService/components/ContainerEditor.js b/wax-prosemirror-services/src/MultipleDropDownService/components/ContainerEditor.js index 71632a7df8370379ef6656d2c1c1e218bff3fba9..892b43a00d957f05ee7c929a1627246496272037 100644 --- a/wax-prosemirror-services/src/MultipleDropDownService/components/ContainerEditor.js +++ b/wax-prosemirror-services/src/MultipleDropDownService/components/ContainerEditor.js @@ -12,6 +12,7 @@ import { keymap } from 'prosemirror-keymap'; import { baseKeymap, chainCommands } from 'prosemirror-commands'; import { undo, redo } from 'prosemirror-history'; import { WaxContext, ComponentPlugin } from 'wax-prosemirror-core'; +import FakeCursorPlugin from '../plugins/FakeCursorPlugin'; const EditorWrapper = styled.div` position: relative; @@ -53,7 +54,7 @@ const ContainerEditor = ({ node, view, getPos }) => { return editable; }); - let finalPlugins = []; + let finalPlugins = [FakeCursorPlugin()]; const createKeyBindings = () => { const keys = getKeys(); @@ -104,7 +105,6 @@ const ContainerEditor = ({ node, view, getPos }) => { const plugins = [keymap(createKeyBindings()), ...app.getPlugins()]; finalPlugins = finalPlugins.concat([...plugins]); - useEffect(() => { WaxOverlays = ComponentPlugin('waxOverlays'); @@ -120,7 +120,7 @@ const ContainerEditor = ({ node, view, getPos }) => { }), dispatchTransaction, disallowedTools: ['Images', 'FillTheGap', 'MultipleChoice'], - type: 'MultipleDropDownContaier', + type: 'MultipleDropDownContainer', handleDOMEvents: { mousedown: () => { main.dispatch( diff --git a/wax-prosemirror-services/src/MultipleDropDownService/components/MultipleDropDownComponent.js b/wax-prosemirror-services/src/MultipleDropDownService/components/MultipleDropDownComponent.js index bf35fc9a43605d52b0d538349732d54ba08ace4b..4a9d509450332a3ff0edcad3f5540a27d4846403 100644 --- a/wax-prosemirror-services/src/MultipleDropDownService/components/MultipleDropDownComponent.js +++ b/wax-prosemirror-services/src/MultipleDropDownService/components/MultipleDropDownComponent.js @@ -12,8 +12,8 @@ const activeStylesSvg = css` fill: white !important; `; -const StyledIconActionContainer = styled.div` - display: inline-block; +const StyledIconActionContainer = styled.span` + display: inline; padding: 2px; ${props => props.isActive && activeStylesContainer} `; diff --git a/wax-prosemirror-services/src/MultipleDropDownService/plugins/FakeCursorPlugin.js b/wax-prosemirror-services/src/MultipleDropDownService/plugins/FakeCursorPlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..dd51ee312763e546366c118e54eb4e8ef02eb2b4 --- /dev/null +++ b/wax-prosemirror-services/src/MultipleDropDownService/plugins/FakeCursorPlugin.js @@ -0,0 +1,64 @@ +/* eslint-disable */ + +import { Plugin, PluginKey } from 'prosemirror-state'; +import { Decoration, DecorationSet } from 'prosemirror-view'; +import { Commands } from 'wax-prosemirror-core'; + +const fakeCursorPluginMultiple = new PluginKey('fakeCursorPluginMultiple'); +export default props => { + return new Plugin({ + key: fakeCursorPluginMultiple, + state: { + init: (_, state) => {}, + apply(tr, prev, _, newState) { + let createDecoration; + if ( + newState.selection.from === newState.selection.to && + Commands.isInTable(newState) + ) { + const widget = document.createElement('fakecursor'); + createDecoration = DecorationSet.create(newState.doc, [ + Decoration.widget(newState.selection.from, widget, { + key: 'fakecursor', + }), + ]); + } + return { + createDecoration, + }; + }, + }, + props: { + decorations: state => { + const fakeCursorPluginMultipleState = + state && fakeCursorPluginMultiple.getState(state); + if (fakeCursorPluginMultipleState) + return fakeCursorPluginMultipleState.createDecoration; + }, + handleDOMEvents: { + focus: (view, event) => { + event.preventDefault(); + const fakeCursor = document.getElementsByTagName('fakecursor'); + if (fakeCursor && fakeCursor[0]) { + for (let i = 0; i < fakeCursor.length; i++) { + fakeCursor[i].style.visibility = 'hidden'; + } + } + }, + blur: (view, event) => { + event.preventDefault(); + if (view && event.relatedTarget === null) { + view.focus(); + } else { + const fakeCursor = document.getElementsByTagName('fakecursor'); + if (fakeCursor && fakeCursor[0]) { + for (let i = 0; i < fakeCursor.length; i++) { + fakeCursor[i].style.visibility = 'visible'; + } + } + } + }, + }, + }, + }); +}; diff --git a/wax-prosemirror-services/src/TablesService/components/TableDropDown.js b/wax-prosemirror-services/src/TablesService/components/TableDropDown.js index 5abe535803b46d693b8138a6dff86c26a8e24b5a..1ee58b2ac419c1bd313e8104b10817f35157d322 100644 --- a/wax-prosemirror-services/src/TablesService/components/TableDropDown.js +++ b/wax-prosemirror-services/src/TablesService/components/TableDropDown.js @@ -1,44 +1,67 @@ /* eslint react/prop-types: 0 */ -import React, { useMemo, useContext, useState, useEffect } from 'react'; +import React, { + useMemo, + useContext, + useState, + useEffect, + useRef, + createRef, +} from 'react'; import styled from 'styled-components'; import * as tablesFn from 'prosemirror-tables'; -import { WaxContext, ReactDropDownStyles } from 'wax-prosemirror-core'; -import Dropdown from 'react-dropdown'; +import { WaxContext, Icon, useOnClickOutside } from 'wax-prosemirror-core'; -const Wrapper = styled.span` - ${ReactDropDownStyles}; +const Wrapper = styled.div` + opacity: ${props => (props.disabled ? '0.4' : '1')}; `; -const DropdownStyled = styled(Dropdown)` - display: inline-flex; - opacity: ${props => (props.select ? 1 : 0.4)}; - pointer-events: ${props => (props.select ? 'default' : 'none')}; +const DropDownButton = styled.button` + background: #fff; + border: none; + color: #000; + cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')}; + display: flex; + position: relative; + width: 160px; - .Dropdown-control { - border: none; - padding-top: 12px; - - &:hover { - box-shadow: none; - } + span { + position: relative; + top: 2px; } +`; - .Dropdown-arrow { - top: 17px; - } +const DropDownMenu = styled.div` + visibility: ${props => (props.isOpen ? 'visible' : 'hidden')}; + background: #fff; + display: flex; + flex-direction: column; + border: 1px solid #ddd; + border-radius: 0.25rem; + box-shadow: 0 0.2rem 0.4rem rgb(0 0 0 / 10%); + margin: 10px auto auto; + position: absolute; + width: 170px; + max-height: 150px; + overflow-y: scroll; + z-index: 2; - .Dropdown-menu { - align-items: flex-start; - display: flex; - flex-direction: column; - width: 120%; + span { + cursor: pointer; + padding: 8px 10px; + } - .Dropdown-option { - width: 100%; - } + span:focus { + background: #f2f9fc; + outline: 2px solid #f2f9fc; } `; +const StyledIcon = styled(Icon)` + height: 18px; + width: 18px; + margin-left: auto; +`; + const TableDropDown = ({ item }) => { const dropDownOptions = [ { label: 'Add column before', value: 'addColumnBefore' }, @@ -56,41 +79,105 @@ const TableDropDown = ({ item }) => { ]; const { activeView } = useContext(WaxContext); - const [selectedOption, setSelectedOption] = useState(''); - const appliedDropDownOptions = []; + const itemRefs = useRef([]); + const wrapperRef = useRef(); + const [isOpen, setIsOpen] = useState(false); + const isDisabled = !item.select(activeView.state); + + useOnClickOutside(wrapperRef, () => setIsOpen(false)); + + useEffect(() => { + if (isDisabled) setIsOpen(false); + }, [isDisabled]); - dropDownOptions.forEach(option => { - if (tablesFn[option.value](activeView.state)) { - appliedDropDownOptions.push(option); + const openCloseMenu = () => { + if (!isDisabled) setIsOpen(!isOpen); + if (isOpen) + setTimeout(() => { + activeView.focus(); + }); + }; + + const onKeyDown = (e, index) => { + e.preventDefault(); + // arrow down + if (e.keyCode === 40) { + if (index === itemRefs.current.length - 1) { + itemRefs.current[0].current.focus(); + } else { + itemRefs.current[index + 1].current.focus(); + } + } + + // arrow up + if (e.keyCode === 38) { + if (index === 0) { + itemRefs.current[itemRefs.current.length - 1].current.focus(); + } else { + itemRefs.current[index - 1].current.focus(); + } + } + + // enter + if (e.keyCode === 13) { + itemRefs.current[index].current.click(); } - }); - const isDisabled = item.select(activeView.state); - useEffect(() => {}, [selectedOption]); + // ESC + if (e.keyCode === 27) { + openCloseMenu(); + } + }; const TableDropDownComponent = useMemo( () => ( - <Wrapper> - <DropdownStyled - onChange={option => { - item.run( - activeView.state, - activeView.dispatch, - tablesFn[option.value], - ); - setSelectedOption(option.value); - - setTimeout(() => { - activeView.focus(); - }); + <Wrapper disabled={isDisabled} ref={wrapperRef}> + <DropDownButton + aria-expanded={isOpen} + aria-haspopup + disabled={isDisabled} + onKeyDown={e => { + e.preventDefault(); + if (e.keyCode === 40) { + itemRefs.current[0].current.focus(); + } + if (e.keyCode === 27) { + openCloseMenu(); + } }} - options={appliedDropDownOptions} - placeholder="Table Options" - select={isDisabled} - /> + onMouseDown={openCloseMenu} + type="button" + > + <span>Table Options</span> <StyledIcon name="expand" /> + </DropDownButton> + <DropDownMenu isOpen={isOpen} role="menu"> + {dropDownOptions.map((option, index) => { + itemRefs.current[index] = itemRefs.current[index] || createRef(); + return ( + <span + key={option.value} + onClick={() => { + item.run( + activeView.state, + activeView.dispatch, + tablesFn[option.value], + ); + + openCloseMenu(); + }} + onKeyDown={e => onKeyDown(e, index)} + ref={itemRefs.current[index]} + role="menuitem" + tabIndex="-1" + > + {option.label} + </span> + ); + })} + </DropDownMenu> </Wrapper> ), - [isDisabled, selectedOption, appliedDropDownOptions], + [isDisabled, isOpen], ); return TableDropDownComponent; diff --git a/wax-prosemirror-services/src/WaxToolGroups/QuestionsDropDownToolGroupService/DropDownComponent.js b/wax-prosemirror-services/src/WaxToolGroups/QuestionsDropDownToolGroupService/DropDownComponent.js index fe05823b367c367a80085f576c5d8d5537263f0c..fac5f300bd7c8e8e9584a830e2337ec1833e75e0 100644 --- a/wax-prosemirror-services/src/WaxToolGroups/QuestionsDropDownToolGroupService/DropDownComponent.js +++ b/wax-prosemirror-services/src/WaxToolGroups/QuestionsDropDownToolGroupService/DropDownComponent.js @@ -1,55 +1,71 @@ /* eslint-disable no-underscore-dangle */ -import React, { useContext, useMemo, useEffect, useState } from 'react'; +/* eslint react/prop-types: 0 */ +import React, { + useMemo, + useContext, + useState, + useEffect, + useRef, + createRef, +} from 'react'; import styled from 'styled-components'; -import { WaxContext, ReactDropDownStyles } from 'wax-prosemirror-core'; -import Dropdown from 'react-dropdown'; -import { v4 as uuidv4 } from 'uuid'; +import { WaxContext, Icon, useOnClickOutside } from 'wax-prosemirror-core'; const Wrapper = styled.div` - ${ReactDropDownStyles}; + opacity: ${props => (props.disabled ? '0.4' : '1')}; `; -const DropdownStyled = styled(Dropdown)` - display: inline-flex; - cursor: not-allowed; - opacity: ${props => (props.select ? 1 : 0.4)}; - pointer-events: ${props => (props.select ? 'default' : 'none')}; - .Dropdown-control { - border: none; - padding: 12px 122px 8px 10px; - &:hover { - box-shadow: none; - } + +const DropDownButton = styled.button` + background: #fff; + border: none; + color: #000; + cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')}; + display: flex; + position: relative; + width: 215px; + height: 100%; + + span { + position: relative; + top: 12px; } +`; - .Dropdown-arrow { - top: 17px; +const DropDownMenu = styled.div` + visibility: ${props => (props.isOpen ? 'visible' : 'hidden')}; + background: #fff; + display: flex; + flex-direction: column; + border: 1px solid #ddd; + border-radius: 0.25rem; + box-shadow: 0 0.2rem 0.4rem rgb(0 0 0 / 10%); + margin: 2px auto auto; + position: absolute; + width: 220px; + max-height: 150px; + overflow-y: scroll; + z-index: 2; + + span { + cursor: pointer; + padding: 8px 10px; } - .Dropdown-menu { - width: 100.4%; - display: flex; - flex-direction: column; - align-items: flex-start; - .Dropdown-option { - width: 100%; - } + span:focus { + background: #f2f9fc; + outline: 2px solid #f2f9fc; } `; -const DropDownComponent = ({ view, tools }) => { - const context = useContext(WaxContext); - const { - activeView, - activeViewId, - pmViews: { main }, - } = context; - const { state } = view; - - const [label, setLabel] = useState(null); - const isEditable = main.props.editable(editable => { - return editable; - }); +const StyledIcon = styled(Icon)` + height: 18px; + width: 18px; + margin-left: auto; + position: relative; + top: 10px; +`; +const DropDownComponent = ({ view, tools }) => { const dropDownOptions = [ { label: 'Multiple Choice', @@ -93,38 +109,126 @@ const DropDownComponent = ({ view, tools }) => { }, ]; + const context = useContext(WaxContext); + const { + activeView, + activeViewId, + pmViews: { main }, + } = context; + const { state } = view; + + const itemRefs = useRef([]); + const wrapperRef = useRef(); + const [isOpen, setIsOpen] = useState(false); + useOnClickOutside(wrapperRef, () => setIsOpen(false)); + + const [label, setLabel] = useState('Question Type'); + const isEditable = main.props.editable(editable => { + return editable; + }); + useEffect(() => { setLabel('Question Type'); dropDownOptions.forEach(option => { if (option.item.active(main.state)) { - setTimeout(() => { - setLabel(option.label); - }); + setLabel(option.label); } }); }, [activeViewId]); - let isDisabled = tools[0].select(state, activeView); + let isDisabled = !tools[0].select(state, activeView); + + useEffect(() => { + if (isDisabled) setIsOpen(false); + }, [isDisabled]); + + const openCloseMenu = () => { + if (!isDisabled) setIsOpen(!isOpen); + if (isOpen) + setTimeout(() => { + activeView.focus(); + }); + }; + if (!isEditable) isDisabled = false; + const onKeyDown = (e, index) => { + e.preventDefault(); + // arrow down + if (e.keyCode === 40) { + if (index === itemRefs.current.length - 1) { + itemRefs.current[0].current.focus(); + } else { + itemRefs.current[index + 1].current.focus(); + } + } + + // arrow up + if (e.keyCode === 38) { + if (index === 0) { + itemRefs.current[itemRefs.current.length - 1].current.focus(); + } else { + itemRefs.current[index - 1].current.focus(); + } + } + + // enter + if (e.keyCode === 13) { + itemRefs.current[index].current.click(); + } + + // ESC + if (e.keyCode === 27) { + openCloseMenu(); + } + }; + const onChange = option => { tools[option.value].run(main, context); + openCloseMenu(); }; const MultipleDropDown = useMemo( () => ( - <Wrapper key={uuidv4()}> - <DropdownStyled - key={uuidv4()} - onChange={option => onChange(option)} - options={dropDownOptions} - placeholder="Question Type" - select={isDisabled} - value={label} - /> + <Wrapper disabled={isDisabled} ref={wrapperRef}> + <DropDownButton + aria-expanded={isOpen} + aria-haspopup + disabled={isDisabled} + onKeyDown={e => { + e.preventDefault(); + if (e.keyCode === 40) { + itemRefs.current[0].current.focus(); + } + if (e.keyCode === 27) { + openCloseMenu(); + } + }} + onMouseDown={openCloseMenu} + type="button" + > + <span>{label}</span> <StyledIcon name="expand" /> + </DropDownButton> + <DropDownMenu isOpen={isOpen} role="menu"> + {dropDownOptions.map((option, index) => { + itemRefs.current[index] = itemRefs.current[index] || createRef(); + return ( + <span + key={option.value} + onClick={() => onChange(option)} + onKeyDown={e => onKeyDown(e, index)} + ref={itemRefs.current[index]} + role="menuitem" + tabIndex="-1" + > + {option.label} + </span> + ); + })} + </DropDownMenu> </Wrapper> ), - [isDisabled, label], + [isDisabled, isOpen, label], ); return MultipleDropDown;