Compare commits

..

9 Commits

Author SHA1 Message Date
Shawn Taylor
735d276bf9 Revert snapshot 2023-11-02 12:43:23 -04:00
ionitron
71f7d536b7 chore(): add updated snapshots 2023-11-02 16:05:29 +00:00
Shawn Taylor
da02130b58 Add test for select 2023-11-02 11:36:43 -04:00
Shawn Taylor
8e80779b01 Revert snapshot 2023-11-02 11:29:53 -04:00
ionitron
21eecf06de chore(): add updated snapshots 2023-11-02 14:43:26 +00:00
Shawn Taylor
55be77bf38 Fix test 2023-11-02 10:24:36 -04:00
Shawn Taylor
75cc691497 Add a test 2023-11-02 09:51:28 -04:00
Shawn Taylor
e282058e23 Input: allow label size overrides
Input: allow label size overrides
2023-11-01 09:50:39 -04:00
Shawn Taylor
99f08a9578 Allow label size overrides 2023-10-31 10:00:50 -04:00
672 changed files with 9966 additions and 38430 deletions

2
.github/CODEOWNERS vendored
View File

@@ -11,8 +11,6 @@
# In each subsection folders are ordered first by depth, then alphabetically.
# This should make it easy to add new rules without breaking existing ones.
* @ionic-team/framework
# Frameworks
## Angular

15
.github/labeler.yml vendored
View File

@@ -6,17 +6,16 @@
# https://github.com/actions/labeler
'package: core':
- changed-files:
- any-glob-to-any-file: ['core/**/*']
- core/**/*
'package: angular':
- changed-files:
- any-glob-to-any-file: ['packages/angular/**/*', 'packages/angular-*/**/*']
- packages/angular/**/*
- packages/angular-*/**/*
'package: react':
- changed-files:
- any-glob-to-any-file: ['packages/react/**/*', 'packages/react-*/**/*']
- packages/react/**/*
- packages/react-*/**/*
'package: vue':
- changed-files:
- any-glob-to-any-file: ['packages/vue/**/*', 'packages/vue-*/**/*']
- packages/vue/**/*
- packages/vue-*/**/*

View File

@@ -8,7 +8,7 @@ runs:
steps:
- uses: actions/setup-node@v3
with:
node-version: 18
node-version: 16
- uses: ./.github/workflows/actions/download-archive
with:
name: ionic-core

View File

@@ -140,7 +140,7 @@ jobs:
strategy:
fail-fast: false
matrix:
apps: [ng14, ng15, ng16, ng17]
apps: [ng14, ng15, ng16]
needs: [build-angular, build-angular-server]
runs-on: ubuntu-latest
steps:

View File

@@ -13,7 +13,7 @@ jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v5
- uses: actions/labeler@main
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
sync-labels: true

View File

@@ -3,124 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [7.6.0](https://github.com/ionic-team/ionic-framework/compare/v7.5.8...v7.6.0) (2023-12-06)
### Bug Fixes
* **angular,vue:** range form value updates while dragging knob ([#28422](https://github.com/ionic-team/ionic-framework/issues/28422)) ([0854a11](https://github.com/ionic-team/ionic-framework/commit/0854a11a25759d0201eae66c96a62fe138d486f8)), closes [#28256](https://github.com/ionic-team/ionic-framework/issues/28256)
* **animation:** add stronger types to Animation interface ([#28334](https://github.com/ionic-team/ionic-framework/issues/28334)) ([4a088d5](https://github.com/ionic-team/ionic-framework/commit/4a088d5d612ab0387064d388b37d46cdf15cf1ff))
* **animation:** progressEnd coercion is reset before onFinish ([#28394](https://github.com/ionic-team/ionic-framework/issues/28394)) ([eae8162](https://github.com/ionic-team/ionic-framework/commit/eae8162d0dc2e0bd7a9d56a3662a8e5f5d142b72)), closes [#28393](https://github.com/ionic-team/ionic-framework/issues/28393)
* **infinite-scroll:** remaining in threshold after ionInfinite can trigger event again on scroll ([#28569](https://github.com/ionic-team/ionic-framework/issues/28569)) ([8c235fd](https://github.com/ionic-team/ionic-framework/commit/8c235fd30c50f317de1f37f69068507aa0979068)), closes [#18071](https://github.com/ionic-team/ionic-framework/issues/18071)
* **item:** allow item to grow when it is used in a flex container ([#28594](https://github.com/ionic-team/ionic-framework/issues/28594)) ([1c1b567](https://github.com/ionic-team/ionic-framework/commit/1c1b567279dee44da70bb9b90c129946c9043987))
* **item:** wrap elements and label contents when the font size increases or the elements do not fit ([#28146](https://github.com/ionic-team/ionic-framework/issues/28146)) ([6438e3e](https://github.com/ionic-team/ionic-framework/commit/6438e3e919c665569b731a2d74fe1547b4f3c1cc))
* **select:** do not collapse to width: 0 when placed in flex container ([#28631](https://github.com/ionic-team/ionic-framework/issues/28631)) ([e71e7a0](https://github.com/ionic-team/ionic-framework/commit/e71e7a069000db8738abc304758de64286817442))
* **toast:** add swipeGesture to ToastOptions ([#28518](https://github.com/ionic-team/ionic-framework/issues/28518)) ([4ad6df6](https://github.com/ionic-team/ionic-framework/commit/4ad6df67f01cebce30d4da46c7541c4b14c5d4a4))
### Features
* **checkbox:** add shadow part for label ([#28604](https://github.com/ionic-team/ionic-framework/issues/28604)) ([f9f5654](https://github.com/ionic-team/ionic-framework/commit/f9f5654ab0e920bf97089fbabfb9eedbcf6fe8ae)), closes [#28300](https://github.com/ionic-team/ionic-framework/issues/28300)
* **input, textarea, select:** add start and end slots ([#28583](https://github.com/ionic-team/ionic-framework/issues/28583)) ([357b8b2](https://github.com/ionic-team/ionic-framework/commit/357b8b2beb29b95d53ef043af349067be1d32658)), closes [#26297](https://github.com/ionic-team/ionic-framework/issues/26297)
* **radio-group:** add compareWith property ([#28452](https://github.com/ionic-team/ionic-framework/issues/28452)) ([0ae327f](https://github.com/ionic-team/ionic-framework/commit/0ae327f0e09cd97d705f2d3051c215034381e226))
* **radio:** add shadow part for label ([#28607](https://github.com/ionic-team/ionic-framework/issues/28607)) ([b757970](https://github.com/ionic-team/ionic-framework/commit/b757970d23e87c59aa883ecb1bfa9b66bcae8de2)), closes [#28300](https://github.com/ionic-team/ionic-framework/issues/28300)
* **range:** expose label wrapper as shadow part ([#28601](https://github.com/ionic-team/ionic-framework/issues/28601)) ([52ed2bf](https://github.com/ionic-team/ionic-framework/commit/52ed2bf63777c764f57bb4c3a5d4a127bff46c50))
* **toast:** add swipe to dismiss functionality ([#28442](https://github.com/ionic-team/ionic-framework/issues/28442)) ([30c21aa](https://github.com/ionic-team/ionic-framework/commit/30c21aab3ed40d73c28e7d60d0952d8891b0a9d3)), closes [#21769](https://github.com/ionic-team/ionic-framework/issues/21769)
* **toggle:** expose label wrapper as shadow part ([#28585](https://github.com/ionic-team/ionic-framework/issues/28585)) ([a34188f](https://github.com/ionic-team/ionic-framework/commit/a34188f7dbec4a16e4f2043ed3dc096e337725a7))
## [7.5.8](https://github.com/ionic-team/ionic-framework/compare/v7.5.7...v7.5.8) (2023-12-06)
### Bug Fixes
* **angular:** add missing menu controller methods ([#28618](https://github.com/ionic-team/ionic-framework/issues/28618)) ([7871b56](https://github.com/ionic-team/ionic-framework/commit/7871b56eccfe63326b6dd4b56ade3b3afd444fce)), closes [#20053](https://github.com/ionic-team/ionic-framework/issues/20053)
* **overlays:** trigger is configured on load ([#28526](https://github.com/ionic-team/ionic-framework/issues/28526)) ([a3cd204](https://github.com/ionic-team/ionic-framework/commit/a3cd204f616606ccffc35082655e55fdfb19fe28)), closes [#28524](https://github.com/ionic-team/ionic-framework/issues/28524)
* **react:** router creates new view instances of parameterized routes ([#28616](https://github.com/ionic-team/ionic-framework/issues/28616)) ([1705d06](https://github.com/ionic-team/ionic-framework/commit/1705d064cc041e99f432a27207f3aab7fa62c778)), closes [#26524](https://github.com/ionic-team/ionic-framework/issues/26524)
* **vue:** nav component accepts kebab-case component properties ([#28615](https://github.com/ionic-team/ionic-framework/issues/28615)) ([60303aa](https://github.com/ionic-team/ionic-framework/commit/60303aad23f823488afc8f8824e9c72e3ab86acc)), closes [#28611](https://github.com/ionic-team/ionic-framework/issues/28611)
## [7.5.7](https://github.com/ionic-team/ionic-framework/compare/v7.5.6...v7.5.7) (2023-11-29)
### Bug Fixes
* **alert:** date inputs render correctly in mobile safari ([#28495](https://github.com/ionic-team/ionic-framework/issues/28495)) ([b833f0e](https://github.com/ionic-team/ionic-framework/commit/b833f0e826ddd261230e2e29b70e2dc884d8cb04)), closes [#28494](https://github.com/ionic-team/ionic-framework/issues/28494)
* **datetime:** allow disabling datetime with prefer-wheel ([#28511](https://github.com/ionic-team/ionic-framework/issues/28511)) ([01130e1](https://github.com/ionic-team/ionic-framework/commit/01130e12e1d73bbf558da9d4dffd7122822ff39c))
## [7.5.6](https://github.com/ionic-team/ionic-framework/compare/v7.5.5...v7.5.6) (2023-11-21)
### Bug Fixes
* **alert:** match MD spec on tablet ([#28501](https://github.com/ionic-team/ionic-framework/issues/28501)) ([6a2be9f](https://github.com/ionic-team/ionic-framework/commit/6a2be9fa3c12a893d98dc139a1575a6e7e3c7c26)), closes [#23977](https://github.com/ionic-team/ionic-framework/issues/23977)
* **angular:** ng add @ionic/angular in standalone projects ([#28523](https://github.com/ionic-team/ionic-framework/issues/28523)) ([c07312e](https://github.com/ionic-team/ionic-framework/commit/c07312e5ed931f6f825ccf083c9dead9fa815843)), closes [#28514](https://github.com/ionic-team/ionic-framework/issues/28514)
* **angular:** overlays are defined when using standalone controllers ([#28560](https://github.com/ionic-team/ionic-framework/issues/28560)) ([9453132](https://github.com/ionic-team/ionic-framework/commit/9453132aa8952b4adfa1326e61138b329e254f76)), closes [#28385](https://github.com/ionic-team/ionic-framework/issues/28385)
* **datetime:** updating value with min scrolls to new value ([#28549](https://github.com/ionic-team/ionic-framework/issues/28549)) ([388d19e](https://github.com/ionic-team/ionic-framework/commit/388d19e04f83f85abd4602adb04cc71ac575764a)), closes [#28548](https://github.com/ionic-team/ionic-framework/issues/28548)
## [7.5.5](https://github.com/ionic-team/ionic-framework/compare/v7.5.4...v7.5.5) (2023-11-15)
### Bug Fixes
* **accordion-group:** correct accordion is open on load ([#28510](https://github.com/ionic-team/ionic-framework/issues/28510)) ([a000dd2](https://github.com/ionic-team/ionic-framework/commit/a000dd2c0b65be8ab5b2ad19f2748fbca13d5085)), closes [#28506](https://github.com/ionic-team/ionic-framework/issues/28506)
* **action-sheet:** adjust height for safe area with scrollable options ([#28504](https://github.com/ionic-team/ionic-framework/issues/28504)) ([900267e](https://github.com/ionic-team/ionic-framework/commit/900267eb36c36f2af63435f6b46acca52b3bdab7)), closes [#27777](https://github.com/ionic-team/ionic-framework/issues/27777)
* **header:** collapsible large title does not flicker when collapse prop not reflected ([#28472](https://github.com/ionic-team/ionic-framework/issues/28472)) ([8227b0e](https://github.com/ionic-team/ionic-framework/commit/8227b0ee6d5250e122a34a83c644f8a74fbbafd5)), closes [#28466](https://github.com/ionic-team/ionic-framework/issues/28466)
* **item-divider:** apply safe area to proper side regardless of direction ([#28420](https://github.com/ionic-team/ionic-framework/issues/28420)) ([4513e0c](https://github.com/ionic-team/ionic-framework/commit/4513e0c6b066d4990800c707e1d97f69c8fcfb0c))
* **radio-group:** emit value change on componentDidLoad ([#28488](https://github.com/ionic-team/ionic-framework/issues/28488)) ([73b8bfd](https://github.com/ionic-team/ionic-framework/commit/73b8bfde3f060490958c10f58d0f68de80cb957f)), closes [#28356](https://github.com/ionic-team/ionic-framework/issues/28356)
* **searchbar:** cancel icon aligns with back button ([#28478](https://github.com/ionic-team/ionic-framework/issues/28478)) ([c053fd9](https://github.com/ionic-team/ionic-framework/commit/c053fd9c68d9b1add1335db80be962215946a0b1)), closes [#28468](https://github.com/ionic-team/ionic-framework/issues/28468)
## [7.5.4](https://github.com/ionic-team/ionic-framework/compare/v7.5.3...v7.5.4) (2023-11-08)
### Bug Fixes
* **inputs:** remove invalid legacy warnings in input, textarea, and select ([#28484](https://github.com/ionic-team/ionic-framework/issues/28484)) ([c765dcb](https://github.com/ionic-team/ionic-framework/commit/c765dcbac4148762768d8c2bea9103e7d38c510b))
* **item:** apply safe area to proper side regardless of direction ([#28403](https://github.com/ionic-team/ionic-framework/issues/28403)) ([ed040b0](https://github.com/ionic-team/ionic-framework/commit/ed040b09e9cbd4246864e690542132defc6a6578))
* **list:** remove border from last item with item-sliding ([#28439](https://github.com/ionic-team/ionic-framework/issues/28439)) ([cafafcc](https://github.com/ionic-team/ionic-framework/commit/cafafcc9d166ef536dcb73edd522c8f2a0fb95b6)), closes [#28435](https://github.com/ionic-team/ionic-framework/issues/28435)
## [7.5.3](https://github.com/ionic-team/ionic-framework/compare/v7.5.2...v7.5.3) (2023-11-01)
### Bug Fixes
* **alert:** long words wrap to next line ([#28408](https://github.com/ionic-team/ionic-framework/issues/28408)) ([34257d6](https://github.com/ionic-team/ionic-framework/commit/34257d681e9034b0a001aa45e17222f3aab5ed76)), closes [#28406](https://github.com/ionic-team/ionic-framework/issues/28406)
* **angular:** inputs on standalone form controls are reactive ([#28434](https://github.com/ionic-team/ionic-framework/issues/28434)) ([3b6e631](https://github.com/ionic-team/ionic-framework/commit/3b6e6318bf94125b3f8305b4d072a5945ceb3730)), closes [#28431](https://github.com/ionic-team/ionic-framework/issues/28431)
* **angular:** NavController works with nested outlets ([#28421](https://github.com/ionic-team/ionic-framework/issues/28421)) ([90acad1](https://github.com/ionic-team/ionic-framework/commit/90acad1837b117542830ec0083389a35c5b4ee76)), closes [#28417](https://github.com/ionic-team/ionic-framework/issues/28417)
* **angular:** run platform subscriptions inside zone ([#28404](https://github.com/ionic-team/ionic-framework/issues/28404)) ([a4b303e](https://github.com/ionic-team/ionic-framework/commit/a4b303e1338a35756a9cf7f67508d24d2f8537a2)), closes [#19539](https://github.com/ionic-team/ionic-framework/issues/19539)
* **angular:** standalone form components do not error when multiple are used ([#28423](https://github.com/ionic-team/ionic-framework/issues/28423)) ([89698b3](https://github.com/ionic-team/ionic-framework/commit/89698b338fb05cde427c98720c238d2365abdaa7)), closes [#28418](https://github.com/ionic-team/ionic-framework/issues/28418)
* **datetime:** allow calendar navigation in readonly mode; disallow keyboard navigation when disabled ([#28336](https://github.com/ionic-team/ionic-framework/issues/28336)) ([f6a6877](https://github.com/ionic-team/ionic-framework/commit/f6a6877044a6d912a92aab00c3c78897da09415d)), closes [#28121](https://github.com/ionic-team/ionic-framework/issues/28121)
* **input, textarea, select:** use consistent sizes ([#28390](https://github.com/ionic-team/ionic-framework/issues/28390)) ([b31ecbb](https://github.com/ionic-team/ionic-framework/commit/b31ecbbfe8deb87604686d752e92e672dd9b277a)), closes [#28388](https://github.com/ionic-team/ionic-framework/issues/28388)
* **list-header:** apply safe area to proper side regardless of direction ([#28371](https://github.com/ionic-team/ionic-framework/issues/28371)) ([f99d530](https://github.com/ionic-team/ionic-framework/commit/f99d5305fb4b1607b42e34a0b7653d8e1b5bf23f))
* **segment:** avoid scrolling webkit bug ([#28376](https://github.com/ionic-team/ionic-framework/issues/28376)) ([8e2f818](https://github.com/ionic-team/ionic-framework/commit/8e2f81867175e9980e6d072b0a4414baae571223)), closes [#28373](https://github.com/ionic-team/ionic-framework/issues/28373)
* **tab-bar:** apply safe area to proper side regardless of direction ([#28372](https://github.com/ionic-team/ionic-framework/issues/28372)) ([d47b7e7](https://github.com/ionic-team/ionic-framework/commit/d47b7e750310ceb2f2c7ecfda8343923ff8d564a))
## [7.5.2](https://github.com/ionic-team/ionic-framework/compare/v7.5.1...v7.5.2) (2023-10-25)

View File

@@ -3,119 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [7.6.0](https://github.com/ionic-team/ionic-framework/compare/v7.5.8...v7.6.0) (2023-12-06)
### Bug Fixes
* **angular,vue:** range form value updates while dragging knob ([#28422](https://github.com/ionic-team/ionic-framework/issues/28422)) ([0854a11](https://github.com/ionic-team/ionic-framework/commit/0854a11a25759d0201eae66c96a62fe138d486f8)), closes [#28256](https://github.com/ionic-team/ionic-framework/issues/28256)
* **animation:** add stronger types to Animation interface ([#28334](https://github.com/ionic-team/ionic-framework/issues/28334)) ([4a088d5](https://github.com/ionic-team/ionic-framework/commit/4a088d5d612ab0387064d388b37d46cdf15cf1ff))
* **animation:** progressEnd coercion is reset before onFinish ([#28394](https://github.com/ionic-team/ionic-framework/issues/28394)) ([eae8162](https://github.com/ionic-team/ionic-framework/commit/eae8162d0dc2e0bd7a9d56a3662a8e5f5d142b72)), closes [#28393](https://github.com/ionic-team/ionic-framework/issues/28393)
* **infinite-scroll:** remaining in threshold after ionInfinite can trigger event again on scroll ([#28569](https://github.com/ionic-team/ionic-framework/issues/28569)) ([8c235fd](https://github.com/ionic-team/ionic-framework/commit/8c235fd30c50f317de1f37f69068507aa0979068)), closes [#18071](https://github.com/ionic-team/ionic-framework/issues/18071)
* **item:** allow item to grow when it is used in a flex container ([#28594](https://github.com/ionic-team/ionic-framework/issues/28594)) ([1c1b567](https://github.com/ionic-team/ionic-framework/commit/1c1b567279dee44da70bb9b90c129946c9043987))
* **item:** wrap elements and label contents when the font size increases or the elements do not fit ([#28146](https://github.com/ionic-team/ionic-framework/issues/28146)) ([6438e3e](https://github.com/ionic-team/ionic-framework/commit/6438e3e919c665569b731a2d74fe1547b4f3c1cc))
* **select:** do not collapse to width: 0 when placed in flex container ([#28631](https://github.com/ionic-team/ionic-framework/issues/28631)) ([e71e7a0](https://github.com/ionic-team/ionic-framework/commit/e71e7a069000db8738abc304758de64286817442))
* **toast:** add swipeGesture to ToastOptions ([#28518](https://github.com/ionic-team/ionic-framework/issues/28518)) ([4ad6df6](https://github.com/ionic-team/ionic-framework/commit/4ad6df67f01cebce30d4da46c7541c4b14c5d4a4))
### Features
* **checkbox:** add shadow part for label ([#28604](https://github.com/ionic-team/ionic-framework/issues/28604)) ([f9f5654](https://github.com/ionic-team/ionic-framework/commit/f9f5654ab0e920bf97089fbabfb9eedbcf6fe8ae)), closes [#28300](https://github.com/ionic-team/ionic-framework/issues/28300)
* **input, textarea, select:** add start and end slots ([#28583](https://github.com/ionic-team/ionic-framework/issues/28583)) ([357b8b2](https://github.com/ionic-team/ionic-framework/commit/357b8b2beb29b95d53ef043af349067be1d32658)), closes [#26297](https://github.com/ionic-team/ionic-framework/issues/26297)
* **radio-group:** add compareWith property ([#28452](https://github.com/ionic-team/ionic-framework/issues/28452)) ([0ae327f](https://github.com/ionic-team/ionic-framework/commit/0ae327f0e09cd97d705f2d3051c215034381e226))
* **radio:** add shadow part for label ([#28607](https://github.com/ionic-team/ionic-framework/issues/28607)) ([b757970](https://github.com/ionic-team/ionic-framework/commit/b757970d23e87c59aa883ecb1bfa9b66bcae8de2)), closes [#28300](https://github.com/ionic-team/ionic-framework/issues/28300)
* **range:** expose label wrapper as shadow part ([#28601](https://github.com/ionic-team/ionic-framework/issues/28601)) ([52ed2bf](https://github.com/ionic-team/ionic-framework/commit/52ed2bf63777c764f57bb4c3a5d4a127bff46c50))
* **toast:** add swipe to dismiss functionality ([#28442](https://github.com/ionic-team/ionic-framework/issues/28442)) ([30c21aa](https://github.com/ionic-team/ionic-framework/commit/30c21aab3ed40d73c28e7d60d0952d8891b0a9d3)), closes [#21769](https://github.com/ionic-team/ionic-framework/issues/21769)
* **toggle:** expose label wrapper as shadow part ([#28585](https://github.com/ionic-team/ionic-framework/issues/28585)) ([a34188f](https://github.com/ionic-team/ionic-framework/commit/a34188f7dbec4a16e4f2043ed3dc096e337725a7))
## [7.5.8](https://github.com/ionic-team/ionic-framework/compare/v7.5.7...v7.5.8) (2023-12-06)
### Bug Fixes
* **overlays:** trigger is configured on load ([#28526](https://github.com/ionic-team/ionic-framework/issues/28526)) ([a3cd204](https://github.com/ionic-team/ionic-framework/commit/a3cd204f616606ccffc35082655e55fdfb19fe28)), closes [#28524](https://github.com/ionic-team/ionic-framework/issues/28524)
## [7.5.7](https://github.com/ionic-team/ionic-framework/compare/v7.5.6...v7.5.7) (2023-11-29)
### Bug Fixes
* **alert:** date inputs render correctly in mobile safari ([#28495](https://github.com/ionic-team/ionic-framework/issues/28495)) ([b833f0e](https://github.com/ionic-team/ionic-framework/commit/b833f0e826ddd261230e2e29b70e2dc884d8cb04)), closes [#28494](https://github.com/ionic-team/ionic-framework/issues/28494)
* **datetime:** allow disabling datetime with prefer-wheel ([#28511](https://github.com/ionic-team/ionic-framework/issues/28511)) ([01130e1](https://github.com/ionic-team/ionic-framework/commit/01130e12e1d73bbf558da9d4dffd7122822ff39c))
## [7.5.6](https://github.com/ionic-team/ionic-framework/compare/v7.5.5...v7.5.6) (2023-11-21)
### Bug Fixes
* **alert:** match MD spec on tablet ([#28501](https://github.com/ionic-team/ionic-framework/issues/28501)) ([6a2be9f](https://github.com/ionic-team/ionic-framework/commit/6a2be9fa3c12a893d98dc139a1575a6e7e3c7c26)), closes [#23977](https://github.com/ionic-team/ionic-framework/issues/23977)
* **datetime:** updating value with min scrolls to new value ([#28549](https://github.com/ionic-team/ionic-framework/issues/28549)) ([388d19e](https://github.com/ionic-team/ionic-framework/commit/388d19e04f83f85abd4602adb04cc71ac575764a)), closes [#28548](https://github.com/ionic-team/ionic-framework/issues/28548)
## [7.5.5](https://github.com/ionic-team/ionic-framework/compare/v7.5.4...v7.5.5) (2023-11-15)
### Bug Fixes
* **accordion-group:** correct accordion is open on load ([#28510](https://github.com/ionic-team/ionic-framework/issues/28510)) ([a000dd2](https://github.com/ionic-team/ionic-framework/commit/a000dd2c0b65be8ab5b2ad19f2748fbca13d5085)), closes [#28506](https://github.com/ionic-team/ionic-framework/issues/28506)
* **action-sheet:** adjust height for safe area with scrollable options ([#28504](https://github.com/ionic-team/ionic-framework/issues/28504)) ([900267e](https://github.com/ionic-team/ionic-framework/commit/900267eb36c36f2af63435f6b46acca52b3bdab7)), closes [#27777](https://github.com/ionic-team/ionic-framework/issues/27777)
* **header:** collapsible large title does not flicker when collapse prop not reflected ([#28472](https://github.com/ionic-team/ionic-framework/issues/28472)) ([8227b0e](https://github.com/ionic-team/ionic-framework/commit/8227b0ee6d5250e122a34a83c644f8a74fbbafd5)), closes [#28466](https://github.com/ionic-team/ionic-framework/issues/28466)
* **item-divider:** apply safe area to proper side regardless of direction ([#28420](https://github.com/ionic-team/ionic-framework/issues/28420)) ([4513e0c](https://github.com/ionic-team/ionic-framework/commit/4513e0c6b066d4990800c707e1d97f69c8fcfb0c))
* **radio-group:** emit value change on componentDidLoad ([#28488](https://github.com/ionic-team/ionic-framework/issues/28488)) ([73b8bfd](https://github.com/ionic-team/ionic-framework/commit/73b8bfde3f060490958c10f58d0f68de80cb957f)), closes [#28356](https://github.com/ionic-team/ionic-framework/issues/28356)
* **searchbar:** cancel icon aligns with back button ([#28478](https://github.com/ionic-team/ionic-framework/issues/28478)) ([c053fd9](https://github.com/ionic-team/ionic-framework/commit/c053fd9c68d9b1add1335db80be962215946a0b1)), closes [#28468](https://github.com/ionic-team/ionic-framework/issues/28468)
> [!NOTE]
> Ionic Vue developers utilizing the `v-ion-change` or `v-ion-input` workaround for https://github.com/ionic-team/ionic-framework/issues/27292 should remove this workaround when updating to Ionic v7.5.5.
## [7.5.4](https://github.com/ionic-team/ionic-framework/compare/v7.5.3...v7.5.4) (2023-11-08)
### Bug Fixes
* **inputs:** remove invalid legacy warnings in input, textarea, and select ([#28484](https://github.com/ionic-team/ionic-framework/issues/28484)) ([c765dcb](https://github.com/ionic-team/ionic-framework/commit/c765dcbac4148762768d8c2bea9103e7d38c510b))
* **item:** apply safe area to proper side regardless of direction ([#28403](https://github.com/ionic-team/ionic-framework/issues/28403)) ([ed040b0](https://github.com/ionic-team/ionic-framework/commit/ed040b09e9cbd4246864e690542132defc6a6578))
* **list:** remove border from last item with item-sliding ([#28439](https://github.com/ionic-team/ionic-framework/issues/28439)) ([cafafcc](https://github.com/ionic-team/ionic-framework/commit/cafafcc9d166ef536dcb73edd522c8f2a0fb95b6)), closes [#28435](https://github.com/ionic-team/ionic-framework/issues/28435)
## [7.5.3](https://github.com/ionic-team/ionic-framework/compare/v7.5.2...v7.5.3) (2023-11-01)
### Bug Fixes
* **alert:** long words wrap to next line ([#28408](https://github.com/ionic-team/ionic-framework/issues/28408)) ([34257d6](https://github.com/ionic-team/ionic-framework/commit/34257d681e9034b0a001aa45e17222f3aab5ed76)), closes [#28406](https://github.com/ionic-team/ionic-framework/issues/28406)
* **angular:** standalone form components do not error when multiple are used ([#28423](https://github.com/ionic-team/ionic-framework/issues/28423)) ([89698b3](https://github.com/ionic-team/ionic-framework/commit/89698b338fb05cde427c98720c238d2365abdaa7)), closes [#28418](https://github.com/ionic-team/ionic-framework/issues/28418)
* **datetime:** allow calendar navigation in readonly mode; disallow keyboard navigation when disabled ([#28336](https://github.com/ionic-team/ionic-framework/issues/28336)) ([f6a6877](https://github.com/ionic-team/ionic-framework/commit/f6a6877044a6d912a92aab00c3c78897da09415d)), closes [#28121](https://github.com/ionic-team/ionic-framework/issues/28121)
* **input, textarea, select:** use consistent sizes ([#28390](https://github.com/ionic-team/ionic-framework/issues/28390)) ([b31ecbb](https://github.com/ionic-team/ionic-framework/commit/b31ecbbfe8deb87604686d752e92e672dd9b277a)), closes [#28388](https://github.com/ionic-team/ionic-framework/issues/28388)
* **list-header:** apply safe area to proper side regardless of direction ([#28371](https://github.com/ionic-team/ionic-framework/issues/28371)) ([f99d530](https://github.com/ionic-team/ionic-framework/commit/f99d5305fb4b1607b42e34a0b7653d8e1b5bf23f))
* **segment:** avoid scrolling webkit bug ([#28376](https://github.com/ionic-team/ionic-framework/issues/28376)) ([8e2f818](https://github.com/ionic-team/ionic-framework/commit/8e2f81867175e9980e6d072b0a4414baae571223)), closes [#28373](https://github.com/ionic-team/ionic-framework/issues/28373)
* **tab-bar:** apply safe area to proper side regardless of direction ([#28372](https://github.com/ionic-team/ionic-framework/issues/28372)) ([d47b7e7](https://github.com/ionic-team/ionic-framework/commit/d47b7e750310ceb2f2c7ecfda8343923ff8d564a))
## [7.5.2](https://github.com/ionic-team/ionic-framework/compare/v7.5.1...v7.5.2) (2023-10-25)

View File

@@ -315,7 +315,6 @@ ion-checkbox,css-prop,--checkmark-width
ion-checkbox,css-prop,--size
ion-checkbox,css-prop,--transition
ion-checkbox,part,container
ion-checkbox,part,label
ion-checkbox,part,mark
ion-chip,shadow
@@ -1030,12 +1029,10 @@ ion-radio,css-prop,--color
ion-radio,css-prop,--color-checked
ion-radio,css-prop,--inner-border-radius
ion-radio,part,container
ion-radio,part,label
ion-radio,part,mark
ion-radio-group,none
ion-radio-group,prop,allowEmptySelection,boolean,false,false,false
ion-radio-group,prop,compareWith,((currentValue: any, compareValue: any) => boolean) | null | string | undefined,undefined,false,false
ion-radio-group,prop,name,string,this.inputId,false,false
ion-radio-group,prop,value,any,undefined,false,false
ion-radio-group,event,ionChange,RadioGroupChangeEventDetail<any>,true
@@ -1079,7 +1076,6 @@ ion-range,css-prop,--pin-color
ion-range,part,bar
ion-range,part,bar-active
ion-range,part,knob
ion-range,part,label
ion-range,part,pin
ion-range,part,tick
ion-range,part,tick-active
@@ -1450,7 +1446,6 @@ ion-toast,prop,message,IonicSafeString | string | undefined,undefined,false,fals
ion-toast,prop,mode,"ios" | "md",undefined,false,false
ion-toast,prop,position,"bottom" | "middle" | "top",'bottom',false,false
ion-toast,prop,positionAnchor,HTMLElement | string | undefined,undefined,false,false
ion-toast,prop,swipeGesture,"vertical" | undefined,undefined,false,false
ion-toast,prop,translucent,boolean,false,false,false
ion-toast,prop,trigger,string | undefined,undefined,false,false
ion-toast,method,dismiss,dismiss(data?: any, role?: string) => Promise<boolean>
@@ -1517,7 +1512,6 @@ ion-toggle,css-prop,--handle-width
ion-toggle,css-prop,--track-background
ion-toggle,css-prop,--track-background-checked
ion-toggle,part,handle
ion-toggle,part,label
ion-toggle,part,track
ion-toolbar,shadow

5283
core/package-lock.json generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "7.6.0",
"version": "7.5.2",
"description": "Base components for Ionic",
"keywords": [
"ionic",
@@ -31,7 +31,7 @@
"loader/"
],
"dependencies": {
"@stencil/core": "^4.8.1",
"@stencil/core": "^4.6.0",
"ionicons": "^7.2.1",
"tslib": "^2.1.0"
},
@@ -43,14 +43,15 @@
"@capacitor/status-bar": "^5.0.6",
"@ionic/eslint-config": "^0.3.0",
"@ionic/prettier-config": "^2.0.0",
"@jest/core": "^27.5.1",
"@playwright/test": "^1.39.0",
"@rollup/plugin-node-resolve": "^8.4.0",
"@rollup/plugin-virtual": "^2.0.3",
"@stencil/angular-output-target": "^0.8.3",
"@stencil/react-output-target": "^0.5.3",
"@stencil/sass": "^3.0.7",
"@stencil/vue-output-target": "^0.8.7",
"@types/jest": "^29.5.6",
"@stencil/vue-output-target": "^0.8.6",
"@types/jest": "^27.5.2",
"@types/node": "^14.6.0",
"@typescript-eslint/eslint-plugin": "^6.7.2",
"@typescript-eslint/parser": "^6.7.2",
@@ -61,10 +62,9 @@
"eslint-plugin-custom-rules": "file:custom-rules",
"execa": "^5.0.0",
"fs-extra": "^9.0.1",
"jest": "^29.7.0",
"jest-cli": "^29.7.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"prettier": "^2.6.1",
"puppeteer": "21.1.1",
"rollup": "^2.26.4",
"sass": "^1.33.0",
"serve": "^14.0.1",
@@ -86,11 +86,13 @@
"lint.sass.fix": "npm run lint.sass -- --fix",
"lint.ts": "npm run eslint",
"lint.ts.fix": "npm run eslint -- --fix",
"prerelease": "npm run validate && np prerelease --yolo --any-branch --tag next",
"prerender.e2e": "node scripts/testing/prerender.js",
"prettier": "prettier \"./src/**/*.{html,ts,tsx,js,jsx}\"",
"start": "npm run build.css && stencil build --dev --watch --serve",
"test": "npm run test.spec && npm run test.e2e",
"test.spec": "stencil test --spec --max-workers=2",
"test.spec.debug": "npx --node-arg=\"--inspect-brk\" stencil test --spec",
"test.e2e": "npx playwright test",
"test.e2e.update-snapshots": "npm run test.e2e -- --update-snapshots",
"test.watch": "jest --watch --no-cache",

View File

@@ -1,151 +0,0 @@
/*
* Dark Colors
* -------------------------------------------
*/
:root {
--ion-color-primary: #428cff;
--ion-color-primary-rgb: 66, 140, 255;
--ion-color-primary-contrast: #ffffff;
--ion-color-primary-contrast-rgb: 255, 255, 255;
--ion-color-primary-shade: #3a7be0;
--ion-color-primary-tint: #5598ff;
--ion-color-secondary: #50c8ff;
--ion-color-secondary-rgb: 80, 200, 255;
--ion-color-secondary-contrast: #ffffff;
--ion-color-secondary-contrast-rgb: 255, 255, 255;
--ion-color-secondary-shade: #46b0e0;
--ion-color-secondary-tint: #62ceff;
--ion-color-tertiary: #6a64ff;
--ion-color-tertiary-rgb: 106, 100, 255;
--ion-color-tertiary-contrast: #ffffff;
--ion-color-tertiary-contrast-rgb: 255, 255, 255;
--ion-color-tertiary-shade: #5d58e0;
--ion-color-tertiary-tint: #7974ff;
--ion-color-success: #2fdf75;
--ion-color-success-rgb: 47, 223, 117;
--ion-color-success-contrast: #000000;
--ion-color-success-contrast-rgb: 0, 0, 0;
--ion-color-success-shade: #29c467;
--ion-color-success-tint: #44e283;
--ion-color-warning: #ffd534;
--ion-color-warning-rgb: 255, 213, 52;
--ion-color-warning-contrast: #000000;
--ion-color-warning-contrast-rgb: 0, 0, 0;
--ion-color-warning-shade: #e0bb2e;
--ion-color-warning-tint: #ffd948;
--ion-color-danger: #ff4961;
--ion-color-danger-rgb: 255, 73, 97;
--ion-color-danger-contrast: #ffffff;
--ion-color-danger-contrast-rgb: 255, 255, 255;
--ion-color-danger-shade: #e04055;
--ion-color-danger-tint: #ff5b71;
--ion-color-dark: #f4f5f8;
--ion-color-dark-rgb: 244, 245, 248;
--ion-color-dark-contrast: #000000;
--ion-color-dark-contrast-rgb: 0, 0, 0;
--ion-color-dark-shade: #d7d8da;
--ion-color-dark-tint: #f5f6f9;
--ion-color-medium: #989aa2;
--ion-color-medium-rgb: 152, 154, 162;
--ion-color-medium-contrast: #000000;
--ion-color-medium-contrast-rgb: 0, 0, 0;
--ion-color-medium-shade: #86888f;
--ion-color-medium-tint: #a2a4ab;
--ion-color-light: #222428;
--ion-color-light-rgb: 34, 36, 40;
--ion-color-light-contrast: #ffffff;
--ion-color-light-contrast-rgb: 255, 255, 255;
--ion-color-light-shade: #1e2023;
--ion-color-light-tint: #383a3e;
}
/*
* iOS Dark Theme
* -------------------------------------------
*/
.ios body {
--ion-background-color: #000000;
--ion-background-color-rgb: 0, 0, 0;
--ion-text-color: #ffffff;
--ion-text-color-rgb: 255, 255, 255;
--ion-color-step-50: #0d0d0d;
--ion-color-step-100: #1a1a1a;
--ion-color-step-150: #262626;
--ion-color-step-200: #333333;
--ion-color-step-250: #404040;
--ion-color-step-300: #4d4d4d;
--ion-color-step-350: #595959;
--ion-color-step-400: #666666;
--ion-color-step-450: #737373;
--ion-color-step-500: #808080;
--ion-color-step-550: #8c8c8c;
--ion-color-step-600: #999999;
--ion-color-step-650: #a6a6a6;
--ion-color-step-700: #b3b3b3;
--ion-color-step-750: #bfbfbf;
--ion-color-step-800: #cccccc;
--ion-color-step-850: #d9d9d9;
--ion-color-step-900: #e6e6e6;
--ion-color-step-950: #f2f2f2;
--ion-toolbar-background: #0d0d0d;
--ion-item-background: #000000;
--ion-card-background: #1c1c1d;
}
/*
* Material Design Dark Theme
* -------------------------------------------
*/
.md body {
--ion-background-color: #121212;
--ion-background-color-rgb: 18, 18, 18;
--ion-text-color: #ffffff;
--ion-text-color-rgb: 255, 255, 255;
--ion-border-color: #222222;
--ion-color-step-50: #1e1e1e;
--ion-color-step-100: #2a2a2a;
--ion-color-step-150: #363636;
--ion-color-step-200: #414141;
--ion-color-step-250: #4d4d4d;
--ion-color-step-300: #595959;
--ion-color-step-350: #656565;
--ion-color-step-400: #717171;
--ion-color-step-450: #7d7d7d;
--ion-color-step-500: #898989;
--ion-color-step-550: #949494;
--ion-color-step-600: #a0a0a0;
--ion-color-step-650: #acacac;
--ion-color-step-700: #b8b8b8;
--ion-color-step-750: #c4c4c4;
--ion-color-step-800: #d0d0d0;
--ion-color-step-850: #dbdbdb;
--ion-color-step-900: #e7e7e7;
--ion-color-step-950: #f3f3f3;
--ion-item-background: #1e1e1e;
--ion-toolbar-background: #1f1f1f;
--ion-tab-bar-background: #1f1f1f;
--ion-card-background: #1e1e1e;
}

View File

@@ -1,21 +0,0 @@
expect.extend({
toHaveShadowPart(received, part) {
if (typeof part !== 'string') {
throw new Error('expected toHaveShadowPart to be called with a string of the CSS shadow part name');
}
if (received.shadowRoot === null) {
throw new Error('expected toHaveShadowPart to be called on an element with a shadow root');
}
const shadowPart = received.shadowRoot.querySelector(`[part="${part}"]`);
const pass = shadowPart !== null;
const message = `expected ${received.tagName.toLowerCase()} to have shadow part "${part}"`;
return {
pass,
message: () => message,
};
},
});

View File

@@ -27,7 +27,7 @@ import { PickerButton, PickerColumn } from "./components/picker/picker-interface
import { PickerColumnItem } from "./components/picker-column-internal/picker-column-internal-interfaces";
import { PickerInternalChangeEventDetail } from "./components/picker-internal/picker-internal-interfaces";
import { PopoverSize, PositionAlign, PositionReference, PositionSide, TriggerAction } from "./components/popover/popover-interface";
import { RadioGroupChangeEventDetail, RadioGroupCompareFn } from "./components/radio-group/radio-group-interface";
import { RadioGroupChangeEventDetail } from "./components/radio-group/radio-group-interface";
import { PinFormatter, RangeChangeEventDetail, RangeKnobMoveEndEventDetail, RangeKnobMoveStartEventDetail, RangeValue } from "./components/range/range-interface";
import { RefresherEventDetail } from "./components/refresher/refresher-interface";
import { ItemReorderEventDetail } from "./components/reorder-group/reorder-group-interface";
@@ -39,7 +39,7 @@ import { SelectChangeEventDetail, SelectCompareFn, SelectInterface } from "./com
import { SelectPopoverOption } from "./components/select-popover/select-popover-interface";
import { TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout } from "./components/tab-bar/tab-bar-interface";
import { TextareaChangeEventDetail, TextareaInputEventDetail } from "./components/textarea/textarea-interface";
import { ToastButton, ToastDismissOptions, ToastLayout, ToastPosition, ToastPresentOptions, ToastSwipeGestureDirection } from "./components/toast/toast-interface";
import { ToastButton, ToastDismissOptions, ToastLayout, ToastPosition, ToastPresentOptions } from "./components/toast/toast-interface";
import { ToggleChangeEventDetail } from "./components/toggle/toggle-interface";
export { AccordionGroupChangeEventDetail } from "./components/accordion-group/accordion-group-interface";
export { AnimationBuilder, AutocompleteTypes, Color, ComponentProps, ComponentRef, FrameworkDelegate, StyleEventDetail, TextFieldTypes } from "./interface";
@@ -63,7 +63,7 @@ export { PickerButton, PickerColumn } from "./components/picker/picker-interface
export { PickerColumnItem } from "./components/picker-column-internal/picker-column-internal-interfaces";
export { PickerInternalChangeEventDetail } from "./components/picker-internal/picker-internal-interfaces";
export { PopoverSize, PositionAlign, PositionReference, PositionSide, TriggerAction } from "./components/popover/popover-interface";
export { RadioGroupChangeEventDetail, RadioGroupCompareFn } from "./components/radio-group/radio-group-interface";
export { RadioGroupChangeEventDetail } from "./components/radio-group/radio-group-interface";
export { PinFormatter, RangeChangeEventDetail, RangeKnobMoveEndEventDetail, RangeKnobMoveStartEventDetail, RangeValue } from "./components/range/range-interface";
export { RefresherEventDetail } from "./components/refresher/refresher-interface";
export { ItemReorderEventDetail } from "./components/reorder-group/reorder-group-interface";
@@ -75,7 +75,7 @@ export { SelectChangeEventDetail, SelectCompareFn, SelectInterface } from "./com
export { SelectPopoverOption } from "./components/select-popover/select-popover-interface";
export { TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout } from "./components/tab-bar/tab-bar-interface";
export { TextareaChangeEventDetail, TextareaInputEventDetail } from "./components/textarea/textarea-interface";
export { ToastButton, ToastDismissOptions, ToastLayout, ToastPosition, ToastPresentOptions, ToastSwipeGestureDirection } from "./components/toast/toast-interface";
export { ToastButton, ToastDismissOptions, ToastLayout, ToastPosition, ToastPresentOptions } from "./components/toast/toast-interface";
export { ToggleChangeEventDetail } from "./components/toggle/toggle-interface";
export namespace Components {
interface IonAccordion {
@@ -915,7 +915,7 @@ export namespace Components {
*/
"presentation": DatetimePresentation;
/**
* If `true`, the datetime appears normal but the selected date cannot be changed.
* If `true`, the datetime appears normal but is not interactive.
*/
"readonly": boolean;
/**
@@ -1162,7 +1162,7 @@ export namespace Components {
*/
"autocorrect": 'on' | 'off';
/**
* Sets the [`autofocus` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) on the native input element. This may not be sufficient for the element to be focused on page load. See [managing focus](/docs/developing/managing-focus) for more information.
* This Boolean attribute lets you specify that a form control should have input focus when the page loads.
*/
"autofocus": boolean;
/**
@@ -1274,7 +1274,7 @@ export namespace Components {
*/
"required": boolean;
/**
* Sets focus on the native `input` in `ion-input`. Use this method instead of the global `input.focus()`. Developers who wish to focus an input when a page enters should call `setFocus()` in the `ionViewDidEnter()` lifecycle method. Developers who wish to focus an input when an overlay is presented should call `setFocus` after `didPresent` has resolved. See [managing focus](/docs/developing/managing-focus) for more information.
* Sets focus on the native `input` in `ion-input`. Use this method instead of the global `input.focus()`. Developers who wish to focus an input when a page enters should call `setFocus()` in the `ionViewDidEnter()` lifecycle method. Developers who wish to focus an input when an overlay is presented should call `setFocus` after `didPresent` has resolved.
*/
"setFocus": () => Promise<void>;
/**
@@ -2043,10 +2043,6 @@ export namespace Components {
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
*/
"color"?: Color;
/**
* If `true`, the user cannot interact with the picker.
*/
"disabled": boolean;
/**
* A list of options to be displayed in the picker
*/
@@ -2269,10 +2265,6 @@ export namespace Components {
* If `true`, the radios can be deselected.
*/
"allowEmptySelection": boolean;
/**
* This property allows developers to specify a custom function or property name for comparing objects when determining the selected option in the ion-radio-group. When not specified, the default behavior will use strict equality (===) for comparison.
*/
"compareWith"?: string | RadioGroupCompareFn | null;
/**
* The name of the control, which is submitted with the form data.
*/
@@ -2605,7 +2597,7 @@ export namespace Components {
*/
"searchIcon"?: string;
/**
* Sets focus on the native `input` in `ion-searchbar`. Use this method instead of the global `input.focus()`. Developers who wish to focus an input when a page enters should call `setFocus()` in the `ionViewDidEnter()` lifecycle method. Developers who wish to focus an input when an overlay is presented should call `setFocus` after `didPresent` has resolved. See [managing focus](/docs/developing/managing-focus) for more information.
* Sets focus on the native `input` in `ion-searchbar`. Use this method instead of the global `input.focus()`. Developers who wish to focus an input when a page enters should call `setFocus()` in the `ionViewDidEnter()` lifecycle method. Developers who wish to focus an input when an overlay is presented should call `setFocus` after `didPresent` has resolved.
*/
"setFocus": () => Promise<void>;
/**
@@ -2692,7 +2684,7 @@ export namespace Components {
*/
"color"?: Color;
/**
* This property allows developers to specify a custom function or property name for comparing objects when determining the selected option in the ion-select. When not specified, the default behavior will use strict equality (===) for comparison.
* A property name or function used to compare object values
*/
"compareWith"?: string | SelectCompareFn | null;
/**
@@ -2954,7 +2946,7 @@ export namespace Components {
*/
"autocapitalize": string;
/**
* Sets the [`autofocus` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) on the native input element. This may not be sufficient for the element to be focused on page load. See [managing focus](/docs/developing/managing-focus) for more information.
* This Boolean attribute lets you specify that a form control should have input focus when the page loads.
*/
"autofocus": boolean;
/**
@@ -3054,7 +3046,7 @@ export namespace Components {
*/
"rows"?: number;
/**
* Sets focus on the native `textarea` in `ion-textarea`. Use this method instead of the global `textarea.focus()`. See [managing focus](/docs/developing/managing-focus) for more information.
* Sets focus on the native `textarea` in `ion-textarea`. Use this method instead of the global `textarea.focus()`.
*/
"setFocus": () => Promise<void>;
/**
@@ -3176,10 +3168,6 @@ export namespace Components {
* Present the toast overlay after it has been created.
*/
"present": () => Promise<void>;
/**
* If set to 'vertical', the Toast can be dismissed with a swipe gesture. The swipe direction is determined by the value of the `position` property: `top`: The Toast can be swiped up to dismiss. `bottom`: The Toast can be swiped down to dismiss. `middle`: The Toast can be swiped up or down to dismiss.
*/
"swipeGesture"?: ToastSwipeGestureDirection;
/**
* If `true`, the toast will be translucent. Only applies when the mode is `"ios"` and the device supports [`backdrop-filter`](https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter#Browser_compatibility).
*/
@@ -3398,10 +3386,6 @@ export interface IonSelectCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLIonSelectElement;
}
export interface IonSkeletonTextCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLIonSkeletonTextElement;
}
export interface IonSplitPaneCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLIonSplitPaneElement;
@@ -4416,18 +4400,7 @@ declare global {
prototype: HTMLIonSelectPopoverElement;
new (): HTMLIonSelectPopoverElement;
};
interface HTMLIonSkeletonTextElementEventMap {
"ionStyle": StyleEventDetail;
}
interface HTMLIonSkeletonTextElement extends Components.IonSkeletonText, HTMLStencilElement {
addEventListener<K extends keyof HTMLIonSkeletonTextElementEventMap>(type: K, listener: (this: HTMLIonSkeletonTextElement, ev: IonSkeletonTextCustomEvent<HTMLIonSkeletonTextElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeEventListener<K extends keyof HTMLIonSkeletonTextElementEventMap>(type: K, listener: (this: HTMLIonSkeletonTextElement, ev: IonSkeletonTextCustomEvent<HTMLIonSkeletonTextElementEventMap[K]>) => any, options?: boolean | EventListenerOptions): void;
removeEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}
var HTMLIonSkeletonTextElement: {
prototype: HTMLIonSkeletonTextElement;
@@ -5622,7 +5595,7 @@ declare namespace LocalJSX {
*/
"presentation"?: DatetimePresentation;
/**
* If `true`, the datetime appears normal but the selected date cannot be changed.
* If `true`, the datetime appears normal but is not interactive.
*/
"readonly"?: boolean;
/**
@@ -5877,7 +5850,7 @@ declare namespace LocalJSX {
*/
"autocorrect"?: 'on' | 'off';
/**
* Sets the [`autofocus` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) on the native input element. This may not be sufficient for the element to be focused on page load. See [managing focus](/docs/developing/managing-focus) for more information.
* This Boolean attribute lets you specify that a form control should have input focus when the page loads.
*/
"autofocus"?: boolean;
/**
@@ -6710,10 +6683,6 @@ declare namespace LocalJSX {
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
*/
"color"?: Color;
/**
* If `true`, the user cannot interact with the picker.
*/
"disabled"?: boolean;
/**
* A list of options to be displayed in the picker
*/
@@ -6957,10 +6926,6 @@ declare namespace LocalJSX {
* If `true`, the radios can be deselected.
*/
"allowEmptySelection"?: boolean;
/**
* This property allows developers to specify a custom function or property name for comparing objects when determining the selected option in the ion-radio-group. When not specified, the default behavior will use strict equality (===) for comparison.
*/
"compareWith"?: string | RadioGroupCompareFn | null;
/**
* The name of the control, which is submitted with the form data.
*/
@@ -7443,7 +7408,7 @@ declare namespace LocalJSX {
*/
"color"?: Color;
/**
* This property allows developers to specify a custom function or property name for comparing objects when determining the selected option in the ion-select. When not specified, the default behavior will use strict equality (===) for comparison.
* A property name or function used to compare object values
*/
"compareWith"?: string | SelectCompareFn | null;
/**
@@ -7580,10 +7545,6 @@ declare namespace LocalJSX {
* If `true`, the skeleton text will animate.
*/
"animated"?: boolean;
/**
* Emitted when the styles change.
*/
"onIonStyle"?: (event: IonSkeletonTextCustomEvent<StyleEventDetail>) => void;
}
interface IonSpinner {
/**
@@ -7730,7 +7691,7 @@ declare namespace LocalJSX {
*/
"autocapitalize"?: string;
/**
* Sets the [`autofocus` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) on the native input element. This may not be sufficient for the element to be focused on page load. See [managing focus](/docs/developing/managing-focus) for more information.
* This Boolean attribute lets you specify that a form control should have input focus when the page loads.
*/
"autofocus"?: boolean;
/**
@@ -7982,10 +7943,6 @@ declare namespace LocalJSX {
* The element to anchor the toast's position to. Can be set as a direct reference or the ID of the element. With `position="bottom"`, the toast will sit above the chosen element. With `position="top"`, the toast will sit below the chosen element. With `position="middle"`, the value of `positionAnchor` is ignored.
*/
"positionAnchor"?: HTMLElement | string;
/**
* If set to 'vertical', the Toast can be dismissed with a swipe gesture. The swipe direction is determined by the value of the `position` property: `top`: The Toast can be swiped up to dismiss. `bottom`: The Toast can be swiped down to dismiss. `middle`: The Toast can be swiped up or down to dismiss.
*/
"swipeGesture"?: ToastSwipeGestureDirection;
/**
* If `true`, the toast will be translucent. Only applies when the mode is `"ios"` and the device supports [`backdrop-filter`](https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter#Browser_compatibility).
*/

View File

@@ -180,16 +180,6 @@ export class AccordionGroup implements ComponentInterface {
if (this.readonly) {
this.readonlyChanged();
}
/**
* When binding values in frameworks such as Angular
* it is possible for the value to be set after the Web Component
* initializes but before the value watcher is set up in Stencil.
* As a result, the watcher callback may not be fired.
* We work around this by manually calling the watcher
* callback when the component has loaded and the watcher
* is configured.
*/
this.valueChanged();
}
/**

View File

@@ -1,8 +1,8 @@
import { newSpecPage } from '@stencil/core/testing';
import { AccordionGroup } from '../../accordion-group/accordion-group';
import { Item } from '../../item/item';
import { Accordion } from '../accordion';
import { AccordionGroup } from '../../accordion-group/accordion-group.tsx';
import { Item } from '../../item/item.tsx';
import { Accordion } from '../accordion.tsx';
it('should open correct accordions when accordion group value is set', async () => {
const page = await newSpecPage({
@@ -25,7 +25,7 @@ it('should open correct accordions when accordion group value is set', async ()
`,
});
const accordionGroup = page.body.querySelector('ion-accordion-group')!;
const accordionGroup = page.body.querySelector('ion-accordion-group');
const accordions = accordionGroup.querySelectorAll('ion-accordion');
accordions.forEach((accordion) => {
@@ -61,7 +61,7 @@ it('should open correct accordions when accordion value is set', async () => {
`,
});
const accordionGroup = page.body.querySelector('ion-accordion-group')!;
const accordionGroup = page.body.querySelector('ion-accordion-group');
const accordions = accordionGroup.querySelectorAll('ion-accordion');
accordions.forEach((accordion) => {
@@ -97,7 +97,7 @@ it('should open more than one accordion when multiple="true"', async () => {
`,
});
const accordionGroup = page.body.querySelector('ion-accordion-group')!;
const accordionGroup = page.body.querySelector('ion-accordion-group');
const accordions = accordionGroup.querySelectorAll('ion-accordion');
accordions.forEach((accordion) => {
@@ -133,7 +133,7 @@ it('should render with accordion open', async () => {
`,
});
const accordionGroup = page.body.querySelector('ion-accordion-group')!;
const accordionGroup = page.body.querySelector('ion-accordion-group');
const accordions = accordionGroup.querySelectorAll('ion-accordion');
expect(accordions[0].classList.contains('accordion-collapsed')).toEqual(false);
@@ -162,7 +162,7 @@ it('should accept a string when multiple="true"', async () => {
`,
});
const accordionGroup = page.body.querySelector('ion-accordion-group')!;
const accordionGroup = page.body.querySelector('ion-accordion-group');
const accordions = accordionGroup.querySelectorAll('ion-accordion');
expect(accordions[0].classList.contains('accordion-collapsed')).toEqual(false);
@@ -183,8 +183,8 @@ it('should set default values if not provided', async () => {
`,
});
const accordionGroup = page.body.querySelector('ion-accordion-group')!;
const accordion = accordionGroup.querySelector('ion-accordion')!;
const accordionGroup = page.body.querySelector('ion-accordion-group');
const accordion = accordionGroup.querySelector('ion-accordion');
/**
* ID is determined via an auto incrementing counter

View File

@@ -128,8 +128,8 @@
height: 100%;
/* Fallback for browsers that do not support dvh */
max-height: calc(100vh - (var(--ion-safe-area-top, 0) + var(--ion-safe-area-bottom, 0)));
max-height: calc(100dvh - (var(--ion-safe-area-top, 0) + var(--ion-safe-area-bottom, 0)));
max-height: 100vh;
max-height: 100dvh;
}
.action-sheet-group {

View File

@@ -337,17 +337,6 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
if (this.isOpen === true) {
raf(() => this.present());
}
/**
* When binding values in frameworks such as Angular
* it is possible for the value to be set after the Web Component
* initializes but before the value watcher is set up in Stencil.
* As a result, the watcher callback may not be fired.
* We work around this by manually calling the watcher
* callback when the component has loaded and the watcher
* is configured.
*/
this.triggerChanged();
}
render() {

View File

@@ -100,69 +100,3 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ config, title }) =>
});
});
});
/**
* This behavior needs to be tested in both modes but does not vary
* across directions due to the component only applying safe area
* to the top and bottom
*/
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('action-sheet: basic'), () => {
test.describe('safe area', () => {
test('should have padding added by the safe area', async ({ page }, testInfo) => {
testInfo.annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/27777',
});
await page.setContent(
`
<style>
:root {
--ion-safe-area-top: 60px;
--ion-safe-area-bottom: 40px;
}
</style>
<ion-action-sheet></ion-action-sheet>
<script>
const actionSheet = document.querySelector('ion-action-sheet');
actionSheet.header = 'Header';
actionSheet.subHeader = 'Sub Header';
actionSheet.buttons = [
'Add Reaction',
'Copy Text',
'Share Text',
'Copy Link to Message',
'Remind Me',
'Pin File',
'Star File',
'Mark Unread',
'Mark Read',
'Edit Title',
'Erase Title',
'Save Image',
'Copy Image',
'Erase Image',
'Delete File',
{
text: 'Cancel',
role: 'cancel'
},
];
</script>
`,
config
);
const ionActionSheetDidPresent = await page.spyOnEvent('ionActionSheetDidPresent');
const actionSheet = page.locator('ion-action-sheet');
await actionSheet.evaluate((el: HTMLIonActionSheetElement) => el.present());
await ionActionSheetDidPresent.next();
await expect(actionSheet).toHaveScreenshot(screenshot(`action-sheet-safe-area`));
});
});
});
});

View File

@@ -7,17 +7,10 @@ describe('action sheet: htmlAttributes inheritance', () => {
it('should correctly inherit attributes on host', async () => {
const page = await newSpecPage({
components: [ActionSheet],
template: () => (
<ion-action-sheet
htmlAttributes={{
'data-testid': 'basic-action-sheet',
}}
overlayIndex={1}
></ion-action-sheet>
),
template: () => <ion-action-sheet htmlAttributes={{ 'data-testid': 'basic-action-sheet' }}></ion-action-sheet>,
});
const actionSheet = page.body.querySelector('ion-action-sheet')!;
const actionSheet = page.body.querySelector('ion-action-sheet');
await expect(actionSheet.getAttribute('data-testid')).toBe('basic-action-sheet');
});

View File

@@ -105,17 +105,6 @@
&::-ms-clear {
display: none;
}
&::-webkit-date-and-time-value {
/**
* The -webkit-date-and-time-value pseudo element is used
* to style the date/time input on iOS/Mobile Safari.
* To avoid layout shift between an empty state and a selected state,
* we set the height `18px` to match the native input height for
* date/time inputs on iOS/Mobile Safari.
*/
height: 18px;
}
}

View File

@@ -52,18 +52,9 @@
}
.alert-message {
font-size: $alert-md-message-font-size;
}
max-height: $alert-md-content-max-height;
/**
* MD Alerts on tablets can expand vertically up to
* a total maximum height. We only want to set a max-height
* on mobile phones.
*/
@include mobile-viewport() {
.alert-message {
max-height: $alert-md-content-max-height;
}
font-size: $alert-md-message-font-size;
}
.alert-message:empty {
@@ -111,24 +102,14 @@
.alert-checkbox-group {
position: relative;
max-height: $alert-md-content-max-height;
border-top: $alert-md-list-border-top;
border-bottom: $alert-md-list-border-bottom;
overflow: auto;
}
/**
* MD Alerts on tablets can expand vertically up to
* a total maximum height. We only want to set a max-height
* on mobile phones.
*/
@include mobile-viewport() {
.alert-radio-group,
.alert-checkbox-group {
max-height: $alert-md-content-max-height;
}
}
.alert-tappable {
position: relative;
@@ -301,14 +282,3 @@
.alert-button-inner {
justify-content: $alert-md-button-group-justify-content;
}
/**
* MD alerts should scale up to 560px x 560px
* on tablet dimensions.
*/
@include tablet-viewport() {
:host {
--max-width: #{$alert-md-max-width-tablet};
--max-height: #{$alert-md-max-height-tablet};
}
}

View File

@@ -10,20 +10,6 @@ $alert-md-font-size: dynamic-font(14px) !default;
/// @prop - Max width of the alert
$alert-md-max-width: 280px !default;
/// @prop - Max width of the alert on a tablet
/**
* Large display requirements for MD Alert:
* 1. Maintain a minimum of 48px distance from the leading and
* trailing edges of the screen. (48px * 2 = 96px)
* 2. The width can increase up to 560px.
* 3. The height can increase up to 560px.
* Source: https://m2.material.io/components/dialogs#behavior
*/
$alert-md-max-width-tablet: min(calc(100vw - 96px), 560px) !default;
/// @prop - Max width of the alert on a tablet
$alert-md-max-height-tablet: min(calc(100vh - 96px), 560px) !default;
/// @prop - Border radius of the alert
$alert-md-border-radius: 4px !default;

View File

@@ -84,15 +84,7 @@
font-weight: normal;
}
/**
* Alert has a maximum height in scenarios
* such as the MD alert on tablet devices.
* As a result, we need to make sure the inner
* containers can scroll otherwise content
* may be cut off.
*/
.alert-message,
.alert-input-group {
.alert-message {
box-sizing: border-box;
-webkit-overflow-scrolling: touch;
overflow-y: auto;

View File

@@ -376,17 +376,6 @@ export class Alert implements ComponentInterface, OverlayInterface {
if (this.isOpen === true) {
raf(() => this.present());
}
/**
* When binding values in frameworks such as Angular
* it is possible for the value to be set after the Web Component
* initializes but before the value watcher is set up in Stencil.
* As a result, the watcher callback may not be fired.
* We work around this by manually calling the watcher
* callback when the component has loaded and the watcher
* is configured.
*/
this.triggerChanged();
}
/**

View File

@@ -1,7 +1,6 @@
import { newSpecPage } from '@stencil/core/testing';
import { config } from '../../../global/config';
import { Alert } from '../alert';
import { config } from '../../../global/config';
describe('alert: custom html', () => {
it('should not allow for custom html by default', async () => {
@@ -10,7 +9,7 @@ describe('alert: custom html', () => {
html: `<ion-alert message="<button class='custom-html'>Custom Text</button>"></ion-alert>`,
});
const content = page.body.querySelector('.alert-message')!;
const content = page.body.querySelector('.alert-message');
expect(content.textContent).toContain('Custom Text');
expect(content.querySelector('button.custom-html')).toBe(null);
});
@@ -22,7 +21,7 @@ describe('alert: custom html', () => {
html: `<ion-alert message="<button class='custom-html'>Custom Text</button>"></ion-alert>`,
});
const content = page.body.querySelector('.alert-message')!;
const content = page.body.querySelector('.alert-message');
expect(content.textContent).toContain('Custom Text');
expect(content.querySelector('button.custom-html')).not.toBe(null);
});
@@ -34,7 +33,7 @@ describe('alert: custom html', () => {
html: `<ion-alert message="<button class='custom-html'>Custom Text</button>"></ion-alert>`,
});
const content = page.body.querySelector('.alert-message')!;
const content = page.body.querySelector('.alert-message');
expect(content.textContent).toContain('Custom Text');
expect(content.querySelector('button.custom-html')).toBe(null);
});

View File

@@ -1,47 +0,0 @@
import { expect } from '@playwright/test';
import { configs, test, Viewports } from '@utils/test/playwright';
/**
* This behavior does not vary across directions.
*/
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('alert: rendering - tablet'), () => {
test.beforeEach(async ({ page }) => {
await page.setViewportSize(Viewports.tablet.portrait);
await page.goto('/src/components/alert/test/basic', config);
});
test('should expand width and height on larger displays with text', async ({ page }) => {
const ionAlertDidPresent = await page.spyOnEvent('ionAlertDidPresent');
const button = page.locator('#longMessage');
const alert = page.locator('ion-alert');
await button.click();
await ionAlertDidPresent.next();
await expect(alert).toHaveScreenshot(screenshot('alert-tablet-text'));
});
test('should expand width and height on larger displays with checkboxes', async ({ page }) => {
const ionAlertDidPresent = await page.spyOnEvent('ionAlertDidPresent');
const button = page.locator('#checkbox');
const alert = page.locator('ion-alert');
await button.click();
await ionAlertDidPresent.next();
await expect(alert).toHaveScreenshot(screenshot('alert-tablet-checkboxes'));
});
test('should expand width and height on larger displays with radios', async ({ page }) => {
const ionAlertDidPresent = await page.spyOnEvent('ionAlertDidPresent');
const button = page.locator('#radio');
const alert = page.locator('ion-alert');
await button.click();
await ionAlertDidPresent.next();
await expect(alert).toHaveScreenshot(screenshot('alert-tablet-radios'));
});
});
});

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -1,7 +1,7 @@
import { newSpecPage } from '@stencil/core/testing';
import { Breadcrumb } from '../../breadcrumb/breadcrumb';
import { Breadcrumbs } from '../breadcrumbs';
import { Breadcrumb } from '../../breadcrumb/breadcrumb.tsx';
import { Breadcrumbs } from '../breadcrumbs.tsx';
it('should correctly provide the collapsed breadcrumbs in the event payload', async () => {
const page = await newSpecPage({
@@ -18,8 +18,8 @@ it('should correctly provide the collapsed breadcrumbs in the event payload', as
});
const onCollapsedClick = jest.fn((ev) => ev);
const breadcrumbs = page.body.querySelector('ion-breadcrumbs')!;
const breadcrumb = page.body.querySelectorAll('ion-breadcrumb')!;
const breadcrumbs = page.body.querySelector('ion-breadcrumbs');
const breadcrumb = page.body.querySelectorAll('ion-breadcrumb');
breadcrumbs.addEventListener('ionCollapsedClick', onCollapsedClick);
@@ -46,8 +46,8 @@ it('should exclude the separator from narrators', async () => {
`,
});
const firstBreadcrumb = page.body.querySelector('ion-breadcrumb:first-of-type')!;
const separator = firstBreadcrumb.shadowRoot!.querySelector('[part="separator"]')!;
const firstBreadcrumb = page.body.querySelector('ion-breadcrumb:first-of-type');
const separator = firstBreadcrumb.shadowRoot.querySelector('[part="separator"]');
expect(separator.getAttribute('aria-hidden')).toBe('true');
});
@@ -62,7 +62,7 @@ it('should have color attribute', async () => {
`,
});
const breadcrumbs = page.body.querySelector('ion-breadcrumbs')!;
const breadcrumbs = page.body.querySelector('ion-breadcrumbs');
expect(breadcrumbs.hasAttribute('color')).toBe(true);
});

View File

@@ -1,5 +1,4 @@
import { newSpecPage } from '@stencil/core/testing';
import { Button } from '../../button';
describe('Button: Hidden Form Button', () => {
@@ -16,7 +15,8 @@ describe('Button: Hidden Form Button', () => {
return page.body.querySelectorAll('form button');
};
const button = page.body.querySelector('ion-button')!;
const form = page.body.querySelectorAll('form');
const button = page.body.querySelector('ion-button');
await page.waitForChanges();

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -90,6 +90,13 @@
}
.label-text-wrapper {
/**
* This ensures that double tapping this text
* clicks the <label> and focuses the checkbox
* when a screen reader is enabled.
*/
pointer-events: none;
text-overflow: ellipsis;
white-space: nowrap;
@@ -166,6 +173,7 @@ input {
opacity: 0;
}
// Justify Content
// ---------------------------------------------
@@ -192,6 +200,7 @@ input {
align-items: center;
}
// Label Placement - Start
// ----------------------------------------------------------------
@@ -212,6 +221,7 @@ input {
@include margin(null, $form-control-label-margin, null, 0);
}
// Label Placement - End
// ----------------------------------------------------------------
@@ -232,6 +242,7 @@ input {
@include margin(null, 0, null, $form-control-label-margin);
}
// Label Placement - Fixed
// ----------------------------------------------------------------
@@ -306,6 +317,7 @@ input {
opacity: 1;
}
// Disabled Checkbox
// ---------------------------------------------

View File

@@ -18,7 +18,6 @@ import type { CheckboxChangeEventDetail } from './checkbox-interface';
* @slot - The label text to associate with the checkbox. Use the "labelPlacement" property to control where the label is placed relative to the checkbox.
*
* @part container - The container for the checkbox mark.
* @part label - The label text describing the checkbox.
* @part mark - The checkmark used to indicate the checked state.
*/
@Component({
@@ -165,8 +164,6 @@ export class Checkbox implements ComponentInterface {
private emitStyle() {
const style: StyleEventDetail = {
'interactive-disabled': this.disabled,
// TODO(FW-3100): remove this
legacy: !!this.legacy,
};
// TODO(FW-3100): remove this
@@ -285,7 +282,6 @@ export class Checkbox implements ComponentInterface {
'label-text-wrapper': true,
'label-text-wrapper-hidden': el.textContent === '',
}}
part="label"
>
<slot></slot>
</div>

View File

@@ -2,23 +2,6 @@ import { newSpecPage } from '@stencil/core/testing';
import { Checkbox } from '../checkbox';
describe('ion-checkbox: shadow parts', () => {
it('should render the checkbox with shadow parts', async () => {
const page = await newSpecPage({
components: [Checkbox],
html: `
<ion-checkbox>Checkbox</ion-checkbox>
`,
});
const checkbox = page.body.querySelector('ion-checkbox')!;
expect(checkbox).toHaveShadowPart('container');
expect(checkbox).toHaveShadowPart('label');
expect(checkbox).toHaveShadowPart('mark');
});
});
describe('ion-checkbox: disabled', () => {
it('clicking disabled checkbox should not toggle checked state', async () => {
const page = await newSpecPage({
@@ -28,7 +11,7 @@ describe('ion-checkbox: disabled', () => {
`,
});
const checkbox = page.body.querySelector('ion-checkbox')!;
const checkbox = page.body.querySelector('ion-checkbox');
expect(checkbox.checked).toBe(false);

View File

@@ -136,3 +136,7 @@
justify-content: flex-end;
}
:host .datetime-view-buttons ion-button {
color: $text-color-step-200;
}

View File

@@ -185,37 +185,13 @@ ion-picker-column-internal {
display: none;
}
:host(.datetime-readonly),
:host(.datetime-disabled) {
pointer-events: none;
.calendar-days-of-week,
.datetime-time {
opacity: 0.4;
}
}
:host(.datetime-readonly) {
pointer-events: none;
/**
* Allow user to navigate months
* while in readonly mode
*/
.calendar-action-buttons,
.calendar-body,
.datetime-year {
pointer-events: initial;
}
/**
* Disabled buttons should have full opacity
* in readonly mode
*/
.calendar-day[disabled]:not(.calendar-day-constrained),
.datetime-action-buttons ion-button[disabled] {
opacity: 1;
}
:host(.datetime-disabled) {
opacity: 0.4;
}
/**
@@ -267,9 +243,6 @@ ion-picker-column-internal {
justify-content: space-between;
}
// TODO(FW-3547): the styles targeting ion-item
// can be removed if we refactor datetime to
// not use an ion-item
:host .calendar-action-buttons ion-item,
:host .calendar-action-buttons ion-button {
--background: translucent;
@@ -279,11 +252,6 @@ ion-picker-column-internal {
display: flex;
align-items: center;
// Width is set to auto because it is set
// to min-content elsewhere and we want to
// prevent wrapping the label in datetime
width: auto;
}
:host .calendar-action-buttons ion-item ion-icon {

View File

@@ -172,7 +172,7 @@ export class Datetime implements ComponentInterface {
@Prop() disabled = false;
/**
* If `true`, the datetime appears normal but the selected date cannot be changed.
* If `true`, the datetime appears normal but is not interactive.
*/
@Prop() readonly = false;
@@ -599,14 +599,6 @@ export class Datetime implements ComponentInterface {
};
private setActiveParts = (parts: DatetimeParts, removeDate = false) => {
/** if the datetime component is in readonly mode,
* allow browsing of the calendar without changing
* the set value
*/
if (this.readonly) {
return;
}
const { multiple, minParts, maxParts, activeParts } = this;
/**
@@ -1132,7 +1124,7 @@ export class Datetime implements ComponentInterface {
* so we need to re-init behavior with the new elements.
*/
componentDidRender() {
const { presentation, prevPresentation, calendarBodyRef, minParts, preferWheel, forceRenderDate } = this;
const { presentation, prevPresentation, calendarBodyRef, minParts, preferWheel } = this;
/**
* TODO(FW-2165)
@@ -1150,20 +1142,7 @@ export class Datetime implements ComponentInterface {
const hasCalendarGrid = !preferWheel && ['date-time', 'time-date', 'date'].includes(presentation);
if (minParts !== undefined && hasCalendarGrid && calendarBodyRef) {
const workingMonth = calendarBodyRef.querySelector('.calendar-month:nth-of-type(1)');
/**
* We need to make sure the datetime is not in the process
* of scrolling to a new datetime value if the value
* is updated programmatically.
* Otherwise, the datetime will appear to not scroll at all because
* we are resetting the scroll position to the center of the view.
* Prior to the datetime's value being updated programmatically,
* the calendarBodyRef is scrolled such that the middle month is centered
* in the view. The below code updates the scroll position so the middle
* month is also centered in the view. Since the scroll position did not change,
* the scroll callback in this file does not fire,
* and the resolveForceDateScrolling promise never resolves.
*/
if (workingMonth && forceRenderDate === undefined) {
if (workingMonth) {
calendarBodyRef.scrollLeft = workingMonth.clientWidth * (isRTL(this.el) ? -1 : 1);
}
}
@@ -1435,13 +1414,7 @@ export class Datetime implements ComponentInterface {
*/
private renderFooter() {
const { disabled, readonly, showDefaultButtons, showClearButton } = this;
/**
* The cancel, clear, and confirm buttons
* should not be interactive if the datetime
* is disabled or readonly.
*/
const isButtonDisabled = disabled || readonly;
const { showDefaultButtons, showClearButton } = this;
const hasSlottedButtons = this.el.querySelector('[slot="buttons"]') !== null;
if (!hasSlottedButtons && !showDefaultButtons && !showClearButton) {
return;
@@ -1471,33 +1444,18 @@ export class Datetime implements ComponentInterface {
<slot name="buttons">
<ion-buttons>
{showDefaultButtons && (
<ion-button
id="cancel-button"
color={this.color}
onClick={() => this.cancel(true)}
disabled={isButtonDisabled}
>
<ion-button id="cancel-button" color={this.color} onClick={() => this.cancel(true)}>
{this.cancelText}
</ion-button>
)}
<div class="datetime-action-buttons-container">
{showClearButton && (
<ion-button
id="clear-button"
color={this.color}
onClick={() => clearButtonClick()}
disabled={isButtonDisabled}
>
<ion-button id="clear-button" color={this.color} onClick={() => clearButtonClick()}>
{this.clearText}
</ion-button>
)}
{showDefaultButtons && (
<ion-button
id="confirm-button"
color={this.color}
onClick={() => this.confirm(true)}
disabled={isButtonDisabled}
>
<ion-button id="confirm-button" color={this.color} onClick={() => this.confirm(true)}>
{this.doneText}
</ion-button>
)}
@@ -1538,7 +1496,7 @@ export class Datetime implements ComponentInterface {
}
private renderCombinedDatePickerColumn() {
const { defaultParts, disabled, workingParts, locale, minParts, maxParts, todayParts, isDateEnabled } = this;
const { defaultParts, workingParts, locale, minParts, maxParts, todayParts, isDateEnabled } = this;
const activePart = this.getActivePartsWithFallback();
@@ -1617,7 +1575,6 @@ export class Datetime implements ComponentInterface {
<ion-picker-column-internal
class="date-column"
color={this.color}
disabled={disabled}
items={items}
value={todayString}
onIonChange={(ev: CustomEvent) => {
@@ -1729,7 +1686,7 @@ export class Datetime implements ComponentInterface {
return [];
}
const { disabled, workingParts } = this;
const { workingParts } = this;
const activePart = this.getActivePartsWithFallback();
@@ -1737,7 +1694,6 @@ export class Datetime implements ComponentInterface {
<ion-picker-column-internal
class="day-column"
color={this.color}
disabled={disabled}
items={days}
value={(workingParts.day !== null ? workingParts.day : this.defaultParts.day) ?? undefined}
onIonChange={(ev: CustomEvent) => {
@@ -1774,7 +1730,7 @@ export class Datetime implements ComponentInterface {
return [];
}
const { disabled, workingParts } = this;
const { workingParts } = this;
const activePart = this.getActivePartsWithFallback();
@@ -1782,7 +1738,6 @@ export class Datetime implements ComponentInterface {
<ion-picker-column-internal
class="month-column"
color={this.color}
disabled={disabled}
items={months}
value={workingParts.month}
onIonChange={(ev: CustomEvent) => {
@@ -1818,7 +1773,7 @@ export class Datetime implements ComponentInterface {
return [];
}
const { disabled, workingParts } = this;
const { workingParts } = this;
const activePart = this.getActivePartsWithFallback();
@@ -1826,7 +1781,6 @@ export class Datetime implements ComponentInterface {
<ion-picker-column-internal
class="year-column"
color={this.color}
disabled={disabled}
items={years}
value={workingParts.year}
onIonChange={(ev: CustomEvent) => {
@@ -1892,7 +1846,7 @@ export class Datetime implements ComponentInterface {
}
private renderHourPickerColumn(hoursData: PickerColumnItem[]) {
const { disabled, workingParts } = this;
const { workingParts } = this;
if (hoursData.length === 0) return [];
const activePart = this.getActivePartsWithFallback();
@@ -1900,7 +1854,6 @@ export class Datetime implements ComponentInterface {
return (
<ion-picker-column-internal
color={this.color}
disabled={disabled}
value={activePart.hour}
items={hoursData}
numericInput
@@ -1921,7 +1874,7 @@ export class Datetime implements ComponentInterface {
);
}
private renderMinutePickerColumn(minutesData: PickerColumnItem[]) {
const { disabled, workingParts } = this;
const { workingParts } = this;
if (minutesData.length === 0) return [];
const activePart = this.getActivePartsWithFallback();
@@ -1929,7 +1882,6 @@ export class Datetime implements ComponentInterface {
return (
<ion-picker-column-internal
color={this.color}
disabled={disabled}
value={activePart.minute}
items={minutesData}
numericInput
@@ -1950,7 +1902,7 @@ export class Datetime implements ComponentInterface {
);
}
private renderDayPeriodPickerColumn(dayPeriodData: PickerColumnItem[]) {
const { disabled, workingParts } = this;
const { workingParts } = this;
if (dayPeriodData.length === 0) {
return [];
}
@@ -1962,7 +1914,6 @@ export class Datetime implements ComponentInterface {
<ion-picker-column-internal
style={isDayPeriodRTL ? { order: '-1' } : {}}
color={this.color}
disabled={disabled}
value={activePart.ampm}
items={dayPeriodData}
onIonChange={(ev: CustomEvent) => {
@@ -2006,12 +1957,11 @@ export class Datetime implements ComponentInterface {
*/
private renderCalendarHeader(mode: Mode) {
const { disabled } = this;
const expandedIcon = mode === 'ios' ? chevronDown : caretUpSharp;
const collapsedIcon = mode === 'ios' ? chevronForward : caretDownSharp;
const prevMonthDisabled = disabled || isPrevMonthDisabled(this.workingParts, this.minParts, this.maxParts);
const nextMonthDisabled = disabled || isNextMonthDisabled(this.workingParts, this.maxParts);
const prevMonthDisabled = isPrevMonthDisabled(this.workingParts, this.minParts, this.maxParts);
const nextMonthDisabled = isNextMonthDisabled(this.workingParts, this.maxParts);
// don't use the inheritAttributes util because it removes dir from the host, and we still need that
const hostDir = this.el.getAttribute('dir') || undefined;
@@ -2027,7 +1977,6 @@ export class Datetime implements ComponentInterface {
aria-label="Show year picker"
detail={false}
lines="none"
disabled={disabled}
onClick={() => {
this.toggleMonthAndYearView();
/**
@@ -2094,28 +2043,23 @@ export class Datetime implements ComponentInterface {
);
}
private renderMonth(month: number, year: number) {
const { disabled, readonly } = this;
const yearAllowed = this.parsedYearValues === undefined || this.parsedYearValues.includes(year);
const monthAllowed = this.parsedMonthValues === undefined || this.parsedMonthValues.includes(month);
const isCalMonthDisabled = !yearAllowed || !monthAllowed;
const isDatetimeDisabled = disabled || readonly;
const swipeDisabled =
disabled ||
isMonthDisabled(
{
month,
year,
day: null,
},
{
// The day is not used when checking if a month is disabled.
// Users should be able to access the min or max month, even if the
// min/max date is out of bounds (e.g. min is set to Feb 15, Feb should not be disabled).
minParts: { ...this.minParts, day: null },
maxParts: { ...this.maxParts, day: null },
}
);
const swipeDisabled = isMonthDisabled(
{
month,
year,
day: null,
},
{
// The day is not used when checking if a month is disabled.
// Users should be able to access the min or max month, even if the
// min/max date is out of bounds (e.g. min is set to Feb 15, Feb should not be disabled).
minParts: { ...this.minParts, day: null },
maxParts: { ...this.maxParts, day: null },
}
);
// The working month should never have swipe disabled.
// Otherwise the CSS scroll snap will not work and the user
// can free-scroll the calendar.
@@ -2139,14 +2083,7 @@ export class Datetime implements ComponentInterface {
const { el, highlightedDates, isDateEnabled, multiple } = this;
const referenceParts = { month, day, year };
const isCalendarPadding = day === null;
const {
isActive,
isToday,
ariaLabel,
ariaSelected,
disabled: isDayDisabled,
text,
} = getCalendarDayState(
const { isActive, isToday, ariaLabel, ariaSelected, disabled, text } = getCalendarDayState(
this.locale,
referenceParts,
this.activeParts,
@@ -2157,8 +2094,7 @@ export class Datetime implements ComponentInterface {
);
const dateIsoString = convertDataToISO(referenceParts);
let isCalDayDisabled = isCalMonthDisabled || isDayDisabled;
let isCalDayDisabled = isCalMonthDisabled || disabled;
if (!isCalDayDisabled && isDateEnabled !== undefined) {
try {
@@ -2177,15 +2113,6 @@ export class Datetime implements ComponentInterface {
}
}
/**
* Some days are constrained through max & min or allowed dates
* and also disabled because the component is readonly or disabled.
* These need to be displayed differently.
*/
const isCalDayConstrained = isCalDayDisabled && isDatetimeDisabled;
const isButtonDisabled = isCalDayDisabled || isDatetimeDisabled;
let dateStyle: DatetimeHighlightStyle | undefined = undefined;
/**
@@ -2231,12 +2158,11 @@ export class Datetime implements ComponentInterface {
data-year={year}
data-index={index}
data-day-of-week={dayOfWeek}
disabled={isButtonDisabled}
disabled={isCalDayDisabled}
class={{
'calendar-day-padding': isCalendarPadding,
'calendar-day': true,
'calendar-day-active': isActive,
'calendar-day-constrained': isCalDayConstrained,
'calendar-day-today': isToday,
}}
part={dateParts}
@@ -2311,7 +2237,7 @@ export class Datetime implements ComponentInterface {
}
private renderTimeOverlay() {
const { disabled, hourCycle, isTimePopoverOpen, locale } = this;
const { hourCycle, isTimePopoverOpen, locale } = this;
const computedHourCycle = getHourCycle(locale, hourCycle);
const activePart = this.getActivePartsWithFallback();
@@ -2325,7 +2251,6 @@ export class Datetime implements ComponentInterface {
part={`time-button${isTimePopoverOpen ? ' active' : ''}`}
aria-expanded="false"
aria-haspopup="true"
disabled={disabled}
onClick={async (ev) => {
const { popoverRef } = this;

View File

@@ -30,102 +30,3 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
});
});
});
/**
* This behavior does not differ across
* modes/directions.
*/
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('datetime: a11y'), () => {
test('datetime should be keyboard navigable', async ({ page, browserName }) => {
await page.setContent(
`
<ion-datetime value="2022-02-22T16:30:00"></ion-datetime>
`,
config
);
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
const datetime = page.locator('ion-datetime');
const monthYearButton = page.locator('.calendar-month-year ion-item');
const prevButton = page.locator('.calendar-next-prev ion-button:nth-child(1)');
const nextButton = page.locator('.calendar-next-prev ion-button:nth-child(2)');
await page.keyboard.press(tabKey);
await expect(monthYearButton).toBeFocused();
await page.keyboard.press(tabKey);
await expect(prevButton).toBeFocused();
await page.keyboard.press(tabKey);
await expect(nextButton).toBeFocused();
// check value before & after selecting via keyboard
const initialValue = await datetime.evaluate((el: HTMLIonDatetimeElement) => el.value);
expect(initialValue).toBe('2022-02-22T16:30:00');
await page.keyboard.press(tabKey);
await page.waitForChanges();
await page.keyboard.press('ArrowLeft');
await page.waitForChanges();
await page.keyboard.press('Enter');
await page.waitForChanges();
const newValue = await datetime.evaluate((el: HTMLIonDatetimeElement) => el.value);
expect(newValue).not.toBe('2022-02-22T16:30:00');
});
test('buttons should be keyboard navigable', async ({ page }) => {
await page.setContent(
`
<ion-datetime value="2022-02-22T16:30:00" show-default-buttons="true" show-clear-button="true"></ion-datetime>
`,
config
);
await page.waitForSelector('.datetime-ready');
const clearButton = page.locator('#clear-button button');
const selectedDay = page.locator('.calendar-day-active');
await expect(selectedDay).toHaveText('22');
await clearButton.focus();
await page.waitForChanges();
await expect(clearButton).toBeFocused();
await page.keyboard.press('Enter');
await page.waitForChanges();
await expect(selectedDay).toHaveCount(0);
});
test('should navigate through months via right arrow key', async ({ page }) => {
await page.setContent(
`
<ion-datetime value="2022-02-28"></ion-datetime>
`,
config
);
await page.waitForSelector('.datetime-ready');
const calendarMonthYear = page.locator('ion-datetime .calendar-month-year');
const calendarBody = page.locator('.calendar-body');
await expect(calendarMonthYear).toHaveText('February 2022');
await calendarBody.focus();
await page.waitForChanges();
await page.keyboard.press('ArrowRight');
await page.waitForChanges();
await expect(calendarMonthYear).toHaveText('March 2022');
});
});
});

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -1,22 +1,21 @@
import type { DatetimeParts } from '../datetime-interface';
import { isSameDay, isBefore, isAfter } from '../utils/comparison';
describe('isSameDay()', () => {
it('should return correct results for month, day, and year', () => {
const reference: DatetimeParts = { month: 1, day: 1, year: 2021 };
const reference = { month: 1, day: 1, year: 2021 };
expect(isSameDay(reference, { month: 1, day: 1, year: 2021 })).toEqual(true);
expect(isSameDay(reference, { month: 2, day: 1, year: 2021 })).toEqual(false);
expect(isSameDay(reference, { month: 1, day: 2, year: 2021 })).toEqual(false);
expect(isSameDay(reference, { month: 1, day: 1, year: 2022 })).toEqual(false);
expect(isSameDay(reference, { month: 0, day: 0, year: 0 })).toEqual(false);
expect(isSameDay(reference, { month: null, day: null, year: null } as any)).toEqual(false);
expect(isSameDay(reference, { month: null, day: null, year: null })).toEqual(false);
});
});
describe('isBefore()', () => {
it('should return correct results for month, day, and year', () => {
const reference: DatetimeParts = { month: 1, day: 1, year: 2021 };
const reference = { month: 1, day: 1, year: 2021 };
expect(isBefore(reference, { month: 1, day: 1, year: 2021 })).toEqual(false);
expect(isBefore(reference, { month: 2, day: 1, year: 2021 })).toEqual(true);
@@ -24,13 +23,13 @@ describe('isBefore()', () => {
expect(isBefore(reference, { month: 1, day: 1, year: 2022 })).toEqual(true);
expect(isBefore(reference, { month: 1, day: 1, year: 2020 })).toEqual(false);
expect(isBefore(reference, { month: 0, day: 0, year: 0 })).toEqual(false);
expect(isBefore(reference, { month: null, day: null, year: null } as any)).toEqual(false);
expect(isBefore(reference, { month: null, day: null, year: null })).toEqual(false);
});
});
describe('isAfter()', () => {
it('should return correct results for month, day, and year', () => {
const reference: DatetimeParts = { month: 2, day: 2, year: 2021 };
const reference = { month: 2, day: 2, year: 2021 };
expect(isAfter(reference, { month: 2, day: 2, year: 2021 })).toEqual(false);
expect(isAfter(reference, { month: 2, day: 1, year: 2021 })).toEqual(true);
@@ -43,6 +42,6 @@ describe('isAfter()', () => {
* 2021 > undefined === false
* 2021 > null === true
*/
expect(isAfter(reference, { month: null, day: null, year: null } as any)).toEqual(true);
expect(isAfter(reference, { month: null, day: null, year: null })).toEqual(true);
});
});

View File

@@ -1,4 +1,3 @@
import type { DatetimeParts } from '../datetime-interface';
import {
generateMonths,
getDaysOfWeek,
@@ -365,7 +364,7 @@ describe('generateTime()', () => {
year: undefined,
hour: 19,
minute: 50,
} as unknown as DatetimeParts;
};
const minParts = {
day: undefined,
@@ -373,7 +372,7 @@ describe('generateTime()', () => {
year: undefined,
hour: 19,
minute: 50,
} as unknown as DatetimeParts;
};
const { hours } = generateTime('en-US', refValue, 'h23', minParts);
@@ -388,7 +387,7 @@ describe('generateTime()', () => {
year: undefined,
hour: 20,
minute: 22,
} as unknown as DatetimeParts;
};
const minParts = {
day: undefined,
@@ -396,7 +395,7 @@ describe('generateTime()', () => {
year: undefined,
hour: 19,
minute: 30,
} as unknown as DatetimeParts;
};
const { hours, minutes } = generateTime('en-US', refValue, 'h23', minParts);
@@ -412,7 +411,7 @@ describe('generateTime()', () => {
year: undefined,
hour: 20,
minute: 30,
} as unknown as DatetimeParts;
};
const minParts = {
day: undefined,
@@ -420,7 +419,7 @@ describe('generateTime()', () => {
year: undefined,
hour: 19,
minute: 30,
} as unknown as DatetimeParts;
};
const maxParts = {
day: undefined,
@@ -428,7 +427,7 @@ describe('generateTime()', () => {
year: undefined,
hour: 20,
minute: 40,
} as unknown as DatetimeParts;
};
const { hours } = generateTime('en-US', refValue, 'h23', minParts, maxParts);
@@ -442,7 +441,7 @@ describe('generateTime()', () => {
year: undefined,
hour: 13,
minute: 0,
} as unknown as DatetimeParts;
};
const maxParts = {
day: undefined,
@@ -450,7 +449,7 @@ describe('generateTime()', () => {
year: undefined,
hour: 13,
minute: 2,
} as unknown as DatetimeParts;
};
const { minutes } = generateTime('en-US', refValue, 'h23', undefined, maxParts);
@@ -464,7 +463,7 @@ describe('generateTime()', () => {
year: undefined,
hour: 12,
minute: 0,
} as unknown as DatetimeParts;
};
const maxParts = {
day: undefined,
@@ -472,7 +471,7 @@ describe('generateTime()', () => {
year: undefined,
hour: 13,
minute: 2,
} as unknown as DatetimeParts;
};
const { minutes } = generateTime('en-US', refValue, 'h23', undefined, maxParts);
@@ -483,7 +482,7 @@ describe('generateTime()', () => {
describe('getToday', () => {
beforeAll(() => {
jest.useFakeTimers();
jest.useFakeTimers('modern');
// System time is zero based, 1 = February
jest.setSystemTime(new Date(2022, 1, 21, 18, 30));
});

View File

@@ -1,103 +0,0 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
/**
* This behavior does not differ across
* modes/directions.
*/
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config, screenshot }) => {
test.describe(title('datetime: disabled'), () => {
test('should not have visual regressions', async ({ page }) => {
await page.setContent(
`
<ion-datetime value="2022-02-05T00:00:00" min="2022-01-01T00:00:00" max="2022-02-20T23:59:59" day-values="5,6,10,11,15,16,20" show-default-buttons disabled></ion-datetime>
`,
config
);
const datetime = page.locator('ion-datetime');
await expect(datetime).toHaveScreenshot(screenshot(`datetime-disabled`));
});
test('date should be disabled', async ({ page }) => {
await page.setContent(
`
<ion-datetime value="2022-02-28" disabled></ion-datetime>
`,
config
);
await page.waitForSelector('.datetime-ready');
const febFirstButton = page.locator(`.calendar-day[data-day='1'][data-month='2']`);
await expect(febFirstButton).toBeDisabled();
});
test('month-year button should be disabled', async ({ page }) => {
await page.setContent(
`
<ion-datetime value="2022-02-28" disabled></ion-datetime>
`,
config
);
await page.waitForSelector('.datetime-ready');
const calendarMonthYear = page.locator('ion-datetime .calendar-month-year');
await expect(calendarMonthYear.locator('button')).toBeDisabled();
});
test('next and prev buttons should be disabled', async ({ page }) => {
await page.setContent(
`
<ion-datetime value="2022-02-28" disabled></ion-datetime>
`,
config
);
const prevMonthButton = page.locator('ion-datetime .calendar-next-prev ion-button:first-of-type button');
const nextMonthButton = page.locator('ion-datetime .calendar-next-prev ion-button:last-of-type button');
await expect(prevMonthButton).toBeDisabled();
await expect(nextMonthButton).toBeDisabled();
});
test('clear button should be disabled', async ({ page }) => {
await page.setContent(
`
<ion-datetime value="2022-02-22T16:30:00" show-default-buttons="true" show-clear-button="true" disabled></ion-datetime>
`,
config
);
await page.waitForSelector('.datetime-ready');
const clearButton = page.locator('#clear-button button');
await expect(clearButton).toBeDisabled();
});
test('should not navigate through months via right arrow key', async ({ page }) => {
await page.setContent(
`
<ion-datetime value="2022-02-28" disabled></ion-datetime>
`,
config
);
await page.waitForSelector('.datetime-ready');
const calendarMonthYear = page.locator('ion-datetime .calendar-month-year');
const calendarBody = page.locator('.calendar-body');
await expect(calendarMonthYear).toHaveText('February 2022');
await calendarBody.focus();
await page.waitForChanges();
await page.keyboard.press('ArrowRight');
await page.waitForChanges();
await expect(calendarMonthYear).toHaveText('February 2022');
});
});
});

View File

@@ -1,39 +0,0 @@
import { h } from '@stencil/core';
import { newSpecPage } from '@stencil/core/testing';
import { Datetime } from '../../../datetime/datetime';
import { PickerColumnInternal } from '../../../picker-column-internal/picker-column-internal';
import { PickerInternal } from '../../../picker-internal/picker-internal';
describe('ion-datetime disabled', () => {
beforeEach(() => {
// IntersectionObserver isn't available in test environment
const mockIntersectionObserver = jest.fn();
mockIntersectionObserver.mockReturnValue({
observe: () => null,
unobserve: () => null,
disconnect: () => null,
});
global.IntersectionObserver = mockIntersectionObserver;
});
it('picker should be disabled in prefer wheel mode', async () => {
const page = await newSpecPage({
components: [Datetime, PickerColumnInternal, PickerInternal],
template: () => (
<ion-datetime id="inline-datetime-wheel" disabled prefer-wheel value="2022-04-21T00:00:00"></ion-datetime>
),
});
await page.waitForChanges();
const datetime = page.body.querySelector('ion-datetime')!;
const columns = datetime.shadowRoot!.querySelectorAll('ion-picker-column-internal');
await expect(columns.length).toEqual(4);
columns.forEach((column) => {
expect(column.disabled).toBe(true);
});
});
});

View File

@@ -1,82 +0,0 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Datetime - Disabled</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<style>
.grid {
display: grid;
grid-template-columns: repeat(3, minmax(250px, 1fr));
grid-row-gap: 20px;
grid-column-gap: 20px;
}
h2 {
font-size: 12px;
font-weight: normal;
color: #6f7378;
margin-top: 10px;
margin-left: 5px;
}
@media screen and (max-width: 800px) {
.grid {
grid-template-columns: 1fr;
padding: 0;
}
}
ion-datetime {
width: 350px;
}
</style>
</head>
<body>
<ion-app>
<ion-header translucent="true">
<ion-toolbar>
<ion-title>Datetime - Disabled</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<div class="grid">
<div class="grid-item">
<h2>Inline - Default Value</h2>
<ion-datetime id="inline-datetime-value" disabled value="2022-04-21T00:00:00"></ion-datetime>
</div>
<div class="grid-item">
<h2>Inline</h2>
<ion-datetime
id="inline-datetime"
presentation="date"
disabled
show-default-buttons="true"
show-clear-button="true"
multiple="true"
></ion-datetime>
</div>
<div class="grid-item">
<h2>Inline - No Default Value</h2>
<ion-datetime id="inline-datetime-no-value" disabled></ion-datetime>
</div>
<div class="grid-item">
<h2>Inline - Prefer Wheel</h2>
<ion-datetime id="inline-datetime-wheel" disabled prefer-wheel value="2022-04-21T00:00:00"></ion-datetime>
</div>
</div>
</ion-content>
<script>
const firstDatetime = document.querySelector('#inline-datetime');
firstDatetime.value = ['2023-08-03', '2023-08-13', '2023-08-29'];
</script>
</ion-app>
</body>
</html>

View File

@@ -1,4 +1,3 @@
import type { DatetimeParts } from '../datetime-interface';
import {
generateDayAriaLabel,
getMonthAndDay,
@@ -110,7 +109,7 @@ describe('getLocalizedDayPeriod', () => {
describe('getLocalizedTime', () => {
it('should localize the time to PM', () => {
const datetimeParts: DatetimeParts = {
const datetimeParts = {
day: 1,
month: 1,
year: 2022,
@@ -122,7 +121,7 @@ describe('getLocalizedTime', () => {
});
it('should localize the time to AM', () => {
const datetimeParts: DatetimeParts = {
const datetimeParts = {
day: 1,
month: 1,
year: 2022,
@@ -134,7 +133,7 @@ describe('getLocalizedTime', () => {
});
it('should avoid Chromium bug when using 12 hour time in a 24 hour locale', () => {
const datetimeParts: DatetimeParts = {
const datetimeParts = {
day: 1,
month: 1,
year: 2022,
@@ -145,12 +144,12 @@ describe('getLocalizedTime', () => {
expect(getLocalizedTime('en-GB', datetimeParts, 'h12')).toEqual('12:00 am');
});
it('should parse time-only values correctly', () => {
const datetimeParts: Partial<DatetimeParts> = {
const datetimeParts = {
hour: 22,
minute: 40,
};
expect(getLocalizedTime('en-US', datetimeParts as DatetimeParts, 'h12')).toEqual('10:40 PM');
expect(getLocalizedTime('en-US', datetimeParts as DatetimeParts, 'h23')).toEqual('22:40');
expect(getLocalizedTime('en-US', datetimeParts, 'h12')).toEqual('10:40 PM');
expect(getLocalizedTime('en-US', datetimeParts, 'h23')).toEqual('22:40');
});
});

View File

@@ -1,4 +1,3 @@
import type { DatetimeParts } from '../datetime-interface';
import {
getPreviousYear,
getNextYear,
@@ -104,31 +103,31 @@ describe('getInternalHourValue()', () => {
describe('calculateHourFromAMPM()', () => {
it('should correctly convert from AM to PM', () => {
expect(calculateHourFromAMPM({ hour: 12, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(12);
expect(calculateHourFromAMPM({ hour: 1, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(13);
expect(calculateHourFromAMPM({ hour: 2, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(14);
expect(calculateHourFromAMPM({ hour: 3, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(15);
expect(calculateHourFromAMPM({ hour: 4, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(16);
expect(calculateHourFromAMPM({ hour: 5, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(17);
expect(calculateHourFromAMPM({ hour: 6, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(18);
expect(calculateHourFromAMPM({ hour: 7, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(19);
expect(calculateHourFromAMPM({ hour: 8, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(20);
expect(calculateHourFromAMPM({ hour: 9, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(21);
expect(calculateHourFromAMPM({ hour: 10, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(22);
expect(calculateHourFromAMPM({ hour: 11, ampm: 'am' } as DatetimeParts, 'pm')).toEqual(23);
expect(calculateHourFromAMPM({ hour: 12, ampm: 'am' }, 'pm')).toEqual(12);
expect(calculateHourFromAMPM({ hour: 1, ampm: 'am' }, 'pm')).toEqual(13);
expect(calculateHourFromAMPM({ hour: 2, ampm: 'am' }, 'pm')).toEqual(14);
expect(calculateHourFromAMPM({ hour: 3, ampm: 'am' }, 'pm')).toEqual(15);
expect(calculateHourFromAMPM({ hour: 4, ampm: 'am' }, 'pm')).toEqual(16);
expect(calculateHourFromAMPM({ hour: 5, ampm: 'am' }, 'pm')).toEqual(17);
expect(calculateHourFromAMPM({ hour: 6, ampm: 'am' }, 'pm')).toEqual(18);
expect(calculateHourFromAMPM({ hour: 7, ampm: 'am' }, 'pm')).toEqual(19);
expect(calculateHourFromAMPM({ hour: 8, ampm: 'am' }, 'pm')).toEqual(20);
expect(calculateHourFromAMPM({ hour: 9, ampm: 'am' }, 'pm')).toEqual(21);
expect(calculateHourFromAMPM({ hour: 10, ampm: 'am' }, 'pm')).toEqual(22);
expect(calculateHourFromAMPM({ hour: 11, ampm: 'am' }, 'pm')).toEqual(23);
expect(calculateHourFromAMPM({ hour: 13, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(1);
expect(calculateHourFromAMPM({ hour: 14, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(2);
expect(calculateHourFromAMPM({ hour: 15, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(3);
expect(calculateHourFromAMPM({ hour: 16, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(4);
expect(calculateHourFromAMPM({ hour: 17, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(5);
expect(calculateHourFromAMPM({ hour: 18, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(6);
expect(calculateHourFromAMPM({ hour: 19, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(7);
expect(calculateHourFromAMPM({ hour: 20, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(8);
expect(calculateHourFromAMPM({ hour: 21, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(9);
expect(calculateHourFromAMPM({ hour: 22, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(10);
expect(calculateHourFromAMPM({ hour: 23, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(11);
expect(calculateHourFromAMPM({ hour: 0, ampm: 'pm' } as DatetimeParts, 'am')).toEqual(12);
expect(calculateHourFromAMPM({ hour: 13, ampm: 'pm' }, 'am')).toEqual(1);
expect(calculateHourFromAMPM({ hour: 14, ampm: 'pm' }, 'am')).toEqual(2);
expect(calculateHourFromAMPM({ hour: 15, ampm: 'pm' }, 'am')).toEqual(3);
expect(calculateHourFromAMPM({ hour: 16, ampm: 'pm' }, 'am')).toEqual(4);
expect(calculateHourFromAMPM({ hour: 17, ampm: 'pm' }, 'am')).toEqual(5);
expect(calculateHourFromAMPM({ hour: 18, ampm: 'pm' }, 'am')).toEqual(6);
expect(calculateHourFromAMPM({ hour: 19, ampm: 'pm' }, 'am')).toEqual(7);
expect(calculateHourFromAMPM({ hour: 20, ampm: 'pm' }, 'am')).toEqual(8);
expect(calculateHourFromAMPM({ hour: 21, ampm: 'pm' }, 'am')).toEqual(9);
expect(calculateHourFromAMPM({ hour: 22, ampm: 'pm' }, 'am')).toEqual(10);
expect(calculateHourFromAMPM({ hour: 23, ampm: 'pm' }, 'am')).toEqual(11);
expect(calculateHourFromAMPM({ hour: 0, ampm: 'pm' }, 'am')).toEqual(12);
});
});

View File

@@ -42,8 +42,7 @@ describe('parseDate()', () => {
* See https://github.com/ionic-team/ionic-framework/commit/3fb4caf21ffac12f765c4c80bf1850e05d211c6a
*/
it('should return the correct time zone offset', () => {
// Casting as any since `tzOffset` does not exist on DatetimeParts
expect((parseDate('2022-12-15T13:47:30-02:00') as any)?.tzOffset).toEqual(undefined);
expect(parseDate('2022-12-15T13:47:30-02:00').tzOffset).toEqual(undefined);
});
it('should parse an array of dates', () => {
@@ -163,8 +162,8 @@ describe('parseMinParts()', () => {
minute: 4,
hour: 2,
};
expect(parseMinParts(undefined as any, today)).toEqual(undefined);
expect(parseMinParts(null as any, today)).toEqual(undefined);
expect(parseMinParts(undefined, today)).toEqual(undefined);
expect(parseMinParts(null, today)).toEqual(undefined);
expect(parseMinParts('foo', today)).toEqual(undefined);
});
});
@@ -226,8 +225,8 @@ describe('parseMaxParts()', () => {
minute: 4,
hour: 2,
};
expect(parseMaxParts(undefined as any, today)).toEqual(undefined);
expect(parseMaxParts(null as any, today)).toEqual(undefined);
expect(parseMaxParts(undefined, today)).toEqual(undefined);
expect(parseMaxParts(null, today)).toEqual(undefined);
expect(parseMaxParts('foo', today)).toEqual(undefined);
});
});

View File

@@ -1,167 +0,0 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
/**
* This behavior does not differ across
* modes/directions.
*/
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config, screenshot }) => {
test.describe(title('datetime: readonly'), () => {
test('should not have visual regressions', async ({ page }) => {
await page.setContent(
`
<ion-datetime value="2022-02-05T00:00:00" min="2022-01-01T00:00:00" max="2022-02-20T23:59:59" day-values="5,6,10,11,15,16,20" show-default-buttons readonly></ion-datetime>
`,
config
);
const datetime = page.locator('ion-datetime');
await expect(datetime).toHaveScreenshot(screenshot(`datetime-readonly`));
});
test('date should be disabled', async ({ page }) => {
await page.setContent(
`
<ion-datetime value="2022-02-28" readonly></ion-datetime>
`,
config
);
await page.waitForSelector('.datetime-ready');
const febFirstButton = page.locator(`.calendar-day[data-day='1'][data-month='2']`);
await expect(febFirstButton).toBeDisabled();
});
test('should navigate months via month-year button', async ({ page }) => {
await page.setContent(
`
<ion-datetime value="2022-02-22T16:30:00" readonly></ion-datetime>
`,
config
);
const ionChange = await page.spyOnEvent('ionChange');
await page.waitForSelector('.datetime-ready');
const calendarMonthYear = page.locator('ion-datetime .calendar-month-year');
await expect(calendarMonthYear).toHaveText('February 2022');
await calendarMonthYear.click();
await page.waitForChanges();
await page.locator('.month-column .picker-item[data-value="3"]').click();
await page.waitForChanges();
await expect(calendarMonthYear).toHaveText('March 2022');
await expect(ionChange).not.toHaveReceivedEvent();
});
test('should open picker using keyboard navigation', async ({ page, browserName }) => {
await page.setContent(
`
<ion-datetime value="2022-02-22T16:30:00" readonly></ion-datetime>
`,
config
);
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
await page.waitForSelector('.datetime-ready');
const calendarMonthYear = page.locator('ion-datetime .calendar-month-year');
const monthYearButton = page.locator('.calendar-month-year ion-item');
await expect(calendarMonthYear).toHaveText('February 2022');
await page.keyboard.press(tabKey);
await expect(monthYearButton).toBeFocused();
await page.waitForChanges();
await page.keyboard.press('Enter');
await page.waitForChanges();
const marchPickerItem = page.locator('.month-column .picker-item[data-value="3"]');
await expect(marchPickerItem).toBeVisible();
});
test('should view next month via next button', async ({ page }) => {
await page.setContent(
`
<ion-datetime value="2022-02-22T16:30:00" readonly></ion-datetime>
`,
config
);
const ionChange = await page.spyOnEvent('ionChange');
const calendarMonthYear = page.locator('ion-datetime .calendar-month-year');
await expect(calendarMonthYear).toHaveText('February 2022');
const nextMonthButton = page.locator('ion-datetime .calendar-next-prev ion-button + ion-button');
await nextMonthButton.click();
await page.waitForChanges();
await expect(calendarMonthYear).toHaveText('March 2022');
await expect(ionChange).not.toHaveReceivedEvent();
});
test('should not change value when the month is changed via keyboard navigation', async ({ page, browserName }) => {
await page.setContent(
`
<ion-datetime value="2022-02-22T16:30:00" readonly></ion-datetime>
`,
config
);
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
const datetime = page.locator('ion-datetime');
const monthYearButton = page.locator('.calendar-month-year ion-item');
const prevButton = page.locator('.calendar-next-prev ion-button:nth-child(1)');
const nextButton = page.locator('.calendar-next-prev ion-button:nth-child(2)');
const calendarMonthYear = page.locator('ion-datetime .calendar-month-year');
await page.keyboard.press(tabKey);
await expect(monthYearButton).toBeFocused();
await page.keyboard.press(tabKey);
await expect(prevButton).toBeFocused();
await page.keyboard.press(tabKey);
await expect(nextButton).toBeFocused();
// check value before & after selecting via keyboard
const initialValue = await datetime.evaluate((el: HTMLIonDatetimeElement) => el.value);
expect(initialValue).toBe('2022-02-22T16:30:00');
await expect(calendarMonthYear).toHaveText('February 2022');
await page.keyboard.press(tabKey);
await page.waitForChanges();
await page.keyboard.press('ArrowLeft');
await page.waitForChanges();
await expect(calendarMonthYear).toHaveText('January 2022');
await page.keyboard.press('Enter');
await page.waitForChanges();
const newValue = await datetime.evaluate((el: HTMLIonDatetimeElement) => el.value);
// should not have changed
expect(newValue).toBe('2022-02-22T16:30:00');
});
test('clear button should be disabled', async ({ page }) => {
await page.setContent(
`
<ion-datetime value="2022-02-22T16:30:00" show-default-buttons="true" show-clear-button="true" readonly></ion-datetime>
`,
config
);
await page.waitForSelector('.datetime-ready');
const clearButton = page.locator('#clear-button button');
await expect(clearButton).toBeDisabled();
});
});
});

View File

@@ -1,83 +0,0 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Datetime - Readonly</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<style>
.grid {
display: grid;
grid-template-columns: repeat(3, minmax(250px, 1fr));
grid-row-gap: 20px;
grid-column-gap: 20px;
}
h2 {
font-size: 12px;
font-weight: normal;
color: #6f7378;
margin-top: 10px;
margin-left: 5px;
}
@media screen and (max-width: 800px) {
.grid {
grid-template-columns: 1fr;
padding: 0;
}
}
ion-datetime {
width: 350px;
}
</style>
</head>
<body>
<ion-app>
<ion-header translucent="true">
<ion-toolbar>
<ion-title>Datetime - Readonly</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<div class="grid">
<div class="grid-item">
<h2>Inline</h2>
<ion-datetime
id="inline-datetime"
presentation="date"
readonly
show-default-buttons="true"
show-clear-button="true"
multiple="true"
></ion-datetime>
</div>
<div class="grid-item">
<h2>Inline - No Default Value</h2>
<ion-datetime id="inline-datetime-no-value" readonly></ion-datetime>
</div>
</div>
</ion-content>
<script>
const firstDatetime = document.querySelector('#inline-datetime');
firstDatetime.value = ['2023-08-03', '2023-08-13', '2023-08-29'];
firstDatetime.isDateEnabled = (dateString) => {
const date = new Date(dateString);
const utcDay = date.getUTCDay();
/**
* Date will be enabled if it is not
* Sunday or Saturday
*/
return utcDay !== 0 && utcDay !== 6;
};
</script>
</ion-app>
</body>
</html>

View File

@@ -84,13 +84,13 @@ describe('isPrevMonthDisabled()', () => {
// Date month and year is the same as min month and year
expect(isPrevMonthDisabled({ month: 1, year: 2021, day: null }, { month: 1, year: 2021, day: null })).toEqual(true);
// Date year is the same as min year (month not provided)
expect(
isPrevMonthDisabled({ month: 1, year: 2021, day: null }, { year: 2021, month: null, day: null } as any)
).toEqual(true);
expect(isPrevMonthDisabled({ month: 1, year: 2021, day: null }, { year: 2021, month: null, day: null })).toEqual(
true
);
// Date year is less than the min year (month not provided)
expect(
isPrevMonthDisabled({ month: 5, year: 2021, day: null }, { year: 2022, month: null, day: null } as any)
).toEqual(true);
expect(isPrevMonthDisabled({ month: 5, year: 2021, day: null }, { year: 2022, month: null, day: null })).toEqual(
true
);
// Date is above the maximum bounds and the previous month does not does not fall within the
// min-max range.
@@ -118,12 +118,12 @@ describe('isPrevMonthDisabled()', () => {
expect(isPrevMonthDisabled({ month: 12, year: 2021, day: null })).toEqual(false);
// Date year is the same as min year,
// but can navigate to a previous month without reducing the year.
expect(
isPrevMonthDisabled({ month: 12, year: 2021, day: null }, { year: 2021, month: null, day: null } as any)
).toEqual(false);
expect(
isPrevMonthDisabled({ month: 2, year: 2021, day: null }, { year: 2021, month: null, day: null } as any)
).toEqual(false);
expect(isPrevMonthDisabled({ month: 12, year: 2021, day: null }, { year: 2021, month: null, day: null })).toEqual(
false
);
expect(isPrevMonthDisabled({ month: 2, year: 2021, day: null }, { year: 2021, month: null, day: null })).toEqual(
false
);
});
});

View File

@@ -130,10 +130,7 @@
* 2. This will only apply when that content has a collapse header (ion-header[collapse="condense"])
*
* We use opacity: 0 to avoid a layout shift.
* We target both the attribute and the class in the event that the attribute
* is not reflected on the host in some frameworks.
*/
ion-header:not(.header-collapse-main):has(~ ion-content ion-header[collapse="condense"],
~ ion-content ion-header.header-collapse-condense) {
ion-header:not(.header-collapse-main):has(~ ion-content ion-header[collapse="condense"]) {
opacity: 0;
}

View File

@@ -1,7 +1,6 @@
import { newSpecPage } from '@stencil/core/testing';
import { config } from '../../../global/config';
import { InfiniteScrollContent } from '../infinite-scroll-content';
import { config } from '../../../global/config';
describe('infinite-scroll-content: custom html', () => {
it('should not allow for custom html by default', async () => {
@@ -10,7 +9,7 @@ describe('infinite-scroll-content: custom html', () => {
html: `<ion-infinite-scroll-content loading-text="<button class='custom-html'>Custom Text</button>"></ion-infinite-scroll-content>`,
});
const content = page.body.querySelector('.infinite-loading-text')!;
const content = page.body.querySelector('.infinite-loading-text');
expect(content.textContent).toContain('Custom Text');
expect(content.querySelector('button.custom-html')).toBe(null);
});
@@ -22,7 +21,7 @@ describe('infinite-scroll-content: custom html', () => {
html: `<ion-infinite-scroll-content loading-text="<button class='custom-html'>Custom Text</button>"></ion-infinite-scroll-content>`,
});
const content = page.body.querySelector('.infinite-loading-text')!;
const content = page.body.querySelector('.infinite-loading-text');
expect(content.textContent).toContain('Custom Text');
expect(content.querySelector('button.custom-html')).not.toBe(null);
});
@@ -34,7 +33,7 @@ describe('infinite-scroll-content: custom html', () => {
html: `<ion-infinite-scroll-content loading-text="<button class='custom-html'>Custom Text2</button>"></ion-infinite-scroll-content>`,
});
const content = page.body.querySelector('.infinite-loading-text')!;
const content = page.body.querySelector('.infinite-loading-text');
expect(content.textContent).toContain('Custom Text');
expect(content.querySelector('button.custom-html')).toBe(null);
});

View File

@@ -12,14 +12,6 @@ export class InfiniteScroll implements ComponentInterface {
private thrPx = 0;
private thrPc = 0;
private scrollEl?: HTMLElement;
/**
* didFire exists so that ionInfinite
* does not fire multiple times if
* users continue to scroll after
* scrolling into the infinite
* scroll threshold.
*/
private didFire = false;
private isBusy = false;
@@ -135,6 +127,8 @@ export class InfiniteScroll implements ComponentInterface {
this.ionInfinite.emit();
return 3;
}
} else {
this.didFire = false;
}
return 4;
@@ -196,13 +190,10 @@ export class InfiniteScroll implements ComponentInterface {
writeTask(() => {
scrollEl.scrollTop = newScrollTop;
this.isBusy = false;
this.didFire = false;
});
});
});
});
} else {
this.didFire = false;
}
}

View File

@@ -1,63 +0,0 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Infinite Scroll - Small DOM Update</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<style>
#list .item {
width: 100%;
border-bottom: 1px solid gray;
padding: 10px;
}
</style>
</head>
<body>
<ion-app>
<ion-content class="ion-padding" id="content">
<div id="list"></div>
<ion-infinite-scroll threshold="100px" id="infinite-scroll">
<ion-infinite-scroll-content loading-spinner="crescent" loading-text="Loading more data...">
</ion-infinite-scroll-content>
</ion-infinite-scroll>
</ion-content>
</ion-app>
<script>
const list = document.getElementById('list');
const infiniteScroll = document.getElementById('infinite-scroll');
infiniteScroll.addEventListener('ionInfinite', () => {
setTimeout(() => {
appendItems();
infiniteScroll.complete();
// Custom event consumed in the e2e tests
window.dispatchEvent(new CustomEvent('ionInfiniteComplete'));
}, 500);
});
function appendItems(count = 3) {
for (var i = 0; i < count; i++) {
const el = document.createElement('div');
el.classList.add('item');
el.textContent = `${1 + i}`;
list.appendChild(el);
}
}
appendItems(30);
</script>
</body>
</html>

View File

@@ -1,34 +0,0 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('infinite-scroll: appending small amounts to dom'), () => {
test('should load more after remaining in threshold', async ({ page }) => {
await page.goto('/src/components/infinite-scroll/test/small-dom-update', config);
const ionInfiniteComplete = await page.spyOnEvent('ionInfiniteComplete');
const content = page.locator('ion-content');
const items = page.locator('#list .item');
expect(await items.count()).toBe(30);
await content.evaluate((el: HTMLIonContentElement) => el.scrollToBottom(0));
await ionInfiniteComplete.next();
/**
* Even after appending we'll still be within
* the infinite scroll's threshold
*/
expect(await items.count()).toBe(33);
await content.evaluate((el: HTMLIonContentElement) => el.scrollToBottom(0));
await ionInfiniteComplete.next();
/**
* Scrolling down again without leaving
* the threshold should still trigger
* infinite scroll again.
*/
expect(await items.count()).toBe(36);
});
});
});

View File

@@ -66,15 +66,20 @@
:host(.input-fill-outline.input-label-placement-stacked) .label-text-wrapper,
:host(.input-fill-outline.input-label-placement-floating) .label-text-wrapper {
@include transform(translateY(-50%), scale(1));
@include transform-origin(start, top);
position: absolute;
top: 50%;
/**
* Label text should not extend
* beyond the bounds of the input.
*/
max-width: calc(100% - var(--padding-start) - var(--padding-end));
transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1), transform 150ms cubic-bezier(0.4, 0, 0.2, 1),
top 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
/**
@@ -89,9 +94,12 @@
/**
* This makes the label sit above the input.
*/
:host(.label-floating.input-fill-outline) .label-text-wrapper {
:host(.has-focus.input-fill-outline.input-label-placement-floating) .label-text-wrapper,
:host(.has-value.input-fill-outline.input-label-placement-floating) .label-text-wrapper,
:host(.input-fill-outline.input-label-placement-stacked) .label-text-wrapper {
@include transform(translateY(-32%), scale(#{$form-control-label-stacked-scale}));
@include margin(0);
top: 0;
/**
* Label text should not extend
@@ -214,6 +222,8 @@
* the floating/stacked label. We simulate this "cut out"
* by removing the top border from the notch fragment.
*/
:host(.label-floating.input-fill-outline) .input-outline-notch {
:host(.has-focus.input-fill-outline.input-label-placement-floating) .input-outline-notch,
:host(.has-value.input-fill-outline.input-label-placement-floating) .input-outline-notch,
:host(.input-fill-outline.input-label-placement-stacked) .input-outline-notch {
border-top: none;
}

View File

@@ -67,7 +67,9 @@
// Input Label
// ----------------------------------------------------------------
:host(.label-floating.input-fill-solid.input-label-placement-floating) .label-text-wrapper {
:host(.input-fill-solid.input-label-placement-stacked) .label-text-wrapper,
:host(.has-focus.input-fill-solid.input-label-placement-floating) .label-text-wrapper,
:host(.has-value.input-fill-solid.input-label-placement-floating) .label-text-wrapper {
/**
* Label text should not extend
* beyond the bounds of the input.

View File

@@ -322,9 +322,6 @@
flex-grow: 1;
// ensure start/end slot content is vertically centered
align-items: center;
width: 100%;
}
@@ -644,7 +641,9 @@
/**
* This makes the label sit above the input.
*/
:host(.label-floating) .label-text-wrapper {
:host(.input-label-placement-stacked) .label-text-wrapper,
:host(.has-focus.input-label-placement-floating) .label-text-wrapper,
:host(.has-value.input-label-placement-floating) .label-text-wrapper {
@include transform(translateY(50%), scale(#{$form-control-label-stacked-scale}));
/**
@@ -653,14 +652,3 @@
*/
max-width: calc(100% / #{$form-control-label-stacked-scale});
}
// Start/End Slots
// ----------------------------------------------------------------
::slotted([slot="start"]) {
margin-inline-end: $form-control-label-margin;
}
::slotted([slot="end"]) {
margin-inline-start: $form-control-label-margin;
}

View File

@@ -26,8 +26,6 @@ import { getCounterText } from './input.utils';
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
*
* @slot label - The label text to associate with the input. Use the `labelPlacement` property to control where the label is placed relative to the input. Use this if you need to render a label with custom HTML. (EXPERIMENTAL)
* @slot start - Content to display at the leading edge of the input. (EXPERIMENTAL)
* @slot end - Content to display at the trailing edge of the input. (EXPERIMENTAL)
*/
@Component({
tag: 'ion-input',
@@ -97,9 +95,7 @@ export class Input implements ComponentInterface {
@Prop() autocorrect: 'on' | 'off' = 'off';
/**
* Sets the [`autofocus` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) on the native input element.
*
* This may not be sufficient for the element to be focused on page load. See [managing focus](/docs/developing/managing-focus) for more information.
* This Boolean attribute lets you specify that a form control should have input focus when the page loads.
*/
@Prop() autofocus = false;
@@ -373,7 +369,7 @@ export class Input implements ComponentInterface {
const { el } = this;
this.legacyFormController = createLegacyFormController(el);
this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this));
this.slotMutationController = createSlotMutationController(el, 'label', () => forceUpdate(this));
this.notchController = createNotchController(
el,
() => this.notchSpacerEl,
@@ -428,8 +424,6 @@ export class Input implements ComponentInterface {
*
* Developers who wish to focus an input when an overlay is presented
* should call `setFocus` after `didPresent` has resolved.
*
* See [managing focus](/docs/developing/managing-focus) for more information.
*/
@Method()
async setFocus() {
@@ -498,8 +492,6 @@ export class Input implements ComponentInterface {
'has-value': this.hasValue(),
'has-focus': this.hasFocus,
'interactive-disabled': this.disabled,
// TODO(FW-2764): remove this
legacy: !!this.legacy,
});
}
}
@@ -703,42 +695,18 @@ export class Input implements ComponentInterface {
}
private renderInput() {
const { disabled, fill, readonly, shape, inputId, labelPlacement, el, hasFocus } = this;
const { disabled, fill, readonly, shape, inputId, labelPlacement } = this;
const mode = getIonMode(this);
const value = this.getValue();
const inItem = hostContext('ion-item', this.el);
const shouldRenderHighlight = mode === 'md' && fill !== 'outline' && !inItem;
const hasValue = this.hasValue();
const hasStartEndSlots = el.querySelector('[slot="start"], [slot="end"]') !== null;
/**
* If the label is stacked, it should always sit above the input.
* For floating labels, the label should move above the input if
* the input has a value, is focused, or has anything in either
* the start or end slot.
*
* If there is content in the start slot, the label would overlap
* it if not forced to float. This is also applied to the end slot
* because with the default or solid fills, the input is not
* vertically centered in the container, but the label is. This
* causes the slots and label to appear vertically offset from each
* other when the label isn't floating above the input. This doesn't
* apply to the outline fill, but this was not accounted for to keep
* things consistent.
*
* TODO(FW-5592): Remove hasStartEndSlots condition
*/
const labelShouldFloat =
labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || hasStartEndSlots));
return (
<Host
class={createColorClasses(this.color, {
[mode]: true,
'has-value': hasValue,
'has-focus': hasFocus,
'label-floating': labelShouldFloat,
'has-value': this.hasValue(),
'has-focus': this.hasFocus,
[`input-fill-${fill}`]: fill !== undefined,
[`input-shape-${shape}`]: shape !== undefined,
[`input-label-placement-${labelPlacement}`]: true,
@@ -747,16 +715,9 @@ export class Input implements ComponentInterface {
'input-disabled': disabled,
})}
>
{/**
* htmlFor is needed so that clicking the label always focuses
* the input. Otherwise, if the start slot has something
* interactable, clicking the label would focus that instead
* since it comes before the input in the DOM.
*/}
<label class="input-wrapper" htmlFor={inputId}>
<label class="input-wrapper">
{this.renderLabelContainer()}
<div class="native-wrapper">
<slot name="start"></slot>
<input
class="native-input"
ref={(input) => (this.nativeInput = input)}
@@ -811,7 +772,6 @@ export class Input implements ComponentInterface {
<ion-icon aria-hidden="true" icon={mode === 'ios' ? closeCircle : closeSharp}></ion-icon>
</button>
)}
<slot name="end"></slot>
</div>
{shouldRenderHighlight && <div class="input-highlight"></div>}
</label>

View File

@@ -21,13 +21,7 @@
<ion-input label="Email" label-placement="stacked" value="hi@ionic.io"></ion-input><br />
<ion-input label="Email" label-placement="floating"></ion-input> <br />
<ion-input label="Email" label-placement="floating" fill="outline" value="hi@ionic.io"></ion-input> <br />
<ion-input label="Email" label-placement="floating" fill="solid" value="hi@ionic.io"></ion-input><br />
<ion-input label="Email" fill="solid" value="hi@ionic.io">
<ion-icon slot="start" name="lock-closed" aria-hidden="true"></ion-icon>
<ion-button slot="end" aria-label="button">
<ion-icon slot="icon-only" name="lock-closed" aria-hidden="true"></ion-icon>
</ion-button>
</ion-input>
<ion-input label="Email" label-placement="floating" fill="solid" value="hi@ionic.io"></ion-input>
</main>
</body>
</html>

View File

@@ -1,5 +1,4 @@
import { newSpecPage } from '@stencil/core/testing';
import { Input } from '../input';
describe('input: rendering', () => {
@@ -9,7 +8,7 @@ describe('input: rendering', () => {
html: '<ion-input title="my title" tabindex="-1" data-form-type="password"></ion-input>',
});
const nativeEl = page.body.querySelector('ion-input input')!;
const nativeEl = page.body.querySelector('ion-input input');
expect(nativeEl.getAttribute('title')).toBe('my title');
expect(nativeEl.getAttribute('tabindex')).toBe('-1');
expect(nativeEl.getAttribute('data-form-type')).toBe('password');
@@ -64,9 +63,9 @@ describe('input: label rendering', () => {
`,
});
const input = page.body.querySelector('ion-input')!;
const input = page.body.querySelector('ion-input');
const labelText = input.querySelector('.label-text-wrapper')!;
const labelText = input.querySelector('.label-text-wrapper');
expect(labelText.textContent).toBe('Label Prop Text');
});
@@ -78,9 +77,9 @@ describe('input: label rendering', () => {
`,
});
const input = page.body.querySelector('ion-input')!;
const input = page.body.querySelector('ion-input');
const labelText = input.querySelector('.label-text-wrapper')!;
const labelText = input.querySelector('.label-text-wrapper');
expect(labelText.textContent).toBe('Label Slot Text');
});
@@ -92,9 +91,9 @@ describe('input: label rendering', () => {
`,
});
const input = page.body.querySelector('ion-input')!;
const input = page.body.querySelector('ion-input');
const labelText = input.querySelector('.label-text-wrapper')!;
const labelText = input.querySelector('.label-text-wrapper');
expect(labelText.textContent).toBe('Label Prop Text');
});

View File

@@ -1,7 +1,6 @@
import { newSpecPage } from '@stencil/core/testing';
import { Item } from '../../../item/item';
import { Input } from '../../input';
import { Item } from '../../../item/item';
it('should render as modern when label is set asynchronously', async () => {
const page = await newSpecPage({
@@ -13,7 +12,7 @@ it('should render as modern when label is set asynchronously', async () => {
`,
});
const input = page.body.querySelector('ion-input')!;
const input = page.body.querySelector('ion-input');
// Template should be modern
expect(input.classList.contains('legacy-input')).toBe(false);

View File

@@ -231,4 +231,28 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
await expect(input).toHaveScreenshot(screenshot(`input-label-layering`));
});
});
test.describe(title('input: floating label custom font size'), () => {
test('large label should be centered when field is not focused', async ({ page }, testInfo) => {
testInfo.annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/27194',
});
await page.setContent(
`
<ion-input
style="font-size: 30px"
fill="outline"
label="Large floating label"
label-placement="floating"
></ion-input>
`,
config
);
const input = page.locator('ion-input');
await expect(input).toHaveScreenshot(screenshot('input-large-floating-label'));
});
});
});

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -29,7 +29,7 @@
<h2>Inactive</h2>
<ion-item>
<ion-label position="floating">Label</ion-label>
<ion-input legacy="true" placeholder="Placeholder Text"></ion-input>
<ion-input placeholder="Placeholder Text"></ion-input>
</ion-item>
</div>
@@ -37,7 +37,7 @@
<h2>Focused</h2>
<ion-item class="item-has-focus">
<ion-label position="floating">Label</ion-label>
<ion-input legacy="true" placeholder="Placeholder Text"></ion-input>
<ion-input placeholder="Placeholder Text"></ion-input>
</ion-item>
</div>
@@ -45,7 +45,7 @@
<h2>Activated</h2>
<ion-item>
<ion-label position="floating">Label</ion-label>
<ion-input legacy="true" placeholder="Placeholder Text" value="Input Text"></ion-input>
<ion-input placeholder="Placeholder Text" value="Input Text"></ion-input>
</ion-item>
</div>
@@ -53,7 +53,7 @@
<h2>Hover</h2>
<ion-item class="item-hovered">
<ion-label position="floating">Label</ion-label>
<ion-input legacy="true"></ion-input>
<ion-input></ion-input>
</ion-item>
</div>
@@ -61,7 +61,7 @@
<h2>Disabled</h2>
<ion-item>
<ion-label position="floating">Label</ion-label>
<ion-input legacy="true" disabled></ion-input>
<ion-input disabled></ion-input>
</ion-item>
</div>
@@ -69,7 +69,7 @@
<h2>Toggle Placeholder</h2>
<ion-item>
<ion-label position="floating">Label</ion-label>
<ion-input legacy="true" id="floatingToggle" type="password"></ion-input>
<ion-input id="floatingToggle" type="password"></ion-input>
<ion-button
fill="clear"
slot="end"
@@ -89,7 +89,7 @@
<h2>Inactive</h2>
<ion-item>
<ion-label position="stacked">Label</ion-label>
<ion-input legacy="true"></ion-input>
<ion-input></ion-input>
</ion-item>
</div>
@@ -97,7 +97,7 @@
<h2>Focused</h2>
<ion-item class="item-has-focus">
<ion-label position="stacked">Label</ion-label>
<ion-input legacy="true" placeholder="Placeholder Text"></ion-input>
<ion-input placeholder="Placeholder Text"></ion-input>
</ion-item>
</div>
@@ -105,7 +105,7 @@
<h2>Activated</h2>
<ion-item>
<ion-label position="stacked">Label</ion-label>
<ion-input legacy="true" placeholder="Placeholder Text" value="Input Text"></ion-input>
<ion-input placeholder="Placeholder Text" value="Input Text"></ion-input>
</ion-item>
</div>
@@ -113,7 +113,7 @@
<h2>Hover</h2>
<ion-item class="item-hovered">
<ion-label position="stacked">Label</ion-label>
<ion-input legacy="true"></ion-input>
<ion-input></ion-input>
</ion-item>
</div>
@@ -121,7 +121,7 @@
<h2>Disabled</h2>
<ion-item>
<ion-label position="stacked">Label</ion-label>
<ion-input legacy="true" disabled></ion-input>
<ion-input disabled></ion-input>
</ion-item>
</div>
@@ -129,7 +129,7 @@
<h2>Toggle Placeholder</h2>
<ion-item>
<ion-label position="stacked">Label</ion-label>
<ion-input legacy="true" id="stackedToggle" type="password"></ion-input>
<ion-input id="stackedToggle" type="password"></ion-input>
<ion-button
fill="clear"
slot="end"

View File

@@ -49,125 +49,53 @@
</ion-header>
<ion-content id="content" class="ion-padding">
<h1>Label Slot</h1>
<div class="grid">
<div class="grid-item">
<h2>No Fill / Start Label</h2>
<h2>No Fill / Start</h2>
<ion-input label-placement="start" value="hi@ionic.io">
<div slot="label">Email <span class="required">*</span></div>
</ion-input>
</div>
<div class="grid-item">
<h2>Solid / Start Label</h2>
<h2>Solid / Start</h2>
<ion-input label-placement="start" fill="solid" value="hi@ionic.io">
<div slot="label">Email <span class="required">*</span></div>
</ion-input>
</div>
<div class="grid-item">
<h2>Outline / Start Label</h2>
<h2>Outline / Start</h2>
<ion-input label-placement="start" fill="outline" value="hi@ionic.io">
<div slot="label">Email <span class="required">*</span></div>
</ion-input>
</div>
<div class="grid-item">
<h2>No Fill / Floating Label</h2>
<h2>No Fill / Floating</h2>
<ion-input label-placement="floating" value="hi@ionic.io">
<div slot="label">Email <span class="required">*</span></div>
</ion-input>
</div>
<div class="grid-item">
<h2>Solid / Floating Label</h2>
<h2>Solid / Floating</h2>
<ion-input label-placement="floating" fill="solid" value="hi@ionic.io">
<div slot="label">Email <span class="required">*</span></div>
</ion-input>
</div>
<div class="grid-item">
<h2>Outline / Floating Label</h2>
<h2>Outline / Floating</h2>
<ion-input label-placement="floating" fill="outline" value="hi@ionic.io">
<div slot="label">Email <span class="required">*</span></div>
</ion-input>
</div>
</div>
<h1>Start/End Slots</h1>
<div class="grid">
<div class="grid-item">
<h2>No Fill / Start Label</h2>
<ion-input label-placement="start" value="hi@ionic.io" label="Email">
<ion-icon slot="start" name="lock-closed" aria-hidden="true"></ion-icon>
<ion-button fill="clear" slot="end" aria-label="Show/hide password">
<ion-icon slot="icon-only" name="eye" aria-hidden="true"></ion-icon>
</ion-button>
</ion-input>
</div>
<div class="grid-item">
<h2>Solid / Start Label</h2>
<ion-input label-placement="start" fill="solid" value="hi@ionic.io" label="Email">
<ion-icon slot="start" name="lock-closed" aria-hidden="true"></ion-icon>
<ion-button fill="clear" slot="end" aria-label="Show/hide password">
<ion-icon slot="icon-only" name="eye" aria-hidden="true"></ion-icon>
</ion-button>
</ion-input>
</div>
<div class="grid-item">
<h2>Outline / Start Label</h2>
<ion-input label-placement="start" fill="outline" value="hi@ionic.io" label="Email">
<ion-icon slot="start" name="lock-closed" aria-hidden="true"></ion-icon>
<ion-button fill="clear" slot="end" aria-label="Show/hide password">
<ion-icon slot="icon-only" name="eye" aria-hidden="true"></ion-icon>
</ion-button>
</ion-input>
</div>
<div class="grid-item">
<h2>No Fill / Floating Label</h2>
<ion-input label-placement="floating" value="hi@ionic.io" label="Email">
<ion-icon slot="start" name="lock-closed" aria-hidden="true"></ion-icon>
<ion-button fill="clear" slot="end" aria-label="Show/hide password">
<ion-icon slot="icon-only" name="eye" aria-hidden="true"></ion-icon>
</ion-button>
</ion-input>
</div>
<div class="grid-item">
<h2>Solid / Floating Label</h2>
<ion-input label-placement="floating" fill="solid" value="hi@ionic.io" label="Email">
<ion-icon slot="start" name="lock-closed" aria-hidden="true"></ion-icon>
<ion-button fill="clear" slot="end" aria-label="Show/hide password">
<ion-icon slot="icon-only" name="eye" aria-hidden="true"></ion-icon>
</ion-button>
</ion-input>
</div>
<div class="grid-item">
<h2>Outline / Floating Label</h2>
<ion-input label-placement="floating" fill="outline" value="hi@ionic.io" label="Email">
<ion-icon slot="start" name="lock-closed" aria-hidden="true"></ion-icon>
<ion-button fill="clear" slot="end" aria-label="Show/hide password">
<ion-icon slot="icon-only" name="eye" aria-hidden="true"></ion-icon>
</ion-button>
</ion-input>
</div>
</div>
<h1>Async Slot Content</h1>
<div class="grid">
<div class="grid-item">
<h2>Outline / Async Label</h2>
<h2>Outline / Floating / Async</h2>
<ion-input id="solid-async" label-placement="floating" fill="outline" value="hi@ionic.io"></ion-input>
</div>
<div class="grid-item">
<h2>Outline / Async Decorations</h2>
<ion-input id="async-decorations" label-placement="floating" fill="outline" label="Email"></ion-input>
</div>
</div>
<ion-button onclick="addSlot()">Add Slotted Content</ion-button>
@@ -178,65 +106,29 @@
<script>
const solidAsync = document.querySelector('#solid-async');
const asyncDecos = document.querySelector('#async-decorations');
const getSlottedLabel = () => {
const getSlottedContent = () => {
return solidAsync.querySelector('[slot="label"]');
};
const getSlottedStartDeco = () => {
return asyncDecos.querySelector('[slot="start"]');
};
const getSlottedEndDeco = () => {
return asyncDecos.querySelector('[slot="end"]');
};
const addSlot = () => {
if (getSlottedLabel() === null) {
if (getSlottedContent() === null) {
const labelEl = document.createElement('div');
labelEl.slot = 'label';
labelEl.innerHTML = 'Email <span class="required">*</span>';
solidAsync.appendChild(labelEl);
}
if (getSlottedStartDeco() === null) {
const startEl = document.createElement('div');
startEl.slot = 'start';
startEl.innerHTML = 'Start';
asyncDecos.insertAdjacentElement('afterbegin', startEl);
}
if (getSlottedEndDeco() === null) {
const endEl = document.createElement('div');
endEl.slot = 'end';
endEl.innerHTML = 'End';
asyncDecos.insertAdjacentElement('beforeend', endEl);
}
};
const removeSlot = () => {
const slottedLabel = getSlottedLabel();
if (slottedLabel !== null) {
slottedLabel.remove();
}
const slottedStartDeco = getSlottedStartDeco();
if (slottedStartDeco !== null) {
slottedStartDeco.remove();
}
const slottedEndDeco = getSlottedEndDeco();
if (slottedEndDeco !== null) {
slottedEndDeco.remove();
if (getSlottedContent() !== null) {
solidAsync.querySelector('[slot="label"]').remove();
}
};
const updateSlot = () => {
const slottedContent = getSlottedLabel();
const slottedContent = getSlottedContent();
if (slottedContent !== null) {
slottedContent.textContent = 'This is my really really really long text';

View File

@@ -1,68 +0,0 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
configs().forEach(({ title, screenshot, config }) => {
test.describe(title('input: start and end slots (visual checks)'), () => {
test('should not have visual regressions with a start-positioned label', async ({ page }) => {
await page.setContent(
`
<ion-input label-placement="start" fill="solid" value="100" label="Weight" clear-input="true">
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
<ion-label slot="end">lbs</ion-label>
</ion-input>
`,
config
);
const input = page.locator('ion-input');
await expect(input).toHaveScreenshot(screenshot(`input-slots-label-start`));
});
test('should not have visual regressions with a floating label', async ({ page }) => {
await page.setContent(
`
<ion-input label-placement="floating" fill="solid" value="100" label="Weight" clear-input="true">
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
<ion-label slot="end">lbs</ion-label>
</ion-input>
`,
config
);
const input = page.locator('ion-input');
await expect(input).toHaveScreenshot(screenshot(`input-slots-label-floating`));
});
});
});
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('input: start and end slots (functionality checks)'), () => {
test('should raise floating label when there is content in the start slot', async ({ page }) => {
await page.setContent(
`
<ion-input label-placement="floating" fill="solid" label="Weight">
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
</ion-input>
`,
config
);
const input = page.locator('ion-input');
await expect(input).toHaveClass(/label-floating/);
});
test('should raise floating label when there is content in the end slot', async ({ page }) => {
await page.setContent(
`
<ion-input label-placement="floating" fill="solid" label="Weight">
<ion-icon slot="end" name="barbell" aria-hidden="true"></ion-icon>
</ion-input>
`,
config
);
const input = page.locator('ion-input');
await expect(input).toHaveClass(/label-floating/);
});
});
});

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

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