LeCodesdocs

UIWidget

Плавающий оверлей, живущий вне системы экранов: всегда position-fixed относительно устройства, рисуется над активным экраном и не тронут навигацией Router. Используй для нижних шторок, диалогов, тостов-с-действиями, плавающих плееров. Создай виджет один раз на уровне модуля и переиспользуй — видимость императивна.

Обзор

TypeScript
const dialog = UIWidget([
  UIText("Delete item?").style({ fontWeight: 700, fontSize: 18, color: "black" }),
  UIButton([ UIText("Delete").style({ color: "white" }) ])
    .style({ bgColor: "#FF4032", borderRadius: 8, height: 40, justifyContent: "center" })
    .onClick(() => { doDelete(); dialog.hide() }),
])
.style({ left: 24, right: 24, top: "40%", p: 16, gap: 12, borderRadius: 16,
         bgColor: "white", overlayColor: "rgba(0,0,0,0.5)" })
.onOverlayTap(() => dialog.hide())
.onBackPressed(() => dialog.hide())

// где угодно, на любом экране:
dialog.show()

Создание и видимость

TypeScript
UIWidget(): UIWidget
UIWidget(children: UINodeChild[]): UIWidget

widget.show(): void        // смонтировать и показать (по умолчанию скрыт)
widget.hide(): void        // размонтировать
widget.isShow: boolean     // геттер — показан ли сейчас?

Полная поверхность контейнера: .append / .insert / .remove / .setContent, .style, .onLayout. Позиционируй его через top / left / right / bottom / width / height — координаты в пространстве экрана устройства (значения safe-area вроде "safe-bottom" работают).

Note

виджет сохраняется через Router.push / pop. Если он принадлежит одному экрану, спаруй его с жизненным циклом экрана: .onOpen(() => sheet.show()), .onClose(() => sheet.hide()).

Модальное поведение — overlayColor

TypeScript
widget.style({ overlayColor: "rgba(0, 0, 0, 0.5)" })   // Color | null
widget.onOverlayTap(cb: () => void): this

overlayColor добавляет полноэкранный scrim за виджетом, блокирующий все тапы под ним — это и превращает виджет в модалку (диалог / нижнюю шторку). "transparent" невидим, но всё равно перехватывает; null (по умолчанию) убирает слой целиком. Тап по scrim'у вызывает onOverlayTap — обычно () => widget.hide().

Касания и кнопка «назад»

TypeScript
widget.onTouchStart(ev => …)           // ev.track({...}) для жестов перетаскивания (перетаскивание шторки)
widget.onBackPressed(cb: () => void)   // Android «назад», пока виджет поднят — обычно hide()

Виджеты — один из трёх элементов, принимающих касания (с UIButton и UIScreen) — см. События указателя.

Анимации выхода — animateTo с commit: false

TypeScript
widget.animateTo({ overlayColor, opacity, transform, …, duration?, delay?, commit?: boolean })
widget.animateFrom({ … })              // анимировать от заданных значений к текущему стилю

animateTo обычно пишет целевые значения в стиль виджета при старте. Для анимации выхода это неверно — следующий show() стартовал бы с затухших значений. Передай commit: false, чтобы проиграть анимацию без сохранения, и hide() по завершении:

TypeScript
const close = () => {
  widget.animateTo({ opacity: 0, commit: false, duration: 250 })
  setTimeout(() => widget.hide(), 250)   // стиль всё ещё с opacity 1 для следующего show()
}

Нижняя шторка — канонический виджет

Шторка drag-to-dismiss: позиция управляется transform, scrim через overlayColor, перетаскивание захвачено claim: "pan-y".

TypeScript
let pos = 0, height = 0

const slideTo = (to: number, done?: () => void) => animate({
  from: pos, to, duration: 300, easing: cubicBezier(0.2, 0, 0.2, 1),
  onUpdate(v) { pos = v; sheet.style.transform = `translateY(${v}px)` },
  onComplete() { done?.() },
})

const close = () => {
  slideTo(height, () => sheet.hide())                              // выехать, затем размонтировать
  sheet.animateTo({ overlayColor: "transparent", commit: false })  // затухание scrim'а, не сохранять
}

const sheet = UIWidget([
  UIColumn([]).style({ width: 50, height: 6, borderRadius: 3, bgColor: "#D9D9D9", mx: "auto", my: 12 }),
  UIText("Title").style({ px: 16, fontWeight: 700, fontSize: 20, color: "black" }),
])
.style({ bgColor: "white", borderRadius: 20, bottom: 0, left: 0, right: 0, height: 400,
         overlayColor: "rgba(0, 0, 0, 0.5)" })
.onTouchStart(ev => ev.track({
  onMove({ deltaY }) { pos = Math.max(0, pos + deltaY); sheet.style.transform = `translateY(${pos}px)` },
  onEnd() { pos > height * 0.3 ? close() : slideTo(0) },
  claim: "pan-y",              // выиграть вертикальный жест у любого скроллера внутри
}))
.onOverlayTap(close)
.onBackPressed(close)
.onLayout(l => { height = l.height })

Подводные камни

TypeScript
// ✗ пересоздание виджета на каждый экран / на каждый показ
const openSheet = () => UIWidget([...]).show()     // течёт новый виджет на каждый вызов
// ✓ создай один раз на уровне модуля, show()/hide() тот же экземпляр

// ✗ ждать, что он появится при создании
const w = UIWidget([...])                          // скрыт до w.show()

Смотрите также