Vitral 0.2
Dados

Spreadsheet

Uma planilha com motor de fórmulas próprio e sem dependências: referências A1, sessenta e três funções e recálculo só do que mudou.

Importação

main.ts
import { Spreadsheet } from '@vitral/vue';

Padrão

Digite nela, arraste a alça no canto da seleção para preencher e veja os totais acompanharem. Comece uma fórmula — `=B2*C2` — e cada célula que ela lê fica contornada onde está.

A1
A
B
C
D
E
F
Item
Price
Qty
Total
Standing desk
£ 320,00
2
£ 640,00
Office chair
£ 95,50
4
£ 382,00
Monitor arm
£ 48,00
2
£ 96,00
Subtotal
£ 1.118,00
VAT
20%
£ 223,60
Due
£ 1.341,60
<script setup lang="ts">import { Spreadsheet } from '@vitral/vue';import { ref } from 'vue'; const invoice = ref<Record<string, string | number | boolean | null>>({    A1: 'Item',    B1: 'Price',    C1: 'Qty',    D1: 'Total',    A2: 'Standing desk',    B2: 320,    C2: 2,    D2: '=B2*C2',    A3: 'Office chair',    B3: 95.5,    C3: 4,    D3: '=B3*C3',    A4: 'Monitor arm',    B4: 48,    C4: 2,    D4: '=B4*C4',    A6: 'Subtotal',    D6: '=SUM(D2:D4)',    A7: 'VAT',    B7: '20%',    D7: '=D6*B7',    A8: 'Due',    D8: '=D6+D7'}); const formats = {    'B2:B4': { kind: 'currency', currency: 'GBP' } as const,    'D2:D8': { kind: 'currency', currency: 'GBP' } as const,    B7: { kind: 'percent' } as const};</script> <template>    <Spreadsheet v-model="invoice" :formats="formats" :rows="20" :columns="6" aria-label="Invoice" style="height: 22rem; width: 100%" /></template>

O que volta

`v-model` é o que foi digitado, indexado por A1 — as fórmulas são fórmulas, e não o resultado delas.

A1
A
B
C
D
E
Item
Price
Qty
Total
Standing desk
£ 320,00
2
£ 640,00
Office chair
£ 95,50
4
£ 382,00
Monitor arm
£ 48,00
2
£ 96,00
Subtotal
£ 1.118,00
VAT
20%
£ 223,60
Due
£ 1.341,60

What is kept for D8 is =D6+D7, and it reads £ 1.341,60 — edit a price above and both follow.

<script setup lang="ts">import { Spreadsheet, StackPanel } from '@vitral/vue';import { nextTick, onMounted, ref } from 'vue'; const invoice = ref<Record<string, string | number | boolean | null>>({    A1: 'Item',    B1: 'Price',    C1: 'Qty',    D1: 'Total',    A2: 'Standing desk',    B2: 320,    C2: 2,    D2: '=B2*C2',    A3: 'Office chair',    B3: 95.5,    C3: 4,    D3: '=B3*C3',    A4: 'Monitor arm',    B4: 48,    C4: 2,    D4: '=B4*C4',    A6: 'Subtotal',    D6: '=SUM(D2:D4)',    A7: 'VAT',    B7: '20%',    D7: '=D6*B7',    A8: 'Due',    D8: '=D6+D7'}); const formats = {    'B2:B4': { kind: 'currency', currency: 'GBP' } as const,    'D2:D8': { kind: 'currency', currency: 'GBP' } as const,    B7: { kind: 'percent' } as const}; // `v-model` carries what was typed; what a cell reads as comes from the sheet// the component exposes.const sheet = ref<InstanceType<typeof Spreadsheet> | null>(null);const due = ref('');async function readDue() {    await nextTick();    due.value = sheet.value?.sheet()?.display({ row: 7, col: 3 }) ?? '';}onMounted(readDue);</script> <template>    <StackPanel spacing="1rem" style="width: 100%">        <Spreadsheet ref="sheet" v-model="invoice" :formats="formats" :rows="10" :columns="5" aria-label="Invoice" style="height: 16rem; width: 100%" @change="readDue" />        <p>            What is kept for <code>D8</code> is <code>{{ invoice.D8 }}</code            >, and it reads <strong>{{ due }}</strong> — edit a price above and both follow.        </p>    </StackPanel></template>

Fórmulas

Sessenta e três: SUM, AVERAGE, MIN, MAX, COUNT e COUNTIF, IF e IFERROR, as de texto, INDEX, MATCH e VLOOKUP, e as datas como os dias em que toda planilha conta.

A1
A
B
C
D
Name
Score
Grade
Ada
92
A
Alan
84
B
Grace
78
C
Average
84.7
Top
92
Above 80
2
<script setup lang="ts">import { Spreadsheet } from '@vitral/vue';import { ref } from 'vue'; const scores = ref<Record<string, string | number | boolean | null>>({    A1: 'Name',    B1: 'Score',    C1: 'Grade',    A2: 'Ada',    B2: 92,    C2: '=IF(B2>=90,"A",IF(B2>=80,"B","C"))',    A3: 'Alan',    B3: 84,    C3: '=IF(B3>=90,"A",IF(B3>=80,"B","C"))',    A4: 'Grace',    B4: 78,    C4: '=IF(B4>=90,"A",IF(B4>=80,"B","C"))',    A6: 'Average',    B6: '=ROUND(AVERAGE(B2:B4),1)',    A7: 'Top',    B7: '=MAX(B2:B4)',    A8: 'Above 80',    B8: '=COUNTIF(B2:B4,">=80")'});</script> <template>    <Spreadsheet v-model="scores" :rows="12" :columns="4" aria-label="Scores" style="height: 16rem; width: 100%" /></template>

Quando uma fórmula não tem resposta

Um erro é um valor: ele atravessa a aritmética, e `IFERROR` o captura.

A1
A
B
C
#DIV/0!
divided by nothing
#NAME?
no such function
#CIRCULAR!
reads itself
#N/A
not in the list
caught
caught instead
<script setup lang="ts">import { Spreadsheet } from '@vitral/vue';import { ref } from 'vue'; const errors = ref<Record<string, string | number | boolean | null>>({    A1: '=1/0',    A2: '=NOPE(1)',    A3: '=A3+1',    A4: '=VLOOKUP("nothing",B1:C2,2)',    A5: '=IFERROR(1/0,"caught")',    B1: 'divided by nothing',    B2: 'no such function',    B3: 'reads itself',    B4: 'not in the list',    B5: 'caught instead'});</script> <template>    <Spreadsheet v-model="errors" :rows="8" :columns="3" :column-widths="{ B: 180 }" aria-label="Errors" style="height: 12rem; width: 100%" /></template>

A barra de ferramentas

`toolbar` recebe grupos de nomes de itens, ou `false` para nenhum; o slot `toolbar` substitui a barra por peças suas. Toda ferramenta age sobre o retângulo selecionado, e as que ficam pressionadas refletem a célula onde está o cursor.

A1
A
B
C
D
E
Month
Budget
Spent
Left
Used
January
4000
3120
880
0.78
February
4000
4310
-310
1.0775
March
4500
3980
520
0.884444444444
A
B
C
D
Left
Centre
Right
Pick a cell and press a tool.
<script setup lang="ts">import { Spreadsheet, StackPanel } from '@vitral/vue';import { ref } from 'vue'; const budget = ref<Record<string, string | number | boolean | null>>({    A1: 'Month',    B1: 'Budget',    C1: 'Spent',    D1: 'Left',    E1: 'Used',    A2: 'January',    B2: 4000,    C2: 3120,    D2: '=B2-C2',    E2: '=C2/B2',    A3: 'February',    B3: 4000,    C3: 4310,    D3: '=B3-C3',    E3: '=C3/B3',    A4: 'March',    B4: 4500,    C4: 3980,    D4: '=B4-C4',    E4: '=C4/B4'}); const minimal = ref<Record<string, string | number | boolean | null>>({    A1: 'Left',    B1: 'Centre',    C1: 'Right',    A2: 'Pick a cell and press a tool.'});</script> <template>    <StackPanel spacing="1rem" style="width: 100%">        <Spreadsheet v-model="budget" :rows="10" :columns="5" aria-label="Toolbar example" style="height: 14rem; width: 100%" />        <Spreadsheet            v-model="minimal"            :toolbar="[['bold', 'italic'], ['alignLeft', 'alignCenter', 'alignRight']]"            :formula-bar="false"            :rows="6"            :columns="4"            aria-label="Fewer tools"            style="height: 10rem; width: 100%"        />    </StackPanel></template>

Somente leitura

`readonly` mantém o teclado e a seleção, e tira a digitação e a alça de preenchimento.

A
B
C
D
Plan
Seats
Monthly
Team
12
108
Business
40
280
<script setup lang="ts">import { Spreadsheet } from '@vitral/vue';import { ref } from 'vue'; const terms = ref<Record<string, string | number | boolean | null>>({    A1: 'Plan',    B1: 'Seats',    C1: 'Monthly',    A2: 'Team',    B2: 12,    C2: '=B2*9',    A3: 'Business',    B3: 40,    C3: '=B3*7'});</script> <template>    <Spreadsheet v-model="terms" readonly :formula-bar="false" :rows="6" :columns="4" aria-label="Terms" style="height: 10rem; width: 100%" /></template>

Onde está o teclado

`selection-change` diz qual célula, ou qual retângulo, toda vez que ela se move.

A1
A
B
C
D
Plan
Seats
Monthly
Team
12
108
Business
40
280

Selected: A1

<script setup lang="ts">import { Spreadsheet, StackPanel } from '@vitral/vue';import { ref } from 'vue'; const plans = ref<Record<string, string | number | boolean | null>>({    A1: 'Plan',    B1: 'Seats',    C1: 'Monthly',    A2: 'Team',    B2: 12,    C2: '=B2*9',    A3: 'Business',    B3: 40,    C3: '=B3*7'}); const selection = ref('A1');</script> <template>    <StackPanel spacing="1rem" style="width: 100%">        <Spreadsheet            v-model="plans"            :rows="8"            :columns="4"            aria-label="Selection example"            style="height: 12rem; width: 100%"            @selection-change="selection = $event.address"        />        <p>            Selected: <code>{{ selection }}</code>        </p>    </StackPanel></template>

API

Lido de packages/vue/src/components/Spreadsheet/types.ts, então diz o que o componente aceita de fato.

Props

NomeTipoDescrição
modelValueRecord<string, string | number | boolean | null>As células, com chave em A1: `{ A1: 'Sales', B2: 42, B3: '=B2*2' }`. O que se guarda é o que foi digitado, então uma fórmula sobrevive à ida e volta.
rowsnumberQuantas linhas e colunas a grade oferece.
columnsnumber—
formatsRecord<string, CellFormatLike>Formatos, por A1 ou por um retângulo: `{ 'B2:B9': { kind: 'currency' } }`.
columnWidthsRecord<string, number>Larguras em pixels por letra de coluna; o redimensionamento feito pelo leitor também vai aqui.
rowHeightsRecord<number, number>Alturas em pixels por número de linha, contado como uma pessoa conta.
toolbarboolean | SpreadsheetToolbarItemLike[][]A barra de ferramentas sobre a grade: `false` para nenhuma, ou grupos de nomes de itens. O slot `toolbar` substitui a barra por partes suas.
formulaBarbooleanA barra sobre a grade que mostra o endereço e a fórmula. Ligada por padrão.
currencystringA moeda em que uma célula formatada como dinheiro é escrita. O padrão vem do locale.
readonlybooleanNada pode ser digitado nela.
ariaLabelstringDá nome à grade, quando nenhum rótulo aponta para ela.

Mais pt, dt e unstyled de BaseProps; veja pass-through e modo sem estilo.

Slots

NomeProps do slotDescrição
toolbar—Substitui a barra de ferramentas sobre a grade.