supported hover and press actions

commit_hash:dfd0403da40ba5f5d7de502611b918ad8d37d98e
This commit is contained in:
babaevmm
2025-06-02 11:29:08 +03:00
parent 5f6322b210
commit d56d50116d
7 changed files with 139 additions and 27 deletions
@@ -63,4 +63,8 @@ extension [DivAction] {
public func uiActions(context: DivBlockModelingContext) -> [UserInterfaceAction] {
compactMap { $0.uiAction(context: context) }
}
func nonEmptyUIActions(context: DivBlockModelingContext) -> NonEmptyArray<UserInterfaceAction>? {
NonEmptyArray(uiActions(context: context))
}
}
@@ -7,6 +7,10 @@ protocol DivActionsHolder {
var actionAnimation: DivAnimation { get }
var doubletapActions: [DivAction]? { get }
var longtapActions: [DivAction]? { get }
var pressStartActions: [DivAction]? { get }
var pressEndActions: [DivAction]? { get }
var hoverStartActions: [DivAction]? { get }
var hoverEndActions: [DivAction]? { get }
func resolveCaptureFocusOnAction(_ resolver: ExpressionResolver) -> Bool
}
@@ -31,24 +35,6 @@ extension DivActionsHolder {
return NonEmptyArray(allActions)
}
fileprivate func makeDoubleTapActions(
context: DivBlockModelingContext
) -> NonEmptyArray<UserInterfaceAction>? {
if let actions = doubletapActions?.uiActions(context: context) {
return NonEmptyArray(actions)
}
return nil
}
fileprivate func makeLongTapActions(
context: DivBlockModelingContext
) -> LongTapActions? {
if let actions = longtapActions?.uiActions(context: context) {
return NonEmptyArray(actions).map(LongTapActions.actions)
}
return nil
}
}
extension Block {
@@ -62,9 +48,17 @@ extension Block {
}
let actions = actionsHolder.makeActions(context: context)
let doubletapActions = actionsHolder.makeDoubleTapActions(context: context)
let longtapActions = actionsHolder.makeLongTapActions(context: context)
if actions == nil, doubletapActions == nil, longtapActions == nil {
let doubletapActions = actionsHolder.doubletapActions?.nonEmptyUIActions(context: context)
let longtapActions = actionsHolder.longtapActions?.nonEmptyUIActions(context: context)
.map(LongTapActions.actions)
let pressStartActions = actionsHolder.pressStartActions?.nonEmptyUIActions(context: context)
let pressEndActions = actionsHolder.pressEndActions?.nonEmptyUIActions(context: context)
let hoverStartActions = actionsHolder.hoverStartActions?.nonEmptyUIActions(context: context)
let hoverEndActions = actionsHolder.hoverEndActions?.nonEmptyUIActions(context: context)
if actions == nil, doubletapActions == nil, longtapActions == nil,
pressStartActions == nil, pressEndActions == nil,
hoverStartActions == nil, hoverEndActions == nil {
return self
}
@@ -75,6 +69,10 @@ extension Block {
.resolveActionAnimation(context.expressionResolver),
doubleTapActions: doubletapActions,
longTapActions: longtapActions,
pressStartActions: pressStartActions,
pressEndActions: pressEndActions,
hoverStartActions: hoverStartActions,
hoverEndActions: hoverEndActions,
path: context.path,
captureFocusOnAction: actionsHolder.resolveCaptureFocusOnAction(context.expressionResolver)
)
@@ -22,6 +22,10 @@ extension Block {
actionAnimation: ActionAnimation? = nil,
doubleTapActions: NonEmptyArray<UserInterfaceAction>? = nil,
longTapActions: LongTapActions? = nil,
pressStartActions: NonEmptyArray<UserInterfaceAction>? = nil,
pressEndActions: NonEmptyArray<UserInterfaceAction>? = nil,
hoverStartActions: NonEmptyArray<UserInterfaceAction>? = nil,
hoverEndActions: NonEmptyArray<UserInterfaceAction>? = nil,
analyticsURL: URL? = nil,
visibilityParams: VisibilityParams? = nil,
tooltips: [BlockTooltip]? = nil,
@@ -70,6 +74,10 @@ extension Block {
actionAnimation: actionAnimation ?? block.actionAnimation,
doubleTapActions: doubleTapActions ?? block.doubleTapActions,
longTapActions: longTapActions ?? block.longTapActions,
pressStartActions: pressStartActions ?? block.pressStartActions,
pressEndActions: pressEndActions ?? block.pressEndActions,
hoverStartActions: hoverStartActions ?? block.hoverStartActions,
hoverEndActions: hoverEndActions ?? block.hoverEndActions,
analyticsURL: (analyticsURL ?? block.analyticsURL) as URL?,
boundary: boundary ?? block.boundary,
border: (border ?? block.border) as BlockBorder?,
@@ -99,6 +107,10 @@ extension Block {
actionAnimation: actionAnimation,
doubleTapActions: doubleTapActions,
longTapActions: longTapActions,
pressStartActions: pressStartActions,
pressEndActions: pressEndActions,
hoverStartActions: hoverStartActions,
hoverEndActions: hoverEndActions,
analyticsURL: analyticsURL,
boundary: boundary ?? DecoratingBlock.defaultBoundary,
border: border,
@@ -134,6 +146,10 @@ extension Block {
actionAnimation: ActionAnimation? = nil,
doubleTapActions: NonEmptyArray<UserInterfaceAction>? = nil,
longTapActions: LongTapActions? = nil,
pressStartActions: NonEmptyArray<UserInterfaceAction>? = nil,
pressEndActions: NonEmptyArray<UserInterfaceAction>? = nil,
hoverStartActions: NonEmptyArray<UserInterfaceAction>? = nil,
hoverEndActions: NonEmptyArray<UserInterfaceAction>? = nil,
analyticsURL: URL? = nil,
shadow: BlockShadow? = nil,
visibilityParams: VisibilityParams? = nil,
@@ -157,6 +173,10 @@ extension Block {
actionAnimation: actionAnimation,
doubleTapActions: doubleTapActions,
longTapActions: longTapActions,
pressStartActions: pressStartActions,
pressEndActions: pressEndActions,
hoverStartActions: hoverStartActions,
hoverEndActions: hoverEndActions,
analyticsURL: analyticsURL,
visibilityParams: visibilityParams,
tooltips: tooltips,
@@ -16,6 +16,10 @@ final class DecoratingBlock: WrapperBlock {
let actionAnimation: ActionAnimation?
let doubleTapActions: NonEmptyArray<UserInterfaceAction>?
let longTapActions: LongTapActions?
let pressStartActions: NonEmptyArray<UserInterfaceAction>?
let pressEndActions: NonEmptyArray<UserInterfaceAction>?
let hoverStartActions: NonEmptyArray<UserInterfaceAction>?
let hoverEndActions: NonEmptyArray<UserInterfaceAction>?
let analyticsURL: URL?
let boundary: BoundaryTrait
let border: BlockBorder?
@@ -39,6 +43,10 @@ final class DecoratingBlock: WrapperBlock {
actionAnimation: ActionAnimation? = nil,
doubleTapActions: NonEmptyArray<UserInterfaceAction>? = nil,
longTapActions: LongTapActions? = nil,
pressStartActions: NonEmptyArray<UserInterfaceAction>? = nil,
pressEndActions: NonEmptyArray<UserInterfaceAction>? = nil,
hoverStartActions: NonEmptyArray<UserInterfaceAction>? = nil,
hoverEndActions: NonEmptyArray<UserInterfaceAction>? = nil,
analyticsURL: URL? = nil,
boundary: BoundaryTrait = DecoratingBlock.defaultBoundary,
border: BlockBorder? = nil,
@@ -61,6 +69,10 @@ final class DecoratingBlock: WrapperBlock {
self.actionAnimation = actionAnimation
self.doubleTapActions = doubleTapActions
self.longTapActions = longTapActions
self.pressStartActions = pressStartActions
self.pressEndActions = pressEndActions
self.hoverStartActions = hoverStartActions
self.hoverEndActions = hoverEndActions
self.analyticsURL = analyticsURL
self.boundary = boundary
self.border = border
@@ -118,6 +130,10 @@ final class DecoratingBlock: WrapperBlock {
&& actions == other.actions
&& longTapActions == other.longTapActions
&& doubleTapActions == other.doubleTapActions
&& pressStartActions == other.pressStartActions
&& pressEndActions == other.pressEndActions
&& hoverStartActions == other.hoverStartActions
&& hoverEndActions == other.hoverEndActions
&& actionAnimation == other.actionAnimation
&& analyticsURL == other.analyticsURL
&& boundary == other.boundary
@@ -154,6 +170,10 @@ extension DecoratingBlock {
actionAnimation: ActionAnimation? = nil,
doubleTapActions: NonEmptyArray<UserInterfaceAction>? = nil,
longTapActions: LongTapActions? = nil,
pressStartActions: NonEmptyArray<UserInterfaceAction>? = nil,
pressEndActions: NonEmptyArray<UserInterfaceAction>? = nil,
hoverStartActions: NonEmptyArray<UserInterfaceAction>? = nil,
hoverEndActions: NonEmptyArray<UserInterfaceAction>? = nil,
analyticsURL: URL?? = nil,
boundary: BoundaryTrait? = nil,
border: BlockBorder?? = nil,
@@ -177,6 +197,10 @@ extension DecoratingBlock {
actionAnimation: actionAnimation ?? self.actionAnimation,
doubleTapActions: doubleTapActions ?? self.doubleTapActions,
longTapActions: longTapActions ?? self.longTapActions,
pressStartActions: pressStartActions ?? self.pressStartActions,
pressEndActions: pressEndActions ?? self.pressEndActions,
hoverStartActions: hoverStartActions ?? self.hoverStartActions,
hoverEndActions: hoverEndActions ?? self.hoverEndActions,
analyticsURL: analyticsURL ?? self.analyticsURL,
boundary: boundary ?? self.boundary,
border: border ?? self.border,
@@ -37,6 +37,10 @@ extension DecoratingBlock {
actionAnimation: actionAnimation,
doubleTapActions: doubleTapActions,
longTapActions: longTapActions,
pressStartActions: pressStartActions,
pressEndActions: pressEndActions,
hoverStartActions: hoverStartActions,
hoverEndActions: hoverEndActions,
analyticsURL: analyticsURL,
boundary: boundary,
border: border,
@@ -113,6 +117,10 @@ private final class DecoratingView: UIControl, BlockViewProtocol, VisibleBoundsT
let actionAnimation: ActionAnimation?
let doubleTapActions: NonEmptyArray<UserInterfaceAction>?
let longTapActions: LongTapActions?
let pressStartActions: NonEmptyArray<UserInterfaceAction>?
let pressEndActions: NonEmptyArray<UserInterfaceAction>?
let hoverStartActions: NonEmptyArray<UserInterfaceAction>?
let hoverEndActions: NonEmptyArray<UserInterfaceAction>?
let analyticsURL: URL?
let boundary: BoundaryTrait
let border: BlockBorder?
@@ -143,6 +151,22 @@ private final class DecoratingView: UIControl, BlockViewProtocol, VisibleBoundsT
var shouldHandleDoubleTap: Bool {
doubleTapActions != nil
}
var shouldHandlePress: Bool {
pressStartActions != nil || pressEndActions != nil
}
var shouldHandleHover: Bool {
hoverStartActions != nil || hoverEndActions != nil
}
var shouldHandleAnyAction: Bool {
shouldHandleTap ||
shouldHandleLongTap ||
shouldHandleDoubleTap ||
shouldHandlePress ||
shouldHandleHover
}
}
fileprivate var model: Model!
@@ -183,6 +207,13 @@ private final class DecoratingView: UIControl, BlockViewProtocol, VisibleBoundsT
}
}
private var hoverRecognizer: UIHoverGestureRecognizer? {
didSet {
oldValue.flatMap(removeGestureRecognizer(_:))
hoverRecognizer.flatMap(addGestureRecognizer(_:))
}
}
override var isHighlighted: Bool {
didSet {
guard oldValue != isHighlighted else {
@@ -239,10 +270,11 @@ private final class DecoratingView: UIControl, BlockViewProtocol, VisibleBoundsT
}
private func configureRecognizers() {
guard model.shouldHandleTap else {
guard model.shouldHandleAnyAction else {
tapRecognizer?.isEnabled = false
doubleTapRecognizer?.isEnabled = false
longPressRecognizer?.isEnabled = false
hoverRecognizer?.isEnabled = false
return
}
@@ -265,9 +297,17 @@ private final class DecoratingView: UIControl, BlockViewProtocol, VisibleBoundsT
)
}
if model.shouldHandleHover, hoverRecognizer == nil {
hoverRecognizer = UIHoverGestureRecognizer(
target: self,
action: #selector(handleHover)
)
}
tapRecognizer?.isEnabled = model.shouldHandleTap
doubleTapRecognizer?.isEnabled = model.shouldHandleDoubleTap
longPressRecognizer?.isEnabled = model.shouldHandleLongTap
hoverRecognizer?.isEnabled = model.shouldHandleHover
}
private func checkTouchableArea() {
@@ -315,7 +355,7 @@ private final class DecoratingView: UIControl, BlockViewProtocol, VisibleBoundsT
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let result = super.hitTest(point, with: event)
if result === self {
return model.shouldHandleTap ? self : nil
return model.shouldHandleAnyAction ? self : nil
} else {
return result
}
@@ -365,6 +405,16 @@ private final class DecoratingView: UIControl, BlockViewProtocol, VisibleBoundsT
updateVoiceOverFocus()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
model.pressStartActions?.asArray().perform(sendingFrom: self)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
model.pressEndActions?.asArray().perform(sendingFrom: self)
}
func configure(
model: Model,
observer: ElementStateObserver?,
@@ -549,6 +599,17 @@ private final class DecoratingView: UIControl, BlockViewProtocol, VisibleBoundsT
}
}
@objc private func handleHover(recognizer: UIHoverGestureRecognizer) {
switch recognizer.state {
case .began:
model.hoverStartActions?.asArray().perform(sendingFrom: self)
case .ended, .cancelled, .failed:
model.hoverEndActions?.asArray().perform(sendingFrom: self)
default:
break
}
}
deinit {
if model?.tooltips.isEmpty == false {
renderingDelegate?.tooltipAnchorViewRemoved(anchorView: self)
+8 -4
View File
@@ -43,7 +43,8 @@
"$description": "translations.json#/div_actionable_press_start_actions",
"platforms": [
"web",
"android"
"android",
"ios"
]
},
"press_end_actions": {
@@ -54,7 +55,8 @@
"$description": "translations.json#/div_actionable_press_end_actions",
"platforms": [
"web",
"android"
"android",
"ios"
]
},
"hover_start_actions": {
@@ -65,7 +67,8 @@
"$description": "translations.json#/div_actionable_hover_start_actions",
"platforms": [
"web",
"android"
"android",
"ios"
]
},
"hover_end_actions": {
@@ -76,7 +79,8 @@
"$description": "translations.json#/div_actionable_hover_end_actions",
"platforms": [
"web",
"android"
"android",
"ios"
]
},
"action_animation": {
@@ -135,6 +135,7 @@
],
"platforms": [
"android",
"ios",
"web"
],
"file": "actions/hover-and-press.json"