spreadsheet updates

This commit is contained in:
“chrisshank” 2024-11-08 14:41:35 -08:00
parent 5fb915387a
commit 0d5a632e33
2 changed files with 170 additions and 48 deletions

View File

@ -23,25 +23,118 @@
</head> </head>
<body> <body>
<fc-geometry x="50" y="50" height="300" width="500"> <fc-geometry x="50" y="50" height="300" width="500">
<s-table></s-table> <s-table>
<s-cell column="A" row="1" expression="1"></s-cell>
<s-cell column="B" row="1"></s-cell>
<s-cell column="C" row="1"></s-cell>
<s-cell column="D" row="1"></s-cell>
<s-cell column="E" row="1"></s-cell>
<s-cell column="F" row="1"></s-cell>
<s-cell column="G" row="1"></s-cell>
<s-cell column="H" row="1"></s-cell>
<s-cell column="I" row="1"></s-cell>
<s-cell column="J" row="1"></s-cell>
<s-cell column="A" row="2" expression="$A1 * 2"></s-cell>
<s-cell column="B" row="2"></s-cell>
<s-cell column="C" row="2"></s-cell>
<s-cell column="D" row="2"></s-cell>
<s-cell column="E" row="2"></s-cell>
<s-cell column="F" row="2"></s-cell>
<s-cell column="G" row="2"></s-cell>
<s-cell column="H" row="2"></s-cell>
<s-cell column="I" row="2"></s-cell>
<s-cell column="J" row="2"></s-cell>
<s-cell column="A" row="3"></s-cell>
<s-cell column="B" row="3"></s-cell>
<s-cell column="C" row="3"></s-cell>
<s-cell column="D" row="3"></s-cell>
<s-cell column="E" row="3"></s-cell>
<s-cell column="F" row="3"></s-cell>
<s-cell column="G" row="3"></s-cell>
<s-cell column="H" row="3"></s-cell>
<s-cell column="I" row="3"></s-cell>
<s-cell column="J" row="3"></s-cell>
<s-cell column="A" row="4"></s-cell>
<s-cell column="B" row="4"></s-cell>
<s-cell column="C" row="4"></s-cell>
<s-cell column="D" row="4"></s-cell>
<s-cell column="E" row="4"></s-cell>
<s-cell column="F" row="4"></s-cell>
<s-cell column="G" row="4"></s-cell>
<s-cell column="H" row="4"></s-cell>
<s-cell column="I" row="4"></s-cell>
<s-cell column="J" row="4"></s-cell>
<s-cell column="A" row="5"></s-cell>
<s-cell column="B" row="5"></s-cell>
<s-cell column="C" row="5"></s-cell>
<s-cell column="D" row="5"></s-cell>
<s-cell column="E" row="5"></s-cell>
<s-cell column="F" row="5"></s-cell>
<s-cell column="G" row="5"></s-cell>
<s-cell column="H" row="5"></s-cell>
<s-cell column="I" row="5"></s-cell>
<s-cell column="J" row="5"></s-cell>
<s-cell column="A" row="6"></s-cell>
<s-cell column="B" row="6"></s-cell>
<s-cell column="C" row="6"></s-cell>
<s-cell column="D" row="6"></s-cell>
<s-cell column="E" row="6"></s-cell>
<s-cell column="F" row="6"></s-cell>
<s-cell column="G" row="6"></s-cell>
<s-cell column="H" row="6"></s-cell>
<s-cell column="I" row="6"></s-cell>
<s-cell column="J" row="6"></s-cell>
<s-cell column="A" row="7"></s-cell>
<s-cell column="B" row="7"></s-cell>
<s-cell column="C" row="7"></s-cell>
<s-cell column="D" row="7"></s-cell>
<s-cell column="E" row="7"></s-cell>
<s-cell column="F" row="7"></s-cell>
<s-cell column="G" row="7"></s-cell>
<s-cell column="H" row="7"></s-cell>
<s-cell column="I" row="7"></s-cell>
<s-cell column="J" row="7"></s-cell>
<s-cell column="A" row="8"></s-cell>
<s-cell column="B" row="8"></s-cell>
<s-cell column="C" row="8"></s-cell>
<s-cell column="D" row="8"></s-cell>
<s-cell column="E" row="8"></s-cell>
<s-cell column="F" row="8"></s-cell>
<s-cell column="G" row="8"></s-cell>
<s-cell column="H" row="8"></s-cell>
<s-cell column="I" row="8"></s-cell>
<s-cell column="J" row="8"></s-cell>
<s-cell column="A" row="9"></s-cell>
<s-cell column="B" row="9"></s-cell>
<s-cell column="C" row="9"></s-cell>
<s-cell column="D" row="9"></s-cell>
<s-cell column="E" row="9"></s-cell>
<s-cell column="F" row="9"></s-cell>
<s-cell column="G" row="9"></s-cell>
<s-cell column="H" row="9"></s-cell>
<s-cell column="I" row="9"></s-cell>
<s-cell column="J" row="9"></s-cell>
<s-cell column="A" row="10"></s-cell>
<s-cell column="B" row="10"></s-cell>
<s-cell column="C" row="10"></s-cell>
<s-cell column="D" row="10"></s-cell>
<s-cell column="E" row="10"></s-cell>
<s-cell column="F" row="10"></s-cell>
<s-cell column="G" row="10"></s-cell>
<s-cell column="H" row="10"></s-cell>
<s-cell column="I" row="10"></s-cell>
<s-cell column="J" row="10"></s-cell>
</s-table>
</fc-geometry> </fc-geometry>
<script type="module"> <script type="module">
import { FolkGeometry } from '../src/canvas/fc-geometry.ts'; import { FolkGeometry } from '../src/canvas/fc-geometry.ts';
import { import { SpreadsheetTable, SpreadsheetHeader, SpreadsheetCell } from '../src/spreadsheet/spreadsheet.ts';
SpreadsheetTable,
SpreadsheetHeader,
SpreadsheetCell,
} from '../src/spreadsheet/spreadsheet.ts';
FolkGeometry.register(); FolkGeometry.register();
SpreadsheetTable.register(); SpreadsheetTable.register();
SpreadsheetHeader.register(); SpreadsheetHeader.register();
SpreadsheetCell.register(); SpreadsheetCell.register();
document.querySelector(`s-cell[column="A"][row="1"]`).expression = '1';
document.querySelector(`s-cell[column="A"][row="2"]`).expression =
'$A1 * 2';
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,10 +1,14 @@
const styles = new CSSStyleSheet(); const styles = new CSSStyleSheet();
// hardcoded column and row numbers
styles.replaceSync(` styles.replaceSync(`
:host { :host {
--column-number: 26; --column-number: 10;
--row-number: 100; --row-number: 10;
--cell-height: 1.75rem; --cell-height: 1.75rem;
--cell-width: 100px; --cell-width: 100px;
--border-color: #e1e1e1;
border: solid 1px var(--border-color);
box-sizing: border-box;
display: grid; display: grid;
font-family: monospace; font-family: monospace;
grid-template-columns: 50px repeat(var(--column-number), var(--cell-width)); grid-template-columns: 50px repeat(var(--column-number), var(--cell-width));
@ -52,7 +56,6 @@ s-rows {
s-header { s-header {
background-color: #f8f9fa; background-color: #f8f9fa;
border: 1px solid #e1e1e1;
display: flex; display: flex;
padding: 0.125rem 0.5rem; padding: 0.125rem 0.5rem;
align-items: center; align-items: center;
@ -74,17 +77,21 @@ s-header {
} }
s-body { s-body {
background-color: rgb(255, 255, 255);
display: grid; display: grid;
grid-column: 2 / -1; grid-column: 2 / -1;
grid-row: 2 / -1; grid-row: 2 / -1;
grid-template-columns: subgrid; grid-template-columns: subgrid;
grid-template-rows: subgrid; grid-template-rows: subgrid;
}
s-columns, s-rows, s-body {
background-color: var(--border-color);
gap: 1px;
} }
::slotted(s-cell) { ::slotted(s-cell) {
align-items: center; align-items: center;
border: 0.5px solid #e1e1e1; background-color: rgb(255, 255, 255);
display: flex; display: flex;
padding: 0.25rem; padding: 0.25rem;
justify-content: start; justify-content: start;
@ -96,8 +103,8 @@ s-body {
} }
::slotted(s-cell:focus) { ::slotted(s-cell:focus) {
border: 2px solid #1b73e8; outline: 2px solid #1b73e8;
outline: none; z-index: 2;
} }
`); `);
@ -116,6 +123,23 @@ function relativeColumnName(name: string, num: number) {
return alphabet[index + num]; return alphabet[index + num];
} }
export function templateCells(numberOfRows: number, numberOfColumns: number, expressions: Record<string, string>) {
const cells: string[] = [];
for (let i = 0; i < numberOfRows; i += 1) {
for (let j = 0; j < numberOfColumns; j += 1) {
const column = getColumnName(j);
const row = i + 1;
const expression = expressions[`${column}${row}`];
cells.push(
`<s-cell column="${column}" row="${row}" tabindex="0" ${
expression ? `expression="${expression}"` : ''
}></s-cell>`
);
}
}
return cells.join('\n');
}
export class SpreadsheetTable extends HTMLElement { export class SpreadsheetTable extends HTMLElement {
static tagName = 's-table'; static tagName = 's-table';
@ -139,17 +163,24 @@ export class SpreadsheetTable extends HTMLElement {
this.addEventListener('focusout', this); this.addEventListener('focusout', this);
this.#shadow.adoptedStyleSheets.push(styles); this.#shadow.adoptedStyleSheets.push(styles);
const columnHeaders = Array.from({ length: 26 }) }
connectedCallback() {
this.#shadow.textContent = '';
const cells = this.querySelector('s-cell');
const columnHeaders = Array.from({ length: 10 })
.map((_, i) => `<s-header column="${getColumnName(i)}">${getColumnName(i)}</s-header>`) .map((_, i) => `<s-header column="${getColumnName(i)}">${getColumnName(i)}</s-header>`)
.join(''); .join('');
const columnRows = Array.from({ length: 100 }) const rowHeaders = Array.from({ length: 10 })
.map((_, i) => `<s-header row="${i + 1}">${i + 1}</s-header>`) .map((_, i) => `<s-header row="${i + 1}">${i + 1}</s-header>`)
.join(''); .join('');
this.#shadow.innerHTML = ` this.#shadow.innerHTML = `
<s-header empty></s-header> <s-header empty></s-header>
<s-columns>${columnHeaders}</s-columns> <s-columns>${columnHeaders}</s-columns>
<s-rows>${columnRows}</s-rows> <s-rows>${rowHeaders}</s-rows>
<s-body><slot></slot></s-body> <s-body><slot></slot></s-body>
<textarea hidden></textarea> <textarea hidden></textarea>
`; `;
@ -165,18 +196,6 @@ export class SpreadsheetTable extends HTMLElement {
this.#range = range; this.#range = range;
} }
connectedCallback() {
let html = '';
for (let i = 0; i < 100; i += 1) {
for (let j = 0; j < 26; j += 1) {
html += `<s-cell column="${getColumnName(j)}" row="${i + 1}" tabindex="0"></s-cell>`;
}
}
this.innerHTML = html;
}
handleEvent(event: Event) { handleEvent(event: Event) {
switch (event.type) { switch (event.type) {
case 'keydown': { case 'keydown': {
@ -315,6 +334,10 @@ export class SpreadsheetCell extends HTMLElement {
connectedCallback() { connectedCallback() {
// this should run after all of the other cells have run // this should run after all of the other cells have run
this.expression = this.getAttribute('expression') || ''; this.expression = this.getAttribute('expression') || '';
if (this.tabIndex === -1) {
this.tabIndex = 0;
}
} }
get type() { get type() {
@ -342,8 +365,15 @@ export class SpreadsheetCell extends HTMLElement {
} }
#expression = ''; #expression = '';
#dependencies: SpreadsheetCell[] = [];
#dependencies: ReadonlyArray<SpreadsheetCell> = [];
get dependencies() {
return this.#dependencies;
}
#function = new Function(); #function = new Function();
get expression() { get expression() {
return this.#expression; return this.#expression;
} }
@ -361,12 +391,14 @@ export class SpreadsheetCell extends HTMLElement {
const argNames: string[] = expression.match(/\$[A-Z]+\d+/g) ?? []; const argNames: string[] = expression.match(/\$[A-Z]+\d+/g) ?? [];
this.#dependencies = argNames this.#dependencies = Object.freeze(
.map((dep) => { argNames
const [, column, row] = dep.split(/([A-Z]+)(\d+)/s); .map((dep) => {
return this.#getCell(column, row); const [, column, row] = dep.split(/([A-Z]+)(\d+)/s);
}) return this.#getCell(column, row);
.filter((cell) => cell !== null); })
.filter((cell) => cell !== null)
);
this.#dependencies.forEach((dep) => dep.addEventListener('propagate', this)); this.#dependencies.forEach((dep) => dep.addEventListener('propagate', this));
@ -375,9 +407,8 @@ export class SpreadsheetCell extends HTMLElement {
this.#evaluate(); this.#evaluate();
} }
// More generic parsing? #value: any;
#value = NaN; get value() {
get value(): number {
return this.#value; return this.#value;
} }
@ -408,12 +439,10 @@ export class SpreadsheetCell extends HTMLElement {
const value = this.#function.apply(null, args); const value = this.#function.apply(null, args);
if (typeof value === 'number' && !Number.isNaN(value)) { this.#value = value;
this.#value = value; this.textContent = value.toString();
this.textContent = value.toString(); this.dispatchEvent(new Event('propagate'));
this.dispatchEvent(new Event('propagate')); this.setAttribute('type', typeof value);
this.setAttribute('type', 'number');
}
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }