Merge branch 'main' into chore-update-from-main

This commit is contained in:
Brandy Carney
2024-05-10 17:29:30 -04:00
115 changed files with 16864 additions and 230 deletions

View File

@ -29,7 +29,7 @@ runs:
shell: bash
working-directory: ./packages/angular/test
- name: Install Dependencies
run: npm install
run: npm install --legacy-peer-deps # TODO(FW-6227): Remove legacy-peer-deps flag
shell: bash
working-directory: ./packages/angular/test/build/${{ inputs.app }}
- name: Sync Built Changes

View File

@ -13,6 +13,6 @@ jobs:
- name: 'Auto-assign issue'
uses: pozil/auto-assign-issue@65947009a243e6b3993edeef4e64df3ca85d760c # v1.14.0
with:
assignees: sean-perkins, brandyscarney, thetaPC
assignees: brandyscarney, thetaPC
numOfAssignee: 1
allowSelfAssign: false

View File

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

View File

@ -24,3 +24,9 @@ jobs:
# within the message.
subjectPatternError: |
The subject "{subject}" found in the pull request title "{title}" didn't match the configured pattern. Please ensure that the subject doesn't start with an uppercase character.
# If the PR contains one of these newline-delimited labels, the
# validation is skipped. If you want to rerun the validation when
# labels change, you might want to use the `labeled` and `unlabeled`
# event triggers in your workflow.
ignoreLabels: |
release

View File

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

View File

@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [8.1.1](https://github.com/ionic-team/ionic-framework/compare/v8.1.0...v8.1.1) (2024-05-08)
### Bug Fixes
* **angular:** add formatOptions property to standalone datetime ([#29468](https://github.com/ionic-team/ionic-framework/issues/29468)) ([bb1db52](https://github.com/ionic-team/ionic-framework/commit/bb1db52567e0884a896f9ccd76c27540b52f5e48)), closes [#29464](https://github.com/ionic-team/ionic-framework/issues/29464)
* **angular:** persist select disabled state in item ([#29448](https://github.com/ionic-team/ionic-framework/issues/29448)) ([dfb72d7](https://github.com/ionic-team/ionic-framework/commit/dfb72d7ea06e28d76069b23eb90c3426181b7c4c)), closes [#29234](https://github.com/ionic-team/ionic-framework/issues/29234)
* **angular:** set active segment button when dynamically changing items ([#29418](https://github.com/ionic-team/ionic-framework/issues/29418)) ([ee83388](https://github.com/ionic-team/ionic-framework/commit/ee833881da3ecaa0a9153397f0c7e62c1923f19c)), closes [#29414](https://github.com/ionic-team/ionic-framework/issues/29414)
* **radio:** persist checked state when items are updated in radio-group ([#29457](https://github.com/ionic-team/ionic-framework/issues/29457)) ([7ea14ae](https://github.com/ionic-team/ionic-framework/commit/7ea14ae41eb27f2a58952bd27d91ef4c77bb6a0c)), closes [#29442](https://github.com/ionic-team/ionic-framework/issues/29442)
# [8.1.0](https://github.com/ionic-team/ionic-framework/compare/v8.0.2...v8.1.0) (2024-05-01)

View File

@ -3,6 +3,19 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [8.1.1](https://github.com/ionic-team/ionic-framework/compare/v8.1.0...v8.1.1) (2024-05-08)
### Bug Fixes
* **angular:** persist select disabled state in item ([#29448](https://github.com/ionic-team/ionic-framework/issues/29448)) ([dfb72d7](https://github.com/ionic-team/ionic-framework/commit/dfb72d7ea06e28d76069b23eb90c3426181b7c4c)), closes [#29234](https://github.com/ionic-team/ionic-framework/issues/29234)
* **angular:** set active segment button when dynamically changing items ([#29418](https://github.com/ionic-team/ionic-framework/issues/29418)) ([ee83388](https://github.com/ionic-team/ionic-framework/commit/ee833881da3ecaa0a9153397f0c7e62c1923f19c)), closes [#29414](https://github.com/ionic-team/ionic-framework/issues/29414)
* **radio:** persist checked state when items are updated in radio-group ([#29457](https://github.com/ionic-team/ionic-framework/issues/29457)) ([7ea14ae](https://github.com/ionic-team/ionic-framework/commit/7ea14ae41eb27f2a58952bd27d91ef4c77bb6a0c)), closes [#29442](https://github.com/ionic-team/ionic-framework/issues/29442)
# [8.1.0](https://github.com/ionic-team/ionic-framework/compare/v8.0.2...v8.1.0) (2024-05-01)

View File

@ -1,12 +1,12 @@
{
"name": "@ionic/core",
"version": "8.1.0",
"version": "8.1.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/core",
"version": "8.1.0",
"version": "8.1.1",
"license": "MIT",
"dependencies": {
"@stencil/core": "^4.17.2",

View File

@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "8.1.0",
"version": "8.1.1",
"description": "Base components for Ionic",
"keywords": [
"ionic",

View File

@ -53,4 +53,7 @@ if (requestHeaded && !hasHeadedConfigFiles) {
console.warn(chalk.yellow.bold('\n⚠ You are running tests in headed mode, but one or more of your headed config files was not found.\nPlease ensure that both docker-display.txt and docker-display-volume.txt have been created in the correct location.\n'));
}
execa('docker', args, { shell: true, stdio: 'inherit' });
const res = await execa('docker', args, { shell: true, stdio: 'inherit' });
// If underlying scripts failed this whole process should fail too
process.exit(res.exitCode);

View File

@ -3,10 +3,11 @@ import { configs, test } from '@utils/test/playwright';
configs().forEach(({ config, title }) => {
test.describe(title('accordion: a11y'), () => {
test('accordions should be keyboard navigable', async ({ page, skip, browserName }) => {
// TODO(FW-1764): remove skip once issue is resolved
// TODO(ROU-8157): remove skip once the keyboard navigation is working again
test.skip('accordions should be keyboard navigable', async ({ page, skip, browserName }) => {
// TODO(ROU-5358): remove skip once issue is resolved
skip.browser('firefox', 'https://github.com/ionic-team/ionic-framework/issues/25070');
// TODO (FW-2979)
// TODO (ROU-5437)
skip.browser('webkit', 'Safari 16 only allows text fields and pop-up menus to be focused.');
await page.goto(`/src/components/accordion/test/a11y`, config);

View File

@ -2,7 +2,7 @@
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Accordion - Basic</title>
<title>Accordion - Standalone</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
@ -119,6 +119,7 @@
outline: none;
text-align: left;
padding: 20px 16px;
color: black;
}
.custom-accordion-content {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -11,7 +11,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ config, screenshot, t
test('should update the active item', async ({ page }) => {
const breadcrumbItems = page.locator('ion-breadcrumb');
const addItemButton = page.locator('ion-button#add-btn');
const addItemButton = page.locator('#add-btn');
await expect(breadcrumbItems).toHaveCount(4);

View File

@ -7,7 +7,7 @@ configs().forEach(({ title, screenshot, config }) => {
await page.goto('/src/components/datetime/test/position', config);
const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent');
const openDateTimeBtn = page.locator('ion-button#open-datetime');
const openDateTimeBtn = page.locator('#open-datetime');
await openDateTimeBtn.click();
await ionPopoverDidPresent.next();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -74,7 +74,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, c
await ionLoadingDidPresent.next();
const button = page.locator('ion-loading ion-button');
const button = page.locator('ion-loading button');
if (browserName === 'webkit') {
await page.keyboard.down('Alt');

View File

@ -29,7 +29,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
test('should trap focus', async ({ page, skip, browserName }) => {
skip.browser('firefox', 'Firefox incorrectly allows keyboard focus to move to ion-content');
// TODO (FW-2979)
// TODO (ROU-5437)
skip.browser('webkit', 'Safari 16 only allows text fields and pop-up menus to be focused.');
const ionDidOpen = await page.spyOnEvent('ionDidOpen');

View File

@ -24,7 +24,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await modal.evaluate((modal: HTMLIonModalElement) => {
modal.remove();
document.querySelector('ion-button')?.insertAdjacentElement('afterend', modal);
document.querySelector('button')?.insertAdjacentElement('afterend', modal);
});
await page.waitForChanges();

View File

@ -18,7 +18,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
test.describe('pushing a new page', () => {
test('should render the pushed component', async ({ page }) => {
const pageTwoButton = page.locator('ion-button:has-text("Go to Page Two")');
const pageTwoButton = page.locator('button:has-text("Go to Page Two")');
const pageOne = page.locator('page-one');
const pageTwo = page.locator('page-two');
@ -32,7 +32,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
});
test('should render the back button', async ({ page }) => {
const pageTwoButton = page.locator('ion-button:has-text("Go to Page Two")');
const pageTwoButton = page.locator('button:has-text("Go to Page Two")');
const pageTwoBackButton = page.locator('page-two ion-back-button');
await pageTwoButton.click();
@ -45,7 +45,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
test('back button should pop to the previous page', async ({ page }) => {
const pageOne = page.locator('page-one');
const pageTwo = page.locator('page-two');
const pageTwoButton = page.locator('ion-button:has-text("Go to Page Two")');
const pageTwoButton = page.locator('button:has-text("Go to Page Two")');
const pageTwoBackButton = page.locator('page-two ion-back-button');
await pageTwoButton.click();
@ -65,8 +65,8 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
const pageTwo = page.locator('page-two');
const pageThree = page.locator('page-three');
const pageTwoButton = page.locator('ion-button:has-text("Go to Page Two")');
const pageThreeButton = page.locator('ion-button:has-text("Go to Page Three")');
const pageTwoButton = page.locator('button:has-text("Go to Page Two")');
const pageThreeButton = page.locator('button:has-text("Go to Page Three")');
await pageTwoButton.click();
await page.waitForChanges();

View File

@ -16,8 +16,8 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
const pageOne = page.locator('page-one');
const pageTwo = page.locator('page-two');
const pageTwoButton = page.locator('ion-button:has-text("Go to Page 2")');
const pageTwoTwoButton = page.locator('ion-button:has-text("Go to Page 2.2")');
const pageTwoButton = page.locator('button:has-text("Go to Page 2")');
const pageTwoTwoButton = page.locator('button:has-text("Go to Page 2.2")');
await pageTwoButton.click();
await page.waitForChanges();
@ -35,7 +35,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await expect(pageTwoOne).toHaveCount(1);
await expect(pageTwoTwo).toBeVisible();
const pageThreeButton = page.locator('ion-button:has-text("Go to Page 3")');
const pageThreeButton = page.locator('button:has-text("Go to Page 3")');
await pageThreeButton.click();
await page.waitForChanges();
@ -49,8 +49,8 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
test.describe('back button', () => {
test('should work with nested ion-nav', async ({ page }) => {
const pageTwoButton = page.locator('ion-button:has-text("Go to Page 2")');
const pageTwoTwoButton = page.locator('ion-button:has-text("Go to Page 2.2")');
const pageTwoButton = page.locator('button:has-text("Go to Page 2")');
const pageTwoTwoButton = page.locator('button:has-text("Go to Page 2.2")');
await pageTwoButton.click();
await page.waitForChanges();
@ -61,7 +61,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await pageTwoTwoButton.click();
await page.waitForChanges();
const pageThreeButton = page.locator('ion-button:has-text("Go to Page 3")');
const pageThreeButton = page.locator('button:has-text("Go to Page 3")');
const pageThreeBackButton = page.locator('page-three ion-back-button');
await pageThreeButton.click();

View File

@ -24,8 +24,8 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
const pageOne = page.locator('page-one');
const pageTwo = page.locator('page-two');
const pageOneButton = page.locator('ion-button:has-text("Go to Page One")');
const pageTwoButton = page.locator('ion-button:has-text("Go to Page Two")');
const pageOneButton = page.locator('button:has-text("Go to Page One")');
const pageTwoButton = page.locator('button:has-text("Go to Page Two")');
await pageOneButton.click();
await page.waitForChanges();
@ -45,7 +45,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
});
test('should render the back button', async ({ page }) => {
const pageOneButton = page.locator('ion-button:has-text("Go to Page One")');
const pageOneButton = page.locator('button:has-text("Go to Page One")');
const pageOneBackButton = page.locator('page-one ion-back-button');
await pageOneButton.click();
@ -59,7 +59,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
const pageRoot = page.locator('page-root');
const pageOne = page.locator('page-one');
const pageOneButton = page.locator('ion-button:has-text("Go to Page One")');
const pageOneButton = page.locator('button:has-text("Go to Page One")');
const pageOneBackButton = page.locator('page-one ion-back-button');
await pageOneButton.click();
@ -80,9 +80,9 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
const pageTwo = page.locator('page-two');
const pageThree = page.locator('page-three');
const pageOneButton = page.locator('ion-button:has-text("Go to Page One")');
const pageTwoButton = page.locator('ion-button:has-text("Go to Page Two")');
const pageThreeButton = page.locator('ion-button:has-text("Go to Page Three")');
const pageOneButton = page.locator('button:has-text("Go to Page One")');
const pageTwoButton = page.locator('button:has-text("Go to Page Two")');
const pageThreeButton = page.locator('button:has-text("Go to Page Three")');
await pageOneButton.click();
await page.waitForChanges();

View File

@ -105,7 +105,8 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
);
});
test('tabbing should correctly move focus between columns', async ({ page }) => {
// TODO(FW-6232): remove this skip when tabbing is working properly
test.skip('tabbing should correctly move focus between columns', async ({ page }) => {
const firstColumn = page.locator('ion-picker-column#first');
const secondColumn = page.locator('ion-picker-column#second');
@ -120,7 +121,8 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await expect(secondColumn).toBeFocused();
});
test('tabbing should correctly move focus back', async ({ page }) => {
// TODO(FW-6232): remove this skip when tabbing is working properly
test.skip('tabbing should correctly move focus back', async ({ page }) => {
const firstColumn = page.locator('ion-picker-column#first');
const secondColumn = page.locator('ion-picker-column#second');

View File

@ -7,7 +7,8 @@ import type { E2ELocator } from '@utils/test/playwright/page/utils/locator';
*/
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('picker: keyboard entry'), () => {
test('should scroll to and update the value prop for a single column', async ({ page }) => {
// TODO(FW-6232): remove this skip when keyboard entry is working properly
test.skip('should scroll to and update the value prop for a single column', async ({ page }) => {
await page.setContent(
`
<ion-picker>
@ -122,7 +123,8 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await expect(secondColumn).toHaveJSProperty('value', 24);
});
test('should select 00', async ({ page }) => {
// TODO(FW-6232): remove this skip when keyboard entry is working properly
test.skip('should select 00', async ({ page }) => {
await page.setContent(
`
<ion-picker>

View File

@ -40,7 +40,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await popover.evaluate((popover: HTMLIonPopoverElement) => {
popover.remove();
document.querySelector('ion-button')?.insertAdjacentElement('afterend', popover);
document.querySelector('button')?.insertAdjacentElement('afterend', popover);
});
await page.waitForChanges();

View File

@ -112,6 +112,18 @@ export class Radio implements ComponentInterface {
*/
@Event() ionBlur!: EventEmitter<void>;
componentDidLoad() {
/**
* The value may be `undefined` if it
* gets set before the radio is
* rendered. This ensures that the radio
* is checked if the value matches. This
* happens most often when Angular is
* rendering the radio.
*/
this.updateState();
}
/** @internal */
@Method()
async setFocus(ev: globalThis.Event) {

View File

@ -20,7 +20,7 @@ const checkedOptions: SelectPopoverOption[] = [
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('select-popover: basic'), () => {
test.beforeEach(({ browserName }) => {
test.skip(browserName === 'webkit', 'https://ionic-cloud.atlassian.net/browse/FW-2979');
test.skip(browserName === 'webkit', 'ROU-5437');
});
test.describe('single selection', () => {
@ -55,7 +55,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
});
test('pressing Space on a selected option should dismiss the popover', async ({ browserName }) => {
test.skip(browserName === 'firefox', 'Same behavior as https://ionic-cloud.atlassian.net/browse/FW-2979');
test.skip(browserName === 'firefox', 'Same behavior as ROU-5437');
await selectPopoverPage.setup(config, checkedOptions, false);

View File

@ -244,10 +244,6 @@ export class Select implements ComponentInterface {
this.ionChange.emit({ value });
}
componentWillLoad() {
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
}
async connectedCallback() {
const { el } = this;
@ -273,6 +269,24 @@ export class Select implements ComponentInterface {
});
}
componentWillLoad() {
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
}
componentDidLoad() {
/**
* If any of the conditions that trigger the styleChanged callback
* are met on component load, it is possible the event emitted
* prior to a parent web component registering an event listener.
*
* To ensure the parent web component receives the event, we
* emit the style event again after the component has loaded.
*
* This is often seen in Angular with the `dist` output target.
*/
this.emitStyle();
}
disconnectedCallback() {
if (this.mutationO) {
this.mutationO.disconnect();

View File

@ -40,7 +40,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe('select: popover', () => {
test('it should open a popover select', async ({ page, skip }) => {
// TODO (FW-2979)
// TODO (ROU-5437)
skip.browser('webkit', 'Safari 16 only allows text fields and pop-up menus to be focused.');
const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent');

View File

@ -4,7 +4,7 @@ import { configs, test } from '@utils/test/playwright';
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('select: disabled options'), () => {
test('should not focus a disabled option when no value is set', async ({ page, skip }) => {
// TODO (FW-2979)
// TODO (ROU-5437)
skip.browser('webkit', 'Safari 16 only allows text fields and pop-up menus to be focused.');
test.info().annotations.push({

View File

@ -71,16 +71,16 @@ $alert-ios-radio-label-text-color-checked: $alert-ios-button-text-color !default
The abundance of Sass variables currently in Ionic Framework is a result of their historical usage, being used to rebuild the CSS and customize Ionic Framework components.
The comments for Sass variables are also still visible today in [v7.7.0](https://github.com/ionic-team/ionic-framework/blob/v7.7.0/core/src/components/alert/alert.ios.vars.scss), even though they are no longer used by any documentation generators:
The comments for Sass variables are also still visible today in [v8.1.0](https://github.com/ionic-team/ionic-framework/blob/v8.1.0/core/src/components/alert/alert.ios.vars.scss), even though they are no longer used by any documentation generators:
```scss
// alert.ios.vars.scss
/// @prop - Max width of the alert
$alert-ios-max-width: dynamic-font-clamp(1, 270px, 1.2) !default;
$alert-ios-max-width: dynamic-font-clamp(1, 270px, 1.2);
/// @prop - Border radius of the alert
$alert-ios-border-radius: 13px !default;
$alert-ios-border-radius: 13px;
```
These comments aren't necessary when the naming describes its use thoroughly. The comments for the variables above do not need to be there, as it is fairly obvious what they are used for.
@ -91,7 +91,7 @@ However, the comment for the following variable might be helpful in explaining w
// action-sheet.ios.vars.scss
/// @prop - Font weight of the action sheet title when it has a sub title
$action-sheet-ios-title-with-sub-title-font-weight: 600 !default;
$action-sheet-ios-title-with-sub-title-font-weight: 600;
```
It could be argued though that the comment doesn't really help, as seeing the variable in use will explain its purpose the best. Additionally, this is an example of a variable that isn't necessary, given it is only used in one place, which is why it is so specific in the first place.
@ -105,22 +105,20 @@ There are two things that need to be outlined here: when we should use comments
We should update the comments for Sass variables in one of the following ways:
1. If we don't intend to ever publicly document the Sass variables again, we should update the comments to remove the syntax that was added for documentation generation:
```diff
// alert.ios.vars.scss
-/// @prop - Border radius of the alert
+// Border radius of the alert
$alert-ios-border-radius: 13px !default;
$alert-ios-border-radius: 13px;
```
2. If we don't find the comments to be helpful, and want to stick with keeping the variable names specific, we should remove the comments entirely:
```diff
// alert.ios.vars.scss
-/// @prop - Border radius of the alert
$alert-ios-border-radius: 13px !default;
$alert-ios-border-radius: 13px;
```
3. If we find the comments to be helpful for certain variables or situations, like when there are math calculations involved, we should keep only the comments that are necessary to explain what is going on:
@ -135,7 +133,7 @@ We should update the comments for Sass variables in one of the following ways:
* a hairline (<1px) width, this will cause subpixel rendering
* differences across browsers.
*/
$alert-ios-button-height: dynamic-font-min(1, 44px) !default;
$alert-ios-button-height: dynamic-font-min(1, 44px);
```
### Variables
@ -163,11 +161,11 @@ Example of global variables:
```scss
// ionic.globals.scss
$font-family-base: var(--ion-font-family, inherit) !default;
$font-family-base: var(--ion-font-family, inherit);
$hairlines-width: 0.55px !default;
$hairlines-width: 0.55px;
$placeholder-opacity: 0.6 !default;
$placeholder-opacity: 0.6;
```
#### ✅ Theming
@ -179,32 +177,32 @@ Example of theme variables:
```scss
// ionic.theme.default.scss
$background-color-value: #fff !default;
$background-color-rgb-value: 255, 255, 255 !default;
$background-color-value: #fff;
$background-color-rgb-value: 255, 255, 255;
$text-color-value: #000 !default;
$text-color-rgb-value: 0, 0, 0 !default;
$text-color-value: #000;
$text-color-rgb-value: 0, 0, 0;
$background-color: var(
--ion-background-color,
$background-color-value
) !default;
);
$background-color-rgb: var(
--ion-background-color-rgb,
$background-color-rgb-value
) !default;
$text-color: var(--ion-text-color, $text-color-value) !default;
$text-color-rgb: var(--ion-text-color-rgb, $text-color-rgb-value) !default;
);
$text-color: var(--ion-text-color, $text-color-value);
$text-color-rgb: var(--ion-text-color-rgb, $text-color-rgb-value);
```
```scss
// ionic.theme.default.ios.scss
$backdrop-ios-color: var(--ion-backdrop-color, #000) !default;
$backdrop-ios-color: var(--ion-backdrop-color, #000);
$overlay-ios-background-color: var(
--ion-overlay-background-color,
var(--ion-color-step-100, #f9f9f9)
) !default;
);
```
#### ✅ Reusable values
@ -227,10 +225,10 @@ Example of reusable values:
// alert.ios.vars.scss
/// @prop - Padding end of the alert head
$alert-ios-head-padding-end: 16px !default;
$alert-ios-head-padding-end: 16px;
/// @prop - Padding start of the alert head
$alert-ios-head-padding-start: $alert-ios-head-padding-end !default;
$alert-ios-head-padding-start: $alert-ios-head-padding-end;
```
```scss
@ -259,10 +257,10 @@ $alert-ios-head-padding-start: $alert-ios-head-padding-end !default;
// alert.ios.vars.scss
/// @prop - Padding top of the alert head
$alert-ios-head-padding-top: 12px !default;
$alert-ios-head-padding-top: 12px;
/// @prop - Padding bottom of the alert head
$alert-ios-head-padding-bottom: 7px !default;
$alert-ios-head-padding-bottom: 7px;
```
```scss
@ -303,10 +301,10 @@ $global-md-item-padding-start: $global-md-item-padding-end;
@import "../../themes/native/native.globals.md";
/// @prop - Padding end for the item content
$item-md-padding-end: $global-md-item-padding-end !default;
$item-md-padding-end: $global-md-item-padding-end;
/// @prop - Padding start for the item content
$item-md-padding-start: $global-md-item-padding-start !default;
$item-md-padding-start: $global-md-item-padding-start;
```
```scss
@ -315,10 +313,10 @@ $item-md-padding-start: $global-md-item-padding-start !default;
@import "../../themes/native/native.globals.md";
/// @prop - Padding start for the divider
$item-divider-md-padding-start: $global-md-item-padding-start !default;
$item-divider-md-padding-start: $global-md-item-padding-start;
/// @prop - Padding end for the divider
$item-divider-md-padding-end: $global-md-item-padding-end !default;
$item-divider-md-padding-end: $global-md-item-padding-end;
```
</td>
@ -338,10 +336,10 @@ $item-divider-md-padding-end: $global-md-item-padding-end !default;
@import "../../themes/native/native.globals.md";
/// @prop - Padding end for the item content
$item-md-padding-end: 16px !default;
$item-md-padding-end: 16px;
/// @prop - Padding start for the item content
$item-md-padding-start: 16px !default;
$item-md-padding-start: 16px;
```
```scss
@ -351,10 +349,10 @@ $item-md-padding-start: 16px !default;
@import "../item/item.md.vars";
/// @prop - Padding start for the divider
$item-divider-md-padding-start: $item-md-padding-start !default;
$item-divider-md-padding-start: $item-md-padding-start;
/// @prop - Padding end for the divider
$item-divider-md-padding-end: $item-md-padding-end !default;
$item-divider-md-padding-end: $item-md-padding-end;
```
</td>
@ -380,8 +378,8 @@ $screen-breakpoints: (
sm: 576px,
md: 768px,
lg: 992px,
xl: 1200px,
) !default;
xl: 1200px
);
```
#### ✅ Dynamic calculations
@ -429,7 +427,7 @@ $chip-avatar-size: math.div(24em, $chip-base-font-size);
// alert.vars.scss
/// @prop - Font size of the alert button
$alert-button-font-size: dynamic-font(14px) !default;
$alert-button-font-size: dynamic-font(14px);
```
</td>
@ -456,7 +454,7 @@ For example, the color of the label changes when focused in `md` mode. However,
// label.md.vars.scss
/// @prop - Text color of the stacked/floating label when it is focused
$label-md-text-color-focused: ion-color(primary, base) !default;
$label-md-text-color-focused: ion-color(primary, base);
```
```scss
@ -485,7 +483,7 @@ $label-md-text-color-focused: ion-color(primary, base) !default;
// label.ios.vars.scss
/// @prop - Text color of the stacked/floating label when it is focused
$label-ios-text-color-focused: null !default;
$label-ios-text-color-focused: null;
```
```scss
@ -537,7 +535,7 @@ A text alignment property should not be stored in a Sass variable, even if it is
// action-sheet.ios.vars.scss
/// @prop - Text align of the action sheet
$action-sheet-ios-text-align: center !default;
$action-sheet-ios-text-align: center;
```
```scss
@ -597,10 +595,10 @@ Variables should not be used when they are structural changes of an element. Thi
// alert.ios.vars.scss
/// @prop - Flex wrap of the alert button group
$alert-ios-button-group-flex-wrap: wrap !default;
$alert-ios-button-group-flex-wrap: wrap;
/// @prop - Flex of the alert button
$alert-ios-button-flex: 1 1 auto !default;
$alert-ios-button-flex: 1 1 auto;
```
```scss
@ -662,13 +660,13 @@ We shouldn't use variables for changing things such as `font-size` or `font-weig
// action-sheet.ios.vars.scss
/// @prop - Font size of the action sheet title
$action-sheet-ios-title-font-size: dynamic-font-min(1, 13px) !default;
$action-sheet-ios-title-font-size: dynamic-font-min(1, 13px);
/// @prop - Font weight of the action sheet title
$action-sheet-ios-title-font-weight: 400 !default;
$action-sheet-ios-title-font-weight: 400;
/// @prop - Font size of the action sheet sub title
$action-sheet-ios-sub-title-font-size: dynamic-font-min(1, 13px) !default;
$action-sheet-ios-sub-title-font-size: dynamic-font-min(1, 13px);
```
```scss

View File

@ -3,5 +3,5 @@
"core",
"packages/*"
],
"version": "8.1.0"
"version": "8.1.1"
}

View File

@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [8.1.1](https://github.com/ionic-team/ionic-framework/compare/v8.1.0...v8.1.1) (2024-05-08)
**Note:** Version bump only for package @ionic/angular-server
# [8.1.0](https://github.com/ionic-team/ionic-framework/compare/v8.0.2...v8.1.0) (2024-05-01)
**Note:** Version bump only for package @ionic/angular-server

View File

@ -1,15 +1,15 @@
{
"name": "@ionic/angular-server",
"version": "8.1.0",
"version": "8.1.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular-server",
"version": "8.1.0",
"version": "8.1.1",
"license": "MIT",
"dependencies": {
"@ionic/core": "^8.1.0"
"@ionic/core": "^8.1.1"
},
"devDependencies": {
"@angular-eslint/eslint-plugin": "^16.0.0",
@ -1119,9 +1119,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.1.0.tgz",
"integrity": "sha512-CTa0JZA7T0Je7HiAinj/kjpxChQYDvitFBqMtNv88nOJn1KerbUKmV2JfQ0quNFneN8z/bBdNOaKc8T++cyDyg==",
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.1.1.tgz",
"integrity": "sha512-ooB8gZtBuLeoHlE1wUvrXI0nyt26f46rPuyNh2Q062QniR4Ur5kqBXqWjUtdVUZqZvaElCbZJk3up+MQavoIMA==",
"dependencies": {
"@stencil/core": "^4.17.2",
"ionicons": "^7.2.2",
@ -7011,9 +7011,9 @@
"dev": true
},
"@ionic/core": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.1.0.tgz",
"integrity": "sha512-CTa0JZA7T0Je7HiAinj/kjpxChQYDvitFBqMtNv88nOJn1KerbUKmV2JfQ0quNFneN8z/bBdNOaKc8T++cyDyg==",
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.1.1.tgz",
"integrity": "sha512-ooB8gZtBuLeoHlE1wUvrXI0nyt26f46rPuyNh2Q062QniR4Ur5kqBXqWjUtdVUZqZvaElCbZJk3up+MQavoIMA==",
"requires": {
"@stencil/core": "^4.17.2",
"ionicons": "^7.2.2",

View File

@ -1,6 +1,6 @@
{
"name": "@ionic/angular-server",
"version": "8.1.0",
"version": "8.1.1",
"description": "Angular SSR Module for Ionic",
"keywords": [
"ionic",
@ -62,6 +62,6 @@
},
"prettier": "@ionic/prettier-config",
"dependencies": {
"@ionic/core": "^8.1.0"
"@ionic/core": "^8.1.1"
}
}

View File

@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [8.1.1](https://github.com/ionic-team/ionic-framework/compare/v8.1.0...v8.1.1) (2024-05-08)
### Bug Fixes
* **angular:** add formatOptions property to standalone datetime ([#29468](https://github.com/ionic-team/ionic-framework/issues/29468)) ([bb1db52](https://github.com/ionic-team/ionic-framework/commit/bb1db52567e0884a896f9ccd76c27540b52f5e48)), closes [#29464](https://github.com/ionic-team/ionic-framework/issues/29464)
# [8.1.0](https://github.com/ionic-team/ionic-framework/compare/v8.0.2...v8.1.0) (2024-05-01)

View File

@ -1,15 +1,15 @@
{
"name": "@ionic/angular",
"version": "8.1.0",
"version": "8.1.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular",
"version": "8.1.0",
"version": "8.1.1",
"license": "MIT",
"dependencies": {
"@ionic/core": "^8.1.0",
"@ionic/core": "^8.1.1",
"ionicons": "^7.0.0",
"jsonc-parser": "^3.0.0",
"tslib": "^2.3.0"
@ -1398,9 +1398,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.1.0.tgz",
"integrity": "sha512-CTa0JZA7T0Je7HiAinj/kjpxChQYDvitFBqMtNv88nOJn1KerbUKmV2JfQ0quNFneN8z/bBdNOaKc8T++cyDyg==",
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.1.1.tgz",
"integrity": "sha512-ooB8gZtBuLeoHlE1wUvrXI0nyt26f46rPuyNh2Q062QniR4Ur5kqBXqWjUtdVUZqZvaElCbZJk3up+MQavoIMA==",
"dependencies": {
"@stencil/core": "^4.17.2",
"ionicons": "^7.2.2",
@ -9820,9 +9820,9 @@
"dev": true
},
"@ionic/core": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.1.0.tgz",
"integrity": "sha512-CTa0JZA7T0Je7HiAinj/kjpxChQYDvitFBqMtNv88nOJn1KerbUKmV2JfQ0quNFneN8z/bBdNOaKc8T++cyDyg==",
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.1.1.tgz",
"integrity": "sha512-ooB8gZtBuLeoHlE1wUvrXI0nyt26f46rPuyNh2Q062QniR4Ur5kqBXqWjUtdVUZqZvaElCbZJk3up+MQavoIMA==",
"requires": {
"@stencil/core": "^4.17.2",
"ionicons": "^7.2.2",

View File

@ -1,6 +1,6 @@
{
"name": "@ionic/angular",
"version": "8.1.0",
"version": "8.1.1",
"description": "Angular specific wrappers for @ionic/core",
"keywords": [
"ionic",
@ -47,7 +47,7 @@
}
},
"dependencies": {
"@ionic/core": "^8.1.0",
"@ionic/core": "^8.1.1",
"ionicons": "^7.0.0",
"jsonc-parser": "^3.0.0",
"tslib": "^2.3.0"

View File

@ -24,6 +24,7 @@ const DATETIME_INPUTS = [
'disabled',
'doneText',
'firstDayOfWeek',
'formatOptions',
'highlightedDates',
'hourCycle',
'hourValues',

View File

@ -0,0 +1,156 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"test-app": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"prefix": "app",
"schematics": {},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/test-app/browser",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"buildOptimizer": true,
"assets": [
"src/favicon.ico",
{
"glob": "**/*",
"input": "src/assets",
"output": "assets"
},
{
"glob": "**/*.svg",
"input": "node_modules/ionicons/dist/ionicons/svg",
"output": "./svg"
}
],
"styles": ["src/styles.css"],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": true,
"progress": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb"
}
]
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"buildTarget": "test-app:build"
},
"configurations": {
"production": {
"buildTarget": "test-app:build:production"
},
"development": {
"buildTarget": "test-app:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "test-app:build"
}
},
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
}
},
"server": {
"builder": "@angular-devkit/build-angular:server",
"options": {
"outputPath": "dist/test-app/server",
"main": "server.ts",
"tsConfig": "tsconfig.server.json"
},
"configurations": {
"production": {
"outputHashing": "media",
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"sourceMap": false,
"optimization": true
}
}
},
"serve-ssr": {
"builder": "@angular-devkit/build-angular:ssr-dev-server",
"options": {
"browserTarget": "test-app:build",
"serverTarget": "test-app:server"
},
"configurations": {
"production": {
"browserTarget": "test-app:build:production",
"serverTarget": "test-app:server:production"
}
}
},
"prerender": {
"builder": "@angular-devkit/build-angular:prerender",
"options": {
"browserTarget": "test-app:build:production",
"serverTarget": "test-app:server:production",
"routes": []
}
}
}
}
},
"cli": {
"schematicCollections": ["@angular-eslint/schematics"],
"cache": {
"enabled": false
}
}
}

View File

@ -0,0 +1,5 @@
it("should be on Angular 18", () => {
cy.visit('/lazy');
cy.get('ion-title').contains('Angular 18');
});

View File

@ -0,0 +1,8 @@
it("binding route data to inputs should work", () => {
cy.visit('/lazy/version-test/bind-route/test?query=test');
cy.get('#route-params').contains('test');
cy.get('#query-params').contains('test');
cy.get('#data').contains('data:bindToComponentInputs');
cy.get('#resolve').contains('resolve:bindToComponentInputs');
});

View File

@ -0,0 +1,20 @@
describe('Modal Nav Params', () => {
beforeEach(() => {
cy.visit('/lazy/version-test/modal-nav-params');
});
it('should assign the rootParams when presented in a modal multiple times', () => {
cy.contains('Open Modal').click();
cy.get('ion-modal').should('exist').should('be.visible');
cy.get('ion-modal').contains('OK');
cy.contains("Close").click();
cy.get('ion-modal').should('not.be.visible');
cy.contains('Open Modal').click();
cy.get('ion-modal').should('exist').should('be.visible');
cy.get('ion-modal').contains('OK').should('exist');
});
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,69 @@
{
"name": "ionic-angular-test-app",
"version": "0.0.0",
"private": true,
"scripts": {
"ng": "ng",
"start": "ng serve",
"sync:build": "sh scripts/build-ionic.sh",
"sync": "sh scripts/sync.sh",
"build": "ng build --configuration production --no-progress",
"lint": "ng lint",
"serve:ssr": "node dist/test-app/server/main.js",
"build:ssr": "ng build --prod && ng run test-app:server:production",
"dev:ssr": "ng run test-app:serve-ssr",
"prerender": "ng run test-app:prerender",
"cy.open": "cypress open",
"cy.run": "cypress run",
"test": "concurrently \"npm run start -- --configuration production\" \"wait-on http-get://localhost:4200 && npm run cy.run\" --kill-others --success first",
"test.watch": "concurrently \"npm run start\" \"wait-on http-get://localhost:4200 && npm run cy.open\" --kill-others --success first"
},
"dependencies": {
"@angular/animations": "^18.0.0-rc.0",
"@angular/common": "^18.0.0-rc.0",
"@angular/compiler": "^18.0.0-rc.0",
"@angular/core": "^18.0.0-rc.0",
"@angular/forms": "^18.0.0-rc.0",
"@angular/platform-browser": "^18.0.0-rc.0",
"@angular/platform-browser-dynamic": "^18.0.0-rc.0",
"@angular/platform-server": "^18.0.0-rc.0",
"@angular/router": "^18.0.0-rc.0",
"@angular/ssr": "^18.0.0-rc.0",
"@ionic/angular": "^8.0.0",
"@ionic/angular-server": "^8.0.0",
"core-js": "^3.33.2",
"express": "^4.15.2",
"ionicons": "^7.2.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"typescript-eslint-language-service": "^4.1.5",
"zone.js": "~0.14.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "^18.0.0-rc.0",
"@angular-eslint/builder": "^17.0.0",
"@angular-eslint/eslint-plugin": "^17.0.0",
"@angular-eslint/eslint-plugin-template": "^17.0.0",
"@angular-eslint/schematics": "^17.0.0",
"@angular-eslint/template-parser": "^17.0.0",
"@angular/cli": "^17.0.0",
"@angular/compiler-cli": "^18.0.0-rc.0",
"@angular/language-service": "^18.0.0-rc.0",
"@types/express": "^4.17.7",
"@types/ws": "8.5.3",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"concurrently": "^6.0.0",
"cypress": "^13.2.0",
"eslint": "^7.26.0",
"ts-loader": "^6.2.2",
"ts-node": "^8.3.0",
"typescript": "^5.4.0",
"wait-on": "^5.2.1",
"webpack": "^5.61.0",
"webpack-cli": "^4.9.2"
},
"engines": {
"node": ">= 18"
}
}

View File

@ -0,0 +1,71 @@
import 'zone.js/node';
import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr';
import * as express from 'express';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import bootstrap from './src/main.server';
// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
const server = express();
const distFolder = join(process.cwd(), 'dist/test-app/browser');
const indexHtml = existsSync(join(distFolder, 'index.original.html'))
? join(distFolder, 'index.original.html')
: join(distFolder, 'index.html');
const commonEngine = new CommonEngine();
server.set('view engine', 'html');
server.set('views', distFolder);
// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));
// All regular routes use the Angular engine
server.get('*', (req, res, next) => {
const { protocol, originalUrl, baseUrl, headers } = req;
commonEngine
.render({
bootstrap,
documentFilePath: indexHtml,
url: `${protocol}://${headers.host}${originalUrl}`,
publicPath: distFolder,
providers: [
{ provide: APP_BASE_HREF, useValue: baseUrl },],
})
.then((html) => res.send(html))
.catch((err) => next(err));
});
return server;
}
function run(): void {
const port = process.env['PORT'] || 4000;
// Start up the Node server
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
run();
}
export default bootstrap;

View File

@ -0,0 +1,10 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { routes } from './app.routes';
@NgModule({
imports: [RouterModule.forRoot(routes, { bindToComponentInputs: true })],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

@ -0,0 +1,41 @@
import { Component, Input, OnInit } from "@angular/core";
import { IonicModule } from '@ionic/angular';
@Component({
selector: 'app-bind-route',
template: `
<ion-header>
<ion-toolbar>
<ion-title>bindToComponentInputs</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<div>
<h3>Bind Route</h3>
<p id="route-params">Route params: id: {{id}}</p>
<p id="query-params">Query params: query: {{query}}</p>
<p id="data">Data: title: {{title}}</p>
<p id="resolve">Resolve: name: {{name}}</p>
</div>
</ion-content>
`,
standalone: true,
imports: [IonicModule]
})
export class BindComponentInputsComponent implements OnInit {
@Input() id?: string; // path parameter
@Input() query?: string; // query parameter
@Input() title?: string; // data property
@Input() name?: string; // resolve property
ngOnInit(): void {
console.log('BindComponentInputsComponent.ngOnInit', {
id: this.id,
query: this.query,
title: this.title,
name: this.name
});
}
}

View File

@ -0,0 +1,45 @@
import { Component } from "@angular/core";
import { IonicModule } from "@ionic/angular";
import { NavRootComponent } from "./nav-root.component";
@Component({
selector: 'app-modal-nav-params',
template: `
<ion-header>
<ion-toolbar>
<ion-title>Modal Nav Params</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-button id="open">Open Modal</ion-button>
<ion-modal #modal trigger="open">
<ng-template>
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button (click)="modal.dismiss()">Close</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-nav [root]="root" [rootParams]="rootParams"></ion-nav>
</ion-content>
</ng-template>
</ion-modal>
</ion-content>
`,
standalone: true,
imports: [IonicModule, NavRootComponent]
})
export class ModalNavParamsComponent {
root = NavRootComponent;
rootParams = {
params: {
id: 123
}
};
}

View File

@ -0,0 +1,38 @@
import { JsonPipe } from "@angular/common";
import { Component } from "@angular/core";
import { IonicModule } from "@ionic/angular";
/**
* This is used to track if any occurences of
* the ion-nav root component being attached to
* the DOM result in the rootParams not being
* assigned to the component instance.
*
* https://github.com/ionic-team/ionic-framework/issues/27146
*/
let rootParamsException = false;
@Component({
selector: 'app-modal-content',
template: `
{{ hasException ? 'ERROR' : 'OK' }}
`,
standalone: true,
imports: [IonicModule, JsonPipe]
})
export class NavRootComponent {
params: any;
ngOnInit() {
if (this.params === undefined) {
rootParamsException = true;
}
}
get hasException() {
return rootParamsException;
}
}

View File

@ -0,0 +1,25 @@
import { NgModule } from "@angular/core";
import { RouterModule } from "@angular/router";
@NgModule({
imports: [
RouterModule.forChild([
{
path: 'modal-nav-params',
loadComponent: () => import('./modal-nav-params/modal-nav-params.component').then(m => m.ModalNavParamsComponent),
},
{
path: 'bind-route/:id',
data: {
title: 'data:bindToComponentInputs'
},
resolve: {
name: () => 'resolve:bindToComponentInputs'
},
loadComponent: () => import('./bind-component-inputs/bind-component-inputs.component').then(c => c.BindComponentInputsComponent)
}
])
],
exports: [RouterModule]
})
export class VersionTestRoutingModule { }

View File

@ -0,0 +1,35 @@
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"sourceMap": false,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"emitDecoratorMetadata": true,
"typeRoots": ["node_modules/@types"],
"lib": ["ES2022", "dom"],
"plugins": [
{
"name": "typescript-eslint-language-service"
}
],
"useDefineForClassFields": false
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View File

@ -25,7 +25,8 @@ describe('Form Controls: Range', () => {
cy.get('ion-range').should('have.class', 'ion-valid');
cy.get('ion-range').should('have.class', 'ion-dirty');
cy.get('ion-range').should('have.class', 'ion-touched');
// TODO(FW-6226): Investigate why this regresses in Angular 18
// cy.get('ion-range').should('have.class', 'ion-touched');
cy.get('ion-range').invoke('prop', 'value').should('eq', 10);
});

View File

@ -15,7 +15,8 @@ npm pack ../../../dist
npm pack ../../../../angular-server/dist
# Install Dependencies
npm install *.tgz --no-save
# TODO(FW-6227): Remove --legacy-peer-deps once Angular 18 is released
npm install *.tgz --no-save --legacy-peer-deps
# Delete Angular cache directory
rm -rf .angular/

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