a new start

This commit is contained in:
2025-10-25 12:39:30 +02:00
commit c97cadef78
726 changed files with 454051 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
import { VELOCITY_THRESHOLD } from "./constants";
import { Coord } from "./observables";
// Using shorthands for common functions
const min = Math.min.bind(Math);
const max = Math.max.bind(Math);
export class CalcMixin {
side!: "left" | "right";
range!: [number, number];
calcIsInRange({ clientX }: Coord, opened: boolean) {
// console.log(this.range, this.align);
switch (this.side) {
case "left": {
const [lower, upper] = this.range;
return clientX > lower && (opened || clientX < upper);
}
case "right": {
const upper = window.innerWidth - this.range[0];
const lower = window.innerWidth - this.range[1];
return clientX < upper && (opened || clientX > lower);
}
default:
throw Error();
}
}
calcIsSwipe(
{ clientX: startX }: Coord,
{ clientX: endX }: Coord,
translateX: number,
drawerWidth: number,
_: number,
): boolean {
return endX !== startX || (translateX > 0 && translateX < drawerWidth);
}
calcWillOpen(
_: {},
__: {},
translateX: number,
drawerWidth: number,
velocity: number,
): boolean {
switch (this.side) {
case "left": {
if (velocity > VELOCITY_THRESHOLD) return true;
else if (velocity < -VELOCITY_THRESHOLD) return false;
else if (translateX >= drawerWidth / 2) return true;
else return false;
}
case "right": {
if (-velocity > VELOCITY_THRESHOLD) return true;
else if (-velocity < -VELOCITY_THRESHOLD) return false;
else if (translateX <= -drawerWidth / 2) return true;
else return false;
}
default:
throw Error();
}
}
calcTranslateX(
{ clientX: moveX }: Coord,
{ clientX: startX }: Coord,
startTranslateX: number,
drawerWidth: number,
): number {
switch (this.side) {
case "left": {
const deltaX = moveX - startX;
const translateX = startTranslateX + deltaX;
return max(0, min(drawerWidth, translateX));
}
case "right": {
const deltaX = moveX - startX;
const translateX = startTranslateX + deltaX;
return min(0, max(-drawerWidth, translateX));
}
default:
throw Error();
}
}
}

View File

@@ -0,0 +1,42 @@
import { Observable, of } from "rxjs";
import { map } from "rxjs/operators";
import { createResizeObservable } from "@hydecorp/component";
import { ComplexAttributeConverter } from "lit";
export {
applyMixins,
subscribeWhen,
filterWhen,
tween,
} from "@hydecorp/component";
export function easeOutSine(t: number, b: number, c: number, d: number) {
return c * Math.sin((t / d) * (Math.PI / 2)) + b;
}
export function observeWidth(el: HTMLElement) {
// This component should have at least basic support without `ResizeObserver` support,
// so we pass a one-time measurement when it's missing. Obviously this won't update, so BYO polyfill.
const resize$: Observable<{ contentRect: { width: number } }> =
"ResizeObserver" in window
? createResizeObservable(el)
: of({ contentRect: { width: el.clientWidth } });
return resize$.pipe(map((rect) => rect.contentRect.width));
}
export const rangeConverter: ComplexAttributeConverter<number[]> = {
fromAttribute(attr) {
return (attr ?? "")
.replace(/[\[\]]/g, "")
.split(",")
.map(Number);
},
toAttribute(range) {
return range.join(",");
},
};
export function rangeHasChanged(curr: number[], prev: number[] = []) {
return curr.length !== prev.length || curr.some((v, i) => v !== prev[i]);
}

View File

@@ -0,0 +1,12 @@
// The base duration of the fling animation.
export const BASE_DURATION = 200;
// We adjust the duration of the animation using the width of the drawer.
// There is no physics to this, but we know from testing that the animation starts to feel bad
// when the drawer increases in size.
// From testing we know that, if we increase the duration as a fraction of the drawer width,
// the animation stays smooth across common display sizes.
export const WIDTH_CONTRIBUTION = 0.15;
// Minimum velocity of the drawer (in px/ms) when releasing to make it fling to opened/closed state.
export const VELOCITY_THRESHOLD = 0.15;

View File

@@ -0,0 +1,485 @@
/**
* Copyright (c) 2020 Florian Klampfer <https://qwtel.com/>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @license
* @nocompile
*/
import { html, ReactiveElement } from "lit";
import { property, customElement, query } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import { styleMap } from "lit/directives/style-map.js";
import {
Observable,
Subject,
BehaviorSubject,
combineLatest,
merge,
NEVER,
defer,
fromEvent,
} from "rxjs";
import {
startWith,
takeUntil,
map,
share,
withLatestFrom,
tap,
sample,
timestamp,
pairwise,
filter,
switchMap,
skip,
finalize,
} from "rxjs/operators";
import { RxLitElement, createResolvablePromise } from "@hydecorp/component";
import { BASE_DURATION, WIDTH_CONTRIBUTION } from "./constants";
import {
applyMixins,
filterWhen,
easeOutSine,
observeWidth,
rangeConverter,
rangeHasChanged,
tween,
} from "./common";
import { ObservablesMixin, Coord } from "./observables";
import { CalcMixin } from "./calc";
import { UpdateMixin, DOMUpdater } from "./update";
import { styles } from "./styles";
@customElement("hy-drawer")
export class HyDrawer
extends applyMixins(RxLitElement, [ObservablesMixin, UpdateMixin, CalcMixin])
implements ObservablesMixin, UpdateMixin, CalcMixin
{
static styles = styles;
@query(".scrim") scrimEl!: HTMLElement;
@query(".wrapper") contentEl!: HTMLElement;
@query(".peek") peekEl!: HTMLElement;
@property({ type: Boolean, reflect: true }) opened: boolean = false;
@property({ type: String, reflect: true }) side: "left" | "right" = "left";
@property({ type: Boolean, reflect: true }) persistent: boolean = false;
@property({ type: Number, reflect: true }) threshold: number = 10;
@property({ type: Boolean, reflect: true }) noScroll: boolean = false;
@property({ type: Boolean, reflect: true }) mouseEvents: boolean = false;
// @property({ type: Boolean, reflect: true }) hashChange: boolean = false;
@property({
reflect: true,
converter: rangeConverter,
hasChanged: rangeHasChanged,
})
range: [number, number] = [0, 100];
// State
@property() scrimClickable!: boolean;
@property() grabbing!: boolean;
@property() willChange: boolean = false;
#initialized = createResolvablePromise();
get initialized() {
return this.#initialized;
}
// get histId() { return this.id || this.tagName; }
// get hashId() { return `#${this.histId}--opened`; }
translateX!: number;
opacity!: number;
isSliding!: boolean;
$!: {
opened: Subject<boolean>;
side: Subject<"left" | "right">;
persistent: Subject<boolean>;
preventDefault: Subject<boolean>;
mouseEvents: Subject<boolean>;
// hashChange: Subject<boolean>;
};
animateTo$!: Subject<boolean>;
// TODO: Prefer composition to mixins...
// ObservablesMixin
getStartObservable!: () => Observable<Coord>;
getMoveObservable!: (
start: Observable<Coord>,
end: Observable<Coord>,
) => Observable<Coord>;
getEndObservable!: () => Observable<Coord>;
getIsSlidingObservable!: (
move: Observable<Coord>,
start: Observable<Coord>,
end: Observable<Coord>,
) => Observable<boolean>;
getIsSlidingObservableInner!: (
move: Observable<Coord>,
start: Observable<Coord>,
) => Observable<boolean>;
// CalcMixin
calcIsInRange!: (start: Coord, opened: boolean) => boolean;
calcIsSwipe!: (
start: Coord,
end: Coord,
translateX: number,
drawerWidth: number,
_: number,
) => boolean;
calcWillOpen!: (
start: {},
end: {},
translateX: number,
drawerWidth: number,
velocity: number,
) => boolean;
calcTranslateX!: (
move: Coord,
start: Coord,
startTranslateX: number,
drawerWidth: number,
) => number;
// UpdateMixin
updateDOM!: (translateX: number, drawerWidth: number) => void;
updater!: DOMUpdater;
// HyDrawer
getDrawerWidth(): Observable<number> {
const content$ = observeWidth(this.contentEl).pipe(
tap((px) => this.fireEvent("content-width-change", { detail: px })),
);
const peek$ = observeWidth(this.peekEl).pipe(
tap((px) => this.fireEvent("peek-width-change", { detail: px })),
);
return combineLatest([content$, peek$]).pipe(
// takeUntil(this.subjects.disconnect),
map(([contentWidth, peekWidth]) => contentWidth - peekWidth),
share(),
);
}
// private consolidateState() {
// const hashOpened = location.hash === this.hashId;
// const isReload = history.state && history.state[this.histId];
// if (isReload) {
// if (hashOpened !== this.opened) {
// this.opened = hashOpened;
// }
// } else {
// const url = new URL(location.href);
// const newState = { ...history.state, [this.histId]: { backable: false } };
// if (hashOpened && !this.opened) {
// url.hash = '';
// history.replaceState(newState, document.title, url.href);
// url.hash = this.hashId;
// history.pushState({ [this.histId]: { backable: true } }, document.title, url.href);
// this.opened = true;
// }
// else if (!hashOpened && this.opened) {
// history.replaceState(newState, document.title, url.href);
// url.hash = this.hashId;
// history.pushState({ [this.histId]: { backable: true } }, document.title, url.href);
// }
// else {
// history.replaceState(newState, document.title, url.href);
// }
// }
// }
connectedCallback() {
super.connectedCallback();
// if (this.hashChange) this.consolidateState()
this.$ = {
opened: new BehaviorSubject(this.opened),
side: new BehaviorSubject(this.side),
persistent: new BehaviorSubject(this.persistent),
preventDefault: new BehaviorSubject(this.noScroll),
mouseEvents: new BehaviorSubject(this.mouseEvents),
// hashChange: new BehaviorSubject(this.hashChange),
};
this.scrimClickable = this.opened;
this.animateTo$ = new Subject<boolean>();
this.updater = DOMUpdater.getUpdaterForPlatform(this);
this.updateComplete.then(this.upgrade);
}
#translateX$!: Observable<number>;
#startTranslateX$!: Observable<number>;
#tweenTranslateX$!: Observable<number>;
upgrade = () => {
const drawerWidth$ = this.getDrawerWidth();
const active$ = this.$.persistent.pipe(map((_) => !_));
const start$ = this.getStartObservable().pipe(
// takeUntil(this.subjects.disconnect),
filterWhen(active$),
share(),
);
const isScrimVisible$ = defer(() => {
return this.#translateX$.pipe(map((translateX) => translateX !== 0));
});
const isInRange$ = start$.pipe(
withLatestFrom(isScrimVisible$),
map((args) => this.calcIsInRange(...args)),
tap((inRange) => {
if (inRange) {
this.willChange = true;
this.fireEvent("prepare");
}
}),
share(),
);
const end$ = this.getEndObservable().pipe(
// takeUntil(this.subjects.disconnect),
filterWhen(active$, isInRange$),
tap(() => {
if (this.mouseEvents) this.grabbing = false;
}),
share(),
);
const move$ = this.getMoveObservable(start$, end$).pipe(
// takeUntil(this.subjects.disconnect),
filterWhen(active$, isInRange$),
share(),
);
const isSliding$ = this.getIsSlidingObservable(move$, start$, end$).pipe(
tap((isSliding) => {
this.isSliding = isSliding;
if (isSliding && this.mouseEvents) this.grabbing = true;
// if (isSliding) this.fireEvent('slidestart', { detail: this.opened });
}),
);
const translateX$ = (this.#translateX$ = defer(() => {
const jumpTranslateX$ = combineLatest([
this.$.opened,
this.$.side,
drawerWidth$,
]).pipe(
map(([opened, side, drawerWidth]) => {
return !opened ? 0 : drawerWidth * (side === "left" ? 1 : -1);
}),
);
const moveTranslateX$ = move$.pipe(
filterWhen(isSliding$),
tap(() => (this.scrimClickable = false)),
tap(({ event }) => event && this.noScroll && event.preventDefault()),
withLatestFrom(start$, this.#startTranslateX$, drawerWidth$),
map((args) => this.calcTranslateX(...args)),
);
return merge(this.#tweenTranslateX$, jumpTranslateX$, moveTranslateX$);
}).pipe(share()));
this.#startTranslateX$ = translateX$.pipe(sample(start$));
const velocity$ = translateX$.pipe(
timestamp(),
pairwise(),
filter(
([{ timestamp: prevTime }, { timestamp: time }]) => time - prevTime > 0,
),
map(
([
{ value: prevX, timestamp: prevTime },
{ value: x, timestamp: time },
]) => (x - prevX) / (time - prevTime),
),
startWith(0),
);
const willOpen$ = end$.pipe(
withLatestFrom(start$, translateX$, drawerWidth$, velocity$),
filter((args) => this.calcIsSwipe(...args)),
map((args) => this.calcWillOpen(...args)),
// TODO: only fire `slideend` event when slidestart fired as well?
// tap(willOpen => this.fireEvent('slideend', { detail: willOpen })),
);
const animateTo$ = this.animateTo$.pipe(
tap(() => {
this.willChange = true;
this.fireEvent("prepare");
}),
);
this.#tweenTranslateX$ = merge(willOpen$, animateTo$).pipe(
withLatestFrom(translateX$, drawerWidth$),
switchMap(([willOpen, translateX, drawerWidth]) => {
const inv = this.side === "left" ? 1 : -1;
const endTranslateX = willOpen ? drawerWidth * inv : 0;
const diffTranslateX = endTranslateX - translateX;
const duration = Math.ceil(
BASE_DURATION + drawerWidth * WIDTH_CONTRIBUTION,
);
return tween(easeOutSine, translateX, diffTranslateX, duration).pipe(
finalize(() => {
this.transitioned(willOpen);
}),
takeUntil(start$),
takeUntil(this.$.side.pipe(skip(1))),
share(),
);
}),
);
translateX$
.pipe(
withLatestFrom(drawerWidth$),
tap((args) => {
this.updateDOM(...args);
const { translateX, opacity } = this;
this.fireEvent("move", {
detail: { translateX, opacity },
bubbles: false,
});
}),
)
.subscribe();
fromEvent(this.scrimEl, "click")
.pipe(
// takeUntil(this.subjects.disconnect),
tap(() => this.close()),
)
.subscribe();
active$
.pipe(
// takeUntil(this.subjects.disconnect),
tap((active) => {
this.scrimEl.style.display = active ? "block" : "none";
}),
)
.subscribe();
this.$.mouseEvents
.pipe(
// takeUntil(this.subjects.disconnect),
switchMap((mouseEvents) => {
return mouseEvents ? start$.pipe(withLatestFrom(isInRange$)) : NEVER;
}),
filter(([coord, isInRange]) => isInRange && coord.event != null),
tap(([{ event }]) => event && event.preventDefault()),
)
.subscribe();
// fromEvent(window, 'hashchange').pipe(
// // takeUntil(this.subjects.disconnect),
// subscribeWhen(this.$.hashChange),
// tap(() => {
// const opened = location.hash === this.hashId;
// if (!history.state && opened) {
// history.replaceState({ [this.histId]: { backable: true } }, document.title)
// }
// // If the state doesn't match open/close the drawer
// if (opened !== this.opened) this.animateTo$.next(opened);
// }),
// ).subscribe();
this.fireEvent("init", { detail: this.opened });
this.#initialized.resolve(this);
};
private transitioned = (hasOpened: boolean) => {
this.opened = this.scrimClickable = hasOpened;
this.willChange = false;
// if (this.hashChange) this.transitionedHash(hasOpened)
this.fireEvent("transitioned", { detail: hasOpened });
};
// private transitionedHash(hasOpened: boolean) {
// const hasClosed = !hasOpened;
// const { backable } = history.state && history.state[this.histId] || { backable: false }
// if (hasClosed && backable) {
// history.back()
// }
// if (hasOpened && location.hash !== this.hashId) {
// location.hash = this.hashId;
// }
// }
render() {
return html`
<div class="peek full-height"></div>
<div
class="scrim"
style=${styleMap({
willChange: this.willChange ? "opacity" : "",
pointerEvents: this.scrimClickable ? "all" : "",
})}
></div>
${
this.mouseEvents && this.grabbing && !this.scrimClickable
? html`<div class="grabbing-screen full-screen"></div>`
: null
}
<div
class=${classMap({
wrapper: true,
"full-height": true,
[this.side]: true,
grab: this.mouseEvents,
grabbing: this.mouseEvents && this.grabbing,
})}
style=${styleMap({
willChange: this.willChange ? "transform" : "",
})}
>
<div class="overflow">
<slot></slot>
</div>
</div>
`;
}
open() {
this.animateTo$.next(true);
}
close() {
this.animateTo$.next(false);
}
toggle() {
this.animateTo$.next(!this.opened);
}
}

View File

@@ -0,0 +1,161 @@
import {
Observable,
combineLatest,
fromEvent,
merge,
NEVER,
ObservedValueOf,
} from "rxjs";
import {
// tap,
filter,
map,
mapTo,
repeatWhen,
skipWhile,
startWith,
switchMap,
take,
withLatestFrom,
} from "rxjs/operators";
import { subscribeWhen } from "./common";
const abs = Math.abs.bind(Math);
export type Coord = {
readonly target: EventTarget;
readonly clientX: number;
readonly clientY: number;
readonly pageX: number;
readonly pageY: number;
readonly screenX: number;
readonly screenY: number;
event?: Event;
};
export class ObservablesMixin {
$!: {
mouseEvents: Observable<boolean>;
preventDefault: Observable<boolean>;
};
threshold!: number;
noScroll!: boolean;
getStartObservable() {
return combineLatest([this.$.mouseEvents]).pipe(
switchMap(([mouseEvents]) => {
const touchstart$ = (<Observable<TouchEvent>>(
fromEvent(document, "touchstart", { passive: true })
)).pipe(
filter(({ touches }) => touches.length === 1),
map(({ touches }) => touches[0] as Coord),
);
const mousedown$ = !mouseEvents
? NEVER
: (<Observable<MouseEvent>>fromEvent(document, "mousedown")).pipe(
map((e) => (((e as Coord).event = e), e as Coord)),
);
return merge(touchstart$, mousedown$);
}),
);
}
getMoveObservable(start$: Observable<Coord>, end$: Observable<Coord>) {
return combineLatest([this.$.mouseEvents, this.$.preventDefault]).pipe(
switchMap(([mouseEvents, preventDefault]) => {
const touchmove$ = (<Observable<TouchEvent>>(
fromEvent(document, "touchmove", { passive: !preventDefault })
)).pipe(
map(
(e) => (((e.touches[0] as Coord).event = e), e.touches[0] as Coord),
),
);
const mousemove$ = !mouseEvents
? NEVER
: (<Observable<MouseEvent>>(
fromEvent(document, "mousemove", { passive: !preventDefault })
)).pipe(
subscribeWhen(
merge(start$.pipe(mapTo(true)), end$.pipe(mapTo(false))),
),
map((e) => (((e as Coord).event = e), e as Coord)),
);
return merge(touchmove$, mousemove$);
}),
);
}
getEndObservable() {
return combineLatest([this.$.mouseEvents]).pipe(
switchMap(([mouseEvents]) => {
const touchend$ = (<Observable<TouchEvent>>(
fromEvent(document, "touchend", { passive: true })
)).pipe(
filter(({ touches }) => touches.length === 0),
map((event) => event.changedTouches[0] as Coord),
);
const mouseup$ = !mouseEvents
? NEVER
: (<Observable<MouseEvent>>(
fromEvent(document, "mouseup", { passive: true })
)).pipe(map((e) => (((e as Coord).event = e), e as Coord)));
return merge(touchend$, mouseup$);
}),
);
}
getIsSlidingObservable(
move$: Observable<Coord>,
start$: Observable<Coord>,
end$: Observable<Coord>,
) {
return this.getIsSlidingObservableInner(move$, start$).pipe(
take(1),
startWith(undefined),
repeatWhen(() => end$),
);
}
getIsSlidingObservableInner(
move$: Observable<Coord>,
start$: Observable<Coord>,
) {
if (this.threshold) {
return move$.pipe(
withLatestFrom(start$),
skipWhile(
([{ clientX, clientY }, { clientX: startX, clientY: startY }]) =>
abs(startY - clientY) < this.threshold &&
abs(startX - clientX) < this.threshold,
),
map(
([{ clientX, clientY }, { clientX: startX, clientY: startY }]) =>
abs(startX - clientX) >= abs(startY - clientY),
),
);
} else {
return move$.pipe(
withLatestFrom(start$),
map(
([
{ clientX, clientY, event },
{ clientX: startX, clientY: startY },
]) => {
const isSliding = abs(startX - clientX) >= abs(startY - clientY);
if (this.noScroll && isSliding && event) event.preventDefault();
return isSliding;
},
),
);
}
}
}

View File

@@ -0,0 +1,104 @@
import { css } from "lit";
export const styles = css`
@media screen {
:host {
touch-action: pan-x;
}
.full-screen {
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 100vw;
}
.full-height {
position: fixed;
top: 0;
height: 100vh;
}
.peek {
left: 0;
width: var(--hy-drawer-peek-width, 0px);
visibility: hidden;
z-index: calc(var(--hy-drawer-z-index, 100) - 1);
}
.scrim {
position: fixed;
top: 0;
left: 0;
height: 10vh;
width: 10vw;
transform: scale(10);
transform-origin: top left;
opacity: 0;
pointer-events: none;
background: var(--hy-drawer-scrim-background, rgba(0, 0, 0, 0.5));
z-index: var(--hy-drawer-z-index, 100);
-webkit-tap-highlight-color: transparent;
}
.range {
position: fixed;
top: 0;
height: 100vh;
z-index: calc(var(--hy-drawer-z-index, 100) + 1);
}
.grabbing-screen {
cursor: grabbing;
z-index: calc(var(--hy-drawer-z-index, 100) + 2);
}
.wrapper {
width: var(--hy-drawer-width, 300px);
background: var(--hy-drawer-background, inherit);
box-shadow: var(--hy-drawer-box-shadow, 0 0 15px rgba(0, 0, 0, 0.25));
z-index: calc(var(--hy-drawer-z-index, 100) + 3);
contain: strict;
}
.wrapper.left {
left: calc(-1 * var(--hy-drawer-width, 300px) + var(--hy-drawer-peek-width, 0px));
}
.wrapper.right {
right: calc(-1 * var(--hy-drawer-width, 300px) + var(--hy-drawer-peek-width, 0px));
}
.wrapper > .overflow {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
}
.grab {
cursor: move;
cursor: grab;
}
.grabbing {
cursor: grabbing;
}
}
@media print {
.scrim {
display: none !important;
}
.wrapper {
transform: none !important;
}
}
`;

View File

@@ -0,0 +1,79 @@
export type CallbackValue = { translateX: number; opacity: number };
export class UpdateMixin {
contentEl!: HTMLElement;
scrimEl!: HTMLElement;
translateX!: number;
side!: string;
opacity!: number;
updater!: DOMUpdater;
updateDOM(translateX: number, drawerWidth: number) {
const inv = this.side === "left" ? 1 : -1;
const opacity = (translateX / drawerWidth) * inv || 0;
this.translateX = translateX;
this.opacity = opacity;
this.updater.updateDOM(translateX, opacity);
}
}
export abstract class DOMUpdater {
static getUpdaterForPlatform(parent: UpdateMixin) {
const hasCSSOM =
"attributeStyleMap" in Element.prototype &&
"CSS" in window &&
"number" in CSS;
return hasCSSOM
? new AttributeStyleMapUpdater(parent)
: new StyleUpdater(parent);
}
private parent: UpdateMixin;
constructor(parent: UpdateMixin) {
this.parent = parent;
}
get contentEl() {
return this.parent.contentEl;
}
get scrimEl() {
return this.parent.scrimEl;
}
abstract updateDOM(translateX: number, opacity: number): void;
}
export class StyleUpdater extends DOMUpdater {
constructor(parent: UpdateMixin) {
super(parent);
}
updateDOM(translateX: number, opacity: number) {
this.contentEl.style.transform = `translate(${translateX}px, 0px)`;
this.scrimEl.style.opacity = `${opacity}`;
}
}
export class AttributeStyleMapUpdater extends DOMUpdater {
private tvalue: CSSTransformValue;
private ovalue: CSSUnitValue;
constructor(parent: UpdateMixin) {
super(parent);
this.tvalue = new CSSTransformValue([
new CSSTranslate(CSS.px(0), CSS.px(0)),
]);
this.ovalue = CSS.number(1);
}
updateDOM(translateX: number, opacity: number) {
((this.tvalue[0] as CSSTranslate).x as CSSUnitValue).value = translateX;
this.ovalue.value = opacity;
this.contentEl.attributeStyleMap.set("transform", this.tvalue);
this.scrimEl.attributeStyleMap.set("opacity", this.ovalue);
}
}