From bf731a8a84597b1d571c0049ae037caee311c0b0 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 1 Jul 2024 14:24:08 -0500 Subject: [PATCH 01/22] added in accessibility to icons Signed-off-by: Matt Bruce --- VDS/Components/Tilelet/Tilelet.swift | 29 ++++++++++++++----- .../Tilelet/TileletIconModels.swift | 8 +++-- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/VDS/Components/Tilelet/Tilelet.swift b/VDS/Components/Tilelet/Tilelet.swift index 615c0b40..e52332ff 100644 --- a/VDS/Components/Tilelet/Tilelet.swift +++ b/VDS/Components/Tilelet/Tilelet.swift @@ -205,15 +205,10 @@ open class Tilelet: TileContainerBase { } /// Descriptive Icon positioned in the contentView. - open var descriptiveIcon = Icon().with { - $0.isAccessibilityElement = false - } + open var descriptiveIcon = Icon() /// Directional Icon positioned in the contentView. - open var directionalIcon = Icon().with { - $0.isAccessibilityElement = false - $0.name = .rightArrow - } + open var directionalIcon = Icon() private var _textWidth: TextWidth? @@ -304,7 +299,7 @@ open class Tilelet: TileContainerBase { super.setup() color = .black aspectRatio = .none - + addContentView(stackView) //badge @@ -382,6 +377,16 @@ open class Tilelet: TileContainerBase { titleLockupSubTitleLabelHeightGreaterThanConstraint = titleLockup.subTitleLabel.heightGreaterThanEqualTo(constant: titleLockup.subTitleLabel.minimumLineHeight) titleLockupSubTitleLabelHeightGreaterThanConstraint?.priority = .defaultHigh titleLockupSubTitleLabelHeightGreaterThanConstraint?.activate() + + directionalIcon.bridge_accessibilityLabelBlock = { [weak self] in + guard let self, let directionalIconModel else { return nil } + return directionalIconModel.accessibleText + } + + descriptiveIcon.bridge_accessibilityLabelBlock = { [weak self] in + guard let self, let descriptiveIconModel else { return nil } + return descriptiveIconModel.accessibleText + } } /// Resets to default settings. @@ -425,6 +430,14 @@ open class Tilelet: TileContainerBase { let titleLockupViews = gatherAccessibilityElements(from: titleLockup) views.append(contentsOf: titleLockupViews) } + + if descriptiveIconModel != nil { + views.append(descriptiveIcon) + + } else if directionalIconModel != nil { + views.append(directionalIcon) + } + containerView.setAccessibilityLabel(for: views) // get the views to return diff --git a/VDS/Components/Tilelet/TileletIconModels.swift b/VDS/Components/Tilelet/TileletIconModels.swift index 58057942..2a8b6dac 100644 --- a/VDS/Components/Tilelet/TileletIconModels.swift +++ b/VDS/Components/Tilelet/TileletIconModels.swift @@ -66,6 +66,10 @@ extension Tilelet { public var iconName: Icon.Name { return self == .rightArrow ? .rightArrow : .externalLink } + + public var accessibilityLabel: String { + self == .rightArrow ? "Right Arrow" : "External Link" + } } public enum IconSize: String, EnumSubset { @@ -80,7 +84,7 @@ extension Tilelet { public var iconColor: IconColor? /// Accessible Text for the Icon - public var accessibleText: String + public var accessibleText: String? /// Enum for a icon type you want shown.. public var iconType: IconType @@ -95,7 +99,7 @@ extension Tilelet { self.iconType = iconType self.iconColor = iconColor - self.accessibleText = accessibleText ?? iconType.iconName.rawValue + self.accessibleText = accessibleText ?? iconType.accessibilityLabel self.size = size } } From 72e86dafc29cd7dbddff4b75fd3fc03f5f03a694 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 1 Jul 2024 14:26:41 -0500 Subject: [PATCH 02/22] CXTDT-560485 - updated text Signed-off-by: Matt Bruce --- VDS/Components/Tilelet/TileletIconModels.swift | 2 +- VDS/SupportingFiles/ReleaseNotes.txt | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/VDS/Components/Tilelet/TileletIconModels.swift b/VDS/Components/Tilelet/TileletIconModels.swift index 2a8b6dac..03b2d225 100644 --- a/VDS/Components/Tilelet/TileletIconModels.swift +++ b/VDS/Components/Tilelet/TileletIconModels.swift @@ -68,7 +68,7 @@ extension Tilelet { } public var accessibilityLabel: String { - self == .rightArrow ? "Right Arrow" : "External Link" + self == .rightArrow ? "Directional right arrow" : "External link" } } diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 54be0815..12661050 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,3 +1,7 @@ +1.0.70 +---------------- +- CXTDT-560485 - Tilelet - Accessibility Icons + 1.0.69 ---------------- - DatePicker - Refactored how this is shown From 315601e048c5e4e2b8bf205c94c563e5d945418c Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 1 Jul 2024 15:26:51 -0500 Subject: [PATCH 03/22] CXTDT-577463 - InputField - Accessibility - #1 Typing Feedback Signed-off-by: Matt Bruce --- VDS/Components/TextFields/InputField/InputField.swift | 8 +++++++- VDS/Components/TextFields/TextArea/TextArea.swift | 11 ++++++++++- VDS/SupportingFiles/ReleaseNotes.txt | 2 ++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index f7f54896..11a8ae17 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -105,6 +105,8 @@ open class InputField: EntryFieldBase { $0.translatesAutoresizingMaskIntoConstraints = false $0.textStyle = TextStyle.bodyLarge $0.isAccessibilityElement = false + $0.autocorrectionType = .no + $0.spellCheckingType = .no } /// Color configuration for the textField. @@ -360,7 +362,11 @@ extension InputField: UITextFieldDelegate { } public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - return fieldType.handler().textField(self, textField: textField, shouldChangeCharactersIn: range, replacementString: string) + let shouldChange = fieldType.handler().textField(self, textField: textField, shouldChangeCharactersIn: range, replacementString: string) + if shouldChange { + UIAccessibility.post(notification: .announcement, argument: string) + } + return shouldChange } } diff --git a/VDS/Components/TextFields/TextArea/TextArea.swift b/VDS/Components/TextFields/TextArea/TextArea.swift index d0396d7a..c668a851 100644 --- a/VDS/Components/TextFields/TextArea/TextArea.swift +++ b/VDS/Components/TextFields/TextArea/TextArea.swift @@ -101,6 +101,7 @@ open class TextArea: EntryFieldBase { $0.isScrollEnabled = true $0.textContainerInset = .zero $0.autocorrectionType = .no + $0.spellCheckingType = .no $0.textContainer.lineFragmentPadding = 0 } @@ -132,6 +133,7 @@ open class TextArea: EntryFieldBase { super.setup() accessibilityHintText = "Double tap to edit" + textView.delegate = self //events textView @@ -226,7 +228,7 @@ open class TextArea: EntryFieldBase { } } - func textViewDidChange(_ textView: UITextView) { + public func textViewDidChange(_ textView: UITextView) { //dynamic textView Height sizing based on Figma //if you want it to work "as-is" delete this code @@ -288,3 +290,10 @@ open class TextArea: EntryFieldBase { //-------------------------------------------------- var countRule = CharacterCountRule() } + +extension TextArea: UITextViewDelegate { + public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { + UIAccessibility.post(notification: .announcement, argument: text) + return true + } +} diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 12661050..0be4ba37 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,6 +1,8 @@ 1.0.70 ---------------- +- CXTDT-577463 - InputField - Accessibility - #1 Typing Feedback - CXTDT-560485 - Tilelet - Accessibility Icons +- DatePicker - Final logic for how the calendar shows. 1.0.69 ---------------- From 801b488eb43408e228bc876fdffa7e55a33f6a60 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 1 Jul 2024 15:28:44 -0500 Subject: [PATCH 04/22] updated version Signed-off-by: Matt Bruce --- VDS.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index a5403c49..ee0265b4 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -1535,7 +1535,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 68; + CURRENT_PROJECT_VERSION = 70; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1573,7 +1573,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 68; + CURRENT_PROJECT_VERSION = 70; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; From 1e0b0279b7d9cd6b81fe5aa917486ef761bd63a4 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 2 Jul 2024 09:33:54 -0500 Subject: [PATCH 05/22] CXTDT-577463 - InputField - Accessibility - Issue #5 Signed-off-by: Matt Bruce --- .../TextFields/InputField/FieldTypes/Password.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/VDS/Components/TextFields/InputField/FieldTypes/Password.swift b/VDS/Components/TextFields/InputField/FieldTypes/Password.swift index 00c00a0d..c08af9c8 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/Password.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/Password.swift @@ -42,6 +42,13 @@ extension InputField { self.passwordActionType = nextPasswordActionType inputField.setNeedsUpdate() }) + // set the accessibilityLabel + if let labelText = inputField.labelText { + inputField.actionTextLink.bridge_accessibilityLabelBlock = { + return "\(buttonText) \(labelText)" + } + } + } else { passwordActionType = .show } From 0ee459946f04e7c8a6d17f6ca5317841d4312136 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 2 Jul 2024 09:37:36 -0500 Subject: [PATCH 06/22] CXTDT-577463 - InputField - Accessibility - #5 Password / Inline Action Signed-off-by: Matt Bruce --- .../TextFields/InputField/FieldTypes/FieldType.swift | 6 ++++++ .../TextFields/InputField/FieldTypes/Password.swift | 9 +-------- VDS/SupportingFiles/ReleaseNotes.txt | 1 + 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/VDS/Components/TextFields/InputField/FieldTypes/FieldType.swift b/VDS/Components/TextFields/InputField/FieldTypes/FieldType.swift index b3c2a92b..180478af 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/FieldType.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/FieldType.swift @@ -68,6 +68,12 @@ extension InputField { actionModel.onClick(inputField) } inputField.actionTextLink.isHidden = false + // set the accessibilityLabel + if let labelText = inputField.labelText { + inputField.actionTextLink.bridge_accessibilityLabelBlock = { + return "\(actionModel.text) \(labelText)" + } + } inputField.fieldStackView.setCustomSpacing(VDSLayout.space2X, after: inputField.statusIcon) } else { inputField.actionTextLink.isHidden = true diff --git a/VDS/Components/TextFields/InputField/FieldTypes/Password.swift b/VDS/Components/TextFields/InputField/FieldTypes/Password.swift index c08af9c8..7d45d091 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/Password.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/Password.swift @@ -41,14 +41,7 @@ extension InputField { guard let self else { return } self.passwordActionType = nextPasswordActionType inputField.setNeedsUpdate() - }) - // set the accessibilityLabel - if let labelText = inputField.labelText { - inputField.actionTextLink.bridge_accessibilityLabelBlock = { - return "\(buttonText) \(labelText)" - } - } - + }) } else { passwordActionType = .show } diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 0be4ba37..f831af12 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,6 +1,7 @@ 1.0.70 ---------------- - CXTDT-577463 - InputField - Accessibility - #1 Typing Feedback +- CXTDT-577463 - InputField - Accessibility - #5 Password / Inline Action - CXTDT-560485 - Tilelet - Accessibility Icons - DatePicker - Final logic for how the calendar shows. From 88fdcabe67b2af2847633a940aaf13ab049f3d52 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 2 Jul 2024 14:52:58 -0500 Subject: [PATCH 07/22] updated to use generic Signed-off-by: Matt Bruce --- VDS/Components/DatePicker/DatePicker.swift | 26 ++++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index 17ededf4..22ab9d58 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -223,7 +223,7 @@ extension DatePicker { //find scrollView if scrollView == nil { - scrollView = findScrollView(from: containerView) + scrollView = containerView.findSuperview(ofType: UIScrollView.self) scrollViewContentSize = scrollView?.contentSize } @@ -348,17 +348,6 @@ extension DatePicker { } } - 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 - } - 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 @@ -442,3 +431,16 @@ extension DatePicker { } } } + +extension UIView { + public func findSuperview(ofType type: T.Type) -> T? { + var currentView: UIView? = self + while let view = currentView { + if let superview = view.superview as? T { + return superview + } + currentView = view.superview + } + return nil + } +} From 2b60e8cff33af8266d8755c3cde9f48176e8fb9a Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 3 Jul 2024 12:19:30 -0500 Subject: [PATCH 08/22] CXTDT-581803 - Date Picker - Calendar does not switch to Dark Mode Signed-off-by: Matt Bruce --- VDS/Components/DatePicker/DatePicker.swift | 2 +- VDS/Components/DatePicker/DatePickerCalendarModel.swift | 6 +----- VDS/SupportingFiles/ReleaseNotes.txt | 4 ++++ 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index 22ab9d58..83450403 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -214,7 +214,7 @@ extension DatePicker { calendar.indicators = calendarModel.indicators calendar.maxDate = calendarModel.maxDate calendar.minDate = calendarModel.minDate - calendar.surface = calendarModel.surface + calendar.surface = surface calendar.setNeedsLayout() calendar.layoutIfNeeded() diff --git a/VDS/Components/DatePicker/DatePickerCalendarModel.swift b/VDS/Components/DatePicker/DatePickerCalendarModel.swift index ccceb5c9..16a46e61 100644 --- a/VDS/Components/DatePicker/DatePickerCalendarModel.swift +++ b/VDS/Components/DatePicker/DatePickerCalendarModel.swift @@ -10,8 +10,6 @@ import UIKit extension DatePicker { public struct CalendarModel { - public let surface: Surface - /// If set to true, the calendar will not have a border. public let hideContainerBorder: Bool @@ -35,15 +33,13 @@ extension DatePicker { /// Array of ``CalendarIndicatorModel`` you are wanting to show on legend. public let indicators: [CalendarBase.CalendarIndicatorModel] - public init(surface: Surface = .light, - hideContainerBorder: Bool = false, + public init(hideContainerBorder: Bool = false, hideCurrentDateIndicator: Bool = false, activeDates: [Date] = [], inactiveDates: [Date] = [], minDate: Date = Date().startOfMonth, maxDate: Date = Date().endOfMonth, indicators: [CalendarBase.CalendarIndicatorModel] = []) { - self.surface = surface self.hideContainerBorder = hideContainerBorder self.hideCurrentDateIndicator = hideCurrentDateIndicator self.activeDates = activeDates diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index f831af12..6ea73a9b 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,3 +1,7 @@ +1.0.71 +---------------- +- CXTDT-581803 - DatePicker - Calendar does not switch to Dark Mode + 1.0.70 ---------------- - CXTDT-577463 - InputField - Accessibility - #1 Typing Feedback From d3a7d45ab9719b463666a6b559750b3d2ae8f7c9 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 9 Jul 2024 09:38:17 -0500 Subject: [PATCH 09/22] fixed bug in radiobox Signed-off-by: Matt Bruce --- VDS/Components/RadioBox/RadioBoxItem.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VDS/Components/RadioBox/RadioBoxItem.swift b/VDS/Components/RadioBox/RadioBoxItem.swift index 562e1e54..872a4d05 100644 --- a/VDS/Components/RadioBox/RadioBoxItem.swift +++ b/VDS/Components/RadioBox/RadioBoxItem.swift @@ -214,7 +214,7 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable { selectorView.isAccessibilityElement = true selectorView.accessibilityTraits = .button addSubview(selectorView) - selectorView.isUserInteractionEnabled = true + selectorView.isUserInteractionEnabled = false selectorView.addSubview(selectorStackView) From 1c01fabef07b181a07880694512ef0f0cc9c8238 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 9 Jul 2024 10:50:36 -0500 Subject: [PATCH 10/22] =?UTF-8?q?CXTDT-584278=20=E2=80=93=20InputField=20-?= =?UTF-8?q?=20Accessibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matt Bruce --- VDS/Components/TextFields/InputField/InputField.swift | 7 +++++++ VDS/SupportingFiles/ReleaseNotes.txt | 1 + 2 files changed, 8 insertions(+) diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index 11a8ae17..a7e0bbb7 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -184,6 +184,8 @@ open class InputField: EntryFieldBase { super.setup() accessibilityHintText = "Double tap to edit" + actionTextLink.accessibilityTraits = .button + textField.heightAnchor.constraint(equalToConstant: 20).isActive = true textField.delegate = self bottomContainerStackView.insertArrangedSubview(successLabel, at: 0) @@ -246,6 +248,11 @@ open class InputField: EntryFieldBase { return nil } } + + containerView.bridge_accessibilityValueBlock = { [weak self] in + guard let self else { return "" } + return textField.isSecureTextEntry ? "\(textField.text.count) stars" : value + } } open override func getFieldContainer() -> UIView { diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 6ea73a9b..973e8ed8 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,6 +1,7 @@ 1.0.71 ---------------- - CXTDT-581803 - DatePicker - Calendar does not switch to Dark Mode +- CXTDT-584278 – InputField - Accessibility 1.0.70 ---------------- From d7086b070275eb446ef06cba74e254b56c598a06 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 10 Jul 2024 16:21:50 -0500 Subject: [PATCH 11/22] added a placeholder Signed-off-by: Matt Bruce --- .../TextFields/TextArea/TextView.swift | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/VDS/Components/TextFields/TextArea/TextView.swift b/VDS/Components/TextFields/TextArea/TextView.swift index 54fa9452..3f40ae38 100644 --- a/VDS/Components/TextFields/TextArea/TextView.swift +++ b/VDS/Components/TextFields/TextArea/TextView.swift @@ -41,10 +41,23 @@ open class TextView: UITextView, ViewProtocol, Errorable { // MARK: - Private Properties //-------------------------------------------------- private var initialSetupPerformed = false - + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- + open var placeholder: String? { + didSet { + placeholderLabel.text = placeholder + } + } + + open var placeholderLabel = Label().with { + $0.textColorConfiguration = ViewColorConfiguration().with { + $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forDisabled: true) + $0.setSurfaceColors(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark, forDisabled: false) + }.eraseToAnyColorable() + } + /// Key of whether or not updateView() is called in setNeedsUpdate() open var shouldUpdateView: Bool = true @@ -88,6 +101,7 @@ open class TextView: UITextView, ViewProtocol, Errorable { if textAlignment != oldValue { // Text alignment can be part of our paragraph style, so we may need to // re-style when changed + placeholderLabel.textAlignment = textAlignment updateLabel() } } @@ -118,6 +132,9 @@ open class TextView: UITextView, ViewProtocol, Errorable { done.pinCenterY() .pinTrailing(16) inputAccessoryView = accessView + + addSubview(placeholderLabel) + placeholderLabel.pinToSuperView() } @objc func doneButtonAction() { @@ -145,7 +162,11 @@ open class TextView: UITextView, ViewProtocol, Errorable { setNeedsUpdate() } - + open override func layoutSubviews() { + super.layoutSubviews() + placeholderLabel.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2 + } + //-------------------------------------------------- // MARK: - Accessibility //-------------------------------------------------- @@ -297,6 +318,9 @@ open class TextView: UITextView, ViewProtocol, Errorable { } else { attributedText = nil } + placeholderLabel.textStyle = textStyle + placeholderLabel.isHidden = !text.isEmpty + placeholderLabel.surface = surface } } From d943202e8389b2b666499e753694fa515b46138f Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 10 Jul 2024 16:33:54 -0500 Subject: [PATCH 12/22] updated placeholder logic Signed-off-by: Matt Bruce --- VDS/Components/TextFields/TextArea/TextView.swift | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/VDS/Components/TextFields/TextArea/TextView.swift b/VDS/Components/TextFields/TextArea/TextView.swift index 3f40ae38..3c5dc5ee 100644 --- a/VDS/Components/TextFields/TextArea/TextView.swift +++ b/VDS/Components/TextFields/TextArea/TextView.swift @@ -45,11 +45,7 @@ open class TextView: UITextView, ViewProtocol, Errorable { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - open var placeholder: String? { - didSet { - placeholderLabel.text = placeholder - } - } + open var placeholder: String? { didSet { setNeedsUpdate() } } open var placeholderLabel = Label().with { $0.textColorConfiguration = ViewColorConfiguration().with { @@ -319,8 +315,9 @@ open class TextView: UITextView, ViewProtocol, Errorable { attributedText = nil } placeholderLabel.textStyle = textStyle - placeholderLabel.isHidden = !text.isEmpty placeholderLabel.surface = surface + placeholderLabel.text = placeholder + placeholderLabel.isHidden = !text.isEmpty } } From 46d4098cf2a1c762e81c686670f543260a98aabb Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 10:53:41 -0500 Subject: [PATCH 13/22] fixed issue with showing errorIcon without error message Signed-off-by: Matt Bruce --- .../TextFields/EntryFieldBase.swift | 93 ++++++++++--------- 1 file changed, 50 insertions(+), 43 deletions(-) diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index 5bd50351..89186a3b 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -28,7 +28,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { public required init?(coder: NSCoder) { super.init(coder: coder) } - + //-------------------------------------------------- // MARK: - Enums //-------------------------------------------------- @@ -91,7 +91,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { $0.spacing = VDSLayout.space2X } }() - + /// 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 = View().with { @@ -115,7 +115,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { internal var widthConstraint: NSLayoutConstraint? internal var trailingEqualsConstraint: NSLayoutConstraint? internal var trailingLessThanEqualsConstraint: NSLayoutConstraint? - + //-------------------------------------------------- // MARK: - Configuration Properties //-------------------------------------------------- @@ -133,14 +133,14 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forDisabled: true) $0.setSurfaceColors(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark, forDisabled: false) } - + internal var backgroundColorConfiguration = ControlColorConfiguration().with { $0.setSurfaceColors(VDSFormControlsColor.backgroundOnlight, VDSFormControlsColor.backgroundOndark, forState: .normal) $0.setSurfaceColors(VDSFormControlsColor.backgroundOnlight, VDSFormControlsColor.backgroundOndark, forState: .disabled) $0.setSurfaceColors(VDSColor.feedbackErrorBackgroundOnlight, VDSColor.feedbackErrorBackgroundOndark, forState: .error) $0.setSurfaceColors(VDSColor.feedbackErrorBackgroundOnlight, VDSColor.feedbackErrorBackgroundOndark, forState: [.error, .focused]) } - + internal var borderColorConfiguration = ControlColorConfiguration().with { $0.setSurfaceColors(VDSFormControlsColor.borderOnlight, VDSFormControlsColor.borderOndark, forState: .normal) $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOnlight, forState: .focused) @@ -155,7 +155,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled) $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .error) } - + internal var readOnlyBorderColorConfiguration = ControlColorConfiguration().with { $0.setSurfaceColors(VDSFormControlsColor.borderReadonlyOnlight, VDSFormControlsColor.borderReadonlyOndark, forState: .normal) } @@ -164,7 +164,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { // MARK: - Public Properties //-------------------------------------------------- open var onChangeSubscriber: AnyCancellable? - + open var titleLabel = Label().with { $0.setContentCompressionResistancePriority(.required, for: .vertical) $0.textStyle = .bodySmall @@ -185,7 +185,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { $0.size = .medium $0.isAccessibilityElement = true } - + open var labelText: String? { didSet { setNeedsUpdate() } } open var helperText: String? { didSet { setNeedsUpdate() } } @@ -195,7 +195,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { /// FormFieldValidator open var validator: (any FormFieldValidatorable)? - + /// Override UIControl state to add the .error state if showError is true. open override var state: UIControl.State { get { @@ -214,17 +214,17 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { return state } } - + open var errorText: String? { didSet { setNeedsUpdate() } } - + open var tooltipModel: Tooltip.TooltipModel? { didSet { setNeedsUpdate() } } - + open var transparentBackground: Bool = false { didSet { setNeedsUpdate() } } open var width: CGFloat? { didSet { setNeedsUpdate() } } - + open var inputId: String? { didSet { setNeedsUpdate() } } - + /// The text of this textField. open var value: String? { get { fatalError("must be read from subclass")} @@ -235,21 +235,21 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { open var isRequired: Bool = false { didSet { setNeedsUpdate() } } open var isReadOnly: Bool = false { didSet { setNeedsUpdate() } } - + open var helperTextPlacement: HelperTextPlacement = .bottom { didSet { updateHelperTextPosition() } } - + open var rules = [AnyRule]() - + open var accessibilityHintText: String = "Double tap to open" //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- - + /// 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() @@ -371,7 +371,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { titleLabel.textStyle = .bodySmall errorLabel.textStyle = .bodySmall helperLabel.textStyle = .bodySmall - + labelText = nil helperText = nil showError = false @@ -389,19 +389,19 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { 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 //-------------------------------------------------- @@ -409,21 +409,21 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { open func getFieldContainer() -> UIView { fatalError("Subclass must return the view that contains the field/view the user will interact with.") } - + /// Container for the area in which helper or error text presents. open func getBottomContainer() -> UIView { return bottomContainerStackView } - + open func validate(){ updateRules() validator = FormFieldValidator(field: self, rules: rules) validator?.validate() setNeedsUpdate() } - + open func updateTitleLabel() { - + //update the local vars for the label since we no //long have a model var attributes: [any LabelAttributeModel] = [] @@ -444,36 +444,43 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { if let tooltipModel { attributes.append(TooltipLabelAttribute(surface: surface, model: tooltipModel, presenter: self)) } - + //set the titleLabel titleLabel.text = updatedLabelText titleLabel.attributes = attributes titleLabel.surface = surface titleLabel.isEnabled = isEnabled } - + open func updateErrorLabel(){ - if showError, let errorText { - errorLabel.text = errorText - errorLabel.surface = surface - errorLabel.isEnabled = isEnabled - errorLabel.isHidden = false - statusIcon.name = .error - statusIcon.surface = surface - statusIcon.isHidden = !isEnabled || state.contains(.focused) - } else if hasInternalError, let internalErrorText { - errorLabel.text = internalErrorText - errorLabel.surface = surface - errorLabel.isEnabled = isEnabled - errorLabel.isHidden = false + + /// always show the errorIcon if there is an error + if showError || hasInternalError { statusIcon.name = .error statusIcon.surface = surface statusIcon.isHidden = !isEnabled || state.contains(.focused) } else { statusIcon.isHidden = true - errorLabel.isHidden = true } statusIcon.color = iconColorConfiguration.getColor(self) + + // only show errorLabel if there is a message + var message: String? + if showError, let errorText { + message = errorText + } else if hasInternalError, let internalErrorText { + message = internalErrorText + } + + if let message { + errorLabel.text = message + errorLabel.surface = surface + errorLabel.isEnabled = isEnabled + errorLabel.isHidden = false + } else { + errorLabel.isHidden = true + } + } open func updateHelperLabel(){ From c2e7465c4e6d4089e2ddcb23e8c74df330f20ce0 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 12:39:34 -0500 Subject: [PATCH 14/22] fixed bug in textarea maxLength Signed-off-by: Matt Bruce --- VDS/Components/TextFields/TextArea/TextArea.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/VDS/Components/TextFields/TextArea/TextArea.swift b/VDS/Components/TextFields/TextArea/TextArea.swift index c668a851..f8a9a6b3 100644 --- a/VDS/Components/TextFields/TextArea/TextArea.swift +++ b/VDS/Components/TextFields/TextArea/TextArea.swift @@ -111,7 +111,9 @@ open class TextArea: EntryFieldBase { } didSet { - validate() + if textView.isFirstResponder { + validate() + } } } From fae53c4f5be00bb71430aaf6eb7945c768840f14 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 12:39:46 -0500 Subject: [PATCH 15/22] added variable to shut off internal required Signed-off-by: Matt Bruce --- VDS/Components/TextFields/EntryFieldBase.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index 89186a3b..bc2c31ba 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -186,6 +186,8 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { $0.isAccessibilityElement = true } + open var useRequiredRule: Bool = true { didSet { setNeedsUpdate() } } + open var labelText: String? { didSet { setNeedsUpdate() } } open var helperText: String? { didSet { setNeedsUpdate() } } @@ -522,7 +524,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { //-------------------------------------------------- internal func updateRules() { rules.removeAll() - if self.isRequired { + if isRequired && useRequiredRule { let rule = RequiredRule() if let errorText, !errorText.isEmpty { rule.errorMessage = errorText From 11622b84d3495b5e161ae293966cc79901b834c4 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 14:03:07 -0500 Subject: [PATCH 16/22] updated icon accessibility issue Signed-off-by: Matt Bruce --- VDS/Components/Icon/Icon.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/VDS/Components/Icon/Icon.swift b/VDS/Components/Icon/Icon.swift index 2ac22ac8..140f7b87 100644 --- a/VDS/Components/Icon/Icon.swift +++ b/VDS/Components/Icon/Icon.swift @@ -93,12 +93,15 @@ open class Icon: View { backgroundColor = .clear isAccessibilityElement = true - accessibilityTraits = .image + accessibilityTraits = .none + accessibilityHint = "image" bridge_accessibilityLabelBlock = { [weak self] in guard let self else { return "" } return name?.rawValue ?? "icon" } + + } From 0fdc9c9109e1af9afdb74c54acae94f294a289f9 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 14:03:18 -0500 Subject: [PATCH 17/22] updated inputfield accessibility issue Signed-off-by: Matt Bruce --- VDS/Components/TextFields/InputField/InputField.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index a7e0bbb7..5a8604fc 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -209,11 +209,11 @@ open class InputField: EntryFieldBase { accessibilityLabels.append(text) } - if let formatText = textField.formatText, !formatText.isEmpty { + if let formatText = textField.formatText, !formatText.isEmpty, textField.text.isEmpty { accessibilityLabels.append("format, \(formatText)") } - if let placeholderText = textField.placeholder, !placeholderText.isEmpty { + if let placeholderText = textField.placeholder, !placeholderText.isEmpty, textField.text.isEmpty { accessibilityLabels.append("placeholder, \(placeholderText)") } From 405a84b3598d3e2d552f8b58d372602426f3a416 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 14:03:48 -0500 Subject: [PATCH 18/22] containerview public Signed-off-by: Matt Bruce --- VDS/Components/TextFields/EntryFieldBase.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index bc2c31ba..cf2dffa2 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -91,13 +91,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { $0.spacing = VDSLayout.space2X } }() - - /// 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 = View().with { - $0.isAccessibilityElement = true - } - + /// This is set by a local method. internal var bottomContainerView: UIView! @@ -163,6 +157,12 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- + /// This is the view that will be wrapped with the border for userInteraction. + /// The only subview of this view is the fieldStackView + open var containerView = View().with { + $0.isAccessibilityElement = true + } + open var onChangeSubscriber: AnyCancellable? open var titleLabel = Label().with { From acd688405d011757dbce22031ad158c361156979 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 14:03:56 -0500 Subject: [PATCH 19/22] updates public Signed-off-by: Matt Bruce --- VDS/Components/TextFields/InputField/InputField.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index a7e0bbb7..71ad6b04 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -107,6 +107,9 @@ open class InputField: EntryFieldBase { $0.isAccessibilityElement = false $0.autocorrectionType = .no $0.spellCheckingType = .no + $0.smartQuotesType = .no + $0.smartDashesType = .no + $0.smartInsertDeleteType = .no } /// Color configuration for the textField. From d573002be536075c275d961247c30f3d3505b5e3 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 14:04:07 -0500 Subject: [PATCH 20/22] refactored textfield Signed-off-by: Matt Bruce --- .../TextFields/InputField/TextField.swift | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/VDS/Components/TextFields/InputField/TextField.swift b/VDS/Components/TextFields/InputField/TextField.swift index 05dc30f2..b56f3423 100644 --- a/VDS/Components/TextFields/InputField/TextField.swift +++ b/VDS/Components/TextFields/InputField/TextField.swift @@ -47,6 +47,11 @@ open class TextField: UITextField, ViewProtocol, Errorable { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- + /// Set to true to hide the blinking textField cursor. + open var hideBlinkingCaret = false + open var enableClipboardActions: Bool = true + open var onDidDeleteBackwards: (() -> Void)? + /// Key of whether or not updateView() is called in setNeedsUpdate() open var shouldUpdateView: Bool = true @@ -209,6 +214,23 @@ open class TextField: UITextField, ViewProtocol, Errorable { return success } + open override func caretRect(for position: UITextPosition) -> CGRect { + + if hideBlinkingCaret { + return .zero + } + + let caretRect = super.caretRect(for: position) + return CGRect(origin: caretRect.origin, size: CGSize(width: 1, height: caretRect.height)) + } + + open override func deleteBackward() { + super.deleteBackward() + onDidDeleteBackwards?() + } + + open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { enableClipboardActions } + //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- From cdede872c0a50b73ad6aab6f1d7a2a0ba167ab1d Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 12 Jul 2024 08:36:07 -0500 Subject: [PATCH 21/22] make open to override Signed-off-by: Matt Bruce --- VDS/Components/TextFields/InputField/InputField.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index 7de89b97..e1609f96 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -350,19 +350,19 @@ open class InputField: EntryFieldBase { } extension InputField: UITextFieldDelegate { - public func textFieldDidBeginEditing(_ textField: UITextField) { + open func textFieldDidBeginEditing(_ textField: UITextField) { fieldType.handler().textFieldDidBeginEditing(self, textField: textField) updateContainerView() updateErrorLabel() } - public func textFieldDidEndEditing(_ textField: UITextField) { + open func textFieldDidEndEditing(_ textField: UITextField) { fieldType.handler().textFieldDidEndEditing(self, textField: textField) validate() UIAccessibility.post(notification: .layoutChanged, argument: self.containerView) } - public func textFieldDidChangeSelection(_ textField: UITextField) { + open func textFieldDidChangeSelection(_ textField: UITextField) { fieldType.handler().textFieldDidChangeSelection(self, textField: textField) if fieldType.handler().validateOnChange { validate() @@ -371,7 +371,7 @@ extension InputField: UITextFieldDelegate { setNeedsUpdate() } - public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + open func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { let shouldChange = fieldType.handler().textField(self, textField: textField, shouldChangeCharactersIn: range, replacementString: string) if shouldChange { UIAccessibility.post(notification: .announcement, argument: string) From 15515b6815b5661b15ce516a2e13f17a3e367981 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 12 Jul 2024 08:37:05 -0500 Subject: [PATCH 22/22] added telephone updating Signed-off-by: Matt Bruce --- .../TextFields/InputField/FieldTypes/Telephone.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/VDS/Components/TextFields/InputField/FieldTypes/Telephone.swift b/VDS/Components/TextFields/InputField/FieldTypes/Telephone.swift index bfcd9ef7..89417c19 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/Telephone.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/Telephone.swift @@ -67,7 +67,14 @@ extension InputField { } - internal func formatUSNumber(_ number: String) -> String { + override func textFieldDidEndEditing(_ inputField: InputField, textField: UITextField) { + if let text = inputField.text { + let rawNumber = text.filter { $0.isNumber } + textField.text = formatUSNumber(rawNumber) + } + } + + func formatUSNumber(_ number: String) -> String { // Format the number in the style XXX-XXX-XXXX let areaCodeLength = 3 let centralOfficeCodeLength = 3