2022-05-17 11:06:15 -07:00
|
|
|
<script>
|
|
|
|
|
export let rows;
|
|
|
|
|
export let columns;
|
2022-06-13 07:42:26 -07:00
|
|
|
export let rowKey; //Must only be null/undefined if the row is a new object (not associated with a row in the table). Should not change.
|
2022-05-17 11:06:15 -07:00
|
|
|
export let edited;
|
2022-06-13 07:42:26 -07:00
|
|
|
export let actions;
|
2022-05-17 11:06:15 -07:00
|
|
|
|
|
|
|
|
// Setup a width for each column.
|
|
|
|
|
columns.forEach(column => {
|
|
|
|
|
let min = column.minWidth ? Math.max(10, column.minWidth) : 10;
|
|
|
|
|
let weight = column.weight ? Math.max(1, column.weight) : 1;
|
|
|
|
|
column.width = 'minmax(' + min + 'px, ' + weight + 'fr)';
|
2022-06-13 07:42:26 -07:00
|
|
|
column.isActions = false;
|
2022-05-17 11:06:15 -07:00
|
|
|
});
|
2022-06-13 07:42:26 -07:00
|
|
|
// Add the actions column to the end.
|
|
|
|
|
// TODO: Allow it to be positioned.
|
|
|
|
|
if(actions) {
|
|
|
|
|
// TODO: make this fixed width based on the possible widget widths.
|
|
|
|
|
actions.width = 'minmax(10px, 1fr)';
|
|
|
|
|
actions.isActions = true;
|
|
|
|
|
columns[columns.length] = actions;
|
|
|
|
|
}
|
|
|
|
|
// Collect the column widths for the layout.
|
2022-05-17 11:06:15 -07:00
|
|
|
let gridTemplateColumns = columns.map(({width}) => width).join(' ');
|
|
|
|
|
|
2022-06-13 07:42:26 -07:00
|
|
|
// Resize column code.
|
2022-05-17 11:06:15 -07:00
|
|
|
let headerBeingResized = null;
|
|
|
|
|
let horizontalScrollOffset = 0;
|
|
|
|
|
const initResize = ({target}) => {
|
|
|
|
|
headerBeingResized = target.parentNode;
|
|
|
|
|
window.addEventListener('mousemove', onMouseMove);
|
|
|
|
|
window.addEventListener('mouseup', completeResize);
|
|
|
|
|
headerBeingResized.classList.add('header--being-resized');
|
|
|
|
|
};
|
|
|
|
|
const completeResize = () => {
|
|
|
|
|
window.removeEventListener('mousemove', onMouseMove);
|
|
|
|
|
window.removeEventListener('mouseup', completeResize);
|
|
|
|
|
headerBeingResized.classList.remove('header--being-resized');
|
|
|
|
|
headerBeingResized = null;
|
|
|
|
|
};
|
|
|
|
|
const onMouseMove = e => {
|
|
|
|
|
try {
|
|
|
|
|
// Calculate the desired width.
|
|
|
|
|
horizontalScrollOffset = document.documentElement.scrollLeft;
|
|
|
|
|
let parentX = Math.round(headerBeingResized.getBoundingClientRect().x);
|
|
|
|
|
const width = horizontalScrollOffset + (e.clientX - parentX);
|
|
|
|
|
// Update the column object with the new size value.
|
|
|
|
|
const column = columns.find(({element}) => element === headerBeingResized);
|
|
|
|
|
column.width = Math.max(column.minWidth, width) + "px";
|
|
|
|
|
// Ensure all the column widths are converted to fixed sizes.
|
|
|
|
|
columns.forEach((column, index) => {
|
|
|
|
|
if((index < columns.length - 1) && (column.width.startsWith('minmax'))) {
|
|
|
|
|
column.width = parseInt(column.element.clientWidth, 10) + 'px';
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
// Render the new column sizes.
|
|
|
|
|
gridTemplateColumns = columns.map(({width}) => width).join(' ');
|
|
|
|
|
} catch(e) {console.log(e);}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-13 07:42:26 -07:00
|
|
|
// Select row code.
|
2022-05-17 11:06:15 -07:00
|
|
|
let selectedRowElement = null;
|
|
|
|
|
const selectRow = (e, row) => {
|
|
|
|
|
let element = e.target;
|
|
|
|
|
|
|
|
|
|
while(element && element.nodeName !== "TR") element = element.parentNode;
|
|
|
|
|
|
|
|
|
|
if(selectedRowElement) {
|
|
|
|
|
selectedRowElement.classList.remove('selected');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
selectedRowElement = element;
|
|
|
|
|
element.classList.add('selected');
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-13 07:42:26 -07:00
|
|
|
// Edit row code.
|
2022-05-17 11:06:15 -07:00
|
|
|
let editorContainer;
|
2022-06-13 07:42:26 -07:00
|
|
|
let editorTable;
|
|
|
|
|
// Listen for changes to the edited variable. Move the editor row and display it when there is a value.
|
|
|
|
|
// Relies on the table row being hidden when the edited value matches the row's value.
|
|
|
|
|
$: {
|
|
|
|
|
if(editorContainer) {
|
|
|
|
|
if($edited) {
|
|
|
|
|
let id = rowKey($edited);
|
|
|
|
|
let hiddenRow = editorTable.querySelector('tbody tr[data-key="' + id + '"]');
|
|
|
|
|
|
|
|
|
|
if(!hiddenRow) {
|
|
|
|
|
let body = editorTable.querySelector('tbody');
|
|
|
|
|
body.firstChild ? body.insertBefore(editorContainer, body.firstChild) : body.appendChild(editorContainer);
|
|
|
|
|
editorContainer.classList.remove('hidden');
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
//let editor = hiddenRow.querySelector('.editor');
|
|
|
|
|
let body = editorTable.querySelector('tbody');
|
|
|
|
|
let next = hiddenRow.nextSibling;
|
|
|
|
|
//editor.appendChild(editorContainer);
|
|
|
|
|
next ? body.insertBefore(editorContainer, next) : body.appendChild(editorContainer);
|
|
|
|
|
editorContainer.classList.remove('hidden');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
console.log("Edited cleared");
|
|
|
|
|
editorContainer.classList.add('hidden');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/*
|
2022-05-17 11:06:15 -07:00
|
|
|
const editRow = (e, row) => {
|
|
|
|
|
let element = e.target;
|
|
|
|
|
while(element && element.nodeName !== "TR") element = element.parentNode;
|
|
|
|
|
let editor = element.querySelector('.editor');
|
|
|
|
|
|
2022-06-13 07:42:26 -07:00
|
|
|
isNew = false;
|
2022-05-17 11:06:15 -07:00
|
|
|
// Save the edited row so the editor has access to it.
|
|
|
|
|
$edited = row;
|
|
|
|
|
|
|
|
|
|
if(editor) {
|
|
|
|
|
editor.appendChild(editorContainer);
|
|
|
|
|
}
|
|
|
|
|
editorContainer.classList.remove('hidden');
|
|
|
|
|
}
|
2022-06-13 07:42:26 -07:00
|
|
|
const newRow = () => {
|
|
|
|
|
isNew = true;
|
|
|
|
|
newEditor.appendChild(editorContainer);
|
|
|
|
|
editorContainer.classList.remove('hidden');
|
|
|
|
|
}
|
|
|
|
|
*/
|
2022-05-17 11:06:15 -07:00
|
|
|
</script>
|
|
|
|
|
|
2022-06-13 07:42:26 -07:00
|
|
|
<div bind:this={editorContainer} class="editor2 hidden"><slot>Slot</slot></div>
|
|
|
|
|
<table bind:this={editorTable} style="--grid-template-columns: {gridTemplateColumns}">
|
2022-05-17 11:06:15 -07:00
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
{#each columns as column}
|
2022-06-13 07:42:26 -07:00
|
|
|
{#if column.isActions}
|
|
|
|
|
<th bind:this={column.element}>{column.title}
|
|
|
|
|
{#each column.headerWidgets as widget}
|
|
|
|
|
<span class="material-icons material-symbols-outlined" on:click={widget.action} title="{widget.tooltip}">{widget.icon}</span>
|
|
|
|
|
{/each}
|
|
|
|
|
</th>
|
|
|
|
|
{:else}
|
|
|
|
|
<th bind:this={column.element}>{column.title} <span class="resize-handle" on:mousedown={initResize}></span></th>
|
|
|
|
|
{/if}
|
2022-05-17 11:06:15 -07:00
|
|
|
{/each}
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
{#each $rows as row (rowKey(row))}
|
|
|
|
|
<!-- data-key="{rowKey(row)}"-->
|
2022-06-13 07:42:26 -07:00
|
|
|
<!-- on:dblclick={(e) => editRow(e, row)} -->
|
|
|
|
|
<tr data-key="{rowKey(row)}" class:hidden={row === $edited} on:mousedown={(e) => selectRow(e, row)} on:dblclick={(e) => {$edited = row}}>
|
2022-05-17 11:06:15 -07:00
|
|
|
{#each columns as column}
|
2022-06-13 07:42:26 -07:00
|
|
|
{#if column.isActions}
|
|
|
|
|
<td>
|
|
|
|
|
{#each column.rowWidgets as widget}
|
|
|
|
|
<span class="material-icons material-symbols-outlined" on:click={widget.action(row)}>{widget.icon}</span>
|
|
|
|
|
{/each}
|
|
|
|
|
</td>
|
|
|
|
|
{:else}
|
|
|
|
|
<td>{column.value(row)}</td>
|
|
|
|
|
{/if}
|
2022-05-17 11:06:15 -07:00
|
|
|
{/each}
|
|
|
|
|
</tr>
|
|
|
|
|
{/each}
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
2022-06-13 07:42:26 -07:00
|
|
|
<!--<button on:click={() => {$edited = null}} type="button">Stop Editing</button>-->
|
2022-05-17 11:06:15 -07:00
|
|
|
|
|
|
|
|
<style>
|
2022-06-13 07:42:26 -07:00
|
|
|
.material-icons {
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
thead .material-icons {
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
2022-05-17 11:06:15 -07:00
|
|
|
table {
|
2022-06-13 07:42:26 -07:00
|
|
|
width: auto;
|
|
|
|
|
-webkit-box-flex: 1;
|
|
|
|
|
flex: 1;
|
|
|
|
|
display: grid;
|
|
|
|
|
border-collapse: collapse;
|
|
|
|
|
grid-template-columns: var(--grid-template-columns);
|
2022-05-17 11:06:15 -07:00
|
|
|
}
|
|
|
|
|
thead, tbody, tr {
|
|
|
|
|
display: contents;
|
|
|
|
|
}
|
|
|
|
|
th, td {
|
2022-06-13 07:42:26 -07:00
|
|
|
padding: 15px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
user-select: none;
|
2022-05-17 11:06:15 -07:00
|
|
|
}
|
|
|
|
|
th {
|
2022-06-13 07:42:26 -07:00
|
|
|
position: -webkit-sticky;
|
|
|
|
|
position: sticky;
|
|
|
|
|
top: 0;
|
|
|
|
|
background: #3f4f3f;
|
|
|
|
|
text-align: left;
|
|
|
|
|
font-weight: normal;
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
color: white;
|
|
|
|
|
position: relative;
|
2022-05-17 11:06:15 -07:00
|
|
|
}
|
|
|
|
|
th:last-child {
|
2022-06-13 07:42:26 -07:00
|
|
|
border: 0;
|
2022-05-17 11:06:15 -07:00
|
|
|
}
|
|
|
|
|
.resize-handle {
|
2022-06-13 07:42:26 -07:00
|
|
|
position: absolute;
|
|
|
|
|
top: 0;
|
|
|
|
|
right: 0;
|
|
|
|
|
bottom: 0;
|
|
|
|
|
background: black;
|
|
|
|
|
opacity: 0;
|
|
|
|
|
width: 3px;
|
|
|
|
|
cursor: col-resize;
|
2022-05-17 11:06:15 -07:00
|
|
|
}
|
|
|
|
|
th:last-child .resize-handle {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
.resize-handle:hover, .header--being-resized .resize-handle {
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
}
|
|
|
|
|
th:hover .resize-handle {
|
|
|
|
|
opacity: 0.3;
|
|
|
|
|
}
|
|
|
|
|
td {
|
|
|
|
|
padding-top: 10px;
|
|
|
|
|
padding-bottom: 10px;
|
|
|
|
|
color: #808080;
|
|
|
|
|
}
|
|
|
|
|
tr:nth-child(even) {
|
|
|
|
|
background: #f8f6ff;
|
|
|
|
|
}
|
|
|
|
|
:global(.selected), :global(.selected) > td {
|
|
|
|
|
background-color: yellow;
|
|
|
|
|
}
|
|
|
|
|
.editor {
|
2022-06-13 07:42:26 -07:00
|
|
|
grid-column: 1 / -1;
|
2022-05-17 11:06:15 -07:00
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
/*:global(td.hidden) {*/
|
|
|
|
|
/* display: none;*/
|
|
|
|
|
/*}*/
|
|
|
|
|
:global(tr.hidden) > td:not(.editor) {
|
|
|
|
|
display: none !important;
|
|
|
|
|
}
|
|
|
|
|
:global(tr.hidden) > td.editor {
|
|
|
|
|
display: block !important;
|
|
|
|
|
}
|
|
|
|
|
:global(div.hidden) {
|
|
|
|
|
display: none !important;
|
|
|
|
|
}
|
2022-06-13 07:42:26 -07:00
|
|
|
|
|
|
|
|
.editor2 {
|
|
|
|
|
grid-column: 1/-1;
|
|
|
|
|
}
|
2022-05-17 11:06:15 -07:00
|
|
|
</style>
|