rewrites utils as ts

This commit is contained in:
MinamiFunakoshiTR 2025-03-31 14:02:13 -07:00
parent d622258413
commit a5b93e5f53
Failed to extract signature
4 changed files with 65 additions and 80 deletions

View file

@ -48,10 +48,3 @@ export interface ScrollerStep {
*/ */
foregroundProps?: object; foregroundProps?: object;
} }
/** Datum type for data that goes into the Table component */
type TableDatum = {
[key: string]: unknown;
searchStr?: string;
};
export type TableData = TableDatum[];

View file

@ -30,7 +30,7 @@
}} }}
/> />
<!-- <Story <Story
name="Metadata" name="Metadata"
args={{ args={{
width: 'normal', width: 'normal',
@ -131,4 +131,4 @@
title: 'The Richest Women in the World', title: 'The Richest Women in the World',
source: 'Source: Forbes', source: 'Source: Forbes',
}} }}
/> --> />

View file

@ -1,5 +1,5 @@
<!-- @component `Table` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-text-elements-table--docs) --> <!-- @component `Table` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-text-elements-table--docs) -->
<script lang="ts"> <script lang="ts" generics="T">
/** Import local helpers */ /** Import local helpers */
import Block from '../Block/Block.svelte'; import Block from '../Block/Block.svelte';
import Pagination from './components/Pagination.svelte'; import Pagination from './components/Pagination.svelte';
@ -8,12 +8,9 @@
import SearchInput from '../SearchInput/SearchInput.svelte'; import SearchInput from '../SearchInput/SearchInput.svelte';
import { filterArray, paginateArray, getOptions } from './utils'; import { filterArray, paginateArray, getOptions } from './utils';
// Types interface Props<T extends Record<string, unknown>> {
import type { TableData } from '../@types/global'; /** Data for the table as an array. */
data: T[];
interface Props {
/** Data for the table as an array of objects. */
data: TableData;
/** A title that runs above the table. */ /** A title that runs above the table. */
title?: string; title?: string;
@ -65,7 +62,7 @@
dek, dek,
notes, notes,
source, source,
includedFields, includedFields = Object.keys(data[0]).filter((f) => f !== 'searchStr'),
truncated = false, truncated = false,
truncateLength = 5, truncateLength = 5,
paginated = false, paginated = false,
@ -75,35 +72,35 @@
filterField, filterField,
filterLabel, filterLabel,
sortable = false, sortable = false,
sortField, sortField = Object.keys(data[0])[0],
sortableFields, sortableFields = Object.keys(data[0]).filter((f) => f !== 'searchStr'),
sortDirection = $bindable('ascending'), sortDirection = $bindable('ascending'),
fieldFormatters = {}, fieldFormatters = {},
width = 'normal', width = 'normal',
id = '', id = '',
class: cls = '', class: cls = '',
}: Props = $props(); }: Props<Record<string, unknown>> = $props();
/** Derived variables */ /** Derived variables */
let includedFieldsDerived = $derived.by(() => { // let includedFieldsDerived = $derived.by(() => {
if (includedFields) return includedFields; // if (includedFields) return includedFields;
if (data.length > 0) // if (data.length > 0)
return Object.keys(data[0]).filter((f) => f !== 'searchStr'); // return Object.keys(data[0]).filter((f) => f !== 'searchStr');
return []; // return [];
}); // });
let sortableFieldsDerived = $derived.by(() => { // let sortableFieldsDerived = $derived.by(() => {
if (sortableFields) return sortableFields; // if (sortableFields) return sortableFields;
if (data.length > 0) // if (data.length > 0)
return Object.keys(data[0]).filter((f) => f !== 'searchStr'); // return Object.keys(data[0]).filter((f) => f !== 'searchStr');
return []; // return [];
}); // });
let sortFieldDerived = $derived.by(() => { // let sortFieldDerived = $derived.by(() => {
if (sortField) return sortField; // if (sortField) return sortField;
if (data.length > 0) return Object.keys(data[0])[0]; // if (data.length > 0) return Object.keys(data[0])[0];
return ''; // return '';
}); // });
/** Set truncate, filtering and pagination configuration */ /** Set truncate, filtering and pagination configuration */
let showAll = $state(false); let showAll = $state(false);
@ -178,20 +175,12 @@
return sortedData; return sortedData;
}); });
// $effect(() => {
// console.log('includedFieldsDerived', includedFieldsDerived);
// console.log('sortableFieldsDerived', sortableFieldsDerived);
// console.log('sortFieldDerived', sortFieldDerived);
// console.log('sortedData', sortedData);
// console.log('currentPageData', currentPageData);
// });
/** Add the `searchStr` field to data */ /** Add the `searchStr` field to data */
let searchableData = $derived.by(() => { let searchableData = $derived.by(() => {
return data.map((d) => { return data.map((d) => {
return { return {
...d, ...d,
searchStr: includedFieldsDerived searchStr: includedFields
.map((field) => d[field]) .map((field) => d[field])
.join(' ') .join(' ')
.toLowerCase(), .toLowerCase(),
@ -243,12 +232,11 @@
> >
<thead class="table--thead"> <thead class="table--thead">
<tr> <tr>
{#each includedFieldsDerived as field} {#each includedFields as field}
<th <th
scope="col" scope="col"
class="table--thead--th h4 pl-0 py-2 pr-2" class="table--thead--th h4 pl-0 py-2 pr-2"
class:sortable={sortable && class:sortable={sortable && sortableFields.includes(field)}
sortableFieldsDerived.includes(field)}
class:sort-ascending={sortable && class:sort-ascending={sortable &&
sortField === field && sortField === field &&
sortDirection === 'ascending'} sortDirection === 'ascending'}
@ -259,7 +247,7 @@
onclick={handleSort} onclick={handleSort}
> >
{field} {field}
{#if sortable && sortableFieldsDerived.includes(field)} {#if sortable && sortableFields.includes(field)}
<div class="table--thead--sortarrow fml-1 avoid-clicks"> <div class="table--thead--sortarrow fml-1 avoid-clicks">
<SortArrow {sortDirection} active={sortField === field} /> <SortArrow {sortDirection} active={sortField === field} />
</div> </div>
@ -271,7 +259,7 @@
<tbody class="table--tbody"> <tbody class="table--tbody">
{#each currentPageData as item, idx} {#each currentPageData as item, idx}
<tr data-row-index={idx}> <tr data-row-index={idx}>
{#each includedFieldsDerived as field} {#each includedFields as field}
<td <td
class="body-note pl-0 py-2 pr-2" class="body-note pl-0 py-2 pr-2"
data-row-index={idx} data-row-index={idx}
@ -285,7 +273,7 @@
{/each} {/each}
{#if searchable && searchText && currentPageData.length === 0} {#if searchable && searchText && currentPageData.length === 0}
<tr> <tr>
<td class="no-results" colspan={includedFieldsDerived.length}> <td class="no-results" colspan={includedFields.length}>
No results found for "{searchText}" No results found for "{searchText}"
</td> </td>
</tr> </tr>
@ -295,7 +283,7 @@
<tfoot class="table--tfoot"> <tfoot class="table--tfoot">
{#if notes} {#if notes}
<tr> <tr>
<td class="" colspan={includedFieldsDerived.length}> <td class="" colspan={includedFields.length}>
<div class="fmt-2"> <div class="fmt-2">
{@html notes} {@html notes}
</div> </div>
@ -304,7 +292,7 @@
{/if} {/if}
{#if source} {#if source}
<tr> <tr>
<td class="" colspan={includedFieldsDerived.length}> <td class="" colspan={includedFields.length}>
<div class="fmt-1"> <div class="fmt-1">
{@html source} {@html source}
</div> </div>
@ -343,10 +331,10 @@
<style lang="scss"> <style lang="scss">
@use '../../scss/mixins' as mixins; @use '../../scss/mixins' as mixins;
section.table { .table {
overflow-x: auto; overflow-x: auto;
} }
section.table table { .table table {
background-color: transparent; background-color: transparent;
border-collapse: separate; border-collapse: separate;
border-spacing: 0; border-spacing: 0;

View file

@ -1,56 +1,60 @@
import type { TableData } from '../@types/global'; export function filterArray<T extends { searchStr: string }>(
data: T[],
export function filterArray(
data: TableData,
searchText: string, searchText: string,
filterField: string | undefined, filterField: keyof T,
filterValue: string filterValue: T[keyof T]
) { ) {
if (searchText) { if (searchText) {
data = data.filter((item) => { data = data.filter((item) => {
return item.searchStr?.includes(searchText.toLowerCase()); return item.searchStr.includes(searchText.toLowerCase());
}); });
} }
if (filterField && filterValue) {
if (filterValue) {
data = data.filter((item) => { data = data.filter((item) => {
return item[filterField] === filterValue; return item[filterField] === filterValue;
}); });
} }
return data; return data;
} }
export function paginateArray( export function paginateArray<T>(
array: TableData, array: T[],
pageSize: number, pageSize: number,
pageNumber: number pageNumber: number
) { ) {
return array.slice((pageNumber - 1) * pageSize, pageNumber * pageSize); return array.slice((pageNumber - 1) * pageSize, pageNumber * pageSize);
} }
function uniqueAttr(array: TableData, attr: string) { /**
* We specify the output type here by adding `string` to the union because we want to explicitly define the output array as accepting strings.
*
* This is to get rid of the type error from `attrList.unshift('All')`
*/
function uniqueAttr<T>(array: T[], attr: keyof T): (T[keyof T] | string)[] {
return array.map((e) => e[attr]).filter(unique); return array.map((e) => e[attr]).filter(unique);
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any function unique<T>(value: T, index: number, array: T[]) {
function unique(value: any, index: number, array: TableData) {
console.log('unique', value, index, array);
return array.indexOf(value) === index; return array.indexOf(value) === index;
} }
export function getOptions(data: TableData, attr: string) { export function getOptions<T>(data: T[], attr: keyof T) {
// Get all the unique values in the provided field. Sort it. // Get all the unique values in the provided field. Sort it.
const attrList = uniqueAttr(data, attr).sort((a, b) => {
// Throw errors if a and b are not strings.
// a and b should be strings since they are keys of T.
if (typeof a !== 'string' || typeof b !== 'string') {
throw new Error(`Expected string, got ${typeof a} and ${typeof b}`);
}
// @TODO - check if a and b need to be typed and sorted for non-strings return a.localeCompare(b);
const attrList = uniqueAttr(data, attr).sort((a: string, b: string) => });
a.localeCompare(b)
);
console.log('attrList', attrList); // Tack 'All' at the start of `attrList`, making it the first option.
// Tack 'All' as the front as the first option.
attrList.unshift('All'); attrList.unshift('All');
// Convert the list into Option typed objects ready for our Select component // Convert the list into Option typed objects ready for our Select component
// eslint-disable-next-line @typescript-eslint/no-explicit-any return attrList.map((a) => ({ text: a, value: a }));
return attrList.map((a: any) => ({ text: a, value: a }));
} }