fix(config): allow LogLevel to work with isolatedModules and update all warns and errors to respect logLevel (#30350)

Issue number: internal

---------

## What is the current behavior?
- `LogLevel` throws error `Error: Cannot access ambient const enums when
'isolatedModules' is enabled`
- Several existing console warns and errors are not calling the function
that respects the `logLevel` config

## What is the new behavior?
- Remove `const` from the `enum` to work with `isolatedModules`
- Update `console.warn`s to `printIonWarning`
- Update `console.error`s to `printIonError`

## Does this introduce a breaking change?
- [ ] Yes
- [x] No

## Other information

Dev build: `8.5.5-dev.11744729748.174bf7e0`

---------

Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
This commit is contained in:
Brandy Smith
2025-04-16 12:23:16 -04:00
committed by GitHub
parent 8dd566b5c1
commit d52fca084c
41 changed files with 124 additions and 92 deletions

View File

@ -87,7 +87,7 @@ export class AccordionGroup implements ComponentInterface {
* Custom behavior: ['a', 'b']
*/
printIonWarning(
`ion-accordion-group was passed an array of values, but multiple="false". This is incorrect usage and may result in unexpected behaviors. To dismiss this warning, pass a string to the "value" property when multiple="false".
`[ion-accordion-group] - An array of values was passed, but multiple is "false". This is incorrect usage and may result in unexpected behaviors. To dismiss this warning, pass a string to the "value" property when multiple="false".
Value Passed: [${value.map((v) => `'${v}'`).join(', ')}]
`,

View File

@ -5,6 +5,7 @@ import type { Gesture } from '@utils/gesture';
import { createButtonActiveGesture } from '@utils/gesture/button-active';
import { raf } from '@utils/helpers';
import { createLockController } from '@utils/lock-controller';
import { printIonWarning } from '@utils/logging';
import {
createDelegateController,
createTriggerController,
@ -318,8 +319,8 @@ export class Alert implements ComponentInterface, OverlayInterface {
// checkboxes and inputs are all accepted, but they cannot be mixed.
const inputTypes = new Set(inputs.map((i) => i.type));
if (inputTypes.has('checkbox') && inputTypes.has('radio')) {
console.warn(
`Alert cannot mix input types: ${Array.from(inputTypes.values()).join(
printIonWarning(
`[ion-alert] - Alert cannot mix input types: ${Array.from(inputTypes.values()).join(
'/'
)}. Please see alert docs for more info.`
);

View File

@ -46,7 +46,7 @@ export class App implements ComponentInterface {
*/
if (shouldUseCloseWatcher()) {
printIonWarning(
'experimentalCloseWatcher was set to `true`, but hardwareBackButton was set to `false`. Both config options must be `true` for the Close Watcher API to be used.'
'[ion-app] - experimentalCloseWatcher was set to `true`, but hardwareBackButton was set to `false`. Both config options must be `true` for the Close Watcher API to be used.'
);
}

View File

@ -235,7 +235,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
* element with that id is not a form element.
*/
printIonWarning(
`Form with selector: "#${form}" could not be found. Verify that the id is attached to a <form> element.`,
`[ion-button] - Form with selector: "#${form}" could not be found. Verify that the id is attached to a <form> element.`,
this.el
);
return null;
@ -246,7 +246,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
* element with that id could not be found in the DOM.
*/
printIonWarning(
`Form with selector: "#${form}" could not be found. Verify that the id is correct and the form is rendered in the DOM.`,
`[ion-button] - Form with selector: "#${form}" could not be found. Verify that the id is correct and the form is rendered in the DOM.`,
this.el
);
return null;
@ -260,7 +260,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
* as the form attribute.
*/
printIonWarning(
`The provided "form" element is invalid. Verify that the form is a HTMLFormElement and rendered in the DOM.`,
`[ion-button] - The provided "form" element is invalid. Verify that the form is a HTMLFormElement and rendered in the DOM.`,
this.el
);
return null;

View File

@ -169,7 +169,7 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ title, config }) =>
expect(logs.length).toBe(1);
expect(logs[0]).toContain(
'[Ionic Warning]: Form with selector: "#missingForm" could not be found. Verify that the id is correct and the form is rendered in the DOM.'
'[Ionic Warning]: [ion-button] - Form with selector: "#missingForm" could not be found. Verify that the id is correct and the form is rendered in the DOM.'
);
});
@ -197,7 +197,7 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ title, config }) =>
expect(logs.length).toBe(1);
expect(logs[0]).toContain(
'[Ionic Warning]: The provided "form" element is invalid. Verify that the form is a HTMLFormElement and rendered in the DOM.'
'[Ionic Warning]: [ion-button] - The provided "form" element is invalid. Verify that the form is a HTMLFormElement and rendered in the DOM.'
);
});
});

View File

@ -63,7 +63,7 @@ export class DatetimeButton implements ComponentInterface {
const { datetime } = this;
if (!datetime) {
printIonError(
'An ID associated with an ion-datetime instance is required for ion-datetime-button to function properly.',
'[ion-datetime-button] - An ID associated with an ion-datetime instance is required to function properly.',
this.el
);
return;
@ -71,7 +71,7 @@ export class DatetimeButton implements ComponentInterface {
const datetimeEl = (this.datetimeEl = document.getElementById(datetime) as HTMLIonDatetimeElement | null);
if (!datetimeEl) {
printIonError(`No ion-datetime instance found for ID '${datetime}'.`, this.el);
printIonError(`[ion-datetime-button] - No ion-datetime instance found for ID '${datetime}'.`, this.el);
return;
}
@ -81,7 +81,7 @@ export class DatetimeButton implements ComponentInterface {
*/
if (datetimeEl.tagName !== 'ION-DATETIME') {
printIonError(
`Expected an ion-datetime instance for ID '${datetime}' but received '${datetimeEl.tagName.toLowerCase()}' instead.`,
`[ion-datetime-button] - Expected an ion-datetime instance for ID '${datetime}' but received '${datetimeEl.tagName.toLowerCase()}' instead.`,
datetimeEl
);
return;
@ -245,7 +245,7 @@ export class DatetimeButton implements ComponentInterface {
try {
headerText = titleSelectedDatesFormatter(parsedValues);
} catch (e) {
printIonError('Exception in provided `titleSelectedDatesFormatter`: ', e);
printIonError('[ion-datetime-button] - Exception in provided `titleSelectedDatesFormatter`:', e);
}
}
this.dateText = headerText;

View File

@ -584,7 +584,7 @@ export class Datetime implements ComponentInterface {
* Custom behavior: ['a', 'b']
*/
printIonWarning(
`ion-datetime was passed an array of values, but multiple="false". This is incorrect usage and may result in unexpected behaviors. To dismiss this warning, pass a string to the "value" property when multiple="false".
`[ion-datetime] - An array of values was passed, but multiple is "false". This is incorrect usage and may result in unexpected behaviors. To dismiss this warning, pass a string to the "value" property when multiple="false".
Value Passed: [${value.map((v) => `'${v}'`).join(', ')}]
`,
@ -1389,24 +1389,24 @@ export class Datetime implements ComponentInterface {
if (multiple) {
if (presentation !== 'date') {
printIonWarning('Multiple date selection is only supported for presentation="date".', el);
printIonWarning('[ion-datetime] - Multiple date selection is only supported for presentation="date".', el);
}
if (preferWheel) {
printIonWarning('Multiple date selection is not supported with preferWheel="true".', el);
printIonWarning('[ion-datetime] - Multiple date selection is not supported with preferWheel="true".', el);
}
}
if (highlightedDates !== undefined) {
if (presentation !== 'date' && presentation !== 'date-time' && presentation !== 'time-date') {
printIonWarning(
'The highlightedDates property is only supported with the date, date-time, and time-date presentations.',
'[ion-datetime] - The highlightedDates property is only supported with the date, date-time, and time-date presentations.',
el
);
}
if (preferWheel) {
printIonWarning('The highlightedDates property is not supported with preferWheel="true".', el);
printIonWarning('[ion-datetime] - The highlightedDates property is not supported with preferWheel="true".', el);
}
}
@ -1668,7 +1668,7 @@ export class Datetime implements ComponentInterface {
disabled = !isDateEnabled(convertDataToISO(referenceParts));
} catch (e) {
printIonError(
'Exception thrown from provided `isDateEnabled` function. Please check your function and try again.',
'[ion-datetime] - Exception thrown from provided `isDateEnabled` function. Please check your function and try again.',
e
);
}
@ -1759,7 +1759,7 @@ export class Datetime implements ComponentInterface {
disabled = !isDateEnabled(convertDataToISO(referenceParts));
} catch (e) {
printIonError(
'Exception thrown from provided `isDateEnabled` function. Please check your function and try again.',
'[ion-datetime] - Exception thrown from provided `isDateEnabled` function. Please check your function and try again.',
e
);
}
@ -2262,7 +2262,7 @@ export class Datetime implements ComponentInterface {
isCalDayDisabled = !isDateEnabled(dateIsoString);
} catch (e) {
printIonError(
'Exception thrown from provided `isDateEnabled` function. Please check your function and try again.',
'[ion-datetime] - Exception thrown from provided `isDateEnabled` function. Please check your function and try again.',
el,
e
);
@ -2483,7 +2483,7 @@ export class Datetime implements ComponentInterface {
try {
headerText = titleSelectedDatesFormatter(convertDataToISO(activeParts));
} catch (e) {
printIonError('Exception in provided `titleSelectedDatesFormatter`: ', e);
printIonError('[ion-datetime] - Exception in provided `titleSelectedDatesFormatter`:', e);
}
}
} else {

View File

@ -687,7 +687,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
expect(logs.length).toBe(1);
expect(logs[0]).toContain(
'[Ionic Warning]: Datetime: "timeZone" and "timeZoneName" are not supported in "formatOptions".'
'[Ionic Warning]: [ion-datetime] - "timeZone" and "timeZoneName" are not supported in "formatOptions".'
);
});
@ -717,7 +717,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
expect(logs.length).toBe(1);
expect(logs[0]).toContain(
"[Ionic Warning]: Datetime: The 'date-time' presentation requires either a date or time object (or both) in formatOptions."
"[Ionic Warning]: [ion-datetime] - The 'date-time' presentation requires either a date or time object (or both) in formatOptions."
);
});
});

View File

@ -48,7 +48,7 @@ export const warnIfValueOutOfBounds = (
for (const val of valueArray) {
if ((min !== undefined && isBefore(val, min)) || (max !== undefined && isAfter(val, max))) {
printIonWarning(
'The value provided to ion-datetime is out of bounds.\n\n' +
'[ion-datetime] - The value provided to ion-datetime is out of bounds.\n\n' +
`Min: ${JSON.stringify(min)}\n` +
`Max: ${JSON.stringify(max)}\n` +
`Value: ${JSON.stringify(value)}`

View File

@ -105,7 +105,9 @@ export function parseDate(val: string | string[] | undefined | null): DatetimePa
if (parse === null) {
// wasn't able to parse the ISO datetime
printIonWarning(`Unable to parse date string: ${val}. Please provide a valid ISO 8601 datetime string.`);
printIonWarning(
`[ion-datetime] - Unable to parse date string: ${val}. Please provide a valid ISO 8601 datetime string.`
);
return undefined;
}

View File

@ -218,7 +218,7 @@ export const getHighlightStyles = (
return highlightedDates(dateIsoString);
} catch (e) {
printIonError(
'Exception thrown from provided `highlightedDates` callback. Please check your function and try again.',
'[ion-datetime] - Exception thrown from provided `highlightedDates` callback. Please check your function and try again.',
el,
e
);

View File

@ -14,7 +14,7 @@ export const warnIfTimeZoneProvided = (el: HTMLElement, formatOptions?: FormatOp
formatOptions?.time?.timeZone ||
formatOptions?.time?.timeZoneName
) {
printIonWarning('Datetime: "timeZone" and "timeZoneName" are not supported in "formatOptions".', el);
printIonWarning('[ion-datetime] - "timeZone" and "timeZoneName" are not supported in "formatOptions".', el);
}
};
@ -33,19 +33,22 @@ export const checkForPresentationFormatMismatch = (
case 'month':
case 'year':
if (formatOptions.date === undefined) {
printIonWarning(`Datetime: The '${presentation}' presentation requires a date object in formatOptions.`, el);
printIonWarning(
`[ion-datetime] - The '${presentation}' presentation requires a date object in formatOptions.`,
el
);
}
break;
case 'time':
if (formatOptions.time === undefined) {
printIonWarning(`Datetime: The 'time' presentation requires a time object in formatOptions.`, el);
printIonWarning(`[ion-datetime] - The 'time' presentation requires a time object in formatOptions.`, el);
}
break;
case 'date-time':
case 'time-date':
if (formatOptions.date === undefined && formatOptions.time === undefined) {
printIonWarning(
`Datetime: The '${presentation}' presentation requires either a date or time object (or both) in formatOptions.`,
`[ion-datetime] - The '${presentation}' presentation requires either a date or time object (or both) in formatOptions.`,
el
);
}

View File

@ -59,7 +59,7 @@ export class InputPasswordToggle implements ComponentInterface {
onTypeChange(newValue: TextFieldTypes) {
if (newValue !== 'text' && newValue !== 'password') {
printIonWarning(
`ion-input-password-toggle only supports inputs of type "text" or "password". Input of type "${newValue}" is not compatible.`,
`[ion-input-password-toggle] - Only inputs of type "text" or "password" are supported. Input of type "${newValue}" is not compatible.`,
this.el
);
@ -74,7 +74,7 @@ export class InputPasswordToggle implements ComponentInterface {
if (!inputElRef) {
printIonWarning(
'No ancestor ion-input found for ion-input-password-toggle. This component must be slotted inside of an ion-input.',
'[ion-input-password-toggle] - No ancestor ion-input found. This component must be slotted inside of an ion-input.',
el
);

View File

@ -24,7 +24,7 @@ export const getCounterText = (
try {
return counterFormatter(valueLength, maxLength);
} catch (e) {
printIonError('Exception in provided `counterFormatter`.', e);
printIonError('[ion-input] - Exception in provided `counterFormatter`:', e);
return defaultCounterText;
}
};

View File

@ -2,6 +2,7 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Method, Prop, State, Watch, h } from '@stencil/core';
import { findClosestIonContent, disableContentScrollY, resetContentScrollY } from '@utils/content';
import { isEndSide } from '@utils/helpers';
import { printIonWarning } from '@utils/logging';
import { watchForOptions } from '@utils/watch-options';
import { getIonMode } from '../../global/ionic-global';
@ -343,7 +344,7 @@ export class ItemSliding implements ComponentInterface {
case ItemSide.None:
return;
default:
console.warn('invalid ItemSideFlags value', this.sides);
printIonWarning('[ion-item-sliding] - invalid ItemSideFlags value', this.sides);
break;
}

View File

@ -6,6 +6,7 @@ import { GESTURE_CONTROLLER } from '@utils/gesture';
import { shouldUseCloseWatcher } from '@utils/hardware-back-button';
import type { Attributes } from '@utils/helpers';
import { inheritAriaAttributes, assert, clamp, isEndSide as isEnd } from '@utils/helpers';
import { printIonError } from '@utils/logging';
import { menuController } from '@utils/menu-controller';
import { BACKDROP, GESTURE, getPresentedOverlay } from '@utils/overlays';
import { isPlatform } from '@utils/platform';
@ -215,13 +216,13 @@ export class Menu implements ComponentInterface, MenuI {
const content = this.contentId !== undefined ? document.getElementById(this.contentId) : null;
if (content === null) {
console.error('Menu: must have a "content" element to listen for drag events on.');
printIonError('[ion-menu] - Must have a "content" element to listen for drag events on.');
return;
}
if (this.el.contains(content)) {
console.error(
`Menu: "contentId" should refer to the main view's ion-content, not the ion-content inside of the ion-menu.`
printIonError(
`[ion-menu] - The "contentId" should refer to the main view's ion-content, not the ion-content inside of the ion-menu.`
);
}

View File

@ -427,7 +427,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
}
if (breakpoints !== undefined && initialBreakpoint !== undefined && !breakpoints.includes(initialBreakpoint)) {
printIonWarning('Your breakpoints array must include the initialBreakpoint value.');
printIonWarning('[ion-modal] - Your breakpoints array must include the initialBreakpoint value.');
}
if (!this.htmlAttributes?.id) {
@ -847,12 +847,12 @@ export class Modal implements ComponentInterface, OverlayInterface {
@Method()
async setCurrentBreakpoint(breakpoint: number): Promise<void> {
if (!this.isSheetModal) {
printIonWarning('setCurrentBreakpoint is only supported on sheet modals.');
printIonWarning('[ion-modal] - setCurrentBreakpoint is only supported on sheet modals.');
return;
}
if (!this.breakpoints!.includes(breakpoint)) {
printIonWarning(
`Attempted to set invalid breakpoint value ${breakpoint}. Please double check that the breakpoint value is part of your defined breakpoints.`
`[ion-modal] - Attempted to set invalid breakpoint value ${breakpoint}. Please double check that the breakpoint value is part of your defined breakpoints.`
);
return;
}

View File

@ -145,7 +145,9 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await modal.evaluate((el: HTMLIonModalElement) => el.setCurrentBreakpoint(0.5));
expect(warnings.length).toBe(1);
expect(warnings[0]).toBe('[Ionic Warning]: setCurrentBreakpoint is only supported on sheet modals.');
expect(warnings[0]).toBe(
'[Ionic Warning]: [ion-modal] - setCurrentBreakpoint is only supported on sheet modals.'
);
});
test('it should return undefined when getting the breakpoint on a non-sheet modal', async ({ page }) => {

View File

@ -96,7 +96,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
test('it should warn when setting an invalid breakpoint', async () => {
expect(warnings.length).toBe(1);
expect(warnings[0]).toBe(
'[Ionic Warning]: Attempted to set invalid breakpoint value 0.01. Please double check that the breakpoint value is part of your defined breakpoints.'
'[Ionic Warning]: [ion-modal] - Attempted to set invalid breakpoint value 0.01. Please double check that the breakpoint value is part of your defined breakpoints.'
);
});
});

View File

@ -97,7 +97,7 @@ export class Nav implements NavOutlet {
this.setRoot(this.root, this.rootParams);
}
} else if (isDev) {
printIonWarning('<ion-nav> does not support a root attribute when using ion-router.', this.el);
printIonWarning('[ion-nav] - A root attribute is not supported when using ion-router.', this.el);
}
}
@ -820,8 +820,8 @@ export class Nav implements NavOutlet {
const finalNumViews = this.views.length + (insertViews?.length ?? 0) - (removeCount ?? 0);
assert(finalNumViews >= 0, 'final balance can not be negative');
if (finalNumViews === 0) {
console.warn(
`You can't remove all the pages in the navigation stack. nav.pop() is probably called too many times.`,
printIonWarning(
`[ion-nav] - You can't remove all the pages in the navigation stack. nav.pop() is probably called too many times.`,
this,
this.el
);

View File

@ -206,7 +206,7 @@ export class Picker implements ComponentInterface, OverlayInterface {
componentDidLoad() {
printIonWarning(
'ion-picker-legacy and ion-picker-legacy-column have been deprecated in favor of new versions of the ion-picker and ion-picker-column components. These new components display inline with your page content allowing for more presentation flexibility than before.',
'[ion-picker-legacy] - ion-picker-legacy and ion-picker-legacy-column have been deprecated in favor of new versions of the ion-picker and ion-picker-column components. These new components display inline with your page content allowing for more presentation flexibility than before.',
this.el
);

View File

@ -648,7 +648,7 @@ export class Popover implements ComponentInterface, PopoverInterface {
const triggerEl = (this.triggerEl = trigger !== undefined ? document.getElementById(trigger) : null);
if (!triggerEl) {
printIonWarning(
`A trigger element with the ID "${trigger}" was not found in the DOM. The trigger element must be in the DOM when the "trigger" property is set on ion-popover.`,
`[ion-popover] - A trigger element with the ID "${trigger}" was not found in the DOM. The trigger element must be in the DOM when the "trigger" property is set on ion-popover.`,
this.el
);
return;

View File

@ -183,13 +183,13 @@ export class Range implements ComponentInterface {
if (activeBarStart !== undefined) {
if (activeBarStart > this.max) {
printIonWarning(
`Range: The value of activeBarStart (${activeBarStart}) is greater than the max (${this.max}). Valid values are greater than or equal to the min value and less than or equal to the max value.`,
`[ion-range] - The value of activeBarStart (${activeBarStart}) is greater than the max (${this.max}). Valid values are greater than or equal to the min value and less than or equal to the max value.`,
this.el
);
this.activeBarStart = this.max;
} else if (activeBarStart < this.min) {
printIonWarning(
`Range: The value of activeBarStart (${activeBarStart}) is less than the min (${this.min}). Valid values are greater than or equal to the min value and less than or equal to the max value.`,
`[ion-range] - The value of activeBarStart (${activeBarStart}) is less than the min (${this.min}). Valid values are greater than or equal to the min value and less than or equal to the max value.`,
this.el
);
this.activeBarStart = this.min;

View File

@ -8,6 +8,7 @@ import {
printIonContentErrorMsg,
} from '@utils/content';
import { clamp, componentOnReady, getElementRoot, raf, transitionEndAsync } from '@utils/helpers';
import { printIonError } from '@utils/logging';
import { ImpactStyle, hapticImpact } from '@utils/native/haptic';
import { getIonMode } from '../../global/ionic-global';
@ -452,7 +453,7 @@ export class Refresher implements ComponentInterface {
async connectedCallback() {
if (this.el.getAttribute('slot') !== 'fixed') {
console.error('Make sure you use: <ion-refresher slot="fixed">');
printIonError('[ion-refresher] - Make sure you use: <ion-refresher slot="fixed">');
return;
}
const contentEl = this.el.closest(ION_CONTENT_ELEMENT_SELECTOR);

View File

@ -4,6 +4,7 @@ import { getTimeGivenProgression } from '@utils/animation/cubic-bezier';
import { attachComponent, detachComponent } from '@utils/framework-delegate';
import { shallowEqualStringMap, hasLazyBuild } from '@utils/helpers';
import { createLockController } from '@utils/lock-controller';
import { printIonError } from '@utils/logging';
import { transition } from '@utils/transition';
import { config } from '../../global/config';
@ -146,7 +147,7 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
try {
changed = await this.transition(enteringEl, leavingEl, opts);
} catch (e) {
console.error(e);
printIonError('[ion-router-outlet] - Exception in commit:', e);
}
unlock();
return changed;

View File

@ -2,6 +2,7 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Listen, Method, Prop } from '@stencil/core';
import type { BackButtonEvent } from '@utils/hardware-back-button';
import { debounce } from '@utils/helpers';
import { printIonError, printIonWarning } from '@utils/logging';
import type { AnimationBuilder } from '../../interface';
import type { NavigationHookResult } from '../route/route-interface';
@ -166,15 +167,15 @@ export class Router implements ComponentInterface {
@Method()
async navChanged(direction: RouterDirection): Promise<boolean> {
if (this.busy) {
console.warn('[ion-router] router is busy, navChanged was cancelled');
printIonWarning('[ion-router] - Router is busy, navChanged was cancelled.');
return false;
}
const { ids, outlet } = await readNavState(window.document.body);
const routes = readRoutes(this.el);
const chain = findChainForIDs(ids, routes);
if (!chain) {
console.warn(
'[ion-router] no matching URL for ',
printIonWarning(
'[ion-router] - No matching URL for',
ids.map((i) => i.id)
);
return false;
@ -182,7 +183,7 @@ export class Router implements ComponentInterface {
const segments = chainToSegments(chain);
if (!segments) {
console.warn('[ion-router] router could not match path because some required param is missing');
printIonWarning('[ion-router] - Router could not match path because some required param is missing.');
return false;
}
@ -232,7 +233,7 @@ export class Router implements ComponentInterface {
animation?: AnimationBuilder
): Promise<boolean> {
if (!segments) {
console.error('[ion-router] URL is not part of the routing set');
printIonError('[ion-router] - URL is not part of the routing set.');
return false;
}
@ -253,7 +254,7 @@ export class Router implements ComponentInterface {
const routes = readRoutes(this.el);
const chain = findChainForSegments(segments, routes);
if (!chain) {
console.error('[ion-router] the path does not match any route');
printIonError('[ion-router] - The path does not match any route.');
return false;
}
@ -275,7 +276,7 @@ export class Router implements ComponentInterface {
try {
changed = await this.writeNavState(node, chain, direction, segments, redirectFrom, index, animation);
} catch (e) {
console.error(e);
printIonError('[ion-router] - Exception in safeWriteNavState:', e);
}
unlock();
return changed;
@ -338,7 +339,7 @@ export class Router implements ComponentInterface {
animation?: AnimationBuilder
): Promise<boolean> {
if (this.busy) {
console.warn('[ion-router] router is busy, transition was cancelled');
printIonWarning('[ion-router] - Router is busy, transition was cancelled.');
return false;
}
this.busy = true;

View File

@ -1,4 +1,5 @@
import { componentOnReady } from '@utils/helpers';
import { printIonError } from '@utils/logging';
import type { AnimationBuilder } from '../../../interface';
@ -51,7 +52,7 @@ export const writeNavState = async (
}
return changed;
} catch (e) {
console.error(e);
printIonError('[ion-router] - Exception in writeNavState:', e);
return false;
}
};

View File

@ -3,6 +3,7 @@ import { Component, Element, Host, Prop, Method, State, Watch, forceUpdate, h }
import type { ButtonInterface } from '@utils/element-interface';
import type { Attributes } from '@utils/helpers';
import { addEventListener, removeEventListener, inheritAttributes } from '@utils/helpers';
import { printIonError, printIonWarning } from '@utils/logging';
import { hostContext } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
@ -75,7 +76,9 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
// Prevent buttons from being disabled when associated with segment content
if (this.contentId && this.disabled) {
console.warn(`Segment Button: Segment buttons cannot be disabled when associated with an <ion-segment-content>.`);
printIonWarning(
`[ion-segment-button] - Segment buttons cannot be disabled when associated with an <ion-segment-content>.`
);
this.disabled = false;
}
}
@ -102,13 +105,15 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
// If no associated Segment Content exists, log an error and return
if (!segmentContent) {
console.error(`Segment Button: Unable to find Segment Content with id="${this.contentId}".`);
printIonError(`[ion-segment-button] - Unable to find Segment Content with id="${this.contentId}".`);
return;
}
// Ensure the found element is a valid ION-SEGMENT-CONTENT
if (segmentContent.tagName !== 'ION-SEGMENT-CONTENT') {
console.error(`Segment Button: Element with id="${this.contentId}" is not an <ion-segment-content> element.`);
printIonError(
`[ion-segment-button] - Element with id="${this.contentId}" is not an <ion-segment-content> element.`
);
return;
}
}

View File

@ -4,6 +4,7 @@ import type { NotchController } from '@utils/forms';
import { compareOptions, createNotchController, isOptionSelected } from '@utils/forms';
import { focusVisibleElement, renderHiddenInput, inheritAttributes } from '@utils/helpers';
import type { Attributes } from '@utils/helpers';
import { printIonWarning } from '@utils/logging';
import { actionSheetController, alertController, popoverController, modalController } from '@utils/overlays';
import type { OverlaySelect } from '@utils/overlays-interface';
import { isRTL } from '@utils/rtl';
@ -426,15 +427,15 @@ export class Select implements ComponentInterface {
private createOverlay(ev?: UIEvent): Promise<OverlaySelect> {
let selectInterface = this.interface;
if (selectInterface === 'action-sheet' && this.multiple) {
console.warn(
`Select interface cannot be "${selectInterface}" with a multi-value select. Using the "alert" interface instead.`
printIonWarning(
`[ion-select] - Interface cannot be "${selectInterface}" with a multi-value select. Using the "alert" interface instead.`
);
selectInterface = 'alert';
}
if (selectInterface === 'popover' && !ev) {
console.warn(
`Select interface cannot be a "${selectInterface}" without passing an event. Using the "alert" interface instead.`
printIonWarning(
`[ion-select] - Interface cannot be a "${selectInterface}" without passing an event. Using the "alert" interface instead.`
);
selectInterface = 'alert';
}

View File

@ -1,5 +1,6 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Build, Component, Element, Event, Host, Method, Prop, State, Watch, h } from '@stencil/core';
import { printIonWarning } from '@utils/logging';
import { getIonMode } from '../../global/ionic-global';
@ -154,7 +155,7 @@ export class SplitPane implements ComponentInterface {
const isMain = contentId !== undefined && child.id === contentId;
if (isMain) {
if (foundMain) {
console.warn('split pane cannot have more than one main node');
printIonWarning('[ion-split-pane] - Cannot have more than one main node.');
return;
} else {
setPaneClass(child, isMain);
@ -163,7 +164,7 @@ export class SplitPane implements ComponentInterface {
}
}
if (!foundMain) {
console.warn('split pane does not have a specified main node');
printIonWarning('[ion-split-pane] - Does not have a specified main node.');
}
}

View File

@ -1,6 +1,7 @@
import type { ComponentInterface } from '@stencil/core';
import { Build, Component, Element, Host, Method, Prop, Watch, h } from '@stencil/core';
import { attachComponent } from '@utils/framework-delegate';
import { printIonError } from '@utils/logging';
import type { ComponentRef, FrameworkDelegate } from '../../interface';
@ -33,8 +34,8 @@ export class Tab implements ComponentInterface {
async componentWillLoad() {
if (Build.isDev) {
if (this.component !== undefined && this.el.childElementCount > 0) {
console.error(
'You can not use a lazy-loaded component in a tab and inlined content at the same time.' +
printIonError(
'[ion-tab] - You can not use a lazy-loaded component in a tab and inlined content at the same time.' +
`- Remove the component attribute in: <ion-tab component="${this.component}">` +
` or` +
`- Remove the embedded content inside the ion-tab: <ion-tab></ion-tab>`
@ -66,7 +67,7 @@ export class Tab implements ComponentInterface {
try {
return attachComponent(this.delegate, this.el, this.component, ['ion-page']);
} catch (e) {
console.error(e);
printIonError('[ion-tab] - Exception in prepareLazyLoaded:', e);
}
}
return Promise.resolve(undefined);

View File

@ -1,5 +1,6 @@
import type { EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Method, Prop, State, h } from '@stencil/core';
import { printIonError } from '@utils/logging';
import type { NavOutlet, RouteID, RouteWrite } from '../router/utils/interface';
import type { TabButtonClickEventDetail } from '../tab-bar/tab-bar-interface';
@ -210,7 +211,7 @@ const getTab = (tabs: HTMLIonTabElement[], tab: string | HTMLIonTabElement): HTM
const tabEl = typeof tab === 'string' ? tabs.find((t) => t.tab === tab) : tab;
if (!tabEl) {
console.error(`tab with id: "${tabEl}" does not exist`);
printIonError(`[ion-tabs] - Tab with id: "${tabEl}" does not exist`);
}
return tabEl;
};

View File

@ -83,7 +83,7 @@ export function getAnimationPosition(
function warnIfAnchorIsHidden(positionAnchor: HTMLElement, toast: HTMLElement) {
if (positionAnchor.offsetParent === null) {
printIonWarning(
'The positionAnchor element for ion-toast was found in the DOM, but appears to be hidden. This may lead to unexpected positioning of the toast.',
'[ion-toast] - The positionAnchor element for ion-toast was found in the DOM, but appears to be hidden. This may lead to unexpected positioning of the toast.',
toast
);
}

View File

@ -4,7 +4,7 @@ import { ENABLE_HTML_CONTENT_DEFAULT } from '@utils/config';
import type { Gesture } from '@utils/gesture';
import { raf } from '@utils/helpers';
import { createLockController } from '@utils/lock-controller';
import { printIonWarning } from '@utils/logging';
import { printIonError, printIonWarning } from '@utils/logging';
import {
GESTURE,
createDelegateController,
@ -498,7 +498,7 @@ export class Toast implements ComponentInterface, OverlayInterface {
}
if (position === 'middle' && positionAnchor !== undefined) {
printIonWarning('The positionAnchor property is ignored when using position="middle".', this.el);
printIonWarning('[ion-toast] - The positionAnchor property is ignored when using position="middle".', this.el);
return undefined;
}
@ -511,7 +511,10 @@ export class Toast implements ComponentInterface, OverlayInterface {
*/
const foundEl = document.getElementById(positionAnchor);
if (foundEl === null) {
printIonWarning(`An anchor element with an ID of "${positionAnchor}" was not found in the DOM.`, el);
printIonWarning(
`[ion-toast] - An anchor element with an ID of "${positionAnchor}" was not found in the DOM.`,
el
);
return undefined;
}
@ -522,7 +525,7 @@ export class Toast implements ComponentInterface, OverlayInterface {
return positionAnchor;
}
printIonWarning('Invalid positionAnchor value:', positionAnchor, el);
printIonWarning('[ion-toast] - Invalid positionAnchor value:', positionAnchor, el);
return undefined;
}
@ -549,7 +552,7 @@ export class Toast implements ComponentInterface, OverlayInterface {
return false;
}
} catch (e) {
console.error(e);
printIonError('[ion-toast] - Exception in callButtonHandler:', e);
}
}
return true;
@ -705,7 +708,7 @@ export class Toast implements ComponentInterface, OverlayInterface {
*/
if (layout === 'stacked' && startButtons.length > 0 && endButtons.length > 0) {
printIonWarning(
'This toast is using start and end buttons with the stacked toast layout. We recommend following the best practice of using either start or end buttons with the stacked toast layout.',
'[ion-toast] - This toast is using start and end buttons with the stacked toast layout. We recommend following the best practice of using either start or end buttons with the stacked toast layout.',
el
);
}

View File

@ -1,4 +1,5 @@
import { getMode, setMode } from '@stencil/core';
import { printIonWarning } from '@utils/logging';
import type { IonicConfig, Mode } from '../interface';
import { isPlatform, setupPlatforms } from '../utils/platform';
@ -67,7 +68,7 @@ export const initialize = (userConfig: IonicConfig = {}) => {
if (isAllowedIonicModeValue(elmMode)) {
return elmMode;
} else if (isIonicElement(elm)) {
console.warn('Invalid ionic mode: "' + elmMode + '", expected: "ios" or "md"');
printIonWarning('Invalid ionic mode: "' + elmMode + '", expected: "ios" or "md"');
}
}
elm = elm.parentElement;

View File

@ -1,3 +1,5 @@
import { printIonError } from '@utils/logging';
import { win } from '../browser';
import type {
@ -455,7 +457,7 @@ export const createAnimation = (animationId?: string): Animation => {
elements.push((el as any)[i]);
}
} else {
console.error('Invalid addElement value');
printIonError('createAnimation - Invalid addElement value.');
}
}

View File

@ -1,5 +1,6 @@
import { win } from '@utils/browser';
import type { CloseWatcher } from '@utils/browser';
import { printIonError } from '@utils/logging';
import { config } from '../global/config';
@ -77,7 +78,7 @@ export const startHardwareBackButton = () => {
}
}
} catch (e) {
console.error(e);
printIonError('[ion-app] - Exception in startHardwareBackButton:', e);
}
};

View File

@ -1,4 +1,5 @@
import type { EventEmitter } from '@stencil/core';
import { printIonError } from '@utils/logging';
import type { Side } from '../components/menu/menu-interface';
@ -287,7 +288,7 @@ export const clamp = (min: number, n: number, max: number) => {
export const assert = (actual: any, reason: string) => {
if (!actual) {
const message = 'ASSERT: ' + reason;
console.error(message);
printIonError(message);
debugger; // eslint-disable-line
throw new Error(message);
}

View File

@ -1,6 +1,6 @@
import { config } from '@global/config';
export const enum LogLevel {
export enum LogLevel {
OFF = 'OFF',
ERROR = 'ERROR',
WARN = 'WARN',

View File

@ -2,6 +2,7 @@ import { doc } from '@utils/browser';
import { focusFirstDescendant, focusLastDescendant, focusableQueryString } from '@utils/focus-trap';
import type { BackButtonEvent } from '@utils/hardware-back-button';
import { shouldUseCloseWatcher } from '@utils/hardware-back-button';
import { printIonError, printIonWarning } from '@utils/logging';
import { config } from '../global/config';
import { getIonMode } from '../global/ionic-global';
@ -31,7 +32,6 @@ import {
getElementRoot,
removeEventListener,
} from './helpers';
import { printIonWarning } from './logging';
import { isPlatform } from './platform';
let lastOverlayIndex = 0;
@ -648,7 +648,7 @@ export const dismiss = async <OverlayDismissOptions>(
const presentedOverlays = doc !== undefined ? getPresentedOverlays(doc) : [];
/**
* For accessibility, toasts lack focus traps and dont receive
* For accessibility, toasts lack focus traps and don't receive
* `aria-hidden` on the root element when presented.
*
* All other overlays use focus traps to keep keyboard focus
@ -723,7 +723,7 @@ export const dismiss = async <OverlayDismissOptions>(
overlay.el.lastFocus = undefined;
}
} catch (err) {
console.error(err);
printIonError(`[${overlay.el.tagName.toLowerCase()}] - `, err);
}
overlay.el.remove();
@ -940,7 +940,7 @@ export const createTriggerController = () => {
const triggerEl = trigger !== undefined ? document.getElementById(trigger) : null;
if (!triggerEl) {
printIonWarning(
`A trigger element with the ID "${trigger}" was not found in the DOM. The trigger element must be in the DOM when the "trigger" property is set on an overlay component.`,
`[${el.tagName.toLowerCase()}] - A trigger element with the ID "${trigger}" was not found in the DOM. The trigger element must be in the DOM when the "trigger" property is set on an overlay component.`,
el
);
return;

View File

@ -1,8 +1,9 @@
import { printIonError } from '@utils/logging';
/**
* Does a simple sanitization of all elements
* in an untrusted string
*/
export const sanitizeDOMString = (untrustedString: IonicSafeString | string | undefined): string | undefined => {
try {
if (untrustedString instanceof IonicSafeString) {
@ -81,7 +82,7 @@ export const sanitizeDOMString = (untrustedString: IonicSafeString | string | un
const getInnerDiv = fragmentDiv.querySelector('div');
return getInnerDiv !== null ? getInnerDiv.innerHTML : fragmentDiv.innerHTML;
} catch (err) {
console.error(err);
printIonError('sanitizeDOMString', err);
return '';
}