Compare commits

..

No commits in common. "develop" and "1.0.71" have entirely different histories.

114 changed files with 1187 additions and 4389 deletions

View File

@ -7,28 +7,14 @@
objects = {
/* Begin PBXBuildFile section */
180636C72C29B0A400C92D86 /* InputStepper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180636C62C29B0A400C92D86 /* InputStepper.swift */; };
180636C92C29B0DF00C92D86 /* InputStepperLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 180636C82C29B0DF00C92D86 /* InputStepperLog.txt */; };
1808BEBC2BA41C3200129230 /* CarouselScrollbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */; };
1818D04D2C9BD2170053E73C /* ModalDialogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1818D04C2C9BD2170053E73C /* ModalDialogViewController.swift */; };
1818D04F2C9BD3F60053E73C /* ModalDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1818D04E2C9BD3F60053E73C /* ModalDialog.swift */; };
1818D0512C9BD4090053E73C /* ModalLaunchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1818D0502C9BD4090053E73C /* ModalLaunchable.swift */; };
1818D0532C9BD47C0053E73C /* ModalModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1818D0522C9BD47C0053E73C /* ModalModel.swift */; };
1832AC572BA0791D008AE476 /* BreadcrumbCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */; };
183B16F32C78CF7C00BA6A10 /* CarouselSlotCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 183B16F22C78CF7C00BA6A10 /* CarouselSlotCell.swift */; };
183B16F72C80B32200BA6A10 /* FootnoteGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 183B16F62C80B32200BA6A10 /* FootnoteGroup.swift */; };
184023452C61E7AD00A412C8 /* PriceLockup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184023442C61E7AD00A412C8 /* PriceLockup.swift */; };
184023472C61E7EC00A412C8 /* PriceLockupChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 184023462C61E7EC00A412C8 /* PriceLockupChangeLog.txt */; };
1842B1DF2BECE28B0021AFCA /* CalendarDateViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1842B1DE2BECE28B0021AFCA /* CalendarDateViewCell.swift */; };
1842B1E12BECE7B70021AFCA /* CalendarHeaderReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1842B1E02BECE7B70021AFCA /* CalendarHeaderReusableView.swift */; };
1842B1E32BECF0A20021AFCA /* CalendarFooterReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1842B1E22BECF0A10021AFCA /* CalendarFooterReusableView.swift */; };
1855EC662BAABF2A002ACAC2 /* BreadcrumbItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1855EC652BAABF2A002ACAC2 /* BreadcrumbItemModel.swift */; };
1859B30F2CBF6FEB0031CD70 /* ListUnordered.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1859B30E2CBF6FDD0031CD70 /* ListUnordered.swift */; };
1859B31B2CBFA0180031CD70 /* ListUnorderedItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1859B31A2CBFA0180031CD70 /* ListUnorderedItemModel.swift */; };
186D13CB2BBA8B1500986B53 /* DropdownSelect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 186D13CA2BBA8B1500986B53 /* DropdownSelect.swift */; };
18792A902B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */; };
18926F5B2C7616A500C55BF6 /* FootnoteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18926F5A2C7616A500C55BF6 /* FootnoteItem.swift */; };
18926F5D2C7616C600C55BF6 /* FootnoteChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18926F5C2C7616C600C55BF6 /* FootnoteChangeLog.txt */; };
18A3F12A2BD9298900498E4A /* Calendar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A3F1292BD9298900498E4A /* Calendar.swift */; };
18A65A022B96E848006602CC /* Breadcrumbs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A65A012B96E848006602CC /* Breadcrumbs.swift */; };
18A65A042B96F050006602CC /* BreadcrumbItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A65A032B96F050006602CC /* BreadcrumbItem.swift */; };
@ -36,8 +22,6 @@
18B42AC62C09D197008D6262 /* CarouselSlotAlignmentModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B42AC52C09D197008D6262 /* CarouselSlotAlignmentModel.swift */; };
18B463A42BBD3C46005C4528 /* DropdownOptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */; };
18B9763F2C11BA4A009271DF /* CarouselPaginationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B9763E2C11BA4A009271DF /* CarouselPaginationModel.swift */; };
18C0F9462C98175900E1DD71 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C0F9452C98175900E1DD71 /* Modal.swift */; };
18C0F94A2C9817C100E1DD71 /* ModalChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18C0F9492C9817C100E1DD71 /* ModalChangeLog.txt */; };
18FEA1AD2BDD137500A56439 /* CalendarIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */; };
18FEA1B52BE0E63600A56439 /* Date+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1B42BE0E63600A56439 /* Date+Extension.swift */; };
445BA07829C07B3D0036A7C5 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 445BA07729C07B3D0036A7C5 /* Notification.swift */; };
@ -71,8 +55,6 @@
EA0D1C412A6AD61C00E5C127 /* Typography+Additional.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0D1C402A6AD61C00E5C127 /* Typography+Additional.swift */; };
EA0D1C452A6AD73000E5C127 /* RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0D1C442A6AD73000E5C127 /* RawRepresentable.swift */; };
EA0FC2C62914222900DF80B4 /* ButtonGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0FC2C52914222900DF80B4 /* ButtonGroup.swift */; };
EA225FC72CA4845100B6B3B3 /* LanguageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA225FC62CA4845100B6B3B3 /* LanguageManager.swift */; };
EA225FC92CA4932900B6B3B3 /* Typography+StyleProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA225FC82CA4932900B6B3B3 /* Typography+StyleProvider.swift */; };
EA297A5529FB07760031ED56 /* TooltipLabelAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA297A5429FB07760031ED56 /* TooltipLabelAttribute.swift */; };
EA297A5729FB0A360031ED56 /* AppleGuidelinesTouchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA297A5629FB0A360031ED56 /* AppleGuidelinesTouchable.swift */; };
EA2DC9B02BE175BA004F58C5 /* RequiredRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA2DC9AF2BE175BA004F58C5 /* RequiredRule.swift */; };
@ -84,6 +66,7 @@
EA33617C288B19210071C351 /* VDSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA33617B288B19210071C351 /* VDSTests.swift */; };
EA33617D288B19210071C351 /* VDS.h in Headers */ = {isa = PBXBuildFile; fileRef = EA33616F288B19200071C351 /* VDS.h */; settings = {ATTRIBUTES = (Public, ); }; };
EA3361A8288B23300071C351 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361A7288B23300071C351 /* UIColor.swift */; };
EA3361AA288B25E40071C351 /* Disabling.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361A9288B25E40071C351 /* Disabling.swift */; };
EA3361AF288B26310071C351 /* FormFieldable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361AE288B26310071C351 /* FormFieldable.swift */; };
EA3361B6288B2A410071C351 /* Control.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361B5288B2A410071C351 /* Control.swift */; };
EA3361B8288B2AAA0071C351 /* ViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361B7288B2AAA0071C351 /* ViewProtocol.swift */; };
@ -115,7 +98,6 @@
EA6642952BCEBF9500D81DC4 /* TextLinkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6642942BCEBF9500D81DC4 /* TextLinkModel.swift */; };
EA6F330E2B911E9000BACAB9 /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6F330D2B911E9000BACAB9 /* TextView.swift */; };
EA78C7962C00CAC200430AD1 /* Groupable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA78C7952C00CAC200430AD1 /* Groupable.swift */; };
EA7AE5592C78C7D000107C74 /* ParentViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AE5582C78C7D000107C74 /* ParentViewProtocol.swift */; };
EA81410B2A0E8E3C004F60D2 /* ButtonIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA81410A2A0E8E3C004F60D2 /* ButtonIcon.swift */; };
EA8141102A127066004F60D2 /* UIColor+VDSColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA81410F2A127066004F60D2 /* UIColor+VDSColor.swift */; };
EA89200428AECF4B006B9984 /* UITextField+Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89200328AECF4B006B9984 /* UITextField+Publisher.swift */; };
@ -225,33 +207,18 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
180636C62C29B0A400C92D86 /* InputStepper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputStepper.swift; sourceTree = "<group>"; };
180636C82C29B0DF00C92D86 /* InputStepperLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = InputStepperLog.txt; sourceTree = "<group>"; };
1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselScrollbar.swift; sourceTree = "<group>"; };
1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CarouselScrollbarChangeLog.txt; sourceTree = "<group>"; };
1818D04C2C9BD2170053E73C /* ModalDialogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalDialogViewController.swift; sourceTree = "<group>"; };
1818D04E2C9BD3F60053E73C /* ModalDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalDialog.swift; sourceTree = "<group>"; };
1818D0502C9BD4090053E73C /* ModalLaunchable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalLaunchable.swift; sourceTree = "<group>"; };
1818D0522C9BD47C0053E73C /* ModalModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalModel.swift; sourceTree = "<group>"; };
1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbCellItem.swift; sourceTree = "<group>"; };
183B16F22C78CF7C00BA6A10 /* CarouselSlotCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselSlotCell.swift; sourceTree = "<group>"; };
183B16F62C80B32200BA6A10 /* FootnoteGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FootnoteGroup.swift; sourceTree = "<group>"; };
184023442C61E7AD00A412C8 /* PriceLockup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceLockup.swift; sourceTree = "<group>"; };
184023462C61E7EC00A412C8 /* PriceLockupChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = PriceLockupChangeLog.txt; sourceTree = "<group>"; };
1842B1DE2BECE28B0021AFCA /* CalendarDateViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarDateViewCell.swift; sourceTree = "<group>"; };
1842B1E02BECE7B70021AFCA /* CalendarHeaderReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarHeaderReusableView.swift; sourceTree = "<group>"; };
1842B1E22BECF0A10021AFCA /* CalendarFooterReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarFooterReusableView.swift; sourceTree = "<group>"; };
18450CF02BA1B19C009FDF2A /* BreadcrumbsChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = BreadcrumbsChangeLog.txt; sourceTree = "<group>"; };
1855EC652BAABF2A002ACAC2 /* BreadcrumbItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbItemModel.swift; sourceTree = "<group>"; };
1859B30E2CBF6FDD0031CD70 /* ListUnordered.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListUnordered.swift; sourceTree = "<group>"; };
1859B3122CBF70AB0031CD70 /* ListUnordered.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ListUnordered.txt; sourceTree = "<group>"; };
1859B31A2CBFA0180031CD70 /* ListUnorderedItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListUnorderedItemModel.swift; sourceTree = "<group>"; };
186B2A892B88DA7F001AB71F /* TextAreaChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TextAreaChangeLog.txt; sourceTree = "<group>"; };
186D13CA2BBA8B1500986B53 /* DropdownSelect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownSelect.swift; sourceTree = "<group>"; };
186D13CE2BBC36EE00986B53 /* DropdownSelectChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = DropdownSelectChangeLog.txt; sourceTree = "<group>"; };
18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIconBadgeIndicatorModel.swift; sourceTree = "<group>"; };
18926F5A2C7616A500C55BF6 /* FootnoteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FootnoteItem.swift; sourceTree = "<group>"; };
18926F5C2C7616C600C55BF6 /* FootnoteChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = FootnoteChangeLog.txt; sourceTree = "<group>"; };
18A3F1292BD9298900498E4A /* Calendar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Calendar.swift; sourceTree = "<group>"; };
18A65A012B96E848006602CC /* Breadcrumbs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Breadcrumbs.swift; sourceTree = "<group>"; };
18A65A032B96F050006602CC /* BreadcrumbItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbItem.swift; sourceTree = "<group>"; };
@ -261,8 +228,6 @@
18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownOptionModel.swift; sourceTree = "<group>"; };
18B9763E2C11BA4A009271DF /* CarouselPaginationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselPaginationModel.swift; sourceTree = "<group>"; };
18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ButtonIconChangeLog.txt; sourceTree = "<group>"; };
18C0F9452C98175900E1DD71 /* Modal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modal.swift; sourceTree = "<group>"; };
18C0F9492C9817C100E1DD71 /* ModalChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ModalChangeLog.txt; sourceTree = "<group>"; };
18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarIndicatorModel.swift; sourceTree = "<group>"; };
18FEA1B42BE0E63600A56439 /* Date+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extension.swift"; sourceTree = "<group>"; };
18FEA1B82BE1301700A56439 /* CalendarChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CalendarChangeLog.txt; sourceTree = "<group>"; };
@ -277,7 +242,7 @@
44CCF4942C0493A1005C9C5E /* TableChangeLog.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = TableChangeLog.txt; sourceTree = "<group>"; };
5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Useable.swift; sourceTree = "<group>"; };
5FC35BE228D51405004EBEAC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = "<group>"; };
710607942B91A99500F2863F /* TileletChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TileletChangeLog.txt; sourceTree = "<group>"; };
710607942B91A99500F2863F /* TitleletChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TitleletChangeLog.txt; sourceTree = "<group>"; };
7115BD3B2B84C0C200E0A610 /* TileContainerChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TileContainerChangeLog.txt; sourceTree = "<group>"; };
71ACE89B2BA0451200FB6ADC /* PaginationContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationContainer.swift; sourceTree = "<group>"; };
71ACE89D2BA1CC1700FB6ADC /* TiletEyebrowModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TiletEyebrowModel.swift; sourceTree = "<group>"; };
@ -305,8 +270,6 @@
EA0D1C442A6AD73000E5C127 /* RawRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawRepresentable.swift; sourceTree = "<group>"; };
EA0FC2C52914222900DF80B4 /* ButtonGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroup.swift; sourceTree = "<group>"; };
EA21C5DA2B600EDD00CFC139 /* VDSTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSTokens.xcframework; path = ../SharedFrameworks/VDSTokens.xcframework; sourceTree = "<group>"; };
EA225FC62CA4845100B6B3B3 /* LanguageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageManager.swift; sourceTree = "<group>"; };
EA225FC82CA4932900B6B3B3 /* Typography+StyleProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Typography+StyleProvider.swift"; sourceTree = "<group>"; };
EA297A5429FB07760031ED56 /* TooltipLabelAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipLabelAttribute.swift; sourceTree = "<group>"; };
EA297A5629FB0A360031ED56 /* AppleGuidelinesTouchable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleGuidelinesTouchable.swift; sourceTree = "<group>"; };
EA2DC9AF2BE175BA004F58C5 /* RequiredRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequiredRule.swift; sourceTree = "<group>"; };
@ -319,6 +282,7 @@
EA336176288B19210071C351 /* VDSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VDSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
EA33617B288B19210071C351 /* VDSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VDSTests.swift; sourceTree = "<group>"; };
EA3361A7288B23300071C351 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = "<group>"; };
EA3361A9288B25E40071C351 /* Disabling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Disabling.swift; sourceTree = "<group>"; };
EA3361AE288B26310071C351 /* FormFieldable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormFieldable.swift; sourceTree = "<group>"; };
EA3361B5288B2A410071C351 /* Control.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Control.swift; sourceTree = "<group>"; };
EA3361B7288B2AAA0071C351 /* ViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewProtocol.swift; sourceTree = "<group>"; };
@ -353,7 +317,6 @@
EA78C7952C00CAC200430AD1 /* Groupable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Groupable.swift; sourceTree = "<group>"; };
EA78C7A12C0E63D200430AD1 /* vds-dev.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "vds-dev.xcconfig"; sourceTree = "<group>"; };
EA78C7A22C0E63DD00430AD1 /* vds.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = vds.xcconfig; sourceTree = "<group>"; };
EA7AE5582C78C7D000107C74 /* ParentViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParentViewProtocol.swift; sourceTree = "<group>"; };
EA81410A2A0E8E3C004F60D2 /* ButtonIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIcon.swift; sourceTree = "<group>"; };
EA81410F2A127066004F60D2 /* UIColor+VDSColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+VDSColor.swift"; sourceTree = "<group>"; };
EA89200328AECF4B006B9984 /* UITextField+Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextField+Publisher.swift"; sourceTree = "<group>"; };
@ -486,15 +449,6 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
180636C52C29B06200C92D86 /* InputStepper */ = {
isa = PBXGroup;
children = (
180636C62C29B0A400C92D86 /* InputStepper.swift */,
180636C82C29B0DF00C92D86 /* InputStepperLog.txt */,
);
path = InputStepper;
sourceTree = "<group>";
};
1808BEBA2BA41B1D00129230 /* CarouselScrollbar */ = {
isa = PBXGroup;
children = (
@ -504,25 +458,6 @@
path = CarouselScrollbar;
sourceTree = "<group>";
};
184023432C61E78D00A412C8 /* PriceLockup */ = {
isa = PBXGroup;
children = (
184023442C61E7AD00A412C8 /* PriceLockup.swift */,
184023462C61E7EC00A412C8 /* PriceLockupChangeLog.txt */,
);
path = PriceLockup;
sourceTree = "<group>";
};
1859B30D2CBF6EF80031CD70 /* ListUnordered */ = {
isa = PBXGroup;
children = (
1859B30E2CBF6FDD0031CD70 /* ListUnordered.swift */,
1859B31A2CBFA0180031CD70 /* ListUnorderedItemModel.swift */,
1859B3122CBF70AB0031CD70 /* ListUnordered.txt */,
);
path = ListUnordered;
sourceTree = "<group>";
};
186D13C92BBA8A3500986B53 /* DropdownSelect */ = {
isa = PBXGroup;
children = (
@ -533,16 +468,6 @@
path = DropdownSelect;
sourceTree = "<group>";
};
18926F592C76168300C55BF6 /* Footnote */ = {
isa = PBXGroup;
children = (
18926F5A2C7616A500C55BF6 /* FootnoteItem.swift */,
183B16F62C80B32200BA6A10 /* FootnoteGroup.swift */,
18926F5C2C7616C600C55BF6 /* FootnoteChangeLog.txt */,
);
path = Footnote;
sourceTree = "<group>";
};
18A3F1202BD8F5DE00498E4A /* Calendar */ = {
isa = PBXGroup;
children = (
@ -573,7 +498,6 @@
isa = PBXGroup;
children = (
18AE874F2C06FDA60075F181 /* Carousel.swift */,
183B16F22C78CF7C00BA6A10 /* CarouselSlotCell.swift */,
18B9763E2C11BA4A009271DF /* CarouselPaginationModel.swift */,
18B42AC52C09D197008D6262 /* CarouselSlotAlignmentModel.swift */,
18AE87532C06FE610075F181 /* CarouselChangeLog.txt */,
@ -581,19 +505,6 @@
path = Carousel;
sourceTree = "<group>";
};
18C0F9442C980CE500E1DD71 /* Modal */ = {
isa = PBXGroup;
children = (
18C0F9452C98175900E1DD71 /* Modal.swift */,
1818D04E2C9BD3F60053E73C /* ModalDialog.swift */,
1818D04C2C9BD2170053E73C /* ModalDialogViewController.swift */,
1818D0502C9BD4090053E73C /* ModalLaunchable.swift */,
1818D0522C9BD47C0053E73C /* ModalModel.swift */,
18C0F9492C9817C100E1DD71 /* ModalChangeLog.txt */,
);
path = Modal;
sourceTree = "<group>";
};
440B84C82BD8E0CE004A732A /* Table */ = {
isa = PBXGroup;
children = (
@ -769,17 +680,12 @@
EAF7F092289985E200B287F5 /* Checkbox */,
EAC58C1F2BF127F000BA39FA /* DatePicker */,
186D13C92BBA8A3500986B53 /* DropdownSelect */,
18926F592C76168300C55BF6 /* Footnote */,
EA985BF3296C609E00F2FF2E /* Icon */,
180636C52C29B06200C92D86 /* InputStepper */,
EA3362412892EF700071C351 /* Label */,
44604AD529CE195300E62B51 /* Line */,
1859B30D2CBF6EF80031CD70 /* ListUnordered */,
EAD0688C2A55F801002E3A2D /* Loader */,
18C0F9442C980CE500E1DD71 /* Modal */,
445BA07629C07ABA0036A7C5 /* Notification */,
71B23C2B2B91FA510027F7D9 /* Pagination */,
184023432C61E78D00A412C8 /* PriceLockup */,
EA89200B28B530F0006B9984 /* RadioBox */,
EAF7F11428A1470D00B287F5 /* RadioButton */,
440B84C82BD8E0CE004A732A /* Table */,
@ -837,6 +743,7 @@
EAF1FE9829D4850E00101452 /* Clickable.swift */,
EAA5EEDF28F49DB3003B3210 /* Colorable.swift */,
EAACB8972B92706F006A3869 /* DefaultValuing.swift */,
EA3361A9288B25E40071C351 /* Disabling.swift */,
71BFA7092B7F70E6000DCE33 /* DropShadowable.swift */,
EAF978202A99035B00C2FEA9 /* Enabling.swift */,
EA5E305929510F8B0082B959 /* EnumSubset.swift */,
@ -845,7 +752,6 @@
EA78C7952C00CAC200430AD1 /* Groupable.swift */,
EA33624628931B050071C351 /* Initable.swift */,
EA471F392A95587500CE9E58 /* LayoutConstraintable.swift */,
EA7AE5582C78C7D000107C74 /* ParentViewProtocol.swift */,
EA985C7C297DAED300F2FF2E /* Primitive.swift */,
EAF7F0A5289B0CE000B287F5 /* Resetable.swift */,
EA3361C8289054C50071C351 /* Surfaceable.swift */,
@ -860,13 +766,12 @@
EA3361B4288B2A360071C351 /* Classes */ = {
isa = PBXGroup;
children = (
EAF2F4752C231EAA007BFEDC /* AccessibilityActionElement.swift */,
EAF2F4882C2A1075007BFEDC /* AlertViewController.swift */,
EA985C1C296CD13600F2FF2E /* BundleManager.swift */,
EAC58C282BF4118C00BA39FA /* ClearPopoverViewController.swift */,
EAF7F0B8289C139800B287F5 /* ColorConfiguration.swift */,
EA225FC62CA4845100B6B3B3 /* LanguageManager.swift */,
EAB5FEF02927F4AA00998C17 /* SelfSizingCollectionView.swift */,
EAF2F4752C231EAA007BFEDC /* AccessibilityActionElement.swift */,
EAF2F4882C2A1075007BFEDC /* AlertViewController.swift */,
);
path = Classes;
sourceTree = "<group>";
@ -979,7 +884,7 @@
EA985BE72968951C00F2FF2E /* TileletTitleModel.swift */,
EA985BE929689B6D00F2FF2E /* TileletSubTitleModel.swift */,
EA985C2C296F03FE00F2FF2E /* TileletIconModels.swift */,
710607942B91A99500F2863F /* TileletChangeLog.txt */,
710607942B91A99500F2863F /* TitleletChangeLog.txt */,
);
path = Tilelet;
sourceTree = "<group>";
@ -1034,7 +939,6 @@
EA0D1C3C2A6AD57600E5C127 /* Typography+Enums.swift */,
EA0D1C382A6AD4DF00E5C127 /* Typography+SpacingConfig.swift */,
EA0D1C3A2A6AD51B00E5C127 /* Typogprahy+Styles.swift */,
EA225FC82CA4932900B6B3B3 /* Typography+StyleProvider.swift */,
);
path = Typography;
sourceTree = "<group>";
@ -1280,14 +1184,10 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
18926F5D2C7616C600C55BF6 /* FootnoteChangeLog.txt in Resources */,
EA3362042891E14D0071C351 /* VerizonNHGeTX-Bold.otf in Resources */,
184023472C61E7EC00A412C8 /* PriceLockupChangeLog.txt in Resources */,
EA3362072891E14D0071C351 /* VerizonNHGeDS-Regular.otf in Resources */,
EA3362062891E14D0071C351 /* VerizonNHGeTX-Regular.otf in Resources */,
EA3362052891E14D0071C351 /* VerizonNHGeDS-Bold.otf in Resources */,
180636C92C29B0DF00C92D86 /* InputStepperLog.txt in Resources */,
18C0F94A2C9817C100E1DD71 /* ModalChangeLog.txt in Resources */,
EAA5EEB928ECD24B003B3210 /* Icons.xcassets in Resources */,
EAA5EEE428F5B855003B3210 /* VerizonNHGDS-Light.otf in Resources */,
);
@ -1331,7 +1231,6 @@
files = (
445BA07829C07B3D0036A7C5 /* Notification.swift in Sources */,
EA5E304C294CBDD00082B959 /* TileContainer.swift in Sources */,
180636C72C29B0A400C92D86 /* InputStepper.swift in Sources */,
EAF7F0A6289B0CE000B287F5 /* Resetable.swift in Sources */,
EA985C2D296F03FE00F2FF2E /* TileletIconModels.swift in Sources */,
EA89200428AECF4B006B9984 /* UITextField+Publisher.swift in Sources */,
@ -1346,7 +1245,6 @@
EA3361C328902D960071C351 /* Toggle.swift in Sources */,
EAF7F0A0289AB7EC00B287F5 /* View.swift in Sources */,
EAC58C232BF2824200BA39FA /* DatePicker.swift in Sources */,
183B16F72C80B32200BA6A10 /* FootnoteGroup.swift in Sources */,
EA89201328B568D8006B9984 /* RadioBoxItem.swift in Sources */,
71FC86E42B9841AC00700965 /* PaginationFlowLayout.swift in Sources */,
EAC9258C2911C9DE00091998 /* InputField.swift in Sources */,
@ -1363,7 +1261,6 @@
EA6642952BCEBF9500D81DC4 /* TextLinkModel.swift in Sources */,
71FC86E22B97483000700965 /* Clamping.swift in Sources */,
EAF7F0B3289B1ADC00B287F5 /* ActionLabelAttribute.swift in Sources */,
1859B30F2CBF6FEB0031CD70 /* ListUnordered.swift in Sources */,
1855EC662BAABF2A002ACAC2 /* BreadcrumbItemModel.swift in Sources */,
EAC925832911B35400091998 /* TextLinkCaret.swift in Sources */,
EA33622E2891EA3C0071C351 /* DispatchQueue+Once.swift in Sources */,
@ -1374,12 +1271,10 @@
EA8141102A127066004F60D2 /* UIColor+VDSColor.swift in Sources */,
EAF7F0AF289B144C00B287F5 /* UnderlineLabelAttribute.swift in Sources */,
EA0D1C412A6AD61C00E5C127 /* Typography+Additional.swift in Sources */,
18C0F9462C98175900E1DD71 /* Modal.swift in Sources */,
EAC925842911C63100091998 /* Colorable.swift in Sources */,
18B463A42BBD3C46005C4528 /* DropdownOptionModel.swift in Sources */,
EAF2F4762C231EAA007BFEDC /* AccessibilityActionElement.swift in Sources */,
EAC58BFD2BE935C300BA39FA /* TitleLockupTextColor.swift in Sources */,
18926F5B2C7616A500C55BF6 /* FootnoteItem.swift in Sources */,
EAACB89A2B927108006A3869 /* Valuing.swift in Sources */,
EAE785312BA0A438009428EA /* UIImage+Helper.swift in Sources */,
EAF193422C134F3400C68D18 /* Table.swift in Sources */,
@ -1392,7 +1287,6 @@
EA596ABD2A16B4EC00300C4B /* Tab.swift in Sources */,
71ACE89E2BA1CC1700FB6ADC /* TiletEyebrowModel.swift in Sources */,
EAF7F11728A1475A00B287F5 /* RadioButtonItem.swift in Sources */,
1818D0512C9BD4090053E73C /* ModalLaunchable.swift in Sources */,
EA985BEE2968A92400F2FF2E /* TitleLockupSubTitleModel.swift in Sources */,
EA2DC9B22BE175E6004F58C5 /* CharacterCountRule.swift in Sources */,
EA985BF22968B5BB00F2FF2E /* TitleLockupTextStyle.swift in Sources */,
@ -1413,7 +1307,6 @@
EAF7F0A4289B017C00B287F5 /* LabelAttributeModel.swift in Sources */,
EA0B18022A9E236900F2D0CD /* SelectorGroupBase.swift in Sources */,
EA5F86D02A1F936100BC83E4 /* TabsContainer.swift in Sources */,
EA7AE5592C78C7D000107C74 /* ParentViewProtocol.swift in Sources */,
EAF7F0B1289B177F00B287F5 /* ColorLabelAttribute.swift in Sources */,
EAC9258F2911C9DE00091998 /* EntryFieldBase.swift in Sources */,
18B9763F2C11BA4A009271DF /* CarouselPaginationModel.swift in Sources */,
@ -1422,7 +1315,6 @@
44BD43B62C04866600644F87 /* TableRowModel.swift in Sources */,
71FC86DA2B96F44C00700965 /* PaginationButton.swift in Sources */,
EABFEB642A26473700C4C106 /* NSAttributedString.swift in Sources */,
EA225FC72CA4845100B6B3B3 /* LanguageManager.swift in Sources */,
EAF7F13328A2A16500B287F5 /* AttachmentLabelAttributeModel.swift in Sources */,
EA0FC2C62914222900DF80B4 /* ButtonGroup.swift in Sources */,
EA89200628B526D6006B9984 /* CheckboxGroup.swift in Sources */,
@ -1431,7 +1323,6 @@
EAD8D2C128BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift in Sources */,
18B42AC62C09D197008D6262 /* CarouselSlotAlignmentModel.swift in Sources */,
71B23C2D2B91FA690027F7D9 /* Pagination.swift in Sources */,
1859B31B2CBFA0180031CD70 /* ListUnorderedItemModel.swift in Sources */,
EA0D1C372A681CCE00E5C127 /* ToggleView.swift in Sources */,
EAF7F0B9289C139800B287F5 /* ColorConfiguration.swift in Sources */,
EA3361BD288B2C760071C351 /* TypeAlias.swift in Sources */,
@ -1446,7 +1337,6 @@
EAF2F4892C2A1075007BFEDC /* AlertViewController.swift in Sources */,
EA0D1C3D2A6AD57600E5C127 /* Typography+Enums.swift in Sources */,
EAF1FE9B29DB1A6000101452 /* Changeable.swift in Sources */,
1818D04F2C9BD3F60053E73C /* ModalDialog.swift in Sources */,
EAC58C0C2BED01D500BA39FA /* Telephone.swift in Sources */,
EAF7F0A2289AFB3900B287F5 /* Errorable.swift in Sources */,
EA8E40912A7D3F6300934ED3 /* UIView+Accessibility.swift in Sources */,
@ -1461,7 +1351,6 @@
44604AD729CE196600E62B51 /* Line.swift in Sources */,
1808BEBC2BA41C3200129230 /* CarouselScrollbar.swift in Sources */,
EAF978212A99035B00C2FEA9 /* Enabling.swift in Sources */,
EA225FC92CA4932900B6B3B3 /* Typography+StyleProvider.swift in Sources */,
EAC58C062BED000200BA39FA /* CreditCard.swift in Sources */,
EA5E3058295105A40082B959 /* Tilelet.swift in Sources */,
186D13CB2BBA8B1500986B53 /* DropdownSelect.swift in Sources */,
@ -1479,7 +1368,6 @@
EA985BF02968A93600F2FF2E /* TitleLockupEyebrowModel.swift in Sources */,
EA5E30532950DDA60082B959 /* TitleLockup.swift in Sources */,
EAD062B02A3B873E0015965D /* BadgeIndicator.swift in Sources */,
183B16F32C78CF7C00BA6A10 /* CarouselSlotCell.swift in Sources */,
44A952DD2BE3DA820009F874 /* TableFlowLayout.swift in Sources */,
EAA5EEB528ECBFB4003B3210 /* ImageLabelAttribute.swift in Sources */,
18792A902B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift in Sources */,
@ -1487,6 +1375,7 @@
EAB5FF0129424ACB00998C17 /* UIControl.swift in Sources */,
EA985BF5296C60C000F2FF2E /* Icon.swift in Sources */,
1842B1E32BECF0A20021AFCA /* CalendarFooterReusableView.swift in Sources */,
EA3361AA288B25E40071C351 /* Disabling.swift in Sources */,
EA3361B6288B2A410071C351 /* Control.swift in Sources */,
EAC58C122BED0DDD00BA39FA /* Text.swift in Sources */,
5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */,
@ -1499,16 +1388,13 @@
EAB5FED429267EB300998C17 /* UIView+NSLayoutConstraint.swift in Sources */,
EAB2376829E9992800AABE9A /* TooltipAlertViewController.swift in Sources */,
EA33623E2892EE950071C351 /* UIDevice.swift in Sources */,
1818D04D2C9BD2170053E73C /* ModalDialogViewController.swift in Sources */,
EA985C692971B90B00F2FF2E /* IconSize.swift in Sources */,
71FC86E02B973AE500700965 /* DropShadowConfiguration.swift in Sources */,
EA3362302891EB4A0071C351 /* Font.swift in Sources */,
EAC58C0E2BED021600BA39FA /* Password.swift in Sources */,
EAF7F0AD289B142900B287F5 /* StrikeThroughLabelAttribute.swift in Sources */,
EAB5FEF12927F4AA00998C17 /* SelfSizingCollectionView.swift in Sources */,
184023452C61E7AD00A412C8 /* PriceLockup.swift in Sources */,
EA3361B8288B2AAA0071C351 /* ViewProtocol.swift in Sources */,
1818D0532C9BD47C0053E73C /* ModalModel.swift in Sources */,
EA3361A8288B23300071C351 /* UIColor.swift in Sources */,
EA2DC9B42BE2C6FE004F58C5 /* TextField.swift in Sources */,
EAC58C182BED0E2300BA39FA /* SecurityCode.swift in Sources */,
@ -1671,7 +1557,7 @@
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 75;
CURRENT_PROJECT_VERSION = 71;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
@ -1709,7 +1595,7 @@
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 75;
CURRENT_PROJECT_VERSION = 71;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;

View File

@ -77,30 +77,20 @@ open class Control: UIControl, ViewProtocol, UserInfoable, Clickable {
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
private func initialSetup() {
open func initialSetup() {
if !initialSetupPerformed {
initialSetupPerformed = true
shouldUpdateView = false
setup()
setDefaults()
shouldUpdateView = true
setNeedsUpdate()
}
}
open func setup() {
backgroundColor = .clear
translatesAutoresizingMaskIntoConstraints = false
insetsLayoutMarginsFromSafeArea = false
}
open func setDefaults() {
backgroundColor = .clear
surface = .light
isEnabled = true
onClick = nil
userInfo.removeAll()
}
open func updateView() { }
open func updateAccessibility() {
@ -117,12 +107,13 @@ open class Control: UIControl, ViewProtocol, UserInfoable, Clickable {
}
}
open func reset() {
shouldUpdateView = false
setDefaults()
shouldUpdateView = true
setNeedsUpdate()
backgroundColor = .clear
surface = .light
isEnabled = true
onClick = nil
userInfo.removeAll()
}
//--------------------------------------------------

View File

@ -28,9 +28,7 @@ public protocol SelectorControlable: Control, Changeable {
}
/// Base Class used to build out a Selector control.
@objc(VDSSelectorBase)
open class SelectorBase: Control, SelectorControlable, ParentViewProtocol {
open class SelectorBase: Control, SelectorControlable {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
@ -49,8 +47,6 @@ open class SelectorBase: Control, SelectorControlable, ParentViewProtocol {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var children: [any ViewProtocol] { [selectorView] }
open var onChangeSubscriber: AnyCancellable?
open var size = CGSize(width: 20, height: 20) { didSet { setNeedsUpdate() } }
@ -86,6 +82,9 @@ open class SelectorBase: Control, SelectorControlable, ParentViewProtocol {
open var selectorColorConfiguration = ControlColorConfiguration() { didSet { setNeedsUpdate() } }
/// The natural size for the receiving view, considering only properties of the view itself.
open override var intrinsicContentSize: CGSize { size }
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
@ -99,35 +98,13 @@ open class SelectorBase: Control, SelectorControlable, ParentViewProtocol {
//--------------------------------------------------
// 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()
isAccessibilityElement = true
accessibilityTraits = .button
let layoutGuide = UILayoutGuide()
addLayoutGuide(layoutGuide)
layoutGuide
.pinTop(0)
.pinLeading(0)
.pinTrailing(0, .defaultHigh)
.pinBottom(0, .defaultHigh)
.width(size.width)
.height(size.height)
}
open override func setDefaults() {
super.setDefaults()
showError = false
/// Executed on initialization for this View.
open override func initialSetup() {
super.initialSetup()
onClick = { control in
control.toggle()
}
onChange = nil
bridge_accessibilityLabelBlock = { [weak self] in
guard let self else { return "" }
return "\(Self.self)\(showError ? ", error" : "")"
@ -139,6 +116,14 @@ open class SelectorBase: Control, SelectorControlable, ParentViewProtocol {
}
}
/// 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
}
open override func updateView() {
super.updateView()
setNeedsLayout()

View File

@ -39,7 +39,7 @@ extension SelectorGroupSingleSelect {
}
/// Base Class used for any Grouped Form Control of a Selector Type.
open class SelectorGroupBase<SelectorItemType: Groupable>: Control, SelectorGroup, Changeable, ParentViewProtocol {
open class SelectorGroupBase<SelectorItemType: Groupable>: Control, SelectorGroup, Changeable {
//--------------------------------------------------
// MARK: - Private Properties
@ -57,8 +57,6 @@ open class SelectorGroupBase<SelectorItemType: Groupable>: Control, SelectorGrou
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var children: [any ViewProtocol] { items }
/// Array of the HandlerType registered.
/// Array of HandlerType that the user will have the ability to select from.
open var items: [SelectorItemType] = [] {
@ -67,30 +65,25 @@ open class SelectorGroupBase<SelectorItemType: Groupable>: Control, SelectorGrou
}
didSet {
setItemsActions()
for selector in items {
selector.onClick = { [weak self] handler in
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)
}
}
}
open var onChangeSubscriber: AnyCancellable?
private func setItemsActions() {
for selector in items {
selector.onClick = { [weak self] handler in
self?.didSelect(handler)
self?.setNeedsUpdate()
}
selector.accessibilityAction = { [weak self] handler in
guard let handler = handler as? SelectorItemType else { return }
self?.didSelect(handler)
self?.setNeedsUpdate()
}
}
}
/// Whether the Control is enabled or not.
override open var isEnabled: Bool {
didSet {
@ -122,12 +115,6 @@ open class SelectorGroupBase<SelectorItemType: Groupable>: Control, SelectorGrou
.pinBottom(0, .defaultHigh)
}
open override func setDefaults() {
super.setDefaults()
onChange = nil
items = []
}
/// Handler for the Group to override on a select event.
/// - Parameter selectedControl: Selected Control the user interacted.
open func didSelect(_ selectedControl: SelectorItemType) {
@ -140,6 +127,13 @@ open class SelectorGroupBase<SelectorItemType: Groupable>: Control, SelectorGrou
self?.sendActions(for: .valueChanged)
}
}
/// Resets to default settings.
open override func reset() {
super.reset()
onChange = nil
items.forEach{ $0.reset() }
}
}

View File

@ -11,7 +11,7 @@ import Combine
import VDSCoreTokens
/// Base Class used to build out a SelectorControlable control.
open class SelectorItemBase<Selector: SelectorBase>: Control, Errorable, Changeable, Groupable, ParentViewProtocol {
open class SelectorItemBase<Selector: SelectorBase>: Control, Errorable, Changeable, Groupable {
//--------------------------------------------------
// MARK: - Initializers
@ -43,14 +43,13 @@ open class SelectorItemBase<Selector: SelectorBase>: Control, Errorable, Changea
private var mainStackView = UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.alignment = .fill
$0.alignment = .top
$0.axis = .vertical
}
private var selectorStackView = UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.alignment = .fill
$0.alignment = .top
$0.axis = .horizontal
}
@ -62,28 +61,23 @@ open class SelectorItemBase<Selector: SelectorBase>: Control, Errorable, Changea
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var children: [any ViewProtocol] { [label, childLabel, errorLabel, selectorView] }
open var onChangeSubscriber: AnyCancellable?
open var onChangeSubscriber: AnyCancellable?
/// Label used to render labelText.
open var label = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
$0.setContentCompressionResistancePriority(.required, for: .horizontal)
$0.textStyle = .boldBodyLarge
}
/// Label used to render childText.
open var childLabel = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
$0.setContentCompressionResistancePriority(.required, for: .horizontal)
$0.textStyle = .bodyLarge
}
/// Label used to render errorText.
open var errorLabel = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
$0.setContentCompressionResistancePriority(.required, for: .horizontal)
$0.textStyle = .bodyMedium
}
@ -163,38 +157,9 @@ open class SelectorItemBase<Selector: SelectorBase>: Control, Errorable, Changea
//--------------------------------------------------
// 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()
selectorView.isAccessibilityElement = true
isAccessibilityElement = false
addSubview(mainStackView)
//wrap the selectorView in a view that won't stretch it
//do this by not pinning the bottom
let selectorViewWrapper = UIView().with { $0.translatesAutoresizingMaskIntoConstraints = false }
selectorViewWrapper.addSubview(selectorView)
selectorView.pinTop().pinLeading().pinTrailing().pinBottomLessThanOrEqualTo()
mainStackView.isUserInteractionEnabled = false
mainStackView.addArrangedSubview(selectorStackView)
mainStackView.addArrangedSubview(errorLabel)
selectorStackView.addArrangedSubview(selectorViewWrapper)
selectorStackView.addArrangedSubview(selectorLabelStackView)
selectorLabelStackView.addArrangedSubview(label)
selectorLabelStackView.addArrangedSubview(childLabel)
mainStackView
.pinTop()
.pinLeading()
.pinTrailing()
.pinBottom(0, .defaultHigh)
}
open override func setDefaults() {
super.setDefaults()
/// Executed on initialization for this View.
open override func initialSetup() {
super.initialSetup()
onClick = { [weak self] control in
guard let self, isEnabled else { return }
toggle()
@ -238,23 +203,29 @@ open class SelectorItemBase<Selector: SelectorBase>: Control, Errorable, Changea
guard let self else { return "" }
return !isEnabled ? "" : "Double tap to activate."
}
}
label.textStyle = .boldBodyLarge
childLabel.textStyle = .bodyLarge
errorLabel.textStyle = .bodyMedium
/// 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()
labelText = nil
labelTextAttributes = nil
labelAttributedText = nil
childText = nil
childTextAttributes = nil
childAttributedText = nil
showError = false
errorText = nil
inputId = nil
isSelected = false
selectorView.isAccessibilityElement = true
isAccessibilityElement = false
addSubview(mainStackView)
onChange = nil
mainStackView.isUserInteractionEnabled = false
mainStackView.addArrangedSubview(selectorStackView)
mainStackView.addArrangedSubview(errorLabel)
selectorStackView.addArrangedSubview(selectorView)
selectorStackView.addArrangedSubview(selectorLabelStackView)
selectorLabelStackView.addArrangedSubview(label)
selectorLabelStackView.addArrangedSubview(childLabel)
mainStackView
.pinTop()
.pinLeading()
.pinTrailing()
.pinBottom(0, .defaultHigh)
}
/// Used to make changes to the View based off a change events or from local properties.
@ -310,10 +281,30 @@ open class SelectorItemBase<Selector: SelectorBase>: Control, Errorable, Changea
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
label.reset()
childLabel.reset()
errorLabel.reset()
super.reset()
label.textStyle = .boldBodyLarge
childLabel.textStyle = .bodyLarge
errorLabel.textStyle = .bodyMedium
labelText = nil
labelTextAttributes = nil
labelAttributedText = nil
childText = nil
childTextAttributes = nil
childAttributedText = nil
showError = false
errorText = nil
inputId = nil
isSelected = false
onChange = nil
shouldUpdateView = true
setNeedsUpdate()
}
//--------------------------------------------------

View File

@ -11,7 +11,7 @@ import Combine
/// Base Class used to build Views.
@objc(VDSView)
open class View: UIView, ViewProtocol, UserInfoable, Clickable {
open class View: UIView, ViewProtocol, UserInfoable {
//--------------------------------------------------
// MARK: - Initializers
@ -36,7 +36,6 @@ open class View: UIView, ViewProtocol, UserInfoable, Clickable {
//--------------------------------------------------
open var subscribers = Set<AnyCancellable>()
open var onClickSubscriber: AnyCancellable?
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
@ -57,30 +56,20 @@ open class View: UIView, ViewProtocol, UserInfoable, Clickable {
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
private func initialSetup() {
open func initialSetup() {
if !initialSetupPerformed {
initialSetupPerformed = true
shouldUpdateView = false
setup()
setDefaults()
shouldUpdateView = true
setNeedsUpdate()
}
}
open func setup() {
backgroundColor = .clear
translatesAutoresizingMaskIntoConstraints = false
insetsLayoutMarginsFromSafeArea = false
}
open func setDefaults() {
backgroundColor = .clear
surface = .light
isEnabled = true
onClick = nil
userInfo.removeAll()
}
open func updateView() { }
open func updateAccessibility() {
@ -92,10 +81,9 @@ open class View: UIView, ViewProtocol, UserInfoable, Clickable {
}
open func reset() {
shouldUpdateView = false
setDefaults()
shouldUpdateView = true
setNeedsUpdate()
backgroundColor = .clear
surface = .light
isEnabled = true
}
open override func layoutSubviews() {

View File

@ -10,7 +10,6 @@ import UIKit
import Combine
import VDSCoreTokens
@objc(VDSAlertViewController)
open class AlertViewController: UIViewController, Surfaceable {
/// Set of Subscribers for any Publishers for this Control.

View File

@ -8,7 +8,6 @@
import Foundation
import UIKit
@objc(VDSClearPopoverViewController)
open class ClearPopoverViewController: UIViewController, UIPopoverPresentationControllerDelegate {
/// The view to be inserted inside the popover

View File

@ -1,63 +0,0 @@
//
// LanguageManager.swift
// VDS
//
// Created by Matt Bruce on 9/25/24.
//
import Foundation
// Language Manager to control the current language setting
public class LanguageManager {
// Enum to define supported languages
public enum SupportedLanguage: String, CustomStringConvertible {
case english = "en"
case spanish = "es"
public var description: String { self == .english ? "English" : "Spanish"}
}
// Private static variable to hold the in-memory current language
private static var _currentLanguage: SupportedLanguage? {
didSet {
TextStyle.Provider.updateCurrentStyles()
}
}
// Static property to manage the current language setting
public static var currentLanguage: SupportedLanguage {
get {
// Check if there is an in-memory language setting
guard let _currentLanguage else {
// set default
var deviceCurrentLanguage: SupportedLanguage = .english
// Check device's preferred language
let deviceLanguage = Locale.preferredLanguages.first ?? "en"
if deviceLanguage.starts(with: "es") {
deviceCurrentLanguage = .spanish
}
_currentLanguage = deviceCurrentLanguage
return deviceCurrentLanguage
}
return _currentLanguage
}
set {
// Set the in-memory language
_currentLanguage = newValue
}
}
// Method to set language using a language code string
public static func setLanguage(with code: String) {
if code.starts(with: "es") {
_currentLanguage = .spanish
} else {
_currentLanguage = .english
}
}
}

View File

@ -16,7 +16,7 @@ import Combine
/// you need to ensure that one of these Horizontal Contraints is not constraint of "equatTo". If you are to pin the left/right edges
/// to its parent this object will stretch to the parent's width.
@objc(VDSBadge)
open class Badge: View, ParentViewProtocol {
open class Badge: View {
//--------------------------------------------------
// MARK: - Initializers
@ -37,23 +37,13 @@ open class Badge: View, ParentViewProtocol {
// MARK: - Enums
//--------------------------------------------------
/// Enum used to describe the primary color for the view.
public enum FillColor: Equatable {
public enum FillColor: String, CaseIterable {
case red, yellow, green, orange, blue, black, white
case token(UIColor.VDSColor)
case custom(UIColor)
private var reflectedValue: String { String(reflecting: self) }
public static func == (lhs: Self, rhs: Self) -> Bool {
lhs.reflectedValue == rhs.reflectedValue
}
}
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var children: [any ViewProtocol] { [label] }
/// Label used to render text
open var label = Label().with {
$0.isAccessibilityElement = false
@ -68,12 +58,10 @@ open class Badge: View, ParentViewProtocol {
/// This will render the badges fill color based on the available options.
/// When used in conjunction with the surface prop, this fill color will change its tint automatically based on a light or dark surface.
open var fillColor: FillColor = .red { didSet { setNeedsUpdate() }}
/// The text that will be shown in the label.
open var text: String = "" { didSet { setNeedsUpdate() }}
open var textColor: TextColor? { didSet { setNeedsUpdate() }}
/// When applied, this property takes a px value that will restrict the width at that point.
open var maxWidth: CGFloat? { didSet { setNeedsUpdate() }}
@ -102,91 +90,38 @@ open class Badge: View, ParentViewProtocol {
right: VDSLayout.space1X)
/// ColorConfiguration that is mapped to the 'fillColor' for the surface.
private var backgroundColorConfiguration = SurfaceColorConfiguration()
private var backgroundColorConfiguration: AnyColorable = {
let config = KeyedColorConfiguration<Badge, FillColor>(keyPath: \.fillColor)
config.setSurfaceColors(VDSColor.badgesBackgroundRedOnlight, VDSColor.badgesBackgroundRedOndark, forKey: .red)
config.setSurfaceColors(VDSColor.badgesBackgroundYellowOnlight, VDSColor.badgesBackgroundYellowOndark, forKey: .yellow)
config.setSurfaceColors(VDSColor.badgesBackgroundGreenOnlight, VDSColor.badgesBackgroundGreenOndark, forKey: .green)
config.setSurfaceColors(VDSColor.badgesBackgroundOrangeOnlight, VDSColor.badgesBackgroundOrangeOndark, forKey: .orange)
config.setSurfaceColors(VDSColor.badgesBackgroundBlueOnlight, VDSColor.badgesBackgroundBlueOndark, forKey: .blue)
config.setSurfaceColors(VDSColor.badgesBackgroundBlackOnlight, VDSColor.badgesBackgroundBlackOndark, forKey: .black)
config.setSurfaceColors(VDSColor.badgesBackgroundWhiteOnlight, VDSColor.badgesBackgroundWhiteOndark, forKey: .white)
return config.eraseToAnyColorable()
}()
/// ColorConfiguration for the Text.
private var textColorConfiguration = ViewColorConfiguration()
/// Updates the textColorConfiguration based on the fillColor.
public func updateColorConfig() {
var config = backgroundColorConfiguration
switch fillColor {
case .red:
config.lightColor = VDSColor.badgesBackgroundRedOnlight
config.darkColor = VDSColor.badgesBackgroundRedOndark
case .yellow:
config.lightColor = VDSColor.badgesBackgroundYellowOnlight
config.darkColor = VDSColor.badgesBackgroundYellowOndark
case .green:
config.lightColor = VDSColor.badgesBackgroundGreenOnlight
config.darkColor = VDSColor.badgesBackgroundGreenOndark
case .orange:
config.lightColor = VDSColor.badgesBackgroundOrangeOnlight
config.darkColor = VDSColor.badgesBackgroundOrangeOndark
case .blue:
config.lightColor = VDSColor.badgesBackgroundBlueOnlight
config.darkColor = VDSColor.badgesBackgroundBlueOndark
case .black:
config.lightColor = VDSColor.badgesBackgroundBlackOnlight
config.darkColor = VDSColor.badgesBackgroundBlackOndark
case .white:
config.lightColor = VDSColor.badgesBackgroundWhiteOnlight
config.darkColor = VDSColor.badgesBackgroundWhiteOndark
case .token(let color):
config.lightColor = color.uiColor
config.darkColor = color.uiColor
case .custom(let color):
config.lightColor = color
config.darkColor = color
}
public func updateTextColorConfig() {
textColorConfiguration.reset()
func update(for color: UIColor) {
if let configuration = textColor?.configuration {
textColorConfiguration = configuration
} else {
if color.isDark() {
textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOndark, forDisabled: false)
textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOndark, forDisabled: true)
} else {
textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOnlight, forDisabled: false)
textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOnlight, forDisabled: true)
}
}
}
if let textColor {
switch textColor {
case .token(let color):
textColorConfiguration.setSurfaceColors(color.uiColor, color.uiColor, forDisabled: false)
textColorConfiguration.setSurfaceColors(color.uiColor, color.uiColor, forDisabled: true)
case .custom(let color):
textColorConfiguration.setSurfaceColors(color, color, forDisabled: false)
textColorConfiguration.setSurfaceColors(color, color, forDisabled: true)
}
} else {
switch fillColor {
case .red, .black:
textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOndark, forDisabled: false)
textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOndark, forDisabled: true)
case .yellow, .white:
textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOnlight, forDisabled: false)
textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOnlight, forDisabled: true)
case .orange, .green, .blue:
textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOnlight, forDisabled: false)
textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOnlight, forDisabled: true)
case .token(let color):
update(for: color.uiColor)
case .custom(let color):
update(for: color)
}
switch fillColor {
case .red, .black:
textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOndark, forDisabled: false)
textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOndark, forDisabled: true)
case .yellow, .white:
textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOnlight, forDisabled: false)
textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOnlight, forDisabled: true)
case .orange, .green, .blue:
textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOnlight, forDisabled: false)
textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOnlight, forDisabled: true)
}
}
@ -213,35 +148,32 @@ open class Badge: View, ParentViewProtocol {
maxWidthConstraint = label.widthLessThanEqualTo(constant: 0).with { $0.isActive = false }
clipsToBounds = true
}
open override func setDefaults() {
super.setDefaults()
bridge_accessibilityLabelBlock = { [weak self] in
guard let self else { return "" }
return text
}
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
label.reset()
label.lineBreakMode = .byTruncatingTail
label.textStyle = .boldBodySmall
fillColor = .red
text = ""
maxWidth = nil
numberOfLines = 1
}
/// Resets to default settings.
open override func reset() {
label.reset()
super.reset()
shouldUpdateView = true
setNeedsUpdate()
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()
updateColorConfig()
updateTextColorConfig()
updateMaxWidth()
backgroundColor = backgroundColorConfiguration.getColor(self)
@ -253,29 +185,3 @@ open class Badge: View, ParentViewProtocol {
label.isEnabled = isEnabled
}
}
extension Badge{
public enum TextColor: Equatable {
case token(UIColor.VDSColor)
case custom(UIColor)
private var reflectedValue: String { String(reflecting: self) }
public static func == (lhs: Self, rhs: Self) -> Bool {
lhs.reflectedValue == rhs.reflectedValue
}
public var configuration: ViewColorConfiguration {
let config = ViewColorConfiguration()
switch self {
case .token(let color):
config.setSurfaceColors(color.uiColor, color.uiColor, forDisabled: true)
config.setSurfaceColors(color.uiColor, color.uiColor, forDisabled: false)
case .custom(let color):
config.setSurfaceColors(color, color, forDisabled: true)
config.setSurfaceColors(color, color, forDisabled: false)
}
return config
}
}
}

View File

@ -12,7 +12,7 @@ import Combine
/// A badge indicator is a visual label used to convey status or highlight supplemental information.
@objc(VDSBadgeIndicator)
open class BadgeIndicator: View, ParentViewProtocol {
open class BadgeIndicator: View {
//--------------------------------------------------
// MARK: - Initializers
@ -134,8 +134,6 @@ open class BadgeIndicator: View, ParentViewProtocol {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var children: [any ViewProtocol] { [label, badgeView] }
/// Label used for the numeric kind.
open var label = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
@ -294,28 +292,6 @@ open class BadgeIndicator: View, ParentViewProtocol {
label.centerYAnchor.constraint(equalTo: badgeView.centerYAnchor).isActive = true
labelContraints.isActive = true
}
open override func setDefaults() {
super.setDefaults()
label.lineBreakMode = .byTruncatingTail
label.textAlignment = .center
fillColor = .red
number = nil
kind = .simple
leadingCharacter = nil
trailingText = nil
size = .xxlarge
dotSize = nil
verticalPadding = nil
horizontalPadding = nil
hideDot = false
hideBorder = false
width = nil
height = nil
accessibilityText = nil
maximumDigits = .two
bridge_accessibilityLabelBlock = { [weak self] in
guard let self else { return "" }
if let accessibilityText {
@ -330,8 +306,15 @@ open class BadgeIndicator: View, ParentViewProtocol {
/// Resets to default settings.
open override func reset() {
label.reset()
super.reset()
shouldUpdateView = false
label.reset()
label.lineBreakMode = .byTruncatingTail
label.textAlignment = .center
fillColor = .red
number = nil
shouldUpdateView = true
setNeedsUpdate()
}
/// Used to make changes to the View based off a change events or from local properties.

View File

@ -71,15 +71,17 @@ open class BreadcrumbItem: ButtonBase {
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
open override func setDefaults() {
super.setDefaults()
isAccessibilityElement = true
accessibilityTraits = .link
/// 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()
titleLabel?.numberOfLines = 0
titleLabel?.lineBreakMode = .byWordWrapping
contentHorizontalAlignment = .left
isAccessibilityElement = true
accessibilityTraits = .link
bridge_accessibilityHintBlock = { [weak self] in
guard let self else { return "" }
return !isEnabled ? "" : "Double tap to open."
@ -128,4 +130,17 @@ open class BreadcrumbItem: ButtonBase {
}
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
text = nil
accessibilityCustomActions = []
isAccessibilityElement = true
accessibilityTraits = .button
shouldUpdateView = true
setNeedsUpdate()
}
}

View File

@ -8,7 +8,8 @@
import Foundation
extension Breadcrumbs {
public struct BreadcrumbItemModel: Equatable {
public struct BreadcrumbItemModel {
///Text that goes in the breadcrumb item
public var text: String
@ -23,10 +24,5 @@ extension Breadcrumbs {
self.selected = selected
self.onClick = onClick
}
public static func == (lhs: Breadcrumbs.BreadcrumbItemModel, rhs: Breadcrumbs.BreadcrumbItemModel) -> Bool {
lhs.text == rhs.text
&& lhs.selected == rhs.selected
}
}
}

View File

@ -14,13 +14,11 @@ import Combine
/// It contains Breadcrumb Item Default, Breadcrumb Item Selected, Separator.
/// Breadcrumbs are secondary navigation that use a hierarchy of internal links to tell customers where they are in an experience. Each breadcrumb links to its respective page, except for that of current page.
@objc(VDSBreadcrumbs)
open class Breadcrumbs: View, ParentViewProtocol {
open class Breadcrumbs: View {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var children: [any ViewProtocol] { breadcrumbs }
/// Array of ``BreadcrumbItem`` views for the Breadcrumbs.
open var breadcrumbs: [BreadcrumbItem] = [] { didSet { setNeedsUpdate() } }
@ -110,19 +108,21 @@ open class Breadcrumbs: View, ParentViewProtocol {
// MARK: - Overrides
//--------------------------------------------------
/// Executed on initialization for this View.
open override func setup() {
super.setup()
open override func initialSetup() {
super.initialSetup()
containerView.addSubview(collectionView)
collectionView.pinToSuperView()
addSubview(containerView)
containerView.pinToSuperView()
}
open override func setDefaults() {
super.setDefaults()
breadcrumbs = []
breadcrumbModels = []
isEnabled = true
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
breadcrumbs.forEach { $0.reset() }
shouldUpdateView = true
setNeedsUpdate()
}
/// Used to make changes to the View based off a change events or from local properties.

View File

@ -222,12 +222,16 @@ open class Button: ButtonBase, Useable {
isAccessibilityElement = true
accessibilityTraits = .button
}
open override func setDefaults() {
super.setDefaults()
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
use = .primary
width = nil
size = .large
shouldUpdateView = true
setNeedsUpdate()
}
/// Used to make changes to the View based off a change events or from local properties.

View File

@ -96,13 +96,13 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable {
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
private func initialSetup() {
open func initialSetup() {
if !initialSetupPerformed {
initialSetupPerformed = true
shouldUpdateView = false
backgroundColor = .clear
translatesAutoresizingMaskIntoConstraints = false
accessibilityCustomActions = []
setup()
setDefaults()
shouldUpdateView = true
setNeedsUpdate()
}
}
@ -110,19 +110,10 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable {
open func setup() {
translatesAutoresizingMaskIntoConstraints = false
}
open func setDefaults() {
backgroundColor = .clear
accessibilityCustomActions = []
titleLabel?.adjustsFontSizeToFitWidth = false
titleLabel?.lineBreakMode = .byTruncatingTail
titleLabel?.numberOfLines = 1
surface = .light
isEnabled = true
text = nil
onClick = nil
userInfo.removeAll()
}
open func updateView() {
@ -139,7 +130,12 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable {
open func reset() {
shouldUpdateView = false
setDefaults()
surface = .light
isEnabled = true
text = nil
accessibilityCustomActions = []
onClick = nil
userInfo.removeAll()
shouldUpdateView = true
setNeedsUpdate()
}

View File

@ -166,18 +166,15 @@ open class ButtonGroup: View {
collectionView.reloadData()
}
open override func setDefaults() {
super.setDefaults()
open override func reset() {
super.reset()
shouldUpdateView = false
rowQuantityPhone = 0
rowQuantityTablet = 0
alignment = .center
childWidth = nil
buttons = []
}
open override func reset() {
buttons.forEach { $0.reset() }
super.reset()
shouldUpdateView = true
setNeedsUpdate()
}
open override func layoutSubviews() {

View File

@ -49,53 +49,56 @@ class ButtonCollectionViewRow {
func layout(for position: ButtonGroup.Alignment, with collectionViewWidth: CGFloat){
var offset = 0.0
let height = rowHeight
attributes.last?.spacing = 0
//filter only the buttons since this is the only
//object we can change the frames for.
let buttonAttributes = attributes.filter{$0.isButton}
if !buttonAttributes.isEmpty {
let buttonCount = CGFloat(buttonAttributes.count)
//check to see if you have buttons and there is a percentage
if let buttonPercentage, hasButtons, buttonPercentage > 0 {
///Calculate the spaces between items in the row
let totalSpacingBetweenAttributes = attributes.reduce(0.0) { $0 + $1.spacing }
//see how much of the rows width is used for
//non-buttons that are BaseButton Subclasses that are not "Button"
let nonButtonSpace = attributes.filter { !$0.isButton }.reduce(0.0) { $0 + $1.frame.width }
//getting available button space since textlinks need their space
let buttonsAvailableSpace = collectionViewWidth - nonButtonSpace - totalSpacingBetweenAttributes
let buttonEqualSpacing = buttonsAvailableSpace / buttonCount
var maxButtonWidth = buttonEqualSpacing //default to equal spacing
var buttonCalculatedPercentage: CGFloat = 0.0
//check to see if you have buttons and there is a percentage
if let buttonPercentage, hasButtons, buttonPercentage > 0 {
buttonCalculatedPercentage = CGFloat(buttonPercentage / 100.0)
let buttonPercentageWidth = buttonCalculatedPercentage * buttonsAvailableSpace
maxButtonWidth = min(max(buttonPercentageWidth, Button.Size.large.minimumWidth), maxButtonWidth)
var usedSpace = 0.0
//get the width for the buttons
for attribute in attributes {
if !attribute.isButton {
usedSpace += attribute.frame.width
}
usedSpace += attribute.spacing
}
let buttonAvailableSpace = collectionViewWidth - usedSpace
let realPercentage = (buttonPercentage / 100)
let buttonWidth = realPercentage * buttonAvailableSpace
// print("buttonPercentage :\(realPercentage)")
// print("collectionView width:\(collectionViewWidth)")
// print("usedSpace width:\(usedSpace)")
// print("button available width:\(buttonAvailableSpace)")
// print("each button width:\(buttonWidth)\n")
// print("minimum widht:\(ButtonSize.large.minimumWidth)")
// test sizing
var testSize = 0.0
var buttonCount = 0.0
for attribute in attributes {
if attribute.isButton {
testSize += buttonWidth
buttonCount += 1
}
}
//resize the buttonAttributes
if maxButtonWidth >= Button.Size.large.minimumWidth {
//if there is enough room for all buttons
if maxButtonWidth * buttonCount <= buttonsAvailableSpace {
for attribute in buttonAttributes {
attribute.frame.size.width = buttonCalculatedPercentage.isZero ? min(attribute.frame.size.width, maxButtonWidth) : maxButtonWidth
if buttonWidth >= Button.Size.large.minimumWidth {
if testSize <= buttonAvailableSpace {
for attribute in attributes {
if attribute.isButton {
attribute.frame.size.width = buttonWidth
}
}
} else {
//if not enough room, give all buttons the same width
for attribute in buttonAttributes {
attribute.frame.size.width = buttonEqualSpacing
let distributedSize = buttonAvailableSpace / buttonCount
for attribute in attributes {
if attribute.isButton {
attribute.frame.size.width = distributedSize
}
}
}
}
}
//update the offset based on position
switch position {
case .left:
break
@ -178,7 +181,7 @@ class ButtonGroupPositionLayout: UICollectionViewLayout {
var rows = [ButtonCollectionViewRow]()
rows.append(ButtonCollectionViewRow())
let collectionViewWidth = collectionView.horizontalPinnedWidth() ?? collectionView.frame.width
let collectionViewWidth = collectionView.frame.width
for item in 0..<totalItems {
@ -197,8 +200,7 @@ class ButtonGroupPositionLayout: UICollectionViewLayout {
// determine if the current button will fit in the row
let rowItemCount = rows.last?.attributes.count ?? 0
if (layoutWidthIterator + itemSize.width) > collectionViewWidth && rowQuantity == 0
|| (rowQuantity > 0 && rowItemCount == rowQuantity) {
if (layoutWidthIterator + itemSize.width) > collectionViewWidth || (rowQuantity > 0 && rowItemCount == rowQuantity) {
// If the current row width (after this item being laid out) is exceeding
// the width of the collection view content, put it in the next line
@ -316,5 +318,3 @@ class ButtonGroupPositionLayout: UICollectionViewLayout {
}
}

View File

@ -90,7 +90,12 @@ open class TextLink: ButtonBase {
open override func setup() {
super.setup()
isAccessibilityElement = true
accessibilityTraits = .link
//left align titleLabel in case this is pinned leading/trailing
//default is always set to center
contentHorizontalAlignment = .left
if let titleLabel {
addSubview(line)
line.pinLeading(titleLabel.leadingAnchor)
@ -100,21 +105,12 @@ open class TextLink: ButtonBase {
lineHeightConstraint = line.height(constant: 1)
lineHeightConstraint?.isActive = true
}
}
open override func setDefaults() {
super.setDefaults()
size = .large
accessibilityTraits = .link
//left align titleLabel in case this is pinned leading/trailing
//default is always set to center
contentHorizontalAlignment = .left
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.
@ -126,5 +122,18 @@ open class TextLink: ButtonBase {
//always call last so the label is rendered
super.updateView()
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
text = nil
size = .large
accessibilityCustomActions = []
isAccessibilityElement = true
accessibilityTraits = .link
shouldUpdateView = true
setNeedsUpdate()
}
}

View File

@ -75,8 +75,10 @@ open class TextLinkCaret: ButtonBase {
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
open override func setDefaults() {
super.setDefaults()
/// 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()
//left align titleLabel in case this is pinned leading/trailing
//default is always set to center
contentHorizontalAlignment = .left
@ -85,12 +87,11 @@ open class TextLinkCaret: ButtonBase {
titleLabel?.numberOfLines = 0
titleLabel?.lineBreakMode = .byWordWrapping
iconPosition = .right
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.
@ -98,7 +99,14 @@ open class TextLinkCaret: ButtonBase {
imageAttribute = CaretLabelAttribute(tintColor: textColor, position: iconPosition)
super.updateView()
}
/// Resets to default settings.
open override func reset() {
super.reset()
iconPosition = .right
text = nil
}
/// The natural size for the receiving view, considering only properties of the view itself.
open override var intrinsicContentSize: CGSize {
guard let titleLabel else { return super.intrinsicContentSize }

View File

@ -124,6 +124,10 @@ open class CalendarBase: Control, Changeable {
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
open override func initialSetup() {
super.initialSetup()
}
/// 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()
@ -149,19 +153,6 @@ open class CalendarBase: Control, Changeable {
collectionView.pinCenterX(anchor: containerView.centerXAnchor)
}
open override func setDefaults() {
super.setDefaults()
hideContainerBorder = false
hideCurrentDateIndicator = false
transparentBackground = false
activeDates = []
inactiveDates = []
indicators = []
minDate = Date()
maxDate = Calendar.current.date(byAdding: .year, value: 10, to: Date())!
selectedDate = Date()
}
open override func updateView() {
super.updateView()
@ -182,6 +173,17 @@ open class CalendarBase: Control, Changeable {
containerView.layer.borderWidth = VDSFormControls.borderWidth
}
}
/// Resets to default settings.
open override func reset() {
super.reset()
hideContainerBorder = false
hideCurrentDateIndicator = false
transparentBackground = false
activeDates = []
inactiveDates = []
indicators = []
}
//--------------------------------------------------
// MARK: - Private Methods

View File

@ -9,7 +9,7 @@ import Foundation
/// Custom data type for indicators prop
extension CalendarBase {
public struct CalendarIndicatorModel: Equatable {
public struct CalendarIndicatorModel {
/// Text that shown to an indicator for legend
public var label: String

View File

@ -57,7 +57,7 @@ open class Carousel: View {
}
/// Space between each tile. The default value will be 6X in tablet and 3X in mobile.
public enum Gutter: String, CaseIterable , DefaultValuing, Valuing {
public enum Gutter: String, CaseIterable , DefaultValuing {
case gutter3X = "3X"
case gutter6X = "6X"
@ -153,38 +153,27 @@ open class Carousel: View {
$0.backgroundColor = .clear
}
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: frame, collectionViewLayout: layout)
collectionView.isScrollEnabled = true
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.delegate = self
collectionView.dataSource = self
collectionView.showsHorizontalScrollIndicator = false
collectionView.showsVerticalScrollIndicator = false
collectionView.backgroundColor = .clear
collectionView.register(CarouselSlotCell.self,
forCellWithReuseIdentifier: CarouselSlotCell.identifier)
return collectionView
}()
private var scrollView = UIScrollView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.backgroundColor = .clear
}
/// Previous button to show previous slide.
private var previousButton = ButtonIcon().with {
$0.kind = .lowContrast
$0.iconName = .paginationLeftCaret
$0.iconName = .leftCaret
$0.iconOffset = .init(x: -2, y: 0)
$0.customContainerSize = UIDevice.isIPad ? 40 : 28
$0.customIconSize = UIDevice.isIPad ? 16 : 12
$0.icon.customSize = UIDevice.isIPad ? 16 : 12
}
/// Next button to show next slide.
private var nextButton = ButtonIcon().with {
$0.kind = .lowContrast
$0.iconName = .paginationRightCaret
$0.iconName = .rightCaret
$0.iconOffset = .init(x: 2, y: 0)
$0.customContainerSize = UIDevice.isIPad ? 40 : 28
$0.customIconSize = UIDevice.isIPad ? 16 : 12
$0.icon.customSize = UIDevice.isIPad ? 16 : 12
}
/// A publisher for when moving the carousel. Passes parameters selectedGroupIndex (position).
@ -206,6 +195,11 @@ open class Carousel: View {
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
/// Executed on initialization for this View.
open override func initialSetup() {
super.initialSetup()
}
/// 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()
@ -225,9 +219,9 @@ open class Carousel: View {
containerView.addSubview(contentStackView)
// Add scrollview
scrollContainerView.addSubview(collectionView)
collectionView.pinToSuperView()
scrollContainerView.addSubview(scrollView)
scrollView.pinToSuperView()
// Add pagination button icons
scrollContainerView.addSubview(previousButton)
previousButton
@ -254,40 +248,17 @@ open class Carousel: View {
updatePaginationInset()
}
open override func setDefaults() {
super.setDefaults()
gutter = UIDevice.isIPad ? .gutter6X : .gutter3X
layout = UIDevice.isIPad ? .threeUP : .oneUP
onChange = nil
pagination = .init(kind: .lowContrast, floating: true)
paginationDisplay = .none
paginationInset = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X
peek = .standard
groupIndex = 0
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()
updateScrollbar()
updateCarousel()
collectionView.collectionViewLayout.invalidateLayout()
collectionView.reloadData()
}
//--------------------------------------------------
// MARK: - Private Methods
//--------------------------------------------------
private func updateScrollbar() {
carouselScrollBar.numberOfSlides = views.count
carouselScrollBar.layout = layout
if (carouselScrollBar.position == 0 || carouselScrollBar.position > carouselScrollBar.numberOfSlides) {
carouselScrollBar.position = 1
}
carouselScrollBar.isHidden = (totalPositions() <= 1) ? true : false
}
private func updateCarousel() {
// Mobile/Tablet layouts without peek - must show pagination controls.
// If peek is none, pagination controls should show. So set to persistent.
if peek == .none {
@ -305,9 +276,24 @@ open class Carousel: View {
}
updatePaginationControls()
updateContainerHeight()
addCarouselSlots()
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
layout = UIDevice.isIPad ? .threeUP : .oneUP
pagination = .init(kind: .lowContrast, floating: true)
paginationDisplay = .none
paginationInset = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X
gutter = UIDevice.isIPad ? .gutter6X : .gutter3X
peek = .standard
}
//--------------------------------------------------
// MARK: - Private Methods
//--------------------------------------------------
private func addlisteners() {
nextButton.onClick = { _ in self.nextButtonClick() }
previousButton.onClick = { _ in self.previousButtonClick() }
@ -383,13 +369,47 @@ open class Carousel: View {
return height
}
// update carousel size and load data if any
private func updateContainerHeight() {
// Add carousel slots and load data if any
private func addCarouselSlots() {
getSlotWidth()
if containerView.frame.size.width > 0 {
containerViewHeightConstraint?.isActive = false
containerStackHeightConstraint?.isActive = false
let slotHeight = fetchCarouselHeight()
// Perform a loop to iterate each subView
scrollView.subviews.forEach { subView in
// Removing subView from its parent view
subView.removeFromSuperview()
}
// Add carousel items
if views.count > 0 {
var xPos = 0.0
for index in 0...views.count - 1 {
// Add Carousel Slot
let carouselSlot = View().with {
$0.clipsToBounds = true
}
scrollView.addSubview(carouselSlot)
scrollView.delegate = self
carouselSlot
.pinTop()
.pinBottom()
.pinLeading(xPos)
.width(minimumSlotWidth)
.height(slotHeight)
xPos = xPos + minimumSlotWidth + gutter.value
let component = views[index]
carouselSlot.addSubview(component)
setSlotAlignment(contentView: component)
}
scrollView.contentSize = CGSize(width: xPos - gutter.value, height: slotHeight)
}
let containerHeight = slotHeight + scrollbarTopSpace + containerSize.height
if carouselScrollBar.isHidden {
containerStackHeightConstraint = contentStackView.heightAnchor.constraint(equalToConstant: slotHeight)
@ -403,6 +423,43 @@ open class Carousel: View {
}
}
// Set slot alignment if provided. Used only when slot content have different heights or widths.
private func setSlotAlignment(contentView: UIView) {
switch slotAlignment?.vertical {
case .top:
contentView
.pinTop()
.pinBottomLessThanOrEqualTo()
case .middle:
contentView
.pinTopGreaterThanOrEqualTo()
.pinBottomLessThanOrEqualTo()
.pinCenterY()
case .bottom:
contentView
.pinTopGreaterThanOrEqualTo()
.pinBottom()
default: break
}
switch slotAlignment?.horizontal {
case .left:
contentView
.pinLeading()
.pinTrailingLessThanOrEqualTo()
case .center:
contentView
.pinLeadingGreaterThanOrEqualTo()
.pinTrailingLessThanOrEqualTo()
.pinCenterX()
case .right:
contentView
.pinLeadingGreaterThanOrEqualTo()
.pinTrailing()
default: break
}
}
// Get the slot width relative to the peak
private func getSlotWidth() {
let actualWidth = containerView.frame.size.width
@ -452,7 +509,7 @@ open class Carousel: View {
}
private func updateScrollbarPosition(targetContentOffsetXPos:CGFloat) {
let scrollContentSizeWidth = collectionView.contentSize.width
let scrollContentSizeWidth = scrollView.contentSize.width
let totalPositions = totalPositions()
let layoutSpace = Int (floor( Double(scrollContentSizeWidth / Double(totalPositions))))
let remindSpace = Int(targetContentOffsetXPos) % layoutSpace
@ -462,11 +519,10 @@ open class Carousel: View {
updateScrollPosition(position: contentPos, callbackText: "ScrollViewMoved")
}
// Update collectionview offset relative to scrollbar thumb position
// Update scrollview offset relative to scrollbar thumb position
private func updateScrollPosition(position: Int, callbackText: String) {
if carouselScrollBar.numberOfSlides > 0 {
let scrollContentSizeWidth = collectionView.contentSize.width
let scrollContentSizeWidth = scrollView.contentSize.width
let totalPositions = totalPositions()
var xPos = 0.0
if position == 1 {
@ -484,8 +540,8 @@ open class Carousel: View {
}
}
carouselScrollBar.scrubberId = position+1
let yPos = collectionView.contentOffset.y
collectionView.setContentOffset(CGPoint(x: xPos, y: yPos), animated: true)
let yPos = scrollView.contentOffset.y
scrollView.setContentOffset(CGPoint(x: xPos, y: yPos), animated: true)
showPaginationControls()
groupIndex = position-1
onChangePublisher.send(groupIndex)
@ -505,35 +561,5 @@ extension Carousel: UIScrollViewDelegate {
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
updateScrollbarPosition(targetContentOffsetXPos: targetContentOffset.pointee.x)
}
}
extension Carousel: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
//--------------------------------------------------
// MARK: - UICollectionView Delegate & Datasource
//--------------------------------------------------
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
views.count
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CarouselSlotCell.identifier, for: indexPath) as? CarouselSlotCell else { return UICollectionViewCell() }
let component = views[indexPath.row]
cell.update(with: component, slotAlignment: slotAlignment, surface: surface)
cell.layoutIfNeeded()
//component.setNeedsLayout()
if hasDebugBorder {
cell.addDebugBorder()
} else {
cell.removeDebugBorder()
}
return cell
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return gutter.value
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: minimumSlotWidth, height: fetchCarouselHeight())
}
}

View File

@ -10,7 +10,7 @@ import UIKit
/// Custom data type for pagination prop for 'Carousel' component.
extension Carousel {
public struct CarouselPaginationModel: Equatable {
public struct CarouselPaginationModel {
/// Pagination supports Button icon property 'kind'.
public var kind: ButtonIcon.Kind

View File

@ -11,7 +11,7 @@ import Foundation
extension Carousel {
/// Used only when slot content have different heights or widths.
public struct CarouselSlotAlignmentModel: Equatable {
public struct CarouselSlotAlignmentModel {
/// Used for vertical alignment of slot alignment.
public var vertical: Carousel.Vertical

View File

@ -1,85 +0,0 @@
//
// CarouselSlotCell.swift
// VDS
//
// Created by Kanamarlapudi, Vasavi on 23/08/24.
//
import Foundation
import UIKit
final class CarouselSlotCell: UICollectionViewCell {
///Identifier for the Calendar Date Cell.
static let identifier: String = String(describing: CarouselSlotCell.self)
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
override init(frame: CGRect) {
super.init(frame: frame)
setUp()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setUp()
}
//--------------------------------------------------
// MARK: - Private Methods
//--------------------------------------------------
/// Configuring the cell with default setup.
private func setUp() {
isAccessibilityElement = true
}
/// Updating UI based on data along with surface.
func update(with component: UIView, slotAlignment: Carousel.CarouselSlotAlignmentModel?, surface: Surface) {
contentView.subviews.forEach { $0.removeFromSuperview() }
contentView.addSubview(component)
if var surfacedView = component as? Surfaceable {
surfacedView.surface = surface
}
setSlotAlignment(alignment: slotAlignment, contentView: component)
}
// Set slot alignment if provided. Used only when slot content have different heights or widths.
private func setSlotAlignment(alignment: Carousel.CarouselSlotAlignmentModel?, contentView: UIView) {
switch alignment?.vertical {
case .top:
contentView
.pinTop()
.pinBottomLessThanOrEqualTo()
case .middle:
contentView
.pinTopGreaterThanOrEqualTo()
.pinBottomLessThanOrEqualTo()
.pinCenterY()
case .bottom:
contentView
.pinTopGreaterThanOrEqualTo()
.pinBottom()
default: break
}
switch alignment?.horizontal {
case .left:
contentView
.pinLeading()
.pinTrailingLessThanOrEqualTo()
case .center:
contentView
.pinLeadingGreaterThanOrEqualTo()
.pinTrailingLessThanOrEqualTo()
.pinCenterX()
case .right:
contentView
.pinLeadingGreaterThanOrEqualTo()
.pinTrailing()
default: break
}
}
}

View File

@ -105,15 +105,14 @@ open class CarouselScrollbar: View {
/// A callback when the scrubber position changes. Passes parameters (position).
open var onScrubberDrag: ((Int) -> Void)? {
didSet {
get { nil }
set {
onScrubberDragCancellable?.cancel()
if let onScrubberDrag {
if let newValue {
onScrubberDragCancellable = onScrubberDragPublisher
.sink { c in
onScrubberDrag(c)
newValue(c)
}
} else {
onScrubberDragCancellable = nil
}
}
}
@ -124,15 +123,14 @@ open class CarouselScrollbar: View {
/// A callback when the thumb move forward. Passes parameters (position).
open var onMoveForward: ((Int) -> Void)? {
didSet {
get { nil }
set {
onMoveForwardCancellable?.cancel()
if let onMoveForward {
if let newValue {
onMoveForwardCancellable = onMoveForwardPublisher
.sink { c in
onMoveForward(c)
newValue(c)
}
} else {
onMoveForwardCancellable = nil
}
}
}
@ -143,15 +141,14 @@ open class CarouselScrollbar: View {
/// A callback when the thumb move backward. Passes parameters (position).
open var onMoveBackward: ((Int) -> Void)? {
didSet {
get { nil }
set {
onMoveBackwardCancellable?.cancel()
if let onMoveBackward {
if let newValue {
onMoveBackwardCancellable = onMoveBackwardPublisher
.sink { c in
onMoveBackward(c)
newValue(c)
}
} else {
onMoveBackwardCancellable = nil
}
}
}
@ -162,15 +159,14 @@ open class CarouselScrollbar: View {
/// A callback when the thumb touch start. Passes parameters (position).
open var onThumbTouchStart: ((Int) -> Void)? {
didSet {
get { nil }
set {
onThumbTouchStartCancellable?.cancel()
if let onThumbTouchStart {
if let newValue {
onThumbTouchStartCancellable = onThumbTouchStartPublisher
.sink { c in
onThumbTouchStart(c)
newValue(c)
}
} else {
onThumbTouchStartCancellable = nil
}
}
}
@ -181,15 +177,14 @@ open class CarouselScrollbar: View {
/// A callback when the thumb touch end. Passes parameters (position).
open var onThumbTouchEnd: ((Int) -> Void)? {
didSet {
get { nil }
set {
onThumbTouchEndCancellable?.cancel()
if let onThumbTouchEnd {
if let newValue {
onThumbTouchEndCancellable = onThumbTouchEndPublisher
.sink { c in
onThumbTouchEnd(c)
newValue(c)
}
} else {
onThumbTouchEndCancellable = nil
}
}
}
@ -239,6 +234,10 @@ open class CarouselScrollbar: View {
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open override func initialSetup() {
super.initialSetup()
}
open override func setup() {
super.setup()
isAccessibilityElement = false
@ -298,19 +297,6 @@ open class CarouselScrollbar: View {
thumbView.layer.addSublayer(thumbViewLayer)
}
open override func setDefaults() {
super.setDefaults()
onMoveForward = nil
onMoveBackward = nil
onScrubberDrag = nil
onThumbTouchEnd = nil
onThumbTouchStart = nil
layout = .oneUP
numberOfSlides = 1
totalPositions = 1
position = 1
}
open override func updateView() {
super.updateView()
trackView.backgroundColor = trackColorConfiguration.getColor(surface)

View File

@ -61,11 +61,6 @@ open class Checkbox: SelectorBase {
selectorColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOnlight, forState: .selected)
}
open override func setDefaults() {
super.setDefaults()
isAnimated = false
}
/// This will change the state of the Selector and execute the actionBlock if provided.
open override func toggle() {
guard isEnabled else { return }

View File

@ -43,7 +43,7 @@ open class CheckboxGroup: SelectorGroupBase<CheckboxItem>, SelectorGroupMultiSel
if let selectorModels {
items = selectorModels.enumerated().map { index, model in
return CheckboxItem().with {
$0.isEnabled = model.enabled
$0.isEnabled = !model.disabled
$0.surface = model.surface
$0.inputId = model.inputId
$0.hiddenValue = model.value
@ -86,13 +86,7 @@ open class CheckboxGroup: SelectorGroupBase<CheckboxItem>, SelectorGroupMultiSel
mainStackView.spacing = VDSLayout.space6X
}
open override func setDefaults() {
super.setDefaults()
showError = false
inputId = nil
}
open override func didSelect(_ selectedControl: CheckboxItem) {
public override func didSelect(_ selectedControl: CheckboxItem) {
selectedControl.toggle()
if selectedControl.isSelected, showError{
showError.toggle()
@ -107,10 +101,10 @@ open class CheckboxGroup: SelectorGroupBase<CheckboxItem>, SelectorGroupMultiSel
}
extension CheckboxGroup {
public struct CheckboxItemModel : Surfaceable, Initable, Errorable, Equatable {
/// Whether this object is enabled or not
public var enabled: Bool
public struct CheckboxItemModel : Surfaceable, Initable, Errorable {
/// Whether this object is disabled or not
public var disabled: Bool
/// Current Surface and this is used to pass down to child objects that implement Surfacable
public var surface: Surface
public var inputId: String?
@ -127,8 +121,8 @@ extension CheckboxGroup {
public var showError: Bool
public var errorText: String?
public init(enabled: Bool, surface: Surface = .light, inputId: String? = nil, value: AnyHashable? = nil, accessibileText: String? = nil, labelText: String? = nil, labelTextAttributes: [any LabelAttributeModel]? = nil, childText: String? = nil, childTextAttributes: [any LabelAttributeModel]? = nil, selected: Bool = false, showError: Bool = false, errorText: String? = nil) {
self.enabled = enabled
public init(disabled: Bool, surface: Surface = .light, inputId: String? = nil, value: AnyHashable? = nil, accessibileText: String? = nil, labelText: String? = nil, labelTextAttributes: [any LabelAttributeModel]? = nil, childText: String? = nil, childTextAttributes: [any LabelAttributeModel]? = nil, selected: Bool = false, showError: Bool = false, errorText: String? = nil) {
self.disabled = disabled
self.surface = surface
self.inputId = inputId
self.value = value
@ -143,22 +137,7 @@ extension CheckboxGroup {
}
public init() {
self.init(enabled: true)
}
public static func == (lhs: CheckboxGroup.CheckboxItemModel, rhs: CheckboxGroup.CheckboxItemModel) -> Bool {
lhs.enabled == rhs.enabled
&& lhs.surface == rhs.surface
&& lhs.inputId == rhs.inputId
&& lhs.value == rhs.value
&& lhs.accessibileText == rhs.accessibileText
&& lhs.labelText == rhs.labelText
&& lhs.labelTextAttributes == rhs.labelTextAttributes
&& lhs.childText == rhs.childText
&& lhs.childTextAttributes == rhs.childTextAttributes
&& lhs.selected == rhs.selected
&& lhs.showError == rhs.showError
&& lhs.errorText == rhs.errorText
self.init(disabled: false)
}
}

View File

@ -47,11 +47,6 @@ open class CheckboxItem: SelectorItemBase<Checkbox> {
isSelected.toggle()
sendActions(for: .valueChanged)
}
open override func setDefaults() {
super.setDefaults()
isAnimated = false
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {

View File

@ -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<String> {
open class DatePicker: EntryFieldBase {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
@ -93,12 +93,6 @@ open class DatePicker: EntryFieldBase<String> {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open override var children: [any ViewProtocol] {
var current = super.children
current.append(selectedDateLabel)
return current
}
open var calendarIcon = Icon().with {
$0.name = .calendar
$0.size = .medium
@ -157,7 +151,18 @@ open class DatePicker: EntryFieldBase<String> {
// setting color config
selectedDateLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable()
// tap gesture
containerView
.publisher(for: UITapGestureRecognizer())
.sink { [weak self] _ in
guard let self else { return }
if isEnabled && !isReadOnly {
showPopover()
}
}
.store(in: &subscribers)
NotificationCenter.default
.publisher(for: UIDevice.orientationDidChangeNotification).sink { [weak self] _ in
guard let self else { return }
@ -168,31 +173,6 @@ open class DatePicker: EntryFieldBase<String> {
popoverOverlayView.isHidden = true
}
open override func setDefaults() {
super.setDefaults()
selectedDate = nil
calendarModel = .init()
dateFormat = .shortNumeric
selectedDateLabel.textStyle = .bodyLarge
// tap gesture
containerView.onClick = { [weak self] _ in
guard let self else { return }
if isEnabled && !isReadOnly {
showPopover()
}
}
containerView.accessibilityTraits = [.button]
containerView.bridge_accessibilityHintBlock = { [weak self] in
guard let self else { return "" }
return isReadOnly || !isEnabled
? ""
: isCalendarShowing ? "Expanded, Double tap to close" : "Collapsed, \(accessibilityHintText)"
}
}
open override func getFieldContainer() -> UIView {
// stackview for controls in EntryFieldBase.controlContainerView
let controlStackView = UIStackView().with {
@ -219,6 +199,12 @@ open class DatePicker: EntryFieldBase<String> {
calendarIcon.color = iconColorConfiguration.getColor(self)
}
/// Resets to default settings.
open override func reset() {
super.reset()
selectedDateLabel.textStyle = .bodyLarge
}
internal func formatDate(_ date: Date) {
let formatter = DateFormatter()
formatter.dateFormat = dateFormat.format
@ -235,9 +221,6 @@ extension DatePicker {
}
let calendar = CalendarBase()
if let selectedDate, selectedDate != calendar.selectedDate {
calendar.selectedDate = selectedDate
}
calendar.activeDates = calendarModel.activeDates
calendar.hideContainerBorder = calendarModel.hideContainerBorder
calendar.hideCurrentDateIndicator = calendarModel.hideCurrentDateIndicator

View File

@ -9,7 +9,7 @@ import Foundation
import UIKit
extension DatePicker {
public struct CalendarModel: Equatable {
public struct CalendarModel {
/// If set to true, the calendar will not have a border.
public let hideContainerBorder: Bool
@ -24,8 +24,6 @@ extension DatePicker {
/// All other dates will be active.
public let inactiveDates: [Date]
public let selectedDate: Date
/// If provided, the calendar will allow a selection to be made from this date forward. Defaults to today.
public let minDate: Date
@ -39,15 +37,13 @@ extension DatePicker {
hideCurrentDateIndicator: Bool = false,
activeDates: [Date] = [],
inactiveDates: [Date] = [],
selectedDate: Date = Date(),
minDate: Date = Date(),
maxDate: Date = Calendar.current.date(byAdding: .year, value: 10, to: Date())!,
minDate: Date = Date().startOfMonth,
maxDate: Date = Date().endOfMonth,
indicators: [CalendarBase.CalendarIndicatorModel] = []) {
self.hideContainerBorder = hideContainerBorder
self.hideCurrentDateIndicator = hideCurrentDateIndicator
self.activeDates = activeDates
self.inactiveDates = inactiveDates
self.selectedDate = selectedDate
self.minDate = minDate
self.maxDate = maxDate
self.indicators = indicators

View File

@ -8,7 +8,7 @@
import Foundation
extension DropdownSelect {
public struct DropdownOptionModel: Equatable {
public struct DropdownOptionModel {
/// Text that goes as option to DropdownSelect
public var text: String

View File

@ -12,7 +12,7 @@ import Combine
/// A dropdown select is an expandable menu of predefined options that allows a customer to make a single selection.
@objc(VDSDropdownSelect)
open class DropdownSelect: EntryFieldBase<String> {
open class DropdownSelect: EntryFieldBase {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
@ -30,7 +30,7 @@ open class DropdownSelect: EntryFieldBase<String> {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
//--------------------------------------------------
/// 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() }}
@ -60,9 +60,8 @@ open class DropdownSelect: EntryFieldBase<String> {
internal var minWidthInlineLabel = 102.0
internal override var minWidth: CGFloat { showInlineLabel ? minWidthInlineLabel : minWidthDefault }
internal override var maxWidth: CGFloat {
let frameWidth = constrainedWidth
let halfWidth = (frameWidth - horizontalStackView.spacing) / 2
return helperTextPlacement == .right && halfWidth > minWidth * 2 ? halfWidth : frameWidth
let frameWidth = frame.size.width
return helperTextPlacement == .right ? (frameWidth - horizontalStackView.spacing) / 2 : frameWidth
}
/// The is used for the for adding the helperLabel to the right of the containerView.
@ -82,15 +81,24 @@ open class DropdownSelect: EntryFieldBase<String> {
open var inlineDisplayLabel = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
$0.setContentCompressionResistancePriority(.required, for: .horizontal)
$0.textAlignment = .left
$0.textStyle = .boldBodyLarge
$0.numberOfLines = 1
$0.sizeToFit()
}
open var selectedOptionLabel = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
$0.setContentCompressionResistancePriority(.required, for: .horizontal)
$0.textAlignment = .left
$0.textStyle = .bodyLarge
$0.numberOfLines = 1
}
open var dropdownField = UITextField().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.tintColor = UIColor.clear
$0.font = TextStyle.bodyLarge.font
}
open var optionsPicker = UIPickerView()
@ -144,35 +152,15 @@ open class DropdownSelect: EntryFieldBase<String> {
}()
// tap gesture
containerView.onClick = { [weak self] _ in
self?.launchPicker()
}
containerView
.publisher(for: UITapGestureRecognizer())
.sink { [weak self] _ in
self?.launchPicker()
}
.store(in: &subscribers)
containerView.height(44)
}
open override func setDefaults() {
super.setDefaults()
showInlineLabel = false
selectId = nil
inlineDisplayLabel.textAlignment = .left
inlineDisplayLabel.textStyle = .boldBodyLarge
inlineDisplayLabel.numberOfLines = 1
selectedOptionLabel.textAlignment = .left
selectedOptionLabel.textStyle = .bodyLarge
selectedOptionLabel.numberOfLines = 1
dropdownField.tintColor = UIColor.clear
dropdownField.font = TextStyle.bodyLarge.font
showInlineLabel = false
options = []
selectId = nil
}
open override func reset() {
inlineDisplayLabel.reset()
selectedOptionLabel.reset()
super.reset()
}
open override func getFieldContainer() -> UIView {
let controlStackView = UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
@ -199,6 +187,17 @@ open class DropdownSelect: EntryFieldBase<String> {
selectedOptionLabel.surface = surface
selectedOptionLabel.isEnabled = isEnabled
}
/// Resets to default settings.
open override func reset() {
super.reset()
inlineDisplayLabel.textStyle = .boldBodyLarge
selectedOptionLabel.textStyle = .bodyLarge
showInlineLabel = false
options = []
selectId = nil
}
//--------------------------------------------------
// MARK: - Public Methods

View File

@ -1,17 +0,0 @@
MM/DD/YYYY
----------------
Initial Brand 3.0 handoff
12/18/2023
----------------
- New
12/28/2023
----------------
- hideSymbol updated to showSymbol and default set to True.
- Figma-only properties section added in Footnote Item Configurations section.
01/16/2024
----------------
- hideSymbol reverted to hideSymbol and default set to False.
- Figma-only properties section removed.

View File

@ -1,165 +0,0 @@
//
// FootnoteGroup.swift
// VDS
//
// Created by Kanamarlapudi, Vasavi on 29/08/24.
//
import Foundation
import UIKit
import VDSCoreTokens
/// This must always be paired with one or more ``Footnote`` in a FootnoteGroup.
@objc(VDSFootnoteGroup)
open class FootnoteGroup: View {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
//--------------------------------------------------
// MARK: - enums
//--------------------------------------------------
/// Enum used to describe the width of a fixed value or percentage of parent's width.
public enum Width {
case percentage(CGFloat)
case value(CGFloat)
}
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
/// Array of ``Footnote`` for the Footnote items.
open var footnoteItems: [FootnoteItem] = [] { didSet { updateFootnoteItems() } }
/// Any percentage or pixel value and cannot exceed container size.
/// If there is a width that is larger than container size, the footnote will resize to container's width.
open var width: Width? {
get { _width }
set {
if let newValue {
switch newValue {
case .percentage(let percentage):
if percentage <= 100.0 {
_width = newValue
}
case .value(let value):
if value > 0 {
_width = newValue
}
}
} else {
_width = nil
}
updateContainerWidth()
}
}
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
private var _width: Width? = nil
private lazy var stackView = UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.axis = .vertical
$0.distribution = .fill
$0.spacing = VDSLayout.space3X
$0.backgroundColor = .clear
}
//--------------------------------------------------
// MARK: - Configuration Properties
//--------------------------------------------------
internal var maxWidth: CGFloat { constrainedWidth }
internal var minWidth: CGFloat { containerSize.width }
internal var containerSize: CGSize { CGSize(width: 55, height: 44) }
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
internal var widthConstraint: NSLayoutConstraint?
//--------------------------------------------------
// 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()
// add footnote item stackview.
addSubview(stackView)
stackView.pinToSuperView()
widthConstraint = widthAnchor.constraint(equalToConstant: 0).deactivate()
}
open override func setDefaults() {
super.setDefaults()
width = nil
footnoteItems = []
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()
updateFootnoteItems()
}
internal func updateFootnoteItems() {
// symbol containers are as wide as the widest symbol container in the group.
var symbolMaxWidth = 0.0
footnoteItems.forEach { footnote in
let separatorWidth = Label().with {
$0.text = footnote.symbolType
$0.textStyle = footnote.symbolLabel.textStyle
$0.sizeToFit()
}.intrinsicContentSize.width
symbolMaxWidth = max(separatorWidth, symbolMaxWidth)
}
stackView.removeArrangedSubviews()
// add symbol label, text label to stack.
footnoteItems.forEach { footnote in
footnote.symbolWidth = symbolMaxWidth
footnote.surface = surface
stackView.addArrangedSubview(footnote)
}
}
/// Update container width after updating content.
internal func updateContainerWidth() {
var newWidth = 0.0
switch width {
case .percentage(let percentage):
newWidth = max(maxWidth * ((percentage) / 100), minWidth)
case .value(let value):
newWidth = value > maxWidth ? maxWidth : value
case nil: break
}
widthConstraint?.deactivate()
if newWidth > minWidth && newWidth < maxWidth {
widthConstraint?.constant = newWidth
widthConstraint?.activate()
}
}
}

View File

@ -1,256 +0,0 @@
//
// Footnote.swift
// VDS
//
// Created by Kanamarlapudi, Vasavi on 21/08/24.
//
import Foundation
import UIKit
import VDSCoreTokens
/// A footnote is text that provides supporting details, legal copy and links to related content.
/// It exists at the bottom or "foot" of a page or section.
@objc(VDSFootnoteItem)
open class FootnoteItem: View {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
//--------------------------------------------------
// MARK: - enums
//--------------------------------------------------
/// Enum used to describe the kind of component.
public enum Kind: String, DefaultValuing, CaseIterable {
case primary, secondary
/// The default kind is 'primary'.
public static var defaultValue : Self { .secondary }
/// Color configuation to Symbol and Text relative to kind.
public var colorConfiguration: SurfaceColorConfiguration {
switch self {
case .primary:
return SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark)
case .secondary:
return SurfaceColorConfiguration(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark)
}
}
}
/// Enum that represents the size availble for component.
public enum Size: String, DefaultValuing, CaseIterable {
case micro
case small
case large
public static var defaultValue: Self { .micro }
/// TextStyle relative to Size.
public var textStyle: TextStyle.StandardStyle {
switch self {
case .micro:
return .micro
case .small:
return .bodySmall
case .large:
return .bodyLarge
}
}
}
/// Enum used to describe the width of a fixed value or percentage of parent's width.
public enum Width {
case percentage(CGFloat)
case value(CGFloat)
}
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
/// Color to the component. The default kind is Secondary.
open var kind: Kind = .defaultValue { didSet { setNeedsUpdate() } }
/// Size of the component. The default size is Micro.
open var size: Size = .defaultValue { didSet { setNeedsUpdate() } }
/// If hideSymbol true, the component will show text without symbol.
open var hideSymbol: Bool = false { didSet { setNeedsUpdate() } }
/// symbol type will be shown for the footnote item. The default symbolType is 'asterisk'.
open var symbolType: String = "*" { didSet { setNeedsUpdate() } }
/// Text of the footnote item.
open var text: String? { didSet { setNeedsUpdate() } }
open var tooltipModel: Tooltip.TooltipModel? { didSet { setNeedsUpdate() } }
/// Any percentage or pixel value and cannot exceed container size.
/// If there is a width that is larger than container size, the footnote will resize to container's width.
open var width: Width? {
get { _width }
set {
if let newValue {
switch newValue {
case .percentage(let percentage):
if percentage <= 100.0 {
_width = newValue
}
case .value(let value):
if value > 0 {
_width = newValue
}
}
} else {
_width = nil
}
updateContainerWidth()
}
}
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
private var _width: Width? = nil
/// To set the widest symbol width from the symbol container in the group.
internal var symbolWidth: CGFloat? { didSet { setNeedsUpdate() } }
private lazy var itemStackView = UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.axis = .horizontal
$0.alignment = .leading
$0.distribution = .fill
$0.spacing = VDSLayout.space1X
$0.backgroundColor = .clear
}
internal var symbolLabel = Label().with {
$0.isAccessibilityElement = true
$0.numberOfLines = 1
$0.sizeToFit()
}
internal var textLabel = Label().with {
$0.isAccessibilityElement = true
$0.lineBreakMode = .byWordWrapping
}
//--------------------------------------------------
// MARK: - Configuration Properties
//--------------------------------------------------
internal var maxWidth: CGFloat { constrainedWidth }
internal var minWidth: CGFloat { containerSize.width }
internal var containerSize: CGSize { CGSize(width: 45, height: 44) }
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
internal var symbolWidthConstraint: NSLayoutConstraint?
internal var itemWidthConstraint: NSLayoutConstraint?
//--------------------------------------------------
// 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()
// add footnote item stackview.
addSubview(itemStackView)
itemStackView.pinToSuperView()
// width constraints
itemWidthConstraint = widthAnchor.constraint(equalToConstant: 0).deactivate()
// add symbol label, text label to stack.
itemStackView.addArrangedSubview(symbolLabel)
itemStackView.addArrangedSubview(textLabel)
itemStackView.setCustomSpacing(VDSLayout.space1X, after: symbolLabel)
symbolWidthConstraint = symbolLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 0)
symbolWidthConstraint?.isActive = true
}
open override func setDefaults() {
super.setDefaults()
hideSymbol = false
text = nil
tooltipModel = nil
width = nil
}
/// Resets to default settings.
open override func reset() {
symbolLabel.reset()
textLabel.reset()
super.reset()
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()
// Update symbolLabel
symbolLabel.text = symbolType
symbolLabel.isHidden = symbolType.isEmpty ? true : hideSymbol
symbolLabel.textColor = kind.colorConfiguration.getColor(self)
symbolLabel.textStyle = size.textStyle.regular
symbolLabel.surface = surface
//Set width to the symbol label
if let symbolWidth, symbolWidth > 0 {
// Set the widest symbol width from the symbol container in the group.
symbolWidthConstraint?.constant = symbolWidth
} else {
symbolWidthConstraint?.constant = symbolLabel.intrinsicContentSize.width
}
// Update textLabel
textLabel.text = text
textLabel.textColor = kind.colorConfiguration.getColor(self)
textLabel.textStyle = size.textStyle.regular
textLabel.surface = surface
// Set the textLabel attributes
if let tooltipModel {
var attributes: [any LabelAttributeModel] = []
attributes.append(TooltipLabelAttribute(surface: surface, model: tooltipModel, presenter: self))
textLabel.attributes = attributes
}
}
/// Update container width after updating content.
internal func updateContainerWidth() {
var newWidth = 0.0
switch width {
case .percentage(let percentage):
newWidth = max(maxWidth * ((percentage) / 100), minWidth)
case .value(let value):
newWidth = value > maxWidth ? maxWidth : value
case nil:
break
}
itemWidthConstraint?.deactivate()
if newWidth > minWidth && newWidth < maxWidth {
itemWidthConstraint?.constant = newWidth
itemWidthConstraint?.activate()
}
}
}

View File

@ -14,7 +14,7 @@ import Combine
/// It usually represents a supplementary or utilitarian action. A button icon can stand alone, but often
/// exists in a group when there are several actions that can be performed.
@objc(VDSButtonIcon)
open class ButtonIcon: Control, Changeable, ParentViewProtocol {
open class ButtonIcon: Control, Changeable {
//--------------------------------------------------
// MARK: - Initializers
@ -109,8 +109,6 @@ open class ButtonIcon: Control, Changeable, ParentViewProtocol {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var children: [any ViewProtocol] { [icon] }
public var onChangeSubscriber: AnyCancellable?
///Badge Indicator object used to render for the ButtonIcon.
@ -404,36 +402,17 @@ open class ButtonIcon: Control, Changeable, ParentViewProtocol {
centerXConstraint?.activate()
centerYConstraint = icon.centerYAnchor.constraint(equalTo: iconLayoutGuide.centerYAnchor, constant: 0)
centerYConstraint?.activate()
publisher(for: .touchUpInside)
.sink(receiveValue: { [weak self] _ in
guard let self, isEnabled,
selectedIconName != nil,
selectable else { return }
toggle()
})
.store(in: &subscribers)
}
open override func setDefaults() {
super.setDefaults()
badgeIndicatorModel = nil
kind = .ghost
surfaceType = .colorFill
iconName = nil
selectedIconName = nil
selectedIconColorConfiguration = nil
size = .large
floating = false
fitToIcon = false
hideBorder = true
showBadgeIndicator = false
selectable = false
iconOffset = .init(x: 0, y: 0)
customContainerSize = nil
customIconSize = nil
customBadgeIndicatorOffset = nil
onChange = nil
/// Executed on initialization for this View.
open override func initialSetup() {
super.initialSetup()
onClick = { control in
guard control.isEnabled else { return }
if control.selectedIconName != nil && control.selectable {
control.toggle()
}
}
}
/// This will change the state of the Selector and execute the actionBlock if provided.
@ -443,6 +422,26 @@ open class ButtonIcon: Control, Changeable, ParentViewProtocol {
sendActions(for: .valueChanged)
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
kind = .ghost
surfaceType = .colorFill
size = .large
floating = false
hideBorder = true
iconOffset = .init(x: 0, y: 0)
iconName = nil
selectedIconName = nil
showBadgeIndicator = false
selectable = false
badgeIndicatorModel = nil
onChange = nil
shouldUpdateView = true
setNeedsUpdate()
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()

View File

@ -10,7 +10,7 @@ import Foundation
extension ButtonIcon {
//Model that represents the options available for the Badge Indicator
public struct BadgeIndicatorModel: Equatable {
public struct BadgeIndicatorModel {
/// Enum used to describe the badge indicator direction of icon button determining the expand direction.
public enum ExpandDirection: String, CaseIterable {
case right, center, left

View File

@ -89,29 +89,22 @@ open class Icon: View {
addSubview(imageView)
imageView.pinToSuperView()
backgroundColor = .clear
isAccessibilityElement = true
accessibilityTraits = .none
}
open override func setDefaults() {
super.setDefaults()
backgroundColor = .clear
color = VDSColor.paletteBlack
size = .medium
name = nil
customSize = nil
imageView.image = nil
accessibilityHint = "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.
open override func updateView() {
super.updateView()
@ -129,6 +122,12 @@ open class Icon: View {
invalidateIntrinsicContentSize()
}
/// Resets to default settings.
open override func reset() {
super.reset()
color = VDSColor.paletteBlack
imageView.image = nil
}
}
extension UIImage {

View File

@ -24,7 +24,7 @@ extension Icon {
/// let icon = Icon()
/// icon.name = .foo
/// ```
public struct Name: RawRepresentable, Equatable {
public struct Name: RawRepresentable {
public typealias RawValue = String
public var rawValue: String

View File

@ -1,422 +0,0 @@
//
// InputStepper.swift
// VDS
//
// Created by Kanamarlapudi, Vasavi on 24/06/24.
//
import Foundation
import UIKit
import VDSCoreTokens
import Combine
/// A stepper is a two-segment control that people use to increase or decrease an incremental value.'
@objc(VDSInputStepper)
open class InputStepper: EntryFieldBase<Int> {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
//--------------------------------------------------
// MARK: - Enums
//--------------------------------------------------
/// Enum used to describe the size of Input Stepper.
public enum Size: String, CaseIterable {
case large, small
var minWidth: CGFloat {
self == .large ? 121 : 90
}
var minHeight: CGFloat {
self == .large ? 44 : 32
}
var space: CGFloat {
self == .large ? VDSLayout.space3X : VDSLayout.space2X
}
var padding: CGFloat {
self == .large ? 6.0 : VDSLayout.space1X
}
var buttonContainerSize: Int {
self == .large ? 32 : 24
}
var textStyle: TextStyle {
self == .large ? .boldBodyLarge : .boldBodySmall
}
}
/// Enum used to describe the width of a fixed value or percentage of the input stepper control.
public enum ControlWidth {
case percentage(CGFloat)
case value(CGFloat)
}
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open override var children: [any ViewProtocol] {
var current = super.children
current.append(contentsOf: [decrementButton, incrementButton, textLabel])
return current
}
/// If there is a width that is larger than this size's minimumWidth, the input stepper will resize to this width.
open var controlWidth: ControlWidth? {
get { _controlWidth }
set {
if let newValue {
switch newValue {
case .percentage(let percentage):
if percentage <= 100.0 {
_controlWidth = newValue
}
case .value(let value):
if value > 0 && value > containerSize.width {
_controlWidth = newValue
}
}
} else {
_controlWidth = nil
}
setNeedsUpdate()
}
}
/// Accepts percentage value to width of parent container.
open var widthPercentage: CGFloat? {
didSet {
if let percentage = widthPercentage, percentage > 100 {
widthPercentage = 100
}
setNeedsUpdate()
}
}
private var _defaultValue: Int = 0
open override var defaultValue: Int? {
get { _defaultValue }
set {
if let newValue {
_defaultValue = newValue > maxValue ? maxValue : newValue < minValue ? minValue : newValue
} else {
_defaultValue = 0
}
setNeedsUpdate()
}
}
open override var value: Int? { return defaultValue }
/// Maximum value of the input stepper, defaults to '99'.
lazy open var maxValue: Int = { _defaultMaxValue }() {
didSet {
if maxValue > _defaultMaxValue || maxValue < _defaultMinValue && maxValue > minValue {
maxValue = _defaultMaxValue
}
setNeedsUpdate()
}
}
/// Minimum value of the input stepper, defaults to '0'.
lazy open var minValue: Int = { _defaultMinValue }() {
didSet {
if minValue < _defaultMinValue && minValue >= _defaultMaxValue && minValue < maxValue {
minValue = _defaultMinValue
}
setNeedsUpdate()
}
}
/// The size of the input stepper. Defaults to 'large'.
open var size: Size = .large { didSet { setNeedsUpdate() } }
/// Accepts any text or character to appear next to input stepper value.
open var trailingText: String? { didSet { setNeedsUpdate() } }
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
private var _controlWidth: ControlWidth? = nil
private var _defaultMinValue: Int = 0
private var _defaultMaxValue: Int = 99
/// This is the view that will be wrapped with the border for userInteraction.
/// The only subview of this view is the stepperStackView.
internal var stepperContainerView = View().with {
$0.isAccessibilityElement = true
$0.accessibilityLabel = "Input Stepper"
}
internal var stepperStackView = UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.axis = .horizontal
$0.distribution = .fill
$0.alignment = .fill
}
internal var decrementButton = ButtonIcon().with {
$0.kind = .ghost
$0.iconName = Icon.Name(name: "minus")
$0.iconOffset = .init(x: -2, y: 0)
$0.customContainerSize = 32
$0.icon.customSize = 16
$0.backgroundColor = .clear
}
internal var incrementButton = ButtonIcon().with {
$0.kind = .ghost
$0.iconName = Icon.Name(name: "plus")
$0.iconOffset = .init(x: 2, y: 0)
$0.customContainerSize = 32
$0.icon.customSize = 16
$0.backgroundColor = .clear
}
internal var textLabel = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
$0.textStyle = .boldBodyLarge
$0.numberOfLines = 1
$0.lineBreakMode = .byTruncatingTail
$0.textAlignment = .center
}
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
internal var stepperWidthConstraint: NSLayoutConstraint?
internal var stepperHeightConstraint: NSLayoutConstraint?
//--------------------------------------------------
// MARK: - Configuration Properties
//--------------------------------------------------
internal override var containerSize: CGSize { CGSize(width: size.minWidth, height: size.minHeight) }
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
/// 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()
// Set initial states
defaultValue = 0
containerView.isEnabled = false
statusIcon.isHidden = true
//override the default settings since the containerView
//fieldStackView relationShip needs to be updated
//we are not applying spacing either in the edges since this
//is the view that will take place of the containerView for the
//design of the original "containerView". This will get refactored at
//some point.
fieldStackView.applyAlignment(.leading)
// Add listeners
decrementButton.onClick = { _ in self.decrementButtonClick() }
incrementButton.onClick = { _ in self.incrementButtonClick() }
// setting color config
textLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable()
}
open override func getFieldContainer() -> UIView {
stepperStackView.addArrangedSubview(decrementButton)
stepperStackView.addArrangedSubview(textLabel)
stepperStackView.addArrangedSubview(incrementButton)
// Set space between decrement button, label, and increment button relative to input Stepper size.
stepperStackView.setCustomSpacing(size.space, after: decrementButton)
stepperStackView.setCustomSpacing(size.space, after: textLabel)
// stepperContainerView for controls in EntryFieldBase.controlContainerView
stepperContainerView.addSubview(stepperStackView)
// Update Edge insets relative to input Stepper size.
stepperStackView.pinToSuperView(.uniform(size.padding))
stepperWidthConstraint = stepperContainerView.width(constant: containerSize.width, priority: .required)
stepperHeightConstraint = stepperContainerView.height(constant: containerSize.height, priority: .required)
return stepperContainerView
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()
statusIcon.isHidden = true
// Update label
textLabel.isEnabled = isEnabled
textLabel.surface = surface
textLabel.text = "\(_defaultValue) " + (trailingText ?? "")
textLabel.textStyle = size.textStyle
updateButtonStates()
}
open override var accessibilityElements: [Any]? {
get {
var elements = [Any]()
if !isReadOnly || isEnabled {
elements.append(contentsOf: [titleLabel, containerView, decrementButton, textLabel, incrementButton])
} else {
elements.append(contentsOf: [titleLabel, containerView, textLabel])
}
if showError {
if let errorText, !errorText.isEmpty {
elements.append(errorLabel)
}
}
if let helperText, !helperText.isEmpty {
elements.append(helperLabel)
}
return elements
}
set { super.accessibilityElements = newValue }
}
/// Resets to default settings.
open override func reset() {
super.reset()
textLabel.reset()
controlWidth = nil
widthPercentage = nil
defaultValue = 0
minValue = _defaultMinValue
maxValue = _defaultMaxValue
trailingText = nil
size = .large
}
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
override func updateContainerView() {
//we are not calling super since we
//are using the fieldStackView as the "containerView"
//which will get the look/feel of the containerView.
//this will get refactored in the future.
fieldStackView.backgroundColor = containerBackgroundColor
fieldStackView.layer.borderColor = borderColorConfiguration.getColor(self).cgColor
fieldStackView.layer.borderWidth = VDSFormControls.borderWidth
}
internal override func updateContainerWidth() {
//we are not calling super here since
//we are changing how the widths are getting calculated
//now by including a percentage.
defer {
fieldStackView.layer.cornerRadius = containerSize.height / 2
}
stepperWidthConstraint?.deactivate()
widthConstraint?.deactivate()
var widthConstraintConstant: CGFloat?
if let widthPercentage, let superWidth = horizontalPinnedWidth() {
// test value vs minimum width and take the greater value
widthConstraintConstant = max(superWidth * (widthPercentage / 100), minWidth)
} else if let width, width >= minWidth, width <= maxWidth {
widthConstraintConstant = width
} else if let parentWidth = width, parentWidth >= maxWidth {
widthConstraintConstant = maxWidth
} else if let parentWidth = width, parentWidth <= minWidth {
widthConstraintConstant = minWidth
}
if let widthConstraintConstant {
widthConstraint?.constant = widthConstraintConstant
widthConstraint?.activate()
}
// Update Edge insets if size changes applied.
stepperStackView.applyAlignment(.fill, edges: .uniform(size.padding))
// Update height if size changes applied.
stepperHeightConstraint?.constant = containerSize.height
//update the stepper's widthConstraint if
//controlWidth was set
guard let controlWidth else {
return
}
// Set the inputStepper's controlWidth based on percentage received relative to its parentView's frame.
let containerWidth: CGFloat = widthConstraintConstant ?? containerView.frame.size.width
var stepperWidthConstant: CGFloat?
var stepperWidth: CGFloat
switch controlWidth {
case .percentage(let percentage):
stepperWidth = max(containerWidth * ((percentage) / 100), minWidth)
case .value(let value):
stepperWidth = value
}
//get the value of the stepperWidthConstant
if stepperWidth >= containerSize.width && stepperWidth <= containerWidth {
stepperWidthConstant = stepperWidth
} else if stepperWidth >= containerWidth {
stepperWidthConstant = containerWidth
}
if let stepperWidthConstant {
stepperWidthConstraint?.constant = stepperWidthConstant
stepperWidthConstraint?.activate()
}
}
//--------------------------------------------------
// MARK: - Private Methods
//--------------------------------------------------
internal func decrementButtonClick() {
if _defaultValue > minValue {
defaultValue = _defaultValue - 1
sendActions(for: .valueChanged)
}
}
internal func incrementButtonClick() {
if _defaultValue < maxValue {
defaultValue = _defaultValue + 1
sendActions(for: .valueChanged)
}
}
internal func updateButtonStates() {
decrementButton.customContainerSize = size.buttonContainerSize
incrementButton.customContainerSize = size.buttonContainerSize
decrementButton.surface = surface
incrementButton.surface = surface
if isReadOnly || !isEnabled {
decrementButton.isEnabled = false
incrementButton.isEnabled = false
} else {
decrementButton.isEnabled = (defaultValue ?? _defaultMaxValue ) > minValue ? true : false
incrementButton.isEnabled = (defaultValue ?? _defaultMinValue) < maxValue ? true : false
}
}
}

View File

@ -1,44 +0,0 @@
MM/DD/YYYY
----------------
02/2024
----------------
- New component
02/15/2024
----------------
- Added Border align: Inside to Anatomy
- Removed leadingText property values from States.
- Added Read-only to States.
- Created a section for Minimum width in Layout and spacing and updated the minWidth to 145px.
- Added trailingText spacing to Layout and spacing.
- Reduced space between Button Icons and text to 12px in Layout and spacing.
- Added top/bottom padding values to Layout and spacing.
03/01/2024
----------------
- Removed Leading Text from “Content and other properties” section.
03/20/2024
----------------
- Updated Anatomy artwork and items
- Added width and controlWidth to Configurations
- Updated Width under Layout and spacing to show layout examples
04/12/2024
----------------
- Added a new configuration property (size) that includes large and small
- Reduced details from Anatomy page and added them to Configurations/Size
- Added Hit area, Small Input Stepper spacing properties to Layout and spacing
- Updated the Behavior page to display disabled button icon when value is at max and min
04/29/2024
----------------
- Updated the Behavior page to display disabled button icon when value is at max and min
05/10/2024
----------------
- Added helperTextPlacement property to Configurations
- Added Layout examples for right Helper Text placement in Layout and Spacing
- Added overflow examples in Overflow section of Layout and Spacing

View File

@ -39,27 +39,6 @@ public extension String {
func isValid(range: NSRange) -> Bool {
range.location >= 0 && range.length > 0 && range.location + range.length <= count
}
func index(from: Int) -> Index {
return self.index(startIndex, offsetBy: from)
}
func substring(from: Int) -> String {
let fromIndex = index(from: from)
return String(self[fromIndex...])
}
func substring(to: Int) -> String {
let toIndex = index(from: to)
return String(self[..<toIndex])
}
func substring(with r: Range<Int>) -> String {
let startIndex = index(from: r.lowerBound)
let endIndex = index(from: r.upperBound)
return String(self[startIndex..<endIndex])
}
}
public extension NSAttributedString {

View File

@ -63,15 +63,18 @@ public struct TextStyleLabelAttribute: LabelAttributeModel {
//set lineHeight
if textStyle.lineHeight > 0.0 {
let lineHeight = textStyle.lineHeight + abs(textStyle.edgeInsets.bottom) + abs(textStyle.edgeInsets.top)
let lineHeight = textStyle.lineHeight
let adjustment = lineHeight > textStyle.font.lineHeight ? 2.0 : 1.0
let baselineOffset = (lineHeight - textStyle.font.lineHeight) / 2.0 / adjustment
let paragraph = NSMutableParagraphStyle().with {
$0.maximumLineHeight = lineHeight
$0.maximumLineHeight = lineHeight
$0.minimumLineHeight = lineHeight
$0.alignment = textPosition.value
$0.lineBreakMode = lineBreakMode
}
attributedString.removeAttribute(.baselineOffset, range: range)
attributedString.removeAttribute(.paragraphStyle, range: range)
attributedString.addAttribute(.baselineOffset, value: baselineOffset, range: range)
attributedString.addAttribute(.paragraphStyle, value: paragraph, range: range)
} else if textPosition != .left {
@ -84,4 +87,3 @@ public struct TextStyleLabelAttribute: LabelAttributeModel {
}
}
}

View File

@ -191,46 +191,42 @@ open class Label: UILabel, ViewProtocol, UserInfoable {
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
private func initialSetup() {
open func initialSetup() {
if !initialSetupPerformed {
initialSetupPerformed = true
shouldUpdateView = false
//register for ContentSizeChanges
NotificationCenter
.Publisher(center: .default, name: UIContentSizeCategory.didChangeNotification)
.sink { [weak self] notification in
self?.setNeedsUpdate()
}.store(in: &subscribers)
backgroundColor = .clear
numberOfLines = 0
lineBreakMode = .byTruncatingTail
translatesAutoresizingMaskIntoConstraints = false
accessibilityCustomActions = []
isAccessibilityElement = true
accessibilityTraits = .staticText
textAlignment = .left
setup()
setDefaults()
shouldUpdateView = true
setNeedsUpdate()
}
}
open func setup() {
//register for ContentSizeChanges
NotificationCenter
.Publisher(center: .default, name: UIContentSizeCategory.didChangeNotification)
.sink { [weak self] notification in
self?.setNeedsUpdate()
}.store(in: &subscribers)
translatesAutoresizingMaskIntoConstraints = false
isAccessibilityElement = true
}
open func setDefaults() {
backgroundColor = .clear
accessibilityTraits = .staticText
accessibilityCustomActions = []
surface = .light
isEnabled = true
attributes = nil
textStyle = .defaultStyle
lineBreakMode = .byTruncatingTail
textAlignment = .left
text = nil
attributedText = nil
numberOfLines = 0
}
open func reset() {
shouldUpdateView = false
setDefaults()
surface = .light
isEnabled = true
attributes = nil
textStyle = .defaultStyle
textAlignment = .left
text = nil
attributedText = nil
numberOfLines = 0
backgroundColor = .clear
shouldUpdateView = true
setNeedsUpdate()
}

View File

@ -80,6 +80,11 @@ open class Line: View {
//--------------------------------------------------
// 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()
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()
@ -88,8 +93,8 @@ open class Line: View {
}
/// Resets to default settings.
open override func setDefaults() {
super.setDefaults()
open override func reset() {
super.reset()
style = .primary
orientation = .horizontal
}

View File

@ -1,239 +0,0 @@
//
// ListUnordered.swift
// VDS
//
// Created by Vasavi Kanamarlapudi on 16/10/24.
//
import Foundation
import UIKit
import VDSCoreTokens
/// List unordered breaks up related content into distinct phrases or sentences, which improves scannability.
/// This component should be used when the text items dont need to be in numeric order.
@objcMembers
@objc(VDSListUnordered)
open class ListUnordered: View {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
//--------------------------------------------------
// MARK: - Enums
//--------------------------------------------------
/// Enum that represents the size availble for the component.
public enum Size: String, DefaultValuing, CaseIterable {
case large
case medium
case small
case micro
public static var defaultValue: Self { .large }
/// TextStyle relative to Size.
public var textStyle: TextStyle.StandardStyle {
switch self {
case .large:
return .bodyLarge
case .medium:
return .bodyMedium
case .small:
return .bodySmall
case .micro:
return .micro
}
}
}
/// Enum that represents the type of spacing available for the component.
public enum Spacing: String, CaseIterable {
case standard, compact
}
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
/// Size of the component. The default size is Large.
open var size: Size = .defaultValue { didSet { setNeedsUpdate() } }
/// Spacing type of the component.
open var spacing: Spacing = .standard { didSet { setNeedsUpdate() } }
/// Lead-in text that shows as the top text for the component. This is optional.
open var leadInText: String? = nil { didSet { setNeedsUpdate() } }
/// Array of unordered list items to show for the component.
open var unorderedList: [ListUnorderedItemModel] = [] { didSet { setNeedsUpdate() }}
//--------------------------------------------------
// MARK: - Configuration Properties
//--------------------------------------------------
// It can be used for Glyph level 1.
private var disc = ""
// It can be used for Glyph Level 2.
private var endash = ""
// Spacing between the list items.
private var spaceBetweenItems: CGFloat {
switch (size, spacing) {
case (.large, .standard):
return VDSLayout.space4X
case (.medium, .standard), (.small, .standard), (.micro, .standard):
return VDSLayout.space3X
case (.large, .compact):
return VDSLayout.space2X
case (.medium, .compact), (.small, .compact), (.micro, .compact):
return VDSLayout.space1X
}
}
// Padding that can be used in an item between the glyph and the item text.
private var padding: CGFloat {
switch (size, spacing) {
case (.large, .standard), (.large, .compact):
return VDSLayout.space3X
case (.medium, .standard), (.small, .standard), (.micro, .standard), (.medium, .compact), (.small, .compact), (.micro, .compact):
return VDSLayout.space2X
}
}
private let textColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark)
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
private lazy var listStackView = UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.axis = .vertical
$0.distribution = .fill
$0.spacing = spaceBetweenItems
$0.backgroundColor = .clear
}
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
/// Called once when a view is initialized and is used to Setup additional UI or other constants and config.texturations.
open override func setup() {
super.setup()
// add stackview
addSubview(listStackView)
listStackView.heightGreaterThanEqualTo(constant:0)
listStackView.pinToSuperView()
}
open override func setDefaults() {
super.setDefaults()
leadInText = nil
unorderedList = []
}
/// Resets to default settings.
open override func reset() {
super.reset()
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()
listStackView.removeArrangedSubviews()
listStackView.subviews.forEach { $0.removeFromSuperview() }
listStackView.spacing = spaceBetweenItems
if leadInText != nil {
let listItem = getListItem(with:self.leadInText, surface: surface)
listStackView.addArrangedSubview(listItem)
}
unorderedList.forEach { item in
let listItem = getListItem(levelOneText: item.levelOneText, surface: surface)
listStackView.addArrangedSubview(listItem)
item.levelTwoText?.forEach { text in
let listItem = getListItem(levelTwoText: text, surface: surface)
listStackView.addArrangedSubview(listItem)
}
}
}
//--------------------------------------------------
// MARK: - Private Methods
//--------------------------------------------------
// Get Label with the required text and text formats.
func getLabel(with text: String?, surface: Surface) -> Label {
let textLabel = Label().with {
$0.isAccessibilityElement = true
$0.lineBreakMode = .byWordWrapping
$0.text = text
$0.textStyle = size.textStyle.regular
$0.textColor = textColorConfiguration.getColor(surface)
$0.surface = surface
}
return textLabel
}
// Get the list item with the required text (LeadInText, Level 1 Text, Level 2 Text).
func getListItem(with leadInText:String? = nil, levelOneText: String? = nil, levelTwoText: String? = nil, surface:Surface) -> UIView {
let itemStackView = UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.axis = .horizontal
$0.alignment = .leading
$0.distribution = .fill
$0.spacing = padding
$0.backgroundColor = .clear
}
// StackView with LeadIntext if provided.
if leadInText != nil {
let leadTextLabel = getLabel(with: leadInText, surface: surface)
itemStackView.addArrangedSubview(leadTextLabel)
}
// StackView with Level 1 Text if provided.
if levelOneText != nil {
// Add level 1 glyph: 'disc, bold'
let discLabel = getLabel(with: disc, surface: surface)
discLabel.widthAnchor.constraint(equalToConstant: discLabel.intrinsicContentSize.width).activate()
itemStackView.addArrangedSubview(discLabel)
// Add level 1 Text
let levelOneLabel = getLabel(with: levelOneText, surface: surface)
itemStackView.addArrangedSubview(levelOneLabel)
}
// StackView with Level 2 Text if provided.
if levelTwoText != nil {
// Set level 2 leading space as needed for alignment.
let discSpaceView = View()
let discLabel = getLabel(with: disc, surface: surface)
discSpaceView.widthAnchor.constraint(equalToConstant: discLabel.intrinsicContentSize.width).activate()
itemStackView.addArrangedSubview(discSpaceView)
// Add level 2 glyph: 'en dash, regular'
let endashLabel = getLabel(with: endash, surface: surface)
endashLabel.widthAnchor.constraint(equalToConstant: endashLabel.intrinsicContentSize.width).activate()
itemStackView.addArrangedSubview(endashLabel)
// Add level 2 Text
let levelTwoLabel = getLabel(with: levelTwoText, surface: surface)
itemStackView.addArrangedSubview(levelTwoLabel)
}
return itemStackView
}
}

View File

@ -1,41 +0,0 @@
MM/DD/YYYY
----------------
- Initial Brand 3.0 handoff
05/2/2022
----------------
- Added Body Medium to size configuration
05/5/2022
----------------
- Added Spacing configuration (Standard, Compact) Web handoff
08/2/2022
----------------
- Included a VDS Note about the Spacing prop naming rationale
08/10/2022
----------------
- Updated default and inverted prop to light and dark surface.
12/13/2022
----------------
- Replaced focus border pixel and style & spacing values with tokens.
01/10/2023
----------------
- Removed from Anatomy section: “List item text”
- Updated “Glyph level 1” to “List Item Level 1”
- Updated “Glyph level 2” to “List Item Level 2”
- Updated image markers to reflect changes
02/02/2023
----------------
- Reduced left padding for all Level 2 sizes so that the Glyph aligns with the text in Level 1.
- Added dashed line on all sizes to indicate Level 2 alignment under Level 1.
- Changed “endash” to “endash, regular” under Size section.
- Updated all Level 1 and Level 2 glyph widths to “Hug”
12/26/23
----------------
- Deleted Decisions log

View File

@ -1,23 +0,0 @@
//
// ListUnorderedItemModel.swift
// VDS
//
// Created by Vasavi Kanamarlapudi on 16/10/24.
//
import Foundation
extension ListUnordered {
public struct ListUnorderedItemModel: Equatable {
/// Item Level 1 that shows text with glyph - disc, bold.
public var levelOneText: String
/// Item Level 2 that shows text (one or many) with glyph - en dash. This is optional.
public var levelTwoText: [String?]?
public init(itemLevelOneText: String, itemLevelTwoTexts: [String?]? = nil) {
self.levelOneText = itemLevelOneText
self.levelTwoText = itemLevelTwoTexts
}
}
}

View File

@ -10,7 +10,6 @@ import UIKit
import VDSCoreTokens
/// ViewController to show the Loader, this will be presented using the LoaderLaunchable Protocl.
@objc(VDSLoaderViewController)
open class LoaderViewController: UIViewController, Surfaceable {
//--------------------------------------------------
// MARK: - Private Properties

View File

@ -1,149 +0,0 @@
//
// Modal.swift
// VDS
//
// Created by Kanamarlapudi, Vasavi on 05/09/24.
//
import Foundation
import UIKit
import VDSCoreTokens
import Combine
/// A Modal is an overlay that interrupts the user flow to force the customer to provide information or a response.
/// After the customer interacts with the modal, they can return to the parent content.
@objc(VDSModal)
open class Modal: Control, ModalLaunchable {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
internal var showModalButton = Button().with {
$0.use = .primary
$0.text = "Show Modal"
}
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
/// Text rendered for the title of the modal
open var title: String? { didSet { setNeedsUpdate() } }
/// Text rendered for the content of the modal
open var content: String? { didSet { setNeedsUpdate() } }
/// UIView rendered for the content area of the modal
open var contentView: UIView? { didSet { setNeedsUpdate() } }
/// Array of Buttonable Views that are shown as Modal Footer. Primary and Close button data for modal button group.
open var buttonData: [ButtonBase]? { didSet { setNeedsUpdate() } }
/// If provided, the Modal has the option to be displayed at full screen.
open var fullScreenDialog: Bool = false { didSet { setNeedsUpdate() } }
/// If provided, close button can not be present.
open var hideCloseButton: Bool = false { didSet { setNeedsUpdate() } }
//--------------------------------------------------
// 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()
addSubview(showModalButton)
showModalButton.pinToSuperView()
backgroundColor = .clear
isAccessibilityElement = true
accessibilityTraits = .button
}
open override func setDefaults() {
super.setDefaults()
title = nil
content = nil
contentView = nil
buttonData = nil
fullScreenDialog = false
hideCloseButton = false
showModalButton.onClick = { _ in self.showModalButtonClick() }
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." : ""
}
}
internal func showModalButtonClick() {
self.presentModal(surface: self.surface,
modalModel: .init(closeButtonText: showModalButton.text ?? "",
title: title,
content: content,
contentView: contentView,
buttonData: buttonData,
fullScreenDialog: fullScreenDialog,
hideCloseButton: hideCloseButton),
presenter: self)
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()
showModalButton.surface = surface
}
public static func accessibleText(for title: String?, content: String?, closeButtonText: String) -> String {
var label = ""
if let title {
label = title
}
if let content {
if !label.isEmpty {
label += ","
}
label += content
}
return label
}
}
// MARK: AppleGuidelinesTouchable
extension Modal: AppleGuidelinesTouchable {
/// Overrides to ensure that the touch point meets a minimum of the minimumTappableArea.
override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
Self.acceptablyOutsideBounds(point: point, bounds: bounds)
}
}

View File

@ -1,67 +0,0 @@
MM/DD/YYYY
----------------
- Initial Brand 3.0 handoff
12/17/2021
----------------
- Replaced focusring colors (previously interactive/onlight/ondark) with accessibility/onlight/ondark colors
- Updated focus border name (previously interactive.focusring.onlight) with focusring.onlight/ondark
12/31/2021
----------------
- Updated Hover and Active state trigger specs. If triggered by mouse, Active same as Hover. If not, Active same as Default.
03/01/2022
----------------
- Replaced Close Non-Scaling icon with VDS Icon.
- Removed “vector effect” from Anatomy.
- Removed “weight” from Configurations.
08/10/2022
----------------
- Updated default and inverted prop to light and dark surface.
- Noted that button is optional within anatomy
09/06/2022
----------------
- Updated Anatomy element names to remove the word “Modal” from text elements, updated Button to be Button Group,
and noted Button Group as optional across all visuals within Anatomy.
11/30/2022
----------------
- Added "(web only)" to any instance of "keyboard focus"
12/13/2022
----------------
- Replaced focus border pixel and style & spacing values with tokens.
04/24/2023
----------------
- Updated all instances of Close Button (VDS Icon) with VDS Button Icon (size small)
- Button Icon placed 8px from top/right edge
- Use the Ghost variant of Button Icon
- Added Button Icon props to Elements spec
10/17/2023
----------------
- Added component tokens table
- Applied component tokens to light, dark surface configurations
11/22/2023
----------------
- Updated tab/desk visuals to reflect new corner radius value - 12px
- Updated border radius value in Anatomy
11/27/2023
----------------
- Updated border radius” to “corner radius” in Anatomy
12/1/2023
----------------
- Applied palette tokens instead of hardcoded values where component tokens included an opacity
- Removed layer opacity annotation for instances where opacity is built into a component token
07/18/2024
----------------
- Added Scrollbar hit area with z-index specifications to the Behaviors page
- Decreased the height of the Grab zone to equal the height of the scrollbar thumb on the Behaviors page

View File

@ -1,237 +0,0 @@
//
// ModalDialog.swift
// VDS
//
// Created by Kanamarlapudi, Vasavi on 09/09/24.
//
import Foundation
import UIKit
import VDSCoreTokens
@objc(VDSModalDialog)
open class ModalDialog: View, UIScrollViewDelegate, ParentViewProtocol {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var children: [any ViewProtocol] { [closeCrossButton, titleLabel, contentLabel, buttonGroupData] }
open var modalModel = Modal.ModalModel() { didSet { setNeedsUpdate() } }
open var titleLabel = Label().with { label in
label.isAccessibilityElement = true
label.textStyle = .boldTitleLarge
}
open var contentLabel = Label().with { label in
label.isAccessibilityElement = true
label.textStyle = .bodyLarge
}
open lazy var closeCrossButton = ButtonIcon().with {
$0.kind = .ghost
$0.surfaceType = .colorFill
$0.iconName = .close
$0.size = .small
$0.customContainerSize = UIDevice.isIPad ? 48 : 48
$0.customIconSize = UIDevice.isIPad ? 32 : 32
}
open var buttonGroupData = ButtonGroup().with {
$0.alignment = .left
}
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
private var scrollView = UIScrollView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.backgroundColor = .clear
}
private var contentStackView = UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.axis = .vertical
$0.alignment = .leading
$0.distribution = .fillProportionally
$0.spacing = 0
}
lazy var primaryAccessibilityElement = UIAccessibilityElement(accessibilityContainer: self).with {
$0.accessibilityLabel = "Modal"
}
// close button with the 48 x 48 px
private var closeCrossButtonSize = 48.0
private let containerViewInset = UIDevice.isIPad ? VDSLayout.space12X : VDSLayout.space4X
private let contentLabelTopSpace = UIDevice.isIPad ? VDSLayout.space8X : VDSLayout.space6X
private let contentLabelBottomSpace = UIDevice.isIPad ? VDSLayout.space8X : VDSLayout.space12X
private let gapBetweenButtonItems = VDSLayout.space3X
//--------------------------------------------------
// MARK: - Configuration Properties
//--------------------------------------------------
private let backgroundColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryDark)
private let textColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark)
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
private var contentStackViewBottomConstraint: NSLayoutConstraint?
//--------------------------------------------------
// 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()
titleLabel.accessibilityTraits = .header
layer.cornerRadius = 12
// Add titleLabel, contentLabel to contentStack.
contentStackView.addArrangedSubview(titleLabel)
contentStackView.addArrangedSubview(contentLabel)
contentStackView.setCustomSpacing(contentLabelTopSpace, after: titleLabel)
scrollView.addSubview(contentStackView)
// Add crossButon, scrollView, buttonsData.
addSubview(closeCrossButton)
addSubview(scrollView)
addSubview(buttonGroupData)
self.bringSubviewToFront(closeCrossButton)
let crossTopSpace = UIDevice.isIPad && !modalModel.fullScreenDialog ? 0 : VDSLayout.space12X
let scrollTopSpace = UIDevice.isIPad && !modalModel.fullScreenDialog ? containerViewInset : (crossTopSpace + closeCrossButtonSize)
let contentTrailingSpace = UIDevice.isIPad ? (containerViewInset/2) - 6 : containerViewInset
// Activate constraints
NSLayoutConstraint.activate([
// Constraints for the closeCrossButton
closeCrossButton.topAnchor.constraint(equalTo: topAnchor, constant: crossTopSpace),
closeCrossButton.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor),
closeCrossButton.trailingAnchor.constraint(equalTo: trailingAnchor),
closeCrossButton.heightAnchor.constraint(equalToConstant: closeCrossButtonSize),
// Constraints for the bottom button view
buttonGroupData.leadingAnchor.constraint(equalTo: leadingAnchor, constant:containerViewInset),
buttonGroupData.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -containerViewInset),
buttonGroupData.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -containerViewInset),
// Constraints for the scrollView
scrollView.topAnchor.constraint(equalTo: topAnchor, constant: scrollTopSpace),
scrollView.leadingAnchor.constraint(equalTo: leadingAnchor, constant:containerViewInset),
scrollView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -contentTrailingSpace),
scrollView.bottomAnchor.constraint(equalTo: buttonGroupData.topAnchor, constant: -contentLabelBottomSpace),
// Constraints for the contentStackView
contentStackView.topAnchor.constraint(equalTo: scrollView.topAnchor),
contentStackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
contentStackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -contentTrailingSpace),
contentStackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -contentTrailingSpace),
contentStackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor)
])
contentStackViewBottomConstraint = contentStackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor)
contentStackViewBottomConstraint?.activate()
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()
// Update surface and background
backgroundColor = backgroundColorConfiguration.getColor(self)
scrollView.indicatorStyle = surface == .light ? .black : .white
closeCrossButton.surface = surface
buttonGroupData.surface = surface
titleLabel.surface = surface
contentLabel.surface = surface
// Re-arrange contentStack
contentStackView.removeArrangedSubviews()
titleLabel.text = modalModel.title
contentLabel.text = modalModel.content
titleLabel.textColor = textColorConfiguration.getColor(self)
contentLabel.textColor = textColorConfiguration.getColor(self)
titleLabel.sizeToFit()
contentLabel.sizeToFit()
// Add buttons data if provided
if let buttons = modalModel.buttonData, buttons.count > 0 {
buttonGroupData.buttons = buttons
let percent = UIDevice.isIPad ? 50.0 : 100.0
buttonGroupData.rowQuantityTablet = 2
buttonGroupData.rowQuantityPhone = 1
buttonGroupData.childWidth = .percentage(percent)
}
// Update title, content and contentview
var addedTitle = false
if let titleText = modalModel.title, !titleText.isEmpty {
contentStackView.addArrangedSubview(titleLabel)
addedTitle = true
}
var addedContent = false
if let contentText = modalModel.content, !contentText.isEmpty {
contentStackView.addArrangedSubview(contentLabel)
addedContent = true
} else if let contentView = modalModel.contentView {
contentView.translatesAutoresizingMaskIntoConstraints = false
if var surfaceable = contentView as? Surfaceable {
surfaceable.surface = surface
}
contentStackView.addArrangedSubview(contentView)
addedContent = true
}
if addedTitle && addedContent {
contentStackView.spacing = contentLabelTopSpace
}
closeCrossButton.isHidden = modalModel.hideCloseButton
contentStackView.setNeedsLayout()
contentStackView.layoutIfNeeded()
scrollView.setNeedsLayout()
scrollView.layoutIfNeeded()
}
/// Used to update any Accessibility properties.
open override func updateAccessibility() {
super.updateAccessibility()
primaryAccessibilityElement.accessibilityHint = "Double tap on the cross 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(buttonGroupData)
return elements
}
set {}
}
}

View File

@ -1,123 +0,0 @@
//
// ModalDialogViewController.swift
// VDS
//
// Created by Kanamarlapudi, Vasavi on 09/09/24.
//
import Foundation
import UIKit
import Combine
import VDSCoreTokens
@objc(VDSModalDialogViewController)
open class ModalDialogViewController: 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()
}
}
}
private let modalDialog = ModalDialog()
//--------------------------------------------------
// 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 modalModel = Modal.ModalModel() { didSet { updateView() }}
open var presenter: UIView? { didSet { updateView() }}
//--------------------------------------------------
// MARK: - Configuration
//--------------------------------------------------
private let backgroundColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteBlack, VDSColor.paletteWhite)
//--------------------------------------------------
// 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: modalDialog)
}
private func dismiss() {
dismiss(animated: true) { [weak self] in
guard let self, let presenter else { return }
UIAccessibility.post(notification: .layoutChanged, argument: presenter)
}
}
open func setup() {
view.accessibilityElements = [modalDialog]
//left-right swipe
view.publisher(for: UISwipeGestureRecognizer().with{ $0.direction = .right })
.sink { [weak self] swipe in
guard let self, !UIAccessibility.isVoiceOverRunning else { return }
self.dismiss()
}.store(in: &subscribers)
//tapping in background
view.publisher(for: UITapGestureRecognizer().with{ $0.numberOfTapsRequired = 1 })
.sink { [weak self] swipe in
guard let self, !UIAccessibility.isVoiceOverRunning else { return }
self.dismiss()
}.store(in: &subscribers)
//clicking button
onClickSubscriber = modalDialog.closeCrossButton.publisher(for: .touchUpInside)
.sink {[weak self] button in
guard let self else { return }
self.dismiss()
}
view.addSubview(modalDialog)
}
/// Used to make changes to the View based off a change events or from local properties.
open func updateView() {
modalDialog.surface = surface
modalDialog.modalModel = modalModel
// Activate constraints
modalDialog.removeConstraints()
let isFullScreen = UIDevice.isIPad && !modalModel.fullScreenDialog ? false : true
if isFullScreen {
view.backgroundColor = modalDialog.backgroundColor
modalDialog
.pinLeading()
.pinTrailing()
modalDialog.pinTop(anchor: UIDevice.isIPad ? view.safeAreaLayoutGuide.topAnchor : view.topAnchor)
modalDialog.pinBottom(UIDevice.isIPad ? view.bottomAnchor : view.safeAreaLayoutGuide.bottomAnchor)
} else {
view.backgroundColor = backgroundColorConfiguration.getColor(self).withAlphaComponent(0.8)
NSLayoutConstraint.activate([
// Constraints for the floating modal view for Tablet.
modalDialog.centerXAnchor.constraint(equalTo: view.centerXAnchor),
modalDialog.centerYAnchor.constraint(equalTo: view.centerYAnchor),
modalDialog.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.7),
modalDialog.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.7)
])
}
}
}

View File

@ -1,28 +0,0 @@
//
// ModalLaunchable.swift
// VDS
//
// Created by Kanamarlapudi, Vasavi on 09/09/24.
//
import Foundation
import UIKit
public protocol ModalLaunchable {
func presentModal(surface: Surface, modalModel: Modal.ModalModel, presenter: UIView?)
}
extension ModalLaunchable {
public func presentModal(surface: Surface, modalModel: Modal.ModalModel, presenter: UIView? = nil) {
if let presenting = UIApplication.topViewController() {
let modalViewController = ModalDialogViewController(nibName: nil, bundle: nil).with {
$0.surface = surface
$0.modalModel = modalModel
$0.presenter = presenter
$0.modalPresentationStyle = UIDevice.isIPad && !modalModel.fullScreenDialog ? .overCurrentContext : .fullScreen
$0.modalTransitionStyle = .crossDissolve
}
presenting.present(modalViewController, animated: true)
}
}
}

View File

@ -1,45 +0,0 @@
//
// ModalModel.swift
// VDS
//
// Created by Kanamarlapudi, Vasavi on 09/09/24.
//
import Foundation
import UIKit
extension Modal {
/// Model used to represent the modal.
public struct ModalModel: Equatable {
/// Current Surface and this is used to pass down to child objects that implement Surfacable
public var closeButtonText: String
public var title: String?
public var content: String?
public var contentView: UIView?
public var accessibleText: String?
public var contentViewAlignment: UIStackView.Alignment?
public var buttonData: [ButtonBase]?
public var fullScreenDialog: Bool
public var hideCloseButton: Bool
public init(closeButtonText: String = "Close",
title: String? = nil,
content: String? = nil,
contentView: UIView? = nil,
buttonData: [ButtonBase]? = nil,
fullScreenDialog: Bool = false,
hideCloseButton: Bool = false,
accessibleText: String? = "Modal",
contentViewAlignment: UIStackView.Alignment = .leading) {
self.closeButtonText = closeButtonText
self.title = title
self.content = content
self.contentView = contentView
self.accessibleText = accessibleText
self.contentViewAlignment = contentViewAlignment
self.buttonData = buttonData
self.fullScreenDialog = fullScreenDialog
self.hideCloseButton = hideCloseButton
}
}
}

View File

@ -15,7 +15,7 @@ import Combine
/// with different color and content. They may be screen-specific, flow-specific or
/// experience-wide.
@objc(VDSNotification)
open class Notification: View, ParentViewProtocol {
open class Notification: View {
//--------------------------------------------------
// MARK: - Initializers
@ -100,8 +100,6 @@ open class Notification: View, ParentViewProtocol {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var children: [any ViewProtocol] { [typeIcon, closeButton, titleLabel, subTitleLabel, primaryButton, secondaryButton] }
/// Icon used for denoting type.
open var typeIcon = Icon().with {
$0.name = .infoBold
@ -269,15 +267,26 @@ open class Notification: View, ParentViewProtocol {
closeButton.accessibilityTraits = [.button]
closeButton.accessibilityLabel = "Close Notification"
typeIcon.bridge_accessibilityLabelBlock = { [weak self] in
guard let self else { return "" }
return style.accessibleText
}
}
open override func setDefaults() {
super.setDefaults()
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
titleLabel.reset()
titleLabel.text = ""
titleLabel.textStyle = UIDevice.isIPad ? .boldBodyLarge : .boldBodySmall
subTitleLabel.reset()
subTitleLabel.textStyle = UIDevice.isIPad ? .bodyLarge : .bodySmall
buttonGroup.reset()
buttonGroup.alignment = .left
primaryButtonModel = nil
@ -292,19 +301,9 @@ open class Notification: View, ParentViewProtocol {
closeButton.name = .close
hideCloseButton = false
typeIcon.bridge_accessibilityLabelBlock = { [weak self] in
guard let self else { return "" }
return style.accessibleText
}
}
/// Resets to default settings.
open override func reset() {
titleLabel.reset()
subTitleLabel.reset()
buttonGroup.reset()
super.reset()
shouldUpdateView = true
setNeedsUpdate()
}
/// Used to make changes to the View based off a change events or from local properties.

View File

@ -8,16 +8,12 @@
import Foundation
extension Notification {
public struct ButtonModel: Equatable {
public struct ButtonModel {
public var text: String
public var onClick: (Button) -> ()
public init(text: String, onClick: @escaping (Button) -> Void) {
self.text = text
self.onClick = onClick
}
public static func == (lhs: Notification.ButtonModel, rhs: Notification.ButtonModel) -> Bool {
lhs.text == rhs.text
}
}
}

View File

@ -17,8 +17,6 @@ open class Pagination: View {
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
private let pageChangedSubject = PassthroughSubject<Pagination, Never>()
///Maximum component width
private let maxWidth: CGFloat = 288.0
///Collectionview width anchor
@ -53,14 +51,12 @@ open class Pagination: View {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
public var pageChangedPublisher: AnyPublisher<Pagination, Never> { pageChangedSubject.eraseToAnyPublisher() }
///Previous button to select previous page
public let previousButton: PaginationButton = .init(type: .previous)
///Next button to select next page
public let nextButton: PaginationButton = .init(type: .next)
/// A callback when the page changes. Passes parameters (selectedPage).
open var onPageDidSelect: ((Int) -> Void)?
public var onPageDidSelect: ((Int) -> Void)?
/// Total number of pages, allows limit ranging from 0 to 9999.
@Clamping(range: 0...9999)
public var total: Int {
@ -73,7 +69,7 @@ open class Pagination: View {
}
}
///Selected active page number and clips to total pages if selected index is greater than the total pages.
open var selectedPage: Int {
public var selectedPage: Int {
set {
if newValue >= total {
_selectedPageIndex = total - 1
@ -98,8 +94,8 @@ open class Pagination: View {
// MARK: - Overrides
//--------------------------------------------------
/// Executed on initialization for this View.
open override func setup() {
super.setup()
open override func initialSetup() {
super.initialSetup()
collectionContainerView.addSubview(collectionView)
containerView.addSubview(previousButton)
@ -148,15 +144,6 @@ open class Pagination: View {
.sink { [weak self] value in
self?.collectionViewWidthAnchor?.constant = value //As cell width is dynamic i.e cell may contain 2 or 3 or 4 charcters. Make sure that all the visible cells are displayed.
}.store(in: &subscribers)
pageChangedPublisher.sink { [weak self] control in
guard let self else { return }
onPageDidSelect?(control.selectedPage)
}.store(in: &subscribers)
}
open override func setDefaults() {
super.setDefaults()
collectionContainerView.onAccessibilityIncrement = { [weak self] in
guard let self else { return }
self.selectedPage = max(0, self.selectedPage + 1)
@ -203,7 +190,6 @@ open class Pagination: View {
let isNextAction = sender == nextButton
_selectedPageIndex = if isNextAction { _selectedPageIndex + 1 } else { _selectedPageIndex - 1 }
updateSelection()
pageChangedSubject.send(self)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
guard let self else { return }
UIAccessibility.post(notification: .announcement, argument: paginationDescription)
@ -258,7 +244,7 @@ extension Pagination: UICollectionViewDelegate, UICollectionViewDataSource, UICo
guard _selectedPageIndex != indexPath.row else { return }
_selectedPageIndex = indexPath.row
updateSelection()
pageChangedSubject.send(self)
onPageDidSelect?(selectedPage)
}
}

View File

@ -59,8 +59,8 @@ open class PaginationButton: ButtonBase {
// MARK: - Overrides
//--------------------------------------------------
/// Executed on initialization for this View.
open override func setup() {
super.setup()
open override func initialSetup() {
super.initialSetup()
if #available(iOS 15.0, *) {
configuration = buttonConfiguration
} else {

View File

@ -1,352 +0,0 @@
//
// PriceLockup.swift
// VDS
//
// Created by Kanamarlapudi, Vasavi on 06/08/24.
//
import Foundation
import UIKit
import VDSCoreTokens
@objc(VDSPriceLockup)
open class PriceLockup: View, ParentViewProtocol {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
//--------------------------------------------------
// MARK: - Enums
//--------------------------------------------------
/// Enum used to describe the term of PriceLockup.
public enum Term: String, DefaultValuing, CaseIterable {
case month, year, biennial, none
/// The default term is 'month'.
public static var defaultValue : Self { .month }
/// Text for this term of PriceLockup.
public var type: String {
switch self {
case .month:
return "mo"
case .year:
return "yr"
case .biennial:
return "biennial"
case .none:
return ""
}
}
}
/// Enum that represents the size availble for PriceLockup.
public enum Size: String, DefaultValuing, CaseIterable {
case xxxsmall = "3XSmall"
case xxsmall = "2XSmall"
case xsmall = "XSmall"
case small
case medium
case large
case xlarge = "XLarge"
case xxlarge = "2XLarge"
public static var defaultValue: Self { .medium }
}
/// Enum used to describe the kind of PriceLockup.
public enum Kind: String, DefaultValuing, CaseIterable {
case primary, secondary, savings
/// The default kind is 'primary'.
public static var defaultValue : Self { .primary }
/// Color configuation relative to kind.
public var colorConfiguration: SurfaceColorConfiguration {
switch self {
case .primary:
return SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark)
case .secondary:
return SurfaceColorConfiguration(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark)
case .savings:
return SurfaceColorConfiguration(VDSColor.paletteGreen26, VDSColor.paletteGreen36)
}
}
}
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var children: [any ViewProtocol] { [priceLockupLabel] }
/// If true, the component will render as bold.
open var bold: Bool = false { didSet { setNeedsUpdate() } }
/// Currency - If hideCurrency true, the component will render without currency.
open var hideCurrency: Bool = false { didSet { setNeedsUpdate() } }
/// Leading text for the component.
open var leadingText: String? { didSet { setNeedsUpdate() } }
/// Value rendered for the component.
open var price: Float? { didSet { setNeedsUpdate() } }
/// Color to the component. The default kind is primary.
open var kind: Kind = .defaultValue { didSet { setNeedsUpdate() } }
/// Size of the component. It varies by size and viewport(mobile/Tablet).
/// The default size is medium with viewport mobile.
open var size: Size = .defaultValue { didSet { setNeedsUpdate() } }
/// If true, the component with a strikethrough. It applies only when uniformSize is true.
/// Does not apply a strikethrough format to leading and trailing text.
open var strikethrough: Bool = false { didSet { setNeedsUpdate() } }
/// Term text for the component. The default term is 'month'.
/// Superscript placement can vary when term and delimeter are "none".
open var term: Term = .defaultValue { didSet { setNeedsUpdate() } }
/// Trailing text for the component.
open var trailingText: String? { didSet { setNeedsUpdate() } }
/// Superscript text for the component.
open var superscript: String? { didSet { setNeedsUpdate() } }
/// If true, currency and value have the same font text style as delimeter, term label and superscript.
/// This will render the pricing and term sections as a uniform size.
open var uniformSize: Bool = false { didSet { setNeedsUpdate() } }
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
internal var priceLockupLabel = Label().with {
$0.isAccessibilityElement = true
$0.lineBreakMode = .byWordWrapping
}
internal var delimiterIndex = 0
internal var strikethroughLocation = 0
internal var strikethroughLength = 0
internal var strikethroughAccessibilityText: String = "price not offering anymore"
internal var textPosition:TextPosition = .preDelimiter
enum TextPosition: String, CaseIterable {
case preDelimiter, postDelimiter
}
//--------------------------------------------------
// MARK: - Configuration Properties
//--------------------------------------------------
internal var containerSize: CGSize { CGSize(width: 45, height: 44) }
// TextStyle for the size.
private var textStyle: TextStyle.StandardStyle {
switch (size, textPosition) {
case (.xxxsmall, .preDelimiter), (.xxxsmall, .postDelimiter):
return .micro
case (.xxsmall, .preDelimiter), (.xxsmall, .postDelimiter):
return .bodySmall
case (.xsmall, .preDelimiter), (.xsmall, .postDelimiter):
return .bodyMedium
case (.small, .preDelimiter), (.small, .postDelimiter):
return .bodyLarge
case (.medium, .preDelimiter):
return UIDevice.isIPad ? .titleSmall : .titleMedium
case (.medium, .postDelimiter):
return .bodyLarge
case (.large, .preDelimiter):
return UIDevice.isIPad ? .titleMedium : .titleLarge
case (.large, .postDelimiter):
return UIDevice.isIPad ? .titleSmall : .titleMedium
case (.xlarge, .preDelimiter):
return UIDevice.isIPad ? .titleLarge : .titleXLarge
case (.xlarge, .postDelimiter):
return UIDevice.isIPad ? .titleMedium : .titleLarge
case (.xxlarge, .preDelimiter):
return UIDevice.isIPad ? .titleXLarge : .featureSmall
case (.xxlarge, .postDelimiter):
return UIDevice.isIPad ? .titleLarge : .titleXLarge
}
}
//--------------------------------------------------
// 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()
// Price lockup label
addSubview(priceLockupLabel)
priceLockupLabel.pinToSuperView()
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()
priceLockupLabel.text = formatText()
priceLockupLabel.surface = surface
// Set the attributed text
updateLabelAttributes()
}
open override func setDefaults() {
super.setDefaults()
priceLockupLabel.bridge_accessibilityLabelBlock = { [weak self] in
guard let self else { return "" }
var accessibilityLabels = [String]()
if let text = priceLockupLabel.text, !text.isEmpty {
if strikethrough, strikethroughLength > 0 {
let preText = text.substring(to: strikethroughLocation)
let postText = text.substring(from: strikethroughLocation)
accessibilityLabels.append(preText)
accessibilityLabels.append(strikethroughAccessibilityText)
accessibilityLabels.append(postText)
} else {
accessibilityLabels.append(text)
}
}
return accessibilityLabels.joined(separator: " ")
}
bold = false
hideCurrency = false
leadingText = nil
price = nil
kind = .defaultValue
size = .defaultValue
strikethrough = false
term = .defaultValue
trailingText = nil
superscript = nil
uniformSize = false
}
/// Resets to default settings.
open override func reset() {
priceLockupLabel.reset()
super.reset()
}
//--------------------------------------------------
// MARK: - Private Methods
//--------------------------------------------------
// Update PriceLockup text attributes
func updateLabelAttributes() {
var attributes: [any LabelAttributeModel] = []
attributes.append(ColorLabelAttribute(location: 0,
length: priceLockupLabel.text.count,
color: kind.colorConfiguration.getColor(self)))
textPosition = .postDelimiter
if strikethrough {
// strike applies only when uniformSize true. Does not apply a strikethrough format to leading, trailing, and superscript text.
attributes.append(TextStyleLabelAttribute(location: 0,
length: priceLockupLabel.text.count,
textStyle: bold ? textStyle.bold : textStyle.regular,
textPosition: .left))
attributes.append(StrikeThroughLabelAttribute(location:strikethroughLocation, length: strikethroughLength))
} else if uniformSize {
// currency and value have the same font text style as delimeter, term, trailing text and superscript.
attributes.append(TextStyleLabelAttribute(location: 0,
length: priceLockupLabel.text.count,
textStyle: bold ? textStyle.bold : textStyle.regular,
textPosition: .left))
} else {
// size updates relative to predelimiter, postdelimiter
if delimiterIndex > 0 {
textPosition = .preDelimiter
attributes.append(TextStyleLabelAttribute(location: 0,
length: delimiterIndex,
textStyle: bold ? textStyle.bold : textStyle.regular,
textPosition: .left))
textPosition = .postDelimiter
attributes.append(TextStyleLabelAttribute(location: delimiterIndex,
length: priceLockupLabel.text.count-delimiterIndex,
textStyle: bold ? textStyle.bold : textStyle.regular,
textPosition: .left))
}
}
priceLockupLabel.attributes = attributes
}
// Get text for PriceLockup.
private func formatText() -> String {
var text : String = ""
let space = " "
let delimiter = "/"
delimiterIndex = 0
strikethroughLength = 0
let currency: String = hideCurrency ? "" : "$"
if let leadingText {
text.append(leadingText)
text.append(space)
delimiterIndex = delimiterIndex + leadingText.count + space.count
}
strikethroughLocation = delimiterIndex
if let price = price?.clean {
text.append(currency)
text.append(price)
delimiterIndex = delimiterIndex + price.count + currency.count
strikethroughLength = price.count + currency.count
}
if term != .none {
text.append(delimiter)
text.append(term.type)
strikethroughLength = strikethroughLength + delimiter.count + term.type.count
}
if let trailingText {
text.append(space)
text.append(trailingText)
}
if let superscript {
text.append(superscript)
}
return text
}
}
extension Float {
// remove a decimal from a float if the decimal is equal to 0
var clean: String {
return self.truncatingRemainder(dividingBy: 1) == 0 ? String(format: "%.0f", self) : String(describing: self)
}
}

View File

@ -1,28 +0,0 @@
MM/DD/YYYY
----------------
11/16/2023
----------------
- Added leadingText and trailingText to anatomy
- Added leadingText and trailingText props to configurations
- Added term prop to configurations
- Removed Suspended orange color and corresponding color tokens
11/27/2023
----------------
- Removed “Delimiter” from Anatomy as “Term” includes both delimiter and term
- Added “Figma only” badge to leadingText and trailingText in Configurations
- Added superscript to “none” under term in Configurations
- Added Overflow section to Layout and spacing
- Updated Spacing to allow for leading and trailing text
12/18/23
----------------
- Updated all pages with spec template updates from Doc Utility Expansion Pack
- Added Content props section to Config page
1/15/24
----------------
- Clarified strikethrough does not apply to leading or trailing text
- Clarified and added to text overflow examples
- Correct Success to Savings in the configuration seciton

View File

@ -48,7 +48,7 @@ open class RadioBoxGroup: SelectorGroupBase<RadioBoxItem>, SelectorGroupSingleSe
$0.subTextAttributes = model.subTextAttributes
$0.subTextRight = model.subTextRight
$0.subTextRightAttributes = model.subTextRightAttributes
$0.isEnabled = model.enabled
$0.isEnabled = !model.disabled
$0.inputId = model.inputId
$0.hiddenValue = model.value
$0.isSelected = model.selected
@ -66,26 +66,17 @@ open class RadioBoxGroup: SelectorGroupBase<RadioBoxItem>, SelectorGroupSingleSe
// MARK: - Overrides
//--------------------------------------------------
private func ensureDevice() {
var axis: NSLayoutConstraint.Axis = .vertical
var distribution: UIStackView.Distribution = .fill
defer {
mainStackView.axis = axis
mainStackView.distribution = distribution
}
if UIDevice.isIPad {
axis = .horizontal
distribution = .fillEqually
mainStackView.axis = .horizontal
mainStackView.distribution = .fillEqually
} else {
guard let supportedOrientations = UIApplication.shared.windows.first?.rootViewController?.supportedInterfaceOrientations else {
return
}
let orientation = UIDevice.current.orientation
if supportedOrientations.contains(.landscape) && (orientation == .landscapeLeft || orientation == .landscapeRight) {
axis = .horizontal
distribution = .fillEqually
if UIDevice.current.orientation.isPortrait || UIDevice.current.orientation == .unknown {
mainStackView.axis = .vertical
mainStackView.distribution = .fill
} else {
mainStackView.axis = .horizontal
mainStackView.distribution = .fillEqually
}
}
}
@ -112,9 +103,9 @@ open class RadioBoxGroup: SelectorGroupBase<RadioBoxItem>, SelectorGroupSingleSe
}
extension RadioBoxGroup {
public struct RadioBoxItemModel: Surfaceable, Initable, FormFieldable, Equatable {
/// Whether this object is enabled or not
public var enabled: Bool
public struct RadioBoxItemModel: Surfaceable, Initable, FormFieldable {
/// Whether this object is disabled or not
public var disabled: Bool
/// Current Surface and this is used to pass down to child objects that implement Surfacable
public var surface: Surface
public var inputId: String?
@ -133,12 +124,12 @@ extension RadioBoxGroup {
public var strikethrough: Bool = false
public var strikethroughAccessibileText: String
public init(enabled: Bool, surface: Surface = .light, inputId: String? = nil, value: String? = 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,
selected: Bool = false, errorText: String? = nil, accessibileText: String? = nil, strikethrough: Bool = false, strikethroughAccessibileText: String = "not available") {
self.enabled = enabled
self.disabled = disabled
self.surface = surface
self.inputId = inputId
self.value = value
@ -155,24 +146,7 @@ extension RadioBoxGroup {
}
public init() {
self.init(enabled: true)
}
public static func == (lhs: RadioBoxGroup.RadioBoxItemModel, rhs: RadioBoxGroup.RadioBoxItemModel) -> Bool {
lhs.enabled == rhs.enabled
&& lhs.surface == rhs.surface
&& lhs.inputId == rhs.inputId
&& lhs.value == rhs.value
&& lhs.accessibileText == rhs.accessibileText
&& lhs.text == rhs.text
&& lhs.textAttributes == rhs.textAttributes
&& lhs.subText == rhs.subText
&& lhs.subTextAttributes == rhs.subTextAttributes
&& lhs.subTextRight == rhs.subTextRight
&& lhs.subTextRightAttributes == rhs.subTextRightAttributes
&& lhs.selected == rhs.selected
&& lhs.strikethrough == rhs.strikethrough
&& lhs.strikethroughAccessibileText == rhs.strikethroughAccessibileText
self.init(disabled: false)
}
}
}

View File

@ -13,7 +13,7 @@ import VDSCoreTokens
/// Radio boxes are single-select components through which a customer indicates a choice
/// that are used within a ``RadioBoxGroup``.
@objc(VDSRadioBoxItem)
open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable, ParentViewProtocol {
open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable {
//--------------------------------------------------
// MARK: - Initializers
@ -52,8 +52,6 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable, ParentVi
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var children: [any ViewProtocol] { [textLabel, subTextLabel, subTextRightLabel, selectorView] }
open var onChangeSubscriber: AnyCancellable?
/// Label used to render the text.
@ -166,38 +164,11 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable, ParentVi
//--------------------------------------------------
// 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()
isAccessibilityElement = false
selectorView.isAccessibilityElement = true
selectorView.accessibilityTraits = .button
addSubview(selectorView)
selectorView.isUserInteractionEnabled = false
selectorView.addSubview(selectorStackView)
selectorStackView.addArrangedSubview(selectorLeftLabelStackView)
selectorStackView.addArrangedSubview(subTextRightLabel)
selectorLeftLabelStackView.addArrangedSubview(textLabel)
selectorLeftLabelStackView.addArrangedSubview(subTextLabel)
selectorView
.pinTop()
.pinLeading()
.pinTrailing(0, .defaultHigh)
.pinBottom(0, .defaultHigh)
selectorStackView.pinToSuperView(.uniform(VDSLayout.space3X))
}
open override func setDefaults() {
super.setDefaults()
onClick = { [weak self] _ in
guard let self, isEnabled else { return }
toggle()
/// Executed on initialization for this View.
open override func initialSetup() {
super.initialSetup()
onClick = { control in
control.toggle()
}
selectorView.bridge_accessibilityLabelBlock = { [weak self] in
@ -232,7 +203,43 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable, ParentVi
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 = false
selectorView.isAccessibilityElement = true
selectorView.accessibilityTraits = .button
addSubview(selectorView)
selectorView.isUserInteractionEnabled = false
selectorView.addSubview(selectorStackView)
selectorStackView.addArrangedSubview(selectorLeftLabelStackView)
selectorStackView.addArrangedSubview(subTextRightLabel)
selectorLeftLabelStackView.addArrangedSubview(textLabel)
selectorLeftLabelStackView.addArrangedSubview(subTextLabel)
selectorView
.pinTop()
.pinLeading()
.pinTrailing(0, .defaultHigh)
.pinBottom(0, .defaultHigh)
selectorStackView.pinToSuperView(.uniform(VDSLayout.space3X))
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
textLabel.reset()
subTextLabel.reset()
subTextRightLabel.reset()
textLabel.textStyle = .boldBodyLarge
subTextLabel.textStyle = .bodyLarge
subTextRightLabel.textStyle = .bodyLarge
@ -252,14 +259,9 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable, ParentVi
isSelected = false
onChange = nil
}
/// Resets to default settings.
open override func reset() {
textLabel.reset()
subTextLabel.reset()
subTextRightLabel.reset()
super.reset()
shouldUpdateView = true
setNeedsUpdate()
}
/// This will change the state of the Selector and execute the actionBlock if provided.

View File

@ -42,7 +42,7 @@ open class RadioButtonGroup: SelectorGroupBase<RadioButtonItem>, SelectorGroupSi
if let selectorModels {
items = selectorModels.enumerated().map { index, model in
return RadioButtonItem().with {
$0.isEnabled = model.enabled
$0.isEnabled = !model.disabled
$0.surface = model.surface
$0.inputId = model.inputId
$0.hiddenValue = model.value
@ -76,13 +76,12 @@ open class RadioButtonGroup: SelectorGroupBase<RadioButtonItem>, SelectorGroupSi
}
}
open override func setDefaults() {
super.setDefaults()
inputId = nil
open override func reset() {
super.reset()
showError = false
}
open override func didSelect(_ selectedControl: RadioButtonItem) {
public override func didSelect(_ selectedControl: RadioButtonItem) {
if let selectedItem {
updateToggle(selectedItem)
}
@ -102,10 +101,10 @@ open class RadioButtonGroup: SelectorGroupBase<RadioButtonItem>, SelectorGroupSi
}
extension RadioButtonGroup {
public struct RadioButtonItemModel: Surfaceable, Initable, FormFieldable, Errorable, Equatable {
public struct RadioButtonItemModel: Surfaceable, Initable, FormFieldable, Errorable {
/// Whether this object is enabled or not
public var enabled: Bool
/// Whether this object is disabled or not
public var disabled: Bool
/// Current Surface and this is used to pass down to child objects that implement Surfacable
public var surface: Surface
public var inputId: String?
@ -122,8 +121,8 @@ extension RadioButtonGroup {
public var showError: Bool
public var errorText: String?
public init(enabled: Bool, surface: Surface = .light, inputId: String? = nil, value: AnyHashable? = nil, accessibileText: String? = nil, labelText: String? = nil, labelTextAttributes: [any LabelAttributeModel]? = nil, childText: String? = nil, childTextAttributes: [any LabelAttributeModel]? = nil, selected: Bool = false, showError: Bool = false, errorText: String? = nil) {
self.enabled = enabled
public init(disabled: Bool, surface: Surface = .light, inputId: String? = nil, value: AnyHashable? = nil, accessibileText: String? = nil, labelText: String? = nil, labelTextAttributes: [any LabelAttributeModel]? = nil, childText: String? = nil, childTextAttributes: [any LabelAttributeModel]? = nil, selected: Bool = false, showError: Bool = false, errorText: String? = nil) {
self.disabled = disabled
self.surface = surface
self.inputId = inputId
self.value = value
@ -138,22 +137,7 @@ extension RadioButtonGroup {
}
public init() {
self.init(enabled: true)
}
public static func == (lhs: RadioButtonGroup.RadioButtonItemModel, rhs: RadioButtonGroup.RadioButtonItemModel) -> Bool {
lhs.enabled == rhs.enabled
&& lhs.surface == rhs.surface
&& lhs.inputId == rhs.inputId
&& lhs.value == rhs.value
&& lhs.accessibileText == rhs.accessibileText
&& lhs.labelText == rhs.labelText
&& lhs.labelTextAttributes == rhs.labelTextAttributes
&& lhs.childText == rhs.childText
&& lhs.childTextAttributes == rhs.childTextAttributes
&& lhs.selected == rhs.selected
&& lhs.showError == rhs.showError
&& lhs.errorText == rhs.errorText
self.init(disabled: false)
}
}
}

View File

@ -26,6 +26,7 @@ open class Table: View {
$0.allowsSelection = false
$0.showsVerticalScrollIndicator = false
$0.showsHorizontalScrollIndicator = false
$0.isAccessibilityElement = true
$0.backgroundColor = .clear
}
@ -90,8 +91,8 @@ open class Table: View {
//--------------------------------------------------
///Called upon initializing the table view
open override func setup() {
super.setup()
open override func initialSetup() {
super.initialSetup()
addSubview(matrixView)
matrixView.pinToSuperView()
}
@ -107,16 +108,18 @@ open class Table: View {
matrixView.collectionViewLayout.invalidateLayout()
}
open override func setDefaults() {
super.setDefaults()
/// Resets to default settings.
open override func reset() {
super.reset()
striped = false
padding = .standard
tableHeader = []
tableRows = []
fillContainer = true
columnWidths = nil
setNeedsUpdate()
}
func calculateColumnWidths() -> [CGFloat] {
guard let noOfColumns = tableData.first?.columnsCount else { return [] }
let itemWidth = floor(matrixView.safeAreaLayoutGuide.layoutFrame.width / CGFloat(noOfColumns))
@ -146,7 +149,6 @@ extension Table: UICollectionViewDelegate, UICollectionViewDataSource, TableColl
var edgePadding = UIEdgeInsets(top: padding.verticalValue(), left: 0, bottom: padding.verticalValue(), right: padding.horizontalValue())
edgePadding.left = (indexPath.row == 0 && !striped) ? VDSLayout.space1X : padding.horizontalValue()
cell.updateCell(content: currentItem, surface: surface, striped: shouldStrip, padding: edgePadding, isHeader: isHeader)
setAccessibilityForCell(cell: cell, content: currentItem, path: indexPath)
return cell
}
@ -161,38 +163,4 @@ extension Table: UICollectionViewDelegate, UICollectionViewDataSource, TableColl
func collectionView(_ collectionView: UICollectionView, widthForItemAt indexPath: IndexPath) -> CGFloat {
return columnWidths?[indexPath.row] ?? 0.0
}
//--------------------------------------------------
// MARK: - Accessibility
//--------------------------------------------------
/// To set the accessibility label for the each cell based on the criteria. Table name along with total no of column & row information should be passed in the first cell's accessibility label.
private func setAccessibilityForCell(cell: TableCellItem, content: TableItemModel, path: IndexPath) {
var accLabel = content.component?.accessibilityLabel ?? "Empty"
///Set the type of header label
if path.section == 0 {
accLabel.append(", Column Header")
} else if path.row == 0 {
///As per design team, inspite of column 0 may not look like a header, it should be read as header.
accLabel.append(", Row Header")
}
///Set the Row/Column number for each cell
if path.row == 0 {
accLabel.append(", Row \(path.section + 1), Column \(path.row + 1)")
} else {
accLabel.append(", Column \(path.row + 1)")
}
///Set the Row header accessibilityLabel at the end of the non-header cells accessibilityLabel
if path.section != 0,
path.row != 0,
let columnHeaderAccLabel = tableHeader.first?.columns[path.row].component?.accessibilityLabel {
accLabel.append(", \(columnHeaderAccLabel)")
}
cell.accessibilityLabel = accLabel
}
}

View File

@ -45,7 +45,6 @@ final class TableCellItem: UICollectionViewCell {
private func setupCell() {
contentView.backgroundColor = .clear
isAccessibilityElement = true
addSubview(containerView)
containerView.pinToSuperView()

View File

@ -112,7 +112,8 @@ class MatrixFlowLayout : UICollectionViewFlowLayout {
/// Fetches estimated height by calling the cell's component estimated height and adding padding
private func estimateHeightFor(item: TableItemModel, with width: CGFloat, index: IndexPath) -> CGFloat {
let itemWidth = width - layoutPadding.horizontalValue() - (index.row == 0 ? defaultLeadingPadding:layoutPadding.horizontalValue())
let horizontalPadding = (index.row == 0 && !striped) ? (VDSLayout.space1X + layoutPadding.horizontalValue()) : (2 * layoutPadding.horizontalValue())
let itemWidth = width - layoutPadding.horizontalValue() - defaultLeadingPadding
let maxSize = CGSize(width: itemWidth, height: CGFloat.greatestFiniteMagnitude)
let estItemSize = item.component?.systemLayoutSizeFitting(maxSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) ?? CGSize(width: itemWidth, height: item.defaultHeight)
return estItemSize.height + (2 * layoutPadding.verticalValue())

View File

@ -10,7 +10,7 @@ import UIKit
import VDSCoreTokens
/// Model that represent the content of each cell of Table component
public struct TableItemModel: Equatable {
public struct TableItemModel {
public let defaultHeight: CGFloat = 50.0

View File

@ -7,7 +7,7 @@
import Foundation
public struct TableRowModel: Equatable {
public struct TableRowModel {
public var columns: [TableItemModel]

View File

@ -11,7 +11,8 @@ import VDSCoreTokens
import Combine
extension Tabs {
@objc(VDSTab)
@objc(VDSTab)
open class Tab: Control, Groupable {
//--------------------------------------------------
@ -149,10 +150,6 @@ extension Tabs {
labelLeadingConstraint = label.pinLeading(anchor: layoutGuide.leadingAnchor)
labelBottomConstraint = label.pinBottom(anchor: layoutGuide.bottomAnchor, priority: .defaultHigh)
}
open override func setDefaults() {
super.setDefaults()
bridge_accessibilityLabelBlock = { [weak self] in
guard let self else { return "" }
return text

View File

@ -8,7 +8,7 @@
import Foundation
extension Tabs {
public struct TabModel: Equatable {
public struct TabModel {
///Text that goes in the Tab
public var text: String
@ -24,10 +24,5 @@ extension Tabs {
self.onClick = onClick
self.width = width
}
public static func == (lhs: Tabs.TabModel, rhs: Tabs.TabModel) -> Bool {
lhs.text == rhs.text
&& lhs.width == rhs.width
}
}
}

View File

@ -11,7 +11,7 @@ import VDSCoreTokens
/// Tabs are organizational components that group content and allow customers to navigate its display. Use them to separate content when the content is related but doesnt need to be compared.
@objc(VDSTabs)
open class Tabs: View, ParentViewProtocol {
open class Tabs: View {
//--------------------------------------------------
// MARK: - Initializers
@ -83,8 +83,6 @@ open class Tabs: View, ParentViewProtocol {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var children: [any ViewProtocol] { tabViews }
/// A callback when the selectedIndex changes. Passes parameters (tabIndex).
open var onTabDidSelect: ((Int) -> Void)?
@ -222,11 +220,10 @@ open class Tabs: View, ParentViewProtocol {
super.layoutSubviews()
updateContentView()
}
open override func setDefaults() {
super.setDefaults()
onTabDidSelect = nil
onTabShouldSelect = nil
open override func reset() {
super.reset()
shouldUpdateView = false
orientation = .horizontal
borderLine = true
fillContainer = false
@ -237,9 +234,11 @@ open class Tabs: View, ParentViewProtocol {
selectedIndex = 0
size = .medium
sticky = false
tabModels = []
tabViews.forEach{ $0.reset() }
shouldUpdateView = true
setNeedsUpdate()
}
//--------------------------------------------------
// MARK: - Private Methods
//--------------------------------------------------

View File

@ -11,7 +11,9 @@ import VDSCoreTokens
import Combine
/// Base Class used to build out a Input controls.
open class EntryFieldBase<ValueType>: Control, Changeable, FormFieldInternalValidatable, ParentViewProtocol {
@objc(VDSEntryField)
open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
@ -105,12 +107,14 @@ open class EntryFieldBase<ValueType>: Control, Changeable, FormFieldInternalVali
// MARK: - Constraints
//--------------------------------------------------
internal var widthConstraint: NSLayoutConstraint?
internal var trailingEqualsConstraint: NSLayoutConstraint?
internal var trailingLessThanEqualsConstraint: NSLayoutConstraint?
//--------------------------------------------------
// MARK: - Configuration Properties
//--------------------------------------------------
// Sizes are from InVision design specs.
internal var maxWidth: CGFloat { constrainedWidth }
internal var maxWidth: CGFloat { frame.size.width }
internal var minWidth: CGFloat { containerSize.width }
internal var containerSize: CGSize { CGSize(width: minWidth, height: 44) }
@ -153,8 +157,6 @@ open class EntryFieldBase<ValueType>: Control, Changeable, FormFieldInternalVali
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var children: [any ViewProtocol] { [titleLabel, helperLabel, errorLabel, statusIcon] }
/// 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 {
@ -226,11 +228,11 @@ open class EntryFieldBase<ValueType>: Control, Changeable, FormFieldInternalVali
open var inputId: String? { didSet { setNeedsUpdate() } }
/// The text of this textField.
open var value: ValueType? {
open var value: String? {
get { fatalError("must be read from subclass")}
}
open var defaultValue: ValueType? { didSet { setNeedsUpdate() } }
open var defaultValue: AnyHashable? { didSet { setNeedsUpdate() } }
open var isRequired: Bool = false { didSet { setNeedsUpdate() } }
@ -242,7 +244,7 @@ open class EntryFieldBase<ValueType>: Control, Changeable, FormFieldInternalVali
}
}
open var rules = [AnyRule<ValueType>]()
open var rules = [AnyRule<String>]()
open var accessibilityHintText: String = "Double tap to open"
@ -256,9 +258,15 @@ open class EntryFieldBase<ValueType>: Control, Changeable, FormFieldInternalVali
let layoutGuide = UILayoutGuide()
addLayoutGuide(layoutGuide)
layoutGuide.pinToSuperView()
layoutGuide
.pinTop()
.pinLeading()
.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
@ -307,41 +315,6 @@ open class EntryFieldBase<ValueType>: Control, Changeable, FormFieldInternalVali
errorLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable()
helperLabel.textColorConfiguration = secondaryColorConfiguration.eraseToAnyColorable()
}
/// Updates the UI
open override func updateView() {
super.updateView()
updateRules()
updateContainerView()
updateContainerWidth()
updateTitleLabel()
updateErrorLabel()
updateHelperLabel()
}
open override func setDefaults() {
super.setDefaults()
titleLabel.textStyle = .bodySmall
errorLabel.textStyle = .bodySmall
helperLabel.textStyle = .bodySmall
labelText = nil
helperText = nil
showError = false
errorText = nil
tooltipModel = nil
transparentBackground = false
width = nil
inputId = nil
defaultValue = nil
isRequired = false
isReadOnly = false
helperTextPlacement = .bottom
rules = []
onChange = nil
containerView.bridge_accessibilityLabelBlock = { [weak self] in
guard let self else { return "" }
var accessibilityLabels = [String]()
@ -368,23 +341,50 @@ open class EntryFieldBase<ValueType>: Control, Changeable, FormFieldInternalVali
}
containerView.bridge_accessibilityValueBlock = { [weak self] in
guard let self, let value else { return "" }
return "\(value)"
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
open override func updateView() {
super.updateView()
updateRules()
updateContainerView()
updateContainerWidth()
updateTitleLabel()
updateErrorLabel()
updateHelperLabel()
}
/// Resets to default settings.
open override func reset() {
super.reset()
titleLabel.reset()
errorLabel.reset()
helperLabel.reset()
super.reset()
titleLabel.textStyle = .bodySmall
errorLabel.textStyle = .bodySmall
helperLabel.textStyle = .bodySmall
labelText = nil
helperText = nil
showError = false
errorText = nil
tooltipModel = nil
transparentBackground = false
width = nil
inputId = nil
defaultValue = nil
isRequired = false
isReadOnly = false
onChange = nil
}
open override var canBecomeFirstResponder: Bool {
@ -522,8 +522,8 @@ open class EntryFieldBase<ValueType>: Control, Changeable, FormFieldInternalVali
//--------------------------------------------------
internal func updateRules() {
rules.removeAll()
if isRequired && useRequiredRule && ValueType.self == String.self {
let rule = RequiredRule<ValueType>()
if isRequired && useRequiredRule {
let rule = RequiredRule()
if let errorText, !errorText.isEmpty {
rule.errorMessage = errorText
} else if let labelText{
@ -541,13 +541,18 @@ open class EntryFieldBase<ValueType>: Control, Changeable, FormFieldInternalVali
containerView.layer.borderWidth = VDSFormControls.borderWidth
containerView.layer.cornerRadius = VDSFormControls.borderRadius
}
internal func updateContainerWidth() {
widthConstraint?.deactivate()
trailingLessThanEqualsConstraint?.deactivate()
trailingEqualsConstraint?.deactivate()
if let width, width >= minWidth, width <= maxWidth {
widthConstraint?.constant = width
widthConstraint?.activate()
trailingLessThanEqualsConstraint?.activate()
} else {
trailingEqualsConstraint?.activate()
}
}

View File

@ -14,7 +14,7 @@ import Combine
/// Specialized input fields capture credit card numbers, inline actions, passwords, phone numbers,
/// dates and security codes in their correct formats.
@objc(VDSInputField)
open class InputField: EntryFieldBase<String> {
open class InputField: EntryFieldBase {
//--------------------------------------------------
// MARK: - Initializers
@ -46,9 +46,8 @@ open class InputField: EntryFieldBase<String> {
internal override var minWidth: CGFloat { fieldType.handler().minWidth }
internal override var maxWidth: CGFloat {
let frameWidth = constrainedWidth
let halfWidth = (frameWidth - horizontalStackView.spacing) / 2
return helperTextPlacement == .right && halfWidth > minWidth * 2 ? halfWidth : frameWidth
let frameWidth = frame.size.width
return helperTextPlacement == .right ? (frameWidth - horizontalStackView.spacing) / 2 : frameWidth
}
/// The is used for the for adding the helperLabel to the right of the containerView.
@ -205,22 +204,6 @@ open class InputField: EntryFieldBase<String> {
textField.textColorConfiguration = textFieldTextColorConfiguration
}
open override func getFieldContainer() -> UIView {
return textField
}
open override func setDefaults() {
super.setDefaults()
textField.text = ""
successLabel.textStyle = .bodySmall
fieldType = .text
showSuccess = false
successText = nil
containerView.bridge_accessibilityLabelBlock = { [weak self] in
guard let self else { return "" }
var accessibilityLabels = [String]()
@ -275,10 +258,22 @@ open class InputField: EntryFieldBase<String> {
}
}
open override func getFieldContainer() -> UIView {
return textField
}
/// Resets to default settings.
open override func reset() {
successLabel.reset()
super.reset()
textField.text = ""
successLabel.reset()
successLabel.textStyle = .bodySmall
fieldType = .text
showSuccess = false
successText = nil
helperTextPlacement = .bottom
}
/// Used to make changes to the View based off a change events or from local properties.
@ -316,21 +311,6 @@ open class InputField: EntryFieldBase<String> {
}
}
open var widthPercentage: CGFloat? { didSet { setNeedsUpdate() } }
internal override func updateContainerWidth() {
widthConstraint?.deactivate()
//see if there is a widthPercentage and follow the same pattern as done for "width"
let currentWidth = (horizontalPinnedWidth() ?? 0) * (widthPercentage ?? 0)
if currentWidth >= minWidth, currentWidth <= maxWidth {
widthConstraint?.constant = currentWidth
widthConstraint?.activate()
} else {
super.updateContainerWidth()
}
}
override func updateRules() {
super.updateRules()
fieldType.handler().appendRules(self)

View File

@ -97,22 +97,19 @@ open class TextField: UITextField, ViewProtocol, Errorable {
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
private func initialSetup() {
open func initialSetup() {
if !initialSetupPerformed {
initialSetupPerformed = true
shouldUpdateView = false
backgroundColor = .clear
translatesAutoresizingMaskIntoConstraints = false
setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
clipsToBounds = true
setup()
setDefaults()
shouldUpdateView = true
setNeedsUpdate()
}
}
open func setup() {
translatesAutoresizingMaskIntoConstraints = false
setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
clipsToBounds = true
let accessView = UIView(frame: .init(origin: .zero, size: .init(width: UIScreen.main.bounds.width, height: 44)))
accessView.backgroundColor = .white
accessView.addBorder(side: .top, width: 1, color: .lightGray)
@ -126,17 +123,6 @@ open class TextField: UITextField, ViewProtocol, Errorable {
inputAccessoryView = accessView
}
open func setDefaults() {
backgroundColor = .clear
surface = .light
text = nil
formatText = nil
useScaledFont = false
showError = false
errorText = nil
textStyle = .defaultStyle
}
@objc func doneButtonAction() {
// Resigns the first responder status when 'Done' is tapped
let _ = resignFirstResponder()
@ -187,7 +173,8 @@ open class TextField: UITextField, ViewProtocol, Errorable {
open func reset() {
shouldUpdateView = false
setDefaults()
surface = .light
text = nil
shouldUpdateView = true
setNeedsUpdate()
}

View File

@ -7,14 +7,12 @@
import Foundation
class RequiredRule<ValueType>: Rule {
class RequiredRule: Rule {
var maxLength: Int?
var errorMessage: String = "This field is required."
func isValid(value: ValueType?) -> Bool {
guard let value,
!"\(value)".isEmpty,
"\(value)".count > 0 else { return false }
func isValid(value: String?) -> Bool {
guard let value, !value.isEmpty, value.count > 0 else { return false }
return true
}
}

View File

@ -13,7 +13,7 @@ import Combine
/// A text area is an input wherein a customer enters long-form information.
/// Use a text area when you want customers to enter text thats longer than a single line.
@objc(VDSTextArea)
open class TextArea: EntryFieldBase<String> {
open class TextArea: EntryFieldBase {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
@ -165,19 +165,14 @@ open class TextArea: EntryFieldBase<String> {
bottomContainerStackView.spacing = VDSLayout.space2X
}
open override func setDefaults() {
super.setDefaults()
minHeight = .twoX
maxLength = nil
textView.text = ""
characterCounterLabel.textStyle = .bodySmall
}
/// Resets to default settings.
open override func reset() {
characterCounterLabel.reset()
super.reset()
textView.text = ""
characterCounterLabel.reset()
characterCounterLabel.textStyle = .bodySmall
setNeedsUpdate()
}
/// Used to make changes to the View based off a change events or from local properties.

View File

@ -106,20 +106,17 @@ open class TextView: UITextView, ViewProtocol, Errorable {
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
private func initialSetup() {
open func initialSetup() {
if !initialSetupPerformed {
initialSetupPerformed = true
shouldUpdateView = false
backgroundColor = .clear
translatesAutoresizingMaskIntoConstraints = false
setup()
setDefaults()
shouldUpdateView = true
setNeedsUpdate()
}
}
open func setup() {
translatesAutoresizingMaskIntoConstraints = false
let accessView = UIView(frame: .init(origin: .zero, size: .init(width: UIScreen.main.bounds.width, height: 44)))
accessView.backgroundColor = .white
accessView.addBorder(side: .top, width: 1, color: .lightGray)
@ -136,15 +133,6 @@ open class TextView: UITextView, ViewProtocol, Errorable {
placeholderLabel.pinToSuperView()
}
open func setDefaults() {
backgroundColor = .clear
surface = .light
text = nil
placeholder = nil
errorText = nil
showError = false
}
@objc func doneButtonAction() {
// Resigns the first responder status when 'Done' is tapped
resignFirstResponder()
@ -164,7 +152,8 @@ open class TextView: UITextView, ViewProtocol, Errorable {
open func reset() {
shouldUpdateView = false
setDefaults()
surface = .light
text = nil
shouldUpdateView = true
setNeedsUpdate()
}

View File

@ -14,7 +14,7 @@ import Combine
open class TileContainer: TileContainerBase<TileContainer.Padding> {
/// Enum used to describe the padding choices used for this component.
public enum Padding: DefaultValuing, Valuing {
public enum Padding: DefaultValuing {
case padding3X
case padding4X
case padding6X
@ -43,7 +43,7 @@ open class TileContainer: TileContainerBase<TileContainer.Padding> {
}
}
open class TileContainerBase<PaddingType: DefaultValuing & Valuing>: View where PaddingType.ValueType == CGFloat {
open class TileContainerBase<PaddingType: DefaultValuing>: Control where PaddingType.ValueType == CGFloat {
//--------------------------------------------------
// MARK: - Initializers
@ -81,25 +81,10 @@ open class TileContainerBase<PaddingType: DefaultValuing & Valuing>: View where
}
/// Enum used to describe the background effect choices used for this component.
public enum BackgroundEffect: Equatable {
public enum BackgroundEffect {
case transparency
case gradient(UIColor, UIColor)
case none
public static func == (lhs: TileContainerBase.BackgroundEffect, rhs: TileContainerBase.BackgroundEffect) -> Bool {
lhs.description == lhs.description
}
public var description: String {
switch self {
case .transparency:
"transparency"
case .gradient(let first, let second):
"gradient(\(first), \(second)"
case .none:
"none"
}
}
}
/// Enum used to describe the aspect ratios used for this component.
@ -132,11 +117,9 @@ open class TileContainerBase<PaddingType: DefaultValuing & Valuing>: View where
$0.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
}
private var isHighlighted: Bool = false { didSet { setNeedsUpdate() } }
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
//--------------------------------------------------
/// This takes an image source url and applies it as a background image.
open var backgroundImage: UIImage? { didSet { setNeedsUpdate() } }
@ -295,18 +278,19 @@ open class TileContainerBase<PaddingType: DefaultValuing & Valuing>: View where
return view
}
open override func setDefaults() {
super.setDefaults()
backgroundImage = nil
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
color = .white
backgroundEffect = .none
padding = .defaultValue
aspectRatio = .ratio1x1
aspectRatio = .none
imageFallbackColor = .light
width = nil
height = nil
showBorder = false
showDropShadow = false
shouldUpdateView = true
setNeedsUpdate()
}
/// Used to make changes to the View based off a change events or from local properties.
@ -345,41 +329,13 @@ open class TileContainerBase<PaddingType: DefaultValuing & Valuing>: View where
containerView.setAccessibilityLabel(for: views)
//append all children that are accessible
if containerView.isAccessibilityElement {
elements.forEach({ element in
if element.accessibilityTraits.contains(.button) || element.accessibilityTraits.contains(.link) {
items.append(element)
}
})
} else {
items.append(contentsOf: elements)
}
items.append(contentsOf: elements)
return items
}
set {}
}
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if onClickSubscriber != nil {
isHighlighted = true
}
}
open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
if onClickSubscriber != nil {
isHighlighted = false
}
}
open override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
if onClickSubscriber != nil {
isHighlighted = false
}
}
//--------------------------------------------------
// MARK: - Public Methods
//--------------------------------------------------
@ -453,8 +409,35 @@ open class TileContainerBase<PaddingType: DefaultValuing & Valuing>: View where
//-------------------------------------------------------------------------
//Overriding Nil Width Rules
//-------------------------------------------------------------------------
//Rule 1:
//In the scenario where we only have a height but the multiplie is nil, we
//want to set the width with the parent's width which will more or less "fill"
//the container horizontally
//- height is set
//- width is not set
//- aspectRatio is not set
if let superviewWidth, superviewWidth > 0,
containerViewHeight != nil,
containerViewWidth == nil,
multiplier == nil {
containerViewWidth = superviewWidth
}
//Rule 2:
//In the scenario where no width and height is set, want to set the width with the
//parent's width which will more or less "fill" the container horizontally
//- height is not set
//- width is not set
else if let superviewWidth, superviewWidth > 0,
containerViewWidth == nil,
containerViewHeight == nil {
containerViewWidth = superviewWidth
}
//-------------------------------------------------------------------------
//Width + AspectRatio Constraint
//-------------------------------------------------------------------------
//Width + AspectRatio Constraint - Will exit out if set
//-------------------------------------------------------------------------
if let containerViewWidth,
let multiplier,
@ -467,7 +450,7 @@ open class TileContainerBase<PaddingType: DefaultValuing & Valuing>: View where
}
//-------------------------------------------------------------------------
//Height + AspectRatio Constraint
//Height + AspectRatio Constraint - Will exit out if set
//-------------------------------------------------------------------------
else if let containerViewHeight,
let multiplier,
@ -478,14 +461,6 @@ open class TileContainerBase<PaddingType: DefaultValuing & Valuing>: View where
aspectRatioConstraint = widthAnchor.constraint(equalTo: heightAnchor, multiplier: multiplier)
aspectRatioConstraint?.activate()
}
//-------------------------------------------------------------------------
//Multiplier, meaning it was pinned with width only Constraint
//-------------------------------------------------------------------------
else if let multiplier {
aspectRatioConstraint = heightAnchor.constraint(equalTo: widthAnchor, multiplier: multiplier)
aspectRatioConstraint?.activate()
} else {
//-------------------------------------------------------------------------
@ -507,6 +482,12 @@ open class TileContainerBase<PaddingType: DefaultValuing & Valuing>: View where
}
}
}
/// This is the size of the superview's allowed space for this container first by constrained size which would include padding/inset values an
private var superviewWidth: CGFloat? {
horizontalPinnedWidth() ?? superview?.frame.size.width
}
}
extension TileContainerBase {

View File

@ -16,10 +16,10 @@ import Combine
/// while it can include an arrow CTA, it does not require one in order to
/// function.
@objc(VDSTilelet)
open class Tilelet: TileContainerBase<Tilelet.Padding>, ParentViewProtocol {
open class Tilelet: TileContainerBase<Tilelet.Padding> {
/// Enum used to describe the padding choices used for this component.
public enum Padding: String, DefaultValuing, Valuing, CaseIterable {
public enum Padding: String, DefaultValuing, CaseIterable {
case small
case large
@ -28,9 +28,9 @@ open class Tilelet: TileContainerBase<Tilelet.Padding>, ParentViewProtocol {
public var value: CGFloat {
switch self {
case .small:
return UIDevice.isIPad ? VDSLayout.space4X : VDSLayout.space3X
return UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space4X
case .large:
return UIDevice.isIPad ? VDSLayout.space6X : VDSLayout.space4X
return UIDevice.isIPad ? VDSLayout.space4X : VDSLayout.space6X
}
}
@ -103,14 +103,12 @@ open class Tilelet: TileContainerBase<Tilelet.Padding>, ParentViewProtocol {
}
private var backgroundColorSurface: Surface {
backgroundColorConfiguration.getColor(self).isDark() ? .dark : .light
backgroundColorConfiguration.getColor(self).surface
}
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var children: [any ViewProtocol] { [badge, titleLockup, descriptiveIcon, directionalIcon] }
/// Title lockup positioned in the contentView.
open var titleLockup = TitleLockup().with {
$0.standardStyleConfiguration = .init(styleConfigurations: [
@ -279,7 +277,6 @@ open class Tilelet: TileContainerBase<Tilelet.Padding>, ParentViewProtocol {
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
internal var iconContainerHeightConstraint: NSLayoutConstraint?
internal var titleLockupWidthConstraint: NSLayoutConstraint?
internal var titleLockupTrailingConstraint: NSLayoutConstraint?
internal var titleLockupTopConstraint: NSLayoutConstraint?
@ -328,15 +325,15 @@ open class Tilelet: TileContainerBase<Tilelet.Padding>, ParentViewProtocol {
iconContainerView.addSubview(descriptiveIcon)
iconContainerView.addSubview(directionalIcon)
iconContainerHeightConstraint = iconContainerView.height(constant: 0)
descriptiveIcon
.pinLeading()
.pinTopGreaterThanOrEqualTo()
.pinTop()
.pinBottom()
directionalIcon
.pinTrailing()
.pinTopGreaterThanOrEqualTo()
.pinTop()
.pinBottom()
badge.bottomAnchor.constraint(equalTo: badge.label.bottomAnchor, constant: 2).activate()
@ -381,22 +378,6 @@ open class Tilelet: TileContainerBase<Tilelet.Padding>, ParentViewProtocol {
titleLockupSubTitleLabelHeightGreaterThanConstraint?.priority = .defaultHigh
titleLockupSubTitleLabelHeightGreaterThanConstraint?.activate()
}
open override func setDefaults() {
super.setDefaults()
aspectRatio = .none
color = .black
textWidth = nil
textPostion = .top
//models
badgeModel = nil
titleModel = nil
subTitleModel = nil
descriptiveIconModel = nil
directionalIconModel = nil
directionalIcon.bridge_accessibilityLabelBlock = { [weak self] in
guard let self, let directionalIconModel else { return nil }
return directionalIconModel.accessibleText
@ -408,6 +389,22 @@ open class Tilelet: TileContainerBase<Tilelet.Padding>, ParentViewProtocol {
}
}
/// Resets to default settings.
open override func reset() {
shouldUpdateView = false
super.reset()
aspectRatio = .none
color = .black
//models
badgeModel = nil
titleModel = nil
subTitleModel = nil
descriptiveIconModel = nil
directionalIconModel = nil
shouldUpdateView = true
setNeedsUpdate()
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()
@ -468,7 +465,6 @@ open class Tilelet: TileContainerBase<Tilelet.Padding>, ParentViewProtocol {
private func updateBadge() {
if let badgeModel {
badge.text = badgeModel.text
badge.textColor = badgeModel.textColor
badge.fillColor = badgeModel.fillColor
badge.numberOfLines = badgeModel.numberOfLines
badge.surface = backgroundColorSurface
@ -559,7 +555,6 @@ open class Tilelet: TileContainerBase<Tilelet.Padding>, ParentViewProtocol {
descriptiveIcon.color = color
}
descriptiveIcon.size = descriptiveIconModel.size
iconContainerHeightConstraint?.constant = descriptiveIcon.size.dimensions.height
descriptiveIcon.surface = backgroundColorSurface
showIconContainerView = true
}
@ -570,7 +565,6 @@ open class Tilelet: TileContainerBase<Tilelet.Padding>, ParentViewProtocol {
directionalIcon.color = color
}
directionalIcon.size = directionalIconModel.size.value
iconContainerHeightConstraint?.constant = directionalIcon.size.dimensions.height
directionalIcon.surface = backgroundColorSurface
showIconContainerView = true
}
@ -601,7 +595,7 @@ open class Tilelet: TileContainerBase<Tilelet.Padding>, ParentViewProtocol {
}
private func updateTextPositionAlignment() {
guard aspectRatio != .none || height != nil else { return }
guard width != nil && (aspectRatio != .none || height != nil) else { return }
switch textPostion {
case .top:
titleLockupTopConstraint?.activate()

View File

@ -11,13 +11,10 @@ import UIKit
extension Tilelet {
/// Model that represents the options available for the badge.
public struct BadgeModel: Equatable {
public struct BadgeModel {
/// Text that will be used for the badge.
public var text: String = ""
/// Text color that will be used for the badge.
public var textColor: Badge.TextColor?
/// Fill color that will be used for the badge.
public var fillColor: Badge.FillColor
@ -33,9 +30,8 @@ extension Tilelet {
/// LineBreakMode used in Badge label.
public var lineBreakMode: NSLineBreakMode
public init(text: String, textColor: Badge.TextColor? = nil, fillColor: Badge.FillColor = .red, surface: Surface = .light, numberOfLines: Int = 0, maxWidth: CGFloat? = nil, lineBreakMode: NSLineBreakMode = .byTruncatingTail) {
public init(text: String, fillColor: Badge.FillColor = .red, surface: Surface = .light, numberOfLines: Int = 0, maxWidth: CGFloat? = nil, lineBreakMode: NSLineBreakMode = .byTruncatingTail) {
self.text = text
self.textColor = textColor
self.fillColor = fillColor
self.surface = surface
self.numberOfLines = numberOfLines

View File

@ -32,7 +32,7 @@ extension Tilelet {
}
/// Model that represents the options available for the descriptive icon.
public struct DescriptiveIcon: Equatable {
public struct DescriptiveIcon {
/// A representation that will be used to render the icon with corresponding name.
public var name: Icon.Name
@ -58,7 +58,7 @@ extension Tilelet {
}
/// Model that represents the options available for the directional icon.
public struct DirectionalIcon: Equatable {
public struct DirectionalIcon {
public enum IconType: String, CaseIterable {
case rightArrow
case externalLink

View File

@ -10,7 +10,7 @@ import UIKit
extension Tilelet {
/// Model that represents the options available for the sub title label.
public struct SubTitleModel: Equatable {
public struct SubTitleModel {
//--------------------------------------------------
// MARK: - Enums
//--------------------------------------------------
@ -67,13 +67,5 @@ extension Tilelet {
textAttributes: textAttributes,
lineBreakMode: lineBreakMode)
}
public static func == (lhs: Tilelet.SubTitleModel, rhs: Tilelet.SubTitleModel) -> Bool {
lhs.text == rhs.text
&& lhs.textColor == rhs.textColor
&& lhs.otherStandardStyle == rhs.otherStandardStyle
&& lhs.textAttributes == rhs.textAttributes
&& lhs.lineBreakMode == rhs.lineBreakMode
}
}
}

View File

@ -10,7 +10,7 @@ import UIKit
extension Tilelet {
/// Model that represents the options available for the title label.
public struct TitleModel: Equatable {
public struct TitleModel {
//--------------------------------------------------
// MARK: - Enums
//--------------------------------------------------
@ -75,14 +75,5 @@ extension Tilelet {
standardStyle: standardStyle.value,
lineBreakMode: lineBreakMode)
}
public static func == (lhs: Tilelet.TitleModel, rhs: Tilelet.TitleModel) -> Bool {
lhs.text == rhs.text
&& lhs.textColor == rhs.textColor
&& lhs.isBold == rhs.isBold
&& lhs.textAttributes == rhs.textAttributes
&& lhs.standardStyle == rhs.standardStyle
&& lhs.lineBreakMode == rhs.lineBreakMode
}
}
}

View File

@ -11,7 +11,7 @@ import UIKit
extension Tilelet {
/// Model that represents the options available for the eyebrow label.
public struct EyebrowModel: Equatable {
public struct EyebrowModel {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
@ -60,14 +60,5 @@ extension Tilelet {
standardStyle: standardStyle.value,
textAttributes: textAttributes)
}
public static func == (lhs: Tilelet.EyebrowModel, rhs: Tilelet.EyebrowModel) -> Bool {
lhs.text == rhs.text
&& lhs.textColor == rhs.textColor
&& lhs.isBold == rhs.isBold
&& lhs.textAttributes == rhs.textAttributes
&& lhs.standardStyle == rhs.standardStyle
&& lhs.lineBreakMode == rhs.lineBreakMode
}
}
}

View File

@ -13,7 +13,7 @@ import Combine
/// Title Lockup ensures the readability of words on the screen
/// with approved built in text size configurations.
@objc(VDSTitleLockup)
open class TitleLockup: View, ParentViewProtocol {
open class TitleLockup: View {
//--------------------------------------------------
// MARK: - Initializers
@ -61,8 +61,6 @@ open class TitleLockup: View, ParentViewProtocol {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var children: [any ViewProtocol] { [eyebrowLabel, titleLabel, subTitleLabel] }
/// Aligns TitleLockup's subcomponent's text
open var textAlignment: TextAlignment = .left { didSet { setNeedsUpdate() } }
@ -281,14 +279,18 @@ open class TitleLockup: View, ParentViewProtocol {
set {}
}
open override func setDefaults() {
super.setDefaults()
textAlignment = .left
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
eyebrowModel = nil
titleModel = nil
subTitleModel = nil
shouldUpdateView = true
setNeedsUpdate()
}
var labelViews = [UIView]()
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()

View File

@ -9,7 +9,7 @@ import Foundation
extension TitleLockup {
/// Model that represents the options available for the eyebrow label.
public struct EyebrowModel: Equatable {
public struct EyebrowModel {
/// Text that will be used for the eyebrow label.
public var text: String
@ -44,15 +44,6 @@ extension TitleLockup {
/// Text style that will be used for the eyebrow label.
public var textStyle: TextStyle { isBold ? standardStyle.value.bold : standardStyle.value.regular }
public static func == (lhs: TitleLockup.EyebrowModel, rhs: TitleLockup.EyebrowModel) -> Bool {
lhs.text == rhs.text
&& lhs.textColor == rhs.textColor
&& lhs.isBold == rhs.isBold
&& lhs.standardStyle == rhs.standardStyle
&& lhs.textAttributes == rhs.textAttributes
&& lhs.numberOfLines == rhs.numberOfLines
}
}
}

View File

@ -46,14 +46,6 @@ extension TitleLockup {
/// TextStyle used to render the text.
public var textStyle: TextStyle { otherStandardStyle.value.regular }
public static func == (lhs: TitleLockup.SubTitleModel, rhs: TitleLockup.SubTitleModel) -> Bool {
lhs.text == rhs.text
&& lhs.textColor == rhs.textColor
&& lhs.otherStandardStyle == rhs.otherStandardStyle
&& lhs.textAttributes == rhs.textAttributes
&& lhs.lineBreakMode == rhs.lineBreakMode
&& lhs.numberOfLines == rhs.numberOfLines
}
}
}

View File

@ -10,7 +10,7 @@ import UIKit
extension TitleLockup {
/// Model that represents the options available for the sub title label.
public struct TitleModel: Equatable {
public struct TitleModel {
/// Text that will be used for the title label.
public var text: String
@ -51,14 +51,5 @@ extension TitleLockup {
/// TextStyle used to render the text.
public var textStyle: TextStyle { isBold ? standardStyle.value.bold : standardStyle.value.regular }
public static func == (lhs: TitleLockup.TitleModel, rhs: TitleLockup.TitleModel) -> Bool {
lhs.text == rhs.text
&& lhs.textColor == rhs.textColor
&& lhs.isBold == rhs.isBold
&& lhs.standardStyle == rhs.standardStyle
&& lhs.textAttributes == rhs.textAttributes
&& lhs.numberOfLines == rhs.numberOfLines
&& lhs.lineBreakMode == rhs.lineBreakMode
}
}
}

View File

@ -13,7 +13,7 @@ import Combine
/// A toggle is a control that lets customers instantly turn on
/// or turn off a single option, setting or function.
@objc(VDSToggle)
open class Toggle: Control, Changeable, FormFieldable, ParentViewProtocol {
open class Toggle: Control, Changeable, FormFieldable {
//--------------------------------------------------
// MARK: - Initializers
@ -54,7 +54,6 @@ open class Toggle: Control, Changeable, FormFieldable, ParentViewProtocol {
private var leftConstraints: [NSLayoutConstraint] = []
private var rightConstraints: [NSLayoutConstraint] = []
private var labelConstraints: [NSLayoutConstraint] = []
private var toggleConstraints: [NSLayoutConstraint] = []
//--------------------------------------------------
// MARK: - Configuration
@ -89,15 +88,13 @@ open class Toggle: Control, Changeable, FormFieldable, ParentViewProtocol {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var children: [any ViewProtocol] { [toggleView, label] }
open var onChangeSubscriber: AnyCancellable?
/// Actual toggle used in this component.
open var toggleView = ToggleView().with {
$0.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
$0.isUserInteractionEnabled = false
$0.isAccessibilityElement = false
$0.isAccessibilityElement = false
}
/// Used in showing the on/off text.
@ -150,15 +147,35 @@ open class Toggle: Control, Changeable, FormFieldable, ParentViewProtocol {
open var value: AnyHashable? { isOn }
/// The natural size for the receiving view, considering only properties of the view itself.
open override var intrinsicContentSize: CGSize {
if showLabel {
label.sizeToFit()
let size = CGSize(width: label.frame.width + spacingBetween + toggleContainerSize.width,
height: max(toggleContainerSize.height, label.frame.height))
return size
} else {
return toggleContainerSize
}
}
open override var shouldHighlight: Bool { false }
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
//--------------------------------------------------
/// Executed on initialization for this View.
open override func initialSetup() {
super.initialSetup()
onClick = { control in
control.toggle()
}
}
/// 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
if #available(iOS 17.0, *) {
accessibilityTraits = .toggleButton
@ -190,59 +207,6 @@ open class Toggle: Control, Changeable, FormFieldable, ParentViewProtocol {
label.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor)
]
// Set content hugging priority
setContentHuggingPriority(.required, for: .horizontal)
isAccessibilityElement = true
if #available(iOS 17.0, *) {
accessibilityTraits = .toggleButton
} else {
accessibilityTraits = .button
}
addSubview(label)
addSubview(toggleView)
// Set up initial constraints for label and switch
toggleView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
//toggle
toggleConstraints = [
toggleView.leadingAnchor.constraint(equalTo: leadingAnchor),
toggleView.trailingAnchor.constraint(equalTo: trailingAnchor)
]
//toggle and label variants
labelConstraints = [
height(constant: toggleContainerSize.height, priority: .defaultLow),
heightGreaterThanEqualTo(constant: toggleContainerSize.height, priority: .defaultHigh),
label.topAnchor.constraint(equalTo: topAnchor),
label.bottomAnchor.constraint(equalTo: bottomAnchor),
]
//label-toggle
leftConstraints = [
label.leadingAnchor.constraint(equalTo: leadingAnchor),
toggleView.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: spacingBetween),
toggleView.trailingAnchor.constraint(equalTo: trailingAnchor)
]
//toggle-label
rightConstraints = [
toggleView.leadingAnchor.constraint(equalTo: leadingAnchor),
label.leadingAnchor.constraint(equalTo: toggleView.trailingAnchor, constant: spacingBetween),
label.trailingAnchor.constraint(equalTo: trailingAnchor)
]
}
open override func setDefaults() {
super.setDefaults()
onClick = { [weak self] _ in
guard let self else { return }
toggle()
}
bridge_accessibilityValueBlock = { [weak self] in
guard let self else { return "" }
if showText {
@ -251,7 +215,13 @@ open class Toggle: Control, Changeable, FormFieldable, ParentViewProtocol {
return isSelected ? "On" : "Off"
}
}
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
label.reset()
isEnabled = true
isOn = false
isAnimated = true
@ -263,12 +233,8 @@ open class Toggle: Control, Changeable, FormFieldable, ParentViewProtocol {
textPosition = .left
inputId = nil
onChange = nil
}
/// Resets to default settings.
open override func reset() {
label.reset()
super.reset()
shouldUpdateView = true
setNeedsUpdate()
}
/// Used to make changes to the View based off a change events or from local properties.
@ -294,8 +260,6 @@ open class Toggle: Control, Changeable, FormFieldable, ParentViewProtocol {
label.isHidden = !showLabel
if showLabel {
NSLayoutConstraint.deactivate(toggleConstraints)
label.textAlignment = textPosition == .left ? .right : .left
label.textStyle = textStyle
label.text = statusText
@ -314,7 +278,6 @@ open class Toggle: Control, Changeable, FormFieldable, ParentViewProtocol {
NSLayoutConstraint.deactivate(leftConstraints)
NSLayoutConstraint.deactivate(rightConstraints)
NSLayoutConstraint.deactivate(labelConstraints)
NSLayoutConstraint.activate(toggleConstraints)
}
}
}

View File

@ -104,10 +104,18 @@ open class ToggleView: Control, Changeable, FormFieldable {
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
/// Executed on initialization for this View.
open override func initialSetup() {
super.initialSetup()
onClick = { control in
control.toggle()
}
}
/// 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
if #available(iOS 17.0, *) {
accessibilityTraits = .toggleButton
@ -148,21 +156,20 @@ open class ToggleView: Control, Changeable, FormFieldable {
accessibilityLabel = "Toggle"
}
open override func setDefaults() {
super.setDefaults()
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
isOn = false
isAnimated = true
inputId = nil
toggleView.backgroundColor = toggleColorConfiguration.getColor(self)
knobView.backgroundColor = knobColorConfiguration.getColor(self)
onChange = nil
onClick = { [weak self] _ in
guard let self else { return }
toggle()
}
onChange = nil
shouldUpdateView = true
setNeedsUpdate()
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()

View File

@ -128,16 +128,6 @@ open class Tooltip: Control, TooltipLaunchable {
isAccessibilityElement = true
accessibilityTraits = .button
}
open override func setDefaults() {
super.setDefaults()
closeButtonText = "Close"
fillColor = .primary
size = .medium
title = nil
content = nil
contentView = nil
onClick = { [weak self] tooltip in
guard let self else { return}
@ -167,7 +157,19 @@ open class Tooltip: Control, TooltipLaunchable {
return isEnabled ? "Double tap to open." : ""
}
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
size = .medium
title = ""
content = ""
fillColor = .primary
closeButtonText = "Close"
shouldUpdateView = true
setNeedsUpdate()
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {

View File

@ -10,7 +10,6 @@ import UIKit
import Combine
import VDSCoreTokens
@objc(VDSTooltipAlertViewController)
open class TooltipAlertViewController: UIViewController, Surfaceable {
/// Set of Subscribers for any Publishers for this Control.

View File

@ -9,8 +9,7 @@ import Foundation
import UIKit
import VDSCoreTokens
@objc(VDSTooltipDialog)
open class TooltipDialog: View, UIScrollViewDelegate, ParentViewProtocol {
open class TooltipDialog: View, UIScrollViewDelegate {
//--------------------------------------------------
// MARK: - Initializers
@ -54,8 +53,6 @@ open class TooltipDialog: View, UIScrollViewDelegate, ParentViewProtocol {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var children: [any ViewProtocol] { [titleLabel, contentLabel] }
open var tooltipModel = Tooltip.TooltipModel() { didSet { setNeedsUpdate() } }
open var titleLabel = Label().with { label in

View File

@ -11,7 +11,7 @@ import UIKit
extension Tooltip {
/// Model used to represent the tooltip.
public struct TooltipModel: Equatable {
public struct TooltipModel {
/// Current Surface and this is used to pass down to child objects that implement Surfacable
public var closeButtonText: String
public var title: String?

View File

@ -84,13 +84,17 @@ open class TrailingTooltipLabel: View, TooltipLaunchable {
}
}
open override func setDefaults() {
super.setDefaults()
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
labelText = nil
labelAttributes = nil
labelTextStyle = .defaultStyle
labelTextAlignment = .left
tooltipModel = nil
shouldUpdateView = true
setNeedsUpdate()
}
}

View File

@ -187,4 +187,10 @@ extension UIColor {
guard let found else { return nil}
return found
}
public var surface: Surface {
var greyScale: CGFloat = 0
getWhite(&greyScale, alpha: nil)
return greyScale < 0.5 ? .dark : .light
}
}

Some files were not shown because too many files have changed in this diff Show More