rudimentary support for multiple selections
This commit is contained in:
parent
d934d4ad90
commit
52c014e53e
2 changed files with 168 additions and 67 deletions
|
|
@ -69,11 +69,11 @@ $table-border-color: #ccc;
|
|||
width: 100%;
|
||||
|
||||
&--selected {
|
||||
outline: 1px solid #37f;
|
||||
outline-offset: -1px;
|
||||
background: #07f3;
|
||||
}
|
||||
|
||||
&--cursor {
|
||||
background: transparent;
|
||||
outline: 3px solid #37f;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,6 +101,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
function selections_eq(
|
||||
a: Omit<Selection, "original_value">,
|
||||
b: Omit<Selection, "original_value">,
|
||||
): boolean {
|
||||
return a.region === b.region && coords_eq(a.coords, b.coords);
|
||||
}
|
||||
|
||||
function update_field_ordinality({
|
||||
field_index,
|
||||
beyond_index,
|
||||
|
|
@ -188,87 +195,166 @@
|
|||
}
|
||||
}
|
||||
|
||||
function move_selection(direction: "Down" | "Left" | "Right" | "Up") {
|
||||
function move_cursor(
|
||||
direction: "Down" | "Left" | "Right" | "Up",
|
||||
{ additive }: { additive?: boolean } = {},
|
||||
) {
|
||||
if (!lazy_data || selections.length === 0) {
|
||||
console.warn("move_selection() preconditions not met");
|
||||
return;
|
||||
}
|
||||
|
||||
const last_selection = selections[selections.length - 1];
|
||||
const cursor = selections[0];
|
||||
let new_cursor: Omit<Selection, "original_value"> | undefined;
|
||||
if (
|
||||
direction === "Right" &&
|
||||
last_selection.coords[1] < lazy_data.fields.length - 1
|
||||
cursor.coords[1] < lazy_data.fields.length - 1
|
||||
) {
|
||||
set_selections([
|
||||
{
|
||||
region: last_selection.region,
|
||||
coords: [last_selection.coords[0], last_selection.coords[1] + 1],
|
||||
},
|
||||
]);
|
||||
} else if (direction === "Left" && last_selection.coords[1] > 0) {
|
||||
set_selections([
|
||||
{
|
||||
region: last_selection.region,
|
||||
coords: [last_selection.coords[0], last_selection.coords[1] - 1],
|
||||
},
|
||||
]);
|
||||
new_cursor = {
|
||||
region: cursor.region,
|
||||
coords: [cursor.coords[0], cursor.coords[1] + 1],
|
||||
};
|
||||
} else if (direction === "Left" && cursor.coords[1] > 0) {
|
||||
new_cursor = {
|
||||
region: cursor.region,
|
||||
coords: [cursor.coords[0], cursor.coords[1] - 1],
|
||||
};
|
||||
} else if (direction === "Down") {
|
||||
if (last_selection.region === "main") {
|
||||
if (last_selection.coords[0] < lazy_data.rows.length - 1) {
|
||||
set_selections([
|
||||
{
|
||||
region: "main",
|
||||
coords: [last_selection.coords[0] + 1, last_selection.coords[1]],
|
||||
},
|
||||
]);
|
||||
if (cursor.region === "main") {
|
||||
if (cursor.coords[0] < lazy_data.rows.length - 1) {
|
||||
new_cursor = {
|
||||
region: "main",
|
||||
coords: [cursor.coords[0] + 1, cursor.coords[1]],
|
||||
};
|
||||
} else {
|
||||
// At bottom of main table.
|
||||
set_selections([
|
||||
{
|
||||
region: "inserter",
|
||||
coords: [0, last_selection.coords[1]],
|
||||
},
|
||||
]);
|
||||
new_cursor = {
|
||||
region: "inserter",
|
||||
coords: [0, cursor.coords[1]],
|
||||
};
|
||||
}
|
||||
} else if (last_selection.region === "inserter") {
|
||||
if (last_selection.coords[0] < inserter_rows.length - 1) {
|
||||
set_selections([
|
||||
{
|
||||
region: "inserter",
|
||||
coords: [last_selection.coords[0] + 1, last_selection.coords[1]],
|
||||
},
|
||||
]);
|
||||
} else if (cursor.region === "inserter") {
|
||||
if (cursor.coords[0] < inserter_rows.length - 1) {
|
||||
new_cursor = {
|
||||
region: "inserter",
|
||||
coords: [cursor.coords[0] + 1, cursor.coords[1]],
|
||||
};
|
||||
}
|
||||
}
|
||||
} else if (direction === "Up") {
|
||||
if (last_selection.region === "main") {
|
||||
if (last_selection.coords[0] > 0) {
|
||||
set_selections([
|
||||
{
|
||||
region: "main",
|
||||
coords: [last_selection.coords[0] - 1, last_selection.coords[1]],
|
||||
},
|
||||
]);
|
||||
if (cursor.region === "main") {
|
||||
if (cursor.coords[0] > 0) {
|
||||
new_cursor = {
|
||||
region: "main",
|
||||
coords: [cursor.coords[0] - 1, cursor.coords[1]],
|
||||
};
|
||||
}
|
||||
} else if (last_selection.region === "inserter") {
|
||||
if (last_selection.coords[0] > 0) {
|
||||
set_selections([
|
||||
{
|
||||
region: "inserter",
|
||||
coords: [last_selection.coords[0] - 1, last_selection.coords[1]],
|
||||
},
|
||||
]);
|
||||
} else if (cursor.region === "inserter") {
|
||||
if (cursor.coords[0] > 0) {
|
||||
new_cursor = {
|
||||
region: "inserter",
|
||||
coords: [cursor.coords[0] - 1, cursor.coords[1]],
|
||||
};
|
||||
} else {
|
||||
// At top of inserter table.
|
||||
set_selections([
|
||||
{
|
||||
region: "main",
|
||||
coords: [lazy_data.rows.length - 1, last_selection.coords[1]],
|
||||
},
|
||||
]);
|
||||
new_cursor = {
|
||||
region: "main",
|
||||
coords: [lazy_data.rows.length - 1, cursor.coords[1]],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (new_cursor !== undefined) {
|
||||
const first_selection = selections[selections.length - 1];
|
||||
if (
|
||||
additive &&
|
||||
first_selection !== undefined &&
|
||||
!selections_eq(new_cursor, first_selection)
|
||||
) {
|
||||
// By convention, we keep the first selected cell at the end of the
|
||||
// selections array, and the current cursor at the beginning. Everything
|
||||
// in the bounded box should be populated in between.
|
||||
const all_selections: Omit<Selection, "original_value">[] = [];
|
||||
const left_idx = Math.min(
|
||||
new_cursor.coords[1],
|
||||
first_selection.coords[1],
|
||||
);
|
||||
const right_idx = Math.max(
|
||||
new_cursor.coords[1],
|
||||
first_selection.coords[1],
|
||||
);
|
||||
if (new_cursor.region === first_selection.region) {
|
||||
for (
|
||||
let row_idx = Math.min(
|
||||
new_cursor.coords[0],
|
||||
first_selection.coords[0],
|
||||
);
|
||||
row_idx <=
|
||||
Math.max(new_cursor.coords[0], first_selection.coords[0]);
|
||||
row_idx += 1
|
||||
) {
|
||||
for (
|
||||
let field_idx = left_idx;
|
||||
field_idx <= right_idx;
|
||||
field_idx += 1
|
||||
) {
|
||||
all_selections.push({
|
||||
region: new_cursor.region,
|
||||
coords: [row_idx, field_idx],
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const main_sel =
|
||||
new_cursor.region === "main" ? new_cursor : first_selection;
|
||||
const inserter_sel =
|
||||
new_cursor.region === "inserter" ? new_cursor : first_selection;
|
||||
for (
|
||||
let row_idx = main_sel.coords[0];
|
||||
row_idx < lazy_data.rows.length;
|
||||
row_idx += 1
|
||||
) {
|
||||
for (
|
||||
let field_idx = left_idx;
|
||||
field_idx <= right_idx;
|
||||
field_idx += 1
|
||||
) {
|
||||
all_selections.push({
|
||||
region: "main",
|
||||
coords: [row_idx, field_idx],
|
||||
});
|
||||
}
|
||||
}
|
||||
for (
|
||||
let row_idx = 0;
|
||||
row_idx <= inserter_sel.coords[0];
|
||||
row_idx += 1
|
||||
) {
|
||||
for (
|
||||
let field_idx = left_idx;
|
||||
field_idx <= right_idx;
|
||||
field_idx += 1
|
||||
) {
|
||||
all_selections.push({
|
||||
region: "inserter",
|
||||
coords: [row_idx, field_idx],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
set_selections([
|
||||
new_cursor,
|
||||
...all_selections.filter(
|
||||
(sel) =>
|
||||
!selections_eq(sel, new_cursor) &&
|
||||
!selections_eq(sel, first_selection),
|
||||
),
|
||||
first_selection,
|
||||
]);
|
||||
} else {
|
||||
set_selections([new_cursor]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function try_sync_edit_to_cells() {
|
||||
|
|
@ -375,16 +461,25 @@
|
|||
|
||||
// -------- Event Handlers -------- //
|
||||
|
||||
function handle_table_keydown(ev: KeyboardEvent) {
|
||||
function handle_cell_keydown(ev: KeyboardEvent) {
|
||||
if (!lazy_data) {
|
||||
console.warn("preconditions for handle_table_keydown() not met");
|
||||
return;
|
||||
}
|
||||
const arrow_direction = arrow_key_direction(ev.key);
|
||||
if (arrow_direction) {
|
||||
move_selection(arrow_direction);
|
||||
ev.preventDefault();
|
||||
} else if (/^[a-zA-Z0-9`~!@#$%^&*()_=+[\]{}\\|;:'",<.>/?-]$/.test(ev.key)) {
|
||||
if (ev.shiftKey) {
|
||||
move_cursor(arrow_direction, { additive: true });
|
||||
} else {
|
||||
move_cursor(arrow_direction);
|
||||
}
|
||||
} else if (
|
||||
!ev.altKey &&
|
||||
!ev.ctrlKey &&
|
||||
!ev.metaKey &&
|
||||
/^[a-zA-Z0-9`~!@#$%^&*()_=+[\]{}\\|;:'",<.>/?-]$/.test(ev.key)
|
||||
) {
|
||||
const sel = selections[0];
|
||||
if (sel) {
|
||||
editor_value = get_empty_datum_for(
|
||||
|
|
@ -483,7 +578,7 @@
|
|||
focus_cursor = focus;
|
||||
}}
|
||||
ondblclick={() => datum_editor?.focus()}
|
||||
onkeydown={(ev) => handle_table_keydown(ev)}
|
||||
onkeydown={(ev) => handle_cell_keydown(ev)}
|
||||
onmousedown={on_cell_click}
|
||||
selected={selections.some(
|
||||
(sel) =>
|
||||
|
|
@ -499,7 +594,13 @@
|
|||
{/if}
|
||||
{/snippet}
|
||||
|
||||
<div class="lens-grid">
|
||||
<div
|
||||
class="lens-grid"
|
||||
onpaste={(ev) => {
|
||||
console.log("paste");
|
||||
console.log(ev.clipboardData?.getData("text"));
|
||||
}}
|
||||
>
|
||||
{#if lazy_data}
|
||||
<div class="lens-table" role="grid">
|
||||
<div class={["lens-table__headers"]}>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue