diff --git a/cosmoz-omnitable-column-mixin.js b/cosmoz-omnitable-column-mixin.js index 5fa2aa68..356afec9 100644 --- a/cosmoz-omnitable-column-mixin.js +++ b/cosmoz-omnitable-column-mixin.js @@ -64,6 +64,15 @@ export const getString = ({ valuePath }, item) => get(item, valuePath), renderCell: { type: Function }, renderEditCell: { type: Function }, renderGroup: { type: Function }, + + /** + * The priority of the column in the mini mode. If missing the column is disabled in the mini mode. + */ + mini: { type: Number, value: null }, + /** + * An alternative render to use in mini mode. Takes the same params as `renderCell`. + */ + renderMini: { type: Function }, }; } @@ -82,7 +91,7 @@ export const getString = ({ valuePath }, item) => get(item, valuePath), state: this.legacyFilterToState(filter), }, bubbles: true, - }) + }), ); } @@ -135,7 +144,7 @@ export const getString = ({ valuePath }, item) => get(item, valuePath), _propertiesChanged(currentProps, changedProps, oldProps) { super._propertiesChanged(currentProps, changedProps, oldProps); this.dispatchEvent( - new CustomEvent('cosmoz-column-prop-changed', { bubbles: true }) + new CustomEvent('cosmoz-column-prop-changed', { bubbles: true }), ); } }; diff --git a/cosmoz-omnitable-item-expand-line.js b/cosmoz-omnitable-item-expand-line.js index 49d7906b..53ab2c42 100644 --- a/cosmoz-omnitable-item-expand-line.js +++ b/cosmoz-omnitable-item-expand-line.js @@ -1,33 +1,39 @@ import { component, html } from '@pionjs/pion'; +import { css, sheet } from '@neovici/cosmoz-utils'; -const OmnitableItemExpandLine = ({ column }) => html` - -
${ column.title }
-
+const ItemExpandLine = ({ column }) => html` +
+ ${column.title} +
+
`; -customElements.define('cosmoz-omnitable-item-expand-line', component(OmnitableItemExpandLine)); +customElements.define( + 'cosmoz-omnitable-item-expand-line', + component(ItemExpandLine, {styleSheets: [sheet(style)]}), +); diff --git a/cosmoz-omnitable-item-expand.js b/cosmoz-omnitable-item-expand.js index 3f7761e8..d4d6498c 100644 --- a/cosmoz-omnitable-item-expand.js +++ b/cosmoz-omnitable-item-expand.js @@ -1,48 +1,28 @@ -/* eslint-disable object-curly-newline */ -import { component, useEffect } from '@pionjs/pion'; -import { html, nothing } from 'lit-html'; +import { component } from '@pionjs/pion'; +import { html } from 'lit-html'; +import { map } from 'lit-html/directives/map.js'; import './cosmoz-omnitable-item-expand-line'; -const renderExpandList = ({ +const ItemExpand = ({ columns, item, selected, expanded, groupOnColumn }) => { + return map( columns, - item, - selected, - expanded, - groupOnColumn, - }) => - columns.map( - (column) => html` + html`${column.renderCell(column, { item, selected, expanded, })}` - ), - ExpandList = (host) => { - const { columns } = host; - - useEffect(() => { - if (columns?.length > 0) { - return; - } - - host.setAttribute('hidden', ''); - return () => host.removeAttribute('hidden'); - }, [columns?.length]); - - return Array.isArray(columns) && columns.length > 0 && host.expanded - ? renderExpandList(host) - : nothing; - }; + >`, + ); +}; customElements.define( 'cosmoz-omnitable-item-expand', - component(ExpandList, { + component(ItemExpand, { useShadowDOM: false, - observedAttributes: ['expanded'], - }) + }), ); diff --git a/cosmoz-omnitable-item-row.js b/cosmoz-omnitable-item-row.js index 659895d7..d2663c67 100644 --- a/cosmoz-omnitable-item-row.js +++ b/cosmoz-omnitable-item-row.js @@ -1,20 +1,37 @@ import { component, html } from '@pionjs/pion'; import { repeat } from 'lit-html/directives/repeat.js'; -const - renderCell = (column, data, onItemChange) => column.editable +const renderCell = (column, data, onItemChange) => + column.editable ? column.renderEditCell(column, data, onItemChange(column, data.item)) - : column.renderCell(column, data), - ItemRow = ({ columns, groupOnColumn, item, index, selected, expanded, onItemChange }) => { - return repeat(columns, column => column.name, column => { + : column.renderCell(column, data); + +const ItemRow = ({ + columns, + groupOnColumn, + item, + index, + selected, + expanded, + onItemChange, +}) => + repeat( + columns, + (column) => column.name, + (column) => { return html`
${ renderCell(column, { item, index, selected, expanded }, onItemChange) }
`; - }); - }; + class="cell itemRow-cell ${column.cellClass ?? ''}" + ?hidden="${column === groupOnColumn}" + ?editable="${column.editable}" + title="${column.cellTitleFn(column, item)}" + name="${column.name}" + > + ${renderCell(column, { item, index, selected, expanded }, onItemChange)} +
`; + }, + ); -customElements.define('cosmoz-omnitable-item-row', component(ItemRow, { useShadowDOM: false })); +customElements.define( + 'cosmoz-omnitable-item-row', + component(ItemRow, { useShadowDOM: false }), +); diff --git a/cosmoz-omnitable-styles.js b/cosmoz-omnitable-styles.js index 08ebe94f..7968cd15 100644 --- a/cosmoz-omnitable-styles.js +++ b/cosmoz-omnitable-styles.js @@ -162,6 +162,7 @@ export default css` --cosmoz-input-padding: 0; --cosmoz-input-label-text-transform: var(--cosmoz-omnitable-header-text-transform, none); --cosmoz-input-label-font-weight: var(--cosmoz-omnitable-header-font-weight, normal); + --cosmoz-input-padding: 0; } cosmoz-omnitable-header-row { @@ -317,11 +318,6 @@ export default css` left: 0; } - .item-row-wrapper { - display: block; - width: 100%; - } - .itemRow { border-bottom-color: var(--cosmoz-omnitable-border-color, #e1e2e5); border-bottom-width: 1px; @@ -330,10 +326,12 @@ export default css` solid ); /* set a min-height for rows so that rows with empty values are visible */ - min-height: var(--item-row-min-height, 24px); - padding-right: 8px; + } + .itemRow-wrapper { display: flex; align-items: center; + min-height: var(--item-row-min-height, 39px); + padding-right: 8px; } .itemRow[selected] { @@ -357,9 +355,8 @@ export default css` background-color: #fafafa; } - cosmoz-omnitable-item-expand[hidden], cosmoz-omnitable-item-expand:not([expanded]) { - display: none !important; + display: none; } .groupRow { @@ -447,7 +444,7 @@ export default css` .itemRow:hover { box-shadow: inset 1px 0 0 #dadce0, inset -1px 0 0 #dadce0, 0 1px 2px 0 rgb(60 64 67 / 30%), 0 1px 3px 1px rgb(60 64 67 / 15%); - background: var(--cosmoz-omnitable-hover-color); + /* background: var(--cosmoz-omnitable-hover-color); */ } .groupRow:hover .checkbox:not(:checked):not(:hover), .itemRow:hover .checkbox:not(:checked):not(:hover) { @@ -530,4 +527,32 @@ export default css` min-width: 0; flex: auto; } + + :host([mini]) .itemRow .expand, + :host([mini]) cosmoz-omnitable-item-expand { + display: none; + } + + .itemRow-minis { + display: flex; + justify-content: space-between; + margin: 0 8px 8px 8px; + } + + :host([mini]) .itemRow { + border-radius: 8px; + border: 1px solid var(--cosmoz-omnitable-border-color, #e1e2e5); + margin: 4px 8px; + } + :host([mini]) .itemRow:not([selected]) { + background: var(--cosmoz-omnitable-mini-item-background, #fdfdfd); + } + + :host([mini]) .itemRow:hover { + box-shadow: none; + } + + :host([mini]) .header { + margin: 0 8px; + } `; diff --git a/cosmoz-omnitable.js b/cosmoz-omnitable.js index afb718fd..4e86310a 100644 --- a/cosmoz-omnitable.js +++ b/cosmoz-omnitable.js @@ -63,6 +63,7 @@ customElements.define( 'no-local-sort', 'no-local-filter', 'loading', + 'mini-breakpoint', ], }) { connectedCallback() { @@ -71,7 +72,7 @@ customElements.define( notifyProperty(this, 'visibleData', []); notifyProperty(this, 'sortedFilteredGroupedItems', []); } - } + }, ); const tmplt = ` diff --git a/lib/settings/style.css.js b/lib/settings/style.css.js index 9284f5b9..48ac6522 100644 --- a/lib/settings/style.css.js +++ b/lib/settings/style.css.js @@ -12,7 +12,8 @@ export default css` } .headline { - box-shadow: inset 0px -1px 0px rgba(0, 0, 0, 0.15), + box-shadow: + inset 0px -1px 0px rgba(0, 0, 0, 0.15), inset 0px 1px 0px rgba(0, 0, 0, 0.15); font-weight: 500; font-size: 16px; @@ -63,7 +64,8 @@ export default css` transform: scaleY(-1); } cosmoz-collapse[opened] + .heading { - box-shadow: inset 0px -1px 0px rgba(0, 0, 0, 0.15), + box-shadow: + inset 0px -1px 0px rgba(0, 0, 0, 0.15), inset 0px 1px 0px rgba(0, 0, 0, 0.15); } diff --git a/lib/use-canvas-width.js b/lib/use-canvas-width.js index 263d5d45..77c7c54d 100644 --- a/lib/use-canvas-width.js +++ b/lib/use-canvas-width.js @@ -1,8 +1,10 @@ import { useState } from '@pionjs/pion'; import { useTrackSize } from './use-track-size'; -export const useCanvasWidth = host => { - const [canvasWidth, setCanvasWidth] = useState(() => host.getBoundingClientRect().width); +export const useCanvasWidth = (host) => { + const [canvasWidth, setCanvasWidth] = useState( + () => host.getBoundingClientRect().width, + ); useTrackSize(host, setCanvasWidth); diff --git a/lib/use-dom-columns.js b/lib/use-dom-columns.js index 6bbc3c9f..205e10f2 100644 --- a/lib/use-dom-columns.js +++ b/lib/use-dom-columns.js @@ -88,6 +88,9 @@ const columnSymbol = Symbol('column'), noLocalFilter: column.noLocalFilter, + mini: column.mini, + renderMini: column.renderMini, + // @deprecated loading: column.loading, externalValues: column.externalValues, diff --git a/lib/use-fast-layout.js b/lib/use-fast-layout.js index dc1858c7..05fd991c 100644 --- a/lib/use-fast-layout.js +++ b/lib/use-fast-layout.js @@ -5,6 +5,7 @@ import { useCanvasWidth } from './use-canvas-width'; import { useTweenArray } from './use-tween-array'; import { useLayout } from './use-layout'; import { useStyleSheet } from '@neovici/cosmoz-utils/hooks/use-stylesheet'; +import { useMini } from './use-mini'; export const useFastLayout = ({ host, @@ -15,10 +16,12 @@ export const useFastLayout = ({ sortAndGroupOptions, }) => { const canvasWidth = useCanvasWidth(host), + { miniColumn, miniColumns } = useMini({ host, canvasWidth, columns }), { groupOnColumn } = sortAndGroupOptions, layout = useLayout({ canvasWidth, groupOnColumn, + miniColumn, config: settings.columns, }), tweenedlayout = useTweenArray(layout, resizeSpeedFactor), @@ -49,5 +52,5 @@ export const useFastLayout = ({ useStyleSheet(layoutCss); - return { collapsedColumns }; + return { collapsedColumns, miniColumns }; }; diff --git a/lib/use-layout.js b/lib/use-layout.js index 29003215..eb07d440 100644 --- a/lib/use-layout.js +++ b/lib/use-layout.js @@ -1,28 +1,32 @@ import { useMemo } from '@pionjs/pion'; import { computeLayout } from './compute-layout'; -export const useLayout = ({ canvasWidth, groupOnColumn, config }) => useMemo(() => { - if (!Array.isArray(config) || canvasWidth == null || canvasWidth === 0) { - return []; - } +export const useLayout = ({ canvasWidth, groupOnColumn, config, miniColumn }) => + useMemo(() => { + if (!Array.isArray(config) || canvasWidth == null || canvasWidth === 0) { + return []; + } - const columnConfigs = config - .map((c, index) => ({ - minWidth: c.minWidth, - width: c.width, - flex: c.flex, - priority: c.priority, - name: c.name, - index, - hidden: c.name === groupOnColumn?.name || c.disabled - })) - .sort( - ( - { index: aIndex, priority: aPriority }, - { index: bIndex, priority: bPriority } - ) => - aPriority === bPriority ? bIndex - aIndex : aPriority - bPriority - ); + const columnConfigs = config + .map((c, index) => ({ + minWidth: c.minWidth, + width: c.width, + flex: c.flex, + priority: c.priority, + name: c.name, + index, + hidden: c.name === groupOnColumn?.name || c.disabled, + })) + .map((c) => + miniColumn ? { ...c, hidden: miniColumn.name !== c.name } : c, + ) + .sort( + ( + { index: aIndex, priority: aPriority }, + { index: bIndex, priority: bPriority }, + ) => + aPriority === bPriority ? bIndex - aIndex : aPriority - bPriority, + ); - return computeLayout(columnConfigs, canvasWidth, columnConfigs.length); -}, [canvasWidth, groupOnColumn, config]); + return computeLayout(columnConfigs, canvasWidth, columnConfigs.length); + }, [canvasWidth, groupOnColumn, config]); diff --git a/lib/use-list.js b/lib/use-list.js index a57cc6fe..7488553f 100644 --- a/lib/use-list.js +++ b/lib/use-list.js @@ -1,7 +1,8 @@ /* eslint-disable max-lines-per-function */ import { html, useCallback, useEffect, useMemo, useRef } from '@pionjs/pion'; -import { indexSymbol } from './utils'; +import { when } from 'lit-html/directives/when.js'; import { isEmpty } from '@neovici/cosmoz-utils/template'; +import { indexSymbol } from './utils'; import { onItemChange as _onItemChange } from './utils-data'; const arrow = html` @@ -19,10 +20,26 @@ const arrow = html` const _getGroupRowClasses = (folded) => folded ? 'groupRow groupRow-folded' : 'groupRow'; +const renderMinis = (item) => (columns) => + when( + columns?.length > 0, + () => html` +
+ ${columns.map( + (column) => + html`
+ ${(column.renderMini ?? column.renderCell)(column, { item })} +
`, + )} +
+ `, + ); + const renderItem = ({ columns, collapsedColumns, + miniColumns, onItemClick, onCheckboxChange, dataIsValid, @@ -30,22 +47,22 @@ const renderItem = onItemChange, rowPartFn, }) => - (item, index, { selected, expanded, toggleCollapse }) => - html`
-
+ (item, index, { selected, expanded, toggleCollapse }) => html` +
+
-
- - -
`; + ${renderMinis(item)(miniColumns)} +
+ + + `; const renderGroup = ({ onCheckboxChange, dataIsValid, groupOnColumn }) => @@ -125,6 +143,7 @@ export const useList = ({ processedItems, columns, collapsedColumns, + miniColumns, sortAndGroupOptions, rowPartFn, ...rest @@ -204,6 +223,7 @@ export const useList = ({ renderItem({ columns, collapsedColumns, + miniColumns, onItemClick, onCheckboxChange, dataIsValid, diff --git a/lib/use-mini.js b/lib/use-mini.js new file mode 100644 index 00000000..f4b159e9 --- /dev/null +++ b/lib/use-mini.js @@ -0,0 +1,28 @@ +import { useMemo, useEffect } from '@pionjs/pion'; + +export const useMini = ({ host, canvasWidth, columns: _columns }) => { + const breakpoint = host.miniBreakpoint ?? 480; + const isMiniSize = useMemo( + () => canvasWidth <= breakpoint, + [canvasWidth, breakpoint], + ); + const columns = useMemo( + () => + isMiniSize + ? _columns + ?.filter((c) => c.mini !== null) + .sort((a, b) => (a.mini ?? 0) - (b.mini ?? 0)) + : [], + [_columns, isMiniSize], + ); + const [miniColumn, ...miniColumns] = columns ?? []; + + useEffect(() => { + host.toggleAttribute('mini', !!miniColumn); + }, [miniColumn]); + + return { + miniColumn, + miniColumns, + }; +}; diff --git a/lib/use-omnitable.js b/lib/use-omnitable.js index 1b70d58c..64088bb9 100644 --- a/lib/use-omnitable.js +++ b/lib/use-omnitable.js @@ -19,7 +19,7 @@ export const useOmnitable = (host) => { noLocalSort = noLocal, noLocalFilter = noLocal, error, - rowPartFn + rowPartFn, } = host, settingS = useSettings({ settingsId, host }), { settings, setSettings, columns, resetRef } = settingS, @@ -28,7 +28,7 @@ export const useOmnitable = (host) => { hashParam, settings, setSettings, - resetRef + resetRef, ), // TODO: drop filterFunctions { processedItems, visibleData, filters, setFilterState, filterFunctions } = @@ -40,7 +40,7 @@ export const useOmnitable = (host) => { noLocalSort, noLocalFilter, }), - { collapsedColumns } = useFastLayout({ + { collapsedColumns, miniColumns } = useFastLayout({ host, columns, settings, @@ -86,8 +86,9 @@ export const useOmnitable = (host) => { setSelectedItems, columns, collapsedColumns, + miniColumns, sortAndGroupOptions, - rowPartFn + rowPartFn, }), footer: useFooter({ host, diff --git a/lib/use-resizable-columns.js b/lib/use-resizable-columns.js index b882ca3b..bb7b3f36 100644 --- a/lib/use-resizable-columns.js +++ b/lib/use-resizable-columns.js @@ -18,7 +18,7 @@ export const useResizableColumns = ({ newConfig = [], maxPriority = config.reduce( (p, c) => Math.max(p, c.priority), - -Infinity + -Infinity, ); for (let i = 0; i < layout.length; i++) { @@ -45,7 +45,7 @@ export const useResizableColumns = ({ newConfig[i].width = Math.min( maxNewSize, - Math.max(newWidth, config[i].minWidth) + Math.max(newWidth, config[i].minWidth), ); newConfig[i].flex = 0; newConfig[i].priority = maxPriority; diff --git a/lib/use-track-size.js b/lib/use-track-size.js index 7603406b..8e22264e 100644 --- a/lib/use-track-size.js +++ b/lib/use-track-size.js @@ -10,10 +10,10 @@ export const useTrackSize = (host, setCanvasWidth) => requestAnimationFrame(() => setCanvasWidth( entry.contentRect?.width - - 20 /* scrollbar width */ - - 44 /* checkbox width */ - - 24 /* expand button width */ - ) + 20 /* scrollbar width */ - + 44 /* checkbox width */ - + 24 /* expand button width */, + ), ); }, observer = new ResizeObserver(onResize); @@ -21,4 +21,3 @@ export const useTrackSize = (host, setCanvasWidth) => observer.observe(host); return () => observer.unobserve(host); }, []); -