diff --git a/VDS/Classes/Control.swift b/VDS/Classes/Control.swift index cf1a9100..6920ac48 100644 --- a/VDS/Classes/Control.swift +++ b/VDS/Classes/Control.swift @@ -128,12 +128,23 @@ open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoab /// Update this view based off of property changes open func updateView() { - updateAccessibilityLabel() + updateAccessibility() } /// 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 diff --git a/VDS/Classes/SelectorBase.swift b/VDS/Classes/SelectorBase.swift index a6d6a7f5..91935143 100644 --- a/VDS/Classes/SelectorBase.swift +++ b/VDS/Classes/SelectorBase.swift @@ -88,18 +88,7 @@ open class SelectorBase: Control, SelectorControlable { layoutIfNeeded() } - open override func updateAccessibilityLabel() { - 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) - } + open override func updateAccessibility() { + super.updateAccessibility() } } diff --git a/VDS/Classes/SelectorGroupHandlerBase.swift b/VDS/Classes/SelectorGroupHandlerBase.swift index 76a6fc74..2575ca2e 100644 --- a/VDS/Classes/SelectorGroupHandlerBase.swift +++ b/VDS/Classes/SelectorGroupHandlerBase.swift @@ -69,18 +69,8 @@ open class SelectorGroupHandlerBase: Control, Changeable { selectorViews.forEach{ $0.reset() } } - open override func updateAccessibilityLabel() { - 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) - } + open override func updateAccessibility() { + super.updateAccessibility() setAccessibilityLabel(for: selectorViews) } } @@ -92,8 +82,8 @@ open class SelectorGroupSelectedHandlerBase: SelectorGroup return selectorViews.filter { $0.isSelected == true }.first } - open override func updateAccessibilityLabel() { - super.updateAccessibilityLabel() + open override func updateAccessibility() { + super.updateAccessibility() if let selectedHandler, let value = selectedHandler.accessibilityValue, let label = selectedHandler.accessibilityLabel { accessibilityValue = "\(label) \(value)" } else { diff --git a/VDS/Classes/SelectorItemBase.swift b/VDS/Classes/SelectorItemBase.swift index b9f659bd..de49b09a 100644 --- a/VDS/Classes/SelectorItemBase.swift +++ b/VDS/Classes/SelectorItemBase.swift @@ -267,22 +267,11 @@ open class SelectorItemBase: Control, Errorable, selectorView.isHighlighted = isHighlighted selectorView.disabled = disabled selectorView.surface = surface - updateAccessibilityLabel() + updateAccessibility() } - open override func updateAccessibilityLabel() { - 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) - } + open override func updateAccessibility() { + super.updateAccessibility() setAccessibilityLabel(for: [label, childLabel, errorLabel]) } } diff --git a/VDS/Classes/View.swift b/VDS/Classes/View.swift index b130b56a..199fadcb 100644 --- a/VDS/Classes/View.swift +++ b/VDS/Classes/View.swift @@ -82,12 +82,16 @@ open class View: UIView, Handlerable, ViewProtocol, Resettable, UserInfoable { /// Update this view based off of property changes open func updateView() { - updateAccessibilityLabel() + updateAccessibility() } /// 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 diff --git a/VDS/Components/Buttons/Button/ButtonBase.swift b/VDS/Components/Buttons/Button/ButtonBase.swift index 054c10b3..637fceb3 100644 --- a/VDS/Components/Buttons/Button/ButtonBase.swift +++ b/VDS/Components/Buttons/Button/ButtonBase.swift @@ -158,14 +158,14 @@ open class ButtonBase: UIButton, Buttonable, Handlerable, ViewProtocol, Resettab open func updateView() { updateLabel() - updateAccessibilityLabel() + updateAccessibility() } - open func updateAccessibilityLabel() { - if !accessibilityTraits.contains(.notEnabled) && !isEnabled { - accessibilityTraits.insert(.notEnabled) - } else if accessibilityTraits.contains(.notEnabled) && !isEnabled { + open func updateAccessibility() { + if isEnabled { accessibilityTraits.remove(.notEnabled) + } else { + accessibilityTraits.insert(.notEnabled) } } diff --git a/VDS/Components/Checkbox/CheckboxGroup.swift b/VDS/Components/Checkbox/CheckboxGroup.swift index 0c499e42..03635d4d 100644 --- a/VDS/Components/Checkbox/CheckboxGroup.swift +++ b/VDS/Components/Checkbox/CheckboxGroup.swift @@ -38,13 +38,14 @@ open class CheckboxGroup: SelectorGroupHandlerBase { public var selectorModels: [CheckboxModel]? { didSet { if let selectorModels { - selectorViews = selectorModels.map { model in + selectorViews = selectorModels.enumerated().map { index, model in return CheckboxItem().with { $0.disabled = model.disabled $0.surface = model.surface $0.inputId = model.inputId $0.value = model.value $0.accessibilityLabel = model.accessibileText + $0.accessibilityValue = "item \(index+1) of \(selectorModels.count)" $0.labelText = model.labelText $0.labelTextAttributes = model.labelTextAttributes $0.childText = model.childText diff --git a/VDS/Components/Icon/Icon.swift b/VDS/Components/Icon/Icon.swift index b1cb3c0b..58a712fb 100644 --- a/VDS/Components/Icon/Icon.swift +++ b/VDS/Components/Icon/Icon.swift @@ -102,11 +102,7 @@ open class Icon: View { imageView.image = nil } } - - public override func updateAccessibilityLabel() { - } - private func getImage(for imageName: String) -> UIImage? { return BundleManager.shared.image(for: imageName) diff --git a/VDS/Components/Label/Attributes/TooltipLabelAttribute.swift b/VDS/Components/Label/Attributes/TooltipLabelAttribute.swift index 067d70ed..2ea9f227 100644 --- a/VDS/Components/Label/Attributes/TooltipLabelAttribute.swift +++ b/VDS/Components/Label/Attributes/TooltipLabelAttribute.swift @@ -23,6 +23,7 @@ public class TooltipLabelAttribute: ActionLabelAttributeModel, TooltipLaunchable public var title: String? public var content: String? public var contentView: UIView? + public var presenter: UIView? public func setAttribute(on attributedString: NSMutableAttributedString) { //update the location @@ -66,7 +67,7 @@ public class TooltipLabelAttribute: ActionLabelAttributeModel, TooltipLaunchable addHandler(on: attributedString) } - public init(id: UUID = UUID(), action: PassthroughSubject = PassthroughSubject(), 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 = PassthroughSubject(), 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.action = action self.subscriber = subscriber @@ -76,6 +77,8 @@ public class TooltipLabelAttribute: ActionLabelAttributeModel, TooltipLaunchable self.title = title self.content = content self.contentView = contentView + self.presenter = presenter + //create the tooltip click event self.subscriber = action.sink { [weak self] in guard let self else { return } @@ -83,7 +86,8 @@ public class TooltipLabelAttribute: ActionLabelAttributeModel, TooltipLaunchable title: self.title, content: self.content, contentView: contentView, - closeButtonText: self.closeButtonText) + closeButtonText: self.closeButtonText, + presenter: self.presenter) } } diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index f2f83116..092b48a2 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -66,7 +66,7 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable { public var textColorConfiguration: AnyColorable = ViewColorConfiguration().with { $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forDisabled: true) $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forDisabled: false) - }.eraseToAnyColorable() + }.eraseToAnyColorable(){ didSet { setNeedsUpdate() }} //-------------------------------------------------- // MARK: - Initializers @@ -156,7 +156,7 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable { attributedText = mutableText //get accessibility - updateAccessibilityLabel() + updateAccessibility() //force a drawText setNeedsDisplay() @@ -164,7 +164,7 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable { } } - open func updateAccessibilityLabel() { + open func updateAccessibility() { accessibilityLabel = text } @@ -222,6 +222,8 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable { private var actions: [LabelAction] = [] { didSet { + isUserInteractionEnabled = !actions.isEmpty + accessibilityTraits = !actions.isEmpty ? .link : .staticText if actions.isEmpty { tapGesture = nil } else { @@ -264,7 +266,6 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable { let actionText = accessibleText ?? NSString(string:text).substring(with: range) let accessibleAction = UIAccessibilityCustomAction(name: actionText, target: self, selector: #selector(accessibilityCustomAction(_:))) accessibilityCustomActions?.append(accessibleAction) - return accessibleAction } @@ -294,4 +295,3 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable { return false } } - diff --git a/VDS/Components/RadioBox/RadioBoxGroup.swift b/VDS/Components/RadioBox/RadioBoxGroup.swift index d0f8bef2..0742459f 100644 --- a/VDS/Components/RadioBox/RadioBoxGroup.swift +++ b/VDS/Components/RadioBox/RadioBoxGroup.swift @@ -33,9 +33,10 @@ open class RadioBoxGroup: SelectorGroupSelectedHandlerBase { public var selectorModels: [RadioBoxModel]? { didSet { if let selectorModels { - selectorViews = selectorModels.map { model in + selectorViews = selectorModels.enumerated().map { index, model in return RadioBoxItem().with { $0.accessibilityLabel = model.accessibileText + $0.accessibilityValue = "item \(index+1) of \(selectorModels.count)" $0.text = model.text $0.textAttributes = model.textAttributes $0.subText = model.subText diff --git a/VDS/Components/RadioBox/RadioBoxItem.swift b/VDS/Components/RadioBox/RadioBoxItem.swift index 74e2f574..9da920ef 100644 --- a/VDS/Components/RadioBox/RadioBoxItem.swift +++ b/VDS/Components/RadioBox/RadioBoxItem.swift @@ -243,24 +243,13 @@ open class RadioBoxItem: Control, Changeable { //-------------------------------------------------- open override func updateView() { updateLabels() - updateAccessibilityLabel() + updateAccessibility() setNeedsLayout() layoutIfNeeded() } - open override func updateAccessibilityLabel() { - 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) - } + open override func updateAccessibility() { + super.updateAccessibility() if accessibilityLabel == nil { setAccessibilityLabel(for: [textLabel, subTextLabel, subTextRightLabel]) } diff --git a/VDS/Components/RadioButton/RadioButtonGroup.swift b/VDS/Components/RadioButton/RadioButtonGroup.swift index 9d8f67eb..1ffa55bb 100644 --- a/VDS/Components/RadioButton/RadioButtonGroup.swift +++ b/VDS/Components/RadioButton/RadioButtonGroup.swift @@ -33,13 +33,14 @@ open class RadioButtonGroup: SelectorGroupSelectedHandlerBase { public var selectorModels: [RadioButtonModel]? { didSet { if let selectorModels { - selectorViews = selectorModels.map { model in + selectorViews = selectorModels.enumerated().map { index, model in return RadioButtonItem().with { $0.disabled = model.disabled $0.surface = model.surface $0.inputId = model.inputId $0.value = model.value $0.accessibilityLabel = model.accessibileText + $0.accessibilityValue = "item \(index+1) of \(selectorModels.count)" $0.labelText = model.labelText $0.labelTextAttributes = model.labelTextAttributes $0.childText = model.childText diff --git a/VDS/Components/RadioSwatch/RadioSwatch.swift b/VDS/Components/RadioSwatch/RadioSwatch.swift index 465aef71..f6651bdd 100644 --- a/VDS/Components/RadioSwatch/RadioSwatch.swift +++ b/VDS/Components/RadioSwatch/RadioSwatch.swift @@ -117,7 +117,7 @@ open class RadioSwatch: Control { layer.setNeedsDisplay() } - public override func updateAccessibilityLabel() { + public override func updateAccessibility() { accessibilityLabel = text } diff --git a/VDS/Components/Tabs/Tab.swift b/VDS/Components/Tabs/Tab.swift index 1b7cda8d..4720fabe 100644 --- a/VDS/Components/Tabs/Tab.swift +++ b/VDS/Components/Tabs/Tab.swift @@ -12,7 +12,7 @@ import Combine extension Tabs { @objc(VDSTab) - open class Tab: View { + open class Tab: Control { //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- @@ -20,7 +20,7 @@ extension Tabs { open var index: Int = 0 ///label to write out the text - open var label: Label = Label() + open var label: Label = Label().with { $0.isUserInteractionEnabled = false } ///orientation of the tabs 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. 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. 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. open var text: String = "" { didSet { setNeedsUpdate() } } @@ -71,7 +65,7 @@ extension Tabs { //-------------------------------------------------- // 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 textColorSelectedConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) private var indicatorColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteRed, VDSColor.elementsPrimaryOndark) @@ -125,7 +119,8 @@ extension Tabs { super.setup() addSubview(label) accessibilityTraits = .button - + isAccessibilityElement = true + label.translatesAutoresizingMaskIntoConstraints = false label.pinTrailing() @@ -149,23 +144,18 @@ extension Tabs { layoutGuide.bottomAnchor.constraint(equalTo: bottomAnchor), layoutGuide.leadingAnchor.constraint(equalTo: leadingAnchor), 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() { + super.updateView() + guard !text.isEmpty else { return } //label properties label.text = text label.textStyle = textStyle label.textPosition = textPosition - label.textColor = textColorConfiguration.getColor(self) + label.textColorConfiguration = textColorConfiguration.eraseToAnyColorable() //constaints labelWidthConstraint?.isActive = false @@ -178,12 +168,17 @@ extension Tabs { setNeedsLayout() } + open override func updateAccessibility() { + super.updateAccessibility() + accessibilityLabel = text + } + open override func layoutSubviews() { super.layoutSubviews() removeBorders() - if selected { + if isSelected { addBorder(side: indicatorSide, width: indicatorWidth, color: indicatorColorConfiguration.getColor(self)) } } diff --git a/VDS/Components/Tabs/Tabs.swift b/VDS/Components/Tabs/Tabs.swift index 6728700b..59cfda95 100644 --- a/VDS/Components/Tabs/Tabs.swift +++ b/VDS/Components/Tabs/Tabs.swift @@ -211,20 +211,15 @@ open class Tabs: View { let tabItem = Tab() tabItem.size = size tabItem.text = model.text - tabItem.onClick = model.onClick tabItem.width = model.width tabViews.append(tabItem) tabStackView.addArrangedSubview(tabItem) - - tabItem - .publisher(for: UITapGestureRecognizer()) - .sink { [weak self] gesture in - guard let self, let tabItem = gesture.view as? Tab else { return } - if let selectedIndex = self.tabViews.firstIndex(of: tabItem) { - self.selectedIndex = selectedIndex - self.onTabChange?(selectedIndex) - } - }.store(in: &tabItem.subscribers) + tabItem.onClick = { [weak self] tab in + guard let self else { return } + model.onClick?(tab.index) + self.selectedIndex = tab.index + self.onTabChange?(tab.index) + } } setNeedsUpdate() scrollToSelectedIndex(animated: false) @@ -249,14 +244,14 @@ open class Tabs: View { // Update tab appearance based on properties for (index, tabItem) in tabViews.enumerated() { tabItem.size = size - tabItem.selected = selectedIndex == index + tabItem.isSelected = selectedIndex == index tabItem.index = index tabItem.minWidth = minWidth tabItem.textPosition = textPosition tabItem.orientation = orientation tabItem.surface = surface 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 diff --git a/VDS/Components/TextFields/EntryField/EntryField.swift b/VDS/Components/TextFields/EntryField/EntryField.swift index eb43ba49..d485c007 100644 --- a/VDS/Components/TextFields/EntryField/EntryField.swift +++ b/VDS/Components/TextFields/EntryField/EntryField.swift @@ -295,7 +295,7 @@ open class EntryField: Control, Changeable { } 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 diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index 6b25c7e1..8ee35f0e 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -198,10 +198,6 @@ open class InputField: EntryField, UITextFieldDelegate { } } - public override func updateAccessibilityLabel() { - - } - open override func updateHelperLabel(){ //remove first helperLabel.removeFromSuperview() diff --git a/VDS/Components/Tilelet/Tilelet.swift b/VDS/Components/Tilelet/Tilelet.swift index 9cc2e938..80c66ea0 100644 --- a/VDS/Components/Tilelet/Tilelet.swift +++ b/VDS/Components/Tilelet/Tilelet.swift @@ -376,10 +376,10 @@ open class Tilelet: TileContainer { updateIcons() layoutIfNeeded() - updateAccessibilityLabel() + updateAccessibility() } - open override func updateAccessibilityLabel() { + open override func updateAccessibility() { setAccessibilityLabel(for: [badge.label, titleLockup.eyebrowLabel, titleLockup.titleLabel, titleLockup.subTitleLabel]) } } diff --git a/VDS/Components/TitleLockup/TitleLockup.swift b/VDS/Components/TitleLockup/TitleLockup.swift index 9fd0f6b6..ab745a33 100644 --- a/VDS/Components/TitleLockup/TitleLockup.swift +++ b/VDS/Components/TitleLockup/TitleLockup.swift @@ -46,20 +46,57 @@ open class TitleLockup: View { $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 //-------------------------------------------------- // Sizes are from InVision design specs. open var standardStyleConfiguration: StandardStyleConfigurationProvider = StandardStyleConfigurationProvider(styleConfigurations: [ - .init(deviceType: .iPad, - titleStandardStyles: [.titleSmall, .titleMedium], + .init(deviceType: .iPad, + titleStandardStyles: [.bodySmall], spacingConfigurations: [ - .init(otherStandardStyles: [.bodySmall, .bodyMedium, .bodyLarge], - topSpacing: VDSLayout.Spacing.space2X.value, - bottomSpacing: VDSLayout.Spacing.space2X.value) + .init(otherStandardStyles: [.bodySmall], + topSpacing: VDSLayout.Spacing.space1X.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, titleStandardStyles: [.titleLarge], spacingConfigurations: [ @@ -100,6 +137,30 @@ open class TitleLockup: View { 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, titleStandardStyles: [.titleSmall], spacingConfigurations: [ @@ -152,6 +213,10 @@ open class TitleLockup: View { 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 @@ -182,6 +247,8 @@ open class TitleLockup: View { open override func setup() { super.setup() + titleLabel.textColorConfiguration = textColorPrimaryConfiguration + accessibilityElements = [eyebrowLabel, titleLabel, subTitleLabel] addSubview(stackView) @@ -223,7 +290,7 @@ open class TitleLockup: View { //-------------------------------------------------- open override func updateView() { super.updateView() - + let allLabelsTextPosition = textPosition.value var eyebrowTextIsEmpty = true var titleTextIsEmpty = true @@ -231,7 +298,7 @@ open class TitleLockup: View { var topSpacing: CGFloat = 0.0 var bottomSpacing: CGFloat = 0.0 - + //get the spacing based on the title style and other style used for eyebrow and subtitle if let titleModel, let config = standardStyleConfiguration.spacing(for: titleModel.standardStyle, otherStandardStyle: otherStandardStyle) { @@ -242,15 +309,32 @@ open class TitleLockup: View { if let eyebrowModel, !eyebrowModel.text.isEmpty { eyebrowTextIsEmpty = false eyebrowLabel.textPosition = allLabelsTextPosition - eyebrowLabel.textStyle = eyebrowModel.isBold ? otherStandardStyle.value.bold : otherStandardStyle.value.regular eyebrowLabel.text = eyebrowModel.text eyebrowLabel.attributes = eyebrowModel.textAttributes eyebrowLabel.numberOfLines = eyebrowModel.numberOfLines 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 { eyebrowLabel.reset() } - + if let titleModel, !titleModel.text.isEmpty { titleTextIsEmpty = false titleLabel.textPosition = allLabelsTextPosition @@ -267,11 +351,11 @@ open class TitleLockup: View { subTitleTextIsEmpty = false subTitleLabel.textPosition = allLabelsTextPosition subTitleLabel.textStyle = otherStandardStyle.value.regular + subTitleLabel.textColorConfiguration = subTitleModel.textColor == .secondary ? textColorSecondaryConfiguration : textColorPrimaryConfiguration subTitleLabel.text = subTitleModel.text subTitleLabel.attributes = subTitleModel.textAttributes subTitleLabel.numberOfLines = subTitleModel.numberOfLines subTitleLabel.surface = surface - subTitleLabel.disabled = subTitleModel.textColor == .secondary } else { subTitleLabel.reset() } diff --git a/VDS/Components/TitleLockup/TitleLockupTextStyle.swift b/VDS/Components/TitleLockup/TitleLockupTextStyle.swift index fa2aefba..0b5c7875 100644 --- a/VDS/Components/TitleLockup/TitleLockupTextStyle.swift +++ b/VDS/Components/TitleLockup/TitleLockupTextStyle.swift @@ -23,6 +23,10 @@ extension TitleLockup { case titleMedium case titleSmall + case bodyLarge + case bodyMedium + case bodySmall + public var defaultValue: TextStyle.StandardStyle {.featureXSmall } public var value: TextStyle.StandardStyle { diff --git a/VDS/Components/Toggle/Toggle.swift b/VDS/Components/Toggle/Toggle.swift index 1d9b77b0..e7b50206 100644 --- a/VDS/Components/Toggle/Toggle.swift +++ b/VDS/Components/Toggle/Toggle.swift @@ -237,23 +237,16 @@ open class Toggle: Control, Changeable { toggleView.surface = surface toggleView.disabled = disabled toggleView.isOn = isOn - updateAccessibilityLabel() + updateAccessibility() } - open override func updateAccessibilityLabel() { - accessibilityValue = isSelected ? "1" : "0" - if !accessibilityTraits.contains(.selected) && isSelected { - accessibilityTraits.insert(.selected) - } else if accessibilityTraits.contains(.selected) && !isSelected{ - accessibilityTraits.remove(.selected) + open override func updateAccessibility() { + super.updateAccessibility() + if showText { + setAccessibilityLabel(for: [label]) + } else { + 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 { diff --git a/VDS/Components/Toggle/ToggleView.swift b/VDS/Components/Toggle/ToggleView.swift index db07e66d..b19afeed 100644 --- a/VDS/Components/Toggle/ToggleView.swift +++ b/VDS/Components/Toggle/ToggleView.swift @@ -199,23 +199,12 @@ open class ToggleView: Control, Changeable { //-------------------------------------------------- open override func updateView() { updateToggle() - updateAccessibilityLabel() + updateAccessibility() } - open override func updateAccessibilityLabel() { + open override func updateAccessibility() { + super.updateAccessibility() 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) - } } } diff --git a/VDS/Components/Tooltip/Tooltip.swift b/VDS/Components/Tooltip/Tooltip.swift index 585ab355..5df97beb 100644 --- a/VDS/Components/Tooltip/Tooltip.swift +++ b/VDS/Components/Tooltip/Tooltip.swift @@ -135,7 +135,8 @@ open class Tooltip: Control, TooltipLaunchable { title: tooltip.title, content: tooltip.content, 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) } - open override func updateAccessibilityLabel() { - if !accessibilityTraits.contains(.notEnabled) && !isEnabled { - accessibilityTraits.insert(.notEnabled) - } else if accessibilityTraits.contains(.notEnabled) && !isEnabled { - accessibilityTraits.remove(.notEnabled) - } + open override func updateAccessibility() { + super.updateAccessibility() + var label = title if label == nil { label = content } + accessibilityHint = isEnabled ? "Click to open Tooltip." : "" + accessibilityValue = "collapsed" 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 + } } diff --git a/VDS/Components/Tooltip/TooltipAlertViewController.swift b/VDS/Components/Tooltip/TooltipAlertViewController.swift index 23d84c48..01aa8f09 100644 --- a/VDS/Components/Tooltip/TooltipAlertViewController.swift +++ b/VDS/Components/Tooltip/TooltipAlertViewController.swift @@ -36,7 +36,7 @@ open class TooltipAlertViewController: UIViewController, Surfaceable { open var contentText: String? { didSet { updateView() }} open var contentView: UIView? { didSet { updateView() }} open var closeButtonText: String = "Close" { didSet { updateView() }} - + open var presenter: UIView? { didSet { updateView() }} //-------------------------------------------------- // MARK: - Configuration //-------------------------------------------------- @@ -50,28 +50,41 @@ open class TooltipAlertViewController: UIViewController, Surfaceable { isModalInPresentation = true 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() { + view.accessibilityElements = [tooltipDialog] + //left-right swipe view.publisher(for: UISwipeGestureRecognizer().with{ $0.direction = .right }) .sink { [weak self] swipe in - guard let self else { return } - self.dismiss(animated: true, completion: nil) - }.store(in: &subscribers) - + guard let self else { return } + self.dismiss() + }.store(in: &subscribers) + //tapping in background view.publisher(for: UITapGestureRecognizer().with{ $0.numberOfTapsRequired = 1 }) .sink { [weak self] swipe in - guard let self else { return } - self.dismiss(animated: true, completion: nil) - }.store(in: &subscribers) - + guard let self else { return } + self.dismiss() + }.store(in: &subscribers) + //clicking button onClickSubscriber = tooltipDialog.closeButton.publisher(for: .touchUpInside) .sink {[weak self] button in - guard let self else { return } - self.dismiss(animated: true, completion: nil) - } + guard let self else { return } + self.dismiss() + } view.addSubview(tooltipDialog) @@ -160,9 +173,8 @@ open class TooltipDialog: View, UIScrollViewDelegate { //-------------------------------------------------- open override func setup() { super.setup() - layer.cornerRadius = 8 - + contentStackView.isAccessibilityElement = true contentStackView.addArrangedSubview(titleLabel) contentStackView.addArrangedSubview(contentLabel) scrollView.addSubview(contentStackView) @@ -204,7 +216,7 @@ open class TooltipDialog: View, UIScrollViewDelegate { open override func updateView() { super.updateView() - + backgroundColor = backgroundColorConfiguration.getColor(self) scrollView.indicatorStyle = surface == .light ? .black : .white @@ -257,6 +269,7 @@ open class TooltipDialog: View, UIScrollViewDelegate { closeButton.setTitleColor(closeButtonTextColor, for: .normal) closeButton.setTitleColor(closeButtonTextColor, for: .highlighted) closeButton.setTitle(closeButtonText, for: .normal) + closeButton.accessibilityLabel = closeButtonText contentStackView.setNeedsLayout() contentStackView.layoutIfNeeded() @@ -282,7 +295,19 @@ open class TooltipDialog: View, UIScrollViewDelegate { //stackView between the bottom of the scrollView contentStackViewBottomConstraint?.constant = -containerViewInset } - + 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] + } } diff --git a/VDS/Components/Tooltip/TooltipLaunchable.swift b/VDS/Components/Tooltip/TooltipLaunchable.swift index 17c3f680..33d09b16 100644 --- a/VDS/Components/Tooltip/TooltipLaunchable.swift +++ b/VDS/Components/Tooltip/TooltipLaunchable.swift @@ -9,11 +9,11 @@ import Foundation import UIKit 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 { - 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() { let tooltipViewController = TooltipAlertViewController(nibName: nil, bundle: nil).with { $0.surface = surface @@ -21,6 +21,7 @@ extension TooltipLaunchable { $0.contentText = content $0.contentView = contentView $0.closeButtonText = closeButtonText + $0.presenter = presenter $0.modalPresentationStyle = .overCurrentContext $0.modalTransitionStyle = .crossDissolve } diff --git a/VDS/Components/Tooltip/TrailingTooltipLabel.swift b/VDS/Components/Tooltip/TrailingTooltipLabel.swift index 00cbc8f3..6986dd3a 100644 --- a/VDS/Components/Tooltip/TrailingTooltipLabel.swift +++ b/VDS/Components/Tooltip/TrailingTooltipLabel.swift @@ -57,7 +57,8 @@ open class TrailingTooltipLabel: View, TooltipLaunchable { self.presentTooltip(surface: self.surface, title: self.tooltipTitle, content: self.tooltipContent, - closeButtonText: self.tooltipCloseButtonText) + closeButtonText: self.tooltipCloseButtonText, + presenter: self) }.store(in: &subscribers) } @@ -126,7 +127,8 @@ extension Label { closeButtonText: model.closeButtonText, title: model.title, content: model.content, - contentView: model.contentView) + contentView: model.contentView, + presenter: self) newAttributes.append(tooltip) } diff --git a/VDS/Protocols/ViewProtocol.swift b/VDS/Protocols/ViewProtocol.swift index a73b83c5..63517b35 100644 --- a/VDS/Protocols/ViewProtocol.swift +++ b/VDS/Protocols/ViewProtocol.swift @@ -12,7 +12,7 @@ public protocol ViewProtocol { // Can setup ui here. Should be called in the initialization functions. func setup() - func updateAccessibilityLabel() + func updateAccessibility() } extension ViewProtocol where Self: UIView { diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 418fbd1e..b135fbc6 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -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 ======= - Added new spec for Bottom Inset for TextStyle