Merge branch 'develop' of https://gitlab.verizon.com/BPHV_MIPS/vds_ios into vasavk/carousel
This commit is contained in:
commit
4b016974d2
@ -157,7 +157,7 @@
|
||||
EAC58C182BED0E2300BA39FA /* SecurityCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C172BED0E2300BA39FA /* SecurityCode.swift */; };
|
||||
EAC58C232BF2824200BA39FA /* DatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C222BF2824200BA39FA /* DatePicker.swift */; };
|
||||
EAC58C272BF4116200BA39FA /* DatePickerCalendarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C262BF4116200BA39FA /* DatePickerCalendarModel.swift */; };
|
||||
EAC58C292BF4118C00BA39FA /* DatePickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C282BF4118C00BA39FA /* DatePickerViewController.swift */; };
|
||||
EAC58C292BF4118C00BA39FA /* ClearPopoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C282BF4118C00BA39FA /* ClearPopoverViewController.swift */; };
|
||||
EAC71A1D2A2E155A00E47A9F /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC71A1C2A2E155A00E47A9F /* Checkbox.swift */; };
|
||||
EAC71A1F2A2E173D00E47A9F /* RadioButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC71A1E2A2E173D00E47A9F /* RadioButton.swift */; };
|
||||
EAC846F3294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC846F2294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift */; };
|
||||
@ -177,6 +177,9 @@
|
||||
EAF193432C134F3800C68D18 /* TableCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 443DBAF92BDA303F0021497E /* TableCellItem.swift */; };
|
||||
EAF1FE9929D4850E00101452 /* Clickable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF1FE9829D4850E00101452 /* Clickable.swift */; };
|
||||
EAF1FE9B29DB1A6000101452 /* Changeable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF1FE9A29DB1A6000101452 /* Changeable.swift */; };
|
||||
EAF2F4762C231EAA007BFEDC /* AccessibilityActionElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF2F4752C231EAA007BFEDC /* AccessibilityActionElement.swift */; };
|
||||
EAF2F4782C249D72007BFEDC /* AccessibilityUpdatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF2F4772C249D72007BFEDC /* AccessibilityUpdatable.swift */; };
|
||||
EAF2F4892C2A1075007BFEDC /* AlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF2F4882C2A1075007BFEDC /* AlertViewController.swift */; };
|
||||
EAF7F0952899861000B287F5 /* CheckboxItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0932899861000B287F5 /* CheckboxItem.swift */; };
|
||||
EAF7F09A2899B17200B287F5 /* CATransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0992899B17200B287F5 /* CATransaction.swift */; };
|
||||
EAF7F0A0289AB7EC00B287F5 /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F09F289AB7EC00B287F5 /* View.swift */; };
|
||||
@ -375,7 +378,7 @@
|
||||
EAC58C222BF2824200BA39FA /* DatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePicker.swift; sourceTree = "<group>"; };
|
||||
EAC58C242BF2A7FB00BA39FA /* DatePickerChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = DatePickerChangeLog.txt; sourceTree = "<group>"; };
|
||||
EAC58C262BF4116200BA39FA /* DatePickerCalendarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerCalendarModel.swift; sourceTree = "<group>"; };
|
||||
EAC58C282BF4118C00BA39FA /* DatePickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerViewController.swift; sourceTree = "<group>"; };
|
||||
EAC58C282BF4118C00BA39FA /* ClearPopoverViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearPopoverViewController.swift; sourceTree = "<group>"; };
|
||||
EAC71A1C2A2E155A00E47A9F /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = "<group>"; };
|
||||
EAC71A1E2A2E173D00E47A9F /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = "<group>"; };
|
||||
EAC846F2294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroupCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
@ -407,6 +410,9 @@
|
||||
EAEEECAE2B1FC2BA00531FC2 /* ToggleViewChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ToggleViewChangeLog.txt; sourceTree = "<group>"; };
|
||||
EAF1FE9829D4850E00101452 /* Clickable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clickable.swift; sourceTree = "<group>"; };
|
||||
EAF1FE9A29DB1A6000101452 /* Changeable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Changeable.swift; sourceTree = "<group>"; };
|
||||
EAF2F4752C231EAA007BFEDC /* AccessibilityActionElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityActionElement.swift; sourceTree = "<group>"; };
|
||||
EAF2F4772C249D72007BFEDC /* AccessibilityUpdatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityUpdatable.swift; sourceTree = "<group>"; };
|
||||
EAF2F4882C2A1075007BFEDC /* AlertViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertViewController.swift; sourceTree = "<group>"; };
|
||||
EAF7F0932899861000B287F5 /* CheckboxItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckboxItem.swift; sourceTree = "<group>"; };
|
||||
EAF7F0992899B17200B287F5 /* CATransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CATransaction.swift; sourceTree = "<group>"; };
|
||||
EAF7F09F289AB7EC00B287F5 /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = "<group>"; };
|
||||
@ -732,6 +738,7 @@
|
||||
EA3361AB288B25EC0071C351 /* Protocols */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EAF2F4772C249D72007BFEDC /* AccessibilityUpdatable.swift */,
|
||||
EA4DB2FC28D3D0CA00103EE3 /* AnyEquatable.swift */,
|
||||
EA297A5629FB0A360031ED56 /* AppleGuidelinesTouchable.swift */,
|
||||
EAF1FE9A29DB1A6000101452 /* Changeable.swift */,
|
||||
@ -762,8 +769,11 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EA985C1C296CD13600F2FF2E /* BundleManager.swift */,
|
||||
EAC58C282BF4118C00BA39FA /* ClearPopoverViewController.swift */,
|
||||
EAF7F0B8289C139800B287F5 /* ColorConfiguration.swift */,
|
||||
EAB5FEF02927F4AA00998C17 /* SelfSizingCollectionView.swift */,
|
||||
EAF2F4752C231EAA007BFEDC /* AccessibilityActionElement.swift */,
|
||||
EAF2F4882C2A1075007BFEDC /* AlertViewController.swift */,
|
||||
);
|
||||
path = Classes;
|
||||
sourceTree = "<group>";
|
||||
@ -981,7 +991,6 @@
|
||||
children = (
|
||||
EAC58C222BF2824200BA39FA /* DatePicker.swift */,
|
||||
EAC58C262BF4116200BA39FA /* DatePickerCalendarModel.swift */,
|
||||
EAC58C282BF4118C00BA39FA /* DatePickerViewController.swift */,
|
||||
EAC58C242BF2A7FB00BA39FA /* DatePickerChangeLog.txt */,
|
||||
);
|
||||
path = DatePicker;
|
||||
@ -1227,6 +1236,7 @@
|
||||
EAF7F0A6289B0CE000B287F5 /* Resetable.swift in Sources */,
|
||||
EA985C2D296F03FE00F2FF2E /* TileletIconModels.swift in Sources */,
|
||||
EA89200428AECF4B006B9984 /* UITextField+Publisher.swift in Sources */,
|
||||
EAF2F4782C249D72007BFEDC /* AccessibilityUpdatable.swift in Sources */,
|
||||
18A65A022B96E848006602CC /* Breadcrumbs.swift in Sources */,
|
||||
1842B1E12BECE7B70021AFCA /* CalendarHeaderReusableView.swift in Sources */,
|
||||
EA78C7962C00CAC200430AD1 /* Groupable.swift in Sources */,
|
||||
@ -1265,6 +1275,7 @@
|
||||
EA0D1C412A6AD61C00E5C127 /* Typography+Additional.swift in Sources */,
|
||||
EAC925842911C63100091998 /* Colorable.swift in Sources */,
|
||||
18B463A42BBD3C46005C4528 /* DropdownOptionModel.swift in Sources */,
|
||||
EAF2F4762C231EAA007BFEDC /* AccessibilityActionElement.swift in Sources */,
|
||||
EAC58BFD2BE935C300BA39FA /* TitleLockupTextColor.swift in Sources */,
|
||||
EAACB89A2B927108006A3869 /* Valuing.swift in Sources */,
|
||||
EAE785312BA0A438009428EA /* UIImage+Helper.swift in Sources */,
|
||||
@ -1319,12 +1330,13 @@
|
||||
EA3361BD288B2C760071C351 /* TypeAlias.swift in Sources */,
|
||||
EAC58C0A2BED004E00BA39FA /* FieldType.swift in Sources */,
|
||||
EA471F3A2A95587500CE9E58 /* LayoutConstraintable.swift in Sources */,
|
||||
EAC58C292BF4118C00BA39FA /* DatePickerViewController.swift in Sources */,
|
||||
EAC58C292BF4118C00BA39FA /* ClearPopoverViewController.swift in Sources */,
|
||||
EAF193432C134F3800C68D18 /* TableCellItem.swift in Sources */,
|
||||
EAB1D2CF28ABEF2B00DAE764 /* Typography+Base.swift in Sources */,
|
||||
EA0D1C3B2A6AD51B00E5C127 /* Typogprahy+Styles.swift in Sources */,
|
||||
EAF7F09A2899B17200B287F5 /* CATransaction.swift in Sources */,
|
||||
EAC58C162BED0E0300BA39FA /* InlineAction.swift in Sources */,
|
||||
EAF2F4892C2A1075007BFEDC /* AlertViewController.swift in Sources */,
|
||||
EA0D1C3D2A6AD57600E5C127 /* Typography+Enums.swift in Sources */,
|
||||
EAF1FE9B29DB1A6000101452 /* Changeable.swift in Sources */,
|
||||
EAC58C0C2BED01D500BA39FA /* Telephone.swift in Sources */,
|
||||
@ -1547,7 +1559,7 @@
|
||||
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 66;
|
||||
CURRENT_PROJECT_VERSION = 68;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@ -1585,7 +1597,7 @@
|
||||
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 66;
|
||||
CURRENT_PROJECT_VERSION = 68;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
|
||||
@ -46,7 +46,7 @@ open class Control: UIControl, ViewProtocol, UserInfoable, Clickable {
|
||||
// MARK: - Public Properties
|
||||
//--------------------------------------------------
|
||||
open var shouldUpdateView: Bool = true
|
||||
|
||||
|
||||
open var userInfo = [String: Primitive]()
|
||||
|
||||
open var surface: Surface = .light { didSet { setNeedsUpdate() } }
|
||||
@ -119,17 +119,132 @@ open class Control: UIControl, ViewProtocol, UserInfoable, Clickable {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Overrides
|
||||
//--------------------------------------------------
|
||||
/// Implement accessibilityActivate on an element in order to handle the default action.
|
||||
/// - Returns: Based on whether the userInteraction is enabled.
|
||||
override open func accessibilityActivate() -> Bool {
|
||||
// Hold state in case User wanted isAnimated to remain off.
|
||||
guard isUserInteractionEnabled else { return false }
|
||||
sendActions(for: .touchUpInside)
|
||||
return true
|
||||
}
|
||||
|
||||
open override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Accessibility
|
||||
//--------------------------------------------------
|
||||
open var accessibilityAction: ((Control) -> Void)?
|
||||
|
||||
open override var isAccessibilityElement: Bool {
|
||||
get {
|
||||
var block: AXBoolReturnBlock?
|
||||
|
||||
// if #available(iOS 17, *) {
|
||||
// block = isAccessibilityElementBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_isAccessibilityElementBlock
|
||||
}
|
||||
|
||||
if let block {
|
||||
return block()
|
||||
} else {
|
||||
return super.isAccessibilityElement
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.isAccessibilityElement = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override var accessibilityLabel: String? {
|
||||
get {
|
||||
var block: AXStringReturnBlock?
|
||||
// if #available(iOS 17, *) {
|
||||
// block = accessibilityLabelBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_accessibilityLabelBlock
|
||||
}
|
||||
|
||||
if let block {
|
||||
return block()
|
||||
} else {
|
||||
return super.accessibilityLabel
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.accessibilityLabel = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override var accessibilityHint: String? {
|
||||
get {
|
||||
var block: AXStringReturnBlock?
|
||||
// if #available(iOS 17, *) {
|
||||
// block = accessibilityHintBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_accessibilityHintBlock
|
||||
}
|
||||
|
||||
if let block {
|
||||
return block()
|
||||
} else {
|
||||
return super.accessibilityHint
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.accessibilityHint = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override var accessibilityValue: String? {
|
||||
get {
|
||||
var block: AXStringReturnBlock?
|
||||
|
||||
// if #available(iOS 17, *) {
|
||||
// block = accessibilityHintBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_accessibilityValueBlock
|
||||
}
|
||||
|
||||
if let block{
|
||||
return block()
|
||||
} else {
|
||||
return super.accessibilityValue
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.accessibilityValue = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override func accessibilityActivate() -> Bool {
|
||||
guard isEnabled, isUserInteractionEnabled else { return false }
|
||||
var value = true
|
||||
|
||||
// if #available(iOS 17, *) {
|
||||
// if let block = accessibilityAction {
|
||||
// block(self)
|
||||
// } else if let block = accessibilityActivateBlock {
|
||||
// value = block()
|
||||
//
|
||||
// } else if let block = bridge_accessibilityActivateBlock {
|
||||
// value = block()
|
||||
// }
|
||||
//
|
||||
// } else {
|
||||
if let block = accessibilityAction {
|
||||
block(self)
|
||||
|
||||
} else if let block = bridge_accessibilityActivateBlock {
|
||||
value = block()
|
||||
}
|
||||
// }
|
||||
|
||||
sendActions(for: .touchUpInside)
|
||||
return value
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -104,6 +104,16 @@ open class SelectorBase: Control, SelectorControlable {
|
||||
onClick = { control in
|
||||
control.toggle()
|
||||
}
|
||||
|
||||
bridge_accessibilityLabelBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
return "\(Self.self)\(showError ? ", error" : "")"
|
||||
}
|
||||
|
||||
bridge_accessibilityHintBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
return !isEnabled ? "" : "Double tap to activate."
|
||||
}
|
||||
}
|
||||
|
||||
/// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations.
|
||||
@ -119,12 +129,6 @@ open class SelectorBase: Control, SelectorControlable {
|
||||
setNeedsLayout()
|
||||
layoutIfNeeded()
|
||||
}
|
||||
|
||||
/// Used to update any Accessibility properties.ß
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
accessibilityLabel = "\(Self.self)\(showError ? ", error" : "")"
|
||||
}
|
||||
|
||||
/// This will change the state of the Selector and execute the actionBlock if provided.
|
||||
open func toggle() { }
|
||||
@ -133,4 +137,36 @@ open class SelectorBase: Control, SelectorControlable {
|
||||
super.reset()
|
||||
onChange = nil
|
||||
}
|
||||
|
||||
open override func accessibilityActivate() -> Bool {
|
||||
guard isEnabled, isUserInteractionEnabled else { return false }
|
||||
guard isEnabled, isUserInteractionEnabled else { return false }
|
||||
var value = true
|
||||
|
||||
// if #available(iOS 17, *) {
|
||||
// if let block = accessibilityAction {
|
||||
// block(self)
|
||||
//
|
||||
// } else if let block = accessibilityActivateBlock {
|
||||
// value = block()
|
||||
//
|
||||
// } else if let block = bridge_accessibilityActivateBlock {
|
||||
// value = block()
|
||||
//
|
||||
// } else {
|
||||
// toggle()
|
||||
// }
|
||||
// } else {
|
||||
if let block = accessibilityAction {
|
||||
block(self)
|
||||
|
||||
} else if let block = bridge_accessibilityActivateBlock {
|
||||
value = block()
|
||||
|
||||
} else {
|
||||
toggle()
|
||||
}
|
||||
// }
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,6 +70,13 @@ open class SelectorGroupBase<SelectorItemType: Groupable>: Control, SelectorGrou
|
||||
self?.didSelect(handler)
|
||||
self?.setNeedsUpdate()
|
||||
}
|
||||
|
||||
selector.accessibilityAction = { [weak self] handler in
|
||||
guard let handler = handler as? SelectorItemType else { return }
|
||||
self?.didSelect(handler)
|
||||
self?.setNeedsUpdate()
|
||||
}
|
||||
|
||||
mainStackView.addArrangedSubview(selector)
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ import Combine
|
||||
import VDSCoreTokens
|
||||
|
||||
/// Base Class used to build out a SelectorControlable control.
|
||||
open class SelectorItemBase<Selector: SelectorControlable>: Control, Errorable, Changeable, Groupable {
|
||||
open class SelectorItemBase<Selector: SelectorBase>: Control, Errorable, Changeable, Groupable {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
@ -145,7 +145,14 @@ open class SelectorItemBase<Selector: SelectorControlable>: Control, Errorable,
|
||||
|
||||
open var hiddenValue: AnyHashable? { didSet { setNeedsUpdate() } }
|
||||
|
||||
open var accessibilityValueText: String?
|
||||
open override var accessibilityAction: ((Control) -> Void)? {
|
||||
didSet {
|
||||
selectorView.accessibilityAction = { [weak self] selectorItemBase in
|
||||
guard let self else { return }
|
||||
accessibilityAction?(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Overrides
|
||||
@ -153,21 +160,61 @@ open class SelectorItemBase<Selector: SelectorControlable>: Control, Errorable,
|
||||
/// Executed on initialization for this View.
|
||||
open override func initialSetup() {
|
||||
super.initialSetup()
|
||||
onClick = { control in
|
||||
control.toggle()
|
||||
onClick = { [weak self] control in
|
||||
guard let self, isEnabled else { return }
|
||||
toggle()
|
||||
}
|
||||
|
||||
selectorView.accessibilityAction = { [weak self] _ in
|
||||
guard let self, isEnabled else { return }
|
||||
toggle()
|
||||
}
|
||||
|
||||
selectorView.bridge_accessibilityLabelBlock = { [weak self ] in
|
||||
guard let self else { return "" }
|
||||
var accessibilityLabels = [String]()
|
||||
|
||||
if isSelected {
|
||||
accessibilityLabels.append("selected")
|
||||
}
|
||||
|
||||
accessibilityLabels.append("\(Selector.self)")
|
||||
|
||||
if let text = labelText, !text.isEmpty {
|
||||
accessibilityLabels.append(text)
|
||||
}
|
||||
|
||||
if let text = childText, !text.isEmpty {
|
||||
accessibilityLabels.append(text)
|
||||
}
|
||||
|
||||
if !isEnabled {
|
||||
accessibilityLabels.append("dimmed")
|
||||
}
|
||||
|
||||
if let errorText, showError, !errorText.isEmpty {
|
||||
accessibilityLabels.append("error, \(errorText)")
|
||||
}
|
||||
|
||||
return accessibilityLabels.joined(separator: ", ")
|
||||
}
|
||||
|
||||
selectorView.bridge_accessibilityHintBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
return !isEnabled ? "" : "Double tap to activate."
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations.
|
||||
open override func setup() {
|
||||
super.setup()
|
||||
|
||||
selectorView.isAccessibilityElement = false
|
||||
isAccessibilityElement = true
|
||||
accessibilityTraits = .button
|
||||
selectorView.isAccessibilityElement = true
|
||||
isAccessibilityElement = false
|
||||
addSubview(mainStackView)
|
||||
mainStackView.isUserInteractionEnabled = false
|
||||
|
||||
mainStackView.isUserInteractionEnabled = false
|
||||
mainStackView.addArrangedSubview(selectorStackView)
|
||||
mainStackView.addArrangedSubview(errorLabel)
|
||||
selectorStackView.addArrangedSubview(selectorView)
|
||||
@ -191,14 +238,47 @@ open class SelectorItemBase<Selector: SelectorControlable>: Control, Errorable,
|
||||
selectorView.isEnabled = isEnabled
|
||||
selectorView.surface = surface
|
||||
}
|
||||
|
||||
/// Used to update any Accessibility properties.
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
setAccessibilityLabel(for: [selectorView, label, childLabel, errorLabel])
|
||||
accessibilityValue = accessibilityValueText
|
||||
|
||||
open override var accessibilityElements: [Any]? {
|
||||
get {
|
||||
var elements = [Any]()
|
||||
|
||||
elements.append(selectorView)
|
||||
|
||||
if let text = labelText, !text.isEmpty {
|
||||
elements.append(label)
|
||||
}
|
||||
|
||||
if let text = childText, !text.isEmpty {
|
||||
elements.append(childLabel)
|
||||
}
|
||||
|
||||
if let errorText, showError, !errorText.isEmpty {
|
||||
elements.append(errorLabel)
|
||||
}
|
||||
return elements
|
||||
}
|
||||
set {
|
||||
super.accessibilityElements = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Overriden to take the hit if there is an onClickSubscriber and the view is not a UIControl
|
||||
open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
guard isEnabled else { return super.hitTest(point, with: event) }
|
||||
|
||||
let labelPoint = convert(point, to: label)
|
||||
let childLabelPoint = convert(point, to: childLabel)
|
||||
if label.isAction(for: labelPoint) {
|
||||
return label
|
||||
} else if childLabel.isAction(for: childLabelPoint) {
|
||||
return childLabel
|
||||
} else {
|
||||
guard !UIAccessibility.isVoiceOverRunning else { return nil }
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets to default settings.
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
@ -290,4 +370,34 @@ open class SelectorItemBase<Selector: SelectorControlable>: Control, Errorable,
|
||||
/// This will change to state of the Selector.
|
||||
open func toggle() {}
|
||||
|
||||
open override func accessibilityActivate() -> Bool {
|
||||
guard isEnabled, isUserInteractionEnabled else { return false }
|
||||
var value = true
|
||||
|
||||
// if #available(iOS 17, *) {
|
||||
// if let block = accessibilityAction {
|
||||
// block(self)
|
||||
//
|
||||
// } else if let block = accessibilityActivateBlock {
|
||||
// value = block()
|
||||
//
|
||||
// } else if let block = bridge_accessibilityActivateBlock {
|
||||
// value = block()
|
||||
//
|
||||
// } else {
|
||||
// toggle()
|
||||
// }
|
||||
// } else {
|
||||
if let block = accessibilityAction {
|
||||
block(self)
|
||||
|
||||
} else if let block = bridge_accessibilityActivateBlock {
|
||||
value = block()
|
||||
|
||||
} else {
|
||||
toggle()
|
||||
}
|
||||
// }
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ open class View: UIView, ViewProtocol, UserInfoable {
|
||||
open var surface: Surface = .light { didSet { setNeedsUpdate() } }
|
||||
|
||||
open var isEnabled: Bool = true { didSet { setNeedsUpdate() } }
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Lifecycle
|
||||
//--------------------------------------------------
|
||||
@ -91,4 +91,132 @@ open class View: UIView, ViewProtocol, UserInfoable {
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Accessibility
|
||||
//--------------------------------------------------
|
||||
open var accessibilityAction: ((View) -> Void)?
|
||||
|
||||
open override var isAccessibilityElement: Bool {
|
||||
get {
|
||||
var block: AXBoolReturnBlock?
|
||||
|
||||
// if #available(iOS 17, *) {
|
||||
// block = isAccessibilityElementBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_isAccessibilityElementBlock
|
||||
}
|
||||
|
||||
if let block {
|
||||
return block()
|
||||
} else {
|
||||
return super.isAccessibilityElement
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.isAccessibilityElement = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override var accessibilityLabel: String? {
|
||||
get {
|
||||
var block: AXStringReturnBlock?
|
||||
// if #available(iOS 17, *) {
|
||||
// block = accessibilityLabelBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_accessibilityLabelBlock
|
||||
}
|
||||
|
||||
if let block {
|
||||
return block()
|
||||
} else {
|
||||
return super.accessibilityLabel
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.accessibilityLabel = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override var accessibilityHint: String? {
|
||||
get {
|
||||
var block: AXStringReturnBlock?
|
||||
// if #available(iOS 17, *) {
|
||||
// block = accessibilityHintBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_accessibilityHintBlock
|
||||
}
|
||||
|
||||
if let block {
|
||||
return block()
|
||||
} else {
|
||||
return super.accessibilityHint
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.accessibilityHint = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override var accessibilityValue: String? {
|
||||
get {
|
||||
var block: AXStringReturnBlock?
|
||||
|
||||
// if #available(iOS 17, *) {
|
||||
// block = accessibilityHintBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_accessibilityValueBlock
|
||||
}
|
||||
|
||||
if let block{
|
||||
return block()
|
||||
} else {
|
||||
return super.accessibilityValue
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.accessibilityValue = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override func accessibilityActivate() -> Bool {
|
||||
guard isEnabled, isUserInteractionEnabled else { return false }
|
||||
|
||||
// if #available(iOS 17, *) {
|
||||
// if let block = accessibilityAction {
|
||||
// block(self)
|
||||
// return true
|
||||
// } else if let block = accessibilityActivateBlock {
|
||||
// return block()
|
||||
//
|
||||
// } else if let block = bridge_accessibilityActivateBlock {
|
||||
// return block()
|
||||
//
|
||||
// } else {
|
||||
// return true
|
||||
//
|
||||
// }
|
||||
//
|
||||
// } else {
|
||||
if let block = accessibilityAction {
|
||||
block(self)
|
||||
return true
|
||||
|
||||
} else if let block = bridge_accessibilityActivateBlock {
|
||||
return block()
|
||||
|
||||
} else {
|
||||
return super.accessibilityActivate()
|
||||
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
21
VDS/Classes/AccessibilityActionElement.swift
Normal file
21
VDS/Classes/AccessibilityActionElement.swift
Normal file
@ -0,0 +1,21 @@
|
||||
//
|
||||
// AccessibilityActionElement.swift
|
||||
// VDS
|
||||
//
|
||||
// Created by Matt Bruce on 6/19/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
/// Custom UIAccessibilityElement that allows you to set the default action used in accessibilityActivate.
|
||||
public class AccessibilityActionElement: UIAccessibilityElement {
|
||||
public var accessibilityAction: AXVoidReturnBlock?
|
||||
|
||||
public override func accessibilityActivate() -> Bool {
|
||||
guard let accessibilityAction else { return super.accessibilityActivate() }
|
||||
accessibilityAction()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
96
VDS/Classes/AlertViewController.swift
Normal file
96
VDS/Classes/AlertViewController.swift
Normal file
@ -0,0 +1,96 @@
|
||||
//
|
||||
// AlertViewController.swift
|
||||
// VDS
|
||||
//
|
||||
// Created by Matt Bruce on 6/24/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Combine
|
||||
import VDSCoreTokens
|
||||
|
||||
open class AlertViewController: UIViewController, Surfaceable {
|
||||
|
||||
/// Set of Subscribers for any Publishers for this Control.
|
||||
open var subscribers = Set<AnyCancellable>()
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Properties
|
||||
//--------------------------------------------------
|
||||
private var onClickSubscriber: AnyCancellable? {
|
||||
willSet {
|
||||
if let onClickSubscriber {
|
||||
onClickSubscriber.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Public Properties
|
||||
//--------------------------------------------------
|
||||
/// Current Surface and this is used to pass down to child objects that implement Surfacable
|
||||
open var surface: Surface = .light { didSet { updateView() }}
|
||||
open var presenter: UIView? { didSet { updateView() }}
|
||||
open var dialog: UIView!
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Configuration
|
||||
//--------------------------------------------------
|
||||
private let backgroundColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundPrimaryDark, VDSColor.backgroundPrimaryLight)
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Lifecycle
|
||||
//--------------------------------------------------
|
||||
open override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
isModalInPresentation = true
|
||||
setup()
|
||||
}
|
||||
open override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
UIAccessibility.post(notification: .screenChanged, argument: dialog)
|
||||
}
|
||||
|
||||
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() {
|
||||
guard let dialog else { return }
|
||||
view.accessibilityElements = [dialog]
|
||||
view.addSubview(dialog)
|
||||
|
||||
// Activate constraints
|
||||
NSLayoutConstraint.activate([
|
||||
// Constraints for the floating modal view
|
||||
dialog.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||
dialog.centerYAnchor.constraint(equalTo: view.centerYAnchor),
|
||||
dialog.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: 10),
|
||||
dialog.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -10),
|
||||
dialog.topAnchor.constraint(greaterThanOrEqualTo: view.topAnchor, constant: 10),
|
||||
dialog.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor, constant: -10)
|
||||
])
|
||||
}
|
||||
|
||||
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
guard let touch = touches.first else { return }
|
||||
let location = touch.location(in: view)
|
||||
if dialog.frame.contains(location) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
} else {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to make changes to the View based off a change events or from local properties.
|
||||
open func updateView() {
|
||||
view.backgroundColor = backgroundColorConfiguration.getColor(self).withAlphaComponent(0.3)
|
||||
if var dialog = dialog as? Surfaceable {
|
||||
dialog.surface = surface
|
||||
}
|
||||
}
|
||||
}
|
||||
152
VDS/Classes/ClearPopoverViewController.swift
Normal file
152
VDS/Classes/ClearPopoverViewController.swift
Normal file
@ -0,0 +1,152 @@
|
||||
//
|
||||
// DatePickerPopoverViewController.swift
|
||||
// VDS
|
||||
//
|
||||
// Created by Matt Bruce on 5/14/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
open class ClearPopoverViewController: UIViewController, UIPopoverPresentationControllerDelegate {
|
||||
|
||||
/// The view to be inserted inside the popover
|
||||
private var contentView: UIView!
|
||||
|
||||
/// An object representing the arrow of the popover.
|
||||
private var arrow: UIPopoverArrowDirection
|
||||
|
||||
/// Popover presentation controller of the popover
|
||||
private var popOver: UIPopoverPresentationController!
|
||||
|
||||
open var maxWidth: CGFloat?
|
||||
|
||||
open var sourceRect: CGRect?
|
||||
|
||||
open var spacing: CGFloat = 0
|
||||
/**
|
||||
A controller that manages the popover.
|
||||
- Parameter contentView: The view to be inserted inside the popover.
|
||||
- Parameter design: An object used for defining visual attributes of the popover.
|
||||
- Parameter arrow: An object representing the arrow in popover.
|
||||
- Parameter sourceView: The view containing the anchor rectangle for the popover.
|
||||
- Parameter sourceRect: The rectangle in the specified view in which to anchor the popover.
|
||||
- Parameter barButtonItem: The bar button item on which to anchor the popover.
|
||||
|
||||
Assign a value to `barButton` to anchor the popover to the specified bar button item. When presented, the popover’s arrow points to the specified item. Alternatively, you may specify the anchor location for the popover using the `sourceView` and `sourceRect` properties.
|
||||
*/
|
||||
public init(contentView: UIView, arrow: UIPopoverArrowDirection, sourceView: UIView? = nil, sourceRect: CGRect? = nil, spacing: CGFloat = 0, barButtonItem: UIBarButtonItem? = nil) {
|
||||
self.contentView = contentView
|
||||
self.spacing = spacing
|
||||
self.arrow = arrow
|
||||
self.sourceRect = sourceRect
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
setupPopover(sourceView, sourceRect, barButtonItem)
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
open override func viewIsAppearing(_ animated: Bool) {
|
||||
super.viewIsAppearing(animated)
|
||||
view.superview?.accessibilityIdentifier = "HadCornerRadius"
|
||||
view.accessibilityIdentifier = "PopoverViewController.View"
|
||||
contentView.accessibilityIdentifier = "PopoverViewController.ContentView"
|
||||
view.superview?.layer.cornerRadius = 0
|
||||
}
|
||||
|
||||
open override func viewDidLayoutSubviews() {
|
||||
contentView.frame.origin = CGPoint(x: 0, y: 0)
|
||||
}
|
||||
|
||||
///Sets up the Popover and starts the timer for its closing.
|
||||
private func setupPopover(_ sourceView: UIView?, _ sourceRect: CGRect?, _ barButtonItem: UIBarButtonItem?) {
|
||||
modalPresentationStyle = .popover
|
||||
view.addSubview(contentView)
|
||||
|
||||
popOver = self.popoverPresentationController!
|
||||
popOver.popoverLayoutMargins = .zero
|
||||
popOver.popoverBackgroundViewClass = ClearPopoverBackgroundView.self
|
||||
popOver.sourceView = sourceView
|
||||
popOver.popoverLayoutMargins = .zero
|
||||
if let sourceRect = sourceRect {
|
||||
popOver.sourceRect = sourceRect
|
||||
}
|
||||
|
||||
popOver.barButtonItem = barButtonItem
|
||||
popOver.delegate = self
|
||||
popOver.permittedArrowDirections = arrow
|
||||
popOver.backgroundColor = .clear
|
||||
|
||||
}
|
||||
|
||||
open func popoverPresentationController(_ popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverTo rect: UnsafeMutablePointer<CGRect>, in view: AutoreleasingUnsafeMutablePointer<UIView>) {
|
||||
if let presentedView = popoverPresentationController.presentedViewController.view.superview {
|
||||
presentedView.layer.cornerRadius = 0
|
||||
}
|
||||
}
|
||||
|
||||
private func updatePopoverPosition() {
|
||||
guard let popoverPresentationController = popoverPresentationController else { return }
|
||||
if let sourceView = popoverPresentationController.sourceView, let sourceRect {
|
||||
popoverPresentationController.sourceRect = sourceRect
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure to handle rotations
|
||||
open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
coordinator.animate(alongsideTransition: { [weak self] _ in
|
||||
self?.updatePopoverPosition()
|
||||
})
|
||||
}
|
||||
|
||||
open func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
|
||||
return .none
|
||||
}
|
||||
|
||||
// Returns presentation controller of the popover
|
||||
open func getPopoverPresentationController() -> UIPopoverPresentationController {
|
||||
return popOver
|
||||
}
|
||||
}
|
||||
|
||||
open class ClearPopoverBackgroundView: UIPopoverBackgroundView {
|
||||
open override var arrowOffset: CGFloat {
|
||||
get { 0 }
|
||||
set { }
|
||||
}
|
||||
|
||||
open override var arrowDirection: UIPopoverArrowDirection {
|
||||
get { .any }
|
||||
set { }
|
||||
}
|
||||
|
||||
open override class var wantsDefaultContentAppearance: Bool {
|
||||
false
|
||||
}
|
||||
|
||||
open override class func contentViewInsets() -> UIEdgeInsets{
|
||||
.zero
|
||||
}
|
||||
|
||||
open override class func arrowHeight() -> CGFloat {
|
||||
0
|
||||
}
|
||||
|
||||
open override class func arrowBase() -> CGFloat{
|
||||
0
|
||||
}
|
||||
|
||||
open override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
layer.shadowOpacity = 0
|
||||
layer.shadowRadius = 0
|
||||
layer.cornerRadius = 0
|
||||
}
|
||||
|
||||
open override func draw(_ rect: CGRect) {
|
||||
|
||||
}
|
||||
}
|
||||
@ -147,6 +147,11 @@ open class Badge: View {
|
||||
label.widthGreaterThanEqualTo(constant: minWidth)
|
||||
maxWidthConstraint = label.widthLessThanEqualTo(constant: 0).with { $0.isActive = false }
|
||||
clipsToBounds = true
|
||||
|
||||
bridge_accessibilityLabelBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets to default settings.
|
||||
@ -179,10 +184,4 @@ open class Badge: View {
|
||||
label.surface = surface
|
||||
label.isEnabled = isEnabled
|
||||
}
|
||||
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
|
||||
accessibilityLabel = text
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,6 +292,16 @@ open class BadgeIndicator: View {
|
||||
label.centerYAnchor.constraint(equalTo: badgeView.centerYAnchor).isActive = true
|
||||
labelContraints.isActive = true
|
||||
|
||||
bridge_accessibilityLabelBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
if let accessibilityText {
|
||||
return kind == .numbered ? label.text + " " + accessibilityText : accessibilityText
|
||||
} else if kind == .numbered {
|
||||
return label.text
|
||||
} else {
|
||||
return "Simple"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets to default settings.
|
||||
@ -347,17 +357,6 @@ open class BadgeIndicator: View {
|
||||
setNeedsLayout()
|
||||
}
|
||||
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
if let accessibilityText {
|
||||
accessibilityLabel = kind == .numbered ? label.text + " " + accessibilityText : accessibilityText
|
||||
} else if kind == .numbered {
|
||||
accessibilityLabel = label.text
|
||||
} else {
|
||||
accessibilityLabel = "Simple"
|
||||
}
|
||||
}
|
||||
|
||||
open override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
|
||||
@ -82,6 +82,15 @@ open class BreadcrumbItem: ButtonBase {
|
||||
isAccessibilityElement = true
|
||||
accessibilityTraits = .link
|
||||
|
||||
bridge_accessibilityHintBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
return !isEnabled ? "" : "Double tap to open."
|
||||
}
|
||||
|
||||
bridge_accessibilityLabelBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to make changes to the View based off a change events or from local properties.
|
||||
@ -134,10 +143,4 @@ open class BreadcrumbItem: ButtonBase {
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
/// Used to update any Accessibility properties.
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
accessibilityLabel = text
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable {
|
||||
//--------------------------------------------------
|
||||
/// Key of whether or not updateView() is called in setNeedsUpdate()
|
||||
open var shouldUpdateView: Bool = true
|
||||
|
||||
|
||||
open var surface: Surface = .light { didSet { setNeedsUpdate() } }
|
||||
|
||||
/// Text that will be used in the titleLabel.
|
||||
@ -75,7 +75,7 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable {
|
||||
|
||||
/// Whether the Button should handle the isHighlighted state.
|
||||
open var shouldHighlight: Bool { isHighlighting == false }
|
||||
|
||||
|
||||
/// Whether the Control is highlighted or not.
|
||||
open override var isHighlighted: Bool {
|
||||
didSet {
|
||||
@ -139,7 +139,7 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable {
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Methods
|
||||
//--------------------------------------------------
|
||||
@ -172,6 +172,129 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable {
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Accessibility
|
||||
//--------------------------------------------------
|
||||
open var accessibilityAction: ((ButtonBase) -> Void)?
|
||||
|
||||
open override var isAccessibilityElement: Bool {
|
||||
get {
|
||||
var block: AXBoolReturnBlock?
|
||||
|
||||
// if #available(iOS 17, *) {
|
||||
// block = isAccessibilityElementBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_isAccessibilityElementBlock
|
||||
}
|
||||
|
||||
if let block {
|
||||
return block()
|
||||
} else {
|
||||
return super.isAccessibilityElement
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.isAccessibilityElement = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override var accessibilityLabel: String? {
|
||||
get {
|
||||
var block: AXStringReturnBlock?
|
||||
// if #available(iOS 17, *) {
|
||||
// block = accessibilityLabelBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_accessibilityLabelBlock
|
||||
}
|
||||
|
||||
if let block {
|
||||
return block()
|
||||
} else {
|
||||
return super.accessibilityLabel
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.accessibilityLabel = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override var accessibilityHint: String? {
|
||||
get {
|
||||
var block: AXStringReturnBlock?
|
||||
// if #available(iOS 17, *) {
|
||||
// block = accessibilityHintBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_accessibilityHintBlock
|
||||
}
|
||||
|
||||
if let block {
|
||||
return block()
|
||||
} else {
|
||||
return super.accessibilityHint
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.accessibilityHint = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override var accessibilityValue: String? {
|
||||
get {
|
||||
var block: AXStringReturnBlock?
|
||||
|
||||
// if #available(iOS 17, *) {
|
||||
// block = accessibilityHintBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_accessibilityValueBlock
|
||||
}
|
||||
|
||||
if let block{
|
||||
return block()
|
||||
} else {
|
||||
return super.accessibilityValue
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.accessibilityValue = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override func accessibilityActivate() -> Bool {
|
||||
guard isEnabled, isUserInteractionEnabled else { return false }
|
||||
var value = true
|
||||
|
||||
// if #available(iOS 17, *) {
|
||||
// if let block = accessibilityAction {
|
||||
// block(self)
|
||||
// } else if let block = accessibilityActivateBlock {
|
||||
// value = block()
|
||||
//
|
||||
// } else if let block = bridge_accessibilityActivateBlock {
|
||||
// value = block()
|
||||
// }
|
||||
//
|
||||
// } else {
|
||||
if let block = accessibilityAction {
|
||||
block(self)
|
||||
|
||||
} else if let block = bridge_accessibilityActivateBlock {
|
||||
value = block()
|
||||
}
|
||||
// }
|
||||
|
||||
sendActions(for: .touchUpInside)
|
||||
return value
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: AppleGuidelinesTouchable
|
||||
|
||||
@ -105,6 +105,12 @@ open class TextLink: ButtonBase {
|
||||
lineHeightConstraint = line.height(constant: 1)
|
||||
lineHeightConstraint?.isActive = true
|
||||
}
|
||||
|
||||
bridge_accessibilityHintBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
return !isEnabled ? "" : "Double tap to open."
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Used to make changes to the View based off a change events or from local properties.
|
||||
|
||||
@ -86,6 +86,12 @@ open class TextLinkCaret: ButtonBase {
|
||||
accessibilityTraits = .link
|
||||
titleLabel?.numberOfLines = 0
|
||||
titleLabel?.lineBreakMode = .byWordWrapping
|
||||
|
||||
bridge_accessibilityHintBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
return !isEnabled ? "" : "Double tap to open."
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Used to make changes to the View based off a change events or from local properties.
|
||||
|
||||
@ -160,7 +160,7 @@ open class CalendarBase: Control, Changeable {
|
||||
if (minDate <= maxDate) {
|
||||
// Check if current date falls between min & max dates.
|
||||
let fallsBetween = displayDate.isBetweeen(date: minDate, andDate: maxDate)
|
||||
displayDate = fallsBetween ? displayDate : minDate
|
||||
displayDate = fallsBetween ? displayDate : (displayDate.monthInt == minDate.monthInt) ? minDate : maxDate
|
||||
fetchDates(with: displayDate)
|
||||
}
|
||||
containerView.backgroundColor = transparentBackground ? .clear : backgroundColorConfiguration.getColor(self)
|
||||
@ -201,7 +201,7 @@ open class CalendarBase: Control, Changeable {
|
||||
}
|
||||
}
|
||||
updateViewConstraints()
|
||||
}
|
||||
}
|
||||
|
||||
func updateViewConstraints() {
|
||||
collectionView.reloadData()
|
||||
@ -331,38 +331,28 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
|
||||
if let cell = collectionView.cellForItem(at: indexPath) as? CalendarDateViewCell {
|
||||
let isEnabled: Bool = cell.isDateEnabled()
|
||||
if isEnabled {
|
||||
cell.activeModeStart()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
// reload selected index, if it is in enabled state.
|
||||
if let cell = collectionView.cellForItem(at: indexPath) as? CalendarDateViewCell {
|
||||
let isEnabled: Bool = cell.isDateEnabled()
|
||||
if isEnabled {
|
||||
cell.activeModeEnd()
|
||||
|
||||
// Callback to pass selected date if it is enabled only.
|
||||
selectedDate = dates[indexPath.row]
|
||||
sendActions(for: .valueChanged)
|
||||
displayDate = selectedDate
|
||||
|
||||
var reloadIndexPaths = [indexPath]
|
||||
|
||||
// If an cell is already selected, then it needs to be deselected.
|
||||
// Add its index path to the array of index paths to be reloaded.
|
||||
if let deselectIndexPath = selectedIndexPath {
|
||||
reloadIndexPaths.append(deselectIndexPath)
|
||||
let hasDate: Bool = cell.hasText()
|
||||
if hasDate {
|
||||
let isEnabled: Bool = cell.isDateEnabled()
|
||||
if isEnabled {
|
||||
// Callback to pass selected date if it is enabled only.
|
||||
selectedDate = dates[indexPath.row]
|
||||
sendActions(for: .valueChanged)
|
||||
displayDate = selectedDate
|
||||
|
||||
var reloadIndexPaths = [indexPath]
|
||||
|
||||
// If an cell is already selected, then it needs to be deselected.
|
||||
// Add its index path to the array of index paths to be reloaded.
|
||||
if let deselectIndexPath = selectedIndexPath {
|
||||
reloadIndexPaths.append(deselectIndexPath)
|
||||
}
|
||||
|
||||
collectionView.reloadItems(at: reloadIndexPaths)
|
||||
}
|
||||
|
||||
collectionView.reloadItems(at: reloadIndexPaths)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,6 +41,21 @@ final class CalendarDateViewCell: UICollectionViewCell {
|
||||
$0.textStyle = .bodySmall
|
||||
}
|
||||
|
||||
override var isHighlighted: Bool {
|
||||
didSet{
|
||||
if self.isHighlighted && hasText() && isDateEnabled() {
|
||||
self.contentView.layer.borderColor = activeBorderColorConfiguration.getColor(surface).cgColor
|
||||
self.contentView.layer.borderWidth = VDSFormControls.borderWidth
|
||||
self.contentView.layer.cornerRadius = VDSFormControls.borderRadius
|
||||
} else {
|
||||
self.contentView.layer.borderColor = nil
|
||||
self.contentView.layer.borderWidth = 0
|
||||
self.contentView.layer.cornerRadius = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var isEnabled = false
|
||||
private lazy var shapeLayer = CAShapeLayer()
|
||||
private var surface: Surface = .light
|
||||
private let selectedTextColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryInverseOnlight, VDSColor.elementsPrimaryInverseOndark)
|
||||
@ -120,20 +135,21 @@ final class CalendarDateViewCell: UICollectionViewCell {
|
||||
}
|
||||
}
|
||||
|
||||
// update text color, bg color, corner radius.
|
||||
if numberLabel.text == selectedDate.getDay()
|
||||
&& selectedDate.monthInt == displayDate.monthInt
|
||||
&& selectedDate.yearInt == displayDate.yearInt
|
||||
&& numberLabel.isEnabled {
|
||||
|
||||
numberLabel.textColor = selectedTextColorConfiguration.getColor(surface)
|
||||
layer.backgroundColor = selectedBackgroundColor.getColor(surface).cgColor
|
||||
layer.cornerRadius = VDSFormControls.borderRadius
|
||||
|
||||
} else {
|
||||
numberLabel.textColor = unselectedTextColorConfiguration.getColor(surface)
|
||||
layer.backgroundColor = nil
|
||||
layer.cornerRadius = 0
|
||||
// Set selected/unselected state text color, bg color, corner radius if cell is in enabled state.
|
||||
if isEnabled {
|
||||
if numberLabel.text == selectedDate.getDay()
|
||||
&& selectedDate.monthInt == displayDate.monthInt
|
||||
&& selectedDate.yearInt == displayDate.yearInt {
|
||||
|
||||
numberLabel.textColor = selectedTextColorConfiguration.getColor(surface)
|
||||
layer.backgroundColor = selectedBackgroundColor.getColor(surface).cgColor
|
||||
layer.cornerRadius = VDSFormControls.borderRadius
|
||||
|
||||
} else {
|
||||
numberLabel.textColor = unselectedTextColorConfiguration.getColor(surface)
|
||||
layer.backgroundColor = nil
|
||||
layer.cornerRadius = 0
|
||||
}
|
||||
}
|
||||
|
||||
// add indicators.
|
||||
@ -155,26 +171,18 @@ final class CalendarDateViewCell: UICollectionViewCell {
|
||||
numberLabel.textStyle = .bodySmall
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func hasText() -> Bool {
|
||||
return !numberLabel.text.isEmpty
|
||||
}
|
||||
|
||||
// returns cell enabled state.
|
||||
func isDateEnabled() -> Bool {
|
||||
return numberLabel.isEnabled
|
||||
}
|
||||
|
||||
func activeModeStart() {
|
||||
numberLabel.layer.borderColor = activeBorderColorConfiguration.getColor(surface).cgColor
|
||||
numberLabel.layer.borderWidth = VDSFormControls.borderWidth
|
||||
numberLabel.layer.cornerRadius = VDSFormControls.borderRadius
|
||||
}
|
||||
|
||||
func activeModeEnd() {
|
||||
numberLabel.layer.borderColor = nil
|
||||
numberLabel.layer.borderWidth = 0
|
||||
numberLabel.layer.cornerRadius = 0
|
||||
return isEnabled
|
||||
}
|
||||
|
||||
func disableLabel(with surface: Surface) {
|
||||
numberLabel.isEnabled = false
|
||||
isEnabled = false
|
||||
numberLabel.textColor = disabledTextColorConfiguration.getColor(surface)
|
||||
layer.backgroundColor = disabledBackgroundColor.getColor(surface).cgColor
|
||||
}
|
||||
@ -183,7 +191,7 @@ final class CalendarDateViewCell: UICollectionViewCell {
|
||||
for x in 0...activeDates.count-1 {
|
||||
if activeDates[x].monthInt == displayDate.monthInt && activeDates[x].yearInt == displayDate.yearInt {
|
||||
if let day:Int = Int(numberLabel.text), day == activeDates[x].dayInt {
|
||||
numberLabel.isEnabled = true
|
||||
isEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -194,7 +202,7 @@ final class CalendarDateViewCell: UICollectionViewCell {
|
||||
if activeDates.count > 0 && inactiveDates.count == 0 {
|
||||
showActiveDates(with: displayDate, activeDates: activeDates, inactiveDates: inactiveDates)
|
||||
} else {
|
||||
numberLabel.isEnabled = true
|
||||
isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,7 +212,7 @@ final class CalendarDateViewCell: UICollectionViewCell {
|
||||
disableLabel(with: surface)
|
||||
showActiveDates(with: displayDate, activeDates: activeDates, inactiveDates: inactiveDates)
|
||||
} else {
|
||||
numberLabel.isEnabled = true
|
||||
isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,7 +221,7 @@ final class CalendarDateViewCell: UICollectionViewCell {
|
||||
if let day = Int(numberLabel.text), day < minDate.dayInt {
|
||||
disableLabel(with: surface)
|
||||
} else {
|
||||
numberLabel.isEnabled = false
|
||||
isEnabled = false
|
||||
handleActiveDates(with: displayDate, activeDates: activeDates, inactiveDates: inactiveDates)
|
||||
}
|
||||
}
|
||||
@ -223,7 +231,7 @@ final class CalendarDateViewCell: UICollectionViewCell {
|
||||
if let day = Int(numberLabel.text), day > maxDate.dayInt {
|
||||
disableLabel(with: surface)
|
||||
} else {
|
||||
numberLabel.isEnabled = false
|
||||
isEnabled = false
|
||||
handleActiveDates(with: displayDate, activeDates: activeDates, inactiveDates: inactiveDates)
|
||||
}
|
||||
}
|
||||
@ -233,7 +241,7 @@ final class CalendarDateViewCell: UICollectionViewCell {
|
||||
if let day = Int(numberLabel.text), day < minDate.dayInt || day > maxDate.dayInt {
|
||||
disableLabel(with: surface)
|
||||
} else {
|
||||
numberLabel.isEnabled = false
|
||||
isEnabled = false
|
||||
handleActiveDates(with: displayDate, activeDates: activeDates, inactiveDates: inactiveDates)
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,7 +224,7 @@ private class LegendCollectionViewCell: UICollectionViewCell {
|
||||
title.text = text
|
||||
title.textColor = textColorConfiguration.getColor(surface)
|
||||
|
||||
legendIndicator.backgroundColor = drawSemiCircle ? .clear : (clearFullcircle ? .clear : color)
|
||||
legendIndicator.backgroundColor = drawSemiCircle ? .clear : (clearFullcircle ? .clear : indicatorColorConfiguration.getColor(surface))
|
||||
legendIndicator.layer.borderColor = indicatorColorConfiguration.getColor(surface).cgColor
|
||||
|
||||
self.layoutIfNeeded()
|
||||
@ -239,7 +239,7 @@ private class LegendCollectionViewCell: UICollectionViewCell {
|
||||
path.addArc(withCenter: center, radius: center.x, startAngle: 2 * .pi, endAngle: .pi, clockwise: true)
|
||||
path.close()
|
||||
shapeLayer.path = path.cgPath
|
||||
shapeLayer.fillColor = color.cgColor
|
||||
shapeLayer.fillColor = indicatorColorConfiguration.getColor(surface).cgColor
|
||||
|
||||
guard legendIndicator.layer.sublayers?.contains(shapeLayer) ?? true else { return }
|
||||
legendIndicator.layer.addSublayer(shapeLayer)
|
||||
|
||||
@ -68,16 +68,16 @@ class CalendarHeaderReusableView: UICollectionReusableView {
|
||||
$0.kind = .ghost
|
||||
$0.iconName = .leftCaret
|
||||
$0.iconOffset = .init(x: -2, y: 0)
|
||||
$0.icon.size = .small
|
||||
$0.size = .small
|
||||
$0.customContainerSize = 40
|
||||
$0.icon.customSize = 16
|
||||
}
|
||||
|
||||
internal var nextButton = ButtonIcon().with {
|
||||
$0.kind = .ghost
|
||||
$0.iconName = .rightCaret
|
||||
$0.iconOffset = .init(x: 2, y: 0)
|
||||
$0.icon.size = .small
|
||||
$0.size = .small
|
||||
$0.customContainerSize = 40
|
||||
$0.icon.customSize = 16
|
||||
}
|
||||
|
||||
internal var headerTitle = Label().with {
|
||||
|
||||
@ -63,6 +63,8 @@ open class Checkbox: SelectorBase {
|
||||
|
||||
/// This will change the state of the Selector and execute the actionBlock if provided.
|
||||
open override func toggle() {
|
||||
guard isEnabled else { return }
|
||||
|
||||
//removed error
|
||||
if showError && isSelected == false {
|
||||
showError.toggle()
|
||||
|
||||
@ -47,8 +47,6 @@ open class CheckboxGroup: SelectorGroupBase<CheckboxItem>, SelectorGroupMultiSel
|
||||
$0.surface = model.surface
|
||||
$0.inputId = model.inputId
|
||||
$0.hiddenValue = model.value
|
||||
$0.accessibilityLabel = model.accessibileText
|
||||
$0.accessibilityValueText = "item \(index+1) of \(selectorModels.count)"
|
||||
$0.labelText = model.labelText
|
||||
$0.labelTextAttributes = model.labelTextAttributes
|
||||
$0.childText = model.childText
|
||||
@ -56,6 +54,7 @@ open class CheckboxGroup: SelectorGroupBase<CheckboxItem>, SelectorGroupMultiSel
|
||||
$0.isSelected = model.selected
|
||||
$0.errorText = model.errorText
|
||||
$0.showError = model.showError
|
||||
$0.selectorView.bridge_accessibilityValueBlock = { "item \(index+1) of \(selectorModels.count)" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,6 +38,8 @@ open class CheckboxItem: SelectorItemBase<Checkbox> {
|
||||
//--------------------------------------------------
|
||||
/// This will change the state of the Selector and execute the actionBlock if provided.
|
||||
open override func toggle() {
|
||||
guard isEnabled else { return }
|
||||
|
||||
//removed error
|
||||
if showError && isSelected == false {
|
||||
showError.toggle()
|
||||
|
||||
@ -5,7 +5,7 @@ import Combine
|
||||
|
||||
/// A dropdown select is an expandable menu of predefined options that allows a customer to make a single selection.
|
||||
@objc(VDSDatePicker)
|
||||
open class DatePicker: EntryFieldBase, DatePickerViewControllerDelegate, UIPopoverPresentationControllerDelegate {
|
||||
open class DatePicker: EntryFieldBase {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
//--------------------------------------------------
|
||||
@ -26,12 +26,19 @@ open class DatePicker: EntryFieldBase, DatePickerViewControllerDelegate, UIPopov
|
||||
//--------------------------------------------------
|
||||
/// A callback when the selected option changes. Passes parameters (option).
|
||||
open var onDateSelected: ((Date, DatePicker) -> Void)?
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Properties
|
||||
//--------------------------------------------------
|
||||
class Responder: UIView {
|
||||
open override var canBecomeFirstResponder: Bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
internal override var responder: UIResponder? { hiddenView }
|
||||
internal var hiddenView = Responder().with { $0.width(0) }
|
||||
internal var minWidthDefault = 186.0
|
||||
|
||||
internal var bottomStackView: UIStackView = {
|
||||
return UIStackView().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
@ -41,6 +48,33 @@ open class DatePicker: EntryFieldBase, DatePickerViewControllerDelegate, UIPopov
|
||||
$0.spacing = VDSLayout.space2X
|
||||
}
|
||||
}()
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Popover/Alert Properties
|
||||
//--------------------------------------------------
|
||||
/// View shown inline
|
||||
internal var popoverOverlayView = UIView().with {
|
||||
$0.backgroundColor = .clear
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
|
||||
/// use this to track touch events outside of the popover in the overlay
|
||||
internal var popupOverlayTapGesture: AnyCancellable?
|
||||
|
||||
/// View shown inline
|
||||
internal var popoverView: UIView!
|
||||
/// Size used for the popover
|
||||
internal var popoverViewSize: CGSize = .zero
|
||||
/// Spacing between the popover and the ContainerView when not a AlertViewController
|
||||
internal var popoverSpacing: CGFloat = VDSLayout.space1X
|
||||
/// Whether or not the popover is visible
|
||||
internal var popoverVisible = false
|
||||
/// If the ContainerView exists somewhere in the superview hierarch in a ScrollView.
|
||||
internal var scrollView: UIScrollView?
|
||||
/// Original Found ScrollView ContentSize, this will get reset back to this size when the Popover is removed.
|
||||
internal var scrollViewContentSize: CGSize?
|
||||
/// Presenting ViewController with showing the AlertViewController Version.
|
||||
internal var topViewController: UIViewController?
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Public Properties
|
||||
@ -87,12 +121,12 @@ open class DatePicker: EntryFieldBase, DatePickerViewControllerDelegate, UIPopov
|
||||
}
|
||||
|
||||
open var dateFormat: DateFormat = .shortNumeric { didSet{ setNeedsUpdate() } }
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Configuration Properties
|
||||
//--------------------------------------------------
|
||||
internal override var containerSize: CGSize { CGSize(width: minWidthDefault, height: 44) }
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Overrides
|
||||
//--------------------------------------------------
|
||||
@ -100,24 +134,29 @@ open class DatePicker: EntryFieldBase, DatePickerViewControllerDelegate, UIPopov
|
||||
/// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations.
|
||||
open override func setup() {
|
||||
super.setup()
|
||||
|
||||
fieldStackView.isAccessibilityElement = true
|
||||
fieldStackView.accessibilityLabel = "Date Picker"
|
||||
fieldStackView.accessibilityHint = "Double Tap to open"
|
||||
|
||||
|
||||
// setting color config
|
||||
selectedDateLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable()
|
||||
|
||||
|
||||
// tap gesture
|
||||
fieldStackView
|
||||
containerView
|
||||
.publisher(for: UITapGestureRecognizer())
|
||||
.sink { [weak self] _ in
|
||||
guard let self else { return }
|
||||
if self.isEnabled && !self.isReadOnly {
|
||||
self.togglePicker()
|
||||
if isEnabled && !isReadOnly {
|
||||
showPopover()
|
||||
}
|
||||
}
|
||||
.store(in: &subscribers)
|
||||
|
||||
NotificationCenter.default
|
||||
.publisher(for: UIDevice.orientationDidChangeNotification).sink { [weak self] _ in
|
||||
guard let self else { return }
|
||||
hidePopoverView()
|
||||
}
|
||||
.store(in: &subscribers)
|
||||
|
||||
popoverOverlayView.isHidden = true
|
||||
}
|
||||
|
||||
open override func getFieldContainer() -> UIView {
|
||||
@ -129,9 +168,10 @@ open class DatePicker: EntryFieldBase, DatePickerViewControllerDelegate, UIPopov
|
||||
}
|
||||
controlStackView.addArrangedSubview(calendarIcon)
|
||||
controlStackView.addArrangedSubview(selectedDateLabel)
|
||||
controlStackView.addArrangedSubview(hiddenView)
|
||||
return controlStackView
|
||||
}
|
||||
|
||||
|
||||
/// Used to make changes to the View based off a change events or from local properties.
|
||||
open override func updateView() {
|
||||
super.updateView()
|
||||
@ -145,13 +185,6 @@ open class DatePicker: EntryFieldBase, DatePickerViewControllerDelegate, UIPopov
|
||||
calendarIcon.color = iconColorConfiguration.getColor(self)
|
||||
}
|
||||
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
fieldStackView.accessibilityLabel = "Date Picker, \(accessibilityLabelText)"
|
||||
fieldStackView.accessibilityHint = isReadOnly || !isEnabled ? "" : "Double tap to open."
|
||||
fieldStackView.accessibilityValue = value
|
||||
}
|
||||
|
||||
/// Resets to default settings.
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
@ -163,32 +196,249 @@ open class DatePicker: EntryFieldBase, DatePickerViewControllerDelegate, UIPopov
|
||||
formatter.dateFormat = dateFormat.format
|
||||
selectedDateLabel.text = formatter.string(from: date)
|
||||
}
|
||||
}
|
||||
|
||||
internal func togglePicker() {
|
||||
let calendarVC = DatePickerViewController(calendarModel, delegate: self)
|
||||
calendarVC.modalPresentationStyle = .popover
|
||||
calendarVC.selectedDate = selectedDate ?? Date()
|
||||
if let popoverController = calendarVC.popoverPresentationController {
|
||||
popoverController.delegate = self
|
||||
popoverController.sourceView = containerView
|
||||
popoverController.sourceRect = containerView.bounds
|
||||
popoverController.permittedArrowDirections = .up
|
||||
extension DatePicker {
|
||||
|
||||
private func showPopover() {
|
||||
guard let viewController = UIApplication.topViewController(), var parentView = viewController.view, !popoverVisible else {
|
||||
hidePopoverView()
|
||||
return
|
||||
}
|
||||
if let viewController = UIApplication.topViewController() {
|
||||
viewController.present(calendarVC, animated: true, completion: nil)
|
||||
|
||||
let calendar = CalendarBase()
|
||||
calendar.activeDates = calendarModel.activeDates
|
||||
calendar.hideContainerBorder = calendarModel.hideContainerBorder
|
||||
calendar.hideCurrentDateIndicator = calendarModel.hideCurrentDateIndicator
|
||||
calendar.inactiveDates = calendarModel.inactiveDates
|
||||
calendar.indicators = calendarModel.indicators
|
||||
calendar.maxDate = calendarModel.maxDate
|
||||
calendar.minDate = calendarModel.minDate
|
||||
calendar.surface = calendarModel.surface
|
||||
calendar.setNeedsLayout()
|
||||
calendar.layoutIfNeeded()
|
||||
|
||||
//size the popover
|
||||
popoverViewSize = .init(width: calendar.frame.width, height: calendar.frame.height)
|
||||
|
||||
//find scrollView
|
||||
if scrollView == nil {
|
||||
scrollView = findScrollView(from: containerView)
|
||||
scrollViewContentSize = scrollView?.contentSize
|
||||
}
|
||||
|
||||
if let scrollView {
|
||||
parentView = scrollView
|
||||
}
|
||||
|
||||
// see if you should use the popover or show an alert
|
||||
if let popoverOrigin = calculatePopoverPosition(relativeTo: containerView,
|
||||
in: parentView,
|
||||
size: popoverViewSize,
|
||||
with: popoverSpacing) {
|
||||
calendar.onChange = { [weak self] control in
|
||||
guard let self else { return }
|
||||
selectedDate = control.selectedDate
|
||||
sendActions(for: .valueChanged)
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: containerView)
|
||||
hidePopoverView()
|
||||
}
|
||||
|
||||
// popoverView container
|
||||
popoverView = UIView()
|
||||
popoverView.backgroundColor = .clear
|
||||
popoverView.frame = CGRect(x: popoverOrigin.x, y: popoverOrigin.y, width: calendar.frame.width, height: calendar.frame.height)
|
||||
popoverView.alpha = 0
|
||||
popoverView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
|
||||
popoverVisible = true
|
||||
popoverView.addSubview(calendar)
|
||||
|
||||
calendar.pinToSuperView()
|
||||
|
||||
// add views
|
||||
popoverOverlayView.isHidden = false
|
||||
popupOverlayTapGesture = popoverOverlayView
|
||||
.publisher(for: UITapGestureRecognizer())
|
||||
.sink(receiveValue: { [weak self] gesture in
|
||||
guard let self else { return }
|
||||
gestureEventOccured(gesture, parentView: parentView)
|
||||
})
|
||||
|
||||
parentView.addSubview(popoverOverlayView)
|
||||
popoverOverlayView.pinToSuperView()
|
||||
parentView.addSubview(popoverView)
|
||||
parentView.layoutIfNeeded()
|
||||
|
||||
// update containerview
|
||||
_ = responder?.becomeFirstResponder()
|
||||
updateContainerView()
|
||||
|
||||
// animate the calendar to show
|
||||
UIView.animate(withDuration: 0.5,
|
||||
delay: 0,
|
||||
usingSpringWithDamping: 0.8,
|
||||
initialSpringVelocity: 0.2,
|
||||
options: .curveEaseOut,
|
||||
animations: { [weak self] in
|
||||
guard let self else { return }
|
||||
popoverView.alpha = 1
|
||||
popoverView.transform = CGAffineTransform.identity
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: calendar)
|
||||
parentView.layoutIfNeeded()
|
||||
})
|
||||
|
||||
} else {
|
||||
let dialog = UIScrollView()
|
||||
dialog.translatesAutoresizingMaskIntoConstraints = false
|
||||
dialog.addSubview(calendar)
|
||||
dialog.backgroundColor = .clear
|
||||
dialog.contentSize = .init(width: calendar.frame.width + 20, height: calendar.frame.width + 20)
|
||||
dialog.width(calendar.frame.width + 20)
|
||||
dialog.height(calendar.frame.height + 20)
|
||||
calendar.pinToSuperView(.uniform(10))
|
||||
calendar.onChange = { [weak self] control in
|
||||
guard let self else { return }
|
||||
selectedDate = control.selectedDate
|
||||
sendActions(for: .valueChanged)
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: containerView)
|
||||
viewController.dismiss(animated: true)
|
||||
}
|
||||
|
||||
let alert = AlertViewController().with {
|
||||
$0.dialog = dialog
|
||||
$0.modalPresentationStyle = .overCurrentContext
|
||||
$0.modalTransitionStyle = .crossDissolve
|
||||
}
|
||||
topViewController = viewController
|
||||
viewController.present(alert, animated: true){
|
||||
dialog.flashScrollIndicators()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func hidePopoverView() {
|
||||
if topViewController != nil {
|
||||
topViewController?.dismiss(animated: true)
|
||||
topViewController = nil
|
||||
} else {
|
||||
popoverOverlayView.isHidden = true
|
||||
popoverOverlayView.removeFromSuperview()
|
||||
popupOverlayTapGesture?.cancel()
|
||||
popupOverlayTapGesture = nil
|
||||
|
||||
UIView.animate(withDuration: 0.2,
|
||||
animations: {[weak self] in
|
||||
guard let self, let popoverView else { return }
|
||||
popoverView.alpha = 0
|
||||
popoverView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
|
||||
if let scrollView, let scrollViewContentSize {
|
||||
scrollView.contentSize = scrollViewContentSize
|
||||
}
|
||||
|
||||
}) { [weak self] _ in
|
||||
guard let self, let popoverView else { return }
|
||||
popoverView.isHidden = true
|
||||
popoverView.removeFromSuperview()
|
||||
popoverVisible = false
|
||||
responder?.resignFirstResponder()
|
||||
setNeedsUpdate()
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: containerView)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal func didSelectDate(_ controller: DatePickerViewController, date: Date) {
|
||||
selectedDate = date
|
||||
controller.dismiss(animated: true) { [weak self] in
|
||||
guard let self else { return }
|
||||
self.sendActions(for: .valueChanged)
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: self.fieldStackView)
|
||||
private func findScrollView(from view: UIView) -> UIScrollView? {
|
||||
var currentView = view
|
||||
while let superview = currentView.superview {
|
||||
if let scrollView = superview as? UIScrollView {
|
||||
return scrollView
|
||||
}
|
||||
currentView = superview
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
|
||||
return .none
|
||||
private func calculatePopoverPosition(relativeTo sourceView: UIView, in parentView: UIView, size: CGSize, with spacing: CGFloat) -> CGPoint? {
|
||||
let sourceFrameInParent = sourceView.convert(sourceView.bounds, to: parentView)
|
||||
let parentBounds = parentView.bounds
|
||||
let safeAreaInsets = parentView.safeAreaInsets
|
||||
let popoverWidth = size.width
|
||||
let popoverHeight = size.height
|
||||
|
||||
var popoverX: CGFloat = 0
|
||||
var popoverY: CGFloat = 0
|
||||
|
||||
// Calculate horizontal position
|
||||
if sourceFrameInParent.width <= popoverWidth {
|
||||
if sourceFrameInParent.midX - popoverWidth / 2 < 0 {
|
||||
// Align to left
|
||||
popoverX = sourceFrameInParent.minX
|
||||
} else if sourceFrameInParent.midX + popoverWidth / 2 > parentBounds.width {
|
||||
// Align to right
|
||||
popoverX = sourceFrameInParent.maxX - popoverWidth
|
||||
} else {
|
||||
// Center on source view
|
||||
popoverX = sourceFrameInParent.midX - popoverWidth / 2
|
||||
}
|
||||
} else {
|
||||
popoverX = sourceFrameInParent.minX //sourceFrameInParent.midX - popoverWidth / 2
|
||||
}
|
||||
|
||||
// Ensure the popover is within the parent's bounds horizontally
|
||||
popoverX = max(0, min(popoverX, parentBounds.width - popoverWidth))
|
||||
|
||||
var availableSpaceAbove: CGFloat = 0.0
|
||||
var availableSpaceBelow: CGFloat = 0.0
|
||||
|
||||
/// if the scrollView is set we want to change how we calculate the containerView's position
|
||||
if let scrollView = parentView as? UIScrollView {
|
||||
// Calculate vertical position and height
|
||||
availableSpaceAbove = sourceFrameInParent.minY - scrollView.bounds.minY - spacing
|
||||
availableSpaceBelow = scrollView.bounds.maxY - sourceFrameInParent.maxY - spacing
|
||||
|
||||
if availableSpaceAbove > availableSpaceBelow {
|
||||
// Show above
|
||||
popoverY = sourceFrameInParent.minY - popoverHeight - spacing
|
||||
} else {
|
||||
// Show below
|
||||
popoverY = sourceFrameInParent.maxY + spacing
|
||||
|
||||
// See if we need to expand the contentSize of the ScrollView
|
||||
let diff = scrollView.contentSize.height - sourceFrameInParent.maxY
|
||||
if diff < popoverHeight {
|
||||
scrollView.contentSize.height += popoverHeight - diff + VDSLayout.space4X
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Calculate vertical position and height
|
||||
availableSpaceAbove = sourceFrameInParent.minY - safeAreaInsets.top - spacing
|
||||
availableSpaceBelow = parentBounds.height - sourceFrameInParent.maxY - safeAreaInsets.bottom - spacing
|
||||
|
||||
if availableSpaceAbove >= popoverHeight {
|
||||
// Show above
|
||||
popoverY = sourceFrameInParent.minY - popoverHeight - spacing
|
||||
|
||||
} else if availableSpaceBelow >= popoverHeight {
|
||||
// Show below
|
||||
popoverY = sourceFrameInParent.maxY + spacing
|
||||
|
||||
} else {
|
||||
|
||||
//return nil since there is no way we can show the popover without a scrollview
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return .init(x: popoverX, y: popoverY)
|
||||
}
|
||||
|
||||
private func gestureEventOccured(_ gesture: UIGestureRecognizer, parentView: UIView) {
|
||||
guard let popoverView, popoverVisible else { return }
|
||||
let location = gesture.location(in: parentView)
|
||||
if !popoverView.frame.contains(location) {
|
||||
hidePopoverView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
//
|
||||
// DatePickerPopoverViewController.swift
|
||||
// VDS
|
||||
//
|
||||
// Created by Matt Bruce on 5/14/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
protocol DatePickerViewControllerDelegate: NSObject {
|
||||
func didSelectDate(_ controller: DatePicker.DatePickerViewController, date: Date)
|
||||
}
|
||||
|
||||
extension DatePicker {
|
||||
class DatePickerViewController: UIViewController {
|
||||
private var padding: CGFloat = 15
|
||||
private var topPadding: CGFloat { 10 + padding }
|
||||
private var calendarModel: CalendarModel
|
||||
private let picker = CalendarBase()
|
||||
weak var delegate: DatePickerViewControllerDelegate?
|
||||
|
||||
init(_ calendarModel: CalendarModel, delegate: DatePickerViewControllerDelegate?) {
|
||||
self.delegate = delegate
|
||||
self.calendarModel = calendarModel
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
self.picker.onChange = { [weak self] control in
|
||||
guard let self else { return }
|
||||
self.delegate?.didSelectDate(self, date: control.selectedDate)
|
||||
}
|
||||
}
|
||||
|
||||
var selectedDate: Date = Date() {
|
||||
didSet {
|
||||
picker.selectedDate = selectedDate
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
view.addSubview(picker)
|
||||
picker.surface = calendarModel.surface
|
||||
picker.hideContainerBorder = calendarModel.hideContainerBorder
|
||||
picker.hideCurrentDateIndicator = calendarModel.hideCurrentDateIndicator
|
||||
picker.indicators = calendarModel.indicators
|
||||
picker.activeDates = calendarModel.activeDates
|
||||
picker.inactiveDates = calendarModel.inactiveDates
|
||||
picker.selectedDate = selectedDate
|
||||
picker.minDate = calendarModel.minDate
|
||||
picker.maxDate = calendarModel.maxDate
|
||||
picker.pinToSuperView(.init(top: topPadding, left: padding, bottom: padding, right: padding))
|
||||
view.backgroundColor = picker.backgroundColor
|
||||
}
|
||||
|
||||
override var preferredContentSize: CGSize {
|
||||
get {
|
||||
var size = picker.frame.size
|
||||
size.height += 40
|
||||
size.width += 30
|
||||
return size
|
||||
}
|
||||
set {
|
||||
super.preferredContentSize = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -30,19 +30,7 @@ open class DropdownSelect: EntryFieldBase {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Public Properties
|
||||
//--------------------------------------------------
|
||||
/// Override UIControl state to add the .error state if showSuccess is true and if showError is true.
|
||||
open override var state: UIControl.State {
|
||||
get {
|
||||
var state = super.state
|
||||
if dropdownField.isFirstResponder {
|
||||
state.insert(.focused)
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
/// If true, the label will be displayed inside the dropdown containerView. Otherwise, the label will be above the dropdown containerView like a normal text input.
|
||||
open var showInlineLabel: Bool = false { didSet { setNeedsUpdate() }}
|
||||
|
||||
@ -66,6 +54,8 @@ open class DropdownSelect: EntryFieldBase {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Properties
|
||||
//--------------------------------------------------
|
||||
internal override var responder: UIResponder? { dropdownField }
|
||||
|
||||
internal var minWidthDefault = 66.0
|
||||
internal var minWidthInlineLabel = 102.0
|
||||
internal override var minWidth: CGFloat { showInlineLabel ? minWidthInlineLabel : minWidthDefault }
|
||||
@ -130,8 +120,8 @@ open class DropdownSelect: EntryFieldBase {
|
||||
/// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations.
|
||||
open override func setup() {
|
||||
super.setup()
|
||||
accessibilityHintText = "has popup, Double tap to open."
|
||||
|
||||
fieldStackView.isAccessibilityElement = true
|
||||
inlineDisplayLabel.isAccessibilityElement = true
|
||||
|
||||
dropdownField.width(0)
|
||||
@ -276,57 +266,11 @@ open class DropdownSelect: EntryFieldBase {
|
||||
statusIcon.color = iconColorConfiguration.getColor(self)
|
||||
}
|
||||
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
fieldStackView.accessibilityLabel = "Dropdown Select, \(accessibilityLabelText)"
|
||||
fieldStackView.accessibilityHint = isReadOnly || !isEnabled ? "" : "has popup, Double tap to open."
|
||||
fieldStackView.accessibilityValue = value
|
||||
}
|
||||
|
||||
open override var accessibilityElements: [Any]? {
|
||||
get {
|
||||
var elements = [Any]()
|
||||
elements.append(contentsOf: [titleLabel, fieldStackView])
|
||||
|
||||
if showError {
|
||||
elements.append(statusIcon)
|
||||
if let errorText, !errorText.isEmpty {
|
||||
elements.append(errorLabel)
|
||||
}
|
||||
}
|
||||
|
||||
if let helperText, !helperText.isEmpty {
|
||||
elements.append(helperLabel)
|
||||
}
|
||||
|
||||
return elements
|
||||
}
|
||||
|
||||
set { super.accessibilityElements = newValue }
|
||||
}
|
||||
|
||||
|
||||
@objc open func pickerDoneClicked() {
|
||||
optionsPicker.isHidden = true
|
||||
dropdownField.resignFirstResponder()
|
||||
setNeedsUpdate()
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: fieldStackView)
|
||||
}
|
||||
|
||||
open override var canBecomeFirstResponder: Bool {
|
||||
return dropdownField.canBecomeFirstResponder
|
||||
}
|
||||
|
||||
open override func becomeFirstResponder() -> Bool {
|
||||
return dropdownField.becomeFirstResponder()
|
||||
}
|
||||
|
||||
open override var canResignFirstResponder: Bool {
|
||||
return dropdownField.canResignFirstResponder
|
||||
}
|
||||
|
||||
open override func resignFirstResponder() -> Bool {
|
||||
return dropdownField.resignFirstResponder()
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: containerView)
|
||||
}
|
||||
}
|
||||
|
||||
@ -337,8 +281,8 @@ extension DropdownSelect: UIPickerViewDelegate, UIPickerViewDataSource {
|
||||
|
||||
internal func launchPicker() {
|
||||
if optionsPicker.isHidden {
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: optionsPicker)
|
||||
dropdownField.becomeFirstResponder()
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: optionsPicker)
|
||||
} else {
|
||||
dropdownField.resignFirstResponder()
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ open class ButtonIcon: Control, Changeable {
|
||||
public required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Enums
|
||||
//--------------------------------------------------
|
||||
@ -43,7 +43,7 @@ open class ButtonIcon: Control, Changeable {
|
||||
public enum SurfaceType: String, CaseIterable {
|
||||
case colorFill, media
|
||||
}
|
||||
|
||||
|
||||
/// Enum used to describe the size of button icon.
|
||||
public enum Size: String, EnumSubset {
|
||||
case large
|
||||
@ -105,12 +105,12 @@ open class ButtonIcon: Control, Changeable {
|
||||
return .init(x: 6, y: 6)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Public Properties
|
||||
//--------------------------------------------------
|
||||
public var onChangeSubscriber: AnyCancellable?
|
||||
|
||||
|
||||
///Badge Indicator object used to render for the ButtonIcon.
|
||||
open var badgeIndicator = BadgeIndicator().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
@ -140,10 +140,10 @@ open class ButtonIcon: Control, Changeable {
|
||||
open var selectedIconName: Icon.Name? { didSet { setNeedsUpdate() } }
|
||||
|
||||
open var selectedIconColorConfiguration: SurfaceColorConfiguration? { didSet { setNeedsUpdate() } }
|
||||
|
||||
|
||||
/// Sets the size of button icon and icon.
|
||||
open var size: Size = .large { didSet { setNeedsUpdate() } }
|
||||
|
||||
|
||||
/// If provided, the button icon will have a box shadow.
|
||||
open var floating: Bool = false { didSet { setNeedsUpdate() } }
|
||||
|
||||
@ -152,7 +152,7 @@ open class ButtonIcon: Control, Changeable {
|
||||
|
||||
/// If set to true, the button icon will not have a border.
|
||||
open var hideBorder: Bool = true { didSet { setNeedsUpdate() } }
|
||||
|
||||
|
||||
/// If provided, the badge indicator will present.
|
||||
open var showBadgeIndicator: Bool = false { didSet { setNeedsUpdate() } }
|
||||
|
||||
@ -169,14 +169,14 @@ open class ButtonIcon: Control, Changeable {
|
||||
|
||||
/// Used to move the icon inside the button in both x and y axis.
|
||||
open var iconOffset: CGPoint = .init(x: 0, y: 0) { didSet { setNeedsUpdate() } }
|
||||
|
||||
|
||||
|
||||
/// Sets a custom size of button icon container.
|
||||
open var customContainerSize: Int? { didSet { setNeedsUpdate() } }
|
||||
|
||||
|
||||
/// Sets a custom size of the icon.
|
||||
open var customIconSize: Int? { didSet { setNeedsUpdate() } }
|
||||
|
||||
|
||||
/// Sets a custom badgeIndicator offset
|
||||
open var customBadgeIndicatorOffset: CGPoint? { didSet { setNeedsUpdate() } }
|
||||
|
||||
@ -246,7 +246,7 @@ open class ButtonIcon: Control, Changeable {
|
||||
SurfaceColorConfiguration(.clear, .clear).eraseToAnyColorable()
|
||||
}()
|
||||
}
|
||||
|
||||
|
||||
private struct LowContrastColorFillConfiguration: Configuration {
|
||||
var kind: Kind = .lowContrast
|
||||
var surfaceType: SurfaceType = .colorFill
|
||||
@ -255,7 +255,7 @@ open class ButtonIcon: Control, Changeable {
|
||||
SurfaceColorConfiguration(VDSColor.paletteGray44.withAlphaComponent(0.06), VDSColor.paletteGray44.withAlphaComponent(0.26)).eraseToAnyColorable()
|
||||
}()
|
||||
}
|
||||
|
||||
|
||||
private struct LowContrastColorFillFloatingConfiguration: Configuration, DropShadowableConfiguration {
|
||||
var kind: Kind = .lowContrast
|
||||
var surfaceType: SurfaceType = .colorFill
|
||||
@ -277,7 +277,7 @@ open class ButtonIcon: Control, Changeable {
|
||||
}
|
||||
var configurations: [DropShadowable] { [dropshadow1Configuration, dropshadow2Configuration] }
|
||||
}
|
||||
|
||||
|
||||
private struct LowContrastMediaConfiguration: Configuration, Borderable {
|
||||
var kind: Kind = .lowContrast
|
||||
var surfaceType: SurfaceType = .media
|
||||
@ -290,7 +290,7 @@ open class ButtonIcon: Control, Changeable {
|
||||
SurfaceColorConfiguration(VDSColor.elementsLowcontrastOnlight, VDSColor.elementsLowcontrastOndark).eraseToAnyColorable()
|
||||
}()
|
||||
}
|
||||
|
||||
|
||||
private struct LowContrastMediaFloatingConfiguration: Configuration, DropShadowableConfiguration {
|
||||
var kind: Kind = .lowContrast
|
||||
var surfaceType: SurfaceType = .media
|
||||
@ -325,10 +325,10 @@ open class ButtonIcon: Control, Changeable {
|
||||
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled)
|
||||
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: [.selected, .disabled])
|
||||
}.eraseToAnyColorable()
|
||||
|
||||
|
||||
}()
|
||||
}
|
||||
|
||||
|
||||
private struct HighContrastFloatingConfiguration: Configuration, DropShadowableConfiguration {
|
||||
var kind: Kind = .highContrast
|
||||
var surfaceType: SurfaceType = .colorFill
|
||||
@ -357,9 +357,9 @@ open class ButtonIcon: Control, Changeable {
|
||||
}
|
||||
var configurations: [DropShadowable] { [dropshadow1Configuration, dropshadow2Configuration] }
|
||||
}
|
||||
|
||||
|
||||
private var badgeIndicatorDefaultSize: CGSize = .zero
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Overrides
|
||||
//--------------------------------------------------
|
||||
@ -367,11 +367,11 @@ open class ButtonIcon: Control, Changeable {
|
||||
open override func setup() {
|
||||
super.setup()
|
||||
isAccessibilityElement = false
|
||||
|
||||
|
||||
//create a layoutGuide for the icon to key off of
|
||||
let iconLayoutGuide = UILayoutGuide()
|
||||
addLayoutGuide(iconLayoutGuide)
|
||||
|
||||
|
||||
//add the icon
|
||||
addSubview(icon)
|
||||
|
||||
@ -379,7 +379,7 @@ open class ButtonIcon: Control, Changeable {
|
||||
addSubview(badgeIndicator)
|
||||
badgeIndicator.isHidden = !showBadgeIndicator
|
||||
badgeIndicatorDefaultSize = badgeIndicator.frame.size
|
||||
|
||||
|
||||
//determines the height/width of the icon
|
||||
layoutGuideWidthConstraint = iconLayoutGuide.width(constant: size.containerSize)
|
||||
layoutGuideHeightConstraint = iconLayoutGuide.height(constant: size.containerSize)
|
||||
@ -388,7 +388,7 @@ open class ButtonIcon: Control, Changeable {
|
||||
badgeIndicatorCenterXConstraint = badgeIndicator.centerXAnchor.constraint(equalTo: icon.centerXAnchor)
|
||||
badgeIndicatorCenterYConstraint = icon.centerYAnchor.constraint(equalTo: badgeIndicator.centerYAnchor)
|
||||
badgeIndicatorCenterYConstraint?.isActive = true
|
||||
|
||||
|
||||
badgeIndicatorLeadingConstraint?.isActive = true
|
||||
//pin layout guide
|
||||
iconLayoutGuide
|
||||
@ -396,7 +396,7 @@ open class ButtonIcon: Control, Changeable {
|
||||
.pinLeading()
|
||||
.pinTrailing(0, .defaultHigh)
|
||||
.pinBottom(0, .defaultHigh)
|
||||
|
||||
|
||||
//determines the center point of the icon
|
||||
centerXConstraint = icon.centerXAnchor.constraint(equalTo: iconLayoutGuide.centerXAnchor, constant: 0)
|
||||
centerXConstraint?.activate()
|
||||
@ -414,14 +414,14 @@ open class ButtonIcon: Control, Changeable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// This will change the state of the Selector and execute the actionBlock if provided.
|
||||
open func toggle() {
|
||||
//removed error
|
||||
isSelected.toggle()
|
||||
sendActions(for: .valueChanged)
|
||||
}
|
||||
|
||||
|
||||
/// Resets to default settings.
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
@ -437,7 +437,7 @@ open class ButtonIcon: Control, Changeable {
|
||||
showBadgeIndicator = false
|
||||
selectable = false
|
||||
badgeIndicatorModel = nil
|
||||
onChange = nil
|
||||
onChange = nil
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
@ -464,16 +464,18 @@ open class ButtonIcon: Control, Changeable {
|
||||
setNeedsLayout()
|
||||
}
|
||||
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
var elements = [Any]()
|
||||
if iconName != nil {
|
||||
elements.append(icon)
|
||||
open override var accessibilityElements: [Any]? {
|
||||
get {
|
||||
var elements = [Any]()
|
||||
if iconName != nil {
|
||||
elements.append(icon)
|
||||
}
|
||||
if badgeIndicatorModel != nil && showBadgeIndicator {
|
||||
elements.append(badgeIndicator)
|
||||
}
|
||||
return elements.count > 0 ? elements : nil
|
||||
}
|
||||
if badgeIndicatorModel != nil && showBadgeIndicator {
|
||||
elements.append(badgeIndicator)
|
||||
}
|
||||
accessibilityElements = elements.count > 0 ? elements : nil
|
||||
set { }
|
||||
}
|
||||
|
||||
open override func layoutSubviews() {
|
||||
|
||||
@ -94,6 +94,12 @@ open class Icon: View {
|
||||
|
||||
isAccessibilityElement = true
|
||||
accessibilityTraits = .image
|
||||
|
||||
bridge_accessibilityLabelBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
return name?.rawValue ?? "icon"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Used to make changes to the View based off a change events or from local properties.
|
||||
@ -118,12 +124,7 @@ open class Icon: View {
|
||||
super.reset()
|
||||
color = VDSColor.paletteBlack
|
||||
imageView.image = nil
|
||||
}
|
||||
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
accessibilityLabel = name?.rawValue ?? "icon"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIImage {
|
||||
|
||||
@ -91,16 +91,14 @@ open class Label: UILabel, ViewProtocol, UserInfoable {
|
||||
private struct LabelAction {
|
||||
var range: NSRange
|
||||
var action: PassthroughSubject<Void, Never>
|
||||
var accessibilityId: Int = 0
|
||||
|
||||
var frame: CGRect = .zero
|
||||
func performAction() {
|
||||
action.send()
|
||||
}
|
||||
|
||||
init(range: NSRange, action: PassthroughSubject<Void, Never>, accessibilityID: Int = 0) {
|
||||
init(range: NSRange, action: PassthroughSubject<Void, Never>) {
|
||||
self.range = range
|
||||
self.action = action
|
||||
self.accessibilityId = accessibilityID
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,7 +213,8 @@ open class Label: UILabel, ViewProtocol, UserInfoable {
|
||||
}
|
||||
}
|
||||
|
||||
open func setup() {}
|
||||
open func setup() {
|
||||
}
|
||||
|
||||
open func reset() {
|
||||
shouldUpdateView = false
|
||||
@ -242,7 +241,6 @@ open class Label: UILabel, ViewProtocol, UserInfoable {
|
||||
}
|
||||
|
||||
open func updateAccessibility() {
|
||||
accessibilityLabel = text
|
||||
if isEnabled {
|
||||
accessibilityTraits.remove(.notEnabled)
|
||||
} else {
|
||||
@ -263,24 +261,7 @@ open class Label: UILabel, ViewProtocol, UserInfoable {
|
||||
super.layoutSubviews()
|
||||
applyActions()
|
||||
}
|
||||
|
||||
/// Addig custom accessibillty actions from the collection of attributes.
|
||||
open override func accessibilityActivate() -> Bool {
|
||||
|
||||
guard let accessibleActions = accessibilityCustomActions else { return false }
|
||||
|
||||
for actionable in actions {
|
||||
for action in accessibleActions {
|
||||
if action.hash == actionable.accessibilityId {
|
||||
actionable.performAction()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Methods
|
||||
//--------------------------------------------------
|
||||
@ -373,15 +354,27 @@ open class Label: UILabel, ViewProtocol, UserInfoable {
|
||||
//see if the attribute is Actionable
|
||||
if let actionable = attribute as? any ActionLabelAttributeModel, mutableAttributedString.isValid(range: actionable.range) {
|
||||
//create a accessibleAction
|
||||
let customAccessibilityAction = customAccessibilityAction(text: mutableAttributedString.string, range: actionable.range, accessibleText: actionable.accessibleText)
|
||||
let customAccessibilityAction = customAccessibilityElement(text: mutableAttributedString.string,
|
||||
range: actionable.range,
|
||||
accessibleText: actionable.accessibleText)
|
||||
|
||||
// creat the action
|
||||
let labelAction = LabelAction(range: actionable.range, action: actionable.action)
|
||||
|
||||
// set the action of the accessibilityElement
|
||||
customAccessibilityAction?.accessibilityAction = { [weak self] in
|
||||
guard let self, isEnabled else { return }
|
||||
labelAction.performAction()
|
||||
}
|
||||
|
||||
//create a wrapper for the attributes range, block and
|
||||
actions.append(LabelAction(range: actionable.range, action: actionable.action, accessibilityID: customAccessibilityAction?.hashValue ?? -1))
|
||||
actions.append(labelAction)
|
||||
isUserInteractionEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
if let accessibilityElements, !accessibilityElements.isEmpty {
|
||||
let staticText = UIAccessibilityElement(accessibilityContainer: self)
|
||||
let staticText = AccessibilityActionElement(accessibilityContainer: self)
|
||||
staticText.accessibilityLabel = text
|
||||
staticText.accessibilityFrameInContainerSpace = bounds
|
||||
|
||||
@ -392,62 +385,236 @@ open class Label: UILabel, ViewProtocol, UserInfoable {
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Touch Events
|
||||
//--------------------------------------------------
|
||||
@objc private func textLinkTapped(_ gesture: UITapGestureRecognizer) {
|
||||
for actionable in actions {
|
||||
// This determines if we tapped on the desired range of text.
|
||||
if gesture.didTapActionInLabel(self, inRange: actionable.range) {
|
||||
actionable.performAction()
|
||||
return
|
||||
}
|
||||
let location = gesture.location(in: self)
|
||||
if let action = actions.first(where: { isAction(for: location, inRange: $0.range) }) {
|
||||
action.performAction()
|
||||
}
|
||||
}
|
||||
|
||||
private func customAccessibilityAction(text: String?, range: NSRange, accessibleText: String? = nil) -> UIAccessibilityCustomAction? {
|
||||
|
||||
public func isAction(for location: CGPoint) -> Bool {
|
||||
actions.contains(where: {isAction(for: location, inRange: $0.range)})
|
||||
}
|
||||
|
||||
public func isAction(for location: CGPoint, inRange targetRange: NSRange) -> Bool {
|
||||
guard let abstractContainer = abstractTextContainer() else { return false }
|
||||
let textContainer = abstractContainer.textContainer
|
||||
let layoutManager = abstractContainer.layoutManager
|
||||
|
||||
guard let text = text, let attributedText else { return nil }
|
||||
let indexOfGlyph = layoutManager.glyphIndex(for: location, in: textContainer)
|
||||
let intrinsicWidth = intrinsicContentSize.width
|
||||
|
||||
let actionText = accessibleText ?? (text.isValid(range: range) ? NSString(string:text).substring(with: range) : text)
|
||||
|
||||
// Calculate the frame of the substring
|
||||
// Assert that tapped occured within acceptable bounds based on alignment.
|
||||
switch textAlignment {
|
||||
case .right:
|
||||
if location.x < bounds.width - intrinsicWidth {
|
||||
return false
|
||||
}
|
||||
case .center:
|
||||
let halfBounds = bounds.width / 2
|
||||
let halfIntrinsicWidth = intrinsicWidth / 2
|
||||
|
||||
if location.x > halfBounds + halfIntrinsicWidth {
|
||||
return false
|
||||
} else if location.x < halfBounds - halfIntrinsicWidth {
|
||||
return false
|
||||
}
|
||||
default: // Left align
|
||||
if location.x > intrinsicWidth {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Affirms that the tap occured in the desired rect of provided by the target range.
|
||||
return layoutManager.boundingRect(forGlyphRange: targetRange, in: textContainer).contains(location)
|
||||
&& NSLocationInRange(indexOfGlyph, targetRange)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a text container and layout manager of how the text would appear on screen.
|
||||
They are used in tandem to derive low-level TextKit results of the label.
|
||||
*/
|
||||
public func abstractTextContainer() -> (textContainer: NSTextContainer, layoutManager: NSLayoutManager, textStorage: NSTextStorage)? {
|
||||
|
||||
// Must configure the attributed string to translate what would appear on screen to accurately analyze.
|
||||
guard let attributedText = attributedText else { return nil }
|
||||
|
||||
let paragraph = NSMutableParagraphStyle()
|
||||
paragraph.alignment = textAlignment
|
||||
|
||||
let stagedAttributedString = NSMutableAttributedString(attributedString: attributedText)
|
||||
stagedAttributedString.addAttributes([NSAttributedString.Key.paragraphStyle: paragraph], range: NSRange(location: 0, length: attributedText.string.count))
|
||||
|
||||
let textStorage = NSTextStorage(attributedString: stagedAttributedString)
|
||||
let layoutManager = NSLayoutManager()
|
||||
let textContainer = NSTextContainer(size: bounds.size)
|
||||
let textStorage = NSTextStorage(attributedString: attributedText)
|
||||
let textContainer = NSTextContainer(size: .zero)
|
||||
|
||||
layoutManager.addTextContainer(textContainer)
|
||||
textStorage.addLayoutManager(layoutManager)
|
||||
|
||||
textContainer.lineFragmentPadding = 0.0
|
||||
textContainer.lineBreakMode = lineBreakMode
|
||||
textContainer.maximumNumberOfLines = numberOfLines
|
||||
textContainer.size = bounds.size
|
||||
|
||||
return (textContainer, layoutManager, textStorage)
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Accessibility
|
||||
//--------------------------------------------------
|
||||
private func customAccessibilityElement(text: String?, range: NSRange, accessibleText: String? = nil) -> AccessibilityActionElement? {
|
||||
|
||||
guard let text = text, let abstractContainer = abstractTextContainer() else { return nil }
|
||||
|
||||
let textContainer = abstractContainer.textContainer
|
||||
let layoutManager = abstractContainer.layoutManager
|
||||
|
||||
let actionText = accessibleText ?? (text.isValid(range: range) ? NSString(string:text).substring(with: range) : text)
|
||||
|
||||
var glyphRange = NSRange()
|
||||
|
||||
// Convert the range for the substring into a range of glyphs
|
||||
layoutManager.characterRange(forGlyphRange: range, actualGlyphRange: &glyphRange)
|
||||
|
||||
let substringBounds = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
|
||||
|
||||
// Create custom accessibility element
|
||||
let element = UIAccessibilityElement(accessibilityContainer: self)
|
||||
let element = AccessibilityActionElement(accessibilityContainer: self)
|
||||
element.accessibilityLabel = actionText
|
||||
element.accessibilityTraits = .link
|
||||
element.accessibilityHint = "Double tap to open"
|
||||
element.accessibilityFrameInContainerSpace = substringBounds
|
||||
|
||||
//TODO: accessibilityHint for Label
|
||||
// element.accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "swipe_to_select_with_action_hint")
|
||||
|
||||
accessibilityElements = (accessibilityElements ?? []).compactMap{$0 as? UIAccessibilityElement}.filter { $0.accessibilityLabel != actionText }
|
||||
accessibilityElements?.append(element)
|
||||
|
||||
let accessibleAction = UIAccessibilityCustomAction(name: actionText, target: self, selector: #selector(accessibilityCustomAction(_:)))
|
||||
accessibilityCustomActions?.append(accessibleAction)
|
||||
return accessibleAction
|
||||
return element
|
||||
}
|
||||
|
||||
@objc private func accessibilityCustomAction(_ action: UIAccessibilityCustomAction) {
|
||||
|
||||
for actionable in actions {
|
||||
if action.hash == actionable.accessibilityId {
|
||||
actionable.performAction()
|
||||
return
|
||||
open var accessibilityAction: ((Label) -> Void)?
|
||||
|
||||
open override var isAccessibilityElement: Bool {
|
||||
get {
|
||||
var block: AXBoolReturnBlock?
|
||||
|
||||
// if #available(iOS 17, *) {
|
||||
// block = isAccessibilityElementBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_isAccessibilityElementBlock
|
||||
}
|
||||
|
||||
if let block {
|
||||
return block()
|
||||
} else {
|
||||
return super.isAccessibilityElement
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.isAccessibilityElement = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override var accessibilityLabel: String? {
|
||||
get {
|
||||
var block: AXStringReturnBlock?
|
||||
// if #available(iOS 17, *) {
|
||||
// block = accessibilityLabelBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_accessibilityLabelBlock
|
||||
}
|
||||
|
||||
if let block {
|
||||
return block()
|
||||
} else {
|
||||
return super.accessibilityLabel
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.accessibilityLabel = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override var accessibilityHint: String? {
|
||||
get {
|
||||
var block: AXStringReturnBlock?
|
||||
// if #available(iOS 17, *) {
|
||||
// block = accessibilityHintBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_accessibilityHintBlock
|
||||
}
|
||||
|
||||
if let block {
|
||||
return block()
|
||||
} else {
|
||||
return super.accessibilityHint
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.accessibilityHint = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override var accessibilityValue: String? {
|
||||
get {
|
||||
var block: AXStringReturnBlock?
|
||||
|
||||
// if #available(iOS 17, *) {
|
||||
// block = accessibilityHintBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_accessibilityValueBlock
|
||||
}
|
||||
|
||||
if let block{
|
||||
return block()
|
||||
} else {
|
||||
return super.accessibilityValue
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.accessibilityValue = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override func accessibilityActivate() -> Bool {
|
||||
guard isEnabled, isUserInteractionEnabled else { return false }
|
||||
|
||||
// if #available(iOS 17, *) {
|
||||
// if let block = accessibilityAction {
|
||||
// block(self)
|
||||
// return true
|
||||
// } else if let block = accessibilityActivateBlock {
|
||||
// return block()
|
||||
//
|
||||
// } else if let block = bridge_accessibilityActivateBlock {
|
||||
// return block()
|
||||
//
|
||||
// } else {
|
||||
// return true
|
||||
//
|
||||
// }
|
||||
//
|
||||
// } else {
|
||||
if let block = accessibilityAction {
|
||||
block(self)
|
||||
return true
|
||||
|
||||
} else if let block = bridge_accessibilityActivateBlock {
|
||||
return block()
|
||||
|
||||
} else {
|
||||
return true
|
||||
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -265,6 +265,12 @@ open class Notification: View {
|
||||
isAccessibilityElement = false
|
||||
accessibilityElements = [closeButton, typeIcon, titleLabel, subTitleLabel, buttonGroup]
|
||||
closeButton.accessibilityTraits = [.button]
|
||||
closeButton.accessibilityLabel = "Close Notification"
|
||||
|
||||
typeIcon.bridge_accessibilityLabelBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
return style.accessibleText
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets to default settings.
|
||||
@ -372,12 +378,6 @@ open class Notification: View {
|
||||
}
|
||||
}
|
||||
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
closeButton.accessibilityLabel = "Close Notification"
|
||||
typeIcon.accessibilityLabel = style.accessibleText
|
||||
}
|
||||
|
||||
private func setConstraints() {
|
||||
labelViewAndButtonViewConstraint?.deactivate()
|
||||
labelViewBottomConstraint?.deactivate()
|
||||
|
||||
@ -86,6 +86,10 @@ open class Pagination: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var paginationDescription: String {
|
||||
"Page \(selectedPage) of \(total) selected"
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Overrides
|
||||
//--------------------------------------------------
|
||||
@ -148,14 +152,26 @@ open class Pagination: View {
|
||||
guard let self else { return }
|
||||
self.selectedPage = max(0, self.selectedPage - 1)
|
||||
}
|
||||
|
||||
collectionContainerView.bridge_accessibilityLabelBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
return "Pagination containing \(total) pages"
|
||||
}
|
||||
|
||||
collectionContainerView.bridge_accessibilityValueBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
return paginationDescription
|
||||
}
|
||||
}
|
||||
|
||||
///Updating the accessiblity values i.e elements, label, value other items for the component.
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
accessibilityElements = [previousButton, collectionContainerView, nextButton]
|
||||
collectionContainerView.accessibilityLabel = "Pagination containing \(total) pages"
|
||||
collectionContainerView.accessibilityValue = "Page \(selectedPage) of \(total) selected"
|
||||
open override var accessibilityElements: [Any]? {
|
||||
get {
|
||||
let views: [UIView] = [previousButton, collectionContainerView, nextButton]
|
||||
return views.filter({ $0.isHidden == false })
|
||||
}
|
||||
set {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to make changes to the View based off a change events or from local properties.
|
||||
@ -176,7 +192,7 @@ open class Pagination: View {
|
||||
updateSelection()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
|
||||
guard let self else { return }
|
||||
UIAccessibility.post(notification: .announcement, argument: "Page \(self.selectedPage) of \(self.total) selected")
|
||||
UIAccessibility.post(notification: .announcement, argument: paginationDescription)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -78,11 +78,6 @@ open class PaginationButton: ButtonBase {
|
||||
tintColor = color
|
||||
super.updateView()
|
||||
}
|
||||
|
||||
open override func accessibilityActivate() -> Bool {
|
||||
sendActions(for: .touchUpInside)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension PaginationButton {
|
||||
|
||||
@ -42,8 +42,6 @@ open class RadioBoxGroup: SelectorGroupBase<RadioBoxItem>, SelectorGroupSingleSe
|
||||
if let selectorModels {
|
||||
items = 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
|
||||
@ -56,7 +54,7 @@ open class RadioBoxGroup: SelectorGroupBase<RadioBoxItem>, SelectorGroupSingleSe
|
||||
$0.isSelected = model.selected
|
||||
$0.strikethrough = model.strikethrough
|
||||
$0.strikethroughAccessibilityText = model.strikethroughAccessibileText
|
||||
$0.accessibilityValueText = "item \(index+1) of \(selectorModels.count)"
|
||||
$0.selectorView.bridge_accessibilityValueBlock = { "item \(index+1) of \(selectorModels.count)" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -111,7 +109,7 @@ extension RadioBoxGroup {
|
||||
/// Current Surface and this is used to pass down to child objects that implement Surfacable
|
||||
public var surface: Surface
|
||||
public var inputId: String?
|
||||
public var value: AnyHashable?
|
||||
public var value: String?
|
||||
public var accessibileText: String?
|
||||
public var text: String
|
||||
/// Array of LabelAttributeModel objects used in rendering the text.
|
||||
@ -126,7 +124,7 @@ extension RadioBoxGroup {
|
||||
public var strikethrough: Bool = false
|
||||
public var strikethroughAccessibileText: String
|
||||
|
||||
public init(disabled: Bool, surface: Surface = .light, inputId: String? = nil, value: AnyHashable? = nil,
|
||||
public init(disabled: Bool, surface: Surface = .light, inputId: String? = nil, value: String? = nil,
|
||||
text: String = "", textAttributes: [any LabelAttributeModel]? = nil,
|
||||
subText: String? = nil, subTextAttributes: [any LabelAttributeModel]? = nil,
|
||||
subTextRight: String? = nil, subTextRightAttributes: [any LabelAttributeModel]? = nil,
|
||||
|
||||
@ -74,9 +74,7 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable {
|
||||
}
|
||||
|
||||
/// Selector for this RadioBox.
|
||||
open var selectorView = UIView().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
open var selectorView = View().with { $0.accessibilityIdentifier = "RadioBox" }
|
||||
|
||||
/// If provided, the RadioBox text will be rendered.
|
||||
open var text: String? { didSet { setNeedsUpdate() } }
|
||||
@ -127,12 +125,19 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable {
|
||||
|
||||
open var inputId: String? { didSet { setNeedsUpdate() } }
|
||||
|
||||
open var value: AnyHashable? { hiddenValue }
|
||||
open var value: String? { hiddenValue }
|
||||
|
||||
open var hiddenValue: AnyHashable? { didSet { setNeedsUpdate() } }
|
||||
open var hiddenValue: String? { didSet { setNeedsUpdate() } }
|
||||
|
||||
open var accessibilityValueText: String?
|
||||
|
||||
open override var accessibilityAction: ((Control) -> Void)? {
|
||||
didSet {
|
||||
selectorView.accessibilityAction = { [weak self] selectorItemBase in
|
||||
guard let self else { return }
|
||||
accessibilityAction?(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Configuration Properties
|
||||
//--------------------------------------------------
|
||||
@ -165,16 +170,51 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable {
|
||||
onClick = { control in
|
||||
control.toggle()
|
||||
}
|
||||
|
||||
selectorView.bridge_accessibilityLabelBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
var accessibilityLabels = [String]()
|
||||
|
||||
if isSelected {
|
||||
accessibilityLabels.append("selected")
|
||||
}
|
||||
|
||||
accessibilityLabels.append("Radiobox")
|
||||
|
||||
if let text, !text.isEmpty {
|
||||
accessibilityLabels.append(text)
|
||||
}
|
||||
|
||||
if let text = subText, !text.isEmpty {
|
||||
accessibilityLabels.append(text)
|
||||
}
|
||||
|
||||
if let text = subTextRight, !text.isEmpty {
|
||||
accessibilityLabels.append(text)
|
||||
}
|
||||
|
||||
if strikethrough {
|
||||
accessibilityLabels.append(strikethroughAccessibilityText)
|
||||
}
|
||||
|
||||
if !isEnabled {
|
||||
accessibilityLabels.append("dimmed")
|
||||
}
|
||||
|
||||
return accessibilityLabels.joined(separator: ", ")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations.
|
||||
open override func setup() {
|
||||
super.setup()
|
||||
|
||||
isAccessibilityElement = true
|
||||
accessibilityTraits = .button
|
||||
isAccessibilityElement = false
|
||||
selectorView.isAccessibilityElement = true
|
||||
selectorView.accessibilityTraits = .button
|
||||
addSubview(selectorView)
|
||||
selectorView.isUserInteractionEnabled = false
|
||||
selectorView.isUserInteractionEnabled = true
|
||||
|
||||
selectorView.addSubview(selectorStackView)
|
||||
|
||||
@ -226,6 +266,8 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable {
|
||||
|
||||
/// This will change the state of the Selector and execute the actionBlock if provided.
|
||||
open func toggle() {
|
||||
guard isEnabled else { return }
|
||||
|
||||
//removed error
|
||||
isSelected.toggle()
|
||||
sendActions(for: .valueChanged)
|
||||
@ -239,26 +281,51 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable {
|
||||
setNeedsLayout()
|
||||
}
|
||||
|
||||
/// Used to update any Accessibility properties.
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
setAccessibilityLabel(for: [textLabel, subTextLabel, subTextRightLabel])
|
||||
if let currentAccessibilityLabel = accessibilityLabel {
|
||||
accessibilityLabel = "Radiobox, \(currentAccessibilityLabel)"
|
||||
} else {
|
||||
accessibilityLabel = "Radiobox"
|
||||
open override var accessibilityElements: [Any]? {
|
||||
get {
|
||||
var items = [Any]()
|
||||
items.append(selectorView)
|
||||
|
||||
if let text = text, !text.isEmpty {
|
||||
items.append(textLabel)
|
||||
}
|
||||
|
||||
if let text = subText, !text.isEmpty {
|
||||
items.append(subTextLabel)
|
||||
}
|
||||
|
||||
if let text = subTextRight, !text.isEmpty {
|
||||
items.append(subTextRightLabel)
|
||||
}
|
||||
return items
|
||||
}
|
||||
if let accessibilityValueText {
|
||||
accessibilityValue = strikethrough
|
||||
? "\(strikethroughAccessibilityText), \(accessibilityValueText)"
|
||||
: accessibilityValueText
|
||||
set {}
|
||||
}
|
||||
|
||||
/// Overriden to take the hit if there is an onClickSubscriber and the view is not a UIControl
|
||||
open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
guard isEnabled else { return super.hitTest(point, with: event) }
|
||||
|
||||
let textPoint = convert(point, to: textLabel)
|
||||
let subTextPoint = convert(point, to: subTextLabel)
|
||||
let subTextRightPoint = convert(point, to: subTextRightLabel)
|
||||
|
||||
if textLabel.isAction(for: textPoint) {
|
||||
return textLabel
|
||||
} else if subTextLabel.isAction(for: subTextPoint) {
|
||||
return subTextLabel
|
||||
} else if subTextRightLabel.isAction(for: subTextRightPoint) {
|
||||
return subTextRightLabel
|
||||
} else {
|
||||
accessibilityValue = strikethrough
|
||||
? "\(strikethroughAccessibilityText)"
|
||||
: accessibilityValueText
|
||||
guard !UIAccessibility.isVoiceOverRunning else { return nil }
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
}
|
||||
|
||||
open func getSelectorView() -> UIView {
|
||||
selectorView
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Methods
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -62,7 +62,7 @@ open class RadioButton: SelectorBase {
|
||||
|
||||
/// This will change the state of the Selector and execute the actionBlock if provided.
|
||||
open override func toggle() {
|
||||
guard !isSelected else { return }
|
||||
guard !isSelected, isEnabled else { return }
|
||||
|
||||
//removed error
|
||||
if showError && isSelected == false {
|
||||
|
||||
@ -46,8 +46,6 @@ open class RadioButtonGroup: SelectorGroupBase<RadioButtonItem>, SelectorGroupSi
|
||||
$0.surface = model.surface
|
||||
$0.inputId = model.inputId
|
||||
$0.hiddenValue = model.value
|
||||
$0.accessibilityLabel = model.accessibileText
|
||||
$0.accessibilityValueText = "item \(index+1) of \(selectorModels.count)"
|
||||
$0.labelText = model.labelText
|
||||
$0.labelTextAttributes = model.labelTextAttributes
|
||||
$0.childText = model.childText
|
||||
@ -55,6 +53,7 @@ open class RadioButtonGroup: SelectorGroupBase<RadioButtonItem>, SelectorGroupSi
|
||||
$0.isSelected = model.selected
|
||||
$0.errorText = model.errorText
|
||||
$0.showError = model.showError
|
||||
$0.selectorView.bridge_accessibilityValueBlock = { "item \(index+1) of \(selectorModels.count)" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ open class RadioButtonItem: SelectorItemBase<RadioButton> {
|
||||
//--------------------------------------------------
|
||||
/// This will change the state of the Selector and execute the actionBlock if provided.
|
||||
open override func toggle() {
|
||||
guard !isSelected else { return }
|
||||
guard !isSelected, isEnabled else { return }
|
||||
|
||||
//removed error
|
||||
if showError && isSelected == false {
|
||||
|
||||
@ -88,8 +88,6 @@ extension Tabs {
|
||||
open var minWidth: CGFloat = 44.0 { didSet { setNeedsUpdate() } }
|
||||
|
||||
open override var shouldHighlight: Bool { false }
|
||||
|
||||
open var accessibilityValueText: String?
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Configuration
|
||||
@ -151,6 +149,11 @@ extension Tabs {
|
||||
labelTopConstraint = label.pinTop(anchor: layoutGuide.topAnchor)
|
||||
labelLeadingConstraint = label.pinLeading(anchor: layoutGuide.leadingAnchor)
|
||||
labelBottomConstraint = label.pinBottom(anchor: layoutGuide.bottomAnchor, priority: .defaultHigh)
|
||||
|
||||
bridge_accessibilityLabelBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to make changes to the View based off a change events or from local properties.
|
||||
@ -176,13 +179,6 @@ extension Tabs {
|
||||
setNeedsLayout()
|
||||
}
|
||||
|
||||
/// Used to update any Accessibility properties.
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
accessibilityLabel = text
|
||||
accessibilityValue = accessibilityValueText
|
||||
}
|
||||
|
||||
open override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
|
||||
@ -305,7 +305,10 @@ open class Tabs: View {
|
||||
tabItem.orientation = orientation
|
||||
tabItem.surface = surface
|
||||
tabItem.indicatorPosition = indicatorPosition
|
||||
tabItem.accessibilityValueText = "\(index+1) of \(tabViews.count) Tabs"
|
||||
tabItem.bridge_accessibilityValueBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
return "\(index+1) of \(tabViews.count) Tabs"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -40,6 +40,8 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Properties
|
||||
//--------------------------------------------------
|
||||
internal var responder: UIResponder? { return nil }
|
||||
|
||||
internal let mainStackView = UIStackView().with {
|
||||
$0.axis = .vertical
|
||||
$0.alignment = .fill
|
||||
@ -92,11 +94,9 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
|
||||
|
||||
/// This is the view that will be wrapped with the border for userInteraction.
|
||||
/// The only subview of this view is the fieldStackView
|
||||
internal var containerView: UIView = {
|
||||
return UIView().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
}()
|
||||
internal var containerView = View().with {
|
||||
$0.isAccessibilityElement = true
|
||||
}
|
||||
|
||||
/// This is set by a local method.
|
||||
internal var bottomContainerView: UIView!
|
||||
@ -183,7 +183,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
|
||||
|
||||
open var statusIcon: Icon = Icon().with {
|
||||
$0.size = .medium
|
||||
$0.isAccessibilityElement = false
|
||||
$0.isAccessibilityElement = true
|
||||
}
|
||||
|
||||
open var labelText: String? { didSet { setNeedsUpdate() } }
|
||||
@ -207,6 +207,9 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
|
||||
if isReadOnly {
|
||||
state.insert(.readonly)
|
||||
}
|
||||
if let responder, responder.isFirstResponder {
|
||||
state.insert(.focused)
|
||||
}
|
||||
}
|
||||
return state
|
||||
}
|
||||
@ -241,22 +244,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
|
||||
|
||||
open var rules = [AnyRule<String>]()
|
||||
|
||||
open var accessibilityLabelText: String {
|
||||
var accessibilityLabels = [String]()
|
||||
if let text = titleLabel.text {
|
||||
accessibilityLabels.append(text)
|
||||
}
|
||||
if isReadOnly {
|
||||
accessibilityLabels.append("read only")
|
||||
}
|
||||
if !isEnabled {
|
||||
accessibilityLabels.append("dimmed")
|
||||
}
|
||||
if let errorText, showError {
|
||||
accessibilityLabels.append("error, \(errorText)")
|
||||
}
|
||||
return accessibilityLabels.joined(separator: ", ")
|
||||
}
|
||||
open var accessibilityHintText: String = "Double tap to open"
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Overrides
|
||||
@ -274,11 +262,11 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
|
||||
.pinBottom()
|
||||
|
||||
trailingEqualsConstraint = layoutGuide.pinTrailing(anchor: trailingAnchor)
|
||||
|
||||
|
||||
// width constraints
|
||||
trailingLessThanEqualsConstraint = layoutGuide.pinTrailingLessThanOrEqualTo(anchor: trailingAnchor)?.deactivate()
|
||||
widthConstraint = layoutGuide.widthAnchor.constraint(equalToConstant: 0).deactivate()
|
||||
|
||||
|
||||
// Add mainStackView to the view
|
||||
addSubview(mainStackView)
|
||||
|
||||
@ -292,7 +280,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
|
||||
//InputContainer, Icons, Buttons
|
||||
containerView.addSubview(fieldStackView)
|
||||
fieldStackView.pinToSuperView(.uniform(VDSLayout.space3X))
|
||||
|
||||
|
||||
let fieldContainerView = getFieldContainer()
|
||||
fieldContainerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
@ -300,11 +288,11 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
|
||||
fieldStackView.addArrangedSubview(fieldContainerView)
|
||||
fieldStackView.addArrangedSubview(statusIcon)
|
||||
fieldStackView.setCustomSpacing(VDSLayout.space3X, after: fieldContainerView)
|
||||
|
||||
|
||||
//get the container this is what show helper text, error text
|
||||
//can include other for character count, max length
|
||||
bottomContainerView = getBottomContainer()
|
||||
|
||||
|
||||
//this is the vertical stack that contains error text, helper text
|
||||
bottomContainerStackView.addArrangedSubview(errorLabel)
|
||||
bottomContainerStackView.addArrangedSubview(helperLabel)
|
||||
@ -312,11 +300,11 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
|
||||
// Add arranged subviews to textFieldStackView
|
||||
contentStackView.addArrangedSubview(containerView)
|
||||
contentStackView.addArrangedSubview(bottomContainerView)
|
||||
|
||||
|
||||
// Add arranged subviews to mainStackView
|
||||
mainStackView.addArrangedSubview(titleLabel)
|
||||
mainStackView.addArrangedSubview(contentStackView)
|
||||
|
||||
|
||||
// Initial position of the helper label
|
||||
updateHelperTextPosition()
|
||||
|
||||
@ -324,6 +312,43 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
|
||||
titleLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable()
|
||||
errorLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable()
|
||||
helperLabel.textColorConfiguration = secondaryColorConfiguration.eraseToAnyColorable()
|
||||
|
||||
containerView.bridge_accessibilityLabelBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
var accessibilityLabels = [String]()
|
||||
|
||||
if let text = titleLabel.text?.trimmingCharacters(in: .whitespaces) {
|
||||
accessibilityLabels.append(text)
|
||||
}
|
||||
if isReadOnly {
|
||||
accessibilityLabels.append("read only")
|
||||
}
|
||||
if !isEnabled {
|
||||
accessibilityLabels.append("dimmed")
|
||||
}
|
||||
if let errorText, showError {
|
||||
accessibilityLabels.append("error, \(errorText)")
|
||||
}
|
||||
|
||||
accessibilityLabels.append("\(Self.self)")
|
||||
|
||||
return accessibilityLabels.joined(separator: ", ")
|
||||
}
|
||||
|
||||
containerView.bridge_accessibilityHintBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
return isReadOnly || !isEnabled ? "" : accessibilityHintText
|
||||
}
|
||||
|
||||
containerView.bridge_accessibilityValueBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
return value
|
||||
}
|
||||
|
||||
statusIcon.bridge_accessibilityLabelBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
return showError || hasInternalError ? "error" : nil
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the UI
|
||||
@ -360,6 +385,22 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
|
||||
isReadOnly = false
|
||||
onChange = nil
|
||||
}
|
||||
|
||||
open override var canBecomeFirstResponder: Bool {
|
||||
responder?.canBecomeFirstResponder ?? super.canBecomeFirstResponder
|
||||
}
|
||||
|
||||
open override func becomeFirstResponder() -> Bool {
|
||||
responder?.becomeFirstResponder() ?? super.becomeFirstResponder()
|
||||
}
|
||||
|
||||
open override var canResignFirstResponder: Bool {
|
||||
responder?.canResignFirstResponder ?? super.canResignFirstResponder
|
||||
}
|
||||
|
||||
open override func resignFirstResponder() -> Bool {
|
||||
responder?.resignFirstResponder() ?? super.resignFirstResponder()
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Public Methods
|
||||
@ -447,6 +488,28 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
|
||||
}
|
||||
}
|
||||
|
||||
open override var accessibilityElements: [Any]? {
|
||||
get {
|
||||
var elements = [Any]()
|
||||
elements.append(contentsOf: [titleLabel, containerView])
|
||||
|
||||
if showError {
|
||||
elements.append(statusIcon)
|
||||
if let errorText, !errorText.isEmpty {
|
||||
elements.append(errorLabel)
|
||||
}
|
||||
}
|
||||
|
||||
if let helperText, !helperText.isEmpty {
|
||||
elements.append(helperLabel)
|
||||
}
|
||||
|
||||
return elements
|
||||
}
|
||||
|
||||
set { super.accessibilityElements = newValue }
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Methods
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -125,7 +125,7 @@ extension InputField {
|
||||
|
||||
class CreditCardHandler: FieldTypeHandler {
|
||||
static let shared = CreditCardHandler()
|
||||
|
||||
|
||||
private override init() {
|
||||
super.init()
|
||||
self.validateOnChange = false
|
||||
@ -135,6 +135,7 @@ extension InputField {
|
||||
fileprivate func updateLeftImage(_ inputField: InputField) {
|
||||
let imageName = inputField.cardType.imageName(surface: inputField.surface)
|
||||
creditCardImageView.image = BundleManager.shared.image(for: imageName)
|
||||
creditCardImageView.accessibilityLabel = inputField.cardType.rawValue
|
||||
}
|
||||
|
||||
override func updateView(_ inputField: InputField) {
|
||||
@ -148,14 +149,14 @@ extension InputField {
|
||||
|
||||
inputField.textField.leftView = iconContainerView
|
||||
inputField.textField.leftViewMode = .always
|
||||
|
||||
|
||||
updateLeftImage(inputField)
|
||||
}
|
||||
|
||||
|
||||
internal var creditCardImageView = UIImageView().with {
|
||||
$0.height(20)
|
||||
$0.width(32)
|
||||
$0.isAccessibilityElement = false
|
||||
$0.isAccessibilityElement = true
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.contentMode = .scaleAspectFill
|
||||
$0.clipsToBounds = true
|
||||
|
||||
@ -34,6 +34,8 @@ open class InputField: EntryFieldBase {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Properties
|
||||
//--------------------------------------------------
|
||||
internal override var responder: UIResponder? { textField }
|
||||
|
||||
internal override var containerBackgroundColor: UIColor {
|
||||
if showSuccess {
|
||||
return backgroundColorConfiguration.getColor(self)
|
||||
@ -102,6 +104,7 @@ open class InputField: EntryFieldBase {
|
||||
open var textField = TextField().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.textStyle = TextStyle.bodyLarge
|
||||
$0.isAccessibilityElement = false
|
||||
}
|
||||
|
||||
/// Color configuration for the textField.
|
||||
@ -163,11 +166,7 @@ open class InputField: EntryFieldBase {
|
||||
if showSuccess {
|
||||
state.insert(.success)
|
||||
}
|
||||
|
||||
if textField.isFirstResponder {
|
||||
state.insert(.focused)
|
||||
}
|
||||
|
||||
|
||||
return state
|
||||
}
|
||||
}
|
||||
@ -181,6 +180,8 @@ open class InputField: EntryFieldBase {
|
||||
/// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations.
|
||||
open override func setup() {
|
||||
super.setup()
|
||||
accessibilityHintText = "Double tap to edit"
|
||||
|
||||
textField.heightAnchor.constraint(equalToConstant: 20).isActive = true
|
||||
textField.delegate = self
|
||||
bottomContainerStackView.insertArrangedSubview(successLabel, at: 0)
|
||||
@ -195,6 +196,54 @@ open class InputField: EntryFieldBase {
|
||||
borderColorConfiguration.setSurfaceColors(VDSColor.feedbackSuccessOnlight, VDSColor.feedbackSuccessOndark, forState: .success)
|
||||
|
||||
textField.textColorConfiguration = textFieldTextColorConfiguration
|
||||
|
||||
containerView.bridge_accessibilityLabelBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
var accessibilityLabels = [String]()
|
||||
|
||||
if let text = titleLabel.text?.trimmingCharacters(in: .whitespaces) {
|
||||
accessibilityLabels.append(text)
|
||||
}
|
||||
|
||||
if let formatText = textField.formatText, !formatText.isEmpty {
|
||||
accessibilityLabels.append("format, \(formatText)")
|
||||
}
|
||||
|
||||
if let placeholderText = textField.placeholder, !placeholderText.isEmpty {
|
||||
accessibilityLabels.append("placeholder, \(placeholderText)")
|
||||
}
|
||||
|
||||
if isReadOnly {
|
||||
accessibilityLabels.append("read only")
|
||||
}
|
||||
|
||||
if !isEnabled {
|
||||
accessibilityLabels.append("dimmed")
|
||||
}
|
||||
|
||||
if let errorText, showError {
|
||||
accessibilityLabels.append("error, \(errorText)")
|
||||
}
|
||||
|
||||
if let successText, showSuccess {
|
||||
accessibilityLabels.append("success, \(successText)")
|
||||
}
|
||||
|
||||
accessibilityLabels.append("\(Self.self)")
|
||||
|
||||
return accessibilityLabels.joined(separator: ", ")
|
||||
}
|
||||
|
||||
statusIcon.bridge_accessibilityLabelBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
if showError {
|
||||
return "error"
|
||||
} else if showSuccess {
|
||||
return "success"
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open override func getFieldContainer() -> UIView {
|
||||
@ -227,13 +276,7 @@ open class InputField: EntryFieldBase {
|
||||
textField.isEnabled = isEnabled
|
||||
textField.isUserInteractionEnabled = isEnabled && !isReadOnly
|
||||
}
|
||||
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
textField.accessibilityLabel = accessibilityLabelText
|
||||
textField.accessibilityHint = isReadOnly || !isEnabled ? "" : "Double tap to open."
|
||||
}
|
||||
|
||||
|
||||
open override func updateErrorLabel() {
|
||||
super.updateErrorLabel()
|
||||
|
||||
@ -264,12 +307,21 @@ open class InputField: EntryFieldBase {
|
||||
open override var accessibilityElements: [Any]? {
|
||||
get {
|
||||
var elements = [Any]()
|
||||
elements.append(contentsOf: [titleLabel, textField])
|
||||
if showError {
|
||||
elements.append(contentsOf: [titleLabel, containerView])
|
||||
if let leftView = textField.leftView {
|
||||
elements.append(leftView)
|
||||
}
|
||||
|
||||
if !statusIcon.isHidden{
|
||||
elements.append(statusIcon)
|
||||
if let errorText, !errorText.isEmpty {
|
||||
elements.append(errorLabel)
|
||||
}
|
||||
}
|
||||
|
||||
if !actionTextLink.isHidden {
|
||||
elements.append(actionTextLink)
|
||||
}
|
||||
|
||||
if let errorText, !errorText.isEmpty, showError || hasInternalError {
|
||||
elements.append(errorLabel)
|
||||
} else if showSuccess, let successText, !successText.isEmpty {
|
||||
elements.append(successLabel)
|
||||
}
|
||||
@ -283,22 +335,6 @@ open class InputField: EntryFieldBase {
|
||||
|
||||
set { super.accessibilityElements = newValue }
|
||||
}
|
||||
|
||||
open override var canBecomeFirstResponder: Bool {
|
||||
return textField.canBecomeFirstResponder
|
||||
}
|
||||
|
||||
open override func becomeFirstResponder() -> Bool {
|
||||
return textField.becomeFirstResponder()
|
||||
}
|
||||
|
||||
open override var canResignFirstResponder: Bool {
|
||||
return textField.canResignFirstResponder
|
||||
}
|
||||
|
||||
open override func resignFirstResponder() -> Bool {
|
||||
return textField.resignFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
extension InputField: UITextFieldDelegate {
|
||||
@ -311,6 +347,7 @@ extension InputField: UITextFieldDelegate {
|
||||
public func textFieldDidEndEditing(_ textField: UITextField) {
|
||||
fieldType.handler().textFieldDidEndEditing(self, textField: textField)
|
||||
validate()
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: self.containerView)
|
||||
}
|
||||
|
||||
public func textFieldDidChangeSelection(_ textField: UITextField) {
|
||||
|
||||
@ -47,7 +47,10 @@ open class TextField: UITextField, ViewProtocol, Errorable {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
private var formatLabel = Label().with {
|
||||
/// Key of whether or not updateView() is called in setNeedsUpdate()
|
||||
open var shouldUpdateView: Bool = true
|
||||
|
||||
private var formatLabel = Label().with {
|
||||
$0.tag = 999
|
||||
$0.textColorConfiguration = ViewColorConfiguration().with {
|
||||
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forDisabled: true)
|
||||
@ -63,9 +66,6 @@ open class TextField: UITextField, ViewProtocol, Errorable {
|
||||
|
||||
/// Will determine if a scaled font should be used for the titleLabel font.
|
||||
open var useScaledFont: Bool = false { didSet { setNeedsUpdate() } }
|
||||
|
||||
/// Key of whether or not updateView() is called in setNeedsUpdate()
|
||||
open var shouldUpdateView: Bool = true
|
||||
|
||||
open var surface: Surface = .light { didSet { setNeedsUpdate() } }
|
||||
|
||||
@ -74,7 +74,7 @@ open class TextField: UITextField, ViewProtocol, Errorable {
|
||||
open var errorText: String? { didSet { setNeedsUpdate() } }
|
||||
|
||||
open var lineBreakMode: NSLineBreakMode = .byClipping { didSet { setNeedsUpdate() } }
|
||||
|
||||
|
||||
open override var isEnabled: Bool { didSet { setNeedsUpdate() } }
|
||||
|
||||
open var textColorConfiguration: AnyColorable = ViewColorConfiguration().with {
|
||||
@ -229,7 +229,135 @@ open class TextField: UITextField, ViewProtocol, Errorable {
|
||||
attributedText = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Accessibility
|
||||
//--------------------------------------------------
|
||||
open var accessibilityAction: ((TextField) -> Void)?
|
||||
|
||||
open override var isAccessibilityElement: Bool {
|
||||
get {
|
||||
var block: AXBoolReturnBlock?
|
||||
|
||||
// if #available(iOS 17, *) {
|
||||
// block = isAccessibilityElementBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_isAccessibilityElementBlock
|
||||
}
|
||||
|
||||
if let block {
|
||||
return block()
|
||||
} else {
|
||||
return super.isAccessibilityElement
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.isAccessibilityElement = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override var accessibilityLabel: String? {
|
||||
get {
|
||||
var block: AXStringReturnBlock?
|
||||
// if #available(iOS 17, *) {
|
||||
// block = accessibilityLabelBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_accessibilityLabelBlock
|
||||
}
|
||||
|
||||
if let block {
|
||||
return block()
|
||||
} else {
|
||||
return super.accessibilityLabel
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.accessibilityLabel = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override var accessibilityHint: String? {
|
||||
get {
|
||||
var block: AXStringReturnBlock?
|
||||
// if #available(iOS 17, *) {
|
||||
// block = accessibilityHintBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_accessibilityHintBlock
|
||||
}
|
||||
|
||||
if let block {
|
||||
return block()
|
||||
} else {
|
||||
return super.accessibilityHint
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.accessibilityHint = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override var accessibilityValue: String? {
|
||||
get {
|
||||
var block: AXStringReturnBlock?
|
||||
|
||||
// if #available(iOS 17, *) {
|
||||
// block = accessibilityHintBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_accessibilityValueBlock
|
||||
}
|
||||
|
||||
if let block{
|
||||
return block()
|
||||
} else {
|
||||
return super.accessibilityValue
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.accessibilityValue = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override func accessibilityActivate() -> Bool {
|
||||
guard isEnabled, isUserInteractionEnabled else { return false }
|
||||
|
||||
// if #available(iOS 17, *) {
|
||||
// if let block = accessibilityAction {
|
||||
// block(self)
|
||||
// return true
|
||||
// } else if let block = accessibilityActivateBlock {
|
||||
// return block()
|
||||
//
|
||||
// } else if let block = bridge_accessibilityActivateBlock {
|
||||
// return block()
|
||||
//
|
||||
// } else {
|
||||
// return super.accessibilityActivate()
|
||||
//
|
||||
// }
|
||||
//
|
||||
// } else {
|
||||
if let block = accessibilityAction {
|
||||
block(self)
|
||||
return true
|
||||
|
||||
} else if let block = bridge_accessibilityActivateBlock {
|
||||
return block()
|
||||
|
||||
} else {
|
||||
return super.accessibilityActivate()
|
||||
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
extension UITextField {
|
||||
|
||||
@ -32,6 +32,8 @@ open class TextArea: EntryFieldBase {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Properties
|
||||
//--------------------------------------------------
|
||||
internal override var responder: UIResponder? { textView }
|
||||
|
||||
internal var textViewHeightConstraint: NSLayoutConstraint?
|
||||
|
||||
internal var inputFieldStackView: UIStackView = {
|
||||
@ -42,30 +44,19 @@ open class TextArea: EntryFieldBase {
|
||||
$0.spacing = VDSLayout.space3X
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
open var characterCounterLabel = Label().with {
|
||||
$0.setContentCompressionResistancePriority(.required, for: .vertical)
|
||||
$0.textStyle = .bodySmall
|
||||
$0.textAlignment = .right
|
||||
$0.numberOfLines = 1
|
||||
}
|
||||
|
||||
|
||||
open var minHeight: Height = .twoX { didSet { setNeedsUpdate() } }
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Public Properties
|
||||
//--------------------------------------------------
|
||||
/// Override UIControl state to add the .error state if showSuccess is true and if showError is true.
|
||||
open override var state: UIControl.State {
|
||||
get {
|
||||
var state = super.state
|
||||
if textView.isFirstResponder {
|
||||
state.insert(.focused)
|
||||
}
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
override var containerSize: CGSize { CGSize(width: 182, height: Height.twoX.value) }
|
||||
|
||||
/// Enum used to describe the the height of TextArea.
|
||||
@ -101,13 +92,15 @@ open class TextArea: EntryFieldBase {
|
||||
open override var value: String? {
|
||||
return textView.text
|
||||
}
|
||||
|
||||
|
||||
/// UITextView shown in the TextArea.
|
||||
open var textView = TextView().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.sizeToFit()
|
||||
$0.isScrollEnabled = false
|
||||
$0.isAccessibilityElement = false
|
||||
$0.isScrollEnabled = true
|
||||
$0.textContainerInset = .zero
|
||||
$0.autocorrectionType = .no
|
||||
$0.textContainer.lineFragmentPadding = 0
|
||||
}
|
||||
|
||||
@ -137,10 +130,8 @@ open class TextArea: EntryFieldBase {
|
||||
/// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations.
|
||||
open override func setup() {
|
||||
super.setup()
|
||||
fieldStackView.pinToSuperView(.uniform(VDSFormControls.spaceInset))
|
||||
|
||||
textView.isScrollEnabled = true
|
||||
textView.autocorrectionType = .no
|
||||
|
||||
accessibilityHintText = "Double tap to edit"
|
||||
|
||||
//events
|
||||
textView
|
||||
@ -159,6 +150,7 @@ open class TextArea: EntryFieldBase {
|
||||
.publisher(for: .editingDidEnd)
|
||||
.sink { [weak self] _ in
|
||||
self?.validate()
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: self?.containerView)
|
||||
}.store(in: &subscribers)
|
||||
|
||||
textViewHeightConstraint = textView.heightAnchor.constraint(greaterThanOrEqualToConstant: containerSize.height)
|
||||
@ -192,13 +184,7 @@ open class TextArea: EntryFieldBase {
|
||||
characterCounterLabel.surface = surface
|
||||
highlightCharacterOverflow()
|
||||
}
|
||||
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
textView.accessibilityLabel = accessibilityLabelText
|
||||
textView.accessibilityHint = isReadOnly || !isEnabled ? "" : "Double tap to open."
|
||||
}
|
||||
|
||||
|
||||
override func updateRules() {
|
||||
super.updateRules()
|
||||
|
||||
@ -222,46 +208,7 @@ open class TextArea: EntryFieldBase {
|
||||
stackView.addArrangedSubview(characterCounterLabel)
|
||||
return stackView
|
||||
}
|
||||
|
||||
open override var accessibilityElements: [Any]? {
|
||||
get {
|
||||
var elements = [Any]()
|
||||
elements.append(contentsOf: [titleLabel, textView])
|
||||
|
||||
if showError {
|
||||
elements.append(statusIcon)
|
||||
if let errorText, !errorText.isEmpty {
|
||||
elements.append(errorLabel)
|
||||
}
|
||||
}
|
||||
|
||||
if let helperText, !helperText.isEmpty {
|
||||
elements.append(helperLabel)
|
||||
}
|
||||
|
||||
return elements
|
||||
}
|
||||
|
||||
set { super.accessibilityElements = newValue }
|
||||
}
|
||||
|
||||
|
||||
open override var canBecomeFirstResponder: Bool {
|
||||
return textView.canBecomeFirstResponder
|
||||
}
|
||||
|
||||
open override func becomeFirstResponder() -> Bool {
|
||||
return textView.becomeFirstResponder()
|
||||
}
|
||||
|
||||
open override var canResignFirstResponder: Bool {
|
||||
return textView.canResignFirstResponder
|
||||
}
|
||||
|
||||
open override func resignFirstResponder() -> Bool {
|
||||
return textView.resignFirstResponder()
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Methods
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -144,6 +144,135 @@ open class TextView: UITextView, ViewProtocol, Errorable {
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Accessibility
|
||||
//--------------------------------------------------
|
||||
open var accessibilityAction: ((TextView) -> Void)?
|
||||
|
||||
open override var isAccessibilityElement: Bool {
|
||||
get {
|
||||
var block: AXBoolReturnBlock?
|
||||
|
||||
// if #available(iOS 17, *) {
|
||||
// block = isAccessibilityElementBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_isAccessibilityElementBlock
|
||||
}
|
||||
|
||||
if let block {
|
||||
return block()
|
||||
} else {
|
||||
return super.isAccessibilityElement
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.isAccessibilityElement = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override var accessibilityLabel: String? {
|
||||
get {
|
||||
var block: AXStringReturnBlock?
|
||||
// if #available(iOS 17, *) {
|
||||
// block = accessibilityLabelBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_accessibilityLabelBlock
|
||||
}
|
||||
|
||||
if let block {
|
||||
return block()
|
||||
} else {
|
||||
return super.accessibilityLabel
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.accessibilityLabel = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override var accessibilityHint: String? {
|
||||
get {
|
||||
var block: AXStringReturnBlock?
|
||||
// if #available(iOS 17, *) {
|
||||
// block = accessibilityHintBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_accessibilityHintBlock
|
||||
}
|
||||
|
||||
if let block {
|
||||
return block()
|
||||
} else {
|
||||
return super.accessibilityHint
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.accessibilityHint = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override var accessibilityValue: String? {
|
||||
get {
|
||||
var block: AXStringReturnBlock?
|
||||
|
||||
// if #available(iOS 17, *) {
|
||||
// block = accessibilityHintBlock
|
||||
// }
|
||||
|
||||
if block == nil {
|
||||
block = bridge_accessibilityValueBlock
|
||||
}
|
||||
|
||||
if let block{
|
||||
return block()
|
||||
} else {
|
||||
return super.accessibilityValue
|
||||
}
|
||||
}
|
||||
set {
|
||||
super.accessibilityValue = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open override func accessibilityActivate() -> Bool {
|
||||
guard isEnabled, isUserInteractionEnabled else { return false }
|
||||
|
||||
// if #available(iOS 17, *) {
|
||||
// if let block = accessibilityAction {
|
||||
// block(self)
|
||||
// return true
|
||||
// } else if let block = accessibilityActivateBlock {
|
||||
// return block()
|
||||
//
|
||||
// } else if let block = bridge_accessibilityActivateBlock {
|
||||
// return block()
|
||||
//
|
||||
// } else {
|
||||
// return super.accessibilityActivate()
|
||||
//
|
||||
// }
|
||||
//
|
||||
// } else {
|
||||
if let block = accessibilityAction {
|
||||
block(self)
|
||||
return true
|
||||
|
||||
} else if let block = bridge_accessibilityActivateBlock {
|
||||
return block()
|
||||
|
||||
} else {
|
||||
return super.accessibilityActivate()
|
||||
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Methods
|
||||
|
||||
@ -44,6 +44,7 @@ open class TileContainer: TileContainerBase<TileContainer.Padding> {
|
||||
}
|
||||
|
||||
open class TileContainerBase<PaddingType: DefaultValuing>: Control where PaddingType.ValueType == CGFloat {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
//--------------------------------------------------
|
||||
@ -69,6 +70,7 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
|
||||
case secondary
|
||||
case white
|
||||
case black
|
||||
case token(UIColor.VDSColor)
|
||||
case custom(UIColor)
|
||||
|
||||
private var reflectedValue: String { String(reflecting: self) }
|
||||
@ -108,8 +110,13 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
|
||||
$0.clipsToBounds = true
|
||||
}
|
||||
|
||||
internal var containerView = View()
|
||||
|
||||
open var containerView = View().with {
|
||||
$0.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
||||
$0.setContentHuggingPriority(.defaultLow, for: .vertical)
|
||||
$0.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
||||
$0.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Public Properties
|
||||
//--------------------------------------------------
|
||||
@ -178,12 +185,7 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
|
||||
//--------------------------------------------------
|
||||
internal var widthConstraint: NSLayoutConstraint?
|
||||
internal var heightConstraint: NSLayoutConstraint?
|
||||
internal var heightGreaterThanConstraint: NSLayoutConstraint?
|
||||
internal var containerTopConstraint: NSLayoutConstraint?
|
||||
internal var containerBottomConstraint: NSLayoutConstraint?
|
||||
internal var containerLeadingConstraint: NSLayoutConstraint?
|
||||
internal var containerTrailingConstraint: NSLayoutConstraint?
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Configuration
|
||||
//--------------------------------------------------
|
||||
@ -221,28 +223,20 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
|
||||
super.setup()
|
||||
isAccessibilityElement = false
|
||||
|
||||
let layoutGuide = UILayoutGuide()
|
||||
addLayoutGuide(layoutGuide)
|
||||
layoutGuide
|
||||
.pinTop()
|
||||
.pinLeading()
|
||||
.pinTrailing(0, .defaultHigh)
|
||||
.pinBottom(0, .defaultHigh)
|
||||
addSubview(backgroundImageView)
|
||||
addSubview(containerView)
|
||||
containerView.addSubview(contentView)
|
||||
addSubview(highlightView)
|
||||
|
||||
containerView.pinToSuperView()
|
||||
|
||||
widthConstraint = layoutGuide.widthAnchor.constraint(equalToConstant: 0)
|
||||
|
||||
heightGreaterThanConstraint = layoutGuide.heightAnchor.constraint(greaterThanOrEqualToConstant: 44.0)
|
||||
heightGreaterThanConstraint?.isActive = false
|
||||
|
||||
heightConstraint = layoutGuide.heightAnchor.constraint(equalToConstant: 0)
|
||||
|
||||
containerView.addSubview(backgroundImageView)
|
||||
backgroundImageView.pinToSuperView()
|
||||
|
||||
containerView.addSubview(contentView)
|
||||
contentView.pinToSuperView()
|
||||
|
||||
containerView.addSubview(highlightView)
|
||||
highlightView.pinToSuperView()
|
||||
|
||||
widthConstraint = widthAnchor.constraint(equalToConstant: 0).deactivate()
|
||||
heightConstraint = heightAnchor.constraint(equalToConstant: 0).deactivate()
|
||||
|
||||
backgroundImageView.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
||||
backgroundImageView.setContentHuggingPriority(.defaultLow, for: .vertical)
|
||||
@ -251,20 +245,28 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
|
||||
backgroundImageView.isUserInteractionEnabled = false
|
||||
backgroundImageView.isHidden = true
|
||||
|
||||
containerTopConstraint = contentView.pinTop(anchor: layoutGuide.topAnchor, constant: padding.value)
|
||||
containerBottomConstraint = layoutGuide.pinBottom(anchor: contentView.bottomAnchor, constant: padding.value)
|
||||
containerLeadingConstraint = contentView.pinLeading(anchor: layoutGuide.leadingAnchor, constant: padding.value)
|
||||
containerTrailingConstraint = layoutGuide.pinTrailing(anchor: contentView.trailingAnchor, constant: padding.value)
|
||||
|
||||
highlightView.pin(layoutGuide)
|
||||
highlightView.isHidden = true
|
||||
highlightView.backgroundColor = .clear
|
||||
|
||||
//corner radius
|
||||
layer.cornerRadius = cornerRadius
|
||||
containerView.layer.cornerRadius = cornerRadius
|
||||
backgroundImageView.layer.cornerRadius = cornerRadius
|
||||
highlightView.layer.cornerRadius = cornerRadius
|
||||
clipsToBounds = true
|
||||
containerView.clipsToBounds = true
|
||||
|
||||
containerView.bridge_isAccessibilityElementBlock = { [weak self] in self?.onClickSubscriber != nil }
|
||||
containerView.accessibilityHint = "Double tap to open."
|
||||
containerView.accessibilityLabel = nil
|
||||
|
||||
NotificationCenter.default
|
||||
.publisher(for: UIDevice.orientationDidChangeNotification)
|
||||
.sink() { [weak self] _ in
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.001) { [weak self] in
|
||||
guard let self else { return }
|
||||
setNeedsUpdate()
|
||||
}
|
||||
}.store(in: &subscribers)
|
||||
|
||||
}
|
||||
|
||||
/// Overriden to take the hit if there is an onClickSubscriber and the view is not a UIControl
|
||||
@ -280,7 +282,7 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
|
||||
super.reset()
|
||||
shouldUpdateView = false
|
||||
color = .white
|
||||
aspectRatio = .ratio1x1
|
||||
aspectRatio = .none
|
||||
imageFallbackColor = .light
|
||||
width = nil
|
||||
height = nil
|
||||
@ -297,53 +299,15 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
|
||||
highlightView.backgroundColor = hightLightViewColorConfiguration.getColor(self)
|
||||
highlightView.isHidden = !isHighlighted
|
||||
|
||||
layer.borderColor = borderColorConfiguration.getColor(self).cgColor
|
||||
layer.borderWidth = showBorder ? VDSFormControls.borderWidth : 0
|
||||
containerView.layer.borderColor = borderColorConfiguration.getColor(self).cgColor
|
||||
containerView.layer.borderWidth = showBorder ? VDSFormControls.borderWidth : 0
|
||||
|
||||
containerTopConstraint?.constant = padding.value
|
||||
containerLeadingConstraint?.constant = padding.value
|
||||
containerBottomConstraint?.constant = padding.value
|
||||
containerTrailingConstraint?.constant = padding.value
|
||||
|
||||
if let width, aspectRatio == .none && height == nil{
|
||||
widthConstraint?.constant = width
|
||||
widthConstraint?.isActive = true
|
||||
heightConstraint?.isActive = false
|
||||
heightGreaterThanConstraint?.isActive = true
|
||||
} else if let height, let width {
|
||||
widthConstraint?.constant = width
|
||||
heightConstraint?.constant = height
|
||||
heightConstraint?.isActive = true
|
||||
widthConstraint?.isActive = true
|
||||
heightGreaterThanConstraint?.isActive = false
|
||||
} else if let width {
|
||||
let size = ratioSize(for: width)
|
||||
widthConstraint?.constant = size.width
|
||||
heightConstraint?.constant = size.height
|
||||
widthConstraint?.isActive = true
|
||||
heightConstraint?.isActive = true
|
||||
heightGreaterThanConstraint?.isActive = false
|
||||
} else {
|
||||
widthConstraint?.isActive = false
|
||||
heightConstraint?.isActive = false
|
||||
}
|
||||
|
||||
applyBackgroundEffects()
|
||||
contentView.removeConstraints()
|
||||
contentView.pinToSuperView(.uniform(padding.value))
|
||||
|
||||
if showDropShadow, surface == .light {
|
||||
addDropShadow(dropShadowConfiguration)
|
||||
} else {
|
||||
removeDropShadows()
|
||||
}
|
||||
updateContainerView()
|
||||
}
|
||||
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
containerView.isAccessibilityElement = onClickSubscriber != nil
|
||||
containerView.accessibilityHint = "Double tap to open."
|
||||
containerView.accessibilityLabel = nil
|
||||
}
|
||||
|
||||
|
||||
open override var accessibilityElements: [Any]? {
|
||||
get {
|
||||
var items = [Any]()
|
||||
@ -370,13 +334,6 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
|
||||
set {}
|
||||
}
|
||||
|
||||
/// Used to update frames for the added CAlayers to our view
|
||||
open override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
dropShadowLayers?.forEach { $0.frame = bounds }
|
||||
gradientLayers?.forEach { $0.frame = bounds }
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Public Methods
|
||||
//--------------------------------------------------
|
||||
@ -401,25 +358,25 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
|
||||
switch backgroundEffect {
|
||||
case .transparency:
|
||||
alphaConfiguration = 0.8
|
||||
removeGradientLayer()
|
||||
containerView.removeGradientLayer()
|
||||
case .gradient(let firstColor, let secondColor):
|
||||
alphaConfiguration = 1.0
|
||||
addGradientLayer(with: firstColor, secondColor: secondColor)
|
||||
containerView.addGradientLayer(with: firstColor, secondColor: secondColor)
|
||||
backgroundImageView.isHidden = true
|
||||
backgroundImageView.alpha = 1.0
|
||||
case .none:
|
||||
alphaConfiguration = 1.0
|
||||
removeGradientLayer()
|
||||
containerView.removeGradientLayer()
|
||||
}
|
||||
if let backgroundImage {
|
||||
backgroundImageView.image = backgroundImage
|
||||
backgroundImageView.isHidden = false
|
||||
backgroundImageView.alpha = alphaConfiguration
|
||||
backgroundColor = imageFallbackColor.withAlphaComponent(alphaConfiguration)
|
||||
containerView.backgroundColor = imageFallbackColor.withAlphaComponent(alphaConfiguration)
|
||||
} else {
|
||||
backgroundImageView.isHidden = true
|
||||
backgroundImageView.alpha = 1.0
|
||||
backgroundColor = color.withAlphaComponent(alphaConfiguration)
|
||||
containerView.backgroundColor = color.withAlphaComponent(alphaConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
@ -452,7 +409,77 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
|
||||
|
||||
return CGSize(width: width, height: height)
|
||||
}
|
||||
|
||||
private func sizeContainerView(width: CGFloat? = nil, height: CGFloat? = nil) {
|
||||
if let width, width > 0 {
|
||||
widthConstraint?.constant = width
|
||||
widthConstraint?.activate()
|
||||
}
|
||||
|
||||
if let height, height > 0 {
|
||||
heightConstraint?.constant = height
|
||||
heightConstraint?.activate()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateContainerView() {
|
||||
applyBackgroundEffects()
|
||||
|
||||
widthConstraint?.deactivate()
|
||||
heightConstraint?.deactivate()
|
||||
|
||||
if showDropShadow, surface == .light {
|
||||
containerView.addDropShadow(dropShadowConfiguration)
|
||||
} else {
|
||||
containerView.removeDropShadows()
|
||||
}
|
||||
|
||||
containerView.dropShadowLayers?.forEach { $0.frame = containerView.bounds }
|
||||
containerView.gradientLayers?.forEach { $0.frame = containerView.bounds }
|
||||
|
||||
if width != nil || height != nil {
|
||||
var containerViewWidth: CGFloat?
|
||||
var containerViewHeight: CGFloat?
|
||||
//run logic to determine which to activate
|
||||
if let width, aspectRatio == .none && height == nil{
|
||||
containerViewWidth = width
|
||||
|
||||
} else if let height, aspectRatio == .none && width == nil{
|
||||
containerViewHeight = height
|
||||
|
||||
} else if let height, let width {
|
||||
containerViewWidth = width
|
||||
containerViewHeight = height
|
||||
|
||||
} else if let width {
|
||||
let size = ratioSize(for: width)
|
||||
containerViewWidth = size.width
|
||||
containerViewHeight = size.height
|
||||
|
||||
} else if let height {
|
||||
let size = ratioSize(for: height)
|
||||
containerViewWidth = size.width
|
||||
containerViewHeight = size.height
|
||||
}
|
||||
sizeContainerView(width: containerViewWidth, height: containerViewHeight)
|
||||
} else {
|
||||
if let parentSize = horizontalPinnedSize() {
|
||||
|
||||
var containerViewWidth: CGFloat?
|
||||
var containerViewHeight: CGFloat?
|
||||
|
||||
let size = ratioSize(for: parentSize.width)
|
||||
if aspectRatio == .none {
|
||||
containerViewWidth = size.width
|
||||
} else {
|
||||
containerViewWidth = size.width
|
||||
containerViewHeight = size.height
|
||||
}
|
||||
|
||||
sizeContainerView(width: containerViewWidth, height: containerViewHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TileContainerBase {
|
||||
@ -484,6 +511,8 @@ extension TileContainerBase {
|
||||
return whiteColorConfig.getColor(object.surface)
|
||||
case .black:
|
||||
return blackColorConfig.getColor(object.surface)
|
||||
case .token(let vdsColor):
|
||||
return vdsColor.uiColor
|
||||
case .custom(let color):
|
||||
return color
|
||||
}
|
||||
|
||||
@ -302,8 +302,9 @@ open class Tilelet: TileContainerBase<Tilelet.Padding> {
|
||||
/// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations.
|
||||
open override func setup() {
|
||||
super.setup()
|
||||
aspectRatio = .none
|
||||
color = .black
|
||||
aspectRatio = .none
|
||||
|
||||
addContentView(stackView)
|
||||
|
||||
//badge
|
||||
@ -386,6 +387,7 @@ open class Tilelet: TileContainerBase<Tilelet.Padding> {
|
||||
/// Resets to default settings.
|
||||
open override func reset() {
|
||||
shouldUpdateView = false
|
||||
super.reset()
|
||||
aspectRatio = .none
|
||||
color = .black
|
||||
//models
|
||||
@ -405,11 +407,7 @@ open class Tilelet: TileContainerBase<Tilelet.Padding> {
|
||||
updateBadge()
|
||||
updateTitleLockup()
|
||||
updateIcons()
|
||||
///Content-driven height Tilelets - Minimum height is configurable.
|
||||
///if width != nil && (aspectRatio != .none || height != nil) then tilelet is not self growing, so we can apply text position alignments.
|
||||
if width != nil && (aspectRatio != .none || height != nil) {
|
||||
updateTextPositionAlignment()
|
||||
}
|
||||
updateTextPositionAlignment()
|
||||
setNeedsLayout()
|
||||
}
|
||||
|
||||
@ -584,6 +582,7 @@ open class Tilelet: TileContainerBase<Tilelet.Padding> {
|
||||
}
|
||||
|
||||
private func updateTextPositionAlignment() {
|
||||
guard width != nil && (aspectRatio != .none || height != nil) else { return }
|
||||
switch textPostion {
|
||||
case .top:
|
||||
titleLockupTopConstraint?.activate()
|
||||
|
||||
@ -262,20 +262,21 @@ open class TitleLockup: View {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Overrides
|
||||
//--------------------------------------------------
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
var elements = [Any]()
|
||||
if eyebrowModel != nil {
|
||||
elements.append(eyebrowLabel)
|
||||
open override var accessibilityElements: [Any]? {
|
||||
get {
|
||||
var elements = [Any]()
|
||||
if eyebrowModel != nil {
|
||||
elements.append(eyebrowLabel)
|
||||
}
|
||||
if titleModel != nil {
|
||||
elements.append(titleLabel)
|
||||
}
|
||||
if subTitleModel != nil {
|
||||
elements.append(subTitleLabel)
|
||||
}
|
||||
return elements.count > 0 ? elements : nil
|
||||
}
|
||||
if titleModel != nil {
|
||||
elements.append(titleLabel)
|
||||
}
|
||||
if subTitleModel != nil {
|
||||
elements.append(subTitleLabel)
|
||||
}
|
||||
setAccessibilityLabel(for: elements.compactMap({$0 as? UIView}))
|
||||
accessibilityElements = elements.count > 0 ? elements : nil
|
||||
set {}
|
||||
}
|
||||
|
||||
/// Resets to default settings.
|
||||
|
||||
@ -207,6 +207,14 @@ open class Toggle: Control, Changeable, FormFieldable {
|
||||
label.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor)
|
||||
]
|
||||
|
||||
bridge_accessibilityValueBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
if showText {
|
||||
return isSelected ? onText : offText
|
||||
} else {
|
||||
return isSelected ? "On" : "Off"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets to default settings.
|
||||
@ -238,16 +246,6 @@ open class Toggle: Control, Changeable, FormFieldable {
|
||||
toggleView.isEnabled = isEnabled
|
||||
toggleView.isOn = isOn
|
||||
}
|
||||
|
||||
/// Used to update any Accessibility properties.
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
if showText {
|
||||
accessibilityValue = isSelected ? onText : offText
|
||||
} else {
|
||||
accessibilityValue = isSelected ? "On" : "Off"
|
||||
}
|
||||
}
|
||||
|
||||
/// This will change the state of the Selector and execute the actionBlock if provided.
|
||||
open func toggle() {
|
||||
|
||||
@ -153,7 +153,7 @@ open class ToggleView: Control, Changeable, FormFieldable {
|
||||
// Update shadow layers frames to match the view's bounds
|
||||
knobView.layer.insertSublayer(shadowLayer1, at: 0)
|
||||
knobView.layer.insertSublayer(shadowLayer2, at: 0)
|
||||
|
||||
accessibilityLabel = "Toggle"
|
||||
}
|
||||
|
||||
/// Resets to default settings.
|
||||
@ -176,13 +176,6 @@ open class ToggleView: Control, Changeable, FormFieldable {
|
||||
|
||||
updateToggle()
|
||||
}
|
||||
|
||||
/// Used to update any Accessibility properties.
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
|
||||
accessibilityLabel = "Toggle"
|
||||
}
|
||||
|
||||
/// This will change the state of the Selector and execute the actionBlock if provided.
|
||||
open func toggle() {
|
||||
@ -226,7 +219,7 @@ open class ToggleView: Control, Changeable, FormFieldable {
|
||||
}
|
||||
knobTrailingConstraint?.isActive = true
|
||||
knobLeadingConstraint?.isActive = true
|
||||
setNeedsLayout()
|
||||
layoutIfNeeded()
|
||||
}
|
||||
|
||||
private func updateToggle() {
|
||||
|
||||
@ -138,6 +138,24 @@ open class Tooltip: Control, TooltipLaunchable {
|
||||
contentView: tooltip.contentView),
|
||||
presenter: self)
|
||||
}
|
||||
|
||||
bridge_accessibilityLabelBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
var label = title
|
||||
if label == nil {
|
||||
label = content
|
||||
}
|
||||
if let label, !label.isEmpty {
|
||||
return label
|
||||
} else {
|
||||
return "Modal"
|
||||
}
|
||||
}
|
||||
|
||||
bridge_accessibilityHintBlock = { [weak self] in
|
||||
guard let self else { return "" }
|
||||
return isEnabled ? "Double tap to open." : ""
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets to default settings.
|
||||
@ -163,23 +181,7 @@ open class Tooltip: Control, TooltipLaunchable {
|
||||
//get the color for the image
|
||||
icon.color = iconColorConfiguration.getColor(self)
|
||||
}
|
||||
|
||||
/// Used to update any Accessibility properties.
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
|
||||
var label = title
|
||||
if label == nil {
|
||||
label = content
|
||||
}
|
||||
if let label, !label.isEmpty {
|
||||
accessibilityLabel = label
|
||||
} else {
|
||||
accessibilityLabel = "Modal"
|
||||
}
|
||||
accessibilityHint = isEnabled ? "Double tap to open." : ""
|
||||
}
|
||||
|
||||
public static func accessibleText(for title: String?, content: String?, closeButtonText: String) -> String {
|
||||
var label = ""
|
||||
if let title {
|
||||
|
||||
@ -219,15 +219,19 @@ open class TooltipDialog: View, UIScrollViewDelegate {
|
||||
/// Used to update any Accessibility properties.
|
||||
open override func updateAccessibility() {
|
||||
super.updateAccessibility()
|
||||
|
||||
primaryAccessibilityElement.accessibilityHint = "Double tap on the \(tooltipModel.closeButtonText) button to close."
|
||||
primaryAccessibilityElement.accessibilityFrameInContainerSpace = .init(origin: .zero, size: frame.size)
|
||||
}
|
||||
|
||||
open override var accessibilityElements: [Any]? {
|
||||
get {
|
||||
var elements: [Any] = [primaryAccessibilityElement]
|
||||
contentStackView.arrangedSubviews.forEach{ elements.append($0) }
|
||||
elements.append(closeButton)
|
||||
|
||||
var elements: [Any] = [primaryAccessibilityElement]
|
||||
contentStackView.arrangedSubviews.forEach{ elements.append($0) }
|
||||
elements.append(closeButton)
|
||||
|
||||
accessibilityElements = elements
|
||||
return elements
|
||||
}
|
||||
set {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -12,23 +12,11 @@ extension UITapGestureRecognizer {
|
||||
|
||||
/// Determines if the touch event has a action attribute within the range given
|
||||
/// - Parameters:
|
||||
/// - label: UILabel in question
|
||||
/// - label: Label in question
|
||||
/// - targetRange: Range to look within
|
||||
/// - Returns: Wether the range in the label has an action
|
||||
public func didTapActionInLabel(_ label: UILabel, inRange targetRange: NSRange) -> Bool {
|
||||
|
||||
guard let attributedText = label.attributedText else { return false }
|
||||
|
||||
let layoutManager = NSLayoutManager()
|
||||
let textContainer = NSTextContainer(size: label.bounds.size)
|
||||
let textStorage = NSTextStorage(attributedString: attributedText)
|
||||
layoutManager.addTextContainer(textContainer)
|
||||
textStorage.addLayoutManager(layoutManager)
|
||||
|
||||
let location = location(in: label)
|
||||
let characterIndex = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
|
||||
|
||||
guard let _ = attributedText.attribute(NSAttributedString.Key.action, at: characterIndex, effectiveRange: nil) as? String, characterIndex < attributedText.length else { return false }
|
||||
return true
|
||||
public func didTapActionInLabel(_ label: Label, inRange targetRange: NSRange) -> Bool {
|
||||
let tapLocation = location(in: label)
|
||||
return label.isAction(for: tapLocation, inRange: targetRange)
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,7 +62,7 @@ extension UIView {
|
||||
} else {
|
||||
removeDebugBorder()
|
||||
}
|
||||
if let view = self as? ViewProtocol {
|
||||
if let view = self as? (any ViewProtocol) {
|
||||
view.updateView()
|
||||
}
|
||||
}
|
||||
|
||||
95
VDS/Protocols/AccessibilityUpdatable.swift
Normal file
95
VDS/Protocols/AccessibilityUpdatable.swift
Normal file
@ -0,0 +1,95 @@
|
||||
//
|
||||
// AccessibilityUpdatable.swift
|
||||
// VDS
|
||||
//
|
||||
// Created by Matt Bruce on 6/20/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
public protocol AccessibilityUpdatable {
|
||||
var bridge_isAccessibilityElementBlock: AXBoolReturnBlock? { get set }
|
||||
|
||||
var bridge_accessibilityLabelBlock: AXStringReturnBlock? { get set }
|
||||
|
||||
var bridge_accessibilityValueBlock: AXStringReturnBlock? { get set }
|
||||
|
||||
var bridge_accessibilityHintBlock: AXStringReturnBlock? { get set }
|
||||
|
||||
var bridge_accessibilityActivateBlock: AXBoolReturnBlock? { get set }
|
||||
|
||||
}
|
||||
|
||||
private struct AccessibilityBridge {
|
||||
static var isAccessibilityElementBlockKey: UInt8 = 0
|
||||
static var activateBlockKey: UInt8 = 1
|
||||
static var valueBlockKey: UInt8 = 2
|
||||
static var hintBlockKey: UInt8 = 3
|
||||
static var labelBlockKey: UInt8 = 4
|
||||
}
|
||||
|
||||
extension AccessibilityUpdatable where Self: NSObject {
|
||||
|
||||
public var bridge_isAccessibilityElementBlock: AXBoolReturnBlock? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &AccessibilityBridge.isAccessibilityElementBlockKey) as? AXBoolReturnBlock
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &AccessibilityBridge.isAccessibilityElementBlockKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
// if #available(iOS 17, *) {
|
||||
// self.isAccessibilityElementBlock = newValue
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
public var bridge_accessibilityActivateBlock: AXBoolReturnBlock? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &AccessibilityBridge.activateBlockKey) as? AXBoolReturnBlock
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &AccessibilityBridge.activateBlockKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
// if #available(iOS 17, *) {
|
||||
// self.accessibilityActivateBlock = newValue
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
public var bridge_accessibilityValueBlock: AXStringReturnBlock? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &AccessibilityBridge.valueBlockKey) as? AXStringReturnBlock
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &AccessibilityBridge.valueBlockKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
// if #available(iOS 17, *) {
|
||||
// self.accessibilityValueBlock = newValue
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
public var bridge_accessibilityHintBlock: AXStringReturnBlock? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &AccessibilityBridge.hintBlockKey) as? AXStringReturnBlock
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &AccessibilityBridge.hintBlockKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
// if #available(iOS 17, *) {
|
||||
// self.accessibilityHintBlock = newValue
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
public var bridge_accessibilityLabelBlock: AXStringReturnBlock? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &AccessibilityBridge.labelBlockKey) as? AXStringReturnBlock
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &AccessibilityBridge.labelBlockKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
// if #available(iOS 17, *) {
|
||||
// self.accessibilityLabelBlock = newValue
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -9,6 +9,4 @@ import Foundation
|
||||
|
||||
public protocol Groupable: Control {
|
||||
|
||||
/// Property used to add context to the Grouping of a set.
|
||||
var accessibilityValueText: String? { get set }
|
||||
}
|
||||
|
||||
@ -631,6 +631,195 @@ extension LayoutConstraintable {
|
||||
return centerYAnchor.constraint(greaterThanOrEqualTo: found, constant: -constant).with { $0.priority = priority; $0.isActive = true }
|
||||
}
|
||||
}
|
||||
|
||||
// alignment
|
||||
public enum LayoutAlignment: String, CaseIterable {
|
||||
case fill
|
||||
case leading
|
||||
case top
|
||||
case center
|
||||
case trailing
|
||||
case bottom
|
||||
}
|
||||
|
||||
public enum LayoutDistribution: String, CaseIterable {
|
||||
case fill
|
||||
case fillProportionally
|
||||
}
|
||||
|
||||
extension LayoutConstraintable {
|
||||
public func removeConstraints() {
|
||||
guard let view = self as? UIView, let superview = view.superview else { return }
|
||||
|
||||
// Remove all existing constraints on the containerView
|
||||
let superviewConstraints = superview.constraints
|
||||
for constraint in superviewConstraints {
|
||||
if constraint.firstItem as? UIView == view
|
||||
|| constraint.secondItem as? UIView == view {
|
||||
superview.removeConstraint(constraint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func applyAlignment(_ alignment: LayoutAlignment, edges: UIEdgeInsets = UIEdgeInsets.zero) {
|
||||
guard let superview = superview else { return }
|
||||
|
||||
removeConstraints()
|
||||
|
||||
switch alignment {
|
||||
case .fill:
|
||||
pinToSuperView(edges)
|
||||
|
||||
case .leading:
|
||||
pinTop(edges.top)
|
||||
pinLeading(edges.left)
|
||||
pinTrailingLessThanOrEqualTo(anchor: superview.trailingAnchor, constant: edges.right)
|
||||
pinBottom(edges.bottom)
|
||||
|
||||
case .trailing:
|
||||
pinTop(edges.top)
|
||||
pinLeadingGreaterThanOrEqualTo(anchor: superview.leadingAnchor, constant: edges.left)
|
||||
pinTrailing(edges.right)
|
||||
pinBottom(edges.bottom)
|
||||
|
||||
case .top:
|
||||
pinTop(edges.top)
|
||||
pinLeadingGreaterThanOrEqualTo(anchor: superview.leadingAnchor, constant: edges.left)
|
||||
pinTrailingLessThanOrEqualTo(anchor: superview.trailingAnchor, constant: edges.right)
|
||||
pinBottomLessThanOrEqualTo(anchor: superview.bottomAnchor, constant: edges.bottom)
|
||||
|
||||
case .bottom:
|
||||
pinTopGreaterThanOrEqualTo(anchor: superview.topAnchor, constant: edges.top)
|
||||
pinLeadingGreaterThanOrEqualTo(anchor: superview.leadingAnchor, constant: edges.left)
|
||||
pinTrailingLessThanOrEqualTo(anchor: superview.trailingAnchor, constant: edges.right)
|
||||
pinBottom(edges.bottom)
|
||||
|
||||
case .center:
|
||||
pinCenterX()
|
||||
pinTop(edges.top)
|
||||
pinLeadingGreaterThanOrEqualTo(anchor: superview.leadingAnchor, constant: edges.left)
|
||||
pinTrailingLessThanOrEqualTo(anchor: superview.trailingAnchor, constant: edges.right)
|
||||
pinBottom(edges.bottom)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Method to check if the view is pinned to its superview
|
||||
public func isPinnedToSuperview() -> Bool {
|
||||
isPinnedVerticallyToSuperview() && isPinnedHorizontallyToSuperview()
|
||||
}
|
||||
|
||||
public func horizontalPinnedSize() -> CGSize? {
|
||||
guard let view = self as? UIView, let superview = view.superview else { return nil }
|
||||
let constraints = superview.constraints
|
||||
|
||||
var leadingPinnedObject: AnyObject?
|
||||
var trailingPinnedObject: AnyObject?
|
||||
|
||||
for constraint in constraints {
|
||||
if (constraint.firstItem === view && (constraint.firstAttribute == .leading || constraint.firstAttribute == .left)) {
|
||||
leadingPinnedObject = constraint.secondItem as AnyObject?
|
||||
} else if (constraint.secondItem === view && (constraint.secondAttribute == .leading || constraint.secondAttribute == .left)) {
|
||||
leadingPinnedObject = constraint.firstItem as AnyObject?
|
||||
} else if (constraint.firstItem === view && (constraint.firstAttribute == .trailing || constraint.firstAttribute == .right)) {
|
||||
trailingPinnedObject = constraint.secondItem as AnyObject?
|
||||
} else if (constraint.secondItem === view && (constraint.secondAttribute == .trailing || constraint.secondAttribute == .right)) {
|
||||
trailingPinnedObject = constraint.firstItem as AnyObject?
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure both leading and trailing pinned objects are identified
|
||||
if let leadingObject = leadingPinnedObject, let trailingObject = trailingPinnedObject {
|
||||
|
||||
// Calculate the size based on the pinned objects
|
||||
if let leadingView = leadingObject as? UIView, let trailingView = trailingObject as? UIView {
|
||||
let leadingPosition = leadingView.convert(leadingView.bounds.origin, to: superview).x
|
||||
let trailingPosition = trailingView.convert(trailingView.bounds.origin, to: superview).x + trailingView.bounds.width
|
||||
return CGSize(width: trailingPosition - leadingPosition, height: view.bounds.size.height)
|
||||
|
||||
} else if let leadingGuide = leadingObject as? UILayoutGuide, let trailingGuide = trailingObject as? UILayoutGuide {
|
||||
let leadingPosition = leadingGuide.layoutFrame.minX
|
||||
let trailingPosition = trailingGuide.layoutFrame.maxX
|
||||
return CGSize(width: trailingPosition - leadingPosition, height: view.bounds.size.height)
|
||||
|
||||
} else if let leadingView = leadingObject as? UIView, let trailingGuide = trailingObject as? UILayoutGuide {
|
||||
let leadingPosition = leadingView.convert(leadingView.bounds.origin, to: superview).x
|
||||
let trailingPosition = trailingGuide.layoutFrame.maxX
|
||||
return CGSize(width: trailingPosition - leadingPosition, height: view.bounds.size.height)
|
||||
|
||||
} else if let leadingGuide = leadingObject as? UILayoutGuide, let trailingView = trailingObject as? UIView {
|
||||
let leadingPosition = leadingGuide.layoutFrame.minX
|
||||
let trailingPosition = trailingView.convert(trailingView.bounds.origin, to: superview).x + trailingView.bounds.width
|
||||
return CGSize(width: trailingPosition - leadingPosition, height: view.bounds.size.height)
|
||||
}
|
||||
|
||||
} else if let pinnedObject = leadingPinnedObject {
|
||||
if let view = pinnedObject as? UIView {
|
||||
return view.bounds.size
|
||||
} else if let layoutGuide = pinnedObject as? UILayoutGuide {
|
||||
return layoutGuide.layoutFrame.size
|
||||
}
|
||||
|
||||
} else if let pinnedObject = trailingPinnedObject {
|
||||
if let view = pinnedObject as? UIView {
|
||||
return view.bounds.size
|
||||
} else if let layoutGuide = pinnedObject as? UILayoutGuide {
|
||||
return layoutGuide.layoutFrame.size
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
public func isPinnedHorizontallyToSuperview() -> Bool {
|
||||
guard let view = self as? UIView, let superview = view.superview else { return false }
|
||||
let constraints = superview.constraints
|
||||
var leadingPinned = false
|
||||
var trailingPinned = false
|
||||
|
||||
for constraint in constraints {
|
||||
if (constraint.firstItem as? UIView == view && constraint.firstAttribute == .leading && constraint.relation == .equal) ||
|
||||
(constraint.secondItem as? UIView == view && constraint.secondAttribute == .leading && constraint.relation == .equal) ||
|
||||
(constraint.firstItem as? UIView == view && constraint.firstAttribute == .left && constraint.relation == .equal) ||
|
||||
(constraint.secondItem as? UIView == view && constraint.secondAttribute == .left && constraint.relation == .equal) {
|
||||
leadingPinned = true
|
||||
}
|
||||
if (constraint.firstItem as? UIView == view && constraint.firstAttribute == .trailing && constraint.relation == .equal) ||
|
||||
(constraint.secondItem as? UIView == view && constraint.secondAttribute == .trailing && constraint.relation == .equal) ||
|
||||
(constraint.firstItem as? UIView == view && constraint.firstAttribute == .right && constraint.relation == .equal) ||
|
||||
(constraint.secondItem as? UIView == view && constraint.secondAttribute == .right && constraint.relation == .equal) {
|
||||
trailingPinned = true
|
||||
}
|
||||
}
|
||||
|
||||
return leadingPinned && trailingPinned
|
||||
}
|
||||
|
||||
public func isPinnedVerticallyToSuperview() -> Bool {
|
||||
guard let view = self as? UIView, let superview = view.superview else { return false }
|
||||
let constraints = superview.constraints
|
||||
var topPinned = false
|
||||
var bottomPinned = false
|
||||
|
||||
for constraint in constraints {
|
||||
if (constraint.firstItem as? UIView == view && constraint.firstAttribute == .top && constraint.relation == .equal) ||
|
||||
(constraint.secondItem as? UIView == view && constraint.secondAttribute == .top && constraint.relation == .equal) {
|
||||
topPinned = true
|
||||
}
|
||||
if (constraint.firstItem as? UIView == view && constraint.firstAttribute == .bottom && constraint.relation == .equal) ||
|
||||
(constraint.secondItem as? UIView == view && constraint.secondAttribute == .bottom && constraint.relation == .equal) {
|
||||
bottomPinned = true
|
||||
}
|
||||
}
|
||||
|
||||
return topPinned && bottomPinned
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Implementations
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -9,13 +9,16 @@ import Foundation
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
public protocol ViewProtocol: AnyObject, Initable, Resettable, Enabling, Surfaceable {
|
||||
public protocol ViewProtocol: AnyObject, Initable, Resettable, Enabling, Surfaceable, AccessibilityUpdatable {
|
||||
/// Set of Subscribers for any Publishers for this Control.
|
||||
var subscribers: Set<AnyCancellable> { get set }
|
||||
|
||||
/// Key of whether or not updateView() is called in setNeedsUpdate()
|
||||
var shouldUpdateView: Bool { get set }
|
||||
|
||||
/// Used for setting an implementation for the default Accessible Action
|
||||
var accessibilityAction: ((Self) -> Void)? { get set }
|
||||
|
||||
/// Executed on initialization for this View.
|
||||
func initialSetup()
|
||||
|
||||
@ -30,6 +33,7 @@ public protocol ViewProtocol: AnyObject, Initable, Resettable, Enabling, Surface
|
||||
}
|
||||
|
||||
extension ViewProtocol {
|
||||
|
||||
/// Called when there are changes in a View based off a change events or from local properties.
|
||||
public func setNeedsUpdate() {
|
||||
if shouldUpdateView {
|
||||
@ -49,7 +53,7 @@ extension ViewProtocol where Self: UIView {
|
||||
view.removeFromSuperview()
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewProtocol where Self: UIControl {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "discover.svg",
|
||||
"filename" : "Discover-02.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
||||
1
VDS/SupportingFiles/Icons.xcassets/CreditCard/discover.imageset/Discover-02.svg
vendored
Normal file
1
VDS/SupportingFiles/Icons.xcassets/CreditCard/discover.imageset/Discover-02.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 19 KiB |
@ -1,49 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 20">
|
||||
<g>
|
||||
<path fill="#231F20" d="M1.447,7.449H0v5.067h1.447c0.763,0,1.324-0.184,1.806-0.587c0.579-0.482,0.921-1.201,0.921-1.946
|
||||
C4.164,8.492,3.051,7.449,1.447,7.449z M2.604,11.254c-0.307,0.281-0.71,0.403-1.35,0.403H0.991V8.308h0.263
|
||||
c0.64,0,1.026,0.114,1.35,0.412c0.342,0.307,0.544,0.772,0.544,1.262C3.147,10.465,2.946,10.947,2.604,11.254z"/>
|
||||
<rect id="XMLID_422_" x="4.62" y="7.449" fill="#231F20" width="0.991" height="5.067"/>
|
||||
<path id="XMLID_421_" fill="#231F20" d="M8.022,9.395c-0.596-0.219-0.763-0.368-0.763-0.64c0-0.316,0.307-0.561,0.736-0.561
|
||||
c0.298,0,0.535,0.123,0.798,0.412l0.517-0.675C8.89,7.563,8.381,7.37,7.82,7.37c-0.894,0-1.578,0.622-1.578,1.447
|
||||
c0,0.701,0.316,1.052,1.245,1.385c0.386,0.14,0.587,0.228,0.684,0.289c0.202,0.132,0.298,0.316,0.298,0.526
|
||||
c0,0.412-0.324,0.71-0.763,0.71c-0.473,0-0.85-0.237-1.078-0.675l-0.64,0.614c0.456,0.666,0.999,0.964,1.753,0.964
|
||||
c1.026,0,1.745-0.684,1.745-1.666C9.486,10.167,9.153,9.807,8.022,9.395z"/>
|
||||
<path id="XMLID_420_" fill="#231F20" d="M9.793,9.982c0,1.49,1.166,2.639,2.674,2.639c0.421,0,0.789-0.088,1.236-0.298v-1.166
|
||||
c-0.395,0.395-0.745,0.552-1.192,0.552c-0.991,0-1.701-0.719-1.701-1.745c0-0.973,0.728-1.736,1.657-1.736
|
||||
c0.473,0,0.824,0.167,1.236,0.57V7.633c-0.43-0.219-0.789-0.307-1.219-0.307C10.994,7.335,9.793,8.51,9.793,9.982z"/>
|
||||
<polygon id="XMLID_419_" fill="#231F20" points="21.532,10.85 20.182,7.449 19.104,7.449 21.26,12.648 21.786,12.648 23.978,7.449
|
||||
22.908,7.449 "/>
|
||||
<polygon id="XMLID_418_" fill="#231F20" points="24.425,12.516 27.222,12.516 27.222,11.657 25.407,11.657 25.407,10.289
|
||||
27.152,10.289 27.152,9.43 25.407,9.43 25.407,8.308 27.222,8.308 27.222,7.449 24.425,7.449 "/>
|
||||
<path fill="#231F20" d="M31.132,8.948c0-0.947-0.649-1.499-1.788-1.499h-1.464v5.067h0.991v-2.034h0.132l1.368,2.034h1.219
|
||||
l-1.596-2.13C30.72,10.228,31.132,9.719,31.132,8.948z M29.151,9.781h-0.289V8.247h0.307c0.614,0,0.947,0.254,0.947,0.754
|
||||
C30.115,9.509,29.782,9.781,29.151,9.781z"/>
|
||||
|
||||
<linearGradient id="XMLID_2_" gradientUnits="userSpaceOnUse" x1="18.0982" y1="592.1596" x2="16.2331" y2="589.2393" gradientTransform="matrix(1 0 0 1 0 -580)">
|
||||
<stop offset="0" stop-color="#F89F20"/>
|
||||
<stop offset="0.2502" stop-color="#F79A20"/>
|
||||
<stop offset="0.5331" stop-color="#F68D20"/>
|
||||
<stop offset="0.6196" stop-color="#F58720"/>
|
||||
<stop offset="0.7232" stop-color="#F48120"/>
|
||||
<stop offset="1" stop-color="#F37521"/>
|
||||
</linearGradient>
|
||||
<circle id="XMLID_415_" fill="url(#XMLID_2_)" cx="16.719" cy="10" r="2.692"/>
|
||||
|
||||
<linearGradient id="XMLID_3_" gradientUnits="userSpaceOnUse" x1="17.8034" y1="592.1198" x2="15.0775" y2="586.7917" gradientTransform="matrix(1 0 0 1 0 -580)">
|
||||
<stop offset="0" stop-color="#F58720"/>
|
||||
<stop offset="0.3587" stop-color="#E16F27"/>
|
||||
<stop offset="0.703" stop-color="#D4602C"/>
|
||||
<stop offset="0.9816" stop-color="#D05B2E"/>
|
||||
</linearGradient>
|
||||
<circle id="XMLID_414_" opacity="0.65" fill="url(#XMLID_3_)" cx="16.719" cy="10" r="2.692"/>
|
||||
<g id="XMLID_430_">
|
||||
<path fill="#231F20" d="M31.763,7.642c0-0.088-0.061-0.14-0.167-0.14h-0.14v0.447h0.105V7.773l0.123,0.175h0.132l-0.149-0.184
|
||||
C31.728,7.747,31.763,7.703,31.763,7.642z M31.579,7.703h-0.018V7.589h0.018c0.053,0,0.079,0.018,0.079,0.061
|
||||
C31.658,7.685,31.632,7.703,31.579,7.703z"/>
|
||||
<path fill="#231F20" d="M31.614,7.335c-0.219,0-0.386,0.175-0.386,0.386c0,0.219,0.175,0.386,0.386,0.386
|
||||
c0.21,0,0.386-0.175,0.386-0.386C32,7.51,31.825,7.335,31.614,7.335z M31.614,8.045c-0.167,0-0.307-0.14-0.307-0.316
|
||||
c0-0.175,0.14-0.316,0.307-0.316c0.167,0,0.307,0.149,0.307,0.316C31.921,7.905,31.781,8.045,31.614,8.045z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.6 KiB |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "jcb.svg",
|
||||
"filename" : "jcb-emblem-logo.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
||||
1
VDS/SupportingFiles/Icons.xcassets/CreditCard/jcb.imageset/jcb-emblem-logo.svg
vendored
Normal file
1
VDS/SupportingFiles/Icons.xcassets/CreditCard/jcb.imageset/jcb-emblem-logo.svg
vendored
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="_レイヤー_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 34 26.19"><path d="M34,20.91c0,2.91-2.37,5.28-5.28,5.28H0V5.28C0,2.37,2.37,0,5.28,0h28.72V20.91Z" fill="#fff"/><path d="M24.65,15.55h2.18l.27-.02c.42-.08,.77-.46,.77-.98s-.35-.87-.77-.98l-.27-.02h-2.18v2Z" fill="#469b23"/><path d="M26.58,1.77c-2.08,0-3.78,1.68-3.78,3.78v3.93h5.34c.12,0,.27,0,.37,.02,1.21,.06,2.1,.69,2.1,1.77,0,.85-.6,1.58-1.72,1.72v.04c1.23,.08,2.16,.77,2.16,1.83,0,1.14-1.04,1.89-2.41,1.89h-5.86v7.69h5.55c2.08,0,3.78-1.68,3.78-3.78V1.77h-5.53Z" fill="#469b23"/><path d="M27.6,11.51c0-.5-.35-.83-.77-.89l-.21-.02h-1.97v1.83h1.97l.21-.02c.42-.06,.77-.39,.77-.89Z" fill="#469b23"/><path d="M5.67,1.77c-2.08,0-3.78,1.68-3.78,3.78V14.88c1.06,.52,2.16,.85,3.26,.85,1.31,0,2.02-.79,2.02-1.87v-4.41h3.24v4.39c0,1.7-1.06,3.1-4.66,3.1-2.18,0-3.89-.48-3.89-.48v7.96H7.42c2.08,0,3.78-1.68,3.78-3.78V1.77H5.67Z" fill="#0c2c84"/><path d="M16.13,1.77c-2.08,0-3.78,1.68-3.78,3.78v4.95c.96-.81,2.62-1.33,5.3-1.21,1.43,.06,2.97,.46,2.97,.46v1.6c-.77-.39-1.68-.75-2.87-.83-2.04-.15-3.26,.85-3.26,2.6s1.23,2.76,3.26,2.6c1.18-.08,2.1-.46,2.87-.83v1.6s-1.52,.39-2.97,.46c-2.68,.12-4.34-.39-5.3-1.21v8.73h5.55c2.08,0,3.78-1.68,3.78-3.78V1.77h-5.55Z" fill="#d7182a"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@ -1,51 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 20">
|
||||
<g>
|
||||
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-856.4599" y1="503.2267" x2="-855.8029" y2="503.2267" gradientTransform="matrix(12.5258 0 0 -12.5258 10748.9648 6314.5825)">
|
||||
<stop offset="0" stop-color="#007940"/>
|
||||
<stop offset="0.229" stop-color="#00873F"/>
|
||||
<stop offset="0.743" stop-color="#40A737"/>
|
||||
<stop offset="1" stop-color="#5CB531"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_1_)" d="M22.7,12.1h1.9c0.1,0,0.2,0,0.2,0c0.4-0.1,0.7-0.4,0.7-0.9c0-0.4-0.3-0.8-0.7-0.9c-0.1,0-0.2,0-0.2,0h-1.9
|
||||
V12.1z"/>
|
||||
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="-856.4599" y1="503.3283" x2="-855.8034" y2="503.3283" gradientTransform="matrix(12.5258 0 0 -12.5258 10748.9648 6314.5825)">
|
||||
<stop offset="0" stop-color="#007940"/>
|
||||
<stop offset="0.229" stop-color="#00873F"/>
|
||||
<stop offset="0.743" stop-color="#40A737"/>
|
||||
<stop offset="1" stop-color="#5CB531"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_2_)" d="M24.5,0c-1.8,0-3.3,1.5-3.3,3.3v3.5h4.7c0.1,0,0.2,0,0.3,0C27.2,6.9,28,7.4,28,8.4c0,0.8-0.5,1.4-1.5,1.5v0
|
||||
c1.1,0.1,1.9,0.7,1.9,1.6c0,1-0.9,1.7-2.1,1.7h-5.2V20H26c1.8,0,3.3-1.5,3.3-3.3V0L24.5,0z"/>
|
||||
|
||||
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="-856.4599" y1="503.4401" x2="-855.8029" y2="503.4401" gradientTransform="matrix(12.5258 0 0 -12.5258 10748.9648 6314.5825)">
|
||||
<stop offset="0" stop-color="#007940"/>
|
||||
<stop offset="0.229" stop-color="#00873F"/>
|
||||
<stop offset="0.743" stop-color="#40A737"/>
|
||||
<stop offset="1" stop-color="#5CB531"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_3_)" d="M25.3,8.6c0-0.4-0.3-0.7-0.7-0.8c0,0-0.1,0-0.2,0h-1.7v1.6h1.7c0.1,0,0.2,0,0.2,0C25,9.3,25.3,9,25.3,8.6
|
||||
L25.3,8.6z"/>
|
||||
|
||||
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="-857.9309" y1="503.329" x2="-857.2637" y2="503.329" gradientTransform="matrix(12.5258 0 0 -12.5258 10748.9648 6314.5825)">
|
||||
<stop offset="0" stop-color="#1F286F"/>
|
||||
<stop offset="0.475" stop-color="#004E94"/>
|
||||
<stop offset="0.826" stop-color="#0066B1"/>
|
||||
<stop offset="1" stop-color="#006FBC"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_4_)" d="M6,0C4.2,0,2.7,1.5,2.7,3.3v8.2c0.9,0.5,1.9,0.8,2.9,0.8c1.2,0,1.8-0.7,1.8-1.6V6.8h2.9v3.9
|
||||
c0,1.5-0.9,2.7-4.1,2.7c-1.9,0-3.4-0.4-3.4-0.4v7h4.9c1.8,0,3.3-1.5,3.3-3.3V0L6,0z"/>
|
||||
|
||||
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="-857.1989" y1="503.3275" x2="-856.5508" y2="503.3275" gradientTransform="matrix(12.5258 0 0 -12.5258 10748.9648 6314.5825)">
|
||||
<stop offset="0" stop-color="#6C2C2F"/>
|
||||
<stop offset="0.173" stop-color="#882730"/>
|
||||
<stop offset="0.573" stop-color="#BE1833"/>
|
||||
<stop offset="0.859" stop-color="#DC0436"/>
|
||||
<stop offset="1" stop-color="#E60039"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_5_)" d="M15.2,0c-1.8,0-3.3,1.5-3.3,3.3v4.4c0.8-0.7,2.3-1.2,4.7-1.1C17.8,6.7,19.2,7,19.2,7v1.4
|
||||
c-0.7-0.3-1.5-0.7-2.5-0.7c-1.8-0.1-2.9,0.8-2.9,2.3c0,1.6,1.1,2.4,2.9,2.3c1-0.1,1.8-0.4,2.5-0.7V13c0,0-1.3,0.3-2.6,0.4
|
||||
c-2.4,0.1-3.8-0.3-4.7-1.1V20h4.9c1.8,0,3.3-1.5,3.3-3.3V0L15.2,0z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.1 KiB |
@ -1,6 +1,26 @@
|
||||
1.0.69
|
||||
----------------
|
||||
- DatePicker - Refactored how this is shown
|
||||
- Checkbox Item/Group - Accessibility Refactor
|
||||
- Radiobox Item/Group - Accessibility Refactor
|
||||
- Radiobutton Item/Group - Accessibility Refactor
|
||||
- CXTDT-553663 - DropdownSelect - Accessibility - has popup
|
||||
- CXTDT-577463 - InputField - Accessibility
|
||||
|
||||
1.0.69
|
||||
----------------
|
||||
- Expired Build because of a issue
|
||||
|
||||
1.0.67
|
||||
----------------
|
||||
- CXTDT-553663 - DropdownSelect - Accessibility - has popup
|
||||
- CXTDT-568463 - Calendar - On long press, hover randomizes
|
||||
- CXTDT-568412 - Calendar - Incorrect side nav icon size
|
||||
- CXTDT-568422 - Calendar - DarkMode Legend icon fill using Light mode color
|
||||
- CXTDT-565796 - DropdownSelect - Accessibility
|
||||
- CXTDT-560458 - Dropdown/TextArea - Different voiceover
|
||||
- CXTDT-565106 - InputField - CreditCard - Icons
|
||||
- CXTDT-546821 - TextArea - Accessibility
|
||||
- CXTDT-560823 - TextArea - Accessibility
|
||||
|
||||
1.0.66
|
||||
----------------
|
||||
|
||||
Loading…
Reference in New Issue
Block a user