Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,97 @@ HyperTableV2 supports several named blocks that allow you to customize specific
</HyperTableV2>
```

#### HyperTableV2 options

`@options` lets you configure optional component behaviors.

##### selectionIntlKeyPath

- Type: `string`
- Required: no

Custom base i18n key path used by `HyperTableV2::Selection` for:

- `<path>.all_records_selected`
- `<path>.records_selected`
- `<path>.select_all`

Default path: `hypertable.selection`.
Note: the clear action label currently uses `hypertable.selection.clear` directly.

```ts
options = {
selectionIntlKeyPath: 'my.table.selection'
};
```

##### delegatedFiltering

- Type: `boolean`
- Required: no

Disables built-in column filter UI and ordering indicators in `HyperTableV2::Column`.
Use this when filtering and sorting are handled by external controls.

```ts
options = {
delegatedFiltering: true
};
```

##### initialLoadAnimation

- Type: `boolean | object`
- Required: no

Enables a one-time animation sequence on the first successful non-empty rows load.

Accepted values:

- `undefined` or `false`: no animation.
- `true`: enables animation with defaults.
- object: enables animation and overrides defaults.

Behavior:

- Base behavior: rows are revealed with a staggered sequence across all non-loading cells.
- Extra class behavior: when `extraColumnEffect.class` is set, that class is added on top of the base sequence on each cell that matches the columns specified in `extraColumnEffect.columns`.
- Selection column behavior: by default, the extra class does not apply on selection checkbox cells. Set `includeSelectionColumnInExtraEffect` to `true` to include them.
- Extra class delay behavior: `extraColumnEffect.delayMs` adds an extra delay before the extra class effect starts.
- If `extraColumnEffect.columns` is omitted or empty, `extraColumnEffect.class` is applied to cells from all columns.


```ts
options = {
initialLoadAnimation: {
delayMs: 300,
staggerMs: 40,
maxAnimationDurationMs: 1500,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: I'm not sure I understand the need for this.
To me it looks like a possible foot-gun in certain edge cases (large screen size with lots of rows... or long staggerMs and delayMs settings)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is exactly for that scenario (large screen with lots of rows) where you don't necessarily want the animation running for 3minutes because you have 500 loaded rows :P

It gives us the opportunity to cancel it after a certain amount of time

extraColumnEffect: {
class: 'smart-rotating-gradient',
delayMs: 120,
columns: ['foo', 'bar']
},
includeSelectionColumnInExtraEffect: false
}
};
```
Comment on lines +255 to +269

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: are these example options or the default options? Maybe something like this?

Suggested change
```ts
options = {
initialRowsAnimation: {
delayMs: 300,
staggerMs: 40,
maxAnimationDurationMs: 1500,
extraColumnCellEffectDelayMs: 120,
extraColumnCellEffectClass: 'smart-rotating-gradient',
columns: ['foo', 'bar'],
includeSelectionColumnInExtraEffect: false
}
};
```
```ts
// default options
options = {
initialRowsAnimation: {
delayMs: 300, // number
staggerMs: 40, // number
maxAnimationDurationMs: 1500, // number
extraColumnCellEffectDelayMs: 120, // number
extraColumnCellEffectClass: 'smart-rotating-gradient', // string
columns: ['foo', 'bar'], // string[]
includeSelectionColumnInExtraEffect: false // boolean
}
};

Also having the defaults here would render the following paragraph useless => Shorter docs 💪

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or maybe just the interface? Idk

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are example options (though some match the defaults), the object is there just to show how a user can use it ; I did the same for the other "options" before this section.

Concerning adding typing in comments next to it, that's not useful IMO as the types are described just below 🙏


Fields:

- `delayMs` (number): Delay before the sequence starts. Default: `300`.
- `staggerMs` (number): Extra delay applied per row (`rowIndex * staggerMs`). Default: `40`.
- `maxAnimationDurationMs` (number): Extra duration added after stagger starts to keep the animation state active. Default: `5000`.
- `extraColumnEffect` (object): Optional extra effect options.
- `extraColumnEffect.class` (string): Optional extra CSS class added to targeted cells while animation is active.
- `extraColumnEffect.delayMs` (number): Extra delay applied before the `extraColumnEffect.class` effect starts. Default: `0`.
- `extraColumnEffect.columns` (string[]): Column keys that receive `extraColumnEffect.class`. If omitted or empty, the extra class is applied to all columns.
- `includeSelectionColumnInExtraEffect` (boolean): Whether the extra class should also be applied on selection checkbox cells when selection is enabled. Default: `false`.

Notes:

- The sequence runs once per component lifecycle.

## Core Concepts

### Column Definitions
Expand Down
8 changes: 3 additions & 5 deletions addon/components/hyper-table-v2/cell.hbs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
<div
class={{concat
"hypertable__cell"
(if this.loading " hypertable__cell--loading")
(if @row.hovered " hypertable__cell--hovered")
}}
class={{this.computedClass}}
style={{this.initialLoadAnimationCellStyle}}
role="button"
{{will-destroy this.teardown}}
{{on "click" this.clickedCell}}
{{on "mouseenter" (fn this.toggleHover @row true)}}
{{on "mouseleave" (fn this.toggleHover @row false)}}
Expand Down
122 changes: 122 additions & 0 deletions addon/components/hyper-table-v2/cell.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { action } from '@ember/object';
import { htmlSafe } from '@ember/template';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';

import type { InitialLoadAnimationContext } from '@upfluence/hypertable/components/hyper-table-v2';
import TableHandler from '@upfluence/hypertable/core/handler';
import { Column, ResolvedRenderingComponent, Row } from '@upfluence/hypertable/core/interfaces';

interface HyperTableV2CellArgs {
handler: TableHandler;
column: Column;
row: Row;
rowIndex?: number;
initialLoadAnimation?: InitialLoadAnimationContext | null;
enableInitialLoadAnimationExtraEffect?: boolean;
loading: boolean;
onClick?(row: Row): void;
onHover?(row: Row, hovered: boolean): void;
Expand All @@ -17,6 +22,9 @@ interface HyperTableV2CellArgs {
export default class HyperTableV2Cell extends Component<HyperTableV2CellArgs> {
@tracked loadingCellComponent: boolean = true;
@tracked cellComponent?: ResolvedRenderingComponent;
@tracked extraEffectReady: boolean = false;

private extraEffectTimeout?: number;

constructor(owner: unknown, args: HyperTableV2CellArgs) {
super(owner, args);
Expand All @@ -37,6 +45,88 @@ export default class HyperTableV2Cell extends Component<HyperTableV2CellArgs> {
return this.args.loading || this.loadingCellComponent;
}

get computedClass(): string {
const classes = ['hypertable__cell'];

if (this.loading) classes.push('hypertable__cell--loading');
if (this.args.row?.hovered) classes.push('hypertable__cell--hovered');
if (this.initialLoadAnimationSequenceClass) classes.push(this.initialLoadAnimationSequenceClass);
if (this.initialLoadAnimationCellClass) classes.push(this.initialLoadAnimationCellClass);

return classes.join(' ');
}
Comment thread
Miexil marked this conversation as resolved.

get initialLoadAnimationCellClass(): string {
const extraColumnEffectClass = this.args.initialLoadAnimation?.extraColumnEffect?.class;

if (!this.shouldApplyInitialLoadAnimationCustomEffect || !extraColumnEffectClass) {
this.resetExtraEffectState();
return '';
}

if (this.extraEffectActivationDelayMs <= 0) {
return extraColumnEffectClass;
}

this.scheduleExtraEffectIfNeeded();

return this.extraEffectReady ? extraColumnEffectClass : '';
}

get initialLoadAnimationSequenceClass(): string {
return this.shouldApplyInitialLoadAnimationSequence ? 'hypertable__cell--initial-load-sequence' : '';
}

get initialLoadAnimationCellStyle(): ReturnType<typeof htmlSafe> | undefined {
if (!this.shouldApplyInitialLoadAnimationSequence) {
return undefined;
Comment thread
edouardmisset marked this conversation as resolved.
}

const extraColumnEffectDelayMs = this.args.initialLoadAnimation?.extraColumnEffect?.delayMs ?? 0;
const staggeredDelayMs = this.rowAnimationDelayMs;
const extraEffectDelayMs = staggeredDelayMs + extraColumnEffectDelayMs;

return htmlSafe(
`--hypertable-initial-rows-animation-delay: ${staggeredDelayMs}ms; --hypertable-initial-rows-extra-effect-delay: ${extraEffectDelayMs}ms;`
);
}

private get rowAnimationDelayMs(): number {
const delayMs = this.args.initialLoadAnimation?.delayMs ?? 0;
const staggerMs = this.args.initialLoadAnimation?.staggerMs ?? 0;
const rowIndex = this.args.rowIndex ?? 0;

return delayMs + rowIndex * staggerMs;
}

private get isInitialLoadAnimationEnabled(): boolean {
return this.args.initialLoadAnimation?.active === true;
}

private get isInitialLoadAnimationTargetedColumn(): boolean {
const columns = this.args.initialLoadAnimation?.extraColumnEffect?.columns ?? [];

if (columns.length === 0) return true;

return columns.includes(this.args.column.definition.key);
Comment thread
edouardmisset marked this conversation as resolved.
}

private get shouldApplyInitialLoadAnimationSequence(): boolean {
return this.isInitialLoadAnimationEnabled && !this.loading;
}

private get shouldApplyInitialLoadAnimationCustomEffect(): boolean {
if (!this.args.enableInitialLoadAnimationExtraEffect) {
return false;
}

return this.shouldApplyInitialLoadAnimationSequence && this.isInitialLoadAnimationTargetedColumn;
}

private get extraEffectActivationDelayMs(): number {
return this.rowAnimationDelayMs + (this.args.initialLoadAnimation?.extraColumnEffect?.delayMs ?? 0);
}

@action
clickedCell(event: MouseEvent) {
event.stopPropagation();
Expand All @@ -50,4 +140,36 @@ export default class HyperTableV2Cell extends Component<HyperTableV2CellArgs> {
toggleHover(row: Row, hovered: boolean) {
this.args.onHover?.(row, hovered);
}

@action
teardown() {
this.resetExtraEffectState();
}

private scheduleExtraEffectIfNeeded(): void {
if (this.extraEffectReady || this.extraEffectTimeout) {
return;
}

const activationDelayMs = this.extraEffectActivationDelayMs;

if (activationDelayMs <= 0) {
this.extraEffectReady = true;
return;
}

this.extraEffectTimeout = window.setTimeout(() => {
this.extraEffectReady = true;
this.extraEffectTimeout = undefined;
}, activationDelayMs);
}

private resetExtraEffectState(): void {
if (this.extraEffectTimeout) {
window.clearTimeout(this.extraEffectTimeout);
this.extraEffectTimeout = undefined;
}

this.extraEffectReady = false;
}
}
15 changes: 12 additions & 3 deletions addon/components/hyper-table-v2/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,14 @@
/>
</header>

{{#each @handler.rows as |row|}}
{{#each @handler.rows as |row rowIndex|}}
<HyperTableV2::Cell
@handler={{@handler}}
@column={{column}}
@row={{row}}
@rowIndex={{rowIndex}}
@initialLoadAnimation={{this.initialLoadAnimationContext}}
@enableInitialLoadAnimationExtraEffect={{this.enableInitialLoadAnimationExtraEffectOnSelectionCells}}
@onClick={{fn this.toggleRowSelection row}}
@onHover={{this.onRowHover}}
@loading={{row._isLoading}}
Expand Down Expand Up @@ -142,11 +145,14 @@
@column={{column}}
@delegatedFiltering={{@options.delegatedFiltering}}
>
{{#each @handler.rows as |row|}}
{{#each @handler.rows as |row rowIndex|}}
<HyperTableV2::Cell
@handler={{@handler}}
@column={{column}}
@row={{row}}
@rowIndex={{rowIndex}}
@initialLoadAnimation={{this.initialLoadAnimationContext}}
@enableInitialLoadAnimationExtraEffect={{true}}
@onClick={{this.onRowClick}}
@onHover={{this.onRowHover}}
@loading={{row._isLoading}}
Expand Down Expand Up @@ -175,11 +181,14 @@
disabled=column.definition.position.sticky
}}
>
{{#each @handler.rows as |row|}}
{{#each @handler.rows as |row rowIndex|}}
<HyperTableV2::Cell
@handler={{@handler}}
@column={{column}}
@row={{row}}
@rowIndex={{rowIndex}}
@initialLoadAnimation={{this.initialLoadAnimationContext}}
@enableInitialLoadAnimationExtraEffect={{true}}
@onClick={{this.onRowClick}}
@onHover={{this.onRowHover}}
@loading={{row._isLoading}}
Expand Down
Loading
Loading