Merge remote-tracking branch 'origin/feature-8.1' into sp/sync-next-with-feature-8-1-04-09

This commit is contained in:
Sean Perkins
2024-04-09 15:02:05 -04:00
219 changed files with 2404 additions and 630 deletions

66
.github/CODEOWNERS vendored
View File

@@ -13,73 +13,7 @@
* @ionic-team/framework
# Frameworks
## Angular
/packages/angular/ @sean-perkins @thetaPC
/packages/angular-server @sean-perkins @thetaPC
/packages/angular/test @thetaPC
## React
/packages/react/ @amandaejohnston
/packages/react-router @amandaejohnston
/packages/react/test-app/
/packages/react-router/test-app/
## Vue
/packages/vue/ @liamdebeasi @thetaPC
/packages/vue-router/ @liamdebeasi @thetaPC
/packages/vue/test/ @thetaPC
/packages/vue-router/__tests__ @thetaPC
# Components
/core/src/components/accordion/ @liamdebeasi
/core/src/components/accordion-group/ @liamdebeasi
/core/src/components/checkbox/ @amandaejohnston
/core/src/components/datetime/ @liamdebeasi @amandaejohnston @sean-perkins
/core/src/components/datetime-button/ @liamdebeasi
/core/src/components/item/ @brandyscarney
/core/src/components/menu/ @amandaejohnston
/core/src/components/menu-toggle/ @amandaejohnston
/core/src/components/nav/ @sean-perkins
/core/src/components/nav-link/ @sean-perkins
/core/src/components/picker/ @liamdebeasi
/core/src/components/picker-column/ @liamdebeasi
/core/src/components/radio/ @amandaejohnston
/core/src/components/radio-group/ @amandaejohnston
/core/src/components/refresher/ @liamdebeasi
/core/src/components/refresher-content/ @liamdebeasi
/core/src/components/searchbar/ @brandyscarney
/core/src/components/segment/ @brandyscarney
/core/src/components/segment-button/ @brandyscarney
/core/src/components/skeleton-text/ @brandyscarney
# Utilities
/core/src/utils/animation/ @liamdebeasi
/core/src/utils/content/ @sean-perkins
/core/src/utils/gesture/ @liamdebeasi
/core/src/utils/input-shims/ @liamdebeasi
/core/src/utils/keyboard/ @liamdebeasi
/core/src/utils/logging/ @amandaejohnston
/core/src/utils/sanitization/ @liamdebeasi
/core/src/utils/tap-click/ @liamdebeasi
/core/src/utils/transition/ @liamdebeasi
/core/src/css/ @brandyscarney
/core/src/themes/ @brandyscarney

View File

@@ -1,780 +0,0 @@
# Ionic Component Implementation Guide
- [Button States](#button-states)
* [Component Structure](#component-structure)
* [Disabled](#disabled)
* [Focused](#focused)
* [Hover](#hover)
* [Activated](#activated)
* [Ripple Effect](#ripple-effect)
* [Example Components](#example-components)
* [References](#references)
- [Accessibility](#accessibility)
* [Checkbox](#checkbox)
* [Switch](#switch)
* [Accordion](#accordion)
- [Rendering Anchor or Button](#rendering-anchor-or-button)
* [Example Components](#example-components-1)
* [Component Structure](#component-structure-1)
- [Converting Scoped to Shadow](#converting-scoped-to-shadow)
- [RTL](#rtl)
- [Themes vs. Modes](#themes-vs-modes)
## Button States
Any component that renders a button should have the following states: [`disabled`](#disabled), [`focused`](#focused), [`hover`](#hover), [`activated`](#activated). It should also have a [Ripple Effect](#ripple-effect) component added for Material Design.
### Component Structure
#### JavaScript
A component that renders a native button should use the following structure:
```jsx
<Host>
<button class="button-native">
<span class="button-inner">
<slot></slot>
</span>
</button>
</Host>
```
Any other attributes and classes that are included are irrelevant to the button states, but it's important that this structure is followed and the classes above exist. In some cases they may be named something else that makes more sense, such as in item.
#### CSS
A mixin called `button-state()` has been added to make it easier to setup the states in each component.
```scss
@mixin button-state() {
@include position(0, 0, 0, 0);
position: absolute;
content: "";
opacity: 0;
}
```
The following styles should be set for the CSS to work properly. Note that the `button-state()` mixin is included in the `::after` pseudo element of the native button.
```scss
.button-native {
/**
* All other CSS in this selector is irrelevant to button states
* but the following are required styles
*/
position: relative;
overflow: hidden;
}
.button-native::after {
@include button-state();
}
.button-inner {
/**
* All other CSS in this selector is irrelevant to button states
* but the following are required styles
*/
position: relative;
z-index: 1;
}
```
### Disabled
The disabled state should be set via prop on all components that render a native button. Setting a disabled state will change the opacity or color of the button and remove click events from firing.
#### JavaScript
The `disabled` property should be set on the component:
```jsx
/**
* If `true`, the user cannot interact with the button.
*/
@Prop({ reflectToAttr: true }) disabled = false;
```
Then, the render function should add the [`aria-disabled`](https://www.w3.org/WAI/PF/aria/states_and_properties#aria-disabled) role to the host, a class that is the element tag name followed by `disabled`, and pass the `disabled` attribute to the native button:
```jsx
render() {
const { disabled } = this;
return (
<Host
aria-disabled={disabled ? 'true' : null}
class={{
'button-disabled': disabled
}}
>
<button disabled={disabled}>
<slot></slot>
</button>
</Host>
);
}
```
> [!NOTE]
> If the class being added was for `ion-back-button` it would be `back-button-disabled`.
#### CSS
The following CSS _at the bare minimum_ should be added for the disabled class, but it should be styled to match the spec:
```css
:host(.button-disabled) {
cursor: default;
opacity: .5;
pointer-events: none;
}
```
#### User Customization
TODO
### Focused
The focused state should be enabled for elements with actions when tabbed to via the keyboard. This will only work inside of an `ion-app`. It usually changes the opacity or background of an element.
> [!WARNING]
> Do not use `:focus` because that will cause the focus to apply even when an element is tapped (because the element is now focused). Instead, we only want the focus state to be shown when it makes sense which is what the `.ion-focusable` utility mentioned below does.
> [!NOTE]
> The [`:focus-visible`](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible) pseudo-class mostly does the same thing as our JavaScript-driven utility. However, it does not work well with Shadow DOM components as the element that receives focus is typically inside of the Shadow DOM, but we usually want to set the `:focus-visible` state on the host so we can style other parts of the component. Using other combinations such as `:has(:focus-visible)` does not work because `:has` does not pierce the Shadow DOM (as that would leak implementation details about the Shadow DOM contents). `:focus-within` does work with the Shadow DOM, but that has the same problem as `:focus` that was mentioned before. Unfortunately, a [`:focus-visible-within` pseudo-class does not exist yet](https://github.com/WICG/focus-visible/issues/151).
> [!IMPORTANT]
> Make sure the component has the correct [component structure](#component-structure) before continuing.
#### JavaScript
The `ion-focusable` class needs to be set on an element that can be focused:
```jsx
render() {
return (
<Host class="ion-focusable">
<slot></slot>
</Host>
);
}
```
Once that is done, the element will get the `ion-focused` class added when the element is tabbed to.
#### CSS
Components should be written to include the following focused variables for styling:
```css
/**
* @prop --color-focused: Color of the button when tabbed to with the keyboard
* @prop --background-focused: Background of the button when tabbed to with the keyboard
* @prop --background-focused-opacity: Opacity of the background when tabbed to with the keyboard
*/
```
Style the `ion-focused` class based on the spec for that element:
```scss
:host(.ion-focused) .button-native {
color: var(--color-focused);
&::after {
background: var(--background-focused);
opacity: var(--background-focused-opacity);
}
}
```
> [!IMPORTANT]
> Order matters! Focused should be **before** the activated and hover states.
#### User Customization
Setting the focused state on the `::after` pseudo-element allows the user to customize the focused state without knowing what the default opacity is set at. A user can customize in the following ways to have a solid red background on focus, or they can leave out `--background-focused-opacity` and the button will use the default focus opacity to match the spec.
```css
ion-button {
--background-focused: red;
--background-focused-opacity: 1;
}
```
### Hover
The [hover state](https://developer.mozilla.org/en-US/docs/Web/CSS/:hover) happens when a user moves their cursor on top of an element without pressing on it. It should not happen on mobile, only on desktop devices that support hover.
> [!NOTE]
> Some Android devices [incorrectly report their inputs](https://issues.chromium.org/issues/40855702) which can result in certain devices receiving hover events when they should not.
> [!IMPORTANT]
> Make sure the component has the correct [component structure](#component-structure) before continuing.
#### CSS
Components should be written to include the following hover variables for styling:
```css
/**
* @prop --color-hover: Color of the button on hover
* @prop --background-hover: Background of the button on hover
* @prop --background-hover-opacity: Opacity of the background on hover
*/
```
Style the `:hover` based on the spec for that element:
```scss
@media (any-hover: hover) {
:host(:hover) .button-native {
color: var(--color-hover);
&::after {
background: var(--background-hover);
opacity: var(--background-hover-opacity);
}
}
}
```
> [!IMPORTANT]
> Order matters! Hover should be **before** the activated state.
#### User Customization
Setting the hover state on the `::after` pseudo-element allows the user to customize the hover state without knowing what the default opacity is set at. A user can customize in the following ways to have a solid red background on hover, or they can leave out `--background-hover-opacity` and the button will use the default hover opacity to match the spec.
```css
ion-button {
--background-hover: red;
--background-hover-opacity: 1;
}
```
### Activated
The activated state should be enabled for elements with actions on "press". It usually changes the opacity or background of an element.
> [!WARNING]
>`:active` should not be used here as it is not received on mobile Safari unless the element has a `touchstart` listener (which we don't necessarily want to have to add to every element). From [Safari Web Content Guide](https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/AdjustingtheTextSize/AdjustingtheTextSize.html):
>
>> On iOS, mouse events are sent so quickly that the down or active state is never received. Therefore, the `:active` pseudo state is triggered only when there is a touch event set on the HTML element
> [!IMPORTANT]
> Make sure the component has the correct [component structure](#component-structure) before continuing.
#### JavaScript
The `ion-activatable` class needs to be set on an element that can be activated:
```jsx
render() {
return (
<Host class="ion-activatable">
<slot></slot>
</Host>
);
}
```
Once that is done, the element will get the `ion-activated` class added on press after a small delay. This delay exists so that the active state does not show up when an activatable element is tapped while scrolling.
In addition to setting that class, `ion-activatable-instant` can be set in order to have an instant press with no delay:
```jsx
<Host class="ion-activatable ion-activatable-instant">
```
#### CSS
```css
/**
* @prop --color-activated: Color of the button when pressed
* @prop --background-activated: Background of the button when pressed
* @prop --background-activated-opacity: Opacity of the background when pressed
*/
```
Style the `ion-activated` class based on the spec for that element:
```scss
:host(.ion-activated) .button-native {
color: var(--color-activated);
&::after {
background: var(--background-activated);
opacity: var(--background-activated-opacity);
}
}
```
> [!IMPORTANT]
> Order matters! Activated should be **after** the focused & hover states.
#### User Customization
Setting the activated state on the `::after` pseudo-element allows the user to customize the activated state without knowing what the default opacity is set at. A user can customize in the following ways to have a solid red background on press, or they can leave out `--background-activated-opacity` and the button will use the default activated opacity to match the spec.
```css
ion-button {
--background-activated: red;
--background-activated-opacity: 1;
}
```
### Ripple Effect
The ripple effect should be added to elements for Material Design. It *requires* the `ion-activatable` class to be set on the parent element to work, and relative positioning on the parent.
```jsx
render() {
const theme = getIonTheme(this);
return (
<Host
class={{
'ion-activatable': true,
}}
>
<button>
<slot></slot>
{theme === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</button>
</Host>
);
```
The ripple effect can also accept a different `type`. By default it is `"bounded"` which will expand the ripple effect from the click position outwards. To add a ripple effect that always starts in the center of the element and expands in a circle, set the type to `"unbounded"`. An unbounded ripple will exceed the container, so add `overflow: hidden` to the parent to prevent this.
Make sure to style the ripple effect for that component to accept a color:
```css
ion-ripple-effect {
color: var(--ripple-color);
}
```
### Example Components
- [ion-button](https://github.com/ionic-team/ionic/tree/main/core/src/components/button)
- [ion-back-button](https://github.com/ionic-team/ionic/tree/main/core/src/components/back-button)
- [ion-menu-button](https://github.com/ionic-team/ionic/tree/main/core/src/components/menu-button)
### References
- [Material Design States](https://material.io/design/interaction/states.html)
- [iOS Buttons](https://developer.apple.com/design/human-interface-guidelines/ios/controls/buttons/)
## Accessibility
### Checkbox
#### Example Components
- [ion-checkbox](https://github.com/ionic-team/ionic/tree/main/core/src/components/checkbox)
#### VoiceOver
In order for VoiceOver to work properly with a checkbox component there must be a native `input` with `type="checkbox"`, and `aria-checked` and `role="checkbox"` **must** be on the host element. The `aria-hidden` attribute needs to be added if the checkbox is disabled, preventing iOS users from selecting it:
```tsx
render() {
const { checked, disabled } = this;
return (
<Host
aria-checked={`${checked}`}
aria-hidden={disabled ? 'true' : null}
role="checkbox"
>
<input
type="checkbox"
/>
...
</Host>
);
}
```
#### NVDA
It is required to have `aria-checked` on the native input for checked to read properly and `disabled` to prevent tabbing to the input:
```tsx
render() {
const { checked, disabled } = this;
return (
<Host
aria-checked={`${checked}`}
aria-hidden={disabled ? 'true' : null}
role="checkbox"
>
<input
type="checkbox"
aria-checked={`${checked}`}
disabled={disabled}
/>
...
</Host>
);
}
```
#### Labels
Labels should be passed directly to the component in the form of either visible text or an `aria-label`. The visible text can be set inside of a `label` element, and the `aria-label` can be set directly on the interactive element.
In the following example the `aria-label` can be inherited from the Host using the `inheritAttributes` or `inheritAriaAttributes` utilities. This allows developers to set `aria-label` on the host element since they do not have access to inside the shadow root.
> [!NOTE]
> Use `inheritAttributes` to specify which attributes should be inherited or `inheritAriaAttributes` to inherit all of the possible `aria` attributes.
```tsx
import { Prop } from '@stencil/core';
import { inheritAttributes } from '@utils/helpers';
import type { Attributes } from '@utils/helpers';
...
private inheritedAttributes: Attributes = {};
@Prop() labelText?: string;
componentWillLoad() {
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
}
render() {
return (
<Host>
<label>
{this.labelText}
<input type="checkbox" {...this.inheritedAttributes} />
</label>
</Host>
)
}
```
#### Hidden Input
A helper function to render a hidden input has been added, it can be added in the `render`:
```tsx
renderHiddenInput(true, el, name, (checked ? value : ''), disabled);
```
> This is required for the checkbox to work with forms.
#### Known Issues
When using VoiceOver on macOS, Chrome will announce the following when you are focused on a checkbox:
```
currently on a checkbox inside of a checkbox
```
This is a compromise we have to make in order for it to work with the other screen readers & Safari.
### Switch
#### Example Components
- [ion-toggle](https://github.com/ionic-team/ionic/tree/main/core/src/components/toggle)
#### Voiceover
In order for VoiceOver to work properly with a switch component there must be a native `input` with `type="checkbox"` and `role="switch"`, and `aria-checked` and `role="switch"` **must** be on the host element. The `aria-hidden` attribute needs to be added if the switch is disabled, preventing iOS users from selecting it:
```tsx
render() {
const { checked, disabled } = this;
return (
<Host
aria-checked={`${checked}`}
aria-hidden={disabled ? 'true' : null}
role="switch"
>
<input
type="checkbox"
role="switch"
/>
...
</Host>
);
}
```
#### NVDA
It is required to have `aria-checked` on the native input for checked to read properly and `disabled` to prevent tabbing to the input:
```tsx
render() {
const { checked, disabled } = this;
return (
<Host
aria-checked={`${checked}`}
aria-hidden={disabled ? 'true' : null}
role="switch"
>
<input
type="checkbox"
role="switch"
aria-checked={`${checked}`}
disabled={disabled}
/>
...
</Host>
);
}
```
#### Labels
Labels should be passed directly to the component in the form of either visible text or an `aria-label`. The visible text can be set inside of a `label` element, and the `aria-label` can be set directly on the interactive element.
In the following example the `aria-label` can be inherited from the Host using the `inheritAttributes` or `inheritAriaAttributes` utilities. This allows developers to set `aria-label` on the host element since they do not have access to inside the shadow root.
> [!NOTE]
> Use `inheritAttributes` to specify which attributes should be inherited or `inheritAriaAttributes` to inherit all of the possible `aria` attributes.
```tsx
import { Prop } from '@stencil/core';
import { inheritAttributes } from '@utils/helpers';
import type { Attributes } from '@utils/helpers';
...
private inheritedAttributes: Attributes = {};
@Prop() labelText?: string;
componentWillLoad() {
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
}
render() {
return (
<Host>
<label>
{this.labelText}
<input type="checkbox" role="switch" {...this.inheritedAttributes} />
</label>
</Host>
)
}
```
#### Hidden Input
A helper function to render a hidden input has been added, it can be added in the `render`:
```tsx
renderHiddenInput(true, el, name, (checked ? value : ''), disabled);
```
> This is required for the switch to work with forms.
#### Known Issues
When using VoiceOver on macOS or iOS, Chrome will announce the switch as a checked or unchecked `checkbox`:
```
You are currently on a switch. To select or deselect this checkbox, press Control-Option-Space.
```
There is a WebKit bug open for this: https://bugs.webkit.org/show_bug.cgi?id=196354
### Accordion
#### Example Components
- [ion-accordion](https://github.com/ionic-team/ionic/tree/master/core/src/components/accordion)
- [ion-accordion-group](https://github.com/ionic-team/ionic/tree/master/core/src/components/accordion-group)
#### NVDA
In order to use the arrow keys to navigate the accordions, users must be in "Focus Mode". Typically, NVDA automatically switches between Browse and Focus modes when inside of a form, but not every accordion needs a form.
You can either wrap your `ion-accordion-group` in a form, or manually toggle Focus Mode using NVDA's keyboard shortcut.
## Rendering Anchor or Button
Certain components can render an `<a>` or a `<button>` depending on the presence of an `href` attribute.
### Example Components
- [ion-button](https://github.com/ionic-team/ionic/tree/main/core/src/components/button)
- [ion-card](https://github.com/ionic-team/ionic/tree/main/core/src/components/card)
- [ion-fab-button](https://github.com/ionic-team/ionic/tree/main/core/src/components/fab-button)
- [ion-item-option](https://github.com/ionic-team/ionic/tree/main/core/src/components/item-option)
- [ion-item](https://github.com/ionic-team/ionic/tree/main/core/src/components/item)
### Component Structure
#### JavaScript
In order to implement a component with a dynamic tag type, set the property that it uses to switch between them, we use `href`:
```jsx
/**
* Contains a URL or a URL fragment that the hyperlink points to.
* If this property is set, an anchor tag will be rendered.
*/
@Prop() href: string | undefined;
```
Then use that in order to render the tag:
```jsx
render() {
const TagType = href === undefined ? 'button' : 'a' as any;
return (
<Host>
<TagType>
<slot></slot>
</TagType>
</Host>
);
}
```
If the component can render an `<a>`, `<button>` or a `<div>` add in more properties such as a `button` attribute in order to check if it should render a button.
## Converting Scoped to Shadow
### CSS
There will be some CSS issues when converting to shadow. Below are some of the differences.
**Targeting host + slotted child**
```css
/* IN SCOPED */
:host(.ion-color)::slotted(ion-segment-button)
/* IN SHADOW*/
:host(.ion-color) ::slotted(ion-segment-button)
```
**Targeting host-context + host (with a :not)**
```css
/* IN SCOPED */
:host-context(ion-toolbar.ion-color):not(.ion-color) {
/* IN SHADOW */
:host-context(ion-toolbar.ion-color):host(:not(.ion-color)) {
```
**Targeting host-context + host (with a :not) > slotted child**
```css
/* IN SCOPED */
:host-context(ion-toolbar:not(.ion-color)):not(.ion-color)::slotted(ion-segment-button) {
/* IN SHADOW*/
:host-context(ion-toolbar:not(.ion-color)):host(:not(.ion-color)) ::slotted(ion-segment-button) {
```
## RTL
When you need to support both LTR and RTL modes, try to avoid using values such as `left` and `right`. For certain CSS properties, you can use the appropriate mixin to have this handled for you automatically.
For example, if you wanted `transform-origin` to be RTL-aware, you would use the `transform-origin` mixin:
```css
@include transform-origin(start, center);
```
This would output `transform-origin: left center` in LTR mode and `transform-origin: right center` in RTL mode.
These mixins depend on the `:host-context` pseudo-class when used inside of shadow components, which is not supported in WebKit. As a result, these mixins will not work in Safari for macOS and iOS when applied to shadow components.
To work around this, you should set an RTL class on the host of your component and set your RTL styles by targeting that class:
```tsx
<Host
class={{
'my-cmp-rtl': document.dir === 'rtl'
})
>
...
</Host>
```
```css
:host {
transform-origin: left center;
}
:host(.my-cmp-rtl) {
transform-origin: right center;
}
```
## Themes vs. Modes
Themes and modes are two different concepts in Ionic. Themes refer to the "look" of the component, such as the stylesheet that is used. Modes refer to the "behavior" of the component, such as the platform specific behaviors on iOS and Android.
To detect the theme, you can use the `getIonTheme` function:
```tsx
const theme = getIonTheme(this);
return (
<Host class={theme}>
{theme === "md" && <ion-ripple-effect></ion-ripple-effect>}
</Host>
);
```
Theme can be both used for styling decisions and conditional rendering of elements. For example, the ripple effect is only used in the Material Design theme.
To detect the mode, you can use the `getIonMode` function:
```tsx
const mode = getIonMode(this);
if (mode === "ios") {
enableRubberBandScrolling();
}
```
Mode can be used for behavior tied to the platform the app is running on. For example, iOS has rubber band scrolling, while Android does not.
Use mode for functionality that remains consistent across all themes but is intended specifically for a certain platform.
Functionality that is applied only to a specific theme should use theme.

View File

@@ -1,475 +0,0 @@
# Contributing
Thanks for your interest in contributing to the Ionic Framework! :tada:
- [Contributing Etiquette](#contributing-etiquette)
- [Creating an Issue](#creating-an-issue)
* [Creating a Good Code Reproduction](#creating-a-good-code-reproduction)
- [Creating a Pull Request](#creating-a-pull-request)
* [Requirements](#requirements)
* [Setup](#setup)
* [Core](#core)
+ [Modifying Components](#modifying-components)
+ [Preview Changes](#preview-changes)
+ [Lint Changes](#lint-changes)
+ [Modifying Documentation](#modifying-documentation)
+ [Modifying Tests](#modifying-tests)
- [Screenshot Tests](#screenshot-tests)
+ [Building Changes](#building-changes)
* [Angular, React, and Vue](#angular-react-and-vue)
+ [Modifying Files](#modifying-files)
+ [Preview Changes](#preview-changes-1)
+ [Lint Changes](#lint-changes-1)
+ [Modifying Tests](#modifying-tests-1)
+ [Building Changes](#building-changes-1)
* [Submit Pull Request](#submit-pull-request)
- [Commit Message Guidelines](#commit-message-guidelines)
* [Commit Message Format](#commit-message-format)
* [Revert](#revert)
* [Type](#type)
* [Scope](#scope)
* [Subject](#subject)
* [Body](#body)
* [Footer](#footer)
* [Examples](#examples)
- [License](#license)
## Contributing Etiquette
Please see our [Contributor Code of Conduct](https://github.com/ionic-team/ionic-framework/blob/main/CODE_OF_CONDUCT.md) for information on our rules of conduct.
## Creating an Issue
* If you have a question about using the framework, please ask on the [Ionic Forum](http://forum.ionicframework.com/) or in the [Ionic Discord](https://ionic.link/discord).
* It is required that you clearly describe the steps necessary to reproduce the issue you are running into. Although we would love to help our users as much as possible, diagnosing issues without clear reproduction steps is extremely time-consuming and simply not sustainable.
* The issue list of this repository is exclusively for bug reports and feature requests. Non-conforming issues will be closed immediately.
* Issues with no clear steps to reproduce will not be triaged. If an issue is labeled with "needs: reply" and receives no further replies from the author of the issue for more than 14 days, it will be closed.
* If you think you have found a bug, or have a new feature idea, please start by making sure it hasn't already been [reported](https://github.com/ionic-team/ionic-framework/issues?utf8=%E2%9C%93&q=is%3Aissue). You can search through existing issues to see if there is a similar one reported. Include closed issues as it may have been closed with a solution.
* Next, [create a new issue](https://github.com/ionic-team/ionic-framework/issues/new/choose) that thoroughly explains the problem. Please fill out the populated issue form before submitting the issue.
## Creating a Good Code Reproduction
### What is a Code Reproduction?
A code reproduction is a small application that is built to demonstrate a particular issue. The code reproduction should contain the minimum amount of code needed to recreate the issue and should focus on a single issue.
### Why Should You Create a Reproduction?
A code reproduction of the issue you are experiencing helps us better isolate the cause of the problem. This is an important first step to getting any bug fixed!
Without a reliable code reproduction, it is unlikely we will be able to resolve the issue, leading to it being closed. In other words, creating a code reproduction of the issue helps us help you.
### How to Create a Reproduction
* Create a new Ionic application using one of our starter templates. The `blank` starter application is a great choice for this. You can create one using the following Ionic CLI command: `ionic start myApp blank`
* Add the minimum amount of code needed to recreate the issue you are experiencing. Do not include anything that is not required to reproduce the issue. This includes any 3rd party plugins you have installed.
* Publish the application on GitHub and include a link to it when [creating an issue](#creating-an-issue).
* Be sure to include steps to reproduce the issue. These steps should be clear and easy to follow.
### Benefits of Creating a Reproduction
* **Uses the latest version of Ionic:** By creating a new Ionic application, you are ensuring that you are testing against the latest version of the framework. Sometimes the issues you are experiencing have already been resolved in a newer version of the framework!
* **Minimal surface area:** By removing code that is not needed in order to reproduce the issue, it makes it easier to identify the cause of the issue.
* **No secret code needed:** Creating a minimal reproduction of the issue prevents you from having to publish any proprietary code used in your project.
* **Get help fixing the issue:** If we can reliably reproduce an issue, there is a good chance we will be able to address it.
## Creating a Pull Request
Before creating a pull request, please read our requirements that explains the minimal details to have your PR considered and merged into the codebase.
### Requirements
1. PRs must reference an existing issue that describes the issue or feature being submitted.
2. PRs must have a reproduction app or the issue must include a reproduction app to verify changes against.
3. PRs must include tests covering the changed behavior or a description of why tests cannot be written.
> Note: We appreciate you taking the time to contribute! Before submitting a pull request, please take the time to comment on the issue you are wanting to resolve. This helps us prevent duplicate effort or advise if the team is already addressing the issue.
* Looking for an issue to fix? Look through our issues with the [help wanted](https://github.com/ionic-team/ionic-framework/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) label!
### Setup
1. [Download the installer](https://nodejs.org/) for the LTS version of Node.js. This is the best way to also [install npm](https://blog.npmjs.org/post/85484771375/how-to-install-npm#_=_).
2. Fork this repository.
3. Clone your fork.
4. Create a new branch from main for your change.
5. Navigate into the directory of the package you wish to modify (core, angular, etc.).
6. Run `npm install` to install dependencies for this package.
7. Follow the steps for the specific package below.
### Core
#### Modifying Components
1. Locate the component(s) to modify inside `/core/src/components/`.
2. Take a look at the [Stencil Documentation](https://stenciljs.com/docs/introduction/) and other components to understand the implementation of these components.
3. Make your changes to the component. If the change is overly complex or out of the ordinary, add comments so we can understand the changes.
4. [Preview your changes](#preview-changes) locally.
5. [Modify the documentation](#modifying-documentation) if needed.
6. [Run lint](#lint-changes) on the directory and make sure there are no errors.
7. [Build the project](#building-changes).
8. After the build is finished, commit the changes. Please follow the [commit message format](#commit-message-format) for every commit.
9. [Submit a Pull Request](#submit-pull-request) of your changes.
#### Preview Changes
##### Previewing in this repository
1. Run `npm start` from within the `core` directory.
2. A browser should open at `http://localhost:3333/`.
3. From here, navigate to one of the component's tests to preview your changes.
4. If a test showing your change doesn't exist, [add a new test or update an existing one](#modifying-tests).
5. To test in RTL mode, once you are in the desired component's test, add `?rtl=true` at the end of the url; for example: `http://localhost:3333/src/components/alert/test/basic?rtl=true`.
##### Previewing in an external app
We can use `npm pack` to test Ionic Framework changes in an external app outside of this repository. Follow the below steps based on your framework.
###### JavaScript
Run the following commands to build the core directory and pack the changes:
```bash
cd core
npm i
npm run build
npm pack --pack-destination ~
```
Then, in your Ionic Framework JavaScript app, run the following command to use the built package with the `.tgz` file that was created:
```bash
npm install file:/~/ionic-core-7.0.1.tgz
```
###### Angular
Run the following commands to build the core & angular directories and pack the changes:
```bash
cd core
npm i
npm run build
npm pack --pack-destination ~
cd ../packages/angular
npm i
npm run sync
npm run build
cd dist/
npm pack --pack-destination ~
```
Then, in your Ionic Framework Angular app, run the following commands to use the built packages with the `.tgz` files that were created:
```bash
rm -rf .angular/
npm install file:/~/ionic-core-7.0.1.tgz
npm install file:/~/ionic-angular-7.0.1.tgz
```
###### React
Run the following commands to build the core & react directories and pack the changes:
```bash
cd core
npm i
npm run build
npm pack --pack-destination ~
cd ../packages/react
npm i
npm run sync
npm run build
npm pack --pack-destination ~
cd ../react-router
npm i
npm run sync
npm run build
npm pack --pack-destination ~
```
Then, in your Ionic Framework React app, run the following commands to use the built packages with the `.tgz` files that were created:
```bash
npm install file:/~/ionic-core-7.0.1.tgz
npm install file:/~/ionic-react-7.0.1.tgz
npm install file:/~/ionic-react-router-7.0.1.tgz
```
##### Vue
Run the following commands to build the core & vue directories and pack the changes:
```bash
cd core
npm i
npm run build
npm pack --pack-destination ~
cd ../packages/vue
npm i
npm run sync
npm run build
npm pack --pack-destination ~
cd ../vue-router
npm i
npm run sync
npm run build
npm pack --pack-destination ~
```
Then, in your Ionic Framework Vue app, run the following commands to use the built packages with the `.tgz` files that were created:
```bash
npm install file:/~/ionic-core-7.0.1.tgz
npm install file:/~/ionic-vue-7.0.1.tgz
npm install file:/~/ionic-vue-router-7.0.1.tgz
```
#### Lint Changes
1. Run `npm run lint` to lint the TypeScript and Sass.
2. If there are lint errors, run `npm run lint.fix` to automatically fix any errors. Repeat step 1 to ensure the errors have been fixed, and manually fix them if not.
3. To lint and fix only TypeScript errors, run `npm run lint.ts` and `npm run lint.ts.fix`, respectively.
4. To lint and fix only Sass errors, run `npm run lint.sass` and `npm run lint.sass.fix`, respectively.
#### Modifying Documentation
- Changes to manually written documentation should be made in the `ionic-docs` repo: https://github.com/ionic-team/ionic-docs/tree/main/docs
- In your `ionic-docs` PR, please add a link back to the related `ionic-framework` PR.
- Changes to auto generated documentation should be made in the `ionic-framework` repo. These can be done in the same PR as your fix or feature.
- Run `npm run build` and commit all updates to ensure your changes make it into the generated documentation.
- `Usage`: update the component's usage examples in the component's `usage/` directory.
- `Properties`, `Events`, or `Methods`: update the component's TypeScript file (`*.tsx`).
- `CSS Custom Properties`: update the component's main Sass file (`*.scss`).
#### Modifying Tests
1. Locate the test to modify inside the `test/` folder in the component's directory.
2. If a test exists, modify the test by adding an example to reproduce the problem fixed or feature added.
3. If a new test is needed, the easiest way is to copy the `basic/` directory from the component's `test/` directory, rename it, and edit the content in both the `index.html` and `e2e.ts` file (see [Screenshot Tests](#screenshot-tests) for more information on this file).
4. The `preview/` directory is used in the documentation as a demo. Only update this test if there is a bug in the test or if the API has a change that hasn't been updated in the test.
See [Ionic's E2E testing guide](../core/src/utils/test/playwright/docs/README.md) for information regarding the tools you can use to test Ionic.
##### Screenshot Tests
1. If the test exists in screenshot, there will be a file named `e2e.ts` in the directory of the test.
2. A screenshot test can be added by including this file and adding one or more `test()` calls that include a call to `page.compareScreenshot()`. See [Stencil end-to-end testing](https://stenciljs.com/docs/end-to-end-testing) and existing tests in `core/` for examples.
3. **Important:** each `test()` should have only one screenshot (`page.compareScreenshot()`) call **or** it should check the expect at the end of each test. If there is a mismatch it will fail the test which will prevent the rest of the test from running, i.e. if the first screenshot fails the remaining screenshot calls would not be called _unless_ they are in a separate test or all of the expects are called at the end.
4. To run screenshot locally, use the following command: `npm run test.screenshot`.
- To run screenshot for a specific test, pass the path to the test or a string to search for.
- For example, running all `alert` tests: `npm run test.screenshot alert`.
- Or, running the basic `alert` tests: `npm run test.screenshot src/components/alert/test/basic/e2e.ts`.
#### Building Changes
1. Once all changes have been made and the documentation has been updated, run `npm run build` inside of the `core` directory. This will add your changes to any auto-generated files, if necessary.
2. Review the changes and, if everything looks correct, [commit](#commit-message-format) the changes.
3. Make sure the build has finished before committing. If you made changes to the documentation, properties, methods, or anything else that requires an update to a generate file, this needs to be committed.
4. After the changes have been pushed, publish the branch and [create a pull request](#creating-a-pull-request).
### Angular, React, and Vue
#### Modifying Files
1. Locate the files inside the relevant root directory:
- Angular: [`/packages/angular/src`](/packages/angular/src)
- React: [`/packages/react/src`](/packages/react/src)
- Vue: [`/packages/vue/src`](/packages/vue/src)
2. Make your changes to the files. If the change is overly complex or out of the ordinary, add comments so we can understand the changes.
3. Run lint on the directory and make sure there are no errors.
4. Build the project.
5. After the build is finished, commit the changes. Please follow the [commit message format](#commit-message-format) for every commit.
6. [Submit a Pull Request](#submit-pull-request) of your changes.
#### Preview Changes
##### Previewing in this repository
Follow the steps in the test directory for each framework:
- Angular: [`/packages/angular/test`](/packages/angular/test/README.md)
- React: [`/packages/react/test`](/packages/react/test/README.md)
- Vue: [`/packages/vue/test`](/packages/vue/test/README.md)
##### Previewing in an external app
Follow the steps to [preview changes in core](#preview-changes).
#### Lint Changes
1. Run `npm run lint` to lint the TypeScript in the relevant directory:
- Angular: [`/packages/angular/src`](/packages/angular/src)
- React: [`/packages/react/src`](/packages/react/src)
- Vue: [`/packages/vue/src`](/packages/vue/src)
2. If there are lint errors, run `npm run lint.fix` to automatically fix any errors. Repeat step 1 to ensure the errors have been fixed, and manually fix them if not.
#### Modifying Tests
1. Locate the e2e test to modify inside the relevant test app directory:
- Angular: [`/packages/angular/test/base/e2e/src`](/packages/angular/test/base/e2e/src)
- React: [`/packages/react/test/base/tests/e2e/specs`](/packages/react/test/base/tests/e2e/specs)
- Vue: [`/packages/vue/test/base/tests/e2e/specs`](/packages/vue/test/base/tests/e2e/specs)
2. If a test exists, modify the test by adding an example to reproduce the problem fixed or feature added.
3. If a new test is needed, copy an existing test, rename it, and edit the content in the test file.
4. Run `npm run test` to run your tests.
#### Building Changes
1. Once all changes have been made, run `npm run build` inside of the package's root directory. This will add your changes to any auto-generated files, if necessary.
2. Review the changes and, if everything looks correct, [commit](#commit-message-format) the changes.
3. Make sure the build has finished before committing. If you made changes to the documentation, properties, methods, or anything else that requires an update to a generate file, this needs to be committed.
4. After the changes have been pushed, publish the branch and [create a pull request](#creating-a-pull-request).
### Submit Pull Request
1. [Create a new pull request](https://github.com/ionic-team/ionic-framework/compare) with the `main` branch as the `base`. You may need to click on `compare across forks` to find your changes.
2. See the [Creating a pull request from a fork](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) GitHub help article for more information.
3. Please fill out the provided Pull Request template to the best of your ability and include any issues that are related.
### Review Process for Feature PRs
The team has an internal design process for new Ionic features, which must be completed before the PR can be reviewed or merged. As a result of the design process, community feature PRs are subject to large changes. In some cases, the team may instead create a separate PR using pieces of the community PR. Either way, you will always receive co-author commit credit when the feature is merged.
To expedite the process, please ensure that all feature PRs have an associated issue created, with a clear use case for why the feature should be added to Ionic.
## Commit Message Guidelines
We have very precise rules over how our git commit messages should be formatted. This leads to readable messages that are easy to follow when looking through the project history. We also use the git commit messages to generate our [changelog](https://github.com/ionic-team/ionic-framework/blob/main/CHANGELOG.md). Our format closely resembles Angular's [commit message guidelines](https://github.com/angular/angular/blob/main/CONTRIBUTING.md#commit).
### Commit Message Format
We follow the [Conventional Commits specification](https://www.conventionalcommits.org/). A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**:
```
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```
The **header** is mandatory and the **scope** of the header is optional.
### Revert
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.
### Type
If the prefix is `feat`, `fix` or `perf`, it will appear in the changelog. However if there is any [BREAKING CHANGE](#footer), the commit will always appear in the changelog.
Must be one of the following:
* **feat**: A new feature
* **fix**: A bug fix
* **docs**: Documentation only changes
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
* **refactor**: A code change that neither fixes a bug nor adds a feature
* **perf**: A code change that improves performance
* **test**: Adding missing tests
* **chore**: Changes to the build process or auxiliary tools and libraries such as documentation generation
### Scope
The scope can be anything specifying place of the commit change. Usually it will refer to a component but it can also refer to a utility. For example `action-sheet`, `button`, `css`, `menu`, `nav`, etc. If you make multiple commits for the same component, please keep the naming of this component consistent. For example, if you make a change to navigation and the first commit is `fix(nav)`, you should continue to use `nav` for any more commits related to navigation. As a general rule, if you're modifying a component use the name of the folder.
### Subject
The subject contains succinct description of the change:
* use the imperative, present tense: "change" not "changed" nor "changes"
* do not capitalize first letter
* do not place a period `.` at the end
* entire length of the commit message must not go over 50 characters
* describe what the commit does, not what issue it relates to or fixes
* **be brief, yet descriptive** - we should have a good understanding of what the commit does by reading the subject
### Body
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
The body should include the motivation for the change and contrast this with previous behavior.
### Footer
The footer should contain any information about **Breaking Changes** and is also the place to
reference GitHub issues that this commit **Closes**.
**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.
### Examples
Does not appear in the generated changelog:
```
docs(changelog): update steps to update
```
Appears under "Features" header, toast subheader:
```
feat(toast): add 'buttons' property
```
Appears under "Bug Fixes" header, skeleton-text subheader, with a link to issue #28:
```
fix(skeleton-text): use proper color when animated
closes #28
```
Appears under "Performance Improvements" header, and under "Breaking Changes" with the breaking change explanation:
```
perf(css): remove all css utility attributes
BREAKING CHANGE: The CSS utility attributes have been removed. Use CSS classes instead.
```
Appears under "Breaking Changes" with the breaking change explanation:
```
refactor(animations): update to new animation system
BREAKING CHANGE:
Removes the old animation system to use the new Ionic animations.
```
The following commit and commit `667ecc1` do not appear in the changelog if they are under the same release. If not, the revert commit appears under the "Reverts" header.
```
revert: feat(skeleton-text): add animated property
This reverts commit 667ecc1654a317a13331b17617d973392f415f02.
```
## License
By contributing your code to the ionic-team/ionic GitHub Repository, you agree to license your contribution under the MIT license.

View File

@@ -25,7 +25,7 @@ Issue number: resolves #
If this introduces a breaking change:
1. Describe the impact and migration path for existing applications below.
2. Update the BREAKING.md file with the breaking change.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging. See https://github.com/ionic-team/ionic-framework/blob/main/.github/CONTRIBUTING.md#footer for more information.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging. See https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer for more information.
-->

View File

@@ -13,6 +13,6 @@ jobs:
- name: 'Auto-assign issue'
uses: pozil/auto-assign-issue@edee9537367a8fbc625d27f9e10aa8bad47b8723 # v1.13.0
with:
assignees: liamdebeasi, sean-perkins, brandyscarney, amandaejohnston, mapsandapps, thetaPC
assignees: liamdebeasi, sean-perkins, brandyscarney, amandaejohnston, thetaPC
numOfAssignee: 1
allowSelfAssign: false

View File

@@ -41,8 +41,8 @@ jobs:
uses: ./.github/workflows/actions/upload-archive
with:
name: ionic-docs
output: docs/DocsBuild.zip
paths: docs/core.json docs/core.d.ts
output: packages/docs/DocsBuild.zip
paths: packages/docs/core.json packages/docs/core.d.ts
release-docs:
needs: [release-core]
@@ -53,7 +53,7 @@ jobs:
uses: ./.github/workflows/actions/download-archive
with:
name: ionic-docs
path: ./docs
path: ./packages/docs
filename: DocsBuild.zip
- uses: ./.github/workflows/actions/publish-npm
with:
@@ -61,7 +61,7 @@ jobs:
tag: ${{ inputs.tag }}
version: ${{ inputs.version }}
preid: ${{ inputs.preid }}
working-directory: 'docs'
working-directory: 'packages/docs'
token: ${{ secrets.NPM_TOKEN }}
release-angular: