Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | import Viselect from '@viselect/vanilla';
import './dragselect.css';
import { nonnull } from './utils';
export class DragSelect {
/** @type {HTMLElement|undefined} */
imagesContainer;
/** @type {string[]} */
selection = $state([]);
/** @type {Viselect|undefined} */
#instance;
/**
* id of last selected item that wasn't selected by shift-clicking.
* needed to properly do shift-click selection
*
* See https://stackoverflow.com/a/16530782
*
* @type {string|undefined}
*/
shiftSelectionAnchor;
/**
* @param {string[]} newSelection
*/
setSelection(newSelection) {
if (!this.#instance) return;
// Tell DragSelect that the set of selected items changed
// Since we store the selection as an array of ids, we
// find the corresponding elements in the imagesContainer by [data-id]
const elements = newSelection
.map((id) => this.imagesContainer?.querySelector(`[data-selectable][data-id="${id}"]`))
.filter((el) => el !== null && el !== undefined);
this.#instance.clearSelection(true, true);
const result = /** @type {HTMLElement[]} */ (this.#instance.select(elements, true));
this.selection = result.map((e) => e.dataset.id).filter((id) => id !== undefined);
return result;
}
refreshSelectables() {
if (!this.#instance) return;
this.#instance.resolveSelectables();
}
destroy() {
this.#instance?.destroy();
}
/**
*
* @param {HTMLElement} container the container where all the [data-selectable] elements are. Selection square will be available in the parent of this container.
* @param {string[]} [initialSelection]
* @param {object} [options]
* @param {(e: MouseEvent|TouchEvent|null) => void} [options.ondeadclick] callback when the user clicks on an empty area, and does not do so in order to unselect everything (i.e. the click did not result in a selection change)
*/
constructor(container, initialSelection = [], { ondeadclick } = {}) {
// Save the container element on the class instance
this.selection = initialSelection;
this.imagesContainer = container;
// If the container doesn't exist, we can't do anything
if (!this.imagesContainer) return;
const boundary = this.imagesContainer.parentElement ?? this.imagesContainer;
// Create a new Viselect instance: this comes from the dragselect package
// It manages the selection of items in a container by dragging or clicking
this.#instance = new Viselect({
// Tell it what elements we want to be able to select: they have a [data-selectable] attribute
selectables: '[data-selectable]',
// Tell it where it should take over mouse dragging and stuff: the parent of the images container
// We use the parent to allow users of the AreaObservations component to decide how much padding they want around the images
// Using the parent, we can let the user start their selection in the padding area, and still select the images
boundaries: boundary,
startAreas: boundary,
// file://./dragselect.css
selectionAreaClass: 'viselect-selection-area',
behaviour: {
overlap: 'keep'
},
features: {
deselectOnBlur: true
}
});
// Clear selection if no modifier key is pressed
this.#instance.on('start', ({ event }) => {
if (!event) return;
if (!event.ctrlKey && !event.metaKey && !event.shiftKey) {
this.selection = [];
}
});
// React to a move event: we just changed the selection
this.#instance.on('move', ({ store: { changed } }) => {
const removed = idsOfElements(changed.removed);
const added = idsOfElements(changed.added);
this.selection = [...this.selection.filter((id) => !removed.includes(id)), ...added];
});
this.#instance.on('stop', ({ event, store: { changed } }) => {
if (changed.added.length > 0) return;
if (changed.removed.length > 0) return;
ondeadclick?.(event);
});
}
}
/** @param {Element[]} elements */
function idsOfElements(elements) {
return elements
.filter((el) => el instanceof HTMLElement)
.map((el) => el.dataset.id)
.filter(nonnull);
}
|