fix table keyboard nav
This commit is contained in:
parent
b12127d220
commit
55c58158cc
4 changed files with 356 additions and 198 deletions
|
|
@ -42,8 +42,9 @@ $table-border-color: #ccc;
|
|||
display: flex;
|
||||
height: 2.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__cell {
|
||||
.lens-cell {
|
||||
align-items: stretch;
|
||||
border: solid 1px $table-border-color;
|
||||
border-left: none;
|
||||
|
|
@ -52,21 +53,14 @@ $table-border-color: #ccc;
|
|||
flex: none;
|
||||
padding: 0;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&--insertable {
|
||||
border-style: dashed;
|
||||
}
|
||||
}
|
||||
|
||||
&__inserter {
|
||||
align-items: stretch;
|
||||
display: flex;
|
||||
grid-area: inserter;
|
||||
justify-items: flex-start;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.lens-cell {
|
||||
&__container {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
|
@ -75,6 +69,11 @@ $table-border-color: #ccc;
|
|||
width: 100%;
|
||||
|
||||
&--selected {
|
||||
outline: 1px solid #37f;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
&--cursor {
|
||||
outline: 3px solid #37f;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
|
@ -176,11 +175,26 @@ $table-border-color: #ccc;
|
|||
}
|
||||
|
||||
.lens-inserter {
|
||||
grid-area: inserter;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
&__help {
|
||||
font-size: 1rem;
|
||||
font-weight: lighter;
|
||||
margin: 8px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&__main {
|
||||
align-items: stretch;
|
||||
display: flex;
|
||||
justify-items: flex-start;
|
||||
}
|
||||
|
||||
&__rows {
|
||||
.lens-table__cell {
|
||||
.lens-cell {
|
||||
border: dashed 1px $table-border-color;
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
} from "./editor-state.svelte";
|
||||
import { type FieldInfo } from "./field.svelte";
|
||||
|
||||
const BLUR_DEBOUNCE_MS = 100;
|
||||
|
||||
type Props = {
|
||||
/**
|
||||
* For use cases in which the user may select between multiple datum types,
|
||||
|
|
@ -17,7 +19,22 @@
|
|||
|
||||
field_info: FieldInfo;
|
||||
|
||||
on_change?(value?: Datum): void;
|
||||
on_blur?(ev: FocusEvent): unknown;
|
||||
|
||||
on_cancel_edit?(): unknown;
|
||||
|
||||
on_change?(value?: Datum): unknown;
|
||||
|
||||
on_focus?(ev: FocusEvent): unknown;
|
||||
|
||||
/**
|
||||
* In addition to `on_blur()`, this callback is invoked when the component
|
||||
* blurs *itself*, for example when a user presses "Enter" or "Escape", as
|
||||
* opposed to blurring in response to a user focusing another element. This
|
||||
* typically indicates that the parent component should restore focus to a
|
||||
* previously focused table cell.
|
||||
*/
|
||||
on_restore_focus?(): unknown;
|
||||
|
||||
value?: Datum;
|
||||
};
|
||||
|
|
@ -25,7 +42,11 @@
|
|||
let {
|
||||
assignable_fields = [],
|
||||
field_info = $bindable(),
|
||||
on_blur,
|
||||
on_cancel_edit,
|
||||
on_change,
|
||||
on_focus,
|
||||
on_restore_focus,
|
||||
value = $bindable(),
|
||||
}: Props = $props();
|
||||
|
||||
|
|
@ -35,6 +56,7 @@
|
|||
>();
|
||||
let type_selector_popover_element = $state<HTMLDivElement | undefined>();
|
||||
let text_input_element = $state<HTMLInputElement | undefined>();
|
||||
let blur_timeout = $state<number | undefined>();
|
||||
|
||||
$effect(() => {
|
||||
if (value) {
|
||||
|
|
@ -46,6 +68,23 @@
|
|||
text_input_element?.focus();
|
||||
}
|
||||
|
||||
function handle_blur(ev: FocusEvent) {
|
||||
// Propagating of blur events upwards is debounced, so that switching focus
|
||||
// between elements does not cause spurious `on_blur()` calls.
|
||||
if (blur_timeout !== undefined) {
|
||||
clearTimeout(blur_timeout);
|
||||
}
|
||||
blur_timeout = setTimeout(() => on_blur?.(ev), BLUR_DEBOUNCE_MS);
|
||||
}
|
||||
|
||||
function handle_focus(ev: FocusEvent) {
|
||||
if (blur_timeout === undefined) {
|
||||
on_focus?.(ev);
|
||||
} else {
|
||||
clearTimeout(blur_timeout);
|
||||
}
|
||||
}
|
||||
|
||||
function handle_input() {
|
||||
if (!editor_state) {
|
||||
console.warn("preconditions for handle_input() not met");
|
||||
|
|
@ -55,7 +94,6 @@
|
|||
editor_state,
|
||||
field_info.field.presentation,
|
||||
);
|
||||
console.log(value);
|
||||
on_change?.(value);
|
||||
}
|
||||
|
||||
|
|
@ -68,16 +106,35 @@
|
|||
type_selector_popover_element?.hidePopover();
|
||||
type_selector_menu_button_element?.focus();
|
||||
}
|
||||
|
||||
function handle_keydown(ev: KeyboardEvent) {
|
||||
if (ev.key === "Enter") {
|
||||
on_restore_focus?.();
|
||||
} else if (ev.key === "Escape") {
|
||||
// Cancel edit before blurring, or else the table will try to commit it.
|
||||
on_cancel_edit?.();
|
||||
on_restore_focus?.();
|
||||
}
|
||||
}
|
||||
|
||||
const interactive_handlers = {
|
||||
onblur: handle_blur,
|
||||
onfocus: handle_focus,
|
||||
onkeydown: handle_keydown,
|
||||
};
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="datum-editor__container"
|
||||
class:datum-editor__container--incomplete={!value}
|
||||
onblur={handle_blur}
|
||||
onfocus={handle_focus}
|
||||
>
|
||||
{#if editor_state}
|
||||
{#if assignable_fields?.length > 0}
|
||||
<div class="datum-editor__type-selector">
|
||||
<button
|
||||
{...interactive_handlers}
|
||||
bind:this={type_selector_menu_button_element}
|
||||
class="datum-editor__type-selector-menu-button"
|
||||
onclick={handle_type_selector_menu_button_click}
|
||||
|
|
@ -88,10 +145,13 @@
|
|||
<div
|
||||
bind:this={type_selector_popover_element}
|
||||
class="datum-editor__type-selector-popover"
|
||||
onblur={handle_blur}
|
||||
onfocus={handle_focus}
|
||||
popover="auto"
|
||||
>
|
||||
{#each assignable_fields as assignable_field_info}
|
||||
<button
|
||||
{...interactive_handlers}
|
||||
onclick={() =>
|
||||
handle_type_selector_field_button_click(assignable_field_info)}
|
||||
type="button"
|
||||
|
|
@ -103,6 +163,7 @@
|
|||
</div>
|
||||
{/if}
|
||||
<button
|
||||
{...interactive_handlers}
|
||||
type="button"
|
||||
class="datum-editor__null-control"
|
||||
class:datum-editor__null-control--disabled={editor_state.text_value !==
|
||||
|
|
@ -125,6 +186,7 @@
|
|||
</button>
|
||||
{#if field_info.field.presentation.t === "Dropdown" || field_info.field.presentation.t === "Text" || field_info.field.presentation.t === "Uuid"}
|
||||
<input
|
||||
{...interactive_handlers}
|
||||
bind:this={text_input_element}
|
||||
value={editor_state.text_value}
|
||||
oninput={({ currentTarget }) => {
|
||||
|
|
@ -142,8 +204,16 @@
|
|||
type="text"
|
||||
/>
|
||||
{:else if field_info.field.presentation.t === "Timestamp"}
|
||||
<input value={editor_state.date_value} type="date" />
|
||||
<input value={editor_state.time_value} type="time" />
|
||||
<input
|
||||
{...interactive_handlers}
|
||||
value={editor_state.date_value}
|
||||
type="date"
|
||||
/>
|
||||
<input
|
||||
{...interactive_handlers}
|
||||
value={editor_state.time_value}
|
||||
type="time"
|
||||
/>
|
||||
{/if}
|
||||
<div class="datum-editor__helpers" tabindex="-1">
|
||||
{#if field_info.field.presentation.t === "Dropdown"}
|
||||
|
|
@ -160,6 +230,7 @@
|
|||
aria-selected={dropdown_option.value === value?.c}
|
||||
>
|
||||
<button
|
||||
{...interactive_handlers}
|
||||
class="datum-editor__dropdown-option-button"
|
||||
onclick={() => {
|
||||
if (!editor_state || !text_input_element) {
|
||||
|
|
|
|||
131
svelte/src/table-cell.svelte
Normal file
131
svelte/src/table-cell.svelte
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
<script lang="ts">
|
||||
import { type Datum } from "./datum.svelte";
|
||||
import { type FieldInfo, type Coords } from "./field.svelte";
|
||||
|
||||
type Props = {
|
||||
coords: Coords;
|
||||
cursor: boolean;
|
||||
field: FieldInfo;
|
||||
onbecomecursor?(focus: () => unknown): unknown;
|
||||
ondblclick?(ev: MouseEvent, coords: Coords): unknown;
|
||||
onfocus?(ev: FocusEvent, coords: Coords): unknown;
|
||||
onkeydown?(ev: KeyboardEvent, coords: Coords): unknown;
|
||||
onmousedown?(ev: MouseEvent, coords: Coords): unknown;
|
||||
selected: boolean;
|
||||
table_region: "main" | "inserter";
|
||||
value: Datum;
|
||||
};
|
||||
|
||||
let {
|
||||
coords,
|
||||
cursor,
|
||||
field,
|
||||
onbecomecursor,
|
||||
ondblclick,
|
||||
onfocus,
|
||||
onkeydown,
|
||||
onmousedown,
|
||||
selected,
|
||||
table_region,
|
||||
value,
|
||||
}: Props = $props();
|
||||
|
||||
let cell_element = $state<HTMLDivElement | undefined>();
|
||||
|
||||
let invalid_value = $derived(
|
||||
field.not_null && !field.has_default && value.c === undefined,
|
||||
);
|
||||
let null_value_class = $derived(
|
||||
table_region === "inserter" && field.has_default
|
||||
? "ti-sparkles"
|
||||
: "ti-cube-3d-sphere-off",
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
if (cursor) {
|
||||
onbecomecursor?.(() => cell_element?.focus());
|
||||
cell_element?.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
aria-colindex={coords[0]}
|
||||
aria-rowindex={coords[1]}
|
||||
aria-selected={selected}
|
||||
bind:this={cell_element}
|
||||
class="lens-cell"
|
||||
onmousedown={(ev) => onmousedown?.(ev, coords)}
|
||||
ondblclick={(ev) => ondblclick?.(ev, coords)}
|
||||
onfocus={(ev) => onfocus?.(ev, coords)}
|
||||
onkeydown={(ev) => onkeydown?.(ev, coords)}
|
||||
role="gridcell"
|
||||
style:width={`${field.field.table_width_px}px`}
|
||||
tabindex={selected ? 0 : -1}
|
||||
>
|
||||
<div
|
||||
class="lens-cell__container"
|
||||
class:lens-cell__container--cursor={cursor}
|
||||
class:lens-cell__container--selected={selected}
|
||||
>
|
||||
{#if field.field.presentation.t === "Dropdown"}
|
||||
{#if value.t === "Text"}
|
||||
<div
|
||||
class="lens-cell__content lens-cell__content--dropdown"
|
||||
class:lens-cell__content--null={value.c === undefined}
|
||||
>
|
||||
{#if value.c === undefined}
|
||||
<i class={["ti", null_value_class]}></i>
|
||||
{:else}
|
||||
<!-- FIXME: validate or escape dropdown_option.color -->
|
||||
<div
|
||||
class={[
|
||||
"dropdown-option-badge",
|
||||
`dropdown-option-badge--${
|
||||
field.field.presentation.c.options
|
||||
.find((option) => option.value === value.c)
|
||||
?.color.toLocaleLowerCase("en-US") ?? "grey"
|
||||
}`,
|
||||
]}
|
||||
>
|
||||
{value.c}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
UNKNOWN
|
||||
{/if}
|
||||
{:else if field.field.presentation.t === "Text"}
|
||||
<div
|
||||
class="lens-cell__content lens-cell__content--text"
|
||||
class:lens-cell__content--null={value.c === undefined}
|
||||
>
|
||||
{#if value.c === undefined}
|
||||
<i class={["ti", null_value_class]}></i>
|
||||
{:else}
|
||||
{value.c}
|
||||
{/if}
|
||||
</div>
|
||||
{:else if field.field.presentation.t === "Uuid"}
|
||||
<div
|
||||
class="lens-cell__content lens-cell__content--uuid"
|
||||
class:lens-cell__content--null={value.c === undefined}
|
||||
>
|
||||
{#if value.c === undefined}
|
||||
<i class={["ti", null_value_class]}></i>
|
||||
{:else}
|
||||
{value.c}
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="lens-cell__content lens-cell__content--unknown">
|
||||
<div>UNKNOWN</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if invalid_value}
|
||||
<div class="lens-cell__notice">
|
||||
<i class="ti ti-alert-circle"></i>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -23,6 +23,7 @@
|
|||
import FieldAdder from "./field-adder.svelte";
|
||||
import FieldHeader from "./field-header.svelte";
|
||||
import { get_empty_datum_for } from "./presentation.svelte";
|
||||
import TableCell from "./table-cell.svelte";
|
||||
|
||||
type Props = {
|
||||
columns?: {
|
||||
|
|
@ -77,7 +78,7 @@
|
|||
reverted: [],
|
||||
});
|
||||
let datum_editor = $state<DatumEditor | undefined>();
|
||||
let table_element = $state<HTMLDivElement | undefined>();
|
||||
let focus_cursor = $state<(() => unknown) | undefined>();
|
||||
let inserter_rows = $state<Row[]>([]);
|
||||
let lazy_data = $state<LazyData | undefined>();
|
||||
|
||||
|
|
@ -313,19 +314,28 @@
|
|||
});
|
||||
// Reset editor input value
|
||||
set_selections(selections);
|
||||
table_element?.focus();
|
||||
}
|
||||
|
||||
// -------- Event Handlers: Both Tables -------- //
|
||||
// -------- Event Handlers -------- //
|
||||
|
||||
function handle_table_keydown(ev: KeyboardEvent) {
|
||||
if (lazy_data) {
|
||||
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)) {
|
||||
const sel = selections[0];
|
||||
if (sel) {
|
||||
editor_value = get_empty_datum_for(
|
||||
lazy_data.fields[sel.coords[1]].field.presentation,
|
||||
);
|
||||
datum_editor?.focus();
|
||||
}
|
||||
if (ev.key === "Enter") {
|
||||
} else if (ev.key === "Enter") {
|
||||
if (ev.shiftKey) {
|
||||
if (selections[0]?.region === "main") {
|
||||
set_selections([
|
||||
|
|
@ -350,30 +360,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------- Event Handlers: Main Table -------- //
|
||||
|
||||
function handle_main_cell_click(ev: MouseEvent, coords: Coords) {
|
||||
if (ev.metaKey || ev.ctrlKey) {
|
||||
// TODO
|
||||
// selections = [...selections.filter((prev) => !coords_eq(prev, coords)), coords];
|
||||
// editor_input_value = "";
|
||||
} else {
|
||||
set_selections([{ region: "main", coords }]);
|
||||
}
|
||||
}
|
||||
|
||||
// -------- Event Handlers: Inserter Table -------- //
|
||||
|
||||
function handle_inserter_cell_click(ev: MouseEvent, coords: Coords) {
|
||||
if (ev.metaKey || ev.ctrlKey) {
|
||||
// TODO
|
||||
// selections = [...selections.filter((prev) => !coords_eq(prev, coords)), coords];
|
||||
// editor_input_value = "";
|
||||
} else {
|
||||
set_selections([{ region: "inserter", coords }]);
|
||||
}
|
||||
function handle_restore_focus() {
|
||||
focus_cursor?.();
|
||||
}
|
||||
|
||||
// -------- Initial API Fetch -------- //
|
||||
|
|
@ -402,6 +391,14 @@
|
|||
),
|
||||
},
|
||||
];
|
||||
if (lazy_data.rows.length > 0 && lazy_data.fields.length > 0) {
|
||||
set_selections([
|
||||
{
|
||||
region: "main",
|
||||
coords: [0, 0],
|
||||
},
|
||||
]);
|
||||
}
|
||||
})().catch(console.error);
|
||||
|
||||
setInterval(tick_delta_queue, 500);
|
||||
|
|
@ -420,97 +417,25 @@
|
|||
{#each rows as row, row_index}
|
||||
<div class="lens-table__row" role="row">
|
||||
{#each lazy_data.fields as field, field_index}
|
||||
{@const cell_data = row.data[field_index]}
|
||||
{@const cell_coords: Coords = [row_index, field_index]}
|
||||
{@const cell_selected = selections.some(
|
||||
(sel) =>
|
||||
sel.region === region_name && coords_eq(sel.coords, cell_coords),
|
||||
)}
|
||||
{@const null_value_class =
|
||||
region_name === "inserter" && field.has_default
|
||||
? "ti-sparkles"
|
||||
: "ti-cube-3d-sphere-off"}
|
||||
{@const invalid_value =
|
||||
field.not_null && !field.has_default && cell_data.c === undefined}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
aria-colindex={field_index}
|
||||
aria-rowindex={row_index}
|
||||
aria-selected={cell_selected}
|
||||
class="lens-table__cell"
|
||||
onmousedown={(ev) => on_cell_click(ev, cell_coords)}
|
||||
ondblclick={() => {
|
||||
datum_editor?.focus();
|
||||
<TableCell
|
||||
coords={[row_index, field_index]}
|
||||
cursor={selections[0]?.region === region_name &&
|
||||
coords_eq(selections[0].coords, [row_index, field_index])}
|
||||
{field}
|
||||
onbecomecursor={(focus) => {
|
||||
focus_cursor = focus;
|
||||
}}
|
||||
role="gridcell"
|
||||
style:width={`${field.field.table_width_px}px`}
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="lens-cell__container"
|
||||
class:lens-cell__container--selected={cell_selected}
|
||||
>
|
||||
{#if field.field.presentation.t === "Dropdown"}
|
||||
{#if cell_data.t === "Text"}
|
||||
<div
|
||||
class="lens-cell__content lens-cell__content--dropdown"
|
||||
class:lens-cell__content--null={cell_data.c === undefined}
|
||||
>
|
||||
{#if cell_data.c === undefined}
|
||||
<i class="ti {null_value_class}"></i>
|
||||
{:else}
|
||||
<!-- FIXME: validate or escape dropdown_option.color -->
|
||||
<div
|
||||
class={[
|
||||
"dropdown-option-badge",
|
||||
`dropdown-option-badge--${
|
||||
field.field.presentation.c.options
|
||||
.find((option) => option.value === cell_data.c)
|
||||
?.color.toLocaleLowerCase("en-US") ?? "grey"
|
||||
}`,
|
||||
]}
|
||||
>
|
||||
{cell_data.c}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
UNKNOWN
|
||||
{/if}
|
||||
{:else if field.field.presentation.t === "Text"}
|
||||
<div
|
||||
class="lens-cell__content lens-cell__content--text"
|
||||
class:lens-cell__content--null={cell_data.c === undefined}
|
||||
>
|
||||
{#if cell_data.c === undefined}
|
||||
<i class="ti {null_value_class}"></i>
|
||||
{:else}
|
||||
{cell_data.c}
|
||||
{/if}
|
||||
</div>
|
||||
{:else if field.field.presentation.t === "Uuid"}
|
||||
<div
|
||||
class="lens-cell__content lens-cell__content--uuid"
|
||||
class:lens-cell__content--null={cell_data.c === undefined}
|
||||
>
|
||||
{#if cell_data.c === undefined}
|
||||
<i class="ti {null_value_class}"></i>
|
||||
{:else}
|
||||
{cell_data.c}
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="lens-cell__content lens-cell__content--unknown">
|
||||
<div>UNKNOWN</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if invalid_value}
|
||||
<div class="lens-cell__notice">
|
||||
<i class="ti ti-alert-circle"></i>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
ondblclick={() => datum_editor?.focus()}
|
||||
onkeydown={(ev) => handle_table_keydown(ev)}
|
||||
onmousedown={on_cell_click}
|
||||
selected={selections.some(
|
||||
(sel) =>
|
||||
sel.region === region_name &&
|
||||
coords_eq(sel.coords, [row_index, field_index]),
|
||||
)}
|
||||
table_region={region_name}
|
||||
value={row.data[field_index]}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
|
|
@ -519,16 +444,7 @@
|
|||
|
||||
<div class="lens-grid">
|
||||
{#if lazy_data}
|
||||
<div
|
||||
bind:this={table_element}
|
||||
class="lens-table"
|
||||
onfocus={() => {
|
||||
try_queue_delta();
|
||||
}}
|
||||
onkeydown={handle_table_keydown}
|
||||
role="grid"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="lens-table" role="grid">
|
||||
<div class={["lens-table__headers"]}>
|
||||
{#each lazy_data.fields as _, field_index}
|
||||
<FieldHeader
|
||||
|
|
@ -544,30 +460,53 @@
|
|||
{@render table_region({
|
||||
region_name: "main",
|
||||
rows: lazy_data.rows,
|
||||
on_cell_click: handle_main_cell_click,
|
||||
on_cell_click: (ev: MouseEvent, coords: Coords) => {
|
||||
if (ev.metaKey || ev.ctrlKey) {
|
||||
// TODO
|
||||
// selections = [...selections.filter((prev) => !coords_eq(prev, coords)), coords];
|
||||
// editor_input_value = "";
|
||||
} else {
|
||||
set_selections([{ region: "main", coords }]);
|
||||
}
|
||||
},
|
||||
})}
|
||||
</div>
|
||||
<form method="post" action="insert">
|
||||
<div class="lens-table__inserter">
|
||||
<div class="lens-inserter">
|
||||
<h3 class="lens-inserter__help">
|
||||
Insert rows — press "shift + enter" to jump here or add a row
|
||||
</h3>
|
||||
<div class="lens-inserter__main">
|
||||
<div class="lens-inserter__rows">
|
||||
{@render table_region({
|
||||
region_name: "inserter",
|
||||
rows: inserter_rows,
|
||||
on_cell_click: handle_inserter_cell_click,
|
||||
on_cell_click: (ev: MouseEvent, coords: Coords) => {
|
||||
if (ev.metaKey || ev.ctrlKey) {
|
||||
// TODO
|
||||
// selections = [...selections.filter((prev) => !coords_eq(prev, coords)), coords];
|
||||
// editor_input_value = "";
|
||||
} else {
|
||||
set_selections([{ region: "inserter", coords }]);
|
||||
}
|
||||
},
|
||||
})}
|
||||
</div>
|
||||
<button
|
||||
aria-label="Insert rows"
|
||||
class="lens-inserter__submit"
|
||||
onkeydown={(ev) => {
|
||||
// Prevent keypress (e.g. pressing Enter on the button to submit
|
||||
// it) from triggering a table interaction.
|
||||
ev.stopPropagation();
|
||||
}}
|
||||
title="Insert rows"
|
||||
type="submit"
|
||||
>
|
||||
<i class="ti ti-upload"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{#each inserter_rows as row}
|
||||
{#each lazy_data.fields as field, field_index}
|
||||
<input
|
||||
|
|
@ -585,9 +524,12 @@
|
|||
bind:this={datum_editor}
|
||||
bind:value={editor_value}
|
||||
field_info={lazy_data.fields[selections[0].coords[1]]}
|
||||
on_blur={() => try_queue_delta()}
|
||||
on_cancel_edit={cancel_edit}
|
||||
on_change={() => {
|
||||
try_sync_edit_to_cells();
|
||||
}}
|
||||
on_restore_focus={handle_restore_focus}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue