mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
feat(angular): ship Ionic components as Angular standalone components (#28311)
Issue number: N/A --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> **1. Bundle Size Reductions** All Ionic UI components and Ionicons are added to the final bundle of an Ionic Angular application. This is because all components and icons are lazily loaded as needed. This prevents the compiler from properly tree shaking applications. This does not cause all components and icons to be loaded on application start, but it does increase the size of the final app output that all users need to download. **Related Issues** https://github.com/ionic-team/ionicons/issues/910 https://github.com/ionic-team/ionicons/issues/536 https://github.com/ionic-team/ionic-framework/issues/27280 https://github.com/ionic-team/ionic-framework/issues/24352 **2. Standalone Component Support** Standalone Components are a stable API as of Angular 15. The Ionic starter apps on the CLI have NgModule and Standalone options, but all of the Ionic components are still lazily/dynamically loaded using `IonicModule`. Standalone components in Ionic also enable support for new Angular features such as bundling with ESBuild instead of Webpack. ESBuild does not work in Ionic Angular right now because components cannot be statically analyzed since they are dynamically imported. We added preliminary support for standalone components in Ionic v6.3.0. This enabled developers to use their own custom standalone components when routing with `ion-router-outlet`. However, we did not ship standalone components for Ionic's UI components. **Related Issues** https://github.com/ionic-team/ionic-framework/issues/25404 https://github.com/ionic-team/ionic-framework/issues/27251 https://github.com/ionic-team/ionic-framework/issues/27387 **3. Faster Component Load Times** Since Ionic Angular components are lazily loaded, they also need to be hydrated. However, this hydration does not happen immediately which prevents components from being usable for multiple frames. **Related Issues** https://github.com/ionic-team/ionic-framework/issues/24352 https://github.com/ionic-team/ionic-framework/issues/26474 ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - Ionic components and directives are accessible as Angular standalone components/directives ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change, please describe the impact and migration path for existing applications below. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. --> Associated documentation branch: https://github.com/ionic-team/ionic-docs/tree/feature-7.5 --------- Co-authored-by: Maria Hutt <thetaPC@users.noreply.github.com> Co-authored-by: Sean Perkins <sean@ionic.io> Co-authored-by: Amanda Johnston <90629384+amandaejohnston@users.noreply.github.com> Co-authored-by: Maria Hutt <maria@ionic.io> Co-authored-by: Sean Perkins <13732623+sean-perkins@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
import { fromEvent } from 'rxjs';
|
||||
|
||||
export const proxyInputs = (Cmp: any, inputs: string[]) => {
|
||||
const Prototype = Cmp.prototype;
|
||||
inputs.forEach((item) => {
|
||||
Object.defineProperty(Prototype, item, {
|
||||
get() {
|
||||
return this.el[item];
|
||||
},
|
||||
set(val: any) {
|
||||
this.z.runOutsideAngular(() => (this.el[item] = val));
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const proxyMethods = (Cmp: any, methods: string[]) => {
|
||||
const Prototype = Cmp.prototype;
|
||||
methods.forEach((methodName) => {
|
||||
Prototype[methodName] = function () {
|
||||
const args = arguments;
|
||||
return this.z.runOutsideAngular(() => this.el[methodName].apply(this.el, args));
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const proxyOutputs = (instance: any, el: any, events: string[]) => {
|
||||
events.forEach((eventName) => (instance[eventName] = fromEvent(el, eventName)));
|
||||
};
|
||||
|
||||
export const defineCustomElement = (tagName: string, customElement: any) => {
|
||||
if (customElement !== undefined && typeof customElements !== 'undefined' && !customElements.get(tagName)) {
|
||||
customElements.define(tagName, customElement);
|
||||
}
|
||||
};
|
||||
|
||||
// tslint:disable-next-line: only-arrow-functions
|
||||
export function ProxyCmp(opts: { defineCustomElementFn?: () => void; inputs?: any; methods?: any }) {
|
||||
const decorator = function (cls: any) {
|
||||
const { defineCustomElementFn, inputs, methods } = opts;
|
||||
|
||||
if (defineCustomElementFn !== undefined) {
|
||||
defineCustomElementFn();
|
||||
}
|
||||
|
||||
if (inputs) {
|
||||
proxyInputs(cls, inputs);
|
||||
}
|
||||
if (methods) {
|
||||
proxyMethods(cls, methods);
|
||||
}
|
||||
return cls;
|
||||
};
|
||||
return decorator;
|
||||
}
|
||||
86
packages/angular/standalone/src/directives/checkbox.ts
Normal file
86
packages/angular/standalone/src/directives/checkbox.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
Injector,
|
||||
NgZone,
|
||||
} from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { ValueAccessor, setIonicClasses } from '@ionic/angular/common';
|
||||
import type { CheckboxChangeEventDetail, Components } from '@ionic/core/components';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-checkbox.js';
|
||||
|
||||
import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';
|
||||
|
||||
const CHECKBOX_INPUTS = [
|
||||
'checked',
|
||||
'color',
|
||||
'disabled',
|
||||
'indeterminate',
|
||||
'justify',
|
||||
'labelPlacement',
|
||||
'legacy',
|
||||
'mode',
|
||||
'name',
|
||||
'value',
|
||||
];
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineCustomElement,
|
||||
inputs: CHECKBOX_INPUTS,
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-checkbox',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: CHECKBOX_INPUTS,
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: IonCheckbox,
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
standalone: true,
|
||||
})
|
||||
export class IonCheckbox extends ValueAccessor {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) {
|
||||
super(injector, r);
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
proxyOutputs(this, this.el, ['ionChange', 'ionFocus', 'ionBlur']);
|
||||
}
|
||||
|
||||
writeValue(value: boolean): void {
|
||||
this.elementRef.nativeElement.checked = this.lastValue = value;
|
||||
setIonicClasses(this.elementRef);
|
||||
}
|
||||
|
||||
@HostListener('ionChange', ['$event.target'])
|
||||
handleIonChange(el: HTMLIonCheckboxElement | HTMLIonToggleElement): void {
|
||||
this.handleValueChange(el, el.checked);
|
||||
}
|
||||
}
|
||||
|
||||
export declare interface IonCheckbox extends Components.IonCheckbox {
|
||||
/**
|
||||
* Emitted when the checked property has changed
|
||||
as a result of a user action such as a click.
|
||||
This event will not emit when programmatically
|
||||
setting the checked property.
|
||||
*/
|
||||
ionChange: EventEmitter<CustomEvent<CheckboxChangeEventDetail>>;
|
||||
/**
|
||||
* Emitted when the checkbox has focus.
|
||||
*/
|
||||
ionFocus: EventEmitter<CustomEvent<void>>;
|
||||
/**
|
||||
* Emitted when the checkbox loses focus.
|
||||
*/
|
||||
ionBlur: EventEmitter<CustomEvent<void>>;
|
||||
}
|
||||
103
packages/angular/standalone/src/directives/datetime.ts
Normal file
103
packages/angular/standalone/src/directives/datetime.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
Injector,
|
||||
NgZone,
|
||||
} from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { ValueAccessor } from '@ionic/angular/common';
|
||||
import type { DatetimeChangeEventDetail, Components } from '@ionic/core/components';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-datetime.js';
|
||||
|
||||
import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';
|
||||
|
||||
const DATETIME_INPUTS = [
|
||||
'cancelText',
|
||||
'clearText',
|
||||
'color',
|
||||
'dayValues',
|
||||
'disabled',
|
||||
'doneText',
|
||||
'firstDayOfWeek',
|
||||
'highlightedDates',
|
||||
'hourCycle',
|
||||
'hourValues',
|
||||
'isDateEnabled',
|
||||
'locale',
|
||||
'max',
|
||||
'min',
|
||||
'minuteValues',
|
||||
'mode',
|
||||
'monthValues',
|
||||
'multiple',
|
||||
'name',
|
||||
'preferWheel',
|
||||
'presentation',
|
||||
'readonly',
|
||||
'showClearButton',
|
||||
'showDefaultButtons',
|
||||
'showDefaultTimeLabel',
|
||||
'showDefaultTitle',
|
||||
'size',
|
||||
'titleSelectedDatesFormatter',
|
||||
'value',
|
||||
'yearValues',
|
||||
];
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineCustomElement,
|
||||
inputs: DATETIME_INPUTS,
|
||||
methods: ['confirm', 'reset', 'cancel'],
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-datetime',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: DATETIME_INPUTS,
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: IonDatetime,
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
standalone: true,
|
||||
})
|
||||
export class IonDatetime extends ValueAccessor {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) {
|
||||
super(injector, r);
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
proxyOutputs(this, this.el, ['ionCancel', 'ionChange', 'ionFocus', 'ionBlur']);
|
||||
}
|
||||
|
||||
@HostListener('ionChange', ['$event.target'])
|
||||
handleIonChange(el: HTMLIonDatetimeElement): void {
|
||||
this.handleValueChange(el, el.value);
|
||||
}
|
||||
}
|
||||
|
||||
export declare interface IonDatetime extends Components.IonDatetime {
|
||||
/**
|
||||
* Emitted when the datetime selection was cancelled.
|
||||
*/
|
||||
ionCancel: EventEmitter<CustomEvent<void>>;
|
||||
/**
|
||||
* Emitted when the value (selected date) has changed.
|
||||
*/
|
||||
ionChange: EventEmitter<CustomEvent<DatetimeChangeEventDetail>>;
|
||||
/**
|
||||
* Emitted when the datetime has focus.
|
||||
*/
|
||||
ionFocus: EventEmitter<CustomEvent<void>>;
|
||||
/**
|
||||
* Emitted when the datetime loses focus.
|
||||
*/
|
||||
ionBlur: EventEmitter<CustomEvent<void>>;
|
||||
}
|
||||
24
packages/angular/standalone/src/directives/icon.ts
Normal file
24
packages/angular/standalone/src/directives/icon.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core';
|
||||
import { defineCustomElement as defineIonIcon } from 'ionicons/components/ion-icon.js';
|
||||
|
||||
import { ProxyCmp } from './angular-component-lib/utils';
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineIonIcon,
|
||||
inputs: ['color', 'flipRtl', 'icon', 'ios', 'lazy', 'md', 'mode', 'name', 'sanitize', 'size', 'src'],
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-icon',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: ['color', 'flipRtl', 'icon', 'ios', 'lazy', 'md', 'mode', 'name', 'sanitize', 'size', 'src'],
|
||||
standalone: true,
|
||||
})
|
||||
export class IonIcon {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
}
|
||||
}
|
||||
12
packages/angular/standalone/src/directives/index.ts
Normal file
12
packages/angular/standalone/src/directives/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export * from './checkbox';
|
||||
export * from './datetime';
|
||||
export * from './icon';
|
||||
export * from './input';
|
||||
export * from './radio-group';
|
||||
export * from './radio';
|
||||
export * from './range';
|
||||
export * from './searchbar';
|
||||
export * from './segment';
|
||||
export * from './select';
|
||||
export * from './textarea';
|
||||
export * from './toggle';
|
||||
145
packages/angular/standalone/src/directives/input.ts
Normal file
145
packages/angular/standalone/src/directives/input.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
Injector,
|
||||
NgZone,
|
||||
} from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { ValueAccessor } from '@ionic/angular/common';
|
||||
import type {
|
||||
InputInputEventDetail as IIonInputInputInputEventDetail,
|
||||
InputChangeEventDetail as IIonInputInputChangeEventDetail,
|
||||
Components,
|
||||
} from '@ionic/core/components';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-input.js';
|
||||
|
||||
import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';
|
||||
|
||||
const INPUT_INPUTS = [
|
||||
'accept',
|
||||
'autocapitalize',
|
||||
'autocomplete',
|
||||
'autocorrect',
|
||||
'autofocus',
|
||||
'clearInput',
|
||||
'clearOnEdit',
|
||||
'color',
|
||||
'counter',
|
||||
'counterFormatter',
|
||||
'debounce',
|
||||
'disabled',
|
||||
'enterkeyhint',
|
||||
'errorText',
|
||||
'fill',
|
||||
'helperText',
|
||||
'inputmode',
|
||||
'label',
|
||||
'labelPlacement',
|
||||
'legacy',
|
||||
'max',
|
||||
'maxlength',
|
||||
'min',
|
||||
'minlength',
|
||||
'mode',
|
||||
'multiple',
|
||||
'name',
|
||||
'pattern',
|
||||
'placeholder',
|
||||
'readonly',
|
||||
'required',
|
||||
'shape',
|
||||
'size',
|
||||
'spellcheck',
|
||||
'step',
|
||||
'type',
|
||||
'value',
|
||||
];
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineCustomElement,
|
||||
inputs: INPUT_INPUTS,
|
||||
methods: ['setFocus', 'getInputElement'],
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-input',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: INPUT_INPUTS,
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: IonInput,
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
standalone: true,
|
||||
})
|
||||
export class IonInput extends ValueAccessor {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) {
|
||||
super(injector, r);
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
proxyOutputs(this, this.el, ['ionInput', 'ionChange', 'ionBlur', 'ionFocus']);
|
||||
}
|
||||
|
||||
@HostListener('ionInput', ['$event.target'])
|
||||
handleIonInput(el: HTMLIonInputElement): void {
|
||||
this.handleValueChange(el, el.value);
|
||||
}
|
||||
|
||||
registerOnChange(fn: (_: any) => void): void {
|
||||
super.registerOnChange((value: string) => {
|
||||
if (this.type === 'number') {
|
||||
/**
|
||||
* If the input type is `number`, we need to convert the value to a number
|
||||
* when the value is not empty. If the value is empty, we want to treat
|
||||
* the value as null.
|
||||
*/
|
||||
fn(value === '' ? null : parseFloat(value));
|
||||
} else {
|
||||
fn(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export declare interface IonInput extends Components.IonInput {
|
||||
/**
|
||||
* The `ionInput` event is fired each time the user modifies the input's value.
|
||||
Unlike the `ionChange` event, the `ionInput` event is fired for each alteration
|
||||
to the input's value. This typically happens for each keystroke as the user types.
|
||||
|
||||
For elements that accept text input (`type=text`, `type=tel`, etc.), the interface
|
||||
is [`InputEvent`](https://developer.mozilla.org/en-US/docs/Web/API/InputEvent); for others,
|
||||
the interface is [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event). If
|
||||
the input is cleared on edit, the type is `null`.
|
||||
*/
|
||||
ionInput: EventEmitter<CustomEvent<IIonInputInputInputEventDetail>>;
|
||||
/**
|
||||
* The `ionChange` event is fired when the user modifies the input's value.
|
||||
Unlike the `ionInput` event, the `ionChange` event is only fired when changes
|
||||
are committed, not as the user types.
|
||||
|
||||
Depending on the way the users interacts with the element, the `ionChange`
|
||||
event fires at a different moment:
|
||||
- When the user commits the change explicitly (e.g. by selecting a date
|
||||
from a date picker for `<ion-input type="date">`, pressing the "Enter" key, etc.).
|
||||
- When the element loses focus after its value has changed: for elements
|
||||
where the user's interaction is typing.
|
||||
*/
|
||||
ionChange: EventEmitter<CustomEvent<IIonInputInputChangeEventDetail>>;
|
||||
/**
|
||||
* Emitted when the input loses focus.
|
||||
*/
|
||||
ionBlur: EventEmitter<CustomEvent<FocusEvent>>;
|
||||
/**
|
||||
* Emitted when the input has focus.
|
||||
*/
|
||||
ionFocus: EventEmitter<CustomEvent<FocusEvent>>;
|
||||
}
|
||||
2286
packages/angular/standalone/src/directives/proxies.ts
Normal file
2286
packages/angular/standalone/src/directives/proxies.ts
Normal file
File diff suppressed because it is too large
Load Diff
59
packages/angular/standalone/src/directives/radio-group.ts
Normal file
59
packages/angular/standalone/src/directives/radio-group.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
Injector,
|
||||
NgZone,
|
||||
} from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { ValueAccessor } from '@ionic/angular/common';
|
||||
import type { RadioGroupChangeEventDetail, Components } from '@ionic/core/components';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-radio-group.js';
|
||||
|
||||
import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';
|
||||
|
||||
const RADIO_GROUP_INPUTS = ['allowEmptySelection', 'name', 'value'];
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineCustomElement,
|
||||
inputs: RADIO_GROUP_INPUTS,
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-radio-group',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: RADIO_GROUP_INPUTS,
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: IonRadioGroup,
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
standalone: true,
|
||||
})
|
||||
export class IonRadioGroup extends ValueAccessor {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) {
|
||||
super(injector, r);
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
proxyOutputs(this, this.el, ['ionChange']);
|
||||
}
|
||||
|
||||
@HostListener('ionChange', ['$event.target'])
|
||||
handleIonChange(el: HTMLIonRadioGroupElement): void {
|
||||
this.handleValueChange(el, el.value);
|
||||
}
|
||||
}
|
||||
|
||||
export declare interface IonRadioGroup extends Components.IonRadioGroup {
|
||||
/**
|
||||
* Emitted when the value has changed.
|
||||
*/
|
||||
ionChange: EventEmitter<CustomEvent<RadioGroupChangeEventDetail>>;
|
||||
}
|
||||
67
packages/angular/standalone/src/directives/radio.ts
Normal file
67
packages/angular/standalone/src/directives/radio.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
Injector,
|
||||
NgZone,
|
||||
} from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { ValueAccessor } from '@ionic/angular/common';
|
||||
import type { Components } from '@ionic/core/components';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-radio.js';
|
||||
|
||||
import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';
|
||||
|
||||
const RADIO_INPUTS = ['color', 'disabled', 'justify', 'labelPlacement', 'legacy', 'mode', 'name', 'value'];
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineCustomElement,
|
||||
inputs: RADIO_INPUTS,
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-radio',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: RADIO_INPUTS,
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: IonRadio,
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
standalone: true,
|
||||
})
|
||||
export class IonRadio extends ValueAccessor {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) {
|
||||
super(injector, r);
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
proxyOutputs(this, this.el, ['ionFocus', 'ionBlur']);
|
||||
}
|
||||
|
||||
@HostListener('ionSelect', ['$event.target'])
|
||||
handleIonSelect(el: any): void {
|
||||
/**
|
||||
* The `el` type is any to access the `checked` state property
|
||||
* that is not exposed on the type interface.
|
||||
*/
|
||||
this.handleValueChange(el, el.checked);
|
||||
}
|
||||
}
|
||||
|
||||
export declare interface IonRadio extends Components.IonRadio {
|
||||
/**
|
||||
* Emitted when the radio button has focus.
|
||||
*/
|
||||
ionFocus: EventEmitter<CustomEvent<void>>;
|
||||
/**
|
||||
* Emitted when the radio button loses focus.
|
||||
*/
|
||||
ionBlur: EventEmitter<CustomEvent<void>>;
|
||||
}
|
||||
112
packages/angular/standalone/src/directives/range.ts
Normal file
112
packages/angular/standalone/src/directives/range.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
Injector,
|
||||
NgZone,
|
||||
} from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { ValueAccessor } from '@ionic/angular/common';
|
||||
import type {
|
||||
RangeChangeEventDetail,
|
||||
RangeKnobMoveStartEventDetail,
|
||||
RangeKnobMoveEndEventDetail,
|
||||
Components,
|
||||
} from '@ionic/core/components';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-range.js';
|
||||
|
||||
import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';
|
||||
|
||||
const RANGE_INPUTS = [
|
||||
'activeBarStart',
|
||||
'color',
|
||||
'debounce',
|
||||
'disabled',
|
||||
'dualKnobs',
|
||||
'label',
|
||||
'labelPlacement',
|
||||
'legacy',
|
||||
'max',
|
||||
'min',
|
||||
'mode',
|
||||
'name',
|
||||
'pin',
|
||||
'pinFormatter',
|
||||
'snaps',
|
||||
'step',
|
||||
'ticks',
|
||||
'value',
|
||||
];
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineCustomElement,
|
||||
inputs: RANGE_INPUTS,
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-range',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: RANGE_INPUTS,
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: IonRange,
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
standalone: true,
|
||||
})
|
||||
export class IonRange extends ValueAccessor {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) {
|
||||
super(injector, r);
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
proxyOutputs(this, this.el, ['ionChange', 'ionInput', 'ionFocus', 'ionBlur', 'ionKnobMoveStart', 'ionKnobMoveEnd']);
|
||||
}
|
||||
|
||||
@HostListener('ionChange', ['$event.target'])
|
||||
handleIonChange(el: HTMLIonRangeElement): void {
|
||||
this.handleValueChange(el, el.value);
|
||||
}
|
||||
}
|
||||
|
||||
export declare interface IonRange extends Components.IonRange {
|
||||
/**
|
||||
* The `ionChange` event is fired for `<ion-range>` elements when the user
|
||||
modifies the element's value:
|
||||
- When the user releases the knob after dragging;
|
||||
- When the user moves the knob with keyboard arrows
|
||||
|
||||
`ionChange` is not fired when the value is changed programmatically.
|
||||
*/
|
||||
ionChange: EventEmitter<CustomEvent<RangeChangeEventDetail>>;
|
||||
/**
|
||||
* The `ionInput` event is fired for `<ion-range>` elements when the value
|
||||
is modified. Unlike `ionChange`, `ionInput` is fired continuously
|
||||
while the user is dragging the knob.
|
||||
*/
|
||||
ionInput: EventEmitter<CustomEvent<RangeChangeEventDetail>>;
|
||||
/**
|
||||
* Emitted when the range has focus.
|
||||
*/
|
||||
ionFocus: EventEmitter<CustomEvent<void>>;
|
||||
/**
|
||||
* Emitted when the range loses focus.
|
||||
*/
|
||||
ionBlur: EventEmitter<CustomEvent<void>>;
|
||||
/**
|
||||
* Emitted when the user starts moving the range knob, whether through
|
||||
mouse drag, touch gesture, or keyboard interaction.
|
||||
*/
|
||||
ionKnobMoveStart: EventEmitter<CustomEvent<RangeKnobMoveStartEventDetail>>;
|
||||
/**
|
||||
* Emitted when the user finishes moving the range knob, whether through
|
||||
mouse drag, touch gesture, or keyboard interaction.
|
||||
*/
|
||||
ionKnobMoveEnd: EventEmitter<CustomEvent<RangeKnobMoveEndEventDetail>>;
|
||||
}
|
||||
108
packages/angular/standalone/src/directives/searchbar.ts
Normal file
108
packages/angular/standalone/src/directives/searchbar.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
Injector,
|
||||
NgZone,
|
||||
} from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { ValueAccessor } from '@ionic/angular/common';
|
||||
import type { SearchbarInputEventDetail, SearchbarChangeEventDetail, Components } from '@ionic/core/components';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-searchbar.js';
|
||||
|
||||
import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';
|
||||
|
||||
const SEARCHBAR_INPUTS = [
|
||||
'animated',
|
||||
'autocomplete',
|
||||
'autocorrect',
|
||||
'cancelButtonIcon',
|
||||
'cancelButtonText',
|
||||
'clearIcon',
|
||||
'color',
|
||||
'debounce',
|
||||
'disabled',
|
||||
'enterkeyhint',
|
||||
'inputmode',
|
||||
'mode',
|
||||
'name',
|
||||
'placeholder',
|
||||
'searchIcon',
|
||||
'showCancelButton',
|
||||
'showClearButton',
|
||||
'spellcheck',
|
||||
'type',
|
||||
'value',
|
||||
];
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineCustomElement,
|
||||
inputs: SEARCHBAR_INPUTS,
|
||||
methods: ['setFocus', 'getInputElement'],
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-searchbar',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: SEARCHBAR_INPUTS,
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: IonSearchbar,
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
standalone: true,
|
||||
})
|
||||
export class IonSearchbar extends ValueAccessor {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) {
|
||||
super(injector, r);
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
proxyOutputs(this, this.el, ['ionInput', 'ionChange', 'ionCancel', 'ionClear', 'ionBlur', 'ionFocus']);
|
||||
}
|
||||
|
||||
@HostListener('ionInput', ['$event.target'])
|
||||
handleIonInput(el: HTMLIonSearchbarElement): void {
|
||||
this.handleValueChange(el, el.value);
|
||||
}
|
||||
}
|
||||
|
||||
export declare interface IonSearchbar extends Components.IonSearchbar {
|
||||
/**
|
||||
* Emitted when the `value` of the `ion-searchbar` element has changed.
|
||||
*/
|
||||
ionInput: EventEmitter<CustomEvent<SearchbarInputEventDetail>>;
|
||||
/**
|
||||
* The `ionChange` event is fired for `<ion-searchbar>` elements when the user
|
||||
modifies the element's value. Unlike the `ionInput` event, the `ionChange`
|
||||
event is not necessarily fired for each alteration to an element's value.
|
||||
|
||||
The `ionChange` event is fired when the value has been committed
|
||||
by the user. This can happen when the element loses focus or
|
||||
when the "Enter" key is pressed. `ionChange` can also fire
|
||||
when clicking the clear or cancel buttons.
|
||||
*/
|
||||
ionChange: EventEmitter<CustomEvent<SearchbarChangeEventDetail>>;
|
||||
/**
|
||||
* Emitted when the cancel button is clicked.
|
||||
*/
|
||||
ionCancel: EventEmitter<CustomEvent<void>>;
|
||||
/**
|
||||
* Emitted when the clear input button is clicked.
|
||||
*/
|
||||
ionClear: EventEmitter<CustomEvent<void>>;
|
||||
/**
|
||||
* Emitted when the input loses focus.
|
||||
*/
|
||||
ionBlur: EventEmitter<CustomEvent<void>>;
|
||||
/**
|
||||
* Emitted when the input has focus.
|
||||
*/
|
||||
ionFocus: EventEmitter<CustomEvent<void>>;
|
||||
}
|
||||
60
packages/angular/standalone/src/directives/segment.ts
Normal file
60
packages/angular/standalone/src/directives/segment.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
Injector,
|
||||
NgZone,
|
||||
} from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { ValueAccessor } from '@ionic/angular/common';
|
||||
import type { SegmentChangeEventDetail, Components } from '@ionic/core/components';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-segment.js';
|
||||
|
||||
import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';
|
||||
|
||||
const SEGMENT_INPUTS = ['color', 'disabled', 'mode', 'scrollable', 'selectOnFocus', 'swipeGesture', 'value'];
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineCustomElement,
|
||||
inputs: SEGMENT_INPUTS,
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-segment',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: SEGMENT_INPUTS,
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: IonSegment,
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
standalone: true,
|
||||
})
|
||||
export class IonSegment extends ValueAccessor {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) {
|
||||
super(injector, r);
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
proxyOutputs(this, this.el, ['ionChange']);
|
||||
}
|
||||
|
||||
@HostListener('ionChange', ['$event.target'])
|
||||
handleIonChange(el: HTMLIonSegmentElement): void {
|
||||
this.handleValueChange(el, el.value);
|
||||
}
|
||||
}
|
||||
|
||||
export declare interface IonSegment extends Components.IonSegment {
|
||||
/**
|
||||
* Emitted when the value property has changed and any
|
||||
dragging pointer has been released from `ion-segment`.
|
||||
*/
|
||||
ionChange: EventEmitter<CustomEvent<SegmentChangeEventDetail>>;
|
||||
}
|
||||
98
packages/angular/standalone/src/directives/select.ts
Normal file
98
packages/angular/standalone/src/directives/select.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
Injector,
|
||||
NgZone,
|
||||
} from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { ValueAccessor } from '@ionic/angular/common';
|
||||
import type { SelectChangeEventDetail, Components } from '@ionic/core/components';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-select.js';
|
||||
|
||||
import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';
|
||||
|
||||
const SELECT_INPUTS = [
|
||||
'cancelText',
|
||||
'color',
|
||||
'compareWith',
|
||||
'disabled',
|
||||
'expandedIcon',
|
||||
'fill',
|
||||
'interface',
|
||||
'interfaceOptions',
|
||||
'justify',
|
||||
'label',
|
||||
'labelPlacement',
|
||||
'legacy',
|
||||
'mode',
|
||||
'multiple',
|
||||
'name',
|
||||
'okText',
|
||||
'placeholder',
|
||||
'selectedText',
|
||||
'shape',
|
||||
'toggleIcon',
|
||||
'value',
|
||||
];
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineCustomElement,
|
||||
inputs: SELECT_INPUTS,
|
||||
methods: ['open'],
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-select',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: SELECT_INPUTS,
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: IonSelect,
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
standalone: true,
|
||||
})
|
||||
export class IonSelect extends ValueAccessor {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) {
|
||||
super(injector, r);
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
proxyOutputs(this, this.el, ['ionChange', 'ionCancel', 'ionDismiss', 'ionFocus', 'ionBlur']);
|
||||
}
|
||||
|
||||
@HostListener('ionChange', ['$event.target'])
|
||||
handleIonChange(el: HTMLIonSelectElement): void {
|
||||
this.handleValueChange(el, el.value);
|
||||
}
|
||||
}
|
||||
|
||||
export declare interface IonSelect extends Components.IonSelect {
|
||||
/**
|
||||
* Emitted when the value has changed.
|
||||
*/
|
||||
ionChange: EventEmitter<CustomEvent<SelectChangeEventDetail>>;
|
||||
/**
|
||||
* Emitted when the selection is cancelled.
|
||||
*/
|
||||
ionCancel: EventEmitter<CustomEvent<void>>;
|
||||
/**
|
||||
* Emitted when the overlay is dismissed.
|
||||
*/
|
||||
ionDismiss: EventEmitter<CustomEvent<void>>;
|
||||
/**
|
||||
* Emitted when the select has focus.
|
||||
*/
|
||||
ionFocus: EventEmitter<CustomEvent<void>>;
|
||||
/**
|
||||
* Emitted when the select loses focus.
|
||||
*/
|
||||
ionBlur: EventEmitter<CustomEvent<void>>;
|
||||
}
|
||||
110
packages/angular/standalone/src/directives/textarea.ts
Normal file
110
packages/angular/standalone/src/directives/textarea.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
Injector,
|
||||
NgZone,
|
||||
} from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { ValueAccessor } from '@ionic/angular/common';
|
||||
import type { TextareaChangeEventDetail, TextareaInputEventDetail, Components } from '@ionic/core/components';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-textarea.js';
|
||||
|
||||
import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';
|
||||
|
||||
const TEXTAREA_INPUTS = [
|
||||
'autoGrow',
|
||||
'autocapitalize',
|
||||
'autofocus',
|
||||
'clearOnEdit',
|
||||
'color',
|
||||
'cols',
|
||||
'counter',
|
||||
'counterFormatter',
|
||||
'debounce',
|
||||
'disabled',
|
||||
'enterkeyhint',
|
||||
'errorText',
|
||||
'fill',
|
||||
'helperText',
|
||||
'inputmode',
|
||||
'label',
|
||||
'labelPlacement',
|
||||
'legacy',
|
||||
'maxlength',
|
||||
'minlength',
|
||||
'mode',
|
||||
'name',
|
||||
'placeholder',
|
||||
'readonly',
|
||||
'required',
|
||||
'rows',
|
||||
'shape',
|
||||
'spellcheck',
|
||||
'value',
|
||||
'wrap',
|
||||
];
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineCustomElement,
|
||||
inputs: TEXTAREA_INPUTS,
|
||||
methods: ['setFocus', 'getInputElement'],
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-textarea',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: TEXTAREA_INPUTS,
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: IonTextarea,
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
standalone: true,
|
||||
})
|
||||
export class IonTextarea extends ValueAccessor {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) {
|
||||
super(injector, r);
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
proxyOutputs(this, this.el, ['ionChange', 'ionInput', 'ionBlur', 'ionFocus']);
|
||||
}
|
||||
|
||||
@HostListener('ionInput', ['$event.target'])
|
||||
handleIonInput(el: HTMLIonTextareaElement): void {
|
||||
this.handleValueChange(el, el.value);
|
||||
}
|
||||
}
|
||||
|
||||
export declare interface IonTextarea extends Components.IonTextarea {
|
||||
/**
|
||||
* The `ionChange` event is fired when the user modifies the textarea's value.
|
||||
Unlike the `ionInput` event, the `ionChange` event is fired when
|
||||
the element loses focus after its value has been modified.
|
||||
*/
|
||||
ionChange: EventEmitter<CustomEvent<TextareaChangeEventDetail>>;
|
||||
/**
|
||||
* The `ionInput` event is fired each time the user modifies the textarea's value.
|
||||
Unlike the `ionChange` event, the `ionInput` event is fired for each alteration
|
||||
to the textarea's value. This typically happens for each keystroke as the user types.
|
||||
|
||||
When `clearOnEdit` is enabled, the `ionInput` event will be fired when
|
||||
the user clears the textarea by performing a keydown event.
|
||||
*/
|
||||
ionInput: EventEmitter<CustomEvent<TextareaInputEventDetail>>;
|
||||
/**
|
||||
* Emitted when the input loses focus.
|
||||
*/
|
||||
ionBlur: EventEmitter<CustomEvent<FocusEvent>>;
|
||||
/**
|
||||
* Emitted when the input has focus.
|
||||
*/
|
||||
ionFocus: EventEmitter<CustomEvent<FocusEvent>>;
|
||||
}
|
||||
84
packages/angular/standalone/src/directives/toggle.ts
Normal file
84
packages/angular/standalone/src/directives/toggle.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
Injector,
|
||||
NgZone,
|
||||
} from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { ValueAccessor, setIonicClasses } from '@ionic/angular/common';
|
||||
import type { ToggleChangeEventDetail, Components } from '@ionic/core/components';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-toggle.js';
|
||||
|
||||
import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';
|
||||
|
||||
const TOGGLE_INPUTS = [
|
||||
'checked',
|
||||
'color',
|
||||
'disabled',
|
||||
'enableOnOffLabels',
|
||||
'justify',
|
||||
'labelPlacement',
|
||||
'legacy',
|
||||
'mode',
|
||||
'name',
|
||||
'value',
|
||||
];
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineCustomElement,
|
||||
inputs: TOGGLE_INPUTS,
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-toggle',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: TOGGLE_INPUTS,
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: IonToggle,
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
standalone: true,
|
||||
})
|
||||
export class IonToggle extends ValueAccessor {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) {
|
||||
super(injector, r);
|
||||
c.detach();
|
||||
this.el = r.nativeElement;
|
||||
proxyOutputs(this, this.el, ['ionChange', 'ionFocus', 'ionBlur']);
|
||||
}
|
||||
|
||||
writeValue(value: boolean): void {
|
||||
this.elementRef.nativeElement.checked = this.lastValue = value;
|
||||
setIonicClasses(this.elementRef);
|
||||
}
|
||||
|
||||
@HostListener('ionChange', ['$event.target'])
|
||||
handleIonChange(el: HTMLIonToggleElement): void {
|
||||
this.handleValueChange(el, el.checked);
|
||||
}
|
||||
}
|
||||
|
||||
export declare interface IonToggle extends Components.IonToggle {
|
||||
/**
|
||||
* Emitted when the user switches the toggle on or off. Does not emit
|
||||
when programmatically changing the value of the `checked` property.
|
||||
*/
|
||||
ionChange: EventEmitter<CustomEvent<ToggleChangeEventDetail>>;
|
||||
/**
|
||||
* Emitted when the toggle has focus.
|
||||
*/
|
||||
ionFocus: EventEmitter<CustomEvent<void>>;
|
||||
/**
|
||||
* Emitted when the toggle loses focus.
|
||||
*/
|
||||
ionBlur: EventEmitter<CustomEvent<void>>;
|
||||
}
|
||||
127
packages/angular/standalone/src/index.ts
Normal file
127
packages/angular/standalone/src/index.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
export { IonBackButton } from './navigation/back-button';
|
||||
export { IonModal } from './overlays/modal';
|
||||
export { IonPopover } from './overlays/popover';
|
||||
export { IonRouterOutlet } from './navigation/router-outlet';
|
||||
export { IonRouterLink, IonRouterLinkWithHref } from './navigation/router-link-delegate';
|
||||
export { IonTabs } from './navigation/tabs';
|
||||
export { provideIonicAngular } from './providers/ionic-angular';
|
||||
export {
|
||||
ActionSheetController,
|
||||
AlertController,
|
||||
LoadingController,
|
||||
MenuController,
|
||||
ModalController,
|
||||
PickerController,
|
||||
PopoverController,
|
||||
ToastController,
|
||||
AnimationController,
|
||||
GestureController,
|
||||
DomController,
|
||||
NavController,
|
||||
Config,
|
||||
Platform,
|
||||
NavParams,
|
||||
IonicRouteStrategy,
|
||||
} from '@ionic/angular/common';
|
||||
export { IonNav } from './navigation/nav';
|
||||
export {
|
||||
IonCheckbox,
|
||||
IonDatetime,
|
||||
IonInput,
|
||||
IonIcon,
|
||||
IonRadioGroup,
|
||||
IonRadio,
|
||||
IonRange,
|
||||
IonSearchbar,
|
||||
IonSegment,
|
||||
IonSelect,
|
||||
IonTextarea,
|
||||
IonToggle,
|
||||
} from './directives';
|
||||
export * from './directives/proxies';
|
||||
|
||||
export {
|
||||
// UTILS
|
||||
createAnimation,
|
||||
createGesture,
|
||||
iosTransitionAnimation,
|
||||
mdTransitionAnimation,
|
||||
IonicSlides,
|
||||
getPlatforms,
|
||||
isPlatform,
|
||||
getTimeGivenProgression,
|
||||
// TYPES
|
||||
Animation,
|
||||
AnimationBuilder,
|
||||
AnimationCallbackOptions,
|
||||
AnimationDirection,
|
||||
AnimationFill,
|
||||
AnimationKeyFrames,
|
||||
AnimationLifecycle,
|
||||
Gesture,
|
||||
GestureConfig,
|
||||
GestureDetail,
|
||||
NavComponentWithProps,
|
||||
SpinnerTypes,
|
||||
AccordionGroupCustomEvent,
|
||||
AccordionGroupChangeEventDetail,
|
||||
BreadcrumbCustomEvent,
|
||||
BreadcrumbCollapsedClickEventDetail,
|
||||
ActionSheetOptions,
|
||||
ActionSheetButton,
|
||||
AlertOptions,
|
||||
AlertInput,
|
||||
AlertButton,
|
||||
BackButtonEvent,
|
||||
CheckboxCustomEvent,
|
||||
CheckboxChangeEventDetail,
|
||||
DatetimeCustomEvent,
|
||||
DatetimeChangeEventDetail,
|
||||
InfiniteScrollCustomEvent,
|
||||
InputCustomEvent,
|
||||
InputChangeEventDetail,
|
||||
ItemReorderEventDetail,
|
||||
ItemReorderCustomEvent,
|
||||
ItemSlidingCustomEvent,
|
||||
IonicSafeString,
|
||||
LoadingOptions,
|
||||
MenuCustomEvent,
|
||||
ModalOptions,
|
||||
NavCustomEvent,
|
||||
PickerOptions,
|
||||
PickerButton,
|
||||
PickerColumn,
|
||||
PickerColumnOption,
|
||||
PlatformConfig,
|
||||
PopoverOptions,
|
||||
RadioGroupCustomEvent,
|
||||
RadioGroupChangeEventDetail,
|
||||
RangeCustomEvent,
|
||||
RangeChangeEventDetail,
|
||||
RangeKnobMoveStartEventDetail,
|
||||
RangeKnobMoveEndEventDetail,
|
||||
RefresherCustomEvent,
|
||||
RefresherEventDetail,
|
||||
RouterEventDetail,
|
||||
RouterCustomEvent,
|
||||
ScrollBaseCustomEvent,
|
||||
ScrollBaseDetail,
|
||||
ScrollDetail,
|
||||
ScrollCustomEvent,
|
||||
SearchbarCustomEvent,
|
||||
SearchbarChangeEventDetail,
|
||||
SearchbarInputEventDetail,
|
||||
SegmentChangeEventDetail,
|
||||
SegmentCustomEvent,
|
||||
SegmentValue,
|
||||
SelectChangeEventDetail,
|
||||
SelectCustomEvent,
|
||||
TabsCustomEvent,
|
||||
TextareaChangeEventDetail,
|
||||
TextareaCustomEvent,
|
||||
ToastOptions,
|
||||
ToastButton,
|
||||
ToastLayout,
|
||||
ToggleChangeEventDetail,
|
||||
ToggleCustomEvent,
|
||||
} from '@ionic/core/components';
|
||||
28
packages/angular/standalone/src/navigation/back-button.ts
Normal file
28
packages/angular/standalone/src/navigation/back-button.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Component, Optional, ChangeDetectionStrategy, ElementRef, NgZone, ChangeDetectorRef } from '@angular/core';
|
||||
import { IonBackButton as IonBackButtonBase, NavController, Config, ProxyCmp } from '@ionic/angular/common';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-back-button.js';
|
||||
|
||||
import { IonRouterOutlet } from './router-outlet';
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineCustomElement,
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-back-button',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
standalone: true,
|
||||
})
|
||||
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
||||
export class IonBackButton extends IonBackButtonBase {
|
||||
constructor(
|
||||
@Optional() routerOutlet: IonRouterOutlet,
|
||||
navCtrl: NavController,
|
||||
config: Config,
|
||||
r: ElementRef,
|
||||
z: NgZone,
|
||||
c: ChangeDetectorRef
|
||||
) {
|
||||
super(routerOutlet, navCtrl, config, r, z, c);
|
||||
}
|
||||
}
|
||||
24
packages/angular/standalone/src/navigation/nav.ts
Normal file
24
packages/angular/standalone/src/navigation/nav.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Component, ElementRef, Injector, EnvironmentInjector, NgZone, ChangeDetectorRef } from '@angular/core';
|
||||
import { IonNav as IonNavBase, ProxyCmp, AngularDelegate } from '@ionic/angular/common';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-nav.js';
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineCustomElement,
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-nav',
|
||||
template: '<ng-content></ng-content>',
|
||||
standalone: true,
|
||||
})
|
||||
export class IonNav extends IonNavBase {
|
||||
constructor(
|
||||
ref: ElementRef,
|
||||
environmentInjector: EnvironmentInjector,
|
||||
injector: Injector,
|
||||
angularDelegate: AngularDelegate,
|
||||
z: NgZone,
|
||||
c: ChangeDetectorRef
|
||||
) {
|
||||
super(ref, environmentInjector, injector, angularDelegate, z, c);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Directive } from '@angular/core';
|
||||
import {
|
||||
RouterLinkDelegateDirective as RouterLinkDelegateBase,
|
||||
RouterLinkWithHrefDelegateDirective as RouterLinkHrefDelegateBase,
|
||||
} from '@ionic/angular/common';
|
||||
|
||||
@Directive({
|
||||
selector: ':not(a):not(area)[routerLink]',
|
||||
standalone: true,
|
||||
})
|
||||
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
||||
export class IonRouterLink extends RouterLinkDelegateBase {}
|
||||
|
||||
@Directive({
|
||||
selector: 'a[routerLink],area[routerLink]',
|
||||
standalone: true,
|
||||
})
|
||||
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
||||
export class IonRouterLinkWithHref extends RouterLinkHrefDelegateBase {}
|
||||
13
packages/angular/standalone/src/navigation/router-outlet.ts
Normal file
13
packages/angular/standalone/src/navigation/router-outlet.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Directive } from '@angular/core';
|
||||
import { IonRouterOutlet as IonRouterOutletBase, ProxyCmp } from '@ionic/angular/common';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-router-outlet.js';
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineCustomElement,
|
||||
})
|
||||
@Directive({
|
||||
selector: 'ion-router-outlet',
|
||||
standalone: true,
|
||||
})
|
||||
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
||||
export class IonRouterOutlet extends IonRouterOutletBase {}
|
||||
57
packages/angular/standalone/src/navigation/tabs.ts
Normal file
57
packages/angular/standalone/src/navigation/tabs.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { Component, ContentChild, ContentChildren, ViewChild, QueryList } from '@angular/core';
|
||||
import { IonTabs as IonTabsBase } from '@ionic/angular/common';
|
||||
|
||||
import { IonTabBar } from '../directives/proxies';
|
||||
|
||||
import { IonRouterOutlet } from './router-outlet';
|
||||
|
||||
@Component({
|
||||
selector: 'ion-tabs',
|
||||
template: `
|
||||
<ng-content select="[slot=top]"></ng-content>
|
||||
<div class="tabs-inner" #tabsInner>
|
||||
<ion-router-outlet
|
||||
#outlet
|
||||
tabs="true"
|
||||
(stackWillChange)="onStackWillChange($event)"
|
||||
(stackDidChange)="onStackDidChange($event)"
|
||||
></ion-router-outlet>
|
||||
</div>
|
||||
<ng-content></ng-content>
|
||||
`,
|
||||
standalone: true,
|
||||
styles: [
|
||||
`
|
||||
:host {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
flex-direction: column;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
contain: layout size style;
|
||||
}
|
||||
.tabs-inner {
|
||||
position: relative;
|
||||
|
||||
flex: 1;
|
||||
|
||||
contain: layout size style;
|
||||
}
|
||||
`,
|
||||
],
|
||||
imports: [IonRouterOutlet],
|
||||
})
|
||||
// eslint-disable-next-line @angular-eslint/component-class-suffix
|
||||
export class IonTabs extends IonTabsBase {
|
||||
@ViewChild('outlet', { read: IonRouterOutlet, static: false }) outlet: IonRouterOutlet;
|
||||
|
||||
@ContentChild(IonTabBar, { static: false }) tabBar: IonTabBar | undefined;
|
||||
@ContentChildren(IonTabBar) tabBars: QueryList<IonTabBar>;
|
||||
}
|
||||
18
packages/angular/standalone/src/overlays/modal.ts
Normal file
18
packages/angular/standalone/src/overlays/modal.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { IonModal as IonModalBase, ProxyCmp } from '@ionic/angular/common';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-modal.js';
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineCustomElement,
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-modal',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `<div class="ion-delegate-host ion-page" *ngIf="isCmpOpen || keepContentsMounted">
|
||||
<ng-container [ngTemplateOutlet]="template"></ng-container>
|
||||
</div>`,
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
})
|
||||
export class IonModal extends IonModalBase {}
|
||||
16
packages/angular/standalone/src/overlays/popover.ts
Normal file
16
packages/angular/standalone/src/overlays/popover.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { IonPopover as IonPopoverBase, ProxyCmp } from '@ionic/angular/common';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-popover.js';
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineCustomElement,
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-popover',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `<ng-container [ngTemplateOutlet]="template" *ngIf="isCmpOpen || keepContentsMounted"></ng-container>`,
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
})
|
||||
export class IonPopover extends IonPopoverBase {}
|
||||
51
packages/angular/standalone/src/providers/ionic-angular.ts
Normal file
51
packages/angular/standalone/src/providers/ionic-angular.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { APP_INITIALIZER } from '@angular/core';
|
||||
import type { Provider } from '@angular/core';
|
||||
import {
|
||||
AngularDelegate,
|
||||
ConfigToken,
|
||||
ModalController,
|
||||
PopoverController,
|
||||
provideComponentInputBinding,
|
||||
} from '@ionic/angular/common';
|
||||
import { initialize } from '@ionic/core/components';
|
||||
import type { IonicConfig } from '@ionic/core/components';
|
||||
|
||||
export const provideIonicAngular = (config?: IonicConfig): Provider[] => {
|
||||
/**
|
||||
* TODO FW-4967
|
||||
* Use makeEnvironmentProviders once Angular 14 support is dropped.
|
||||
* This prevents provideIonicAngular from being accidentally referenced in an @Component.
|
||||
*/
|
||||
return [
|
||||
{
|
||||
provide: ConfigToken,
|
||||
useValue: config,
|
||||
},
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: initializeIonicAngular,
|
||||
multi: true,
|
||||
deps: [ConfigToken, DOCUMENT],
|
||||
},
|
||||
provideComponentInputBinding(),
|
||||
AngularDelegate,
|
||||
ModalController,
|
||||
PopoverController,
|
||||
];
|
||||
};
|
||||
|
||||
const initializeIonicAngular = (config: IonicConfig, doc: Document) => {
|
||||
return () => {
|
||||
/**
|
||||
* By default Ionic Framework hides elements that
|
||||
* are not hydrated, but in the CE build there is no
|
||||
* hydration.
|
||||
* TODO FW-2797: Remove when all integrations have been
|
||||
* migrated to CE build.
|
||||
*/
|
||||
doc.documentElement.classList.add('ion-ce');
|
||||
|
||||
initialize(config);
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user