Files
rodydavis b9e42c3ef4
Build and Deploy / build-and-deploy (push) Has been cancelled
fix(lit-components): fix rendering and interaction issues in force-graph, html-table, and draggable-dom
- lit-force-graph: Refactored for instance reuse, better lifecycle management, and added dynamic imports for AR/VR modes.
- lit-html-table: Improved visual design, readability of headers, and fixed reactivity in editable mode.
- lit-draggable-dom: Fixed clipping issues by switching to relative/absolute positioning and resolved a panning speed bug caused by CSS variable inheritance.
2026-05-17 00:41:14 -07:00

174 lines
4.7 KiB
TypeScript

import { html, css, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
type ObjectData = { [key: string]: any };
@customElement("lit-html-table")
export class LitHtmlTable extends LitElement {
@property() src = "";
@property({ type: Boolean }) editable = false;
values?: ObjectData[];
static styles = css`
:host {
display: block;
width: 100%;
overflow-x: auto;
font-family: var(--table-font-family, sans-serif);
background-color: var(--table-background-color, #ffffff);
color: var(--table-text-color, #1f2937);
border-radius: var(--table-border-radius, 8px);
box-shadow: var(--table-box-shadow, 0 1px 3px 0 rgba(0, 0, 0, 0.1));
}
table {
width: 100%;
border-collapse: collapse;
text-align: left;
}
thead {
background-color: var(--table-header-bg, #f9fafb);
border-bottom: 2px solid var(--table-border-color, #e5e7eb);
}
th {
padding: var(--table-padding, 12px 16px);
font-weight: 600;
text-transform: uppercase;
font-size: 0.75rem;
letter-spacing: 0.05em;
color: var(--table-header-color, #4b5563);
}
tr {
border-bottom: 1px solid var(--table-border-color, #e5e7eb);
transition: background-color 0.2s;
}
tr:last-child {
border-bottom: none;
}
tbody tr:hover {
background-color: var(--table-row-hover-bg, #f3f4f6);
}
td {
padding: var(--table-padding, 12px 16px);
vertical-align: middle;
font-size: 0.875rem;
}
td > input {
width: 100%;
padding: 8px;
border: 1px solid transparent;
border-radius: 4px;
background: transparent;
font-family: inherit;
font-size: inherit;
color: inherit;
transition: all 0.2s;
}
td > input:focus {
outline: none;
border-color: var(--table-focus-color, #6366f1);
background-color: #ffffff;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}
.loading, .empty {
padding: 32px;
text-align: center;
color: var(--table-text-secondary, #6b7280);
font-style: italic;
}
`;
render() {
// Check if data is loaded
if (!this.values) {
return html`<div class="loading"><slot name="loading">Loading data...</slot></div>`;
}
// Check if items are not empty
if (this.values.length === 0) {
return html`<div class="empty"><slot name="empty">No items found.</slot></div>`;
}
// Convert JSON to HTML Table
return html`
<table>
<thead>
<tr>
${Object.keys(this.values[0]).map((key) => {
const name = key.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase());
return html`<th>
<slot name="${key}">${name}</slot>
</th>`;
})}
</tr>
</thead>
<tbody>
${this.values.map((item, index) => {
return html`
<tr>
${Object.entries(item).map(([key, value]) => {
return html`<td>
${this.editable
? html`<input
.value="${value}"
type="text"
@input=${(e: any) => {
const newValue = e.target.value;
const current = { ...this.values![index] };
current[key] = newValue;
this.values![index] = current;
this.requestUpdate();
this.dispatchEvent(
new CustomEvent("input-cell", {
detail: {
index: index,
data: current,
},
})
);
}}
/>`
: html`${value}`}
</td>`;
})}
</tr>
`;
})}
</tbody>
</table>
`;
}
async firstUpdated() {
await this.fetchData();
}
async fetchData() {
if (this.values && this.values.length > 0) return;
let _data: any;
if (this.src.length > 0) {
_data = await fetch(this.src).then((res) => res.json());
} else {
const elem = this.parentElement?.querySelector(
'script[type="application/json"]'
) as HTMLScriptElement;
if (elem) _data = JSON.parse(elem.innerHTML);
}
_data ??= [];
this.values = this.transform(_data);
this.requestUpdate();
}
transform(data: any) {
return data;
}
}