diff --git a/packages/components/grid/src/stories/pagination.stories.ts b/packages/components/grid/src/stories/pagination.stories.ts index 2f4e30b27..388a435f6 100644 --- a/packages/components/grid/src/stories/pagination.stories.ts +++ b/packages/components/grid/src/stories/pagination.stories.ts @@ -3,14 +3,12 @@ import '@sl-design-system/button-bar/register.js'; import '@sl-design-system/paginator/register.js'; import { type Person, getPeople } from '@sl-design-system/example-data'; import { ArrayDataSource } from '@sl-design-system/shared'; -import { type TextField } from '@sl-design-system/text-field'; import '@sl-design-system/text-field/register.js'; import { type Meta, type StoryObj } from '@storybook/web-components'; -import { LitElement, type TemplateResult, html } from 'lit'; -import { state } from 'lit/decorators.js'; -import { repeat } from 'lit/directives/repeat.js'; +import { html } from 'lit'; import '../../register.js'; import {PageSize, Paginator, VisibleItems} from "@sl-design-system/paginator"; +import { Grid } from '../grid.js'; type Story = StoryObj; @@ -25,74 +23,98 @@ export default { } satisfies Meta; export const Basic: Story = { - render: (_, { loaded: { people } }) => html` - - - - - - - - ` -}; + render: (_, { loaded: { people } }) => { + const pageSizes = [5, 10, 15]; + let activePage = 1, + itemsPerPage = pageSizes[1], + startIndex = (activePage - 1) * itemsPerPage, + endIndex = startIndex + itemsPerPage; + + setTimeout(() => { + const paginator = document.querySelector('sl-paginator') as Paginator, + pageSize = document.querySelector('sl-page-size') as PageSize, + visibleItems = document.querySelector('sl-visible-items') as VisibleItems, + grid = document.querySelector('sl-grid') as Grid; + + paginator?.addEventListener('sl-page-change', event => { + visibleItems.activePage = event.detail; + activePage = event.detail; + startIndex = (event.detail - 1) * itemsPerPage; + endIndex = startIndex + itemsPerPage; + grid.items = people.slice(startIndex, endIndex); + }); -export const Filtered: Story = { - render: (_, { loaded: { people } }) => html` - - - - - - - - ` + pageSize?.addEventListener('sl-page-size-change', event => { + paginator.itemsPerPage = event.detail; + visibleItems.itemsPerPage = event.detail; + itemsPerPage = event.detail; + }); + }); + + return html` + + + + + + + + + + ` + } }; -export const PaginatedDataSource: Story = { +export const PaginatedDataSourceWithFilter: Story = { render: (_, { loaded: { people } }) => { - const pageSizes = [5, 10, 15, 20]; - const dataSource = new ArrayDataSource(people as Person[]); - let total = dataSource.paginatedItems.length; //dataSource.items.length; - dataSource.paginate(10, 1); - // dataSource.items = dataSource.paginatedItems; + const pageSizes = [5, 10, 15, 20], + dataSource = new ArrayDataSource(people as Person[]); + + let total = dataSource.paginatedItems.length; + dataSource.paginate(1, 10); setTimeout(() => { - const paginator = document.querySelector('sl-paginator') as Paginator; - const pageSize = document.querySelector('sl-page-size') as PageSize; - const visibleItems = document.querySelector('sl-visible-items') as VisibleItems; - // const pageSizes = [5, 10, 15]; - // const dataSource = new ArrayDataSource(people as Person[]); - // const total = dataSource.items.length; - // dataSource.paginate(10, 1); - console.log('dataSource in the story - paginated data', dataSource, total); - console.log('paginator with grid', paginator, pageSize, visibleItems, paginator.activePage); - // dataSource.addFilter('filter-profession', 'profession', 'Endo'); - // dataSource.addFilter('filter-status', 'status', 'Available'); - // dataSource.addFilter('filter-membership', 'membership', ['Regular', 'Premium']); + const paginator = document.querySelector('sl-paginator') as Paginator, + pageSize = document.querySelector('sl-page-size') as PageSize, + visibleItems = document.querySelector('sl-visible-items') as VisibleItems, + grid = document.querySelector('sl-grid') as Grid; paginator?.addEventListener('sl-page-change', event => { - console.log('sl-page-change event', event, event.detail, paginator.itemsPerPage); - // pageSize.activePage = event.detail; - dataSource.paginate(paginator.itemsPerPage ?? pageSizes[0], event.detail); + dataSource.paginate(event.detail, paginator.itemsPerPage ?? pageSizes[0]); visibleItems.activePage = event.detail; - // visibleItems.requestUpdate(); - // - // selectedTabIndex = event.detail; }); + pageSize?.addEventListener('sl-page-size-change', event => { - console.log('sl-page-size-change event', event, event.detail); paginator.itemsPerPage = event.detail; visibleItems.itemsPerPage = event.detail; - dataSource.paginate(event.detail, paginator.activePage); - // paginator.requestUpdate(); - // - // selectedTabIndex = event.detail; + dataSource.paginate(paginator.activePage, event.detail); + }); + + dataSource?.addEventListener('sl-update', () => { + paginator.total = dataSource.paginatedItems.length; + visibleItems.total = dataSource.paginatedItems.length; }); - dataSource?.addEventListener('sl-update', event => { - console.log('sl-update event', event, dataSource.items.length, dataSource.items, dataSource.filteredItems); - paginator.total = dataSource.paginatedItems.length; //dataSource.items.length; - visibleItems.total = dataSource.paginatedItems.length; //dataSource.items.length; + grid?.addEventListener('sl-filter-value-change', () => { + // go back to the first page on filter change + paginator.activePage = 1; }); }); @@ -126,160 +148,80 @@ export const PaginatedDataSource: Story = { } }; -export const FilteringWithSelection: Story = { - render: (_, { loaded: { people } }) => html` - - - - - - - - ` -}; - -export const Custom: Story = { +export const PaginatedDataSourceWithSorter: Story = { render: (_, { loaded: { people } }) => { - const filter = (person: Person): boolean => { - return person.profession === 'Gastroenterologist'; - }; + const sorter = (a: Person, b: Person): number => { + const lastNameCmp = a.lastName.localeCompare(b.lastName); - const dataSource = new ArrayDataSource(people as Person[]); - dataSource.addFilter('custom', filter); - - return html` -

This grid filters people on the "Gastroenterologist" profession via a custom filter on the data directly.

- - - - - - - `; - } -}; - -export const EmptyValues: Story = { - render: () => { - const items = [ - { key: 'Foo', value: 'foo' }, - { key: '"0"', value: '0' }, - { key: '0', value: 0 }, - { key: 'Spaces', value: ' ' }, - { key: 'Blank', value: '' }, - { key: 'Null', value: null }, - { key: 'Undefined', value: undefined } - ]; - - return html` - - - - - `; - } -}; + if (lastNameCmp === 0) { + return a.firstName.localeCompare(b.firstName); + } else { + return lastNameCmp; + } + }; -export const Grouped: Story = { - render: (_, { loaded: { people } }) => { const dataSource = new ArrayDataSource(people as Person[]); - dataSource.setGroupBy('membership'); + dataSource.setSort('custom', sorter, 'asc'); - return html` - - - - - - - - `; - } -}; + const pageSizes = [10, 15, 20]; + let total = dataSource.paginatedItems.length; + dataSource.paginate(1, 10); -export const OutsideGrid: Story = { - render: (_, { loaded: { people } }) => { - const dataSource = new ArrayDataSource(people as Person[]); + setTimeout(() => { + const paginator = document.querySelector('sl-paginator') as Paginator, + pageSize = document.querySelector('sl-page-size') as PageSize, + visibleItems = document.querySelector('sl-visible-items') as VisibleItems, + grid = document.querySelector('sl-grid') as Grid; - const onInput = ({ target }: Event & { target: TextField }): void => { - const value = target.value?.toString().trim() ?? ''; + paginator?.addEventListener('sl-page-change', event => { + dataSource.paginate(event.detail, paginator.itemsPerPage ?? pageSizes[0]); + visibleItems.activePage = event.detail; + }); - if (value) { - const regex = new RegExp(value, 'i'); + pageSize?.addEventListener('sl-page-size-change', event => { + paginator.itemsPerPage = event.detail; + visibleItems.itemsPerPage = event.detail; + dataSource.paginate(paginator.activePage, event.detail); + }); - dataSource.addFilter('search', ({ firstName, lastName, email, profession }) => { - return regex.test(firstName) || regex.test(lastName) || regex.test(email) || regex.test(profession); - }); - } else { - dataSource.removeFilter('search'); - } + dataSource?.addEventListener('sl-update', () => { + paginator.total = dataSource.paginatedItems.length; + visibleItems.total = dataSource.paginatedItems.length; + }); - dataSource.update(); - }; + grid?.addEventListener('sl-sort-direction-change', () => { + // go back to the first page on filter change + paginator.activePage = 1; + }); + }); return html` - - +

This grid sorts people by last name, then first name, via a custom sorter on the data directly.

- - - + + - + `; } }; -export const ReorderColumns: Story = { - render: (_, { loaded: { people } }) => { - class GridReorderExample extends LitElement { - @state() - columns = [ - { path: 'firstName' }, - { path: 'lastName' }, - { path: 'profession', type: 'filter' }, - { path: 'status', type: 'filter' }, - { path: 'membership', type: 'filter' } - ]; - - override render(): TemplateResult { - return html` - - Reorder columns - - - ${repeat( - this.columns, - column => column.path, - column => { - if (column.type === 'filter') { - return html``; - } else { - return html``; - } - } - )} - - `; - } - - onClick(): void { - this.columns = [...this.columns.sort(() => Math.random() - 0.5)]; - } - } - try { - customElements.define('grid-reorder-example', GridReorderExample); - } catch { - /* empty */ - } - - return html``; - } -}; diff --git a/packages/components/paginator/src/page-size.scss b/packages/components/paginator/src/page-size.scss index 2984cf072..5631f4bb7 100644 --- a/packages/components/paginator/src/page-size.scss +++ b/packages/components/paginator/src/page-size.scss @@ -1,7 +1,5 @@ :host { display: block; - //padding-block: 8px; - //padding-inline: 0; padding: 0; } diff --git a/packages/components/paginator/src/paginator.stories.ts b/packages/components/paginator/src/paginator.stories.ts index 7368f8746..7cda59352 100644 --- a/packages/components/paginator/src/paginator.stories.ts +++ b/packages/components/paginator/src/paginator.stories.ts @@ -104,7 +104,23 @@ export const Mobile: Story = { }; export const ItemsPerPage: Story = { - render: () => html` + args: { + ...Basic.args, + total: 100 + }, + render: ({pageSizes, itemsPerPage}) => { + let pageSize = document.querySelector('sl-page-size') as PageSize; + setTimeout(() => { + pageSize = document.querySelector('sl-page-size') as PageSize; + + pageSize?.addEventListener('sl-page-size-change', event => { + console.log('sl-page-size-change event', event, event.detail); + itemsPerPage = event.detail; + const pEl = document.querySelector('p.info'); + console.log('pEl', pEl); + }); + }) + return html`

TODO...

- ` + +

There will be shown ${itemsPerPage} items per page. ${pageSize?.itemsPerPage}

+ `} }; diff --git a/packages/components/paginator/src/paginator.ts b/packages/components/paginator/src/paginator.ts index debd8b0b7..24909637b 100644 --- a/packages/components/paginator/src/paginator.ts +++ b/packages/components/paginator/src/paginator.ts @@ -438,12 +438,14 @@ export class Paginator extends ScopedElementsMixin(LitElement) { const buttonPrev = this.renderRoot.querySelector('sl-button.prev') as Button; const buttonNext = this.renderRoot.querySelector('sl-button.next') as Button; const selectWrapper = this.renderRoot.querySelector('.select-wrapper') as HTMLDivElement; + const ulElement = this.renderRoot.querySelector('ul') as HTMLUListElement; // reset display to check the width pagesWrapper.style.display = ''; buttonPrev.style.display = ''; buttonNext.style.display = ''; selectWrapper.style.display = 'none'; + ulElement.removeAttribute('mobile'); this.requestUpdate(); console.log( @@ -580,6 +582,7 @@ export class Paginator extends ScopedElementsMixin(LitElement) { buttonPrev.style.display = ''; buttonNext.style.display = ''; selectWrapper.style.display = 'none'; + ulElement.removeAttribute('mobile'); // const containerWidth = pagesWrapper.clientWidth; @@ -615,6 +618,7 @@ export class Paginator extends ScopedElementsMixin(LitElement) { buttonPrev.style.display = ''; buttonNext.style.display = ''; selectWrapper.style.display = 'none'; + ulElement.removeAttribute('mobile'); this.requestUpdate(); @@ -806,6 +810,7 @@ export class Paginator extends ScopedElementsMixin(LitElement) { buttonPrev.style.display = 'none'; buttonNext.style.display = 'none'; selectWrapper.style.display = ''; + ulElement.setAttribute('mobile', ''); // TODO: maybe select-wrapper display block? this.requestUpdate(); return; diff --git a/packages/components/paginator/src/visible-items.scss b/packages/components/paginator/src/visible-items.scss index e6b5b2d34..526979499 100644 --- a/packages/components/paginator/src/visible-items.scss +++ b/packages/components/paginator/src/visible-items.scss @@ -1,7 +1,5 @@ :host { display: block; - //padding-block: 8px; - //padding-inline: 0; padding: 0; text-wrap: nowrap; } diff --git a/packages/components/shared/src/data-source/array-data-source.ts b/packages/components/shared/src/data-source/array-data-source.ts index 78fc52700..95f5a8bd2 100644 --- a/packages/components/shared/src/data-source/array-data-source.ts +++ b/packages/components/shared/src/data-source/array-data-source.ts @@ -46,6 +46,7 @@ export class ArrayDataSource extends DataSource { // Filter the items for (const filter of this.filters.values()) { + console.log('in filters update', filter, this.filteredItems); if ('filter' in filter && filter.filter) { items = items.filter(filter.filter); } else if ('path' in filter && filter.path) { @@ -115,27 +116,36 @@ export class ArrayDataSource extends DataSource { }); } + // const previouslyFilteredItems1 = items; + // if (this.paginatedItems) { // items = this.paginatedItems; // } - console.log('items before paginated, but should be filtered if filtered', items); + console.log('items before paginated, but should be filtered if filtered', items, this.filters, this.filters.size > 0, this.filters.values()); this.#paginatedItems = items; + // paginate items if (this.paginateItems) { console.log('this.paginateItems', this.paginateItems); // const pagination = {pageNumber: pageNumber, pageSize: pageSize}; // console.log('pagination in paginate', pagination); - const startIndex = (this.paginateItems.pageNumber - 1) * this.paginateItems.pageSize; + // const pageNumber = /*filtersChanged /!*this.filters.size > 0*!/ ? 1 :*/ this.paginateItems.pageNumber; + // this.paginateItems.pageNumber = pageNumber; + // this.paginate(10, pageNumber); + // this.paginateItems.pageNumber = pageNumber; + + const startIndex = (this.paginateItems.pageNumber /*pageNumber*/ - 1) * this.paginateItems.pageSize; const endIndex = startIndex + this.paginateItems.pageSize; + // console.log('pageNumber in array data source', pageNumber, filtersChanged, startIndex); // Get the items for the current page /*const paginatedItems*/items = /*this.*/items.slice(startIndex, endIndex); // console.log('paginated data', paginatedItems); // Update this.items with the paginated items // this.paginatedItems/*items*/ = paginatedItems; - console.log('updated items', this.items, this.paginatedItems, items); + // console.log('updated items', this.items, this.paginatedItems, items); } // TODO: I need to have amount of all filtered data for total to show in the paginator... @@ -145,8 +155,13 @@ export class ArrayDataSource extends DataSource { // TODO: pagination needs to work with filteredItems as well + // TODO: detect whether filter values have changed and then activePage = 1 in the paginator? + // this.#paginatedItems = this.paginatedItems; + // console.log('filtereditems1', this.#filteredItems); + // const previouslyFilteredItems = this.#filteredItems; + this.#filteredItems = items; // TODO: maybe needs to be before pagination? this.dispatchEvent(new CustomEvent('sl-update')); } diff --git a/packages/components/shared/src/data-source/data-source.ts b/packages/components/shared/src/data-source/data-source.ts index 5947a4eee..1f67bb205 100644 --- a/packages/components/shared/src/data-source/data-source.ts +++ b/packages/components/shared/src/data-source/data-source.ts @@ -77,7 +77,7 @@ export abstract class DataSource extends EventTarget { /** Total number of items in this data source. */ abstract readonly size: number; - /** Updates the list of items using filter and sorting if available. */ + /** Updates the list of items using filter, sorting and pagination if available. */ abstract update(): void; addFilter>( @@ -85,12 +85,13 @@ export abstract class DataSource extends EventTarget { pathOrFilter: U, value?: string | string[] ): void { + console.log('value in addFilter', value); if (typeof pathOrFilter === 'string') { this.#filters.set(id, { path: pathOrFilter, value: value ?? '' }); } else { this.#filters.set(id, { filter: pathOrFilter, value }); } - } + } // TODO: maybe here emit an event? removeFilter(id: string): void { this.#filters.delete(id); @@ -158,32 +159,9 @@ export abstract class DataSource extends EventTarget { /** * Use to get the paginated data for usage with the sl-paginator component. * */ - paginate(/*data: T[], */pageSize: number, pageNumber: number): void { - // // const items = /*data ? data :*/ this.items; - // console.log('in paginate', this.items); - // const startIndex = (pageNumber - 1) * pageSize; - // console.log('paginated data', this.items.slice(startIndex, startIndex + pageSize), this.items); - // console.log('in paginate2', this.items); - // // Get the items for the current page - // /*return*/ // items.slice(startIndex, startIndex + pageSize); - // this.update(); - // this.#allItems = this.items; - - // console.log('all items in paginated data', this.#allItems); - + paginate(pageNumber: number, pageSize: number): void { this.#paginateItems = { pageNumber: pageNumber, pageSize: pageSize}; - // const pagination = {pageNumber: pageNumber, pageSize: pageSize}; - // console.log('pagination in paginate', pagination); - // - // const startIndex = (pageNumber - 1) * pageSize; - // const endIndex = startIndex + pageSize; - // // Get the items for the current page - // const paginatedItems = this.items.slice(startIndex, endIndex); - // console.log('paginated data', paginatedItems); - // // Update this.items with the paginated items - // this.paginatedItems/*items*/ = paginatedItems; - // console.log('updated items', this.items, this.paginatedItems); this.update(); }