Merge branch 'mbruce/bugfixes' into 'develop'

fixed bug for trailing tooltip

See merge request BPHV_MIPS/vds_ios!100
This commit is contained in:
Bruce, Matt R 2023-08-02 16:10:19 +00:00
commit 9c358c39ce
29 changed files with 270 additions and 188 deletions

View File

@ -128,12 +128,23 @@ open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoab
/// Update this view based off of property changes /// Update this view based off of property changes
open func updateView() { open func updateView() {
updateAccessibilityLabel() updateAccessibility()
} }
/// Used to update any Accessibility properties /// Used to update any Accessibility properties
open func updateAccessibilityLabel() { open func updateAccessibility() {
if isSelected {
accessibilityTraits.insert(.selected)
} else {
accessibilityTraits.remove(.selected)
}
if isEnabled {
accessibilityTraits.remove(.notEnabled)
} else {
accessibilityTraits.insert(.notEnabled)
}
} }
/// Resets to the Controls default values /// Resets to the Controls default values

View File

@ -88,18 +88,7 @@ open class SelectorBase: Control, SelectorControlable {
layoutIfNeeded() layoutIfNeeded()
} }
open override func updateAccessibilityLabel() { open override func updateAccessibility() {
accessibilityValue = isSelected ? "1" : "0" super.updateAccessibility()
if !accessibilityTraits.contains(.selected) && isSelected {
accessibilityTraits.insert(.selected)
} else if accessibilityTraits.contains(.selected) && !isSelected{
accessibilityTraits.remove(.selected)
}
if !accessibilityTraits.contains(.notEnabled) && !isEnabled {
accessibilityTraits.insert(.notEnabled)
} else if accessibilityTraits.contains(.notEnabled) && !isEnabled {
accessibilityTraits.remove(.notEnabled)
}
} }
} }

View File

@ -69,18 +69,8 @@ open class SelectorGroupHandlerBase<HandlerType: Control>: Control, Changeable {
selectorViews.forEach{ $0.reset() } selectorViews.forEach{ $0.reset() }
} }
open override func updateAccessibilityLabel() { open override func updateAccessibility() {
if !accessibilityTraits.contains(.selected) && isSelected { super.updateAccessibility()
accessibilityTraits.insert(.selected)
} else if accessibilityTraits.contains(.selected) && !isSelected{
accessibilityTraits.remove(.selected)
}
if !accessibilityTraits.contains(.notEnabled) && !isEnabled {
accessibilityTraits.insert(.notEnabled)
} else if accessibilityTraits.contains(.notEnabled) && !isEnabled {
accessibilityTraits.remove(.notEnabled)
}
setAccessibilityLabel(for: selectorViews) setAccessibilityLabel(for: selectorViews)
} }
} }
@ -92,8 +82,8 @@ open class SelectorGroupSelectedHandlerBase<HandlerType: Control>: SelectorGroup
return selectorViews.filter { $0.isSelected == true }.first return selectorViews.filter { $0.isSelected == true }.first
} }
open override func updateAccessibilityLabel() { open override func updateAccessibility() {
super.updateAccessibilityLabel() super.updateAccessibility()
if let selectedHandler, let value = selectedHandler.accessibilityValue, let label = selectedHandler.accessibilityLabel { if let selectedHandler, let value = selectedHandler.accessibilityValue, let label = selectedHandler.accessibilityLabel {
accessibilityValue = "\(label) \(value)" accessibilityValue = "\(label) \(value)"
} else { } else {

View File

@ -267,22 +267,11 @@ open class SelectorItemBase<Selector: SelectorControlable>: Control, Errorable,
selectorView.isHighlighted = isHighlighted selectorView.isHighlighted = isHighlighted
selectorView.disabled = disabled selectorView.disabled = disabled
selectorView.surface = surface selectorView.surface = surface
updateAccessibilityLabel() updateAccessibility()
} }
open override func updateAccessibilityLabel() { open override func updateAccessibility() {
accessibilityValue = isSelected ? "1" : "0" super.updateAccessibility()
if !accessibilityTraits.contains(.selected) && isSelected {
accessibilityTraits.insert(.selected)
} else if accessibilityTraits.contains(.selected) && !isSelected{
accessibilityTraits.remove(.selected)
}
if !accessibilityTraits.contains(.notEnabled) && !isEnabled {
accessibilityTraits.insert(.notEnabled)
} else if accessibilityTraits.contains(.notEnabled) && !isEnabled {
accessibilityTraits.remove(.notEnabled)
}
setAccessibilityLabel(for: [label, childLabel, errorLabel]) setAccessibilityLabel(for: [label, childLabel, errorLabel])
} }
} }

View File

@ -82,12 +82,16 @@ open class View: UIView, Handlerable, ViewProtocol, Resettable, UserInfoable {
/// Update this view based off of property changes /// Update this view based off of property changes
open func updateView() { open func updateView() {
updateAccessibilityLabel() updateAccessibility()
} }
/// Used to update any Accessibility properties /// Used to update any Accessibility properties
open func updateAccessibilityLabel() { open func updateAccessibility() {
if isEnabled {
accessibilityTraits.remove(.notEnabled)
} else {
accessibilityTraits.insert(.notEnabled)
}
} }
/// Resets to the Views default values /// Resets to the Views default values

View File

@ -158,14 +158,14 @@ open class ButtonBase: UIButton, Buttonable, Handlerable, ViewProtocol, Resettab
open func updateView() { open func updateView() {
updateLabel() updateLabel()
updateAccessibilityLabel() updateAccessibility()
} }
open func updateAccessibilityLabel() { open func updateAccessibility() {
if !accessibilityTraits.contains(.notEnabled) && !isEnabled { if isEnabled {
accessibilityTraits.insert(.notEnabled)
} else if accessibilityTraits.contains(.notEnabled) && !isEnabled {
accessibilityTraits.remove(.notEnabled) accessibilityTraits.remove(.notEnabled)
} else {
accessibilityTraits.insert(.notEnabled)
} }
} }

View File

@ -38,13 +38,14 @@ open class CheckboxGroup: SelectorGroupHandlerBase<CheckboxItem> {
public var selectorModels: [CheckboxModel]? { public var selectorModels: [CheckboxModel]? {
didSet { didSet {
if let selectorModels { if let selectorModels {
selectorViews = selectorModels.map { model in selectorViews = selectorModels.enumerated().map { index, model in
return CheckboxItem().with { return CheckboxItem().with {
$0.disabled = model.disabled $0.disabled = model.disabled
$0.surface = model.surface $0.surface = model.surface
$0.inputId = model.inputId $0.inputId = model.inputId
$0.value = model.value $0.value = model.value
$0.accessibilityLabel = model.accessibileText $0.accessibilityLabel = model.accessibileText
$0.accessibilityValue = "item \(index+1) of \(selectorModels.count)"
$0.labelText = model.labelText $0.labelText = model.labelText
$0.labelTextAttributes = model.labelTextAttributes $0.labelTextAttributes = model.labelTextAttributes
$0.childText = model.childText $0.childText = model.childText

View File

@ -102,11 +102,7 @@ open class Icon: View {
imageView.image = nil imageView.image = nil
} }
} }
public override func updateAccessibilityLabel() {
}
private func getImage(for imageName: String) -> UIImage? { private func getImage(for imageName: String) -> UIImage? {
return BundleManager.shared.image(for: imageName) return BundleManager.shared.image(for: imageName)

View File

@ -23,6 +23,7 @@ public class TooltipLabelAttribute: ActionLabelAttributeModel, TooltipLaunchable
public var title: String? public var title: String?
public var content: String? public var content: String?
public var contentView: UIView? public var contentView: UIView?
public var presenter: UIView?
public func setAttribute(on attributedString: NSMutableAttributedString) { public func setAttribute(on attributedString: NSMutableAttributedString) {
//update the location //update the location
@ -66,7 +67,7 @@ public class TooltipLabelAttribute: ActionLabelAttributeModel, TooltipLaunchable
addHandler(on: attributedString) addHandler(on: attributedString)
} }
public init(id: UUID = UUID(), action: PassthroughSubject<Void, Never> = PassthroughSubject<Void, Never>(), subscriber: AnyCancellable? = nil, surface: Surface, accessibleText: String? = nil, closeButtonText: String = "Close", title: String? = nil, content: String? = nil, contentView: UIView? = nil) { public init(id: UUID = UUID(), action: PassthroughSubject<Void, Never> = PassthroughSubject<Void, Never>(), subscriber: AnyCancellable? = nil, surface: Surface, accessibleText: String? = nil, closeButtonText: String = "Close", title: String? = nil, content: String? = nil, contentView: UIView? = nil, presenter: UIView? = nil) {
self.id = id self.id = id
self.action = action self.action = action
self.subscriber = subscriber self.subscriber = subscriber
@ -76,6 +77,8 @@ public class TooltipLabelAttribute: ActionLabelAttributeModel, TooltipLaunchable
self.title = title self.title = title
self.content = content self.content = content
self.contentView = contentView self.contentView = contentView
self.presenter = presenter
//create the tooltip click event //create the tooltip click event
self.subscriber = action.sink { [weak self] in self.subscriber = action.sink { [weak self] in
guard let self else { return } guard let self else { return }
@ -83,7 +86,8 @@ public class TooltipLabelAttribute: ActionLabelAttributeModel, TooltipLaunchable
title: self.title, title: self.title,
content: self.content, content: self.content,
contentView: contentView, contentView: contentView,
closeButtonText: self.closeButtonText) closeButtonText: self.closeButtonText,
presenter: self.presenter)
} }
} }

View File

@ -66,7 +66,7 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable {
public var textColorConfiguration: AnyColorable = ViewColorConfiguration().with { public var textColorConfiguration: AnyColorable = ViewColorConfiguration().with {
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forDisabled: true) $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forDisabled: true)
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forDisabled: false) $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forDisabled: false)
}.eraseToAnyColorable() }.eraseToAnyColorable(){ didSet { setNeedsUpdate() }}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initializers // MARK: - Initializers
@ -156,7 +156,7 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable {
attributedText = mutableText attributedText = mutableText
//get accessibility //get accessibility
updateAccessibilityLabel() updateAccessibility()
//force a drawText //force a drawText
setNeedsDisplay() setNeedsDisplay()
@ -164,7 +164,7 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable {
} }
} }
open func updateAccessibilityLabel() { open func updateAccessibility() {
accessibilityLabel = text accessibilityLabel = text
} }
@ -222,6 +222,8 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable {
private var actions: [LabelAction] = [] { private var actions: [LabelAction] = [] {
didSet { didSet {
isUserInteractionEnabled = !actions.isEmpty
accessibilityTraits = !actions.isEmpty ? .link : .staticText
if actions.isEmpty { if actions.isEmpty {
tapGesture = nil tapGesture = nil
} else { } else {
@ -264,7 +266,6 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable {
let actionText = accessibleText ?? NSString(string:text).substring(with: range) let actionText = accessibleText ?? NSString(string:text).substring(with: range)
let accessibleAction = UIAccessibilityCustomAction(name: actionText, target: self, selector: #selector(accessibilityCustomAction(_:))) let accessibleAction = UIAccessibilityCustomAction(name: actionText, target: self, selector: #selector(accessibilityCustomAction(_:)))
accessibilityCustomActions?.append(accessibleAction) accessibilityCustomActions?.append(accessibleAction)
return accessibleAction return accessibleAction
} }
@ -294,4 +295,3 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable {
return false return false
} }
} }

View File

@ -33,9 +33,10 @@ open class RadioBoxGroup: SelectorGroupSelectedHandlerBase<RadioBoxItem> {
public var selectorModels: [RadioBoxModel]? { public var selectorModels: [RadioBoxModel]? {
didSet { didSet {
if let selectorModels { if let selectorModels {
selectorViews = selectorModels.map { model in selectorViews = selectorModels.enumerated().map { index, model in
return RadioBoxItem().with { return RadioBoxItem().with {
$0.accessibilityLabel = model.accessibileText $0.accessibilityLabel = model.accessibileText
$0.accessibilityValue = "item \(index+1) of \(selectorModels.count)"
$0.text = model.text $0.text = model.text
$0.textAttributes = model.textAttributes $0.textAttributes = model.textAttributes
$0.subText = model.subText $0.subText = model.subText

View File

@ -243,24 +243,13 @@ open class RadioBoxItem: Control, Changeable {
//-------------------------------------------------- //--------------------------------------------------
open override func updateView() { open override func updateView() {
updateLabels() updateLabels()
updateAccessibilityLabel() updateAccessibility()
setNeedsLayout() setNeedsLayout()
layoutIfNeeded() layoutIfNeeded()
} }
open override func updateAccessibilityLabel() { open override func updateAccessibility() {
accessibilityValue = isSelected ? "1" : "0" super.updateAccessibility()
if !accessibilityTraits.contains(.selected) && isSelected {
accessibilityTraits.insert(.selected)
} else if accessibilityTraits.contains(.selected) && !isSelected{
accessibilityTraits.remove(.selected)
}
if !accessibilityTraits.contains(.notEnabled) && !isEnabled {
accessibilityTraits.insert(.notEnabled)
} else if accessibilityTraits.contains(.notEnabled) && !isEnabled {
accessibilityTraits.remove(.notEnabled)
}
if accessibilityLabel == nil { if accessibilityLabel == nil {
setAccessibilityLabel(for: [textLabel, subTextLabel, subTextRightLabel]) setAccessibilityLabel(for: [textLabel, subTextLabel, subTextRightLabel])
} }

View File

@ -33,13 +33,14 @@ open class RadioButtonGroup: SelectorGroupSelectedHandlerBase<RadioButtonItem> {
public var selectorModels: [RadioButtonModel]? { public var selectorModels: [RadioButtonModel]? {
didSet { didSet {
if let selectorModels { if let selectorModels {
selectorViews = selectorModels.map { model in selectorViews = selectorModels.enumerated().map { index, model in
return RadioButtonItem().with { return RadioButtonItem().with {
$0.disabled = model.disabled $0.disabled = model.disabled
$0.surface = model.surface $0.surface = model.surface
$0.inputId = model.inputId $0.inputId = model.inputId
$0.value = model.value $0.value = model.value
$0.accessibilityLabel = model.accessibileText $0.accessibilityLabel = model.accessibileText
$0.accessibilityValue = "item \(index+1) of \(selectorModels.count)"
$0.labelText = model.labelText $0.labelText = model.labelText
$0.labelTextAttributes = model.labelTextAttributes $0.labelTextAttributes = model.labelTextAttributes
$0.childText = model.childText $0.childText = model.childText

View File

@ -117,7 +117,7 @@ open class RadioSwatch: Control {
layer.setNeedsDisplay() layer.setNeedsDisplay()
} }
public override func updateAccessibilityLabel() { public override func updateAccessibility() {
accessibilityLabel = text accessibilityLabel = text
} }

View File

@ -12,7 +12,7 @@ import Combine
extension Tabs { extension Tabs {
@objc(VDSTab) @objc(VDSTab)
open class Tab: View { open class Tab: Control {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Public Properties // MARK: - Public Properties
//-------------------------------------------------- //--------------------------------------------------
@ -20,7 +20,7 @@ extension Tabs {
open var index: Int = 0 open var index: Int = 0
///label to write out the text ///label to write out the text
open var label: Label = Label() open var label: Label = Label().with { $0.isUserInteractionEnabled = false }
///orientation of the tabs ///orientation of the tabs
open var orientation: Tabs.Orientation = .horizontal { didSet { setNeedsUpdate() } } open var orientation: Tabs.Orientation = .horizontal { didSet { setNeedsUpdate() } }
@ -33,16 +33,10 @@ extension Tabs {
///Sets the Position of the Selected/Hover Border Accent for All Tabs. ///Sets the Position of the Selected/Hover Border Accent for All Tabs.
open var indicatorPosition: Tabs.IndicatorPosition = .bottom { didSet { setNeedsUpdate() } } open var indicatorPosition: Tabs.IndicatorPosition = .bottom { didSet { setNeedsUpdate() } }
///An optional callback that is called when this Tab is clicked. Passes parameters (tabIndex).
open var onClick: ((Int) -> Void)? { didSet { setNeedsUpdate() } }
///If provided, it will set fixed width for this Tab. ///If provided, it will set fixed width for this Tab.
open var width: CGFloat? { didSet { setNeedsUpdate() } } open var width: CGFloat? { didSet { setNeedsUpdate() } }
///If provided, it will set this Tab to the Active Tab on render.
open var selected: Bool = false { didSet { setNeedsUpdate() } }
///The text label of the tab. ///The text label of the tab.
open var text: String = "" { didSet { setNeedsUpdate() } } open var text: String = "" { didSet { setNeedsUpdate() } }
@ -71,7 +65,7 @@ extension Tabs {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Configuration // MARK: - Configuration
//-------------------------------------------------- //--------------------------------------------------
private var textColorConfiguration: SurfaceColorConfiguration { selected ? textColorSelectedConfiguration : textColorNonSelectedConfiguration } private var textColorConfiguration: SurfaceColorConfiguration { isSelected ? textColorSelectedConfiguration : textColorNonSelectedConfiguration }
private var textColorNonSelectedConfiguration = SurfaceColorConfiguration(VDSColor.elementsSecondaryOnlight , VDSColor.elementsSecondaryOnlight) private var textColorNonSelectedConfiguration = SurfaceColorConfiguration(VDSColor.elementsSecondaryOnlight , VDSColor.elementsSecondaryOnlight)
private var textColorSelectedConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) private var textColorSelectedConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark)
private var indicatorColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteRed, VDSColor.elementsPrimaryOndark) private var indicatorColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteRed, VDSColor.elementsPrimaryOndark)
@ -125,7 +119,8 @@ extension Tabs {
super.setup() super.setup()
addSubview(label) addSubview(label)
accessibilityTraits = .button accessibilityTraits = .button
isAccessibilityElement = true
label.translatesAutoresizingMaskIntoConstraints = false label.translatesAutoresizingMaskIntoConstraints = false
label.pinTrailing() label.pinTrailing()
@ -149,23 +144,18 @@ extension Tabs {
layoutGuide.bottomAnchor.constraint(equalTo: bottomAnchor), layoutGuide.bottomAnchor.constraint(equalTo: bottomAnchor),
layoutGuide.leadingAnchor.constraint(equalTo: leadingAnchor), layoutGuide.leadingAnchor.constraint(equalTo: leadingAnchor),
layoutGuide.trailingAnchor.constraint(equalTo: trailingAnchor)]) layoutGuide.trailingAnchor.constraint(equalTo: trailingAnchor)])
publisher(for: UITapGestureRecognizer())
.sink { [weak self] _ in
guard let self else { return }
self.onClick?(self.index)
}.store(in: &subscribers)
} }
open override func updateView() { open override func updateView() {
super.updateView()
guard !text.isEmpty else { return } guard !text.isEmpty else { return }
//label properties //label properties
label.text = text label.text = text
label.textStyle = textStyle label.textStyle = textStyle
label.textPosition = textPosition label.textPosition = textPosition
label.textColor = textColorConfiguration.getColor(self) label.textColorConfiguration = textColorConfiguration.eraseToAnyColorable()
//constaints //constaints
labelWidthConstraint?.isActive = false labelWidthConstraint?.isActive = false
@ -178,12 +168,17 @@ extension Tabs {
setNeedsLayout() setNeedsLayout()
} }
open override func updateAccessibility() {
super.updateAccessibility()
accessibilityLabel = text
}
open override func layoutSubviews() { open override func layoutSubviews() {
super.layoutSubviews() super.layoutSubviews()
removeBorders() removeBorders()
if selected { if isSelected {
addBorder(side: indicatorSide, width: indicatorWidth, color: indicatorColorConfiguration.getColor(self)) addBorder(side: indicatorSide, width: indicatorWidth, color: indicatorColorConfiguration.getColor(self))
} }
} }

View File

@ -211,20 +211,15 @@ open class Tabs: View {
let tabItem = Tab() let tabItem = Tab()
tabItem.size = size tabItem.size = size
tabItem.text = model.text tabItem.text = model.text
tabItem.onClick = model.onClick
tabItem.width = model.width tabItem.width = model.width
tabViews.append(tabItem) tabViews.append(tabItem)
tabStackView.addArrangedSubview(tabItem) tabStackView.addArrangedSubview(tabItem)
tabItem.onClick = { [weak self] tab in
tabItem guard let self else { return }
.publisher(for: UITapGestureRecognizer()) model.onClick?(tab.index)
.sink { [weak self] gesture in self.selectedIndex = tab.index
guard let self, let tabItem = gesture.view as? Tab else { return } self.onTabChange?(tab.index)
if let selectedIndex = self.tabViews.firstIndex(of: tabItem) { }
self.selectedIndex = selectedIndex
self.onTabChange?(selectedIndex)
}
}.store(in: &tabItem.subscribers)
} }
setNeedsUpdate() setNeedsUpdate()
scrollToSelectedIndex(animated: false) scrollToSelectedIndex(animated: false)
@ -249,14 +244,14 @@ open class Tabs: View {
// Update tab appearance based on properties // Update tab appearance based on properties
for (index, tabItem) in tabViews.enumerated() { for (index, tabItem) in tabViews.enumerated() {
tabItem.size = size tabItem.size = size
tabItem.selected = selectedIndex == index tabItem.isSelected = selectedIndex == index
tabItem.index = index tabItem.index = index
tabItem.minWidth = minWidth tabItem.minWidth = minWidth
tabItem.textPosition = textPosition tabItem.textPosition = textPosition
tabItem.orientation = orientation tabItem.orientation = orientation
tabItem.surface = surface tabItem.surface = surface
tabItem.indicatorPosition = indicatorPosition tabItem.indicatorPosition = indicatorPosition
tabItem.accessibilityLabel = "\(tabItem.text) \(tabItem.selected ? "selected" : "unselected") \(index+1) of \(tabViews.count)" tabItem.accessibilityValue = "\(index+1) of \(tabViews.count) Tabs"
} }
//update the width based on rules //update the width based on rules

View File

@ -295,7 +295,7 @@ open class EntryField: Control, Changeable {
} }
if let tooltipTitle, let tooltipContent { if let tooltipTitle, let tooltipContent {
attributes.append(TooltipLabelAttribute(surface: surface, title: tooltipTitle, content: tooltipContent, contentView: tooltipContentView)) attributes.append(TooltipLabelAttribute(surface: surface, title: tooltipTitle, content: tooltipContent, contentView: tooltipContentView, presenter: self))
} }
//set the titleLabel //set the titleLabel

View File

@ -198,10 +198,6 @@ open class InputField: EntryField, UITextFieldDelegate {
} }
} }
public override func updateAccessibilityLabel() {
}
open override func updateHelperLabel(){ open override func updateHelperLabel(){
//remove first //remove first
helperLabel.removeFromSuperview() helperLabel.removeFromSuperview()

View File

@ -376,10 +376,10 @@ open class Tilelet: TileContainer {
updateIcons() updateIcons()
layoutIfNeeded() layoutIfNeeded()
updateAccessibilityLabel() updateAccessibility()
} }
open override func updateAccessibilityLabel() { open override func updateAccessibility() {
setAccessibilityLabel(for: [badge.label, titleLockup.eyebrowLabel, titleLockup.titleLabel, titleLockup.subTitleLabel]) setAccessibilityLabel(for: [badge.label, titleLockup.eyebrowLabel, titleLockup.titleLabel, titleLockup.subTitleLabel])
} }
} }

View File

@ -46,20 +46,57 @@ open class TitleLockup: View {
$0.distribution = .fill $0.distribution = .fill
} }
///This logic applies when the type style and size used for the title and subtitle/eyebrow is exactly the same (not including the type weight). This should be automatically detected.
private var isUniformSize: Bool {
otherStandardStyle.value == titleModel?.standardStyle.value
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Configuration Properties // MARK: - Configuration Properties
//-------------------------------------------------- //--------------------------------------------------
// Sizes are from InVision design specs. // Sizes are from InVision design specs.
open var standardStyleConfiguration: StandardStyleConfigurationProvider = StandardStyleConfigurationProvider(styleConfigurations: [ open var standardStyleConfiguration: StandardStyleConfigurationProvider = StandardStyleConfigurationProvider(styleConfigurations: [
.init(deviceType: .iPad, .init(deviceType: .iPad,
titleStandardStyles: [.titleSmall, .titleMedium], titleStandardStyles: [.bodySmall],
spacingConfigurations: [ spacingConfigurations: [
.init(otherStandardStyles: [.bodySmall, .bodyMedium, .bodyLarge], .init(otherStandardStyles: [.bodySmall],
topSpacing: VDSLayout.Spacing.space2X.value, topSpacing: VDSLayout.Spacing.space1X.value,
bottomSpacing: VDSLayout.Spacing.space2X.value) bottomSpacing: VDSLayout.Spacing.space1X.value)
]), ]),
.init(deviceType: .iPad,
titleStandardStyles: [.bodyMedium],
spacingConfigurations: [
.init(otherStandardStyles: [.bodyMedium],
topSpacing: VDSLayout.Spacing.space1X.value,
bottomSpacing: VDSLayout.Spacing.space1X.value)
]),
.init(deviceType: .iPad,
titleStandardStyles: [.bodyLarge],
spacingConfigurations: [
.init(otherStandardStyles: [.bodyLarge],
topSpacing: VDSLayout.Spacing.space1X.value,
bottomSpacing: VDSLayout.Spacing.space1X.value)
]),
.init(deviceType: .iPad,
titleStandardStyles: [.titleSmall],
spacingConfigurations: [
.init(otherStandardStyles: [.bodySmall, .bodyMedium, .bodyLarge, .titleSmall],
topSpacing: VDSLayout.Spacing.space2X.value,
bottomSpacing: VDSLayout.Spacing.space2X.value)
]),
.init(deviceType: .iPad,
titleStandardStyles: [.titleMedium],
spacingConfigurations: [
.init(otherStandardStyles: [.bodySmall, .bodyMedium, .bodyLarge],
topSpacing: VDSLayout.Spacing.space2X.value,
bottomSpacing: VDSLayout.Spacing.space2X.value)
]),
.init(deviceType: .iPad, .init(deviceType: .iPad,
titleStandardStyles: [.titleLarge], titleStandardStyles: [.titleLarge],
spacingConfigurations: [ spacingConfigurations: [
@ -100,6 +137,30 @@ open class TitleLockup: View {
bottomSpacing: VDSLayout.Spacing.space6X.value), bottomSpacing: VDSLayout.Spacing.space6X.value),
]), ]),
.init(deviceType: .iPhone,
titleStandardStyles: [.bodySmall],
spacingConfigurations: [
.init(otherStandardStyles: [.bodySmall],
topSpacing: VDSLayout.Spacing.space1X.value,
bottomSpacing: VDSLayout.Spacing.space1X.value)
]),
.init(deviceType: .iPhone,
titleStandardStyles: [.bodyMedium],
spacingConfigurations: [
.init(otherStandardStyles: [.bodyMedium],
topSpacing: VDSLayout.Spacing.space1X.value,
bottomSpacing: VDSLayout.Spacing.space1X.value)
]),
.init(deviceType: .iPhone,
titleStandardStyles: [.bodyLarge],
spacingConfigurations: [
.init(otherStandardStyles: [.bodyLarge],
topSpacing: VDSLayout.Spacing.space1X.value,
bottomSpacing: VDSLayout.Spacing.space1X.value)
]),
.init(deviceType: .iPhone, .init(deviceType: .iPhone,
titleStandardStyles: [.titleSmall], titleStandardStyles: [.titleSmall],
spacingConfigurations: [ spacingConfigurations: [
@ -152,6 +213,10 @@ open class TitleLockup: View {
bottomSpacing: VDSLayout.Spacing.space6X.value) bottomSpacing: VDSLayout.Spacing.space6X.value)
]), ]),
]) ])
private var textColorSecondaryConfiguration = SurfaceColorConfiguration(VDSColor.elementsSecondaryOnlight , VDSColor.elementsSecondaryOnlight).eraseToAnyColorable()
private var textColorPrimaryConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark).eraseToAnyColorable()
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Public Properties // MARK: - Public Properties
@ -182,6 +247,8 @@ open class TitleLockup: View {
open override func setup() { open override func setup() {
super.setup() super.setup()
titleLabel.textColorConfiguration = textColorPrimaryConfiguration
accessibilityElements = [eyebrowLabel, titleLabel, subTitleLabel] accessibilityElements = [eyebrowLabel, titleLabel, subTitleLabel]
addSubview(stackView) addSubview(stackView)
@ -223,7 +290,7 @@ open class TitleLockup: View {
//-------------------------------------------------- //--------------------------------------------------
open override func updateView() { open override func updateView() {
super.updateView() super.updateView()
let allLabelsTextPosition = textPosition.value let allLabelsTextPosition = textPosition.value
var eyebrowTextIsEmpty = true var eyebrowTextIsEmpty = true
var titleTextIsEmpty = true var titleTextIsEmpty = true
@ -231,7 +298,7 @@ open class TitleLockup: View {
var topSpacing: CGFloat = 0.0 var topSpacing: CGFloat = 0.0
var bottomSpacing: CGFloat = 0.0 var bottomSpacing: CGFloat = 0.0
//get the spacing based on the title style and other style used for eyebrow and subtitle //get the spacing based on the title style and other style used for eyebrow and subtitle
if let titleModel, if let titleModel,
let config = standardStyleConfiguration.spacing(for: titleModel.standardStyle, otherStandardStyle: otherStandardStyle) { let config = standardStyleConfiguration.spacing(for: titleModel.standardStyle, otherStandardStyle: otherStandardStyle) {
@ -242,15 +309,32 @@ open class TitleLockup: View {
if let eyebrowModel, !eyebrowModel.text.isEmpty { if let eyebrowModel, !eyebrowModel.text.isEmpty {
eyebrowTextIsEmpty = false eyebrowTextIsEmpty = false
eyebrowLabel.textPosition = allLabelsTextPosition eyebrowLabel.textPosition = allLabelsTextPosition
eyebrowLabel.textStyle = eyebrowModel.isBold ? otherStandardStyle.value.bold : otherStandardStyle.value.regular
eyebrowLabel.text = eyebrowModel.text eyebrowLabel.text = eyebrowModel.text
eyebrowLabel.attributes = eyebrowModel.textAttributes eyebrowLabel.attributes = eyebrowModel.textAttributes
eyebrowLabel.numberOfLines = eyebrowModel.numberOfLines eyebrowLabel.numberOfLines = eyebrowModel.numberOfLines
eyebrowLabel.surface = surface eyebrowLabel.surface = surface
//When uniform size is true
if let titleModel, isUniformSize {
if titleModel.isBold {
//When uniform size is true and the title is bold,
//the eyebrow is always regular weight and the secondary color.
eyebrowLabel.textStyle = otherStandardStyle.value.regular
eyebrowLabel.textColorConfiguration = textColorSecondaryConfiguration
} else {
//When uniform size is true and the title is regular weight
//the eyebrow is always bold and uses the primary color.
eyebrowLabel.textStyle = otherStandardStyle.value.bold
eyebrowLabel.textColorConfiguration = textColorPrimaryConfiguration
}
} else {
eyebrowLabel.textColorConfiguration = textColorPrimaryConfiguration
eyebrowLabel.textStyle = eyebrowModel.isBold ? otherStandardStyle.value.bold : otherStandardStyle.value.regular
}
} else { } else {
eyebrowLabel.reset() eyebrowLabel.reset()
} }
if let titleModel, !titleModel.text.isEmpty { if let titleModel, !titleModel.text.isEmpty {
titleTextIsEmpty = false titleTextIsEmpty = false
titleLabel.textPosition = allLabelsTextPosition titleLabel.textPosition = allLabelsTextPosition
@ -267,11 +351,11 @@ open class TitleLockup: View {
subTitleTextIsEmpty = false subTitleTextIsEmpty = false
subTitleLabel.textPosition = allLabelsTextPosition subTitleLabel.textPosition = allLabelsTextPosition
subTitleLabel.textStyle = otherStandardStyle.value.regular subTitleLabel.textStyle = otherStandardStyle.value.regular
subTitleLabel.textColorConfiguration = subTitleModel.textColor == .secondary ? textColorSecondaryConfiguration : textColorPrimaryConfiguration
subTitleLabel.text = subTitleModel.text subTitleLabel.text = subTitleModel.text
subTitleLabel.attributes = subTitleModel.textAttributes subTitleLabel.attributes = subTitleModel.textAttributes
subTitleLabel.numberOfLines = subTitleModel.numberOfLines subTitleLabel.numberOfLines = subTitleModel.numberOfLines
subTitleLabel.surface = surface subTitleLabel.surface = surface
subTitleLabel.disabled = subTitleModel.textColor == .secondary
} else { } else {
subTitleLabel.reset() subTitleLabel.reset()
} }

View File

@ -23,6 +23,10 @@ extension TitleLockup {
case titleMedium case titleMedium
case titleSmall case titleSmall
case bodyLarge
case bodyMedium
case bodySmall
public var defaultValue: TextStyle.StandardStyle {.featureXSmall } public var defaultValue: TextStyle.StandardStyle {.featureXSmall }
public var value: TextStyle.StandardStyle { public var value: TextStyle.StandardStyle {

View File

@ -237,23 +237,16 @@ open class Toggle: Control, Changeable {
toggleView.surface = surface toggleView.surface = surface
toggleView.disabled = disabled toggleView.disabled = disabled
toggleView.isOn = isOn toggleView.isOn = isOn
updateAccessibilityLabel() updateAccessibility()
} }
open override func updateAccessibilityLabel() { open override func updateAccessibility() {
accessibilityValue = isSelected ? "1" : "0" super.updateAccessibility()
if !accessibilityTraits.contains(.selected) && isSelected { if showText {
accessibilityTraits.insert(.selected) setAccessibilityLabel(for: [label])
} else if accessibilityTraits.contains(.selected) && !isSelected{ } else {
accessibilityTraits.remove(.selected) accessibilityLabel = "Toggle"
} }
if !accessibilityTraits.contains(.notEnabled) && !isEnabled {
accessibilityTraits.insert(.notEnabled)
} else if accessibilityTraits.contains(.notEnabled) && !isEnabled{
accessibilityTraits.remove(.notEnabled)
}
setAccessibilityLabel(for: [label])
} }
open override var intrinsicContentSize: CGSize { open override var intrinsicContentSize: CGSize {

View File

@ -199,23 +199,12 @@ open class ToggleView: Control, Changeable {
//-------------------------------------------------- //--------------------------------------------------
open override func updateView() { open override func updateView() {
updateToggle() updateToggle()
updateAccessibilityLabel() updateAccessibility()
} }
open override func updateAccessibilityLabel() { open override func updateAccessibility() {
super.updateAccessibility()
accessibilityLabel = "Toggle" accessibilityLabel = "Toggle"
accessibilityValue = isSelected ? "1" : "0"
if !accessibilityTraits.contains(.selected) && isSelected {
accessibilityTraits.insert(.selected)
} else if accessibilityTraits.contains(.selected) && !isSelected{
accessibilityTraits.remove(.selected)
}
if !accessibilityTraits.contains(.notEnabled) && !isEnabled {
accessibilityTraits.insert(.notEnabled)
} else if accessibilityTraits.contains(.notEnabled) && !isEnabled{
accessibilityTraits.remove(.notEnabled)
}
} }
} }

View File

@ -135,7 +135,8 @@ open class Tooltip: Control, TooltipLaunchable {
title: tooltip.title, title: tooltip.title,
content: tooltip.content, content: tooltip.content,
contentView: tooltip.contentView, contentView: tooltip.contentView,
closeButtonText: tooltip.closeButtonText) closeButtonText: tooltip.closeButtonText,
presenter: self)
}) })
} }
@ -166,20 +167,33 @@ open class Tooltip: Control, TooltipLaunchable {
imageView.image = infoImage.withTintColor(imageColor) imageView.image = infoImage.withTintColor(imageColor)
} }
open override func updateAccessibilityLabel() { open override func updateAccessibility() {
if !accessibilityTraits.contains(.notEnabled) && !isEnabled { super.updateAccessibility()
accessibilityTraits.insert(.notEnabled)
} else if accessibilityTraits.contains(.notEnabled) && !isEnabled {
accessibilityTraits.remove(.notEnabled)
}
var label = title var label = title
if label == nil { if label == nil {
label = content label = content
} }
accessibilityHint = isEnabled ? "Click to open Tooltip." : ""
accessibilityValue = "collapsed"
if let label { if let label {
accessibilityLabel = "Tooltip: \(label)" accessibilityLabel = label
} }
} }
public static func accessibleText(for title: String?, content: String?, closeButtonText: String) -> String {
var label = ""
if let title {
label = title
}
if let content {
if !label.isEmpty {
label += ","
}
label += content
}
return label
}
} }

View File

@ -36,7 +36,7 @@ open class TooltipAlertViewController: UIViewController, Surfaceable {
open var contentText: String? { didSet { updateView() }} open var contentText: String? { didSet { updateView() }}
open var contentView: UIView? { didSet { updateView() }} open var contentView: UIView? { didSet { updateView() }}
open var closeButtonText: String = "Close" { didSet { updateView() }} open var closeButtonText: String = "Close" { didSet { updateView() }}
open var presenter: UIView? { didSet { updateView() }}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Configuration // MARK: - Configuration
//-------------------------------------------------- //--------------------------------------------------
@ -50,28 +50,41 @@ open class TooltipAlertViewController: UIViewController, Surfaceable {
isModalInPresentation = true isModalInPresentation = true
setup() setup()
} }
open override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIAccessibility.post(notification: .screenChanged, argument: tooltipDialog)
}
private func dismiss() {
dismiss(animated: true) { [weak self] in
guard let self, let presenter else { return }
UIAccessibility.post(notification: .layoutChanged, argument: presenter)
}
}
open func setup() { open func setup() {
view.accessibilityElements = [tooltipDialog]
//left-right swipe //left-right swipe
view.publisher(for: UISwipeGestureRecognizer().with{ $0.direction = .right }) view.publisher(for: UISwipeGestureRecognizer().with{ $0.direction = .right })
.sink { [weak self] swipe in .sink { [weak self] swipe in
guard let self else { return } guard let self else { return }
self.dismiss(animated: true, completion: nil) self.dismiss()
}.store(in: &subscribers) }.store(in: &subscribers)
//tapping in background //tapping in background
view.publisher(for: UITapGestureRecognizer().with{ $0.numberOfTapsRequired = 1 }) view.publisher(for: UITapGestureRecognizer().with{ $0.numberOfTapsRequired = 1 })
.sink { [weak self] swipe in .sink { [weak self] swipe in
guard let self else { return } guard let self else { return }
self.dismiss(animated: true, completion: nil) self.dismiss()
}.store(in: &subscribers) }.store(in: &subscribers)
//clicking button //clicking button
onClickSubscriber = tooltipDialog.closeButton.publisher(for: .touchUpInside) onClickSubscriber = tooltipDialog.closeButton.publisher(for: .touchUpInside)
.sink {[weak self] button in .sink {[weak self] button in
guard let self else { return } guard let self else { return }
self.dismiss(animated: true, completion: nil) self.dismiss()
} }
view.addSubview(tooltipDialog) view.addSubview(tooltipDialog)
@ -160,9 +173,8 @@ open class TooltipDialog: View, UIScrollViewDelegate {
//-------------------------------------------------- //--------------------------------------------------
open override func setup() { open override func setup() {
super.setup() super.setup()
layer.cornerRadius = 8 layer.cornerRadius = 8
contentStackView.isAccessibilityElement = true
contentStackView.addArrangedSubview(titleLabel) contentStackView.addArrangedSubview(titleLabel)
contentStackView.addArrangedSubview(contentLabel) contentStackView.addArrangedSubview(contentLabel)
scrollView.addSubview(contentStackView) scrollView.addSubview(contentStackView)
@ -204,7 +216,7 @@ open class TooltipDialog: View, UIScrollViewDelegate {
open override func updateView() { open override func updateView() {
super.updateView() super.updateView()
backgroundColor = backgroundColorConfiguration.getColor(self) backgroundColor = backgroundColorConfiguration.getColor(self)
scrollView.indicatorStyle = surface == .light ? .black : .white scrollView.indicatorStyle = surface == .light ? .black : .white
@ -257,6 +269,7 @@ open class TooltipDialog: View, UIScrollViewDelegate {
closeButton.setTitleColor(closeButtonTextColor, for: .normal) closeButton.setTitleColor(closeButtonTextColor, for: .normal)
closeButton.setTitleColor(closeButtonTextColor, for: .highlighted) closeButton.setTitleColor(closeButtonTextColor, for: .highlighted)
closeButton.setTitle(closeButtonText, for: .normal) closeButton.setTitle(closeButtonText, for: .normal)
closeButton.accessibilityLabel = closeButtonText
contentStackView.setNeedsLayout() contentStackView.setNeedsLayout()
contentStackView.layoutIfNeeded() contentStackView.layoutIfNeeded()
@ -282,7 +295,19 @@ open class TooltipDialog: View, UIScrollViewDelegate {
//stackView between the bottom of the scrollView //stackView between the bottom of the scrollView
contentStackViewBottomConstraint?.constant = -containerViewInset contentStackViewBottomConstraint?.constant = -containerViewInset
} }
heightConstraint?.constant = contentHeight heightConstraint?.constant = contentHeight
} }
open override func updateAccessibility() {
var label = Tooltip.accessibleText(for: titleText, content: contentText, closeButtonText: closeButtonText)
if !label.isEmpty {
label += ","
}
contentStackView.accessibilityLabel = label
contentStackView.accessibilityHint = "Click on the \(closeButtonText) button to close."
contentStackView.accessibilityValue = "expanded"
accessibilityElements = [contentStackView, closeButton]
}
} }

View File

@ -9,11 +9,11 @@ import Foundation
import UIKit import UIKit
public protocol TooltipLaunchable { public protocol TooltipLaunchable {
func presentTooltip(surface: Surface, title: String?, content: String?, contentView: UIView?, closeButtonText: String) func presentTooltip(surface: Surface, title: String?, content: String?, contentView: UIView?, closeButtonText: String, presenter: UIView?)
} }
extension TooltipLaunchable { extension TooltipLaunchable {
public func presentTooltip(surface: Surface, title: String?, content: String?, contentView: UIView? = nil, closeButtonText: String = "Close") { public func presentTooltip(surface: Surface, title: String?, content: String?, contentView: UIView? = nil, closeButtonText: String = "Close", presenter: UIView? = nil) {
if let presenting = UIApplication.topViewController() { if let presenting = UIApplication.topViewController() {
let tooltipViewController = TooltipAlertViewController(nibName: nil, bundle: nil).with { let tooltipViewController = TooltipAlertViewController(nibName: nil, bundle: nil).with {
$0.surface = surface $0.surface = surface
@ -21,6 +21,7 @@ extension TooltipLaunchable {
$0.contentText = content $0.contentText = content
$0.contentView = contentView $0.contentView = contentView
$0.closeButtonText = closeButtonText $0.closeButtonText = closeButtonText
$0.presenter = presenter
$0.modalPresentationStyle = .overCurrentContext $0.modalPresentationStyle = .overCurrentContext
$0.modalTransitionStyle = .crossDissolve $0.modalTransitionStyle = .crossDissolve
} }

View File

@ -57,7 +57,8 @@ open class TrailingTooltipLabel: View, TooltipLaunchable {
self.presentTooltip(surface: self.surface, self.presentTooltip(surface: self.surface,
title: self.tooltipTitle, title: self.tooltipTitle,
content: self.tooltipContent, content: self.tooltipContent,
closeButtonText: self.tooltipCloseButtonText) closeButtonText: self.tooltipCloseButtonText,
presenter: self)
}.store(in: &subscribers) }.store(in: &subscribers)
} }
@ -126,7 +127,8 @@ extension Label {
closeButtonText: model.closeButtonText, closeButtonText: model.closeButtonText,
title: model.title, title: model.title,
content: model.content, content: model.content,
contentView: model.contentView) contentView: model.contentView,
presenter: self)
newAttributes.append(tooltip) newAttributes.append(tooltip)
} }

View File

@ -12,7 +12,7 @@ public protocol ViewProtocol {
// Can setup ui here. Should be called in the initialization functions. // Can setup ui here. Should be called in the initialization functions.
func setup() func setup()
func updateAccessibilityLabel() func updateAccessibility()
} }
extension ViewProtocol where Self: UIView { extension ViewProtocol where Self: UIView {

View File

@ -1,3 +1,12 @@
1.0.35
=======
- ONEAPP-4684 - (Acessibility) Tooltip
- ONEAPP-4681 - (Acessibility) Checkbox
- ONEAPP-4825 - (Acessibility) Radiobutton
- ONEAPP-5104 - (Acessibility) Button
- ONEAPP-4115 - (Acessibility) Tabs
- TitleLockup update for Janet release
1.0.34 1.0.34
======= =======
- Added new spec for Bottom Inset for TextStyle - Added new spec for Bottom Inset for TextStyle