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%;
|
width: 100%;
|
||||||
|
|
||||||
&--selected {
|
&--selected {
|
||||||
outline: 1px solid #37f;
|
background: #07f3;
|
||||||
outline-offset: -1px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&--cursor {
|
&--cursor {
|
||||||
|
background: transparent;
|
||||||
outline: 3px solid #37f;
|
outline: 3px solid #37f;
|
||||||
outline-offset: -2px;
|
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({
|
function update_field_ordinality({
|
||||||
field_index,
|
field_index,
|
||||||
beyond_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) {
|
if (!lazy_data || selections.length === 0) {
|
||||||
console.warn("move_selection() preconditions not met");
|
console.warn("move_selection() preconditions not met");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const last_selection = selections[selections.length - 1];
|
const cursor = selections[0];
|
||||||
|
let new_cursor: Omit<Selection, "original_value"> | undefined;
|
||||||
if (
|
if (
|
||||||
direction === "Right" &&
|
direction === "Right" &&
|
||||||
last_selection.coords[1] < lazy_data.fields.length - 1
|
cursor.coords[1] < lazy_data.fields.length - 1
|
||||||
) {
|
) {
|
||||||
set_selections([
|
new_cursor = {
|
||||||
{
|
region: cursor.region,
|
||||||
region: last_selection.region,
|
coords: [cursor.coords[0], cursor.coords[1] + 1],
|
||||||
coords: [last_selection.coords[0], last_selection.coords[1] + 1],
|
};
|
||||||
},
|
} else if (direction === "Left" && cursor.coords[1] > 0) {
|
||||||
]);
|
new_cursor = {
|
||||||
} else if (direction === "Left" && last_selection.coords[1] > 0) {
|
region: cursor.region,
|
||||||
set_selections([
|
coords: [cursor.coords[0], cursor.coords[1] - 1],
|
||||||
{
|
};
|
||||||
region: last_selection.region,
|
|
||||||
coords: [last_selection.coords[0], last_selection.coords[1] - 1],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
} else if (direction === "Down") {
|
} else if (direction === "Down") {
|
||||||
if (last_selection.region === "main") {
|
if (cursor.region === "main") {
|
||||||
if (last_selection.coords[0] < lazy_data.rows.length - 1) {
|
if (cursor.coords[0] < lazy_data.rows.length - 1) {
|
||||||
set_selections([
|
new_cursor = {
|
||||||
{
|
region: "main",
|
||||||
region: "main",
|
coords: [cursor.coords[0] + 1, cursor.coords[1]],
|
||||||
coords: [last_selection.coords[0] + 1, last_selection.coords[1]],
|
};
|
||||||
},
|
|
||||||
]);
|
|
||||||
} else {
|
} else {
|
||||||
// At bottom of main table.
|
// At bottom of main table.
|
||||||
set_selections([
|
new_cursor = {
|
||||||
{
|
region: "inserter",
|
||||||
region: "inserter",
|
coords: [0, cursor.coords[1]],
|
||||||
coords: [0, last_selection.coords[1]],
|
};
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
} else if (last_selection.region === "inserter") {
|
} else if (cursor.region === "inserter") {
|
||||||
if (last_selection.coords[0] < inserter_rows.length - 1) {
|
if (cursor.coords[0] < inserter_rows.length - 1) {
|
||||||
set_selections([
|
new_cursor = {
|
||||||
{
|
region: "inserter",
|
||||||
region: "inserter",
|
coords: [cursor.coords[0] + 1, cursor.coords[1]],
|
||||||
coords: [last_selection.coords[0] + 1, last_selection.coords[1]],
|
};
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (direction === "Up") {
|
} else if (direction === "Up") {
|
||||||
if (last_selection.region === "main") {
|
if (cursor.region === "main") {
|
||||||
if (last_selection.coords[0] > 0) {
|
if (cursor.coords[0] > 0) {
|
||||||
set_selections([
|
new_cursor = {
|
||||||
{
|
region: "main",
|
||||||
region: "main",
|
coords: [cursor.coords[0] - 1, cursor.coords[1]],
|
||||||
coords: [last_selection.coords[0] - 1, last_selection.coords[1]],
|
};
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
} else if (last_selection.region === "inserter") {
|
} else if (cursor.region === "inserter") {
|
||||||
if (last_selection.coords[0] > 0) {
|
if (cursor.coords[0] > 0) {
|
||||||
set_selections([
|
new_cursor = {
|
||||||
{
|
region: "inserter",
|
||||||
region: "inserter",
|
coords: [cursor.coords[0] - 1, cursor.coords[1]],
|
||||||
coords: [last_selection.coords[0] - 1, last_selection.coords[1]],
|
};
|
||||||
},
|
|
||||||
]);
|
|
||||||
} else {
|
} else {
|
||||||
// At top of inserter table.
|
// At top of inserter table.
|
||||||
set_selections([
|
new_cursor = {
|
||||||
{
|
region: "main",
|
||||||
region: "main",
|
coords: [lazy_data.rows.length - 1, cursor.coords[1]],
|
||||||
coords: [lazy_data.rows.length - 1, last_selection.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() {
|
function try_sync_edit_to_cells() {
|
||||||
|
|
@ -375,16 +461,25 @@
|
||||||
|
|
||||||
// -------- Event Handlers -------- //
|
// -------- Event Handlers -------- //
|
||||||
|
|
||||||
function handle_table_keydown(ev: KeyboardEvent) {
|
function handle_cell_keydown(ev: KeyboardEvent) {
|
||||||
if (!lazy_data) {
|
if (!lazy_data) {
|
||||||
console.warn("preconditions for handle_table_keydown() not met");
|
console.warn("preconditions for handle_table_keydown() not met");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const arrow_direction = arrow_key_direction(ev.key);
|
const arrow_direction = arrow_key_direction(ev.key);
|
||||||
if (arrow_direction) {
|
if (arrow_direction) {
|
||||||
move_selection(arrow_direction);
|
|
||||||
ev.preventDefault();
|
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];
|
const sel = selections[0];
|
||||||
if (sel) {
|
if (sel) {
|
||||||
editor_value = get_empty_datum_for(
|
editor_value = get_empty_datum_for(
|
||||||
|
|
@ -483,7 +578,7 @@
|
||||||
focus_cursor = focus;
|
focus_cursor = focus;
|
||||||
}}
|
}}
|
||||||
ondblclick={() => datum_editor?.focus()}
|
ondblclick={() => datum_editor?.focus()}
|
||||||
onkeydown={(ev) => handle_table_keydown(ev)}
|
onkeydown={(ev) => handle_cell_keydown(ev)}
|
||||||
onmousedown={on_cell_click}
|
onmousedown={on_cell_click}
|
||||||
selected={selections.some(
|
selected={selections.some(
|
||||||
(sel) =>
|
(sel) =>
|
||||||
|
|
@ -499,7 +594,13 @@
|
||||||
{/if}
|
{/if}
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
<div class="lens-grid">
|
<div
|
||||||
|
class="lens-grid"
|
||||||
|
onpaste={(ev) => {
|
||||||
|
console.log("paste");
|
||||||
|
console.log(ev.clipboardData?.getData("text"));
|
||||||
|
}}
|
||||||
|
>
|
||||||
{#if lazy_data}
|
{#if lazy_data}
|
||||||
<div class="lens-table" role="grid">
|
<div class="lens-table" role="grid">
|
||||||
<div class={["lens-table__headers"]}>
|
<div class={["lens-table__headers"]}>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue