Compare commits
75 Commits
v7.3.4
...
ld/datetim
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
726e4aeae7 | ||
|
|
eee2115fd2 | ||
|
|
d0057352fe | ||
|
|
c70432e693 | ||
|
|
a1690441e5 | ||
|
|
e6031fbef0 | ||
|
|
d5f0c776df | ||
|
|
00767a02e4 | ||
|
|
3259da0de1 | ||
|
|
470c119a05 | ||
|
|
b297529afc | ||
|
|
7375dd6aba | ||
|
|
1167a9325f | ||
|
|
dc75392e9d | ||
|
|
865bd2a004 | ||
|
|
2429caa168 | ||
|
|
a8517f4fcf | ||
|
|
a04a11be35 | ||
|
|
f5df28e2c7 | ||
|
|
ac2c8e6c22 | ||
|
|
70e9177e2c | ||
|
|
51b7ceb5be | ||
|
|
597bec7534 | ||
|
|
eb41b556b5 | ||
|
|
355d95d4d8 | ||
|
|
078964447a | ||
|
|
45bcd4c22b | ||
|
|
71a7af0f52 | ||
|
|
8a9db8c949 | ||
|
|
fe10e7280f | ||
|
|
82a5b310da | ||
|
|
b02f1aff2b | ||
|
|
de1a4c50bb | ||
|
|
5e016a6616 | ||
|
|
0edcb2cd85 | ||
|
|
7b197a3226 | ||
|
|
2e626a909f | ||
|
|
d82414d43f | ||
|
|
5b7e422dc0 | ||
|
|
0104d89927 | ||
|
|
3f06da4cfc | ||
|
|
5ff32b7786 | ||
|
|
b5c736f5ac | ||
|
|
81714d45bd | ||
|
|
4e0b522728 | ||
|
|
94514501a6 | ||
|
|
574d762594 | ||
|
|
45bbea6a34 | ||
|
|
9050a9fbf5 | ||
|
|
a5f14e3933 | ||
|
|
67c97225d0 | ||
|
|
cd1102b985 | ||
|
|
6304645a81 | ||
|
|
c28b66a99a | ||
|
|
1d2b867f22 | ||
|
|
8cb878669e | ||
|
|
53203dbfd5 | ||
|
|
1f9c70b4bd | ||
|
|
fd5a1ac708 | ||
|
|
2e7d89c4dd | ||
|
|
d547944e91 | ||
|
|
f82709595d | ||
|
|
af29857c5f | ||
|
|
bc64f72109 | ||
|
|
474308618d | ||
|
|
19f3bb23fd | ||
|
|
cd8d5091a1 | ||
|
|
79b005da70 | ||
|
|
e6c7bb60e7 | ||
|
|
cbafa6b40d | ||
|
|
e6c09291f5 | ||
|
|
21b0731cf2 | ||
|
|
b655067867 | ||
|
|
32244fbdd1 | ||
|
|
ae9f1ab43e |
@@ -34,7 +34,11 @@ runs:
|
||||
run: |
|
||||
git config user.name ionitron
|
||||
git config user.email hi@ionicframework.com
|
||||
git add src/\*.png --force
|
||||
|
||||
# This adds an empty entry for new
|
||||
# screenshot files so we can track them with
|
||||
# git diff
|
||||
git add src/\*.png --force -N
|
||||
|
||||
if git diff --exit-code; then
|
||||
echo -e "\033[1;31m⚠️ Error: No new screenshots generated ⚠️\033[0m"
|
||||
@@ -42,6 +46,9 @@ runs:
|
||||
echo -e "\033[1;31mMake sure you have pushed any code changes that would result in visual diffs.\033[0m"
|
||||
exit 1
|
||||
else
|
||||
# This actually adds the contents
|
||||
# of the screenshots (including new ones)
|
||||
git add src/\*.png --force
|
||||
git commit -m "chore(): add updated snapshots"
|
||||
git push
|
||||
fi
|
||||
|
||||
11
.github/workflows/stencil-nightly.yml
vendored
@@ -8,7 +8,12 @@ on:
|
||||
# at 6:00 UTC (6:00 am UTC)
|
||||
- cron: '00 06 * * 1-5'
|
||||
workflow_dispatch:
|
||||
# allows for manual invocations in the GitHub UI
|
||||
inputs:
|
||||
npm_release_tag:
|
||||
required: true
|
||||
type: string
|
||||
description: What version should be pulled from NPM?
|
||||
default: nightly
|
||||
|
||||
# When pushing a new commit we should
|
||||
# cancel the previous test run to not
|
||||
@@ -24,7 +29,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/workflows/actions/build-core-stencil-prerelease
|
||||
with:
|
||||
stencil-version: nightly
|
||||
stencil-version: ${{ inputs.npm_release_tag || 'nightly' }}
|
||||
|
||||
test-core-clean-build:
|
||||
needs: [build-core-with-stencil-nightly]
|
||||
@@ -47,7 +52,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/workflows/actions/test-core-spec
|
||||
with:
|
||||
stencil-version: nightly
|
||||
stencil-version: ${{ inputs.npm_release_tag || 'nightly' }}
|
||||
|
||||
test-core-screenshot:
|
||||
strategy:
|
||||
|
||||
60
CHANGELOG.md
@@ -3,6 +3,66 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [7.4.3](https://github.com/ionic-team/ionic-framework/compare/v7.4.2...v7.4.3) (2023-10-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **fab-button:** position is correct with custom sizes ([#28195](https://github.com/ionic-team/ionic-framework/issues/28195)) ([eb41b55](https://github.com/ionic-team/ionic-framework/commit/eb41b556b57c97139b9c36dc3e3be3711d8afaca)), closes [#22564](https://github.com/ionic-team/ionic-framework/issues/22564)
|
||||
* **range:** knob positions are correct on initial render with custom elements build ([#28257](https://github.com/ionic-team/ionic-framework/issues/28257)) ([ac2c8e6](https://github.com/ionic-team/ionic-framework/commit/ac2c8e6c22da4d0d8224def24ddef56ee9d26246)), closes [#25444](https://github.com/ionic-team/ionic-framework/issues/25444)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [7.4.2](https://github.com/ionic-team/ionic-framework/compare/v7.4.1...v7.4.2) (2023-09-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **react:** Nav unmounts component while invoking popTo or popToRoot ([#27821](https://github.com/ionic-team/ionic-framework/issues/27821)) ([0edcb2c](https://github.com/ionic-team/ionic-framework/commit/0edcb2cd85133ae8c304c53c37ca829e5fbad447)), closes [#27798](https://github.com/ionic-team/ionic-framework/issues/27798)
|
||||
* **title:** large title uses custom font on transition ([#28231](https://github.com/ionic-team/ionic-framework/issues/28231)) ([71a7af0](https://github.com/ionic-team/ionic-framework/commit/71a7af0f52fe62937b1dea1ca2739e78801a2a6d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [7.4.1](https://github.com/ionic-team/ionic-framework/compare/v7.4.0...v7.4.1) (2023-09-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **overlays:** correctly re-add root to accessibility tree ([#28183](https://github.com/ionic-team/ionic-framework/issues/28183)) ([81714d4](https://github.com/ionic-team/ionic-framework/commit/81714d45bd97f0ba91729959b60a0dc1d1d06533)), closes [#28180](https://github.com/ionic-team/ionic-framework/issues/28180)
|
||||
* **radio,toggle,checkbox,select:** padded space is clickable in items ([#28136](https://github.com/ionic-team/ionic-framework/issues/28136)) ([5b7e422](https://github.com/ionic-team/ionic-framework/commit/5b7e422dc0bfd4d58fb31f62715af47e62dabb57)), closes [#27169](https://github.com/ionic-team/ionic-framework/issues/27169)
|
||||
* **range:** knob is not cut off in item with modern syntax ([#28199](https://github.com/ionic-team/ionic-framework/issues/28199)) ([0104d89](https://github.com/ionic-team/ionic-framework/commit/0104d899270d73e16f2850a5fd7d2ba25a9e7ef0)), closes [#27199](https://github.com/ionic-team/ionic-framework/issues/27199)
|
||||
* **scroll-assist:** improve input scroll accuracy with native resizing ([#28169](https://github.com/ionic-team/ionic-framework/issues/28169)) ([b5c736f](https://github.com/ionic-team/ionic-framework/commit/b5c736f5ac829efebedf3256ddf77ab3daa7a5f6)), closes [#22940](https://github.com/ionic-team/ionic-framework/issues/22940)
|
||||
* **scroll-assist:** re-run when keyboard changes ([#28174](https://github.com/ionic-team/ionic-framework/issues/28174)) ([3f06da4](https://github.com/ionic-team/ionic-framework/commit/3f06da4cfc0d59c658e17e09ccb1ea28a29339f9)), closes [#22940](https://github.com/ionic-team/ionic-framework/issues/22940)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [7.4.0](https://github.com/ionic-team/ionic-framework/compare/v7.3.4...v7.4.0) (2023-09-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datetime:** scroll to newly selected date when value changes ([#27806](https://github.com/ionic-team/ionic-framework/issues/27806)) ([32244fb](https://github.com/ionic-team/ionic-framework/commit/32244fbdd1931e59a9e3cedd2b143c8ee7d01459)), closes [#26391](https://github.com/ionic-team/ionic-framework/issues/26391)
|
||||
* **many:** add correct scale to stacked labels ([#28163](https://github.com/ionic-team/ionic-framework/issues/28163)) ([8cb8786](https://github.com/ionic-team/ionic-framework/commit/8cb878669e53bad25bbe2787826b6d02d292848a))
|
||||
* **range:** add correct margin in item ([#28161](https://github.com/ionic-team/ionic-framework/issues/28161)) ([1d2b867](https://github.com/ionic-team/ionic-framework/commit/1d2b867f2207d366e355265b081bc9aabe31ce7e))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **checkbox, radio, toggle, range:** stacked labels for form controls ([#28075](https://github.com/ionic-team/ionic-framework/issues/28075)) ([e6c7bb6](https://github.com/ionic-team/ionic-framework/commit/e6c7bb60e7e61c965f45e2bf3e3bd16f5125ad56))
|
||||
* **datetime:** add disabled part ([#28134](https://github.com/ionic-team/ionic-framework/issues/28134)) ([cd8d509](https://github.com/ionic-team/ionic-framework/commit/cd8d5091a133804ac97d0394354dcf7cd73d9355))
|
||||
* **datetime:** add parts for calendar day, active, and today ([#27641](https://github.com/ionic-team/ionic-framework/issues/27641)) ([79b005d](https://github.com/ionic-team/ionic-framework/commit/79b005da704c2ce481e1e3bc4d24cdba06a36d04)), closes [#25340](https://github.com/ionic-team/ionic-framework/issues/25340)
|
||||
* export TransitionOptions interface and getIonPageElement ([#28140](https://github.com/ionic-team/ionic-framework/issues/28140)) ([19f3bb2](https://github.com/ionic-team/ionic-framework/commit/19f3bb23fd5587848fc41a744ca46ef5985c04d2)), closes [#28137](https://github.com/ionic-team/ionic-framework/issues/28137)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [7.3.4](https://github.com/ionic-team/ionic-framework/compare/v7.3.3...v7.3.4) (2023-09-13)
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,65 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [7.4.3](https://github.com/ionic-team/ionic-framework/compare/v7.4.2...v7.4.3) (2023-10-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **fab-button:** position is correct with custom sizes ([#28195](https://github.com/ionic-team/ionic-framework/issues/28195)) ([eb41b55](https://github.com/ionic-team/ionic-framework/commit/eb41b556b57c97139b9c36dc3e3be3711d8afaca)), closes [#22564](https://github.com/ionic-team/ionic-framework/issues/22564)
|
||||
* **range:** knob positions are correct on initial render with custom elements build ([#28257](https://github.com/ionic-team/ionic-framework/issues/28257)) ([ac2c8e6](https://github.com/ionic-team/ionic-framework/commit/ac2c8e6c22da4d0d8224def24ddef56ee9d26246)), closes [#25444](https://github.com/ionic-team/ionic-framework/issues/25444)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [7.4.2](https://github.com/ionic-team/ionic-framework/compare/v7.4.1...v7.4.2) (2023-09-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **title:** large title uses custom font on transition ([#28231](https://github.com/ionic-team/ionic-framework/issues/28231)) ([71a7af0](https://github.com/ionic-team/ionic-framework/commit/71a7af0f52fe62937b1dea1ca2739e78801a2a6d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [7.4.1](https://github.com/ionic-team/ionic-framework/compare/v7.4.0...v7.4.1) (2023-09-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **overlays:** correctly re-add root to accessibility tree ([#28183](https://github.com/ionic-team/ionic-framework/issues/28183)) ([81714d4](https://github.com/ionic-team/ionic-framework/commit/81714d45bd97f0ba91729959b60a0dc1d1d06533)), closes [#28180](https://github.com/ionic-team/ionic-framework/issues/28180)
|
||||
* **radio,toggle,checkbox,select:** padded space is clickable in items ([#28136](https://github.com/ionic-team/ionic-framework/issues/28136)) ([5b7e422](https://github.com/ionic-team/ionic-framework/commit/5b7e422dc0bfd4d58fb31f62715af47e62dabb57)), closes [#27169](https://github.com/ionic-team/ionic-framework/issues/27169)
|
||||
* **range:** knob is not cut off in item with modern syntax ([#28199](https://github.com/ionic-team/ionic-framework/issues/28199)) ([0104d89](https://github.com/ionic-team/ionic-framework/commit/0104d899270d73e16f2850a5fd7d2ba25a9e7ef0)), closes [#27199](https://github.com/ionic-team/ionic-framework/issues/27199)
|
||||
* **scroll-assist:** improve input scroll accuracy with native resizing ([#28169](https://github.com/ionic-team/ionic-framework/issues/28169)) ([b5c736f](https://github.com/ionic-team/ionic-framework/commit/b5c736f5ac829efebedf3256ddf77ab3daa7a5f6)), closes [#22940](https://github.com/ionic-team/ionic-framework/issues/22940)
|
||||
* **scroll-assist:** re-run when keyboard changes ([#28174](https://github.com/ionic-team/ionic-framework/issues/28174)) ([3f06da4](https://github.com/ionic-team/ionic-framework/commit/3f06da4cfc0d59c658e17e09ccb1ea28a29339f9)), closes [#22940](https://github.com/ionic-team/ionic-framework/issues/22940)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [7.4.0](https://github.com/ionic-team/ionic-framework/compare/v7.3.4...v7.4.0) (2023-09-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datetime:** scroll to newly selected date when value changes ([#27806](https://github.com/ionic-team/ionic-framework/issues/27806)) ([32244fb](https://github.com/ionic-team/ionic-framework/commit/32244fbdd1931e59a9e3cedd2b143c8ee7d01459)), closes [#26391](https://github.com/ionic-team/ionic-framework/issues/26391)
|
||||
* **many:** add correct scale to stacked labels ([#28163](https://github.com/ionic-team/ionic-framework/issues/28163)) ([8cb8786](https://github.com/ionic-team/ionic-framework/commit/8cb878669e53bad25bbe2787826b6d02d292848a))
|
||||
* **range:** add correct margin in item ([#28161](https://github.com/ionic-team/ionic-framework/issues/28161)) ([1d2b867](https://github.com/ionic-team/ionic-framework/commit/1d2b867f2207d366e355265b081bc9aabe31ce7e))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **checkbox, radio, toggle, range:** stacked labels for form controls ([#28075](https://github.com/ionic-team/ionic-framework/issues/28075)) ([e6c7bb6](https://github.com/ionic-team/ionic-framework/commit/e6c7bb60e7e61c965f45e2bf3e3bd16f5125ad56))
|
||||
* **datetime:** add disabled part ([#28134](https://github.com/ionic-team/ionic-framework/issues/28134)) ([cd8d509](https://github.com/ionic-team/ionic-framework/commit/cd8d5091a133804ac97d0394354dcf7cd73d9355))
|
||||
* **datetime:** add parts for calendar day, active, and today ([#27641](https://github.com/ionic-team/ionic-framework/issues/27641)) ([79b005d](https://github.com/ionic-team/ionic-framework/commit/79b005da704c2ce481e1e3bc4d24cdba06a36d04)), closes [#25340](https://github.com/ionic-team/ionic-framework/issues/25340)
|
||||
* export TransitionOptions interface and getIonPageElement ([#28140](https://github.com/ionic-team/ionic-framework/issues/28140)) ([19f3bb2](https://github.com/ionic-team/ionic-framework/commit/19f3bb23fd5587848fc41a744ca46ef5985c04d2)), closes [#28137](https://github.com/ionic-team/ionic-framework/issues/28137)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [7.3.4](https://github.com/ionic-team/ionic-framework/compare/v7.3.3...v7.3.4) (2023-09-13)
|
||||
|
||||
|
||||
|
||||
15
core/api.txt
@@ -289,12 +289,13 @@ ion-card-title,prop,mode,"ios" | "md",undefined,false,false
|
||||
ion-card-title,css-prop,--color
|
||||
|
||||
ion-checkbox,shadow
|
||||
ion-checkbox,prop,alignment,"center" | "start",'center',false,false
|
||||
ion-checkbox,prop,checked,boolean,false,false,false
|
||||
ion-checkbox,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
|
||||
ion-checkbox,prop,disabled,boolean,false,false,false
|
||||
ion-checkbox,prop,indeterminate,boolean,false,false,false
|
||||
ion-checkbox,prop,justify,"end" | "space-between" | "start",'space-between',false,false
|
||||
ion-checkbox,prop,labelPlacement,"end" | "fixed" | "start",'start',false,false
|
||||
ion-checkbox,prop,labelPlacement,"end" | "fixed" | "stacked" | "start",'start',false,false
|
||||
ion-checkbox,prop,legacy,boolean | undefined,undefined,false,false
|
||||
ion-checkbox,prop,mode,"ios" | "md",undefined,false,false
|
||||
ion-checkbox,prop,name,string,this.inputId,false,false
|
||||
@@ -427,6 +428,10 @@ ion-datetime,css-prop,--background-rgb
|
||||
ion-datetime,css-prop,--title-color
|
||||
ion-datetime,css-prop,--wheel-fade-background-rgb
|
||||
ion-datetime,css-prop,--wheel-highlight-background
|
||||
ion-datetime,part,calendar-day
|
||||
ion-datetime,part,calendar-day active
|
||||
ion-datetime,part,calendar-day disabled
|
||||
ion-datetime,part,calendar-day today
|
||||
ion-datetime,part,month-year-button
|
||||
ion-datetime,part,time-button
|
||||
ion-datetime,part,time-button active
|
||||
@@ -1008,10 +1013,11 @@ ion-progress-bar,part,stream
|
||||
ion-progress-bar,part,track
|
||||
|
||||
ion-radio,shadow
|
||||
ion-radio,prop,alignment,"center" | "start",'center',false,false
|
||||
ion-radio,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
|
||||
ion-radio,prop,disabled,boolean,false,false,false
|
||||
ion-radio,prop,justify,"end" | "space-between" | "start",'space-between',false,false
|
||||
ion-radio,prop,labelPlacement,"end" | "fixed" | "start",'start',false,false
|
||||
ion-radio,prop,labelPlacement,"end" | "fixed" | "stacked" | "start",'start',false,false
|
||||
ion-radio,prop,legacy,boolean | undefined,undefined,false,false
|
||||
ion-radio,prop,mode,"ios" | "md",undefined,false,false
|
||||
ion-radio,prop,name,string,this.inputId,false,false
|
||||
@@ -1038,7 +1044,7 @@ ion-range,prop,debounce,number | undefined,undefined,false,false
|
||||
ion-range,prop,disabled,boolean,false,false,false
|
||||
ion-range,prop,dualKnobs,boolean,false,false,false
|
||||
ion-range,prop,label,string | undefined,undefined,false,false
|
||||
ion-range,prop,labelPlacement,"end" | "fixed" | "start",'start',false,false
|
||||
ion-range,prop,labelPlacement,"end" | "fixed" | "stacked" | "start",'start',false,false
|
||||
ion-range,prop,legacy,boolean | undefined,undefined,false,false
|
||||
ion-range,prop,max,number,100,false,false
|
||||
ion-range,prop,min,number,0,false,false
|
||||
@@ -1478,12 +1484,13 @@ ion-toast,part,icon
|
||||
ion-toast,part,message
|
||||
|
||||
ion-toggle,shadow
|
||||
ion-toggle,prop,alignment,"center" | "start",'center',false,false
|
||||
ion-toggle,prop,checked,boolean,false,false,false
|
||||
ion-toggle,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
|
||||
ion-toggle,prop,disabled,boolean,false,false,false
|
||||
ion-toggle,prop,enableOnOffLabels,boolean | undefined,config.get('toggleOnOffLabels'),false,false
|
||||
ion-toggle,prop,justify,"end" | "space-between" | "start",'space-between',false,false
|
||||
ion-toggle,prop,labelPlacement,"end" | "fixed" | "start",'start',false,false
|
||||
ion-toggle,prop,labelPlacement,"end" | "fixed" | "stacked" | "start",'start',false,false
|
||||
ion-toggle,prop,legacy,boolean | undefined,undefined,false,false
|
||||
ion-toggle,prop,mode,"ios" | "md",undefined,false,false
|
||||
ion-toggle,prop,name,string,this.inputId,false,false
|
||||
|
||||
110
core/package-lock.json
generated
@@ -1,33 +1,33 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "7.3.4",
|
||||
"version": "7.4.3",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/core",
|
||||
"version": "7.3.4",
|
||||
"version": "7.4.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "^4.2.1",
|
||||
"@stencil/core": "^4.4.1",
|
||||
"ionicons": "7.1.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "^4.7.3",
|
||||
"@capacitor/core": "^5.3.0",
|
||||
"@capacitor/core": "^5.4.2",
|
||||
"@capacitor/haptics": "^5.0.6",
|
||||
"@capacitor/keyboard": "^5.0.6",
|
||||
"@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.37.1",
|
||||
"@playwright/test": "^1.38.1",
|
||||
"@rollup/plugin-node-resolve": "^8.4.0",
|
||||
"@rollup/plugin-virtual": "^2.0.3",
|
||||
"@stencil/angular-output-target": "^0.8.2",
|
||||
"@stencil/react-output-target": "^0.5.3",
|
||||
"@stencil/sass": "^3.0.5",
|
||||
"@stencil/sass": "^3.0.6",
|
||||
"@stencil/vue-output-target": "^0.8.6",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^14.6.0",
|
||||
@@ -607,9 +607,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@capacitor/core": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-5.3.0.tgz",
|
||||
"integrity": "sha512-mvhh1yJtcUTZ0hUUriBKKpxq47hn75bjxH3tYPRgAFu1z3gowCg+OtG4Rce3W5gr5fSfCjQgOSL0Vp7k9hPUWw==",
|
||||
"version": "5.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-5.4.2.tgz",
|
||||
"integrity": "sha512-XbR1vldJFzBWHeoGPpgfNy3Zhjf0NxXdHEaGNANWVBg0ZWG2gwFr1dcRALUUQtbwrEEkCCNiLYg4YiQPRk7SEQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
@@ -1541,22 +1541,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.37.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.37.1.tgz",
|
||||
"integrity": "sha512-bq9zTli3vWJo8S3LwB91U0qDNQDpEXnw7knhxLM0nwDvexQAwx9tO8iKDZSqqneVq+URd/WIoz+BALMqUTgdSg==",
|
||||
"version": "1.38.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.1.tgz",
|
||||
"integrity": "sha512-NqRp8XMwj3AK+zKLbZShl0r/9wKgzqI/527bkptKXomtuo+dOjU9NdMASQ8DNC9z9zLOMbG53T4eihYr3XR+BQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"playwright-core": "1.37.1"
|
||||
"playwright": "1.38.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-node-resolve": {
|
||||
@@ -1634,9 +1630,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@stencil/core": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.2.1.tgz",
|
||||
"integrity": "sha512-alYwqVwxfD0n6HKRVJqJoTzQNnf44n/sddvjNu3JMEn3sfY/Ag7rpmwUntYjtJmRut+or+9gPPgIJviCuKi4yQ==",
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.4.1.tgz",
|
||||
"integrity": "sha512-SirGcrb5yKHCn2BwdM7HGVXuvCdmwiXlVczEj8jJxQIm42CAUQCUECxtZidTzp+oZBZnWLnoAvfanchJsgkQzA==",
|
||||
"bin": {
|
||||
"stencil": "bin/stencil"
|
||||
},
|
||||
@@ -1655,9 +1651,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@stencil/sass": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/sass/-/sass-3.0.5.tgz",
|
||||
"integrity": "sha512-9nyllMXOEvHywo6fP2iwXgnq32A+OOUE36Aq7iUjzwT3wdr04qsvupO1JNIyRvmvCDF15hOKXztrZH1/wDu2Zg==",
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/sass/-/sass-3.0.6.tgz",
|
||||
"integrity": "sha512-d+k8qCvDgzI/vIV7M5QavIS6b75X4yn+YQH80KA/L0oL/hKD8KeF3wMOd7P1nDhGLIGRNw5tBDbe0GeIsGVU7A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12.0.0",
|
||||
@@ -8193,10 +8189,28 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.38.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.1.tgz",
|
||||
"integrity": "sha512-oRMSJmZrOu1FP5iu3UrCx8JEFRIMxLDM0c/3o4bpzU5Tz97BypefWf7TuTNPWeCe279TPal5RtPPZ+9lW/Qkow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"playwright-core": "1.38.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.37.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.1.tgz",
|
||||
"integrity": "sha512-17EuQxlSIYCmEMwzMqusJ2ztDgJePjrbttaefgdsiqeLWidjYz9BxXaTaZWxH1J95SHGk6tjE+dwgWILJoUZfA==",
|
||||
"version": "1.38.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.1.tgz",
|
||||
"integrity": "sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
@@ -10788,9 +10802,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@capacitor/core": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-5.3.0.tgz",
|
||||
"integrity": "sha512-mvhh1yJtcUTZ0hUUriBKKpxq47hn75bjxH3tYPRgAFu1z3gowCg+OtG4Rce3W5gr5fSfCjQgOSL0Vp7k9hPUWw==",
|
||||
"version": "5.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-5.4.2.tgz",
|
||||
"integrity": "sha512-XbR1vldJFzBWHeoGPpgfNy3Zhjf0NxXdHEaGNANWVBg0ZWG2gwFr1dcRALUUQtbwrEEkCCNiLYg4YiQPRk7SEQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "^2.1.0"
|
||||
@@ -11455,14 +11469,12 @@
|
||||
}
|
||||
},
|
||||
"@playwright/test": {
|
||||
"version": "1.37.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.37.1.tgz",
|
||||
"integrity": "sha512-bq9zTli3vWJo8S3LwB91U0qDNQDpEXnw7knhxLM0nwDvexQAwx9tO8iKDZSqqneVq+URd/WIoz+BALMqUTgdSg==",
|
||||
"version": "1.38.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.1.tgz",
|
||||
"integrity": "sha512-NqRp8XMwj3AK+zKLbZShl0r/9wKgzqI/527bkptKXomtuo+dOjU9NdMASQ8DNC9z9zLOMbG53T4eihYr3XR+BQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"fsevents": "2.3.2",
|
||||
"playwright-core": "1.37.1"
|
||||
"playwright": "1.38.1"
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-node-resolve": {
|
||||
@@ -11524,9 +11536,9 @@
|
||||
"requires": {}
|
||||
},
|
||||
"@stencil/core": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.2.1.tgz",
|
||||
"integrity": "sha512-alYwqVwxfD0n6HKRVJqJoTzQNnf44n/sddvjNu3JMEn3sfY/Ag7rpmwUntYjtJmRut+or+9gPPgIJviCuKi4yQ=="
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.4.1.tgz",
|
||||
"integrity": "sha512-SirGcrb5yKHCn2BwdM7HGVXuvCdmwiXlVczEj8jJxQIm42CAUQCUECxtZidTzp+oZBZnWLnoAvfanchJsgkQzA=="
|
||||
},
|
||||
"@stencil/react-output-target": {
|
||||
"version": "0.5.3",
|
||||
@@ -11536,9 +11548,9 @@
|
||||
"requires": {}
|
||||
},
|
||||
"@stencil/sass": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/sass/-/sass-3.0.5.tgz",
|
||||
"integrity": "sha512-9nyllMXOEvHywo6fP2iwXgnq32A+OOUE36Aq7iUjzwT3wdr04qsvupO1JNIyRvmvCDF15hOKXztrZH1/wDu2Zg==",
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/sass/-/sass-3.0.6.tgz",
|
||||
"integrity": "sha512-d+k8qCvDgzI/vIV7M5QavIS6b75X4yn+YQH80KA/L0oL/hKD8KeF3wMOd7P1nDhGLIGRNw5tBDbe0GeIsGVU7A==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
@@ -16331,10 +16343,20 @@
|
||||
"find-up": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"playwright": {
|
||||
"version": "1.38.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.1.tgz",
|
||||
"integrity": "sha512-oRMSJmZrOu1FP5iu3UrCx8JEFRIMxLDM0c/3o4bpzU5Tz97BypefWf7TuTNPWeCe279TPal5RtPPZ+9lW/Qkow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fsevents": "2.3.2",
|
||||
"playwright-core": "1.38.1"
|
||||
}
|
||||
},
|
||||
"playwright-core": {
|
||||
"version": "1.37.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.1.tgz",
|
||||
"integrity": "sha512-17EuQxlSIYCmEMwzMqusJ2ztDgJePjrbttaefgdsiqeLWidjYz9BxXaTaZWxH1J95SHGk6tjE+dwgWILJoUZfA==",
|
||||
"version": "1.38.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.1.tgz",
|
||||
"integrity": "sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==",
|
||||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "7.3.4",
|
||||
"version": "7.4.3",
|
||||
"description": "Base components for Ionic",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -31,25 +31,25 @@
|
||||
"loader/"
|
||||
],
|
||||
"dependencies": {
|
||||
"@stencil/core": "^4.2.1",
|
||||
"@stencil/core": "^4.4.1",
|
||||
"ionicons": "7.1.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "^4.7.3",
|
||||
"@capacitor/core": "^5.3.0",
|
||||
"@capacitor/core": "^5.4.2",
|
||||
"@capacitor/haptics": "^5.0.6",
|
||||
"@capacitor/keyboard": "^5.0.6",
|
||||
"@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.37.1",
|
||||
"@playwright/test": "^1.38.1",
|
||||
"@rollup/plugin-node-resolve": "^8.4.0",
|
||||
"@rollup/plugin-virtual": "^2.0.3",
|
||||
"@stencil/angular-output-target": "^0.8.2",
|
||||
"@stencil/react-output-target": "^0.5.3",
|
||||
"@stencil/sass": "^3.0.5",
|
||||
"@stencil/sass": "^3.0.6",
|
||||
"@stencil/vue-output-target": "^0.8.6",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^14.6.0",
|
||||
@@ -95,6 +95,7 @@
|
||||
"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",
|
||||
"test.treeshake": "node scripts/treeshaking.js dist/index.js",
|
||||
"validate": "npm run lint && npm run test && npm run build && npm run test.treeshake"
|
||||
|
||||
@@ -60,7 +60,8 @@ const config: PlaywrightTestConfig = {
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
maxFailures: 0,
|
||||
retries: 2,
|
||||
/* Test retries help catch flaky tests on CI */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
reportSlowTests: null,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
|
||||
59
core/src/components.d.ts
vendored
@@ -602,6 +602,10 @@ export namespace Components {
|
||||
"mode"?: "ios" | "md";
|
||||
}
|
||||
interface IonCheckbox {
|
||||
/**
|
||||
* How to control the alignment of the checkbox and label on the cross axis. `"start"`: The label and control will appear on the left of the cross axis in LTR, and on the right side in RTL. `"center"`: The label and control will appear at the center of the cross axis in both LTR and RTL.
|
||||
*/
|
||||
"alignment": 'start' | 'center';
|
||||
/**
|
||||
* If `true`, the checkbox is selected.
|
||||
*/
|
||||
@@ -623,9 +627,9 @@ export namespace Components {
|
||||
*/
|
||||
"justify": 'start' | 'end' | 'space-between';
|
||||
/**
|
||||
* Where to place the label relative to the checkbox. `"start"`: The label will appear to the left of the checkbox in LTR and to the right in RTL. `"end"`: The label will appear to the right of the checkbox in LTR and to the left in RTL. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("...").
|
||||
* Where to place the label relative to the checkbox. `"start"`: The label will appear to the left of the checkbox in LTR and to the right in RTL. `"end"`: The label will appear to the right of the checkbox in LTR and to the left in RTL. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("..."). `"stacked"`: The label will appear above the checkbox regardless of the direction. The alignment of the label can be controlled with the `alignment` property.
|
||||
*/
|
||||
"labelPlacement": 'start' | 'end' | 'fixed';
|
||||
"labelPlacement": 'start' | 'end' | 'fixed' | 'stacked';
|
||||
/**
|
||||
* Set the `legacy` property to `true` to forcibly use the legacy form control markup. Ionic will only opt checkboxes in to the modern form markup when they are using either the `aria-label` attribute or have text in the default slot. As a result, the `legacy` property should only be used as an escape hatch when you want to avoid this automatic opt-in behavior. Note that this property will be removed in an upcoming major release of Ionic, and all form components will be opted-in to using the modern form markup.
|
||||
*/
|
||||
@@ -1332,6 +1336,7 @@ export namespace Components {
|
||||
"download": string | undefined;
|
||||
/**
|
||||
* The fill for the item. If `"solid"` the item will have a background. If `"outline"` the item will be transparent with a border. Only available in `md` mode.
|
||||
* @deprecated Use the `fill` property on `ion-input` or `ion-textarea` instead.
|
||||
*/
|
||||
"fill"?: 'outline' | 'solid';
|
||||
/**
|
||||
@@ -2216,6 +2221,10 @@ export namespace Components {
|
||||
"value": number;
|
||||
}
|
||||
interface IonRadio {
|
||||
/**
|
||||
* How to control the alignment of the radio and label on the cross axis. `"start"`: The label and control will appear on the left of the cross axis in LTR, and on the right side in RTL. `"center"`: The label and control will appear at the center of the cross axis in both LTR and RTL.
|
||||
*/
|
||||
"alignment": 'start' | 'center';
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
@@ -2229,9 +2238,9 @@ export namespace Components {
|
||||
*/
|
||||
"justify": 'start' | 'end' | 'space-between';
|
||||
/**
|
||||
* Where to place the label relative to the radio. `"start"`: The label will appear to the left of the radio in LTR and to the right in RTL. `"end"`: The label will appear to the right of the radio in LTR and to the left in RTL. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("...").
|
||||
* Where to place the label relative to the radio. `"start"`: The label will appear to the left of the radio in LTR and to the right in RTL. `"end"`: The label will appear to the right of the radio in LTR and to the left in RTL. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("..."). `"stacked"`: The label will appear above the radio regardless of the direction. The alignment of the label can be controlled with the `alignment` property.
|
||||
*/
|
||||
"labelPlacement": 'start' | 'end' | 'fixed';
|
||||
"labelPlacement": 'start' | 'end' | 'fixed' | 'stacked';
|
||||
/**
|
||||
* Set the `legacy` property to `true` to forcibly use the legacy form control markup. Ionic will only opt components in to the modern form markup when they are using either the `aria-label` attribute or the default slot that contains the label text. As a result, the `legacy` property should only be used as an escape hatch when you want to avoid this automatic opt-in behavior. Note that this property will be removed in an upcoming major release of Ionic, and all form components will be opted-in to using the modern form markup.
|
||||
*/
|
||||
@@ -2291,9 +2300,9 @@ export namespace Components {
|
||||
*/
|
||||
"label"?: string;
|
||||
/**
|
||||
* Where to place the label relative to the range. `"start"`: The label will appear to the left of the range in LTR and to the right in RTL. `"end"`: The label will appear to the right of the range in LTR and to the left in RTL. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("...").
|
||||
* Where to place the label relative to the range. `"start"`: The label will appear to the left of the range in LTR and to the right in RTL. `"end"`: The label will appear to the right of the range in LTR and to the left in RTL. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("..."). `"stacked"`: The label will appear above the range regardless of the direction.
|
||||
*/
|
||||
"labelPlacement": 'start' | 'end' | 'fixed';
|
||||
"labelPlacement": 'start' | 'end' | 'fixed' | 'stacked';
|
||||
/**
|
||||
* Set the `legacy` property to `true` to forcibly use the legacy form control markup. Ionic will only opt components in to the modern form markup when they are using either the `aria-label` attribute or the `label` property. As a result, the `legacy` property should only be used as an escape hatch when you want to avoid this automatic opt-in behavior. Note that this property will be removed in an upcoming major release of Ionic, and all form components will be opted-in to using the modern form markup.
|
||||
*/
|
||||
@@ -3165,6 +3174,10 @@ export namespace Components {
|
||||
"trigger": string | undefined;
|
||||
}
|
||||
interface IonToggle {
|
||||
/**
|
||||
* How to control the alignment of the toggle and label on the cross axis. ``"start"`: The label and control will appear on the left of the cross axis in LTR, and on the right side in RTL. `"center"`: The label and control will appear at the center of the cross axis in both LTR and RTL.
|
||||
*/
|
||||
"alignment": 'start' | 'center';
|
||||
/**
|
||||
* If `true`, the toggle is selected.
|
||||
*/
|
||||
@@ -3186,9 +3199,9 @@ export namespace Components {
|
||||
*/
|
||||
"justify": 'start' | 'end' | 'space-between';
|
||||
/**
|
||||
* Where to place the label relative to the input. `"start"`: The label will appear to the left of the toggle in LTR and to the right in RTL. `"end"`: The label will appear to the right of the toggle in LTR and to the left in RTL. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("...").
|
||||
* Where to place the label relative to the input. `"start"`: The label will appear to the left of the toggle in LTR and to the right in RTL. `"end"`: The label will appear to the right of the toggle in LTR and to the left in RTL. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("..."). `"stacked"`: The label will appear above the toggle regardless of the direction. The alignment of the label can be controlled with the `alignment` property.
|
||||
*/
|
||||
"labelPlacement": 'start' | 'end' | 'fixed';
|
||||
"labelPlacement": 'start' | 'end' | 'fixed' | 'stacked';
|
||||
/**
|
||||
* Set the `legacy` property to `true` to forcibly use the legacy form control markup. Ionic will only opt components in to the modern form markup when they are using either the `aria-label` attribute or the default slot that contains the label text. As a result, the `legacy` property should only be used as an escape hatch when you want to avoid this automatic opt-in behavior. Note that this property will be removed in an upcoming major release of Ionic, and all form components will be opted-in to using the modern form markup.
|
||||
*/
|
||||
@@ -4625,6 +4638,10 @@ declare namespace LocalJSX {
|
||||
"mode"?: "ios" | "md";
|
||||
}
|
||||
interface IonCheckbox {
|
||||
/**
|
||||
* How to control the alignment of the checkbox and label on the cross axis. `"start"`: The label and control will appear on the left of the cross axis in LTR, and on the right side in RTL. `"center"`: The label and control will appear at the center of the cross axis in both LTR and RTL.
|
||||
*/
|
||||
"alignment"?: 'start' | 'center';
|
||||
/**
|
||||
* If `true`, the checkbox is selected.
|
||||
*/
|
||||
@@ -4646,9 +4663,9 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"justify"?: 'start' | 'end' | 'space-between';
|
||||
/**
|
||||
* Where to place the label relative to the checkbox. `"start"`: The label will appear to the left of the checkbox in LTR and to the right in RTL. `"end"`: The label will appear to the right of the checkbox in LTR and to the left in RTL. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("...").
|
||||
* Where to place the label relative to the checkbox. `"start"`: The label will appear to the left of the checkbox in LTR and to the right in RTL. `"end"`: The label will appear to the right of the checkbox in LTR and to the left in RTL. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("..."). `"stacked"`: The label will appear above the checkbox regardless of the direction. The alignment of the label can be controlled with the `alignment` property.
|
||||
*/
|
||||
"labelPlacement"?: 'start' | 'end' | 'fixed';
|
||||
"labelPlacement"?: 'start' | 'end' | 'fixed' | 'stacked';
|
||||
/**
|
||||
* Set the `legacy` property to `true` to forcibly use the legacy form control markup. Ionic will only opt checkboxes in to the modern form markup when they are using either the `aria-label` attribute or have text in the default slot. As a result, the `legacy` property should only be used as an escape hatch when you want to avoid this automatic opt-in behavior. Note that this property will be removed in an upcoming major release of Ionic, and all form components will be opted-in to using the modern form markup.
|
||||
*/
|
||||
@@ -5391,6 +5408,7 @@ declare namespace LocalJSX {
|
||||
"download"?: string | undefined;
|
||||
/**
|
||||
* The fill for the item. If `"solid"` the item will have a background. If `"outline"` the item will be transparent with a border. Only available in `md` mode.
|
||||
* @deprecated Use the `fill` property on `ion-input` or `ion-textarea` instead.
|
||||
*/
|
||||
"fill"?: 'outline' | 'solid';
|
||||
/**
|
||||
@@ -6226,6 +6244,10 @@ declare namespace LocalJSX {
|
||||
"value"?: number;
|
||||
}
|
||||
interface IonRadio {
|
||||
/**
|
||||
* How to control the alignment of the radio and label on the cross axis. `"start"`: The label and control will appear on the left of the cross axis in LTR, and on the right side in RTL. `"center"`: The label and control will appear at the center of the cross axis in both LTR and RTL.
|
||||
*/
|
||||
"alignment"?: 'start' | 'center';
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
@@ -6239,9 +6261,9 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"justify"?: 'start' | 'end' | 'space-between';
|
||||
/**
|
||||
* Where to place the label relative to the radio. `"start"`: The label will appear to the left of the radio in LTR and to the right in RTL. `"end"`: The label will appear to the right of the radio in LTR and to the left in RTL. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("...").
|
||||
* Where to place the label relative to the radio. `"start"`: The label will appear to the left of the radio in LTR and to the right in RTL. `"end"`: The label will appear to the right of the radio in LTR and to the left in RTL. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("..."). `"stacked"`: The label will appear above the radio regardless of the direction. The alignment of the label can be controlled with the `alignment` property.
|
||||
*/
|
||||
"labelPlacement"?: 'start' | 'end' | 'fixed';
|
||||
"labelPlacement"?: 'start' | 'end' | 'fixed' | 'stacked';
|
||||
/**
|
||||
* Set the `legacy` property to `true` to forcibly use the legacy form control markup. Ionic will only opt components in to the modern form markup when they are using either the `aria-label` attribute or the default slot that contains the label text. As a result, the `legacy` property should only be used as an escape hatch when you want to avoid this automatic opt-in behavior. Note that this property will be removed in an upcoming major release of Ionic, and all form components will be opted-in to using the modern form markup.
|
||||
*/
|
||||
@@ -6319,9 +6341,9 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"label"?: string;
|
||||
/**
|
||||
* Where to place the label relative to the range. `"start"`: The label will appear to the left of the range in LTR and to the right in RTL. `"end"`: The label will appear to the right of the range in LTR and to the left in RTL. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("...").
|
||||
* Where to place the label relative to the range. `"start"`: The label will appear to the left of the range in LTR and to the right in RTL. `"end"`: The label will appear to the right of the range in LTR and to the left in RTL. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("..."). `"stacked"`: The label will appear above the range regardless of the direction.
|
||||
*/
|
||||
"labelPlacement"?: 'start' | 'end' | 'fixed';
|
||||
"labelPlacement"?: 'start' | 'end' | 'fixed' | 'stacked';
|
||||
/**
|
||||
* Set the `legacy` property to `true` to forcibly use the legacy form control markup. Ionic will only opt components in to the modern form markup when they are using either the `aria-label` attribute or the `label` property. As a result, the `legacy` property should only be used as an escape hatch when you want to avoid this automatic opt-in behavior. Note that this property will be removed in an upcoming major release of Ionic, and all form components will be opted-in to using the modern form markup.
|
||||
*/
|
||||
@@ -6954,6 +6976,7 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"mode"?: "ios" | "md";
|
||||
"onIonTabBarChanged"?: (event: IonTabBarCustomEvent<TabBarChangedEventDetail>) => void;
|
||||
"onIonTabBarLoaded"?: (event: IonTabBarCustomEvent<void>) => void;
|
||||
/**
|
||||
* The selected tab component
|
||||
*/
|
||||
@@ -7298,6 +7321,10 @@ declare namespace LocalJSX {
|
||||
"trigger"?: string | undefined;
|
||||
}
|
||||
interface IonToggle {
|
||||
/**
|
||||
* How to control the alignment of the toggle and label on the cross axis. ``"start"`: The label and control will appear on the left of the cross axis in LTR, and on the right side in RTL. `"center"`: The label and control will appear at the center of the cross axis in both LTR and RTL.
|
||||
*/
|
||||
"alignment"?: 'start' | 'center';
|
||||
/**
|
||||
* If `true`, the toggle is selected.
|
||||
*/
|
||||
@@ -7319,9 +7346,9 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"justify"?: 'start' | 'end' | 'space-between';
|
||||
/**
|
||||
* Where to place the label relative to the input. `"start"`: The label will appear to the left of the toggle in LTR and to the right in RTL. `"end"`: The label will appear to the right of the toggle in LTR and to the left in RTL. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("...").
|
||||
* Where to place the label relative to the input. `"start"`: The label will appear to the left of the toggle in LTR and to the right in RTL. `"end"`: The label will appear to the right of the toggle in LTR and to the left in RTL. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("..."). `"stacked"`: The label will appear above the toggle regardless of the direction. The alignment of the label can be controlled with the `alignment` property.
|
||||
*/
|
||||
"labelPlacement"?: 'start' | 'end' | 'fixed';
|
||||
"labelPlacement"?: 'start' | 'end' | 'fixed' | 'stacked';
|
||||
/**
|
||||
* Set the `legacy` property to `true` to forcibly use the legacy form control markup. Ionic will only opt components in to the modern form markup when they are using either the `aria-label` attribute or the default slot that contains the label text. As a result, the `legacy` property should only be used as an escape hatch when you want to avoid this automatic opt-in behavior. Note that this property will be removed in an upcoming major release of Ionic, and all form components will be opted-in to using the modern form markup.
|
||||
*/
|
||||
|
||||
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
@@ -108,6 +108,14 @@
|
||||
@include margin($checkbox-item-label-margin-top, null, $checkbox-item-label-margin-bottom, null);
|
||||
}
|
||||
|
||||
:host(.in-item.checkbox-label-placement-stacked) .label-text-wrapper {
|
||||
@include margin($checkbox-item-label-margin-top, null, $form-control-label-margin, null);
|
||||
}
|
||||
|
||||
:host(.in-item.checkbox-label-placement-stacked) .native-wrapper {
|
||||
@include margin(null, null, $checkbox-item-label-margin-bottom, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* If no label text is placed into the slot
|
||||
* then the element should be hidden otherwise
|
||||
@@ -181,6 +189,17 @@ input {
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
// Align Items
|
||||
// ---------------------------------------------
|
||||
|
||||
:host(.checkbox-alignment-start) .checkbox-wrapper {
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
:host(.checkbox-alignment-center) .checkbox-wrapper {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
// Label Placement - Start
|
||||
// ----------------------------------------------------------------
|
||||
@@ -248,6 +267,40 @@ input {
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
// Label Placement - Stacked
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Label is on top of the checkbox.
|
||||
*/
|
||||
:host(.checkbox-label-placement-stacked) .checkbox-wrapper {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
:host(.checkbox-label-placement-stacked) .label-text-wrapper {
|
||||
@include transform(scale(#{$form-control-label-stacked-scale}));
|
||||
|
||||
/**
|
||||
* The margin between the label and
|
||||
* the checkbox should be on the bottom
|
||||
* when the label sits at the top.
|
||||
*/
|
||||
@include margin(null, 0, $form-control-label-margin, 0);
|
||||
|
||||
/**
|
||||
* Label text should not extend
|
||||
* beyond the bounds of the checkbox.
|
||||
*/
|
||||
max-width: calc(100% / #{$form-control-label-stacked-scale});
|
||||
}
|
||||
|
||||
:host(.checkbox-label-placement-stacked.checkbox-alignment-start) .label-text-wrapper {
|
||||
@include transform-origin(start, top);
|
||||
}
|
||||
|
||||
:host(.checkbox-label-placement-stacked.checkbox-alignment-center) .label-text-wrapper {
|
||||
@include transform-origin(center, top);
|
||||
}
|
||||
|
||||
// Checked / Indeterminate Checkbox
|
||||
// ---------------------------------------------
|
||||
|
||||
@@ -81,8 +81,9 @@ export class Checkbox implements ComponentInterface {
|
||||
* `"start"`: The label will appear to the left of the checkbox in LTR and to the right in RTL.
|
||||
* `"end"`: The label will appear to the right of the checkbox in LTR and to the left in RTL.
|
||||
* `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("...").
|
||||
* `"stacked"`: The label will appear above the checkbox regardless of the direction. The alignment of the label can be controlled with the `alignment` property.
|
||||
*/
|
||||
@Prop() labelPlacement: 'start' | 'end' | 'fixed' = 'start';
|
||||
@Prop() labelPlacement: 'start' | 'end' | 'fixed' | 'stacked' = 'start';
|
||||
|
||||
/**
|
||||
* How to pack the label and checkbox within a line.
|
||||
@@ -95,6 +96,13 @@ export class Checkbox implements ComponentInterface {
|
||||
*/
|
||||
@Prop() justify: 'start' | 'end' | 'space-between' = 'space-between';
|
||||
|
||||
/**
|
||||
* How to control the alignment of the checkbox and label on the cross axis.
|
||||
* `"start"`: The label and control will appear on the left of the cross axis in LTR, and on the right side in RTL.
|
||||
* `"center"`: The label and control will appear at the center of the cross axis in both LTR and RTL.
|
||||
*/
|
||||
@Prop() alignment: 'start' | 'center' = 'center';
|
||||
|
||||
// TODO(FW-3100): remove this
|
||||
/**
|
||||
* Set the `legacy` property to `true` to forcibly use the legacy form control markup.
|
||||
@@ -132,9 +140,8 @@ export class Checkbox implements ComponentInterface {
|
||||
*/
|
||||
@Event() ionStyle!: EventEmitter<StyleEventDetail>;
|
||||
|
||||
// TODO(FW-3100): remove this
|
||||
connectedCallback() {
|
||||
this.legacyFormController = createLegacyFormController(this.el);
|
||||
this.legacyFormController = createLegacyFormController(this.el); // TODO(FW-3100): remove this
|
||||
}
|
||||
|
||||
componentWillLoad() {
|
||||
@@ -187,7 +194,7 @@ export class Checkbox implements ComponentInterface {
|
||||
});
|
||||
};
|
||||
|
||||
private toggleChecked = (ev: any) => {
|
||||
private toggleChecked = (ev: Event) => {
|
||||
ev.preventDefault();
|
||||
|
||||
this.setFocus();
|
||||
@@ -203,6 +210,14 @@ export class Checkbox implements ComponentInterface {
|
||||
this.ionBlur.emit();
|
||||
};
|
||||
|
||||
private onClick = (ev: MouseEvent) => {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.toggleChecked(ev);
|
||||
};
|
||||
|
||||
// TODO(FW-3100): run contents of renderCheckbox directly instead
|
||||
render() {
|
||||
const { legacyFormController } = this;
|
||||
@@ -224,6 +239,7 @@ export class Checkbox implements ComponentInterface {
|
||||
labelPlacement,
|
||||
name,
|
||||
value,
|
||||
alignment,
|
||||
} = this;
|
||||
const mode = getIonMode(this);
|
||||
const path = getSVGPath(mode, indeterminate);
|
||||
@@ -240,8 +256,10 @@ export class Checkbox implements ComponentInterface {
|
||||
'checkbox-indeterminate': indeterminate,
|
||||
interactive: true,
|
||||
[`checkbox-justify-${justify}`]: true,
|
||||
[`checkbox-alignment-${alignment}`]: true,
|
||||
[`checkbox-label-placement-${labelPlacement}`]: true,
|
||||
})}
|
||||
onClick={this.onClick}
|
||||
>
|
||||
<label class="checkbox-wrapper">
|
||||
{/*
|
||||
@@ -323,6 +341,7 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
|
||||
'legacy-checkbox': true,
|
||||
interactive: true,
|
||||
})}
|
||||
onClick={this.onClick}
|
||||
>
|
||||
<svg class="checkbox-icon" viewBox="0 0 24 24" part="container">
|
||||
{path}
|
||||
|
||||
@@ -83,5 +83,28 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
await checkbox.evaluate((el: HTMLIonCheckboxElement) => (el.checked = true));
|
||||
expect(ionChange).not.toHaveReceivedEvent();
|
||||
});
|
||||
|
||||
test('clicking padded space within item should click the checkbox', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-item>
|
||||
<ion-checkbox>Size</ion-checkbox>
|
||||
</ion-item>
|
||||
`,
|
||||
config
|
||||
);
|
||||
const itemNative = page.locator('.item-native');
|
||||
const ionChange = await page.spyOnEvent('ionChange');
|
||||
|
||||
// Clicks the padded space within the item
|
||||
await itemNative.click({
|
||||
position: {
|
||||
x: 5,
|
||||
y: 5,
|
||||
},
|
||||
});
|
||||
|
||||
expect(ionChange).toHaveReceivedEvent();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
24
core/src/components/checkbox/test/checkbox.spec.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
|
||||
import { Checkbox } from '../checkbox';
|
||||
|
||||
describe('ion-checkbox: disabled', () => {
|
||||
it('clicking disabled checkbox should not toggle checked state', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Checkbox],
|
||||
html: `
|
||||
<ion-checkbox disabled="true">Checkbox</ion-checkbox>
|
||||
`,
|
||||
});
|
||||
|
||||
const checkbox = page.body.querySelector('ion-checkbox');
|
||||
|
||||
expect(checkbox.checked).toBe(false);
|
||||
|
||||
checkbox.click();
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(checkbox.checked).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -70,4 +70,21 @@ configs({ directions: ['ltr'], modes: ['md'] }).forEach(({ title, screenshot, co
|
||||
await expect(list).toHaveScreenshot(screenshot(`checkbox-long-label-in-item`));
|
||||
});
|
||||
});
|
||||
|
||||
test.describe(title('checkbox: stacked label in item'), () => {
|
||||
test('should render margins correctly when using stacked label in item', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-checkbox label-placement="stacked">Enable Notifications</ion-checkbox>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
`,
|
||||
config
|
||||
);
|
||||
const list = page.locator('ion-list');
|
||||
await expect(list).toHaveScreenshot(screenshot(`checkbox-stacked-label-in-item`));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
@@ -137,6 +137,27 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>Placement Stacked</h1>
|
||||
<div class="grid">
|
||||
<div class="grid-item">
|
||||
<h2>Align Start</h2>
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-checkbox label-placement="stacked" alignment="start">Enable Notifications</ion-checkbox>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Align Center</h2>
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-checkbox label-placement="stacked" alignment="center">Enable Notifications</ion-checkbox>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>States</h1>
|
||||
<div class="grid">
|
||||
<div class="grid-item">
|
||||
|
||||
@@ -138,5 +138,47 @@ configs().forEach(({ title, screenshot, config }) => {
|
||||
await expect(checkbox).toHaveScreenshot(screenshot(`checkbox-label-fixed-justify-space-between`));
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('checkbox: stacked placement', () => {
|
||||
test('should align the label to the start of the container in the stacked position', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-checkbox label-placement="stacked" alignment="start" style="width: 200px">This is a long label</ion-checkbox>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const checkbox = page.locator('ion-checkbox');
|
||||
await expect(checkbox).toHaveScreenshot(screenshot(`checkbox-label-stacked-align-start`));
|
||||
});
|
||||
|
||||
test('should align the label to the center of the container in the stacked position', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-checkbox label-placement="stacked" alignment="center" style="width: 200px">This is a long label</ion-checkbox>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const checkbox = page.locator('ion-checkbox');
|
||||
await expect(checkbox).toHaveScreenshot(screenshot(`checkbox-label-stacked-align-center`));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config, screenshot }) => {
|
||||
test.describe(title('checkbox: stacked long label'), () => {
|
||||
test('long label should truncate', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-checkbox label-placement="stacked" alignment="start" style="width: 200px">Enable Notifications Enable Notifications Enable Notifications Enable Notifications Enable Notifications Enable Notifications Enable Notifications</ion-checkbox>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const checkbox = page.locator('ion-checkbox');
|
||||
await expect(checkbox).toHaveScreenshot(screenshot(`checkbox-label-stacked-long-label`));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
@@ -104,6 +104,19 @@
|
||||
<ion-checkbox label-placement="fixed" justify="space-between">Enable Notifications</ion-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>Placement Stacked</h1>
|
||||
<div class="grid">
|
||||
<div class="grid-item">
|
||||
<h2>Align Start</h2>
|
||||
<ion-checkbox label-placement="stacked" alignment="start">Enable Notifications</ion-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="grid-item">
|
||||
<h2>Align Center</h2>
|
||||
<ion-checkbox label-placement="stacked" alignment="center">Enable Notifications</ion-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
</body>
|
||||
|
||||
@@ -67,5 +67,28 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
await checkbox.evaluate((el: HTMLIonCheckboxElement) => (el.checked = true));
|
||||
await expect(ionChange).not.toHaveReceivedEvent();
|
||||
});
|
||||
|
||||
test('clicking padded space within item should click the checkbox', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-item>
|
||||
<ion-checkbox legacy="true" value="my-checkbox"></ion-checkbox>
|
||||
</ion-item>
|
||||
`,
|
||||
config
|
||||
);
|
||||
const itemNative = page.locator('.item-native');
|
||||
const ionChange = await page.spyOnEvent('ionChange');
|
||||
|
||||
// Clicks the padded space within the item
|
||||
await itemNative.click({
|
||||
position: {
|
||||
x: 5,
|
||||
y: 5,
|
||||
},
|
||||
});
|
||||
|
||||
expect(ionChange).toHaveReceivedEvent();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ComponentInterface, EventEmitter } from '@stencil/core';
|
||||
import { Build, Component, Element, Event, Host, Listen, Method, Prop, forceUpdate, h, readTask } from '@stencil/core';
|
||||
import { componentOnReady } from '@utils/helpers';
|
||||
import { componentOnReady, hasLazyBuild } from '@utils/helpers';
|
||||
import { isPlatform } from '@utils/platform';
|
||||
import { isRTL } from '@utils/rtl';
|
||||
import { createColorClasses, hostContext } from '@utils/theme';
|
||||
@@ -34,6 +34,9 @@ export class Content implements ComponentInterface {
|
||||
private isMainContent = true;
|
||||
private resizeTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
private tabsElement: HTMLElement | null = null;
|
||||
private tabsLoadCallback?: () => void;
|
||||
|
||||
// Detail is used in a hot loop in the scroll event, by allocating it here
|
||||
// V8 will be able to inline any read/write to it since it's a monomorphic class.
|
||||
// https://mrale.ph/blog/2015/01/11/whats-up-with-monomorphism.html
|
||||
@@ -115,15 +118,61 @@ export class Content implements ComponentInterface {
|
||||
|
||||
connectedCallback() {
|
||||
this.isMainContent = this.el.closest('ion-menu, ion-popover, ion-modal') === null;
|
||||
|
||||
/**
|
||||
* The fullscreen content offsets need to be
|
||||
* computed after the tab bar has loaded. Since
|
||||
* lazy evaluation means components are not hydrated
|
||||
* at the same time, we need to wait for the ionTabBarLoaded
|
||||
* event to fire. This does not impact dist-custom-elements
|
||||
* because there is no hydration there.
|
||||
*/
|
||||
if (hasLazyBuild(this.el)) {
|
||||
/**
|
||||
* We need to cache the reference to the tabs.
|
||||
* If just the content is unmounted then we won't
|
||||
* be able to query for the closest tabs on disconnectedCallback
|
||||
* since the content has been removed from the DOM tree.
|
||||
*/
|
||||
const closestTabs = (this.tabsElement = this.el.closest('ion-tabs'));
|
||||
if (closestTabs !== null) {
|
||||
/**
|
||||
* When adding and removing the event listener
|
||||
* we need to make sure we pass the same function reference
|
||||
* otherwise the event listener will not be removed properly.
|
||||
* We can't only pass `this.resize` because "this" in the function
|
||||
* context becomes a reference to IonTabs instead of IonContent.
|
||||
*
|
||||
* Additionally, we listen for ionTabBarLoaded on the IonTabs
|
||||
* instance rather than the IonTabBar instance. It's possible for
|
||||
* a tab bar to be conditionally rendered/mounted. Since ionTabBarLoaded
|
||||
* bubbles, we can catch any instances of child tab bars loading by listening
|
||||
* on IonTabs.
|
||||
*/
|
||||
this.tabsLoadCallback = () => this.resize();
|
||||
closestTabs.addEventListener('ionTabBarLoaded', this.tabsLoadCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.onScrollEnd();
|
||||
}
|
||||
|
||||
@Listen('appload', { target: 'window' })
|
||||
onAppLoad() {
|
||||
this.resize();
|
||||
if (hasLazyBuild(this.el)) {
|
||||
/**
|
||||
* The event listener and tabs caches need to
|
||||
* be cleared otherwise this will create a memory
|
||||
* leak where the IonTabs instance can never be
|
||||
* garbage collected.
|
||||
*/
|
||||
const { tabsElement, tabsLoadCallback } = this;
|
||||
if (tabsElement !== null && tabsLoadCallback !== undefined) {
|
||||
tabsElement.removeEventListener('ionTabBarLoaded', tabsLoadCallback);
|
||||
}
|
||||
|
||||
this.tabsElement = null;
|
||||
this.tabsLoadCallback = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -206,6 +206,10 @@ export class DatetimeButton implements ComponentInterface {
|
||||
*/
|
||||
const parsedDatetimes = parseDate(parsedValues.length > 0 ? parsedValues : [getToday()]);
|
||||
|
||||
if (!parsedDatetimes) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* If developers incorrectly use multiple="true"
|
||||
* with non "date" datetimes, then just select
|
||||
|
||||
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
@@ -20,7 +20,7 @@
|
||||
@include padding($datetime-ios-padding, $datetime-ios-padding, $datetime-ios-padding, $datetime-ios-padding);
|
||||
|
||||
border-bottom: $datetime-ios-border-color;
|
||||
|
||||
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@@ -85,27 +85,32 @@
|
||||
*/
|
||||
@include padding($datetime-ios-padding * 0.5, $datetime-ios-padding * 0.5, $datetime-ios-padding * 0.5, $datetime-ios-padding * 0.5);
|
||||
|
||||
align-items: center;
|
||||
|
||||
height: calc(100% - #{$datetime-ios-padding});
|
||||
}
|
||||
|
||||
:host .calendar-day-wrapper {
|
||||
@include padding(4px);
|
||||
|
||||
// This is required so that the calendar day wrapper
|
||||
// will collapse instead of expanding to fill the button
|
||||
height: 0;
|
||||
|
||||
min-height: 16px;
|
||||
}
|
||||
|
||||
:host .calendar-day {
|
||||
width: $datetime-ios-day-width;
|
||||
min-width: $datetime-ios-day-width;
|
||||
|
||||
height: $datetime-ios-day-height;
|
||||
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.calendar-day:focus .calendar-day-highlight,
|
||||
.calendar-day.calendar-day-active .calendar-day-highlight {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.calendar-day.calendar-day-active .calendar-day-highlight {
|
||||
background: current-color(base);
|
||||
}
|
||||
|
||||
// !important is needed here to overwrite custom highlight background, which is inline.
|
||||
// Does not apply to the active state because highlights aren't applied at all there.
|
||||
.calendar-day:focus .calendar-day-highlight {
|
||||
/* stylelint-disable-next-line declaration-no-important */
|
||||
background: current-color(base) !important;
|
||||
.calendar-day.calendar-day-active {
|
||||
background: current-color(base, 0.2);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,12 +140,6 @@
|
||||
color: current-color(contrast);
|
||||
}
|
||||
|
||||
.calendar-day.calendar-day-today.calendar-day-active .calendar-day-highlight {
|
||||
background: current-color(base);
|
||||
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
// Time / Header
|
||||
// -----------------------------------
|
||||
:host .datetime-time {
|
||||
|
||||
@@ -15,3 +15,9 @@ $datetime-ios-time-width: 68px !default;
|
||||
|
||||
/// @prop - Border radius of the time picker
|
||||
$datetime-ios-time-border-radius: 8px !default;
|
||||
|
||||
/// @prop - Width of the calendar day
|
||||
$datetime-ios-day-width: 40px !default;
|
||||
|
||||
/// @prop - Height of the calendar day
|
||||
$datetime-ios-day-height: $datetime-ios-day-width !default;
|
||||
|
||||
@@ -69,27 +69,22 @@
|
||||
|
||||
// Individual day button in month
|
||||
:host .calendar-day {
|
||||
@include padding(13px, 0, 13px, 0px);
|
||||
width: $datetime-md-day-width;
|
||||
min-width: $datetime-md-day-width;
|
||||
|
||||
height: $datetime-md-day-height;
|
||||
|
||||
font-size: $datetime-md-calendar-item-font-size;
|
||||
}
|
||||
|
||||
.calendar-day:focus .calendar-day-highlight {
|
||||
background: current-color(base, 0.2);
|
||||
|
||||
box-shadow: 0px 0px 0px 4px current-color(base, 0.2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Day that today but not selected
|
||||
* should have ion-color for text color.
|
||||
*/
|
||||
:host .calendar-day.calendar-day-today {
|
||||
color: current-color(base);
|
||||
}
|
||||
|
||||
.calendar-day.calendar-day-today .calendar-day-highlight {
|
||||
border: 1px solid current-color(base);
|
||||
|
||||
color: current-color(base);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,7 +96,7 @@
|
||||
color: current-color(contrast);
|
||||
}
|
||||
|
||||
.calendar-day.calendar-day-active .calendar-day-highlight {
|
||||
.calendar-day.calendar-day-active {
|
||||
border: 1px solid current-color(base);
|
||||
|
||||
background: current-color(base);
|
||||
|
||||
@@ -15,3 +15,9 @@ $datetime-md-header-padding: 20px !default;
|
||||
|
||||
/// @prop - Padding for content
|
||||
$datetime-md-padding: 16px !default;
|
||||
|
||||
/// @prop - Width of the calendar day
|
||||
$datetime-md-day-width: 42px !default;
|
||||
|
||||
/// @prop - Height of the calendar day
|
||||
$datetime-md-day-height: $datetime-md-day-width !default;
|
||||
|
||||
@@ -290,6 +290,10 @@ ion-picker-column-internal {
|
||||
}
|
||||
|
||||
:host .calendar-body .calendar-month {
|
||||
display: flex;
|
||||
|
||||
flex-flow: column;
|
||||
|
||||
/**
|
||||
* Swiping should snap to at
|
||||
* most one month at a time.
|
||||
@@ -325,13 +329,31 @@ ion-picker-column-internal {
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
}
|
||||
|
||||
:host .calendar-day-wrapper {
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
// Adding a min width and min height allows
|
||||
// it to shrink smaller than its content
|
||||
// which keeps the calendar day highlight
|
||||
// larger while letting the grid items shrink
|
||||
min-width: 0;
|
||||
|
||||
min-height: 0;
|
||||
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Center the day text vertically
|
||||
* and horizontally within its grid cell.
|
||||
*/
|
||||
:host .calendar-day {
|
||||
@include padding(0px, 0px, 0px, 0px);
|
||||
@include margin(0px, 0px, 0px, 0px);
|
||||
@include border-radius(50%);
|
||||
@include padding(0px);
|
||||
@include margin(0px);
|
||||
|
||||
display: flex;
|
||||
|
||||
@@ -362,16 +384,10 @@ ion-picker-column-internal {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.calendar-day-highlight {
|
||||
@include border-radius(32px, 32px, 32px, 32px);
|
||||
@include padding(4px, 4px, 4px, 4px);
|
||||
.calendar-day:focus {
|
||||
background: current-color(base, 0.2);
|
||||
|
||||
position: absolute;
|
||||
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
z-index: -1;
|
||||
box-shadow: 0px 0px 0px 4px current-color(base, 0.2);
|
||||
}
|
||||
|
||||
// Time / Header
|
||||
|
||||
@@ -85,6 +85,12 @@ import {
|
||||
*
|
||||
* @part month-year-button - The button that opens the month/year picker when
|
||||
* using a grid style layout.
|
||||
*
|
||||
* @part calendar-day - The individual buttons that display a day inside of the datetime
|
||||
* calendar.
|
||||
* @part calendar-day active - The currently selected calendar day.
|
||||
* @part calendar-day today - The calendar day that contains the current day.
|
||||
* @part calendar-day disabled - The calendar day that is disabled.
|
||||
*/
|
||||
@Component({
|
||||
tag: 'ion-datetime',
|
||||
@@ -117,11 +123,7 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
private prevPresentation: string | null = null;
|
||||
|
||||
/**
|
||||
* Duplicate reference to `activeParts` that does not trigger a re-render of the component.
|
||||
* Allows caching an instance of the `activeParts` in between render cycles.
|
||||
*/
|
||||
private activePartsClone: DatetimeParts | DatetimeParts[] = [];
|
||||
private resolveForceDateScrolling?: () => void;
|
||||
|
||||
@State() showMonthAndYear = false;
|
||||
|
||||
@@ -140,6 +142,17 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
@State() isTimePopoverOpen = false;
|
||||
|
||||
/**
|
||||
* When defined, will force the datetime to render the month
|
||||
* containing the specified date. Currently, this should only
|
||||
* be used to enable immediately auto-scrolling to the new month,
|
||||
* and should then be reset to undefined once the transition is
|
||||
* finished and the forced month is now in view.
|
||||
*
|
||||
* Applies to grid-style datetimes only.
|
||||
*/
|
||||
@State() forceRenderDate?: DatetimeParts;
|
||||
|
||||
/**
|
||||
* The color to use from your application's color palette.
|
||||
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
|
||||
@@ -221,6 +234,12 @@ export class Datetime implements ComponentInterface {
|
||||
*/
|
||||
@Prop() presentation: DatetimePresentation = 'date-time';
|
||||
|
||||
private get isGridStyle() {
|
||||
const { presentation, preferWheel } = this;
|
||||
const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
|
||||
return hasDatePresentation && !preferWheel;
|
||||
}
|
||||
|
||||
/**
|
||||
* The text to display on the picker's cancel button.
|
||||
*/
|
||||
@@ -302,11 +321,6 @@ export class Datetime implements ComponentInterface {
|
||||
this.parsedMinuteValues = convertToArrayOfNumbers(this.minuteValues);
|
||||
}
|
||||
|
||||
@Watch('activeParts')
|
||||
protected activePartsChanged() {
|
||||
this.activePartsClone = this.activeParts;
|
||||
}
|
||||
|
||||
/**
|
||||
* The locale to use for `ion-datetime`. This
|
||||
* impacts month and day name formatting.
|
||||
@@ -356,54 +370,11 @@ export class Datetime implements ComponentInterface {
|
||||
* Update the datetime value when the value changes
|
||||
*/
|
||||
@Watch('value')
|
||||
protected valueChanged() {
|
||||
const { value, minParts, maxParts, workingParts } = this;
|
||||
protected async valueChanged() {
|
||||
const { value } = this;
|
||||
|
||||
if (this.hasValue()) {
|
||||
this.warnIfIncorrectValueUsage();
|
||||
|
||||
/**
|
||||
* Clones the value of the `activeParts` to the private clone, to update
|
||||
* the date display on the current render cycle without causing another render.
|
||||
*
|
||||
* This allows us to update the current value's date/time display without
|
||||
* refocusing or shifting the user's display (leaves the user in place).
|
||||
*/
|
||||
const valueDateParts = parseDate(value);
|
||||
if (valueDateParts) {
|
||||
warnIfValueOutOfBounds(valueDateParts, minParts, maxParts);
|
||||
|
||||
if (Array.isArray(valueDateParts)) {
|
||||
this.activePartsClone = [...valueDateParts];
|
||||
} else {
|
||||
const { month, day, year, hour, minute } = valueDateParts;
|
||||
const ampm = hour != null ? (hour >= 12 ? 'pm' : 'am') : undefined;
|
||||
|
||||
this.activePartsClone = {
|
||||
...this.activeParts,
|
||||
month,
|
||||
day,
|
||||
year,
|
||||
hour,
|
||||
minute,
|
||||
ampm,
|
||||
};
|
||||
|
||||
/**
|
||||
* The working parts am/pm value must be updated when the value changes, to
|
||||
* ensure the time picker hour column values are generated correctly.
|
||||
*
|
||||
* Note that we don't need to do this if valueDateParts is an array, since
|
||||
* multiple="true" does not apply to time pickers.
|
||||
*/
|
||||
this.setWorkingParts({
|
||||
...workingParts,
|
||||
ampm,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
printIonWarning(`Unable to parse date string: ${value}. Please provide a valid ISO 8601 datetime string.`);
|
||||
}
|
||||
this.processValue(value);
|
||||
}
|
||||
|
||||
this.emitStyle();
|
||||
@@ -596,9 +567,9 @@ export class Datetime implements ComponentInterface {
|
||||
* data. This should be used when rendering an
|
||||
* interface in an environment where the `value`
|
||||
* may not be set. This function works
|
||||
* by returning the first selected date in
|
||||
* "activePartsClone" and then falling back to
|
||||
* defaultParts if no active date is selected.
|
||||
* by returning the first selected date and then
|
||||
* falling back to defaultParts if no active date
|
||||
* is selected.
|
||||
*/
|
||||
private getActivePartsWithFallback = () => {
|
||||
const { defaultParts } = this;
|
||||
@@ -606,8 +577,8 @@ export class Datetime implements ComponentInterface {
|
||||
};
|
||||
|
||||
private getActivePart = () => {
|
||||
const { activePartsClone } = this;
|
||||
return Array.isArray(activePartsClone) ? activePartsClone[0] : activePartsClone;
|
||||
const { activeParts } = this;
|
||||
return Array.isArray(activeParts) ? activeParts[0] : activeParts;
|
||||
};
|
||||
|
||||
private closeParentOverlay = () => {
|
||||
@@ -627,7 +598,7 @@ export class Datetime implements ComponentInterface {
|
||||
};
|
||||
|
||||
private setActiveParts = (parts: DatetimeParts, removeDate = false) => {
|
||||
const { multiple, minParts, maxParts, activePartsClone } = this;
|
||||
const { multiple, minParts, maxParts, activeParts } = this;
|
||||
|
||||
/**
|
||||
* When setting the active parts, it is possible
|
||||
@@ -643,16 +614,7 @@ export class Datetime implements ComponentInterface {
|
||||
this.setWorkingParts(validatedParts);
|
||||
|
||||
if (multiple) {
|
||||
/**
|
||||
* We read from activePartsClone here because valueChanged() only updates that,
|
||||
* so it's the more reliable source of truth. If we read from activeParts, then
|
||||
* if you click July 1, manually set the value to July 2, and then click July 3,
|
||||
* the new value would be [July 1, July 3], ignoring the value set.
|
||||
*
|
||||
* We can then pass the new value to activeParts (rather than activePartsClone)
|
||||
* since the clone will be updated automatically by activePartsChanged().
|
||||
*/
|
||||
const activePartsArray = Array.isArray(activePartsClone) ? activePartsClone : [activePartsClone];
|
||||
const activePartsArray = Array.isArray(activeParts) ? activeParts : [activeParts];
|
||||
if (removeDate) {
|
||||
this.activeParts = activePartsArray.filter((p) => !isSameDay(p, validatedParts));
|
||||
} else {
|
||||
@@ -800,7 +762,7 @@ export class Datetime implements ComponentInterface {
|
||||
/**
|
||||
* Get the number of padding days so
|
||||
* we know how much to offset our next selector by
|
||||
* to grab the correct calenday-day element.
|
||||
* to grab the correct calendar-day element.
|
||||
*/
|
||||
const padding = currentMonth.querySelectorAll('.calendar-day-padding');
|
||||
const { day } = this.workingParts;
|
||||
@@ -814,7 +776,7 @@ export class Datetime implements ComponentInterface {
|
||||
* and focus it.
|
||||
*/
|
||||
const dayEl = currentMonth.querySelector(
|
||||
`.calendar-day:nth-of-type(${padding.length + day})`
|
||||
`.calendar-day-wrapper:nth-of-type(${padding.length + day}) .calendar-day`
|
||||
) as HTMLElement | null;
|
||||
if (dayEl) {
|
||||
dayEl.focus();
|
||||
@@ -908,6 +870,20 @@ export class Datetime implements ComponentInterface {
|
||||
const monthBox = month.getBoundingClientRect();
|
||||
if (Math.abs(monthBox.x - box.x) > 2) return;
|
||||
|
||||
/**
|
||||
* If we're force-rendering a month, assume we've
|
||||
* scrolled to that and return it.
|
||||
*
|
||||
* If forceRenderDate is ever used in a context where the
|
||||
* forced month is not immediately auto-scrolled to, this
|
||||
* should be updated to also check whether `month` has the
|
||||
* same month and year as the forced date.
|
||||
*/
|
||||
const { forceRenderDate } = this;
|
||||
if (forceRenderDate !== undefined) {
|
||||
return { month: forceRenderDate.month, year: forceRenderDate.year, day: forceRenderDate.day };
|
||||
}
|
||||
|
||||
/**
|
||||
* From here, we can determine if the start
|
||||
* month or the end month was scrolled into view.
|
||||
@@ -976,6 +952,10 @@ export class Datetime implements ComponentInterface {
|
||||
|
||||
calendarBodyRef.scrollLeft = workingMonth.clientWidth * (isRTL(this.el) ? -1 : 1);
|
||||
calendarBodyRef.style.removeProperty('overflow');
|
||||
|
||||
if (this.resolveForceDateScrolling) {
|
||||
this.resolveForceDateScrolling();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1193,13 +1173,21 @@ export class Datetime implements ComponentInterface {
|
||||
}
|
||||
|
||||
private processValue = (value?: string | string[] | null) => {
|
||||
const hasValue = value !== null && value !== undefined;
|
||||
const hasValue = value !== null && value !== undefined && (!Array.isArray(value) || value.length > 0);
|
||||
const valueToProcess = hasValue ? parseDate(value) : this.defaultParts;
|
||||
|
||||
const { minParts, maxParts } = this;
|
||||
const { minParts, maxParts, workingParts, el } = this;
|
||||
|
||||
this.warnIfIncorrectValueUsage();
|
||||
|
||||
/**
|
||||
* Return early if the value wasn't parsed correctly, such as
|
||||
* if an improperly formatted date string was provided.
|
||||
*/
|
||||
if (!valueToProcess) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Datetime should only warn of out of bounds values
|
||||
* if set by the user. If the `value` is undefined,
|
||||
@@ -1218,19 +1206,11 @@ export class Datetime implements ComponentInterface {
|
||||
* that the values don't necessarily have to be in order.
|
||||
*/
|
||||
const singleValue = Array.isArray(valueToProcess) ? valueToProcess[0] : valueToProcess;
|
||||
const targetValue = clampDate(singleValue, minParts, maxParts);
|
||||
|
||||
const { month, day, year, hour, minute } = clampDate(singleValue, minParts, maxParts);
|
||||
const { month, day, year, hour, minute } = targetValue;
|
||||
const ampm = parseAmPm(hour!);
|
||||
|
||||
this.setWorkingParts({
|
||||
month,
|
||||
day,
|
||||
year,
|
||||
hour,
|
||||
minute,
|
||||
ampm,
|
||||
});
|
||||
|
||||
/**
|
||||
* Since `activeParts` indicates a value that
|
||||
* been explicitly selected either by the
|
||||
@@ -1258,6 +1238,67 @@ export class Datetime implements ComponentInterface {
|
||||
*/
|
||||
this.activeParts = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Only animate if:
|
||||
* 1. We're using grid style (wheel style pickers should just jump to new value)
|
||||
* 2. The month and/or year actually changed, and both are defined (otherwise there's nothing to animate to)
|
||||
* 3. The calendar body is visible (prevents animation when in collapsed datetime-button, for example)
|
||||
* 4. The month/year picker is not open (since you wouldn't see the animation anyway)
|
||||
*/
|
||||
const didChangeMonth =
|
||||
(month !== undefined && month !== workingParts.month) || (year !== undefined && year !== workingParts.year);
|
||||
const bodyIsVisible = el.classList.contains('datetime-ready');
|
||||
const { isGridStyle, showMonthAndYear } = this;
|
||||
if (isGridStyle && didChangeMonth && bodyIsVisible && !showMonthAndYear) {
|
||||
this.animateToDate(targetValue);
|
||||
} else {
|
||||
/**
|
||||
* We only need to do this if we didn't just animate to a new month,
|
||||
* since that calls prevMonth/nextMonth which calls setWorkingParts for us.
|
||||
*/
|
||||
this.setWorkingParts({
|
||||
month,
|
||||
day,
|
||||
year,
|
||||
hour,
|
||||
minute,
|
||||
ampm,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private animateToDate = async (targetValue: DatetimeParts) => {
|
||||
const { workingParts } = this;
|
||||
|
||||
/**
|
||||
* Tell other render functions that we need to force the
|
||||
* target month to appear in place of the actual next/prev month.
|
||||
* Because this is a State variable, a rerender will be triggered
|
||||
* automatically, updating the rendered months.
|
||||
*/
|
||||
this.forceRenderDate = targetValue;
|
||||
|
||||
/**
|
||||
* Flag that we've started scrolling to the forced date.
|
||||
* The resolve function will be called by the datetime's
|
||||
* scroll listener when it's done updating everything.
|
||||
* This is a replacement for making prev/nextMonth async,
|
||||
* since the logic we're waiting on is in a listener.
|
||||
*/
|
||||
const forceDateScrollingPromise = new Promise<void>((resolve) => {
|
||||
this.resolveForceDateScrolling = resolve;
|
||||
});
|
||||
|
||||
/**
|
||||
* Animate smoothly to the forced month. This will also update
|
||||
* workingParts and correct the surrounding months for us.
|
||||
*/
|
||||
const targetMonthIsBefore = isBefore(targetValue, workingParts);
|
||||
targetMonthIsBefore ? this.prevMonth() : this.nextMonth();
|
||||
await forceDateScrollingPromise;
|
||||
this.resolveForceDateScrolling = undefined;
|
||||
this.forceRenderDate = undefined;
|
||||
};
|
||||
|
||||
componentWillLoad() {
|
||||
@@ -1286,16 +1327,18 @@ export class Datetime implements ComponentInterface {
|
||||
}
|
||||
}
|
||||
|
||||
this.processMinParts();
|
||||
this.processMaxParts();
|
||||
const hourValues = (this.parsedHourValues = convertToArrayOfNumbers(this.hourValues));
|
||||
const minuteValues = (this.parsedMinuteValues = convertToArrayOfNumbers(this.minuteValues));
|
||||
const monthValues = (this.parsedMonthValues = convertToArrayOfNumbers(this.monthValues));
|
||||
const yearValues = (this.parsedYearValues = convertToArrayOfNumbers(this.yearValues));
|
||||
const dayValues = (this.parsedDayValues = convertToArrayOfNumbers(this.dayValues));
|
||||
|
||||
const todayParts = (this.todayParts = parseDate(getToday()));
|
||||
const todayParts = (this.todayParts = parseDate(getToday())!);
|
||||
this.defaultParts = getClosestValidDate(todayParts, monthValues, dayValues, yearValues, hourValues, minuteValues);
|
||||
|
||||
this.processMinParts();
|
||||
this.processMaxParts();
|
||||
|
||||
this.processValue(this.value);
|
||||
|
||||
this.emitStyle();
|
||||
@@ -2042,7 +2085,7 @@ export class Datetime implements ComponentInterface {
|
||||
const { isActive, isToday, ariaLabel, ariaSelected, disabled, text } = getCalendarDayState(
|
||||
this.locale,
|
||||
referenceParts,
|
||||
this.activePartsClone,
|
||||
this.activeParts,
|
||||
this.todayParts,
|
||||
this.minParts,
|
||||
this.maxParts,
|
||||
@@ -2079,69 +2122,87 @@ export class Datetime implements ComponentInterface {
|
||||
dateStyle = getHighlightStyles(highlightedDates, dateIsoString, el);
|
||||
}
|
||||
|
||||
let dateParts = undefined;
|
||||
|
||||
// "Filler days" at the beginning of the grid should not get the calendar day
|
||||
// CSS parts added to them
|
||||
if (!isCalendarPadding) {
|
||||
dateParts = `calendar-day${isActive ? ' active' : ''}${isToday ? ' today' : ''}${
|
||||
isCalDayDisabled ? ' disabled' : ''
|
||||
}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
tabindex="-1"
|
||||
data-day={day}
|
||||
data-month={month}
|
||||
data-year={year}
|
||||
data-index={index}
|
||||
data-day-of-week={dayOfWeek}
|
||||
disabled={isCalDayDisabled}
|
||||
class={{
|
||||
'calendar-day-padding': isCalendarPadding,
|
||||
'calendar-day': true,
|
||||
'calendar-day-active': isActive,
|
||||
'calendar-day-today': isToday,
|
||||
}}
|
||||
style={
|
||||
dateStyle && {
|
||||
color: dateStyle.textColor,
|
||||
}
|
||||
}
|
||||
aria-hidden={isCalendarPadding ? 'true' : null}
|
||||
aria-selected={ariaSelected}
|
||||
aria-label={ariaLabel}
|
||||
onClick={() => {
|
||||
if (isCalendarPadding) {
|
||||
return;
|
||||
}
|
||||
<div class="calendar-day-wrapper">
|
||||
<button
|
||||
// We need to use !important for the inline styles here because
|
||||
// otherwise the CSS shadow parts will override these styles.
|
||||
// See https://github.com/WICG/webcomponents/issues/847
|
||||
// Both the CSS shadow parts and highlightedDates styles are
|
||||
// provided by the developer, but highlightedDates styles should
|
||||
// always take priority.
|
||||
ref={(el) => {
|
||||
if (el) {
|
||||
el.style.setProperty('color', `${dateStyle ? dateStyle.textColor : ''}`, 'important');
|
||||
el.style.setProperty(
|
||||
'background-color',
|
||||
`${dateStyle ? dateStyle.backgroundColor : ''}`,
|
||||
'important'
|
||||
);
|
||||
}
|
||||
}}
|
||||
tabindex="-1"
|
||||
data-day={day}
|
||||
data-month={month}
|
||||
data-year={year}
|
||||
data-index={index}
|
||||
data-day-of-week={dayOfWeek}
|
||||
disabled={isCalDayDisabled}
|
||||
class={{
|
||||
'calendar-day-padding': isCalendarPadding,
|
||||
'calendar-day': true,
|
||||
'calendar-day-active': isActive,
|
||||
'calendar-day-today': isToday,
|
||||
}}
|
||||
part={dateParts}
|
||||
aria-hidden={isCalendarPadding ? 'true' : null}
|
||||
aria-selected={ariaSelected}
|
||||
aria-label={ariaLabel}
|
||||
onClick={() => {
|
||||
if (isCalendarPadding) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setWorkingParts({
|
||||
...this.workingParts,
|
||||
month,
|
||||
day,
|
||||
year,
|
||||
});
|
||||
|
||||
// multiple only needs date info, so we can wipe out other fields like time
|
||||
if (multiple) {
|
||||
this.setActiveParts(
|
||||
{
|
||||
month,
|
||||
day,
|
||||
year,
|
||||
},
|
||||
isActive
|
||||
);
|
||||
} else {
|
||||
this.setActiveParts({
|
||||
...activePart,
|
||||
this.setWorkingParts({
|
||||
...this.workingParts,
|
||||
month,
|
||||
day,
|
||||
year,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class="calendar-day-highlight"
|
||||
style={{
|
||||
backgroundColor: dateStyle?.backgroundColor,
|
||||
|
||||
// multiple only needs date info, so we can wipe out other fields like time
|
||||
if (multiple) {
|
||||
this.setActiveParts(
|
||||
{
|
||||
month,
|
||||
day,
|
||||
year,
|
||||
},
|
||||
isActive
|
||||
);
|
||||
} else {
|
||||
this.setActiveParts({
|
||||
...activePart,
|
||||
month,
|
||||
day,
|
||||
year,
|
||||
});
|
||||
}
|
||||
}}
|
||||
></div>
|
||||
{text}
|
||||
</button>
|
||||
>
|
||||
{text}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@@ -2151,7 +2212,7 @@ export class Datetime implements ComponentInterface {
|
||||
private renderCalendarBody() {
|
||||
return (
|
||||
<div class="calendar-body ion-focusable" ref={(el) => (this.calendarBodyRef = el)} tabindex="0">
|
||||
{generateMonths(this.workingParts).map(({ month, year }) => {
|
||||
{generateMonths(this.workingParts, this.forceRenderDate).map(({ month, year }) => {
|
||||
return this.renderMonth(month, year);
|
||||
})}
|
||||
</div>
|
||||
@@ -2360,7 +2421,19 @@ export class Datetime implements ComponentInterface {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { name, value, disabled, el, color, readonly, showMonthAndYear, preferWheel, presentation, size } = this;
|
||||
const {
|
||||
name,
|
||||
value,
|
||||
disabled,
|
||||
el,
|
||||
color,
|
||||
readonly,
|
||||
showMonthAndYear,
|
||||
preferWheel,
|
||||
presentation,
|
||||
size,
|
||||
isGridStyle,
|
||||
} = this;
|
||||
const mode = getIonMode(this);
|
||||
const isMonthAndYearPresentation =
|
||||
presentation === 'year' || presentation === 'month' || presentation === 'month-year';
|
||||
@@ -2368,7 +2441,6 @@ export class Datetime implements ComponentInterface {
|
||||
const monthYearPickerOpen = showMonthAndYear && !isMonthAndYearPresentation;
|
||||
const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
|
||||
const hasWheelVariant = hasDatePresentation && preferWheel;
|
||||
const hasGrid = hasDatePresentation && !preferWheel;
|
||||
|
||||
renderHiddenInput(true, el, name, formatValue(value), disabled);
|
||||
|
||||
@@ -2387,7 +2459,7 @@ export class Datetime implements ComponentInterface {
|
||||
[`datetime-presentation-${presentation}`]: true,
|
||||
[`datetime-size-${size}`]: true,
|
||||
[`datetime-prefer-wheel`]: hasWheelVariant,
|
||||
[`datetime-grid`]: hasGrid,
|
||||
[`datetime-grid`]: isGridStyle,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -490,3 +490,40 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* This behavior does not differ across
|
||||
* directions.
|
||||
*/
|
||||
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('datetime: focus'), () => {
|
||||
test('should focus the selected day and then the day after', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-datetime value="2023-08-01"></ion-datetime>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
await page.waitForSelector('.datetime-ready');
|
||||
|
||||
const datetime = page.locator('ion-datetime');
|
||||
|
||||
const day = datetime.locator(`.calendar-day[data-day='1'][data-month='8']`);
|
||||
|
||||
await day.focus();
|
||||
await page.waitForChanges();
|
||||
|
||||
await expect(day).toBeFocused();
|
||||
await expect(datetime).toHaveScreenshot(screenshot(`datetime-focus-selected-calendar-day`));
|
||||
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await page.waitForChanges();
|
||||
|
||||
const nextDay = datetime.locator(`.calendar-day[data-day='2'][data-month='8']`);
|
||||
|
||||
await expect(nextDay).toBeFocused();
|
||||
await expect(datetime).toHaveScreenshot(screenshot(`datetime-focus-calendar-day`));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |