<div id="page-container">

        <div id="to-do-list">

            <div id="form-container">
                <h1 contenteditable="true">To-do List</h1>

                <form v-on:submit.prevent="addNewItem">
                    <div id="add-item">

                        <label> Priority:
                            <select is="priority-select" v-model="newItem.priority"></select>
                        </label>

                        <label>
                            <input type="text" maxlength="50" v-model="newItem.text" placeholder="Enter something to do"
                                :autofocus="'autofocus'" required>
                            <i class="fas fa-plus pointer-cursor" title="Add New Item" @click='addNewItem'
                                @keyup.enter='addNewItem' tabindex=" 0"></i>
                        </label>
                    </div>
                </form>

            </div>

            <div id="list-display">
                <div v-if="items.length > 0">
                    <h2>
                        {{ incompleteCount }} Thing<span v-if="incompleteCount != 1">s</span> To-do
                        <i :class="[showFilter ? 'fas fa-angle-up' : 'fas fa-angle-down', 'pointer-cursor']"
                            @click="showFilter = !showFilter" @keyup.enter="showFilter = !showFilter"
                            title="Hide/Show Filter Panel" tabindex="0"></i>
                    </h2>
                    <div class="bottom-spacing" v-show="showFilter">
                        <label title="Filter">
                            <i class="fas fa-filter"></i>
                            <select v-model="filterBy" tabindex="0">
                                <option selected>All</option>
                                <option>Completed</option>
                                <option>Incomplete</option>
                            </select>
                        </label>

                        <label>
                            <i class="fas fa-sort-alpha-down pointer-cursor" title="Alphabetical Sort"
                                :class="[{'active-sort': sortAlphaAscend.value}]"
                                @click="flipSortValue(sortAlphaAscend, sortAlphaDescend)"
                                @keyup.enter="flipSortValue(sortAlphaAscend, sortAlphaDescend)" alt='a -> z'
                                tabindex="0"></i>

                            <i class="fas fa-sort-alpha-up-alt pointer-cursor" title="Reverse Alphabetical Sort"
                                :class="[{'active-sort': sortAlphaDescend.value}]"
                                @click="flipSortValue(sortAlphaDescend, sortAlphaAscend)"
                                @keyup.enter="flipSortValue(sortAlphaDescend, sortAlphaAscend)" alt='z -> a'
                                tabindex="0"></i>

                            <i class="fas fa-sort-amount-down-alt pointer-cursor" title="Lowest Priority First"
                                :class="[{'active-sort': sortPriorityAscend.value}]"
                                @click="flipSortValue(sortPriorityAscend, sortPriorityDescend)"
                                @keyup.enter="flipSortValue(sortPriorityAscend, sortPriorityDescend)" alt='low -> hi'
                                tabindex="0"></i>

                            <i class="fas fa-sort-amount-up pointer-cursor" title="Highest Priority First"
                                :class="[{'active-sort': sortPriorityDescend.value}]"
                                @click="flipSortValue(sortPriorityDescend, sortPriorityAscend)"
                                @keyup.enter="flipSortValue(sortPriorityDescend, sortPriorityAscend)" alt='hi -> low'
                                tabindex="0"></i>

                            <i class="fas fa-sort pointer-cursor" title="Incomplete First"
                                :class="[{'active-sort': sortIncompleteFirst.value}]"
                                @click="sortIncompleteFirst.value = !sortIncompleteFirst.value"
                                @keyup.enter="sortIncompleteFirst.value = !sortIncompleteFirst.value"
                                alt='compl -> incompl' tabindex="0"></i>

                            <i class="fas fa-info-circle" alt='info' title="Specific sorting can be done by using a combination of available sorting 
methods. (i.e., to sort alphabetically with the highest priority first 
and seperated by incomplete first, click: 
Alphabetical Sort -> Sort by Highest Priority First -> Incomplete First.)">
                            </i>
                        </label>
                    </div>

                </div>

                <div v-else>
                    <h2>Nothing to do</h2>
                    <label>Catch up on some sleep!
                        <i class=" fas fa-bed"></i>
                    </label>
                </div>

                <ul>
                    <li v-for="item in filteredItems" :key="item.id" :class='{ "strike-through": item.completed}'>

                        <div>

                            <div class='left-align'>
                                <i :class="[item.completed ? 'far fa-check-square' : 'far fa-square', 'pointer-cursor']"
                                    title="Complete" @click='item.completed = !item.completed'
                                    @keyup.enter='item.completed = !item.completed' tabindex="0">
                                </i>
                            </div>

                            <div id="li-label">
                                <label>

                                    <div>
                                        <span id="item-text">{{ item.text}}</span>
                                    </div>

                                    <div>
                                        Priority:
                                        <select is="priority-select"
                                            :class="[{'strike-through': item.completed}, 'colored-bg-select']"
                                            v-model="item.priority" :disabled="item.completed">
                                        </select>
                                    </div>

                                </label>
                            </div>

                            <div class="right-align">
                                <i class='fas fa-trash pointer-cursor' title="Delete" @click='removeItemById(item.id)'
                                    @keyup.enter='removeItemById(item.id)' tabindex="0">
                                </i>
                            </div>

                        </div>

                    </li>
                </ul>

            </div>

        </div>
    </div>
:root {
    --bg-color: rgb(92, 206, 206);
}

* {
    box-sizing: border-box;
}

html, body, #page-container {
    width: 100vw;
    height: 100vh;
    margin: 0;
    text-align: center;

    background-image: radial-gradient(white, rgb(188, 240, 240));
}

/*----HTML Tags----*/

ul {
    padding: 0;
    width: 99%;
    height: 90%;
    margin: auto;
    overflow-y: auto;
    scrollbar-width: thin;
}

ul::-webkit-scrollbar-track {
    background-color: lightgray;
}

ul::-webkit-scrollbar {
    width: 5px;
}

ul::-webkit-scrollbar-thumb{
    background-color: slategray;
    border-radius: 10px;;
}


li {
    width: 100%;
    height: fit-content;
    margin: 2px auto;
    padding: 5px;
    overflow: auto;

    position: relative;
    
    list-style-type: none;
    background-color: var(--bg-color);
}

option {
    background-color: whitesmoke;
}

i {
    width: 5%;
    color: black;
}


/*----IDs----*/

#to-do-list {
    width: 30vw;
    min-width: 400px;
    height: 90vh;
    min-height: 500px;
    position: relative;
    top: 2%;
    margin: auto;
    background-color: white;
    border-radius: 3%;
    box-shadow:1px 1px 1px 1px black;
}

#form-container {
    width: 100%;
    height: 20%;
    padding-top: 2%;
}

#list-display {
    width: 100%;
    height: 75%;
    display: flex;
    flex-direction: column;
}

#li-label {
    width: 70%;
    margin: auto;
    overflow-wrap: break-word;
}

#item-text {
    font-weight: bold;
}


/*----Classes----*/

.pointer-cursor:hover{
    cursor: pointer;
}

.bottom-spacing {
    margin-bottom: 1%;
}

.hide-display {
    display: none;
}

.hide-visibility {
    visibility: hidden;
}

.left-align {
    position: absolute;
    left: 10px;
    top: 35%;
    width: 10%;
}

.right-align {
    position: absolute;
    right: 10px;
    top: 35%;
    width: 10%;
}

.colored-bg-select {
    appearance: none;
    -webkit-appearance: none;

    border: none;
    background-color: var(--bg-color);
    text-decoration: underline;
}

.strike-through {
    text-decoration: line-through;
    color: rgba(0, 0, 0, 0.5);
    background-color: rgb(195, 227, 227);
}

.active-sort {
    background-color: rgba(232, 232, 232, 1);
    border: 1px solid black;
    border-radius: 5px;
}

.no-priority {
    color: black;
}

.low-priority {
    color: blue;
}

.med-priority {
    color: rgb(216, 0, 253);
}

.high-priority {
    color: red;
}
window.onload = function () {

    Vue.component('priority-select', {
        data: function () {
            return {
                noPriority: { type: Boolean, default: true },
                isLow: { type: Boolean, default: false },
                isMed: { type: Boolean, default: false },
                isHigh: { type: Boolean, default: false },
                priorityOptions: [
                    {
                        'priority': 'None',
                        'class': 'no-priority'
                    },
                    {
                        'priority': 'Low',
                        'class': 'low-priority'
                    },
                    {
                        'priority': 'Medium',
                        'class': 'med-priority',
                    },
                    {
                        'priority': 'High',
                        'class': 'high-priority'
                    }
                ]
            }
        },

        props: {
            'value': {
                type: String,
                required: true,
                validator: function (value) {
                    return ['none', 'low', 'medium', 'high'].indexOf(value.toLowerCase()) !== -1;
                }
            }
        },

        template: `<select
        :class="[
            {'no-priority': noPriority}, 
            {'low-priority': isLow}, 
            {'med-priority': isMed}, 
            {'high-priority': isHigh}
        ]" 
        :value="value" 
        @input="$emit('input', $event.target.value)" 
        tabindex="0">
            <option 
                v-for="option in priorityOptions" 
                :class="option.class">
            {{option.priority}}
            </option>
        </select>`,

        watch: {
            value: function () {
                this.setTextColor();
            }
        },

        created: function () {
            this.setTextColor();
        },

        methods: {
            setTextColor() {
                this.noPriority = this.value == "None" ? true : false;
                this.isLow = this.value == "Low" ? true : false;
                this.isMed = this.value == "Medium" ? true : false;
                this.isHigh = this.value == "High" ? true : false;
            },
        }
    });

    new Vue({
        el: "#to-do-list",
        data: {
            items: [],
            filterBy: 'All',
            showFilter: false,
            showSettings: false,
            newItem: {
                "text": "",
                "id": 0,
                "priority": "None",
                "completed": false
            },
            sortAlphaAscend: { 'value': false },
            sortAlphaDescend: { 'value': false },
            sortPriorityAscend: { 'value': false },
            sortPriorityDescend: { 'value': false },
            sortIncompleteFirst: { 'value': true }
        },

        created: function () {
            // adds a default to-do item
            this.items.unshift({
                "id": this.newItem.id++,
                "text": "Make a to-do list",
                "completed": false,
                "priority": "High"
            });
        },

        methods: {
            addNewItem: function () {
                if (this.newItem.text !== "") {
                    this.items.unshift({
                        "id": this.newItem.id++,
                        "text": this.newItem.text,
                        "priority": this.newItem.priority,
                        "completed": this.newItem.completed
                    });

                    this.newItem.text = "";
                }
            },

            removeItemById: function (id) {
                let index = this.items.findIndex(item => item.id === id);
                this.items.splice(index, 1);
            },

            sortByAlphaAscending: function () {
                this.items.sort(function (item1, item2) {
                    if (item1.text.toLowerCase() < item2.text.toLowerCase()) {
                        return -1;
                    } else if (item1.text.toLowerCase() > item2.text.toLowerCase()) {
                        return 1;
                    }

                    return 0;
                });
            },

            sortByAlphaDescending: function () {
                this.items.sort(function (item1, item2) {
                    if (item1.text.toLowerCase() > item2.text.toLowerCase()) {
                        return -1;
                    } else if (item1.text.toLowerCase() < item2.text.toLowerCase()) {
                        return 1;
                    }

                    return 0;
                });
            },

            sortByAscendingPriority: function () {
                this.items.sort(function (item1, item2) {

                    if (item1.priority == item2.priority) {
                        return 0;
                    } else if (item1.priority === 'None') {
                        return -1;
                    } else if (item2.priority === 'None') {
                        return 1;
                    } else if (item2.priority === 'High') {
                        return -1;
                    } else if (item1.priority === 'High') {
                        return 1;
                    } else if (item1.priority === 'Low' && item2.priority === 'Medium') {
                        return -1;
                    } else if (item1.priority === 'Medium' && item2.priority === 'Low') {
                        return 1;
                    }

                    return 0;
                });
            },

            sortByDescendingPriority: function () {
                this.items.sort(function (item1, item2) {

                    if (item1.priority === item2.priority) {
                        return 0;
                    } else if (item1.priority === 'None') {
                        return 1;
                    } else if (item2.priority === 'None') {
                        return -1;
                    } else if (item2.priority === 'High') {
                        return 1;
                    } else if (item1.priority === 'High') {
                        return -1;
                    } else if (item1.priority === 'Low' && item2.priority === 'Medium') {
                        return 1;
                    } else if (item1.priority === 'Medium' && item2.priority === 'Low') {
                        return -1;
                    }

                    return 0;
                });
            },

            sortByCompleted: function () {
                this.items.sort(function (item1, item2) {
                    if (item1.completed && !item2.completed) {
                        return 1;
                    } else if (!item1.completed && item2.completed) {
                        return -1;
                    }

                    return 0;
                });
            },

            sort: function () {
                if (this.sortAlphaAscend.value) {
                    this.sortByAlphaAscending();
                } else if (this.sortAlphaDescend.value) {
                    this.sortByAlphaDescending();
                }

                if (this.sortPriorityAscend.value) {
                    this.sortByAscendingPriority();
                } else if (this.sortPriorityDescend.value) {
                    this.sortByDescendingPriority();
                }

                if (this.sortIncompleteFirst.value) {
                    this.sortByCompleted();
                }
            },

            flipSortValue: function (val1, val2) {
                val1.value = !val1.value;
                val2.value = (val1.value && val2.value) ? !val2.value : val2.value;
            }
        },

        computed: {
            incompleteCount: function () {
                return this.items.filter(item => !item.completed).length;
            },

            filteredItems: function () {
                let filter = this.filterBy.toLowerCase();

                this.sort();

                return this.items.filter(function (item) {
                    switch (filter) {
                        case 'completed':
                            return item.completed;
                        case 'incomplete':
                            return !item.completed;
                        case 'all':
                            return true;
                        default:
                            return;
                    }
                });
            },
        }
    });
}

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.10.2/css/all.css

External JavaScript

  1. https://cdn.jsdelivr.net/npm/vue/dist/vue.js