Scroll actions

a69638341827e9b8ad9ebb318c90e32228da21f6
This commit is contained in:
4eb0da
2024-03-11 16:29:24 +03:00
parent bfa0a8306c
commit d12ce6e7d8
26 changed files with 316 additions and 77 deletions
+6
View File
@@ -12073,6 +12073,7 @@
"client/web/divkit/src/utils/mask/baseInputMask.ts":"divkit/public/client/web/divkit/src/utils/mask/baseInputMask.ts",
"client/web/divkit/src/utils/mask/currencyInputMask.ts":"divkit/public/client/web/divkit/src/utils/mask/currencyInputMask.ts",
"client/web/divkit/src/utils/mask/fixedLengthInputMask.ts":"divkit/public/client/web/divkit/src/utils/mask/fixedLengthInputMask.ts",
"client/web/divkit/src/utils/nonNegativeModulo.ts":"divkit/public/client/web/divkit/src/utils/nonNegativeModulo.ts",
"client/web/divkit/src/utils/padLeft.ts":"divkit/public/client/web/divkit/src/utils/padLeft.ts",
"client/web/divkit/src/utils/prepareBase64.ts":"divkit/public/client/web/divkit/src/utils/prepareBase64.ts",
"client/web/divkit/src/utils/propToString.ts":"divkit/public/client/web/divkit/src/utils/propToString.ts",
@@ -13326,6 +13327,8 @@
"client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/base-properties/firefoxMobile/step8.png":"divkit/public/client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/base-properties/firefoxMobile/step8.png",
"client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/chromeMobile/step0.png":"divkit/public/client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/chromeMobile/step0.png",
"client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/chromeMobile/step1.png":"divkit/public/client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/chromeMobile/step1.png",
"client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/chromeMobile/step10.png":"divkit/public/client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/chromeMobile/step10.png",
"client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/chromeMobile/step11.png":"divkit/public/client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/chromeMobile/step11.png",
"client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/chromeMobile/step2.png":"divkit/public/client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/chromeMobile/step2.png",
"client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/chromeMobile/step3.png":"divkit/public/client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/chromeMobile/step3.png",
"client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/chromeMobile/step4.png":"divkit/public/client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/chromeMobile/step4.png",
@@ -13336,6 +13339,8 @@
"client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/chromeMobile/step9.png":"divkit/public/client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/chromeMobile/step9.png",
"client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/firefoxMobile/step0.png":"divkit/public/client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/firefoxMobile/step0.png",
"client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/firefoxMobile/step1.png":"divkit/public/client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/firefoxMobile/step1.png",
"client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/firefoxMobile/step10.png":"divkit/public/client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/firefoxMobile/step10.png",
"client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/firefoxMobile/step11.png":"divkit/public/client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/firefoxMobile/step11.png",
"client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/firefoxMobile/step2.png":"divkit/public/client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/firefoxMobile/step2.png",
"client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/firefoxMobile/step3.png":"divkit/public/client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/firefoxMobile/step3.png",
"client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/firefoxMobile/step4.png":"divkit/public/client/web/divkit/tests/hermione/screens/crossplatform/interactions/div-gallery/select-elements/firefoxMobile/step4.png",
@@ -14267,6 +14272,7 @@
"client/web/divkit/tests/utils/joinTemplatesSize.test.ts":"divkit/public/client/web/divkit/tests/utils/joinTemplatesSize.test.ts",
"client/web/divkit/tests/utils/lerp.test.ts":"divkit/public/client/web/divkit/tests/utils/lerp.test.ts",
"client/web/divkit/tests/utils/makeStyle.test.ts":"divkit/public/client/web/divkit/tests/utils/makeStyle.test.ts",
"client/web/divkit/tests/utils/nonNegativeModulo.test.ts":"divkit/public/client/web/divkit/tests/utils/nonNegativeModulo.test.ts",
"client/web/divkit/tests/utils/propToString.test.ts":"divkit/public/client/web/divkit/tests/utils/propToString.test.ts",
"client/web/divkit/tests/utils/pxToEm.test.ts":"divkit/public/client/web/divkit/tests/utils/pxToEm.test.ts",
"client/web/divkit/tests/utils/simpleCheckInput.test.ts":"divkit/public/client/web/divkit/tests/utils/simpleCheckInput.test.ts",
+68 -43
View File
@@ -454,55 +454,75 @@
}
}
function setCurrentItem(id: string | null, item: string | null): void {
if (!id || !item) {
throw new Error('Missing data for "set-current-item" action');
function switchElementAction(
type: 'set_current_item' | 'set_previous_item' | 'set_next_item' | 'scroll_to_start' |
'scroll_to_end' | 'scroll_backward' | 'scroll_forward' | 'scroll_to_position',
id: string | null,
{
item,
step,
overflow
}: {
item?: string | null;
step?: string | null;
overflow?: string | null;
}
if (Number.isNaN(Number(item))) {
throw new Error('Incorrect item for "set-current-item" action');
}
const instance = getInstance<SwitchElements>(id);
if (instance) {
instance.setCurrentItem(Number(item));
}
}
function setPreviousItem(id: string | null, overflow: string | null): void {
): void {
if (!id) {
throw new Error('Missing id for "set-previous-item" action');
throw new Error(`Missing id for "${type}" action`);
}
const itemVal = Number(item);
if (type === 'set_current_item' && Number.isNaN(itemVal)) {
throw new Error(`Incorrect item for "${type}" action`);
}
let stepVal = Number(step);
if (!step && (type === 'set_previous_item' || type === 'set_next_item')) {
stepVal = 1;
}
if (
!step && (type === 'scroll_backward' || type === 'scroll_forward' || type === 'scroll_to_position') ||
Number.isNaN(stepVal)
) {
throw new Error(`Incorrect step value for "${type}" action`);
}
if (overflow && overflow !== 'clamp' && overflow !== 'ring') {
throw new Error('Incorrect overflow value for "set-previous-item" action');
throw new Error(`Incorrect overflow value for "${type}" action`);
}
overflow = overflow || 'clamp';
const instance = getInstance<SwitchElements>(id);
if (instance) {
instance.setPreviousItem(overflow as Overflow);
}
}
function setNextItem(id: string | null, overflow: string | null): void {
if (!id) {
throw new Error('Missing id for "set-next-item" action');
if (!instance) {
return;
}
if (overflow && overflow !== 'clamp' && overflow !== 'ring') {
throw new Error('Incorrect overflow value for "set-next-item" action');
}
overflow = overflow || 'clamp';
const instance = getInstance<SwitchElements>(id);
if (instance) {
instance.setNextItem(overflow as Overflow);
switch (type) {
case 'set_current_item':
instance.setCurrentItem(itemVal);
return;
case 'set_previous_item':
instance.setPreviousItem(stepVal, overflow as Overflow);
return;
case 'set_next_item':
instance.setNextItem(stepVal, overflow as Overflow);
return;
case 'scroll_to_start':
instance.scrollToStart?.();
return;
case 'scroll_to_end':
instance.scrollToEnd?.();
return;
case 'scroll_backward':
instance.scrollBackward?.(stepVal, overflow as Overflow);
return;
case 'scroll_forward':
instance.scrollForward?.(stepVal, overflow as Overflow);
return;
case 'scroll_to_position':
instance.scrollToPosition?.(stepVal);
return;
}
}
@@ -718,13 +738,18 @@
setState(params.get('state_id'));
break;
case 'set_current_item':
setCurrentItem(params.get('id'), params.get('item'));
break;
case 'set_previous_item':
setPreviousItem(params.get('id'), params.get('overflow'));
break;
case 'set_next_item':
setNextItem(params.get('id'), params.get('overflow'));
case 'scroll_to_start':
case 'scroll_to_end':
case 'scroll_backward':
case 'scroll_forward':
case 'scroll_to_position':
switchElementAction(parts[1], params.get('id'), {
item: params.get('item'),
step: params.get('step'),
overflow: params.get('overflow')
});
break;
case 'set_variable':
const name = params.get('name');
@@ -30,6 +30,7 @@
import { joinTemplateSizes } from '../../utils/joinTemplateSizes';
import { debounce } from '../../utils/debounce';
import { Truthy } from '../../utils/truthy';
import { nonNegativeModulo } from '../../utils/nonNegativeModulo';
export let componentContext: ComponentContext<DivGalleryData>;
export let layoutParams: LayoutParams | undefined = undefined;
@@ -287,10 +288,19 @@
return res;
}
function scrollTo(offset: number, behavior: ScrollBehavior = 'smooth'): void {
const isHorizontal = orientation === 'horizontal';
const scrollDirection: keyof ScrollToOptions = isHorizontal ? 'left' : 'top';
scroller.scroll({
[scrollDirection]: offset,
behavior
});
}
function scrollToGalleryItem(galleryElements: HTMLElement[], index: number, behavior: ScrollBehavior = 'smooth'): void {
const isHorizontal = orientation === 'horizontal';
const elementOffset: keyof HTMLElement = isHorizontal ? 'offsetLeft' : 'offsetTop';
const scrollDirection: keyof ScrollToOptions = isHorizontal ? 'left' : 'top';
// 0.01 forces Chromium to use scroll-snap (exact correct scroll position will not trigger it)
// Chromium will save scroll-snapped value and will not save exact one
@@ -299,10 +309,14 @@
const elem = galleryElements[index];
if (elem) {
scroller.scroll({
[scrollDirection]: Math.max(0, elem[elementOffset] + .01 - itemSpacing / 2),
behavior
});
let offset;
if ($direction === 'ltr' || !isHorizontal) {
offset = elem[elementOffset] + .01 - itemSpacing / 2;
} else {
const scrollWrapperSize = scroller.offsetWidth;
offset = (elem[elementOffset] + elem.offsetWidth + .01 - itemSpacing / 2) - scrollWrapperSize;
}
scrollTo(offset, behavior);
}
}
@@ -369,21 +383,23 @@
scrollToGalleryItem(galleryElements, item);
},
setPreviousItem(overflow: Overflow) {
setPreviousItem(step: number, overflow: Overflow) {
const currentElementIndex = calculateCurrentElementIndex('prev');
const galleryElements = getItems();
let previousItem = currentElementIndex - 1;
let previousItem = currentElementIndex - step;
if (previousItem < 0) {
previousItem = overflow === 'ring' ? galleryElements.length - 1 : currentElementIndex;
previousItem = overflow === 'ring' ? nonNegativeModulo(previousItem, galleryElements.length) : 0;
}
scrollToGalleryItem(galleryElements, previousItem);
},
setNextItem(overflow: Overflow) {
setNextItem(step: number, overflow: Overflow) {
const isHorizontal = orientation === 'horizontal';
const directionMultiplier = ($direction === 'ltr' || !isHorizontal) ? 1 : -1;
// Go to scroller start, if we reached right/bottom edge of scroller
const isEdgeScroll = orientation === 'horizontal' ? (
scroller.scrollLeft + scroller.offsetWidth === scroller.scrollWidth
const isEdgeScroll = isHorizontal ? (
scroller.scrollLeft * directionMultiplier + scroller.offsetWidth === scroller.scrollWidth
) : (
scroller.scrollTop + scroller.offsetHeight === scroller.scrollHeight
);
@@ -394,14 +410,61 @@
}
const currentElementIndex = calculateCurrentElementIndex('next');
let nextItem = currentElementIndex + 1;
let nextItem = currentElementIndex + step;
if (nextItem > galleryElements.length - 1) {
nextItem = overflow === 'ring' ? 0 : currentElementIndex;
nextItem = overflow === 'ring' ? nonNegativeModulo(nextItem, galleryElements.length) : galleryElements.length - 1;
}
scrollToGalleryItem(galleryElements, nextItem);
}
},
scrollToStart() {
scrollTo(0);
},
scrollToEnd() {
scrollTo(($direction === 'ltr' || orientation !== 'horizontal') ? 1e6 : -1e6);
},
scrollBackward(step: number, overflow: Overflow) {
const isHorizontal = orientation === 'horizontal';
const directionMultiplier = ($direction === 'ltr' || !isHorizontal) ? 1 : -1;
const currentOffset = isHorizontal ?
scroller.scrollLeft :
scroller.scrollTop;
const maxOffset = isHorizontal ?
scroller.scrollWidth - scroller.offsetWidth :
scroller.scrollHeight - scroller.offsetHeight;
let newOffset = currentOffset * directionMultiplier - step;
if (newOffset < 0) {
if (overflow === 'clamp') {
newOffset = 0;
} else if (overflow === 'ring') {
newOffset = nonNegativeModulo(newOffset, maxOffset);
}
}
scrollTo(newOffset * directionMultiplier);
},
scrollForward(step: number, overflow: Overflow) {
const isHorizontal = orientation === 'horizontal';
const directionMultiplier = ($direction === 'ltr' || !isHorizontal) ? 1 : -1;
const currentOffset = isHorizontal ?
scroller.scrollLeft :
scroller.scrollTop;
const maxOffset = isHorizontal ?
scroller.scrollWidth - scroller.offsetWidth :
scroller.scrollHeight - scroller.offsetHeight;
let newOffset = currentOffset * directionMultiplier + step;
if (newOffset > maxOffset) {
if (overflow === 'clamp') {
newOffset = maxOffset;
} else if (overflow === 'ring') {
newOffset = nonNegativeModulo(newOffset, maxOffset);
}
}
scrollTo(newOffset * directionMultiplier);
},
scrollToPosition(step) {
scrollTo(($direction === 'ltr' || orientation !== 'horizontal') ? step : -step);
},
});
}
}
@@ -26,6 +26,7 @@
import { isNonNegativeNumber } from '../../utils/isNonNegativeNumber';
import { debounce } from '../../utils/debounce';
import { Truthy } from '../../utils/truthy';
import { nonNegativeModulo } from '../../utils/nonNegativeModulo';
export let componentContext: ComponentContext<DivPagerData>;
export let layoutParams: LayoutParams | undefined = undefined;
@@ -233,21 +234,21 @@
currentItem = index;
}
function setPreviousItem(overflow: Overflow) {
let previousItem = currentItem - 1;
function setPreviousItem(step: number, overflow: Overflow) {
let previousItem = currentItem - step;
if (previousItem < 0) {
previousItem = overflow === 'ring' ? items.length - 1 : currentItem;
previousItem = overflow === 'ring' ? nonNegativeModulo(previousItem, items.length) : 0;
}
scrollToPagerItem(previousItem);
}
function setNextItem(overflow: Overflow) {
let nextItem = currentItem + 1;
function setNextItem(step: number, overflow: Overflow) {
let nextItem = currentItem + step;
if (nextItem > items.length - 1) {
nextItem = overflow === 'ring' ? 0 : currentItem;
nextItem = overflow === 'ring' ? nonNegativeModulo(nextItem, items.length) : items.length - 1;
}
scrollToPagerItem(nextItem);
@@ -270,7 +271,13 @@
scrollToPagerItem(item);
},
setPreviousItem,
setNextItem
setNextItem,
scrollToStart() {
scrollToPagerItem(0);
},
scrollToEnd() {
scrollToPagerItem(items.length - 1);
}
});
}
}
@@ -328,7 +335,7 @@
{#if hasScrollLeft && shouldCheckArrows}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="{leftClass || `${css.pager__arrow} ${arrowsCss.arrow} ${arrowsCss.arrow_left}`}" on:click={() => ($direction === 'ltr' ? setPreviousItem : setNextItem)('clamp')}>
<div class="{leftClass || `${css.pager__arrow} ${arrowsCss.arrow} ${arrowsCss.arrow_left}`}" on:click={() => ($direction === 'ltr' ? setPreviousItem : setNextItem)(1, 'clamp')}>
{#if !leftClass}
<svg class={arrowsCss.arrow__icon} xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<path class={css['pager__arrow-icon-path']} d="m10 16 8.3 8 1.03-1-4-6-.7-1 .7-1 4-6-1.03-1z"/>
@@ -339,7 +346,7 @@
{#if hasScrollRight && shouldCheckArrows}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="{rightClass || `${css.pager__arrow} ${arrowsCss.arrow} ${arrowsCss.arrow_right}`}" on:click={() => ($direction === 'ltr' ? setNextItem : setPreviousItem)('clamp')}>
<div class="{rightClass || `${css.pager__arrow} ${arrowsCss.arrow} ${arrowsCss.arrow_right}`}" on:click={() => ($direction === 'ltr' ? setNextItem : setPreviousItem)(1, 'clamp')}>
{#if !rightClass}
<svg class={arrowsCss.arrow__icon} xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<path class={css['pager__arrow-icon-path']} d="M22 16l-8.3 8-1.03-1 4-6 .7-1-.7-1-4-6 1.03-1 8.3 8z"/>
@@ -37,6 +37,7 @@
import { correctNonNegativeNumber } from '../../utils/correctNonNegativeNumber';
import { edgeInsertsToCss } from '../../utils/edgeInsertsToCss';
import { filterEnabledActions } from '../../utils/filterEnabledActions';
import { nonNegativeModulo } from '../../utils/nonNegativeModulo';
export let componentContext: ComponentContext<DivTabsData>;
export let layoutParams: LayoutParams | undefined = undefined;
@@ -611,24 +612,30 @@
setSelected(item);
},
setPreviousItem(overflow: Overflow) {
let previousItem = selected - 1;
setPreviousItem(step: number, overflow: Overflow) {
let previousItem = selected - step;
if (previousItem < 0) {
previousItem = overflow === 'ring' ? items.length - 1 : selected;
previousItem = overflow === 'ring' ? nonNegativeModulo(previousItem, items.length) : 0;
}
setSelected(previousItem);
},
setNextItem(overflow: Overflow) {
let nextItem = selected + 1;
setNextItem(step: number, overflow: Overflow) {
let nextItem = selected + step;
if (nextItem > items.length - 1) {
nextItem = overflow === 'ring' ? 0 : selected;
nextItem = overflow === 'ring' ? nonNegativeModulo(nextItem, items.length) : items.length - 1;
}
setSelected(nextItem);
}
},
scrollToStart() {
setSelected(0);
},
scrollToEnd() {
setSelected(items.length - 1);
},
});
}
}
+7 -2
View File
@@ -2,6 +2,11 @@ export type Overflow = 'clamp' | 'ring';
export interface SwitchElements {
setCurrentItem(item: number): void;
setPreviousItem(overflow: Overflow): void;
setNextItem(overflow: Overflow): void;
setPreviousItem(step: number, overflow: Overflow): void;
setNextItem(step: number, overflow: Overflow): void;
scrollToStart?: () => void;
scrollToEnd?: () => void;
scrollBackward?: (step: number, overflow: Overflow) => void;
scrollForward?: (step: number, overflow: Overflow) => void;
scrollToPosition?: (step: number) => void;
}
@@ -0,0 +1,7 @@
export function nonNegativeModulo(value: number, mod: number): number {
let res = value % mod;
if (res < 0) {
res += mod;
}
return res;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 459 KiB

After

Width:  |  Height:  |  Size: 460 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 457 KiB

After

Width:  |  Height:  |  Size: 459 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 146 KiB

@@ -0,0 +1,12 @@
import { nonNegativeModulo } from '../../src/utils/nonNegativeModulo';
describe('nonNegativeModulo', () => {
test('simple', () => {
expect(nonNegativeModulo(3, 10)).toBe(3);
expect(nonNegativeModulo(23, 10)).toBe(3);
expect(nonNegativeModulo(-3, 10)).toBe(7);
expect(nonNegativeModulo(-13, 10)).toBe(7);
expect(nonNegativeModulo(0, 10)).toBe(0);
expect(nonNegativeModulo(10, 10)).toBe(0);
});
});
@@ -1,7 +1,8 @@
{
"description": "TODO: https://nda.ya.ru/t/MMvpmt855oXgre",
"platforms": [
"android"
"android",
"web"
],
"div_data": {
"templates": {
@@ -399,7 +399,7 @@
"text": "<",
"actions": [
{
"log_id": "gallery1/scroll_backward by 150dp",
"log_id": "gallery1/scroll_backward by 50dp",
"url": "div-action://scroll_backward?id=gallery1&step=50"
},
{
@@ -218,6 +218,61 @@
"content_alignment_horizontal": "center",
"alignment_horizontal": "center",
"type": "container"
},
{
"items": [
{
"id": "scroll_click_1",
"actions": [
{
"log_id": "scroll_to_start",
"url": "div-action://scroll_to_start?id=my_pager"
}
],
"text": "<<",
"text_alignment_horizontal": "center",
"type": "button"
},
{
"id": "scroll_click_2",
"actions": [
{
"log_id": "prev_by_2",
"url": "div-action://set_previous_item?id=my_pager&step=2"
}
],
"text": "<",
"text_alignment_horizontal": "center",
"type": "button"
},
{
"id": "scroll_click_3",
"actions": [
{
"log_id": "next_by_2",
"url": "div-action://set_next_item?id=my_pager&step=2"
}
],
"text": ">",
"text_alignment_horizontal": "center",
"type": "button"
},
{
"id": "scroll_click_4",
"actions": [
{
"log_id": "scroll_to_end",
"url": "div-action://scroll_to_end?id=my_pager"
}
],
"text": ">>",
"type": "button"
}
],
"orientation": "horizontal",
"content_alignment_horizontal": "center",
"alignment_horizontal": "center",
"type": "container"
}
],
"orientation": "vertical",
@@ -220,6 +220,57 @@
"orientation": "horizontal",
"content_alignment_horizontal": "center",
"type": "container"
},
{
"items": [
{
"actions": [
{
"log_id": "scroll_to_start",
"url": "div-action://scroll_to_start?id=my_tabs"
}
],
"text": "<<",
"text_alignment_horizontal": "center",
"type": "button"
},
{
"actions": [
{
"log_id": "prev_by_2",
"url": "div-action://set_previous_item?id=my_tabs&step=2"
}
],
"text": "<",
"text_alignment_horizontal": "center",
"type": "button"
},
{
"actions": [
{
"log_id": "next_by_2",
"url": "div-action://set_next_item?id=my_tabs&step=2"
}
],
"text": ">",
"text_alignment_horizontal": "center",
"type": "button"
},
{
"actions": [
{
"log_id": "scroll_to_end",
"url": "div-action://scroll_to_end?id=my_tabs"
}
],
"text": ">>",
"text_alignment_horizontal": "center",
"type": "button"
}
],
"orientation": "horizontal",
"content_alignment_horizontal": "center",
"type": "container"
}
],
"orientation": "vertical",