diff --git a/corsscripts/stacksortable.js b/corsscripts/stacksortable.js
index f8df73ea5d338abcde7bc5727dcbd33940d3d0c2..00a8f30646848cecf165e316bfa9a30c07990d19 100644
--- a/corsscripts/stacksortable.js
+++ b/corsscripts/stacksortable.js
@@ -25,74 +25,74 @@ export const SUPPORTED_CALLBACK_FUNCTIONS = [
 ];
 
 /**
- * Preprocess and validate proof steps, block user options, and sortable user options ready for use in `stack_sortable` class.
+ * Preprocess and validate steps, sortable user options, headers and indices ready for use in `stack_sortable` class.
  *
- * The function takes proof steps in the form of a Parson's JSON or Maxima string variable, along with block user options
+ * The function takes steps in the form of a Parson's JSON or Maxima string variable, along with block user options
  * and sortable user options. It performs the following tasks:
- * 1. If `proofSteps` is a Maxima string of expected format, it converts it to an object using `_stackstring_objectify`.
+ * 1. If `steps` is a Maxima string of expected format, it converts it to an object using `_stackstring_objectify`.
  * 2. It validates the structure of the Parson's JSON using `_validate_parsons_JSON`.
- * 3. If the Parsons JSON contains "steps" and "options," it separates them.
- *    - If "header" is present in options, it separates this away from Sortable options into `blockUserOpts`.
- *    - It splits Sortable options into "used" and "available" and passes to `sortableUserOpts`.
- * 4. If `proofSteps` is a Maxima string (after separation), it converts it to an object.
+ * 3. If the Parsons JSON is of depth two with a valid set of top-level keys it separates them.
+ * 4. If `steps` is a Maxima string (after separation), it converts it to an object.
  *
- * @param {string|Object} proofSteps - The proof steps to be preprocessed. Either a JSON of expected format
- * or
- * @param {Object} blockUserOpts - Block user options for the 'header' setting, should be passed as an empty Object.
- * @param {Object} sortableUserOpts - Sortable user options split into used and available, should be passed as an empty Object.
- * @returns {Array} - An array containing preprocessed proof steps, block user options,
- * sortable user options, and a boolean indicating the validity of the proof steps structure.
+ * @param {Object|string} steps - The steps object or string representation of steps.
+ * @param {Object} sortableUserOpts - Options for the sortable plugin.
+ * @param {Array} headers - Headers for the answer lists.
+ * @param {Array} available_header - Header for the available list.
+ * @param {Array} index - Index column.
+ * @returns {Array} - An array containing preprocessed steps, options, headers, available header, and index in that order.
  *
  * @example
  * // Returns [
- * //   { step1: "Proof step 1", step2: "Proof step 2" },
- * //   { used: { header: "Header 1" }, available: { header: "Header 2" } },
- * //   { used: { option1: "Value 1" }, available: { option2: "Value 2" } },
+ * //   { step1: "step 1 text", step2: "step 2 text" },
+ * //   { option1: "Value 1", option2: "Value 2" },
+ * //   ["header 1", "header 2"],
+ * //   ["Drag from here:"],
+ * //   null,
  * //   true
  * // ]
  * preprocess_steps({
  *   steps: {
- *     step1: "Proof step 1",
- *     step2: "Proof step 2"
+ *     step1: "step 1 text",
+ *     step2: "step 2 text"
  *   },
  *   options: {
- *     header: ["Header 1", "Header 2"],
  *     option1: "Value 1",
  *     option2: "Value 2"
  *   }
- * }, {}, {});
+ * }, ["header 1", "header 2"], ["Drag from here:"], null);
  */
-export function preprocess_steps(proofSteps, sortableUserOpts, headers, available_header, index) {
-    // Check if proofSteps is a string and convert it to an object
-    // (this occurs when proof steps are a flat list coming from a Maxima variable)
-    if (typeof proofSteps === "string") {
-        proofSteps = _stackstring_objectify(proofSteps);
+export function preprocess_steps(steps, sortableUserOpts, headers, available_header, index) {
+    // Check if steps is a string and convert it to an object
+    // (this occurs when the steps are a flat list coming from a Maxima variable)
+    if (typeof steps === "string") {
+        steps = _stackstring_objectify(steps);
     }
 
     // Validate the object
-    var valid = _validate_parsons_JSON(proofSteps);
+    var valid = _validate_parsons_JSON(steps);
 
-    // At this point, we know proofSteps is either a flat JSON, or it's top-level keys are a subset of 
-    // ["steps", "options", "headers", ""index"], and contains at least "steps". Separate these if they are present
-    if (_validate_top_level_keys_JSON(proofSteps, ["steps", "options", "headers", "index", "available_header"], ["steps"])) {
-        var sortableUserOpts = proofSteps["options"];
+    // At this point, we know steps is either a flat JSON, or it's top-level keys are a subset of 
+    // ["steps", "options", "headers", "available_header", "index"], and contains at least "steps". 
+    // Separate these if they are present.
+    if (_validate_top_level_keys_JSON(steps, ["steps", "options", "headers", "index", "available_header"], ["steps"])) {
+        var sortableUserOpts = steps["options"];
         // only want to replace defaults for headers if they have been provided
-        if ("headers" in proofSteps) {
-            headers = proofSteps["headers"];
+        if ("headers" in steps) {
+            headers = steps["headers"];
         }
-        if ("available_header" in proofSteps) {
-            available_header = proofSteps["available_header"];
+        if ("available_header" in steps) {
+            available_header = steps["available_header"];
         }
-        index = proofSteps["index"];
-        proofSteps = proofSteps["steps"];
+        index = steps["index"];
+        steps = steps["steps"];
     }
 
-    // Convert proofSteps to an object if it is still a string (occurs when the proof steps comes from a Maxima variable)
-    if (typeof proofSteps === "string") {
-        proofSteps = _stackstring_objectify(proofSteps);
+    // Convert steps to an object if it is still a string (occurs when the steps comes from a Maxima variable)
+    if (typeof steps === "string") {
+        steps = _stackstring_objectify(steps);
     }
 
-    return [proofSteps, sortableUserOpts, headers, available_header, index, valid];
+    return [steps, sortableUserOpts, headers, available_header, index, valid];
 }
 
 /**
@@ -111,30 +111,31 @@ function _stackstring_objectify(stackjson_array_string) {
 }
 
 /**
- * Validate the structure of Parson's JSON for proof steps.
+ * Validate the structure of Parson's JSON `steps`.
  *
- * The function checks the structure of the provided Parson's JSON (`proofSteps`)
+ * The function checks the structure of the provided Parson's JSON (`steps`)
  * to ensure it follows specific patterns:
- * 1. If the JSON has depth 1, it should be a valid proofStep JSON (i.e., should have string values).
- * 2. If the JSON has depth 2, the top-level keys should be ["steps", "options"], and the value for "steps"
- *    should be a valid proofStep JSON. Options are not validated here.
+ * 1. If the JSON has depth 1, it should be a valid flat JSON (i.e., should have string values).
+ * 2. If the JSON has depth 2, the top-level keys should be a subset of 
+ *    `["steps", "options", "headers", "index", "available_header"]`, and must contain `"steps"`. 
+ *    The value for "steps" should be a valid flat JSON. Options are not validated here.
  *
- * @param {Object} proofSteps - The Parson's JSON to be validated.
+ * @param {Object} steps - The Parson's JSON to be validated.
  * @returns {boolean} - Returns true if the provided Parsons JSON follows the expected structure, false otherwise.
  *
  * @example
  * // Returns true
  * _validate_parsons_JSON({
- *   "step1": "proof step 1",
- *   "step2": "proof step 2"
+ *   "step1": "step 1 text",
+ *   "step2": "step 2 text"
  * });
  *
  * @example
  * // Returns true
  * _validate_parsons_JSON({
  *   "steps": {
- *     "step1": "proof step 1",
- *     "step2": "proof step 2"
+ *     "step1": "step 1 text",
+ *     "step2": "step 2 text"
  *   },
  *   "options": {
  *     "option1": "value1",
@@ -146,72 +147,79 @@ function _stackstring_objectify(stackjson_array_string) {
  * // Returns false
  * _validate_parsons_JSON({
  *   "invalidKey": {
- *     "step1": "proof step 1",
- *     "step2": "proof step 2"
+ *     "step1": "step 1 text",
+ *     "step2": "step 2 text"
  *   }
  * });
  */
-function _validate_parsons_JSON(proofSteps) {
-    // If the JSON has depth 1 then it should be a valid proofStep JSON (i.e., should have string values)
-    if (Object.values(proofSteps).every((val) => !(typeof(val) == 'object'))) {
-        return _validate_proof_steps(proofSteps);
+function _validate_parsons_JSON(steps) {
+    // If the JSON has depth 1 then it should be a valid flat JSON (i.e., should have string values).
+    if (Object.values(steps).every((val) => !(typeof(val) == 'object'))) {
+        return _validate_flat_steps(steps);
     }
-    // Else the top-level of the JSON should have keys ["steps", "options"].
-    // The value for "keys" should be a valid proofStep JSON
-    // We do not validate options here
-    if (Object.values(proofSteps).some((val) => typeof(val) == "object")) {
-        if (JSON.stringify(Object.keys(proofSteps)) !== JSON.stringify(["steps", "options"])) {
+    // Else the top-level of the JSON should have keys that are a subset of ["steps", "options", "headers", "index", "available_header"]
+    // and a superset of ["steps"].
+    // The value for "steps" should be a valid flat JSON.
+    // We do not validate options here.
+    if (Object.values(steps).some((val) => typeof(val) == "object")) {
+        if (!_validate_top_level_keys_JSON(steps, ["steps", "options", "headers", "index", "available_header"], ["steps"])) {
             return false;
         }
-        if (!_validate_proof_steps(proofSteps["steps"])) {
+        if (!_validate_flat_steps(steps["steps"])) {
             return false;
         }
         return true;
     }
-    // TO-DO : we are missing one case here in depth 2 case and unclear how to catch it:
-    // if an author writes {"any string" : {#stackjson_stringify(proof_steps)#}},
-    // then this should throw an error
 }
 
 /**
- * Validate the structure of proof steps.
+ * Validate the structure of a flat steps JSON.
  *
- * The function checks the structure of the provided proof steps (`proofSteps`)
+ * The function checks the structure of the provided steps (`steps`)
  * to ensure that all values are strings.
  *
- * If the proof steps are provided as a Maxima variable (string of form '[["key", "value"], ...]'), they are converted
+ * If the steps are provided as a Maxima variable (string of form '[["key", "value"], ...]'), they are converted
  * to a JSON object using the `_stackstring_objectify` function before validation.
  *
- * @param {string|Object} proofSteps - The proof steps to be validated. If a string,
+ * @param {string|Object} steps - The flat JSON to be validated. If a string,
  * it is assumed to be a Maxima variable and will be converted to a JSON object.
- * @returns {boolean} - Returns true if all values in the proof steps are strings, false otherwise.
+ * @returns {boolean} - Returns true if all values in `steps` are strings, false otherwise.
  *
  * @example
  * // Returns true
- * _validate_proof_steps({
- *   step1: "Proof step 1",
- *   step2: "Proof step 2"
+ * _validate_flat_steps({
+ *   step1: "step 1 text",
+ *   step2: "step 2 text"
  * });
  *
  * @example
  * // Returns true
- * _validate_proof_steps('["step1", "Proof step 1"], ["step2", "Proof step 2"]]');
+ * _validate_flat_steps('["step1", "step 1 text"], ["step2", "step 2 text"]]');
  *
  * @example
  * // Returns false
- * _validate_proof_steps({
- *   step1: "Proof step 1",
+ * _validate_flat_steps({
+ *   step1: "step 1 text",
  *   step2: 123 // Not a string
  * });
  */
-function _validate_proof_steps(proofSteps) {
-    // Case when proof steps are coming from a Maxima variable: convert to a JSON
-    if (typeof(proofSteps) == 'string') {
-        proofSteps = _stackstring_objectify(proofSteps);
+function _validate_flat_steps(steps) {
+    // Case when steps are coming from a Maxima variable: convert to a JSON
+    if (typeof(steps) == 'string') {
+        steps = _stackstring_objectify(steps);
     }
-    return Object.values(proofSteps).every((val) => typeof(val) == 'string');
+    return Object.values(steps).every((val) => typeof(val) == 'string');
 }
 
+/**
+ * Validates the top-level keys of a JSON object by checking they are a subset of `validKeys` 
+ * and a superset of `requiredKeys`.
+ * 
+ * @param {Object} JSON - The JSON object to validate.
+ * @param {Array} validKeys - An array of valid top-level keys.
+ * @param {Array} requiredKeys - An array of top-level keys that are required.
+ * @returns {boolean} - True if the JSON object passes validation, otherwise false.
+ */
 function _validate_top_level_keys_JSON(JSON, validKeys, requiredKeys) {
     const keys = Object.keys(JSON);
     const missingRequiredKeys = requiredKeys.filter(key => !keys.includes(key));
@@ -219,57 +227,6 @@ function _validate_top_level_keys_JSON(JSON, validKeys, requiredKeys) {
     return invalidKeys.length === 0 && missingRequiredKeys.length === 0;
 }
 
-/**
- * Flips the orientation of specified used and available lists, and the bin (if present) in the UI.
- * The function toggles between 'list-group row' and 'list-group col' classes for the specified elements.
- * The bin element (if present) is expected to have ID 'bin'.
- *
- * @param {string} usedId - The ID of the used list element.
- * @param {string} availableId - The ID of the available list element.
- * @returns {void}
- *
- * @example
- * // HTML structure:
- * // <div id="usedList" class="list-group row">...</div>
- * // <div id="availableList" class="list-group row">...</div>
- * // <div id="bin" class="list-group row">...</div>
- *
- * // JavaScript usage:
- * _flip_orientation('usedList', 'availableList');
- */
-function _flip_orientation(usedId, availableId) {
-    var usedList = document.getElementById(usedId);
-    var availableList = document.getElementById(availableId);
-    var newClass = usedList.className == 'list-group row' ? 'list-group col' : 'list-group row';
-    usedList.setAttribute('class', newClass);
-    availableList.setAttribute('class', newClass);
-}
-
-/**
- * Adds an event listener to a button element with the specified ID to trigger the flipping
- * of orientation between 'list-group row' and 'list-group col' classes for specified UI elements.
- * This event will also change the orientation of the bin element (if present), which is expected
- * to have ID 'bin'.
- *
- * @param {string} buttonId - The ID of the button element to which the event listener is added.
- * @param {string} usedId - The ID of the used list element.
- * @param {string} availableId - The ID of the available list element.
- * @returns {void}
- *
- * @example
- * // HTML structure:
- * // <button id="toggleButton">Toggle Orientation</button>
- * // <div id="usedList" class="list-group row">...</div>
- * // <div id="availableList" class="list-group row">...</div>
- *
- * // JavaScript usage:
- * add_orientation_listener('toggleButton', 'usedList', 'availableList');
- */
-export function add_orientation_listener(buttonId, usedId, availableId) {
-    const button = document.getElementById(buttonId);
-    button.addEventListener('click', () => _flip_orientation(usedId, availableId));
-}
-
 /**
  * Get the current height of the iframe's content document.
  *
@@ -279,22 +236,18 @@ export function get_iframe_height() {
     return document.documentElement.offsetHeight;
 }
 
-function _is_empty_li(li) {
-    return li.textContent.trim() === '' && li.children.length === 0;
-}
-
 /**
- * Class for for managing Sortable lists for Parson's proof questions in STACK.
+ * Class for for managing Sortable lists for Parson's block questions in STACK.
  *
  * @class
- * @param {Object} proofSteps - Object containing proof steps.
+ * @param {Object} steps - Object containing flat steps JSON.
  * @param {string} availableId - ID of the available list element.
  * @param {string} usedId - ID of the used list element.
  * @param {string|null} inputId - ID of the input element for storing state (optional).
  * @param {Object|null} options - Custom options for sortable lists (optional).
  * @param {boolean} clone - Flag indicating whether to clone elements during drag-and-drop.
  *
- * @property {Object} proofSteps - Object containing proof steps.
+ * @property {Object} steps - Object containing all steps.
  * @property {string} inputId - ID of the input element for storing state (optional).
  * @property {Object} state - Current state of used and available items.
  * @property {Object} userOptions - User-defined options merged with default options.
@@ -334,7 +287,7 @@ export const stack_sortable = class {
      * Constructor for the StackSortable class.
      *
      * @constructor
-     * @param {Object} proofSteps - Object containing proof steps.
+     * @param {Object} steps - Object containing the flat steps JSON.
      * @param {string} availableId - ID of the available list element.
      * @param {string} usedId - ID of the used list element.
      * @param {string|null} inputId - ID of the input element for storing state (optional).
@@ -342,11 +295,18 @@ export const stack_sortable = class {
      *                                of form {used: UsedOptions, available: AvailableOptions} (optional).
      * @param {boolean} clone - Flag indicating whether to clone elements during sorting.
      */
-
-    // TODO : add containerId as param
-    // TODO : be careful with default parameters, these should all be strings
-    constructor(proofSteps, inputId = null, options = null, clone = false, columns = 1, rows = null, orientation = "col", index = "", grid = false, item_height = null, item_width = null) {
-        this.proofSteps = proofSteps;
+    constructor(steps, 
+            inputId = null, 
+            options = null, 
+            clone = false, 
+            columns = 1, 
+            rows = null, 
+            orientation = "col", 
+            index = "", 
+            grid = false, 
+            item_height = null, 
+            item_width = null) {
+        this.steps = steps;
         this.inputId = inputId;
         this.orientation = orientation;
         this.columns = (this.orientation === "col") ? columns : rows;
@@ -363,22 +323,17 @@ export const stack_sortable = class {
         this.item_height_width = (this.item_height_width['style'] === '') ? {} : this.item_height_width;
         this.item_height = (item_height !== '') ? {'style' : `height:${item_height}px;`} : {};
         this.item_width = (item_width !== '') ? {'style' : `width:${item_width}px;`} : {};
-
         this.container_height_width = (this.item_height_width['style'] !== '') ? {'style' : this.item_height_width['style'] + 'margin: 12px;'} : {};
-        this.state = this._generate_state(this.proofSteps, inputId, Number(this.columns), Number(this.rows));
+        this.state = this._generate_state(this.steps, inputId, Number(this.columns), Number(this.rows));
         if (inputId !== null) {
             this.input = document.getElementById(this.inputId);
             this.submitted = this.input.getAttribute("readonly") === "readonly"
         }
         this.ids = this._create_ids(this.rows, this.columns);
         this.availableId = this.ids.available;
-        //this.available = document.getElementById(this.availableId);
         this.usedId = this.ids.used;
-        //this.used = this.usedId.map(idList => idList.map(id => document.getElementById(id)));
-        //this.used = document.getElementById(this.usedId);
         this.clone = clone;
         
-        // TODO : additional default options?
         this.defaultOptions = {used: {animation: 50, cancel: ".header"}, available: {animation: 50, cancel: ".header"}};
         // Merges user options and default, overwriting default with user options if they clash
         this.userOptions = this._set_user_options(options);
@@ -389,22 +344,118 @@ export const stack_sortable = class {
         this.options = this._set_ghostClass_group_and_disabled_options();
     }
 
-    _create_ids(rows, columns) {
-        var colIdx = Array.from({length: columns}, (_, i) => i);
-        var rowIdx = Array.from({length: rows}, (_, j) => j);
-        this.colIds = colIdx.map((idx) => `usedList_${idx}`);
-        this.rowColIds = {}
-        colIdx.forEach((i) => this.rowColIds[this.colIds[i]] = rowIdx.map((j) => `usedList_${j}${i}`));
-        var usedIds = (rows === "") ? 
-            this.colIds.map((id) => [id]) : 
-            Object.values(this.rowColIds);
+    /**
+     * Adds double-click listeners to move items upon double-click and updates the state accordingly.
+     * Only supported for proofmode
+     * TODO : fix this
+     *
+     * @method
+     * @param {Object} newUsed - Updated used list.
+     * @param {Object} newAvailable - Updated available list.
+     * @returns {void}
+     */
+    add_dblclick_listeners(newUsed, newAvailable) {
+        this.available.addEventListener('dblclick', (e) => {
+            if (this._double_clickable(e.target)) {
+                // get highest-level parent
+                var li = this._get_moveable_parent_li(e.target);
+                li = (this.clone === "true") ? li.cloneNode(true) : this.available.removeChild(li);
+                this.used[0].append(li);
+                this.update_state(newUsed, newAvailable);
+            }
+        });
+        this.used[0].addEventListener('dblclick', (e) => {
+            if (this._double_clickable(e.target)) {
+                // get highest-level parent
+                var li = this._get_moveable_parent_li(e.target);
+                this.used[0].removeChild(li);
+                if (this.clone !== "true") {
+                    this.available.insertBefore(li, this.available.children[1]);
+                }
+                this.update_state(newUsed, newAvailable);
+            }
+        });
+    }
 
-        return {
-            used: usedIds,
-            available: "availableList"
-        };
+    /**
+     * Add a click event listener to a button to delete all items from the "used" list and
+     * updates the state accordingly.
+     *
+     * @method
+     * @param {string} buttonId - ID of the button element to attach the listener.
+     * @param {Object} newUsed - Updated "used" list.
+     * @param {Object} newAvailable - Updated "available" list.
+     * @returns {void}
+     */
+    add_delete_all_listener(buttonId, newUsed, newAvailable) {
+        const button = document.getElementById(buttonId);
+        button.addEventListener('click', () => {
+            this._delete_all_from_used(); this.update_state(newUsed, newAvailable);});
+    }
+
+    /**
+     * Adds header elements to the used and available lists.
+     *
+     * @method
+     * @param {Object} headers - Object containing header text for used and available lists.
+     * @returns {void}
+     */
+    add_headers(headers, available_header) {
+        for (const [i, value] of headers.entries()) {
+            var parentEl = document.getElementById(`usedList_${i}`);
+            var header = this._create_header(value, `usedHeader_${i}`, this.item_height_width);
+            parentEl.insertBefore(header, parentEl.firstChild);
+        }
+        var parentEl = document.getElementById("availableList");
+        parentEl.insertBefore(this._create_header(available_header, "availableHeader", this.item_height_width), parentEl.firstChild);
     }
 
+    /**
+     * Adds index elements to the DOM based on the provided index array.
+     * 
+     * @param {Array} index - The array containing index values to be added.
+     */
+    add_index(index) {
+        for (const [i, value] of index.entries()) {
+            // Deal with the item in both header and index separately
+            if (i === 0) {
+                var idx = this._create_index(value, `usedIndex_${i}`, this.item_height_width);
+                var addClass = this.orientation === "col" ? "header" : "index";
+                idx.classList.add(addClass);
+            } else {
+                var idx = this._create_index(value, `usedIndex_${i}`, this.item_height_width);
+            }
+            document.getElementById("index").append(idx);
+        }
+    }
+
+    /**
+     * Adds a reorientation button to the document body.
+     * 
+     * The button allows users to change the orientation of sortable lists between vertical 
+     * and horizontal.
+     */
+    add_reorientation_button() {
+        var btn = document.createElement("button");
+        btn.id = "orientation";
+        btn.setAttribute("class", "parsons-button");
+        var icon = document.createElement("i");
+        icon.setAttribute("class", "fa fa-refresh");
+        btn.append(icon);
+        btn.addEventListener("click", () => this._flip_orientation());
+        document.body.insertBefore(btn, document.getElementById("containerRow"));
+    }
+
+    /**
+     * Populates the DOM with row and column div elements to the document based 
+     * on how many columns and rows are being passed to the instance.
+     * 
+     * How this occurs depends on various configurations.
+     * - Lists should contain the `"row"` or `"col"` class according to the orientation.
+     * - If the class is being used for proof (i.e., `this.grid === false`), then the list class should
+     *   also contain `"list-group"`.
+     * - Items class depends only on the orientation.
+     */
     create_row_col_divs() {
         var usedClassList = (!this.grid || this.orientation === "col") ? 
             ["list-group", this.orientation, "usedList"]:
@@ -456,121 +507,51 @@ export const stack_sortable = class {
         this.available = document.getElementById(this.availableId);
     }
 
-    _flip_orientation() {
-        var addClass = (this.orientation === "row") ? ["list-group", "col"] : ["row"];
-        if (this.grid) {
-            var removeClass = (this.orientation === "row") ? ["list-group", "row"] : ["list-group", "col"];
-            var currGridClass = (this.orientation === "row") ? "grid-item-rigid" : "grid-item";
-            var gridAddClass = (this.orientation === "row") ? "grid-item" : "grid-item-rigid"
-            var gridItems = document.querySelectorAll(`.${currGridClass}`);
-            gridItems.forEach((item) => {
-                item.classList.remove(currGridClass);
-                item.classList.add(gridAddClass);
-            })
-
-            if (this.rows !== "") {
-                [].concat(...this.used).forEach((div) => {
-                    if (this.orientation === "col") {
-                        div.classList.remove("row");
-                        div.classList.add("col", "col-rigid");
-                    } else {
-                        div.classList.remove("col", "col-rigid");
-                        div.classList.add("row");
-                    }
-                })
-            }
-        } else {
-            var removeClass = (this.orientation === "row") ? ["row"] : ["col"];
-        }
-        this.colIds.forEach((colId) => {
-                var ul = document.getElementById(colId);
-                ul.classList.remove(...removeClass);
-                ul.classList.add(...addClass);
-            }
-        );
-
-        this.available.classList.remove(...removeClass);
-        this.available.classList.add(...addClass);
-        if (this.orientation === "col") {
-            this.available.parentNode.insertBefore(this.available, this.available.parentNode.firstChild);
-        } else {
-            this.available.parentNode.append(this.available);
-        }
+    /**
+     * Generates the available list based on the current state.
+     *
+     * @method
+     * @returns {void}
+     */
+    generate_available() {
+        this.state.available.forEach(key => this.available.append(this._create_li(key, this.item_height_width)));
+    }
 
-        if (this.grid) {
-            if (this.orientation === "col") {
-                document.querySelectorAll(".header").forEach((header) => {
-                    if (!header.classList.contains("index")) {
-                        header.classList.remove("header");
-                        header.classList.add("index");
-                    }
-                });
+    /**
+     * Generates the used list based on the current state.
+     *
+     * @method
+     * @returns {void}
+     */
+    generate_used() {
+        for (const [i, value] of this.state.used.entries()) {
+            if (this.rows !== "" && this.columns !== "") {
+                for (const [j, val] of value.entries()) {
+                    this._apply_attrs(this.used[i][j], this.container_height_width);
+                    val.forEach(key => this.used[i][j].append(this._create_li(key, this.item_height_width)));
+                }
             } else {
-                document.querySelectorAll(".index").forEach((index) => {
-                    if (!index.classList.contains("header")) {
-                        index.classList.remove("index");
-                        index.classList.add("header");
-                    }
-                })
+                value[0].forEach(key => this.used[i][0].append(this._create_li(key, this.item_height_width)));
             }
-        };
-
-        if (this.use_index) {
-            var indexDiv = document.getElementById("index");
-            indexDiv.classList.remove(...removeClass);
-            indexDiv.classList.add(...addClass);
-            if (this.orientation === "col") {
-                document.querySelectorAll("#index > .index").forEach((idx) => {
-                    if (!idx.classList.contains("header")) {
-                        idx.classList.remove("index");
-                        idx.classList.add("header");
-                    }
-                })
-            } else {
-                document.querySelectorAll('#index > .header').forEach((header) => {
-                    if (!header.classList.contains("index")) {
-                        header.classList.remove("header");
-                        header.classList.add("index");
-                }
-            })
         }
     }
 
-        this.orientation = (this.orientation === "row") ? "col" : "row";
-        
-        /*var usedList = document.getElementById(usedId);
-        var availableList = document.getElementById(availableId);
-        var newClass = usedList.className == 'list-group row' ? 'list-group col' : 'list-group row';
-        usedList.setAttribute('class', newClass);
-        availableList.setAttribute('class', newClass);*/
-    }
-
-    add_reorientation_button() {
-        var btn = document.createElement("button");
-        btn.id = "orientation";
-        btn.setAttribute("class", "parsons-button");
-        var icon = document.createElement("i");
-        icon.setAttribute("class", "fa fa-refresh");
-        btn.append(icon);
-        btn.addEventListener("click", () => this._flip_orientation());
-        document.body.insertBefore(btn, document.getElementById("containerRow"));
-    }
-    /*if (orientation === "row") {
-        var availableEl = document.getElementById("availableList");
-        availableEl.classList.remove("list-group", "col");
-        availableEl.classList.add(orientation);
-        availableEl.parentNode.insertBefore(availableEl, availableList.parentNode.firstChild);
-        //document.getElementById("availableList").classList.add(orientation);
-        ids.used.forEach((idList) => {
-            document.getElementById(idList[0]).classList.remove("list-group", "col");
-            document.getElementById(idList[0]).classList.add(orientation);
-        })
-        if (use_index) {
-            var indexEl = document.getElementById("index");
-            indexEl.classList.remove("list-group", "col");
-            indexEl.classList.add("row");
+    /**
+     * Updates the state based on changes in the used and available lists.
+     *
+     * @method
+     * @param {Object} newUsed - Updated used list.
+     * @param {Object} newAvailable - Updated available list.
+     * @returns {void}
+     */
+    update_state(newUsed, newAvailable) {
+        var newState = {used: newUsed.map((usedList) => usedList.map((used) => used.toArray())), available: newAvailable.toArray()};
+        if (this.inputId !== null) {
+            this.input.value = JSON.stringify(newState);
+            this.input.dispatchEvent(new Event('change'));
         }
-    };*/
+        this.state = newState;
+    }
 
     /**
      * Validate user options against a list of possible option keys.
@@ -632,195 +613,299 @@ export const stack_sortable = class {
     }
 
     /**
-     * Generates the available list based on the current state.
-     *
-     * @method
-     * @returns {void}
+     * Applies attributes to an HTML element.
+     * 
+     * @param {HTMLElement} el - The HTML element to which attributes will be applied.
+     * @param {Object} opts - An object containing attribute-value pairs to be applied.
      */
-    generate_available() {
-        this.state.available.forEach(key => this.available.append(this._create_li(key, this.item_height_width)));
+    _apply_attrs(el, opts) {
+        for (const [key, value] of Object.entries(opts)) {
+            el.setAttribute(key, value);
+        }
     }
 
-    _add_index(index, indexDOM) {
-        for (const [i, value] of index.entries()) {
-            indexDOM.append(this._create_index(value, `usedIndex${i}`, this.item_height_width));
-        }
+    /**
+     * Creates a header element with specified inner HTML, ID, and other attributes.
+     * 
+     * @param {string} innerHTML - The inner HTML content of the header element.
+     * @param {string} id - The ID attribute of the header element.
+     * @param {Object} attrs - An object containing additional attributes for the header element.
+     * @returns {HTMLElement} - The created header element.
+     */
+    _create_header(innerHTML, id, attrs) {
+        let i = document.createElement("i");
+        i.innerHTML = innerHTML;
+        var addClass = (this.orientation === "col") ? 
+            [this.item_class, 'header'] : [this.item_class, 'index'];
+        i.classList.add(...addClass);
+        this._apply_attrs(i, {...{"id" : id}, ...attrs});
+        return i;
     }
 
     /**
-     * Generates the used list based on the current state.
-     *
-     * @method
-     * @returns {void}
+     * Creates and organizes identifiers for rows and columns. If only columns are passed, then 
+     * the used IDs will just be a flat list `["usedList_0", ..., "usedList_n"]`, where `columns = "n + 1"`.
+     * If both rows and columns have non-null values, then this will be a two-dimensional array
+     * `[["usedList_00", "usedList_01", ..., "usedList_0n"], ["usedList_10", ...], ...]`. 
+     * In the two-dimensional case, a mapping between the column IDs `["usedList_0", ...]` and the 
+     * two-dimensional array of item IDs is contained in the object `this.rowColIds`, that is 
+     * `this.rowColIds["usedList_0"] = ["usedList_00", "usedList_01", ...]`.
+     * 
+     * @param {number} rows - The number of rows.
+     * @param {number} columns - The number of columns.
+     * @returns {Object} - An object containing identifiers for used and available elements.
      */
-    generate_used() {
-        for (const [i, value] of this.state.used.entries()) {
-            /*if (i === 0 && this.index !== null) {
-                this._add_index(this.index, value[0]);        
-            }*/
-            if (this.rows !== "" && this.columns !== "") {
-                for (const [j, val] of value.entries()) {
-                    this._apply_attrs(this.used[i][j], this.container_height_width);
-                    val.forEach(key => this.used[i][j].append(this._create_li(key, this.item_height_width)));
-                }
-            } else {
-                value[0].forEach(key => this.used[i][0].append(this._create_li(key, this.item_height_width)));
-            }
-        }
+    _create_ids(rows, columns) {
+        var colIdx = Array.from({length: columns}, (_, i) => i);
+        var rowIdx = Array.from({length: rows}, (_, j) => j);
+        this.colIds = colIdx.map((idx) => `usedList_${idx}`);
+        this.rowColIds = {}
+        colIdx.forEach((i) => this.rowColIds[this.colIds[i]] = rowIdx.map((j) => `usedList_${j}${i}`));
+        var usedIds = (rows === "") ? 
+            this.colIds.map((id) => [id]) : 
+            Object.values(this.rowColIds);
+
+        return {
+            used: usedIds,
+            available: "availableList"
+        };
     }
 
-    add_index(index) {
-        for (const [i, value] of index.entries()) {
-            if (i === 0) {
-                var idx = this._create_index(value, `usedIndex_${i}`, this.item_height_width);
-                var addClass = this.orientation === "col" ? "header" : "index";
-                idx.classList.add(addClass);
-            } else {
-                var idx = this._create_index(value, `usedIndex_${i}`, this.item_height_width);
-            }
-            document.getElementById("index").append(idx);
-        }
+    /**
+     * Creates an index element with specified inner HTML, ID, and additional attributes.
+     * 
+     * @param {string} innerHTML - The inner HTML content of the index element.
+     * @param {string} id - The ID attribute of the index element.
+     * @param {Object} attrs - An object containing additional attributes for the index element.
+     * @returns {HTMLElement} - The created index element.
+     */
+    _create_index(innerHTML, id, attrs) {
+        let i = document.createElement("i");
+        i.innerHTML = innerHTML;
+        var addClass = (this.orientation === "col") ? 
+            [this.item_class, 'index'] : [this.item_class, 'header'];
+        i.classList.add(...addClass);
+        this._apply_attrs(i, {...{"id" : id}, ...attrs});
+        return i;
     }
 
     /**
-     * Adds header elements to the used and available lists.
-     *
-     * @method
-     * @param {Object} headers - Object containing header text for used and available lists.
-     * @returns {void}
+     * Creates a list item (li) element containing the value of the specified key from `this.steps` and attributes.
+     * 
+     * @param {string} stepKey - The key whose HTML to get from `this.steps`.
+     * @param {Object} attrs - An object containing additional attributes for the list item element.
+     * @returns {HTMLElement} - The created list item (li) element.
      */
-    add_headers(headers, available_header) {
-        for (const [i, value] of headers.entries()) {
-            var parentEl = document.getElementById(`usedList_${i}`);
-            var header = this._create_header(value, `usedHeader_${i}`, this.item_height_width);
-            parentEl.insertBefore(header, parentEl.firstChild);
-        }
-        var parentEl = document.getElementById("availableList");
-        parentEl.insertBefore(this._create_header(available_header, "availableHeader", this.item_height_width), parentEl.firstChild);
+    _create_li(stepKey, attrs) {
+        let li = document.createElement("li");
+        li.innerHTML = this.steps[stepKey];
+        this._apply_attrs(li, {...{"data-id" : stepKey}, ...attrs});
+        li.className = this.item_class;
+        return li;
     }
 
+    /**
+     * Checks if a list item (li) is deletable.
+     * 
+     * @param {HTMLElement} li - The list item (li) element to check.
+     * @returns {boolean} - True if the list item is deletable, otherwise false.
+     */
+    _deletable_li(li) {
+        return !li.matches(".header") && !li.matches(".index") && !this._is_empty_li(li);
+    }
 
     /**
-     * Updates the state based on changes in the used and available lists.
+     * Delete all non-header items from the "used" list.
      *
      * @method
-     * @param {Object} newUsed - Updated used list.
-     * @param {Object} newAvailable - Updated available list.
+     * @private
      * @returns {void}
      */
-    update_state(newUsed, newAvailable) {
-        var newState = {used: newUsed.map((usedList) => usedList.map((used) => used.toArray())), available: newAvailable.toArray()};
-        if (this.inputId !== null) {
-            this.input.value = JSON.stringify(newState);
-            this.input.dispatchEvent(new Event('change'));
-        }
-        this.state = newState;
+    _delete_all_from_used() {
+        const lis = document.querySelectorAll('.usedList li[data-id]');
+        lis.forEach(li => {if (this._deletable_li(li)) {this._delete_li(li);}});
     }
 
     /**
-     * Adds double-click listeners to move items upon double-click and updates the state accordingly.
+     * Deletes a list item (li) from its parent node.
+     * 
+     * @param {HTMLElement} li - The list item (li) element to delete.
+     */
+    _delete_li(li) {
+        li.parentNode.removeChild(li);
+    }
+
+    /**
+     * Display a warning message on the question page.
      *
      * @method
-     * @param {Object} newUsed - Updated used list.
-     * @param {Object} newAvailable - Updated available list.
+     * @private
+     * @param {string} msg - The message to be displayed in the warning.
      * @returns {void}
      */
-    add_dblclick_listeners(newUsed, newAvailable) {
-        this.available.addEventListener('dblclick', (e) => {
-            if (this._double_clickable(e.target)) {
-                // get highest-level parent
-                var li = this._get_moveable_parent_li(e.target);
-                li = (this.clone === "true") ? li.cloneNode(true) : this.available.removeChild(li);
-                this.used[0].append(li);
-                this.update_state(newUsed, newAvailable);
-            }
-        });
-        this.used[0].addEventListener('dblclick', (e) => {
-            if (this._double_clickable(e.target)) {
-                // get highest-level parent
-                var li = this._get_moveable_parent_li(e.target);
-                this.used[0].removeChild(li);
-                if (this.clone !== "true") {
-                    this.available.insertBefore(li, this.available.children[1]);
-                }
-                this.update_state(newUsed, newAvailable);
-            }
-        });
+    _display_warning(msg) {
+        var warning = document.createElement("div");
+        warning.className = "sortable-warning";
+        var exclamation = document.createElement("i");
+        exclamation.className = "icon fa fa-exclamation-circle text-danger fa-fw";
+        warning.append(exclamation);
+        var warningMessage = document.createElement("span");
+        warningMessage.textContent = msg;
+        warning.append(warningMessage);
+        document.body.insertBefore(warning, document.getElementById("sortableContainer"));
     }
 
     /**
-     * Add a click event listener to a button to delete all items from the "used" list and
-     * updates the state accordingly.
+     * TODO: fix this, it should not be an index or grid-item or grid-item-rigid
+     * Check if an HTML element is double-clickable (i.e., it is not a header element).
+     *
+     * This private method is called on items inside the used or available list.
      *
      * @method
-     * @param {string} buttonId - ID of the button element to attach the listener.
-     * @param {Object} newUsed - Updated "used" list.
-     * @param {Object} newAvailable - Updated "available" list.
-     * @returns {void}
+     * @private
+     * @param {HTMLElement} item - The HTML element to check for double-clickability.
+     * @returns {boolean} - Returns true if the element is double-clickable, false otherwise.
      */
-    add_delete_all_listener(buttonId, newUsed, newAvailable) {
-        const button = document.getElementById(buttonId);
-        button.addEventListener('click', () => {
-            this._delete_all_from_used(); this.update_state(newUsed, newAvailable);});
+    _double_clickable(item) {
+        return !item.matches(".header");
+    }
+
+    /* TODO : simplify this */
+    _flip_orientation() {
+        var addClass = (this.orientation === "row") ? ["list-group", "col"] : ["row"];
+        if (this.grid) {
+            var removeClass = (this.orientation === "row") ? ["list-group", "row"] : ["list-group", "col"];
+            var currGridClass = (this.orientation === "row") ? "grid-item-rigid" : "grid-item";
+            var gridAddClass = (this.orientation === "row") ? "grid-item" : "grid-item-rigid"
+            var gridItems = document.querySelectorAll(`.${currGridClass}`);
+            gridItems.forEach((item) => {
+                item.classList.remove(currGridClass);
+                item.classList.add(gridAddClass);
+            })
+
+            if (this.rows !== "") {
+                [].concat(...this.used).forEach((div) => {
+                    if (this.orientation === "col") {
+                        div.classList.remove("row");
+                        div.classList.add("col", "col-rigid");
+                    } else {
+                        div.classList.remove("col", "col-rigid");
+                        div.classList.add("row");
+                    }
+                })
+            }
+        } else {
+            var removeClass = (this.orientation === "row") ? ["row"] : ["col"];
+        }
+        this.colIds.forEach((colId) => {
+                var ul = document.getElementById(colId);
+                ul.classList.remove(...removeClass);
+                ul.classList.add(...addClass);
+            }
+        );
+
+        this.available.classList.remove(...removeClass);
+        this.available.classList.add(...addClass);
+        if (this.orientation === "col") {
+            this.available.parentNode.insertBefore(this.available, this.available.parentNode.firstChild);
+        } else {
+            this.available.parentNode.append(this.available);
+        }
+
+        if (this.grid) {
+            if (this.orientation === "col") {
+                document.querySelectorAll(".header").forEach((header) => {
+                    if (!header.classList.contains("index")) {
+                        header.classList.remove("header");
+                        header.classList.add("index");
+                    }
+                });
+            } else {
+                document.querySelectorAll(".index").forEach((index) => {
+                    if (!index.classList.contains("header")) {
+                        index.classList.remove("index");
+                        index.classList.add("header");
+                    }
+                })
+            }
+        };
+
+        if (this.use_index) {
+            var indexDiv = document.getElementById("index");
+            indexDiv.classList.remove(...removeClass);
+            indexDiv.classList.add(...addClass);
+            if (this.orientation === "col") {
+                document.querySelectorAll("#index > .index").forEach((idx) => {
+                    if (!idx.classList.contains("header")) {
+                        idx.classList.remove("index");
+                        idx.classList.add("header");
+                    }
+                })
+            } else {
+                document.querySelectorAll('#index > .header').forEach((header) => {
+                    if (!header.classList.contains("index")) {
+                        header.classList.remove("header");
+                        header.classList.add("index");
+                }
+            })
+        }
+    }
+        this.orientation = (this.orientation === "row") ? "col" : "row";
     }
 
     /**
-     * Generates the initial state of used and available items based on the provided proof steps and input ID.
+     * Generates the initial state of used and available items based on the provided steps, input ID,
+     * and number of columns and rows used. The shape of the used state will be `(1, 1, ?)` if in proof 
+     * mode, `(n, 1, ?)` if `n` columns are specified and `(n, m, 1)` if `n` columns and `m` rows are specified.
      *
      * @method
      * @private
-     * @param {Object} proofSteps - Object containing proof steps.
+     * @param {Object} steps - Object containing steps.
      * @param {string} inputId - ID of the input element for storing state.
      * @returns {Object} The initial state object with used and available lists.
      */
-    _generate_state(proofSteps, inputId, columns, rows) {
+    _generate_state(steps, inputId, columns, rows) {
         const usedState = (rows === 0 || columns === 0) ? 
                 Array(columns).fill().map(() => [[]]) : 
                 Array(columns).fill().map(() => Array(rows).fill([]));
         let stateStore = document.getElementById(inputId);
         if (stateStore === null) {
-            return {used: usedState, available: [...Object.keys(proofSteps)]};
+            return {used: usedState, available: [...Object.keys(steps)]};
         }
         return (stateStore.value && stateStore.value != "") ?
             JSON.parse(stateStore.value) :
-            {used: usedState, available: [...Object.keys(proofSteps)]};
+            {used: usedState, available: [...Object.keys(steps)]};
     }
 
     /**
-     * Validate if a given option key is among the possible option keys.
+     * Get the nearest moveable parent list item for a given HTML element.
+     *
+     * This private method traverses the DOM hierarchy starting from the provided HTML
+     * element and finds the nearest parent list item with the class ".list-group-item".
+     * It is useful for identifying the moveable parent when doubling clicking on child
+     * elements (for example MathJax display elements) inside list items.
      *
      * @method
      * @private
-     * @param {string} key - The option key to validate.
-     * @param {string[]} possibleOptionKeys - List of possible option keys.
-     * @returns {boolean} - Returns true if the option key is valid, false otherwise.
+     * @param {HTMLElement} target - The HTML element for which to find the moveable parent list item.
+     * @returns {HTMLElement|null} - The nearest parent list item with class ".list-group-item", or null if not found.
      */
-    _validate_option_key(key, possibleOptionKeys) {
-        return possibleOptionKeys.includes(key);
+    _get_moveable_parent_li(target) {
+        var li = target;
+        while (!li.matches(".list-group-item")) {
+            li = li.parentNode;
+        }
+        return li;
     }
 
     /**
-     * Set and merge user-provided options with default options.
-     *
-     * This private method sets user options for both "used" and "available" lists
-     * by merging the provided options with the default options. If no options are
-     * provided, it returns the default options.
-     *
-     * @method
-     * @private
-     * @param {Object|null} options - Custom options for sortable lists
-     *                                of form {used: UsedOptions, available: AvailableOptions} (optional).
-     * @returns {Object} - Merged user options for "used" and "available" lists.
+     * Checks if a list item (li) element is empty.
+     * 
+     * @param {HTMLElement} li - The list item (li) element to check.
+     * @returns {boolean} - True if the list item is empty, otherwise false.
      */
-    _set_user_options(options) {
-        var userOptions;
-        if (options === null) {
-            userOptions = this.defaultOptions;
-        } else {
-            userOptions = {used: Object.assign(this.defaultOptions.used, options.used),
-                available: Object.assign(this.defaultOptions.available, options.available)};
-        }
-        return userOptions;
+    _is_empty_li(li) {
+        return li.textContent.trim() === '' && li.children.length === 0;
     }
 
     /**
@@ -900,130 +985,41 @@ export const stack_sortable = class {
     }
 
     /**
-     * Display a warning message on the question page.
-     *
-     * @method
-     * @private
-     * @param {string} msg - The message to be displayed in the warning.
-     * @returns {void}
-     */
-    _display_warning(msg) {
-        var warning = document.createElement("div");
-        warning.className = "sortable-warning";
-        var exclamation = document.createElement("i");
-        exclamation.className = "icon fa fa-exclamation-circle text-danger fa-fw";
-        warning.append(exclamation);
-        var warningMessage = document.createElement("span");
-        warningMessage.textContent = msg;
-        warning.append(warningMessage);
-        document.body.insertBefore(warning, document.getElementById("sortableContainer"));
-    }
-
-    /**
-     * Create an HTML list item element based on keys in `this.proofSteps`.
-     *
-     * @method
-     * @private
-     * @param {string} proofKey - The key associated with the proof content in 'proofSteps'.
-     * @returns {HTMLElement} - The created list item element.
-     */
-    _create_li(proofKey, opts) {
-        let li = document.createElement("li");
-        li.innerHTML = this.proofSteps[proofKey];
-        this._apply_attrs(li, {...{"data-id" : proofKey}, ...opts});
-        li.className = this.item_class;
-        return li;
-    }
-
-    _apply_attrs(el, opts) {
-        for (const [key, value] of Object.entries(opts)) {
-            el.setAttribute(key, value);
-        }
-    }
-
-    /**
-     * Creates a header element.
-     *
-     * @method
-     * @private
-     * @param {string} innerHTML - Inner HTML content of the header.
-     * @param {string} id - ID of the header element.
-     * @returns {HTMLElement} The created header element.
-     */
-    _create_header(innerHTML, id, opts) {
-        let i = document.createElement("i");
-        i.innerHTML = innerHTML;
-        var addClass = (this.orientation === "col") ? 
-            [this.item_class, 'header'] : [this.item_class, 'index'];
-        i.classList.add(...addClass);
-        this._apply_attrs(i, {...{"id" : id}, ...opts});
-        return i;
-    }
-
-    _create_index(innerHTML, id, opts) {
-        let i = document.createElement("i");
-        i.innerHTML = innerHTML;
-        var addClass = (this.orientation === "col") ? 
-            [this.item_class, 'index'] : [this.item_class, 'header'];
-        i.classList.add(...addClass);
-        this._apply_attrs(i, {...{"id" : id}, ...opts});
-        return i;
-    }
-
-    /**
-     * Check if an HTML element is double-clickable (i.e., it is not a header element).
-     *
-     * This private method is called on items inside the used or available list.
-     *
-     * @method
-     * @private
-     * @param {HTMLElement} item - The HTML element to check for double-clickability.
-     * @returns {boolean} - Returns true if the element is double-clickable, false otherwise.
-     */
-    _double_clickable(item) {
-        return !item.matches(".header");
-    }
-
-    /**
-     * Get the nearest moveable parent list item for a given HTML element.
+     * Set and merge user-provided options with default options.
      *
-     * This private method traverses the DOM hierarchy starting from the provided HTML
-     * element and finds the nearest parent list item with the class ".list-group-item".
-     * It is useful for identifying the moveable parent when doubling clicking on child
-     * elements (for example MathJax display elements) inside list items.
+     * This private method sets user options for both "used" and "available" lists
+     * by merging the provided options with the default options. If no options are
+     * provided, it returns the default options.
      *
      * @method
      * @private
-     * @param {HTMLElement} target - The HTML element for which to find the moveable parent list item.
-     * @returns {HTMLElement|null} - The nearest parent list item with class ".list-group-item", or null if not found.
+     * @param {Object|null} options - Custom options for sortable lists
+     *                                of form {used: UsedOptions, available: AvailableOptions} (optional).
+     * @returns {Object} - Merged user options for "used" and "available" lists.
      */
-    _get_moveable_parent_li(target) {
-        var li = target;
-        while (!li.matches(".list-group-item")) {
-            li = li.parentNode;
+    _set_user_options(options) {
+        var userOptions;
+        if (options === null) {
+            userOptions = this.defaultOptions;
+        } else {
+            userOptions = {used: Object.assign(this.defaultOptions.used, options.used),
+                available: Object.assign(this.defaultOptions.available, options.available)};
         }
-        return li;
-    }
-
-    _deletable_li(li) {
-        return !li.matches(".header") && !li.matches(".index") && !_is_empty_li(li);
+        return userOptions;
     }
 
-    _delete_li(li) {
-        li.parentNode.removeChild(li);
-    }
     /**
-     * Delete all non-header items from the "used" list.
+     * Validate if a given option key is among the possible option keys.
      *
      * @method
      * @private
-     * @returns {void}
+     * @param {string} key - The option key to validate.
+     * @param {string[]} possibleOptionKeys - List of possible option keys.
+     * @returns {boolean} - Returns true if the option key is valid, false otherwise.
      */
-    _delete_all_from_used() {
-        const lis = document.querySelectorAll('.usedList li[data-id]');
-        lis.forEach(li => {if (this._deletable_li(li)) {this._delete_li(li);}});
+    _validate_option_key(key, possibleOptionKeys) {
+        return possibleOptionKeys.includes(key);
     }
-
 };
 
 export default {stack_sortable};