<div id="calendar"></div>

<div class="modal" id="modal-template">
	<div class="modal__title">
		<label>タイトル: <input type="text" id="title"></label>
	</div>
	<div class="modal__color">
		<label>背景色: <input type="color" id="color"></label>
	</div>
	<div class="modal__times">
		<label>開始: <input type="date" id="start"></label>
		<label>終了: <input type="date" id="end"></label>
	</div>
      
	<div class="modal-action-buttons">
		<button class="modal-action-buttons__button save" id="save">保存</button>
		<button class="modal-action-buttons__button delete" id="delete">削除</button>
		<button class="modal-action-buttons close material-icons" id="cancel">cancel</button>
	</div>
</div>
.modal {
	display: none;
	flex-direction: column;
	align-items: center;
	padding: 1em;
	max-width: 250px;
	position: relative;
	background-color: #fff;
	border: 3px solid #f0f0f0;
	border-radius: 4px;
	
	&__title {
		margin-bottom: .5em;
		align-self: flex-start;

		input[type="text"] {
			width: 100px;
		}
		label {
			display: flex;
			align-items: center;
		}
	}
	
	&__color {
		margin-bottom: .5em;
		align-self: flex-start;
	}
	&__times {
		margin-bottom: 2em;

		& > * {
			margin-bottom: .5em;
			
			&:last-child {
				margin-bottom: 0;
			}
		}

		label {
			display: flex;
			align-items: center;
		}
	}
}

.modal-action-buttons {
	&__button {
		border: 2px solid #000;
		padding: .5em 1em;
		border-radius: 4px;
		background-color: transparent;
		cursor: pointer;
		transition: .25s;
	}
}

@mixin button( $color ) {
	color: $color;
	border-color: $color;

	&:hover {
		color: #fff;
		background-color: $color;
	}
}

.save {
	@include button( #2c75ff );
}

.delete {
	@include button( #dd1155 );
}

.close {
	cursor: pointer;
	border: none;
	background-color: transparent;
	position: absolute;
	top: 0;
	right: 0;
	transform: translate( 50%, -50% );
	transition: .15s;

	&:hover {
		opacity: .85;
		background-color: transparent ;
	}
}

input[type="date"] {
	padding: .5em 1em;
	border: 1px solid #ddd;
	border-radius: 4px;
	font-weight: bold;
}
View Compiled
let calendar;
document.addEventListener( 'DOMContentLoaded', () => {
	const calendarEl = document.getElementById( 'calendar' );
	calendar = new FullCalendar.Calendar( calendarEl, {
    initialView: 'dayGridMonth',
	locale: 'local',
	timeZone: 'local',
	eventDisplay: 'block',
	displayEventTime: false,
	selectable: true,
	select: arg => {
		initEditModal( arg );
	},
	eventClick: arg => {
		console.log( arg );
		initEditModal( arg );
	},
  } );
  
  calendar.render();
} );

const initEditModal = data  => {
    removeAlreadyModal();
    const defModal = document.getElementById( 'modal-template' );
    const modal = defModal.cloneNode( true );
    modal.id = 'modal';

    setupModalPosition( modal, data.jsEvent );
    document.body.appendChild( modal );
	if ( data.event === undefined ) {
		document.querySelector( '#modal .delete' ).remove();
	}
	
	setupModalData( modal, data );

    registerEditModalEvent( modal, data );
};

const setupModalPosition = ( modal, e ) => {
    modal.style.display = 'flex';
    modal.style.position = 'absolute';
    modal.style.zIndex = 9999;

    const position = calcModalPosition( e );
    modal.style.left = `${position.x}px`;
    modal.style.top = `${position.y}px`;
};

const calcModalPosition = e => {
    const windowWidth = window.outerWidth;

    const y = e.pageY + 16;
    let x = e.pageX;

    if ( e.pageX <= 125 ) {
        x = e.pageX;
    } else if (  e.pageX > 125 && windowWidth - e.pageX > 125 ) {
        x = e.pageX - 125;
    } else if ( windowWidth - e.pageX <= 125 ) {
        x = e.pageX - 250;
    }

    return {
        x: x,
        y: y
    };
};

const removeAlreadyModal = () => {
    const modal = document.getElementById( 'modal' );
    if ( modal ) {
        modal.remove();
    }
};

// モーダル登録処理
const registerEditModalEvent = ( modal, arg ) => {
    const start = modal.querySelector( '#start' );
    const end = modal.querySelector( '#end' );
	const title = modal.querySelector( '#title' );
    const color = modal.querySelector( '#color' );
	
    // 保存
    const saveButton = modal.querySelector( '#save' );
    if ( saveButton ) {
        saveButton.addEventListener( 'click', e => {
            e.preventDefault();
			
			
			if ( arg.event !== undefined ) {
				// 変更時
				const endStrings = end.value && start.value !== end.value ? end.value.split( '-' ) : start.value.split( '-' );
				const endDate = new Date( endStrings[0], parseInt( endStrings[1] ) - 1, endStrings[2], 23, 59, 59 );

				arg.event.setStart( start.value );
				arg.event.setEnd( endDate );
				arg.event.setProp( 'title', title.value );
				arg.event.setProp( 'backgroundColor', color.value );
			} else {
				// 新規作成時
				const endStrings = end.value && start.value !== end.value ? end.value.split( '-' ) : start.value.split( '-' );
				const endDate = new Date( endStrings[0], parseInt( endStrings[1] ) - 1, endStrings[2], 23, 59, 59 );	
				calendar.addEvent( {
					start: start.value,
					end: endDate,
					title: title.value,
					backgroundColor: color.value
				} );
			}

            calendar.unselect();
            modal.remove();
        } );
    }

    // キャンセル
    const cancelButton = modal.querySelector( '#cancel' );
    cancelButton.addEventListener( 'click', e => {
        e.preventDefault();

        calendar.unselect();
        modal.remove();
    } );

    // 削除
    const deleteButton = modal.querySelector( '#delete' );
    if ( deleteButton ) {

        deleteButton.addEventListener( 'click', e => {
            e.preventDefault();
            arg.event.remove();
            calendar.unselect();
            modal.remove();
        } );
    }
};

// モダールに既存イベントを設定
const setupModalData = ( modal, data ) => {
    const start = modal.querySelector( '#start' );
    const end = modal.querySelector( '#end' );
	const title = modal.querySelector( '#title' );
    const color = modal.querySelector( '#color' );
	
	console.log( data );
	if ( data.event !== undefined ) {
		start.value = /T/.test( data.event.startStr ) ? data.event.startStr.split( 'T' )[0] : data.event.startStr;
		end.value = /T/.test( data.event.endStr ) ? data.event.endStr.split( 'T' )[0] : data.event.endStr;
		title.value = data.event.title;
		color.value = data.event.backgroundColor;
	} else {
		start.value = data.startStr;
		
		const diffTime = Math.abs( data.end - data.start );
		const diffDays = Math.ceil( diffTime / ( 1000 * 60 * 60 * 24 ) );
		if ( 1 < diffDays ) {
		
			const endDate = data.end;
			endDate.setDate( endDate.getDate() - 1 );
			end.value = formatDate( endDate );
		}
	}
    
};

// DateObject to YYYY-MM-DD
function formatDate(date) {
    var d = new Date(date),
        month = '' + (d.getMonth() + 1),
        day = '' + d.getDate(),
        year = d.getFullYear();

    if (month.length < 2) 
        month = '0' + month;
    if (day.length < 2) 
        day = '0' + day;

    return [year, month, day].join('-');
}

External CSS

  1. https://cdn.jsdelivr.net/npm/[email protected]/main.min.css
  2. https://fonts.googleapis.com/icon?family=Material+Icons

External JavaScript

  1. https://cdn.jsdelivr.net/npm/[email protected]/main.min.js