Compare commits

...

8 Commits

Author SHA1 Message Date
Shawn Taylor
bb789354b0 use ion-activatable & ion-focusable & any-hover 2024-03-01 16:13:51 -05:00
Shawn Taylor
7f35114ef7 change focus state 2024-02-26 08:50:02 -05:00
Shawn Taylor
df06bf53b3 change focus state 2024-02-26 08:49:17 -05:00
Shawn Taylor
d85aacf0f1 example for design doc 2024-02-23 12:41:13 -05:00
Sean Perkins
0a58f90dd0 chore: add virtual prop for theme to all components with theme 2024-02-16 21:22:04 -05:00
Sean Perkins
f85a92b18b chore: fallback to md styles for all components 2024-02-16 21:08:53 -05:00
Sean Perkins
c8dcdfa5a7 chore: revert getIonPlatform in favor of getIonMode
While further evaluating this approach, Ionic already has a concept of "platforms" that refer to iOS, Android, iPhone, iPad, etc. This language is confusing with trying to use platform for just the specific iOS and Android behaviors we have baked into components.
2024-02-16 20:53:43 -05:00
Sean Perkins
c795995393 feat: separate look and feel 2024-02-16 19:19:32 -05:00
103 changed files with 1872 additions and 768 deletions

View File

@@ -328,7 +328,7 @@ The ripple effect should be added to elements for Material Design. It *requires*
```jsx
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
@@ -338,7 +338,7 @@ return (
>
<button>
<slot></slot>
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
{theme === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</button>
</Host>
);

View File

@@ -3,6 +3,7 @@ ion-accordion,shadow
ion-accordion,prop,disabled,boolean,false,false,false
ion-accordion,prop,mode,"ios" | "md",undefined,false,false
ion-accordion,prop,readonly,boolean,false,false,false
ion-accordion,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-accordion,prop,toggleIcon,string,chevronDown,false,false
ion-accordion,prop,toggleIconSlot,"end" | "start",'end',false,false
ion-accordion,prop,value,string,`ion-accordion-${accordionIds++}`,false,false
@@ -17,6 +18,7 @@ ion-accordion-group,prop,expand,"compact" | "inset",'compact',false,false
ion-accordion-group,prop,mode,"ios" | "md",undefined,false,false
ion-accordion-group,prop,multiple,boolean | undefined,undefined,false,false
ion-accordion-group,prop,readonly,boolean,false,false,false
ion-accordion-group,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-accordion-group,prop,value,null | string | string[] | undefined,undefined,false,false
ion-accordion-group,event,ionChange,AccordionGroupChangeEventDetail<any>,true
@@ -33,6 +35,7 @@ ion-action-sheet,prop,keyboardClose,boolean,true,false,false
ion-action-sheet,prop,leaveAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
ion-action-sheet,prop,mode,"ios" | "md",undefined,false,false
ion-action-sheet,prop,subHeader,string | undefined,undefined,false,false
ion-action-sheet,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-action-sheet,prop,translucent,boolean,false,false,false
ion-action-sheet,prop,trigger,string | undefined,undefined,false,false
ion-action-sheet,method,dismiss,dismiss(data?: any, role?: string) => Promise<boolean>
@@ -86,6 +89,7 @@ ion-alert,prop,leaveAnimation,((baseEl: any, opts?: any) => Animation) | undefin
ion-alert,prop,message,IonicSafeString | string | undefined,undefined,false,false
ion-alert,prop,mode,"ios" | "md",undefined,false,false
ion-alert,prop,subHeader,string | undefined,undefined,false,false
ion-alert,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-alert,prop,translucent,boolean,false,false,false
ion-alert,prop,trigger,string | undefined,undefined,false,false
ion-alert,method,dismiss,dismiss(data?: any, role?: string) => Promise<boolean>
@@ -112,6 +116,7 @@ ion-alert,css-prop,--width
ion-app,none
ion-avatar,shadow
ion-avatar,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-avatar,css-prop,--border-radius
ion-back-button,shadow
@@ -122,6 +127,7 @@ ion-back-button,prop,icon,null | string | undefined,undefined,false,false
ion-back-button,prop,mode,"ios" | "md",undefined,false,false
ion-back-button,prop,routerAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
ion-back-button,prop,text,null | string | undefined,undefined,false,false
ion-back-button,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-back-button,prop,type,"button" | "reset" | "submit",'button',false,false
ion-back-button,css-prop,--background
ion-back-button,css-prop,--background-focused
@@ -162,12 +168,14 @@ ion-back-button,part,text
ion-backdrop,shadow
ion-backdrop,prop,stopPropagation,boolean,true,false,false
ion-backdrop,prop,tappable,boolean,true,false,false
ion-backdrop,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-backdrop,prop,visible,boolean,true,false,false
ion-backdrop,event,ionBackdropTap,void,true
ion-badge,shadow
ion-badge,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
ion-badge,prop,mode,"ios" | "md",undefined,false,false
ion-badge,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-badge,css-prop,--background
ion-badge,css-prop,--color
ion-badge,css-prop,--padding-bottom
@@ -187,6 +195,7 @@ ion-breadcrumb,prop,routerAnimation,((baseEl: any, opts?: any) => Animation) | u
ion-breadcrumb,prop,routerDirection,"back" | "forward" | "root",'forward',false,false
ion-breadcrumb,prop,separator,boolean | undefined,undefined,false,false
ion-breadcrumb,prop,target,string | undefined,undefined,false,false
ion-breadcrumb,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-breadcrumb,event,ionBlur,void,true
ion-breadcrumb,event,ionFocus,void,true
ion-breadcrumb,css-prop,--background-focused
@@ -204,6 +213,7 @@ ion-breadcrumbs,prop,itemsAfterCollapse,number,1,false,false
ion-breadcrumbs,prop,itemsBeforeCollapse,number,1,false,false
ion-breadcrumbs,prop,maxItems,number | undefined,undefined,false,false
ion-breadcrumbs,prop,mode,"ios" | "md",undefined,false,false
ion-breadcrumbs,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-breadcrumbs,event,ionCollapsedClick,BreadcrumbCollapsedClickEventDetail,true
ion-button,shadow
@@ -223,6 +233,7 @@ ion-button,prop,shape,"round" | undefined,undefined,false,true
ion-button,prop,size,"default" | "large" | "small" | undefined,undefined,false,true
ion-button,prop,strong,boolean,false,false,false
ion-button,prop,target,string | undefined,undefined,false,false
ion-button,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-button,prop,type,"button" | "reset" | "submit",'button',false,false
ion-button,event,ionBlur,void,true
ion-button,event,ionFocus,void,true
@@ -253,6 +264,7 @@ ion-button,part,native
ion-buttons,scoped
ion-buttons,prop,collapse,boolean,false,false,false
ion-buttons,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-card,shadow
ion-card,prop,button,boolean,false,false,false
@@ -265,6 +277,7 @@ ion-card,prop,rel,string | undefined,undefined,false,false
ion-card,prop,routerAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
ion-card,prop,routerDirection,"back" | "forward" | "root",'forward',false,false
ion-card,prop,target,string | undefined,undefined,false,false
ion-card,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-card,prop,type,"button" | "reset" | "submit",'button',false,false
ion-card,css-prop,--background
ion-card,css-prop,--color
@@ -272,20 +285,24 @@ ion-card,part,native
ion-card-content,none
ion-card-content,prop,mode,"ios" | "md",undefined,false,false
ion-card-content,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-card-header,shadow
ion-card-header,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
ion-card-header,prop,mode,"ios" | "md",undefined,false,false
ion-card-header,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-card-header,prop,translucent,boolean,false,false,false
ion-card-subtitle,shadow
ion-card-subtitle,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
ion-card-subtitle,prop,mode,"ios" | "md",undefined,false,false
ion-card-subtitle,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-card-subtitle,css-prop,--color
ion-card-title,shadow
ion-card-title,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
ion-card-title,prop,mode,"ios" | "md",undefined,false,false
ion-card-title,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-card-title,css-prop,--color
ion-checkbox,shadow
@@ -299,6 +316,8 @@ ion-checkbox,prop,labelPlacement,"end" | "fixed" | "stacked" | "start",'start',f
ion-checkbox,prop,legacy,boolean | undefined,undefined,false,false
ion-checkbox,prop,mode,"ios" | "md",undefined,false,false
ion-checkbox,prop,name,string,this.inputId,false,false
ion-checkbox,prop,required,boolean,false,false,false
ion-checkbox,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-checkbox,prop,value,any,'on',false,false
ion-checkbox,event,ionBlur,void,true
ion-checkbox,event,ionChange,CheckboxChangeEventDetail<any>,true
@@ -323,6 +342,7 @@ ion-chip,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "second
ion-chip,prop,disabled,boolean,false,false,false
ion-chip,prop,mode,"ios" | "md",undefined,false,false
ion-chip,prop,outline,boolean,false,false,false
ion-chip,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-chip,css-prop,--background
ion-chip,css-prop,--color
@@ -414,6 +434,7 @@ ion-datetime,prop,showDefaultButtons,boolean,false,false,false
ion-datetime,prop,showDefaultTimeLabel,boolean,true,false,false
ion-datetime,prop,showDefaultTitle,boolean,false,false,false
ion-datetime,prop,size,"cover" | "fixed",'fixed',false,false
ion-datetime,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-datetime,prop,titleSelectedDatesFormatter,((selectedDates: string[]) => string) | undefined,undefined,false,false
ion-datetime,prop,value,null | string | string[] | undefined,undefined,false,false
ion-datetime,prop,yearValues,number | number[] | string | undefined,undefined,false,false
@@ -444,6 +465,7 @@ ion-datetime-button,prop,color,"danger" | "dark" | "light" | "medium" | "primary
ion-datetime-button,prop,datetime,string | undefined,undefined,false,false
ion-datetime-button,prop,disabled,boolean,false,false,true
ion-datetime-button,prop,mode,"ios" | "md",undefined,false,false
ion-datetime-button,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-datetime-button,part,native
ion-fab,shadow
@@ -467,6 +489,7 @@ ion-fab-button,prop,routerDirection,"back" | "forward" | "root",'forward',false,
ion-fab-button,prop,show,boolean,false,false,false
ion-fab-button,prop,size,"small" | undefined,undefined,false,false
ion-fab-button,prop,target,string | undefined,undefined,false,false
ion-fab-button,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-fab-button,prop,translucent,boolean,false,false,false
ion-fab-button,prop,type,"button" | "reset" | "submit",'button',false,false
ion-fab-button,event,ionBlur,void,true
@@ -504,6 +527,7 @@ ion-fab-list,prop,side,"bottom" | "end" | "start" | "top",'bottom',false,false
ion-footer,none
ion-footer,prop,collapse,"fade" | undefined,undefined,false,false
ion-footer,prop,mode,"ios" | "md",undefined,false,false
ion-footer,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-footer,prop,translucent,boolean,false,false,false
ion-grid,shadow
@@ -524,6 +548,7 @@ ion-grid,css-prop,--ion-grid-width-xs
ion-header,none
ion-header,prop,collapse,"condense" | "fade" | undefined,undefined,false,false
ion-header,prop,mode,"ios" | "md",undefined,false,false
ion-header,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-header,prop,translucent,boolean,false,false,false
ion-img,shadow
@@ -544,6 +569,7 @@ ion-infinite-scroll,event,ionInfinite,void,true
ion-infinite-scroll-content,none
ion-infinite-scroll-content,prop,loadingSpinner,"bubbles" | "circles" | "circular" | "crescent" | "dots" | "lines" | "lines-sharp" | "lines-sharp-small" | "lines-small" | null | undefined,undefined,false,false
ion-infinite-scroll-content,prop,loadingText,IonicSafeString | string | undefined,undefined,false,false
ion-infinite-scroll-content,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-input,scoped
ion-input,prop,accept,string | undefined,undefined,false,false
@@ -581,6 +607,7 @@ ion-input,prop,shape,"round" | undefined,undefined,false,false
ion-input,prop,size,number | undefined,undefined,false,false
ion-input,prop,spellcheck,boolean,false,false,false
ion-input,prop,step,string | undefined,undefined,false,false
ion-input,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-input,prop,type,"date" | "datetime-local" | "email" | "month" | "number" | "password" | "search" | "tel" | "text" | "time" | "url" | "week",'text',false,false
ion-input,prop,value,null | number | string | undefined,'',false,false
ion-input,method,getInputElement,getInputElement() => Promise<HTMLInputElement>
@@ -625,6 +652,7 @@ ion-item,prop,routerAnimation,((baseEl: any, opts?: any) => Animation) | undefin
ion-item,prop,routerDirection,"back" | "forward" | "root",'forward',false,false
ion-item,prop,shape,"round" | undefined,undefined,false,false
ion-item,prop,target,string | undefined,undefined,false,false
ion-item,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-item,prop,type,"button" | "reset" | "submit",'button',false,false
ion-item,css-prop,--background
ion-item,css-prop,--background-activated
@@ -668,6 +696,7 @@ ion-item-divider,shadow
ion-item-divider,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
ion-item-divider,prop,mode,"ios" | "md",undefined,false,false
ion-item-divider,prop,sticky,boolean,false,false,false
ion-item-divider,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-item-divider,css-prop,--background
ion-item-divider,css-prop,--color
ion-item-divider,css-prop,--inner-padding-bottom
@@ -680,6 +709,7 @@ ion-item-divider,css-prop,--padding-start
ion-item-divider,css-prop,--padding-top
ion-item-group,none
ion-item-group,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-item-option,shadow
ion-item-option,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
@@ -690,6 +720,7 @@ ion-item-option,prop,href,string | undefined,undefined,false,false
ion-item-option,prop,mode,"ios" | "md",undefined,false,false
ion-item-option,prop,rel,string | undefined,undefined,false,false
ion-item-option,prop,target,string | undefined,undefined,false,false
ion-item-option,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-item-option,prop,type,"button" | "reset" | "submit",'button',false,false
ion-item-option,css-prop,--background
ion-item-option,css-prop,--color
@@ -697,6 +728,7 @@ ion-item-option,part,native
ion-item-options,none
ion-item-options,prop,side,"end" | "start",'end',false,false
ion-item-options,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-item-options,event,ionSwipe,any,true
ion-item-sliding,none
@@ -712,18 +744,21 @@ ion-label,scoped
ion-label,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
ion-label,prop,mode,"ios" | "md",undefined,false,false
ion-label,prop,position,"fixed" | "floating" | "stacked" | undefined,undefined,false,false
ion-label,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-label,css-prop,--color
ion-list,none
ion-list,prop,inset,boolean,false,false,false
ion-list,prop,lines,"full" | "inset" | "none" | undefined,undefined,false,false
ion-list,prop,mode,"ios" | "md",undefined,false,false
ion-list,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-list,method,closeSlidingItems,closeSlidingItems() => Promise<boolean>
ion-list-header,shadow
ion-list-header,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
ion-list-header,prop,lines,"full" | "inset" | "none" | undefined,undefined,false,false
ion-list-header,prop,mode,"ios" | "md",undefined,false,false
ion-list-header,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-list-header,css-prop,--background
ion-list-header,css-prop,--border-color
ion-list-header,css-prop,--border-style
@@ -745,6 +780,7 @@ ion-loading,prop,message,IonicSafeString | string | undefined,undefined,false,fa
ion-loading,prop,mode,"ios" | "md",undefined,false,false
ion-loading,prop,showBackdrop,boolean,true,false,false
ion-loading,prop,spinner,"bubbles" | "circles" | "circular" | "crescent" | "dots" | "lines" | "lines-sharp" | "lines-sharp-small" | "lines-small" | null | undefined,undefined,false,false
ion-loading,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-loading,prop,translucent,boolean,false,false,false
ion-loading,prop,trigger,string | undefined,undefined,false,false
ion-loading,method,dismiss,dismiss(data?: any, role?: string) => Promise<boolean>
@@ -776,6 +812,7 @@ ion-menu,prop,maxEdgeStart,number,50,false,false
ion-menu,prop,menuId,string | undefined,undefined,false,true
ion-menu,prop,side,"end" | "start",'start',false,true
ion-menu,prop,swipeGesture,boolean,true,false,false
ion-menu,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-menu,prop,type,string | undefined,undefined,false,false
ion-menu,method,close,close(animated?: boolean) => Promise<boolean>
ion-menu,method,isActive,isActive() => Promise<boolean>
@@ -803,6 +840,7 @@ ion-menu-button,prop,color,"danger" | "dark" | "light" | "medium" | "primary" |
ion-menu-button,prop,disabled,boolean,false,false,false
ion-menu-button,prop,menu,string | undefined,undefined,false,false
ion-menu-button,prop,mode,"ios" | "md",undefined,false,false
ion-menu-button,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-menu-button,prop,type,"button" | "reset" | "submit",'button',false,false
ion-menu-button,css-prop,--background
ion-menu-button,css-prop,--background-focused
@@ -842,6 +880,7 @@ ion-modal,prop,leaveAnimation,((baseEl: any, opts?: any) => Animation) | undefin
ion-modal,prop,mode,"ios" | "md",undefined,false,false
ion-modal,prop,presentingElement,HTMLElement | undefined,undefined,false,false
ion-modal,prop,showBackdrop,boolean,true,false,false
ion-modal,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-modal,prop,trigger,string | undefined,undefined,false,false
ion-modal,method,dismiss,dismiss(data?: any, role?: string) => Promise<boolean>
ion-modal,method,getCurrentBreakpoint,getCurrentBreakpoint() => Promise<number | undefined>
@@ -905,6 +944,7 @@ ion-nav-link,prop,routerDirection,"back" | "forward" | "root",'forward',false,fa
ion-note,shadow
ion-note,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
ion-note,prop,mode,"ios" | "md",undefined,false,false
ion-note,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-note,css-prop,--color
ion-picker,scoped
@@ -921,6 +961,7 @@ ion-picker,prop,keyboardClose,boolean,true,false,false
ion-picker,prop,leaveAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
ion-picker,prop,mode,"ios" | "md",undefined,false,false
ion-picker,prop,showBackdrop,boolean,true,false,false
ion-picker,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-picker,prop,trigger,string | undefined,undefined,false,false
ion-picker,method,dismiss,dismiss(data?: any, role?: string) => Promise<boolean>
ion-picker,method,getColumn,getColumn(name: string) => Promise<PickerColumn | undefined>
@@ -969,6 +1010,7 @@ ion-popover,prop,reference,"event" | "trigger",'trigger',false,false
ion-popover,prop,showBackdrop,boolean,true,false,false
ion-popover,prop,side,"bottom" | "end" | "left" | "right" | "start" | "top",'bottom',false,false
ion-popover,prop,size,"auto" | "cover",'auto',false,false
ion-popover,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-popover,prop,translucent,boolean,false,false,false
ion-popover,prop,trigger,string | undefined,undefined,false,false
ion-popover,prop,triggerAction,"click" | "context-menu" | "hover",'click',false,false
@@ -1004,6 +1046,7 @@ ion-progress-bar,prop,buffer,number,1,false,false
ion-progress-bar,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
ion-progress-bar,prop,mode,"ios" | "md",undefined,false,false
ion-progress-bar,prop,reversed,boolean,false,false,false
ion-progress-bar,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-progress-bar,prop,type,"determinate" | "indeterminate",'determinate',false,false
ion-progress-bar,prop,value,number,0,false,false
ion-progress-bar,css-prop,--background
@@ -1022,6 +1065,7 @@ ion-radio,prop,labelPlacement,"end" | "fixed" | "stacked" | "start",'start',fals
ion-radio,prop,legacy,boolean | undefined,undefined,false,false
ion-radio,prop,mode,"ios" | "md",undefined,false,false
ion-radio,prop,name,string,this.inputId,false,false
ion-radio,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-radio,prop,value,any,undefined,false,false
ion-radio,event,ionBlur,void,true
ion-radio,event,ionFocus,void,true
@@ -1057,6 +1101,7 @@ ion-range,prop,pin,boolean,false,false,false
ion-range,prop,pinFormatter,(value: number) => string | number,(value: number): number => Math.round(value),false,false
ion-range,prop,snaps,boolean,false,false,false
ion-range,prop,step,number,1,false,false
ion-range,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-range,prop,ticks,boolean,true,false,false
ion-range,prop,value,number | { lower: number; upper: number; },0,false,false
ion-range,event,ionBlur,void,true
@@ -1092,6 +1137,7 @@ ion-refresher,prop,pullFactor,number,1,false,false
ion-refresher,prop,pullMax,number,this.pullMin + 60,false,false
ion-refresher,prop,pullMin,number,60,false,false
ion-refresher,prop,snapbackDuration,string,'280ms',false,false
ion-refresher,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-refresher,method,cancel,cancel() => Promise<void>
ion-refresher,method,complete,complete() => Promise<void>
ion-refresher,method,getProgress,getProgress() => Promise<number>
@@ -1106,6 +1152,7 @@ ion-refresher-content,prop,refreshingSpinner,"bubbles" | "circles" | "circular"
ion-refresher-content,prop,refreshingText,IonicSafeString | string | undefined,undefined,false,false
ion-reorder,shadow
ion-reorder,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-reorder,part,icon
ion-reorder-group,none
@@ -1174,6 +1221,7 @@ ion-searchbar,prop,searchIcon,string | undefined,undefined,false,false
ion-searchbar,prop,showCancelButton,"always" | "focus" | "never",'never',false,false
ion-searchbar,prop,showClearButton,"always" | "focus" | "never",'always',false,false
ion-searchbar,prop,spellcheck,boolean,false,false,false
ion-searchbar,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-searchbar,prop,type,"email" | "number" | "password" | "search" | "tel" | "text" | "url",'search',false,false
ion-searchbar,prop,value,null | string | undefined,'',false,false
ion-searchbar,method,getInputElement,getInputElement() => Promise<HTMLInputElement>
@@ -1203,6 +1251,7 @@ ion-segment,prop,mode,"ios" | "md",undefined,false,false
ion-segment,prop,scrollable,boolean,false,false,false
ion-segment,prop,selectOnFocus,boolean,false,false,false
ion-segment,prop,swipeGesture,boolean,true,false,false
ion-segment,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-segment,prop,value,number | string | undefined,undefined,false,false
ion-segment,event,ionChange,SegmentChangeEventDetail,true
ion-segment,css-prop,--background
@@ -1211,6 +1260,7 @@ ion-segment-button,shadow
ion-segment-button,prop,disabled,boolean,false,false,false
ion-segment-button,prop,layout,"icon-bottom" | "icon-end" | "icon-hide" | "icon-start" | "icon-top" | "label-hide" | undefined,'icon-top',false,false
ion-segment-button,prop,mode,"ios" | "md",undefined,false,false
ion-segment-button,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-segment-button,prop,type,"button" | "reset" | "submit",'button',false,false
ion-segment-button,prop,value,number | string,'ion-sb-' + ids++,false,false
ion-segment-button,css-prop,--background
@@ -1265,6 +1315,7 @@ ion-select,prop,okText,string,'OK',false,false
ion-select,prop,placeholder,string | undefined,undefined,false,false
ion-select,prop,selectedText,null | string | undefined,undefined,false,false
ion-select,prop,shape,"round" | undefined,undefined,false,false
ion-select,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-select,prop,toggleIcon,string | undefined,undefined,false,false
ion-select,prop,value,any,undefined,false,false
ion-select,method,open,open(event?: UIEvent) => Promise<any>
@@ -1314,6 +1365,7 @@ ion-spinner,css-prop,--color
ion-split-pane,shadow
ion-split-pane,prop,contentId,string | undefined,undefined,false,true
ion-split-pane,prop,disabled,boolean,false,false,false
ion-split-pane,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-split-pane,prop,when,boolean | string,QUERY['lg'],false,false
ion-split-pane,event,ionSplitPaneVisible,{ visible: boolean; },true
ion-split-pane,css-prop,--border
@@ -1330,6 +1382,7 @@ ion-tab-bar,shadow
ion-tab-bar,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
ion-tab-bar,prop,mode,"ios" | "md",undefined,false,false
ion-tab-bar,prop,selectedTab,string | undefined,undefined,false,false
ion-tab-bar,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-tab-bar,prop,translucent,boolean,false,false,false
ion-tab-bar,css-prop,--background
ion-tab-bar,css-prop,--border
@@ -1345,6 +1398,7 @@ ion-tab-button,prop,rel,string | undefined,undefined,false,false
ion-tab-button,prop,selected,boolean,false,false,false
ion-tab-button,prop,tab,string | undefined,undefined,false,false
ion-tab-button,prop,target,string | undefined,undefined,false,false
ion-tab-button,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-tab-button,css-prop,--background
ion-tab-button,css-prop,--background-focused
ion-tab-button,css-prop,--background-focused-opacity
@@ -1398,6 +1452,7 @@ ion-textarea,prop,required,boolean,false,false,false
ion-textarea,prop,rows,number | undefined,undefined,false,false
ion-textarea,prop,shape,"round" | undefined,undefined,false,false
ion-textarea,prop,spellcheck,boolean,false,false,false
ion-textarea,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-textarea,prop,value,null | string | undefined,'',false,false
ion-textarea,prop,wrap,"hard" | "off" | "soft" | undefined,undefined,false,false
ion-textarea,method,getInputElement,getInputElement() => Promise<HTMLTextAreaElement>
@@ -1431,6 +1486,7 @@ ion-thumbnail,css-prop,--size
ion-title,shadow
ion-title,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
ion-title,prop,size,"large" | "small" | undefined,undefined,false,false
ion-title,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-title,css-prop,--color
ion-toast,shadow
@@ -1452,6 +1508,7 @@ ion-toast,prop,mode,"ios" | "md",undefined,false,false
ion-toast,prop,position,"bottom" | "middle" | "top",'bottom',false,false
ion-toast,prop,positionAnchor,HTMLElement | string | undefined,undefined,false,false
ion-toast,prop,swipeGesture,"vertical" | undefined,undefined,false,false
ion-toast,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-toast,prop,translucent,boolean,false,false,false
ion-toast,prop,trigger,string | undefined,undefined,false,false
ion-toast,method,dismiss,dismiss(data?: any, role?: string) => Promise<boolean>
@@ -1501,6 +1558,7 @@ ion-toggle,prop,labelPlacement,"end" | "fixed" | "stacked" | "start",'start',fal
ion-toggle,prop,legacy,boolean | undefined,undefined,false,false
ion-toggle,prop,mode,"ios" | "md",undefined,false,false
ion-toggle,prop,name,string,this.inputId,false,false
ion-toggle,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-toggle,prop,value,null | string | undefined,'on',false,false
ion-toggle,event,ionBlur,void,true
ion-toggle,event,ionChange,ToggleChangeEventDetail<any>,true
@@ -1524,6 +1582,7 @@ ion-toggle,part,track
ion-toolbar,shadow
ion-toolbar,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
ion-toolbar,prop,mode,"ios" | "md",undefined,false,false
ion-toolbar,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-toolbar,css-prop,--background
ion-toolbar,css-prop,--border-color
ion-toolbar,css-prop,--border-style

View File

File diff suppressed because it is too large Load Diff

View File

@@ -2,18 +2,20 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Listen, Method, Prop, Watch, h } from '@stencil/core';
import { printIonWarning } from '@utils/logging';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { AccordionGroupChangeEventDetail } from './accordion-group-interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-accordion-group',
styleUrls: {
ios: 'accordion-group.ios.scss',
md: 'accordion-group.md.scss',
ionic: 'accordion-group.md.scss',
},
shadow: true,
})
@@ -279,12 +281,12 @@ export class AccordionGroup implements ComponentInterface {
render() {
const { disabled, readonly, expand } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={{
[mode]: true,
[theme]: true,
'accordion-group-disabled': disabled,
'accordion-group-readonly': readonly,
[`accordion-group-expand-${expand}`]: true,

View File

@@ -4,7 +4,7 @@ import { addEventListener, getElementRoot, raf, removeEventListener, transitionE
import { chevronDown } from 'ionicons/icons';
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
const enum AccordionState {
Collapsed = 1 << 0,
@@ -14,7 +14,8 @@ const enum AccordionState {
}
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @slot header - Content is placed at the top and is used to
* expand or collapse the accordion item.
@@ -31,6 +32,7 @@ const enum AccordionState {
styleUrls: {
ios: 'accordion.ios.scss',
md: 'accordion.md.scss',
ionic: 'accordion.md.scss',
},
shadow: {
delegatesFocus: true,
@@ -402,7 +404,7 @@ export class Accordion implements ComponentInterface {
render() {
const { disabled, readonly } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const expanded = this.state === AccordionState.Expanded || this.state === AccordionState.Expanding;
const headerPart = expanded ? 'header expanded' : 'header';
const contentPart = expanded ? 'content expanded' : 'content';
@@ -412,7 +414,7 @@ export class Accordion implements ComponentInterface {
return (
<Host
class={{
[mode]: true,
[theme]: true,
'accordion-expanding': this.state === AccordionState.Expanding,
'accordion-expanded': this.state === AccordionState.Expanded,
'accordion-collapsing': this.state === AccordionState.Collapsing,

View File

@@ -1,4 +1,4 @@
import type { AnimationBuilder, LiteralUnion, Mode } from '../../interface';
import type { AnimationBuilder, LiteralUnion, Mode, Theme } from '../../interface';
export interface ActionSheetOptions {
header?: string;
@@ -8,7 +8,11 @@ export interface ActionSheetOptions {
backdropDismiss?: boolean;
translucent?: boolean;
animated?: boolean;
/**
* @deprecated
*/
mode?: Mode;
theme?: Theme;
keyboardClose?: boolean;
id?: string;
htmlAttributes?: { [key: string]: any };

View File

@@ -18,7 +18,7 @@ import {
} from '@utils/overlays';
import { getClassMap } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonMode, getIonTheme } from '../../global/ionic-global';
import type { AnimationBuilder, CssClassMap, FrameworkDelegate, OverlayInterface } from '../../interface';
import type { OverlayEventDetail } from '../../utils/overlays-interface';
@@ -29,13 +29,15 @@ import { mdEnterAnimation } from './animations/md.enter';
import { mdLeaveAnimation } from './animations/md.leave';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-action-sheet',
styleUrls: {
ios: 'action-sheet.ios.scss',
md: 'action-sheet.md.scss',
ionic: 'action-sheet.md.scss',
},
scoped: true,
})
@@ -314,15 +316,16 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
}
componentDidLoad() {
const mode = getIonMode(this);
/**
* Only create gesture if:
* 1. A gesture does not already exist
* 2. App is running in iOS mode
* 2. App is running on iOS platform
* 3. A wrapper ref exists
* 4. A group ref exists
*/
const { groupEl, wrapperEl } = this;
if (!this.gesture && getIonMode(this) === 'ios' && wrapperEl && groupEl) {
if (!this.gesture && mode === 'ios' && wrapperEl && groupEl) {
readTask(() => {
const isScrollable = groupEl.scrollHeight > groupEl.clientHeight;
if (!isScrollable) {
@@ -356,7 +359,7 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
render() {
const { header, htmlAttributes, overlayIndex } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const allButtons = this.getButtons();
const cancelButton = allButtons.find((b) => b.role === 'cancel');
const buttons = allButtons.filter((b) => b.role !== 'cancel');
@@ -373,7 +376,7 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
zIndex: `${20000 + this.overlayIndex}`,
}}
class={{
[mode]: true,
[theme]: true,
...getClassMap(this.cssClass),
'overlay-hidden': true,
'action-sheet-translucent': this.translucent,
@@ -412,7 +415,7 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
{b.icon && <ion-icon icon={b.icon} aria-hidden="true" lazy={false} class="action-sheet-icon" />}
{b.text}
</span>
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
{theme === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</button>
))}
</div>
@@ -431,7 +434,7 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
)}
{cancelButton.text}
</span>
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
{theme === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</button>
</div>
)}

View File

@@ -1,4 +1,4 @@
import type { AnimationBuilder, LiteralUnion, Mode, TextFieldTypes } from '../../interface';
import type { AnimationBuilder, LiteralUnion, Mode, TextFieldTypes, Theme } from '../../interface';
import type { IonicSafeString } from '../../utils/sanitization';
export interface AlertOptions {
@@ -13,7 +13,12 @@ export interface AlertOptions {
animated?: boolean;
htmlAttributes?: { [key: string]: any };
/**
* @deprecated
*/
mode?: Mode;
theme?: Theme;
keyboardClose?: boolean;
id?: string;

View File

@@ -21,7 +21,7 @@ import { sanitizeDOMString } from '@utils/sanitization';
import { getClassMap } from '@utils/theme';
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { getIonMode, getIonTheme } from '../../global/ionic-global';
import type { AnimationBuilder, CssClassMap, OverlayInterface, FrameworkDelegate } from '../../interface';
import type { OverlayEventDetail } from '../../utils/overlays-interface';
import type { IonicSafeString } from '../../utils/sanitization';
@@ -35,13 +35,15 @@ import { mdLeaveAnimation } from './animations/md.leave';
// TODO(FW-2832): types
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-alert',
styleUrls: {
ios: 'alert.ios.scss',
md: 'alert.md.scss',
ionic: 'alert.md.scss',
},
scoped: true,
})
@@ -533,7 +535,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
private renderCheckbox() {
const inputs = this.processedInputs;
const mode = getIonMode(this);
const theme = getIonTheme(this);
if (inputs.length === 0) {
return null;
@@ -565,7 +567,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
</div>
<div class="alert-checkbox-label">{i.label}</div>
</div>
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
{theme === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</button>
))}
</div>
@@ -682,7 +684,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
private renderAlertButtons() {
const buttons = this.processedButtons;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const alertButtonGroupClass = {
'alert-button-group': true,
'alert-button-group-vertical': buttons.length > 2,
@@ -699,7 +701,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
onClick={() => this.buttonClick(button)}
>
<span class="alert-button-inner">{button.text}</span>
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
{theme === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</button>
))}
</div>
@@ -721,7 +723,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
render() {
const { overlayIndex, header, subHeader, message, htmlAttributes } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const hdrId = `alert-${overlayIndex}-hdr`;
const subHdrId = `alert-${overlayIndex}-sub-hdr`;
const msgId = `alert-${overlayIndex}-msg`;
@@ -746,7 +748,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
}}
class={{
...getClassMap(this.cssClass),
[mode]: true,
[theme]: true,
'overlay-hidden': true,
'alert-translucent': this.translucent,
}}

View File

@@ -6,7 +6,7 @@ import { printIonWarning } from '@utils/logging';
import { isPlatform } from '@utils/platform';
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
@Component({
tag: 'ion-app',
@@ -78,11 +78,11 @@ export class App implements ComponentInterface {
}
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={{
[mode]: true,
[theme]: true,
'ion-page': true,
'force-statusbar-padding': config.getBoolean('_forceStatusbarPadding'),
}}

View File

@@ -1,20 +1,29 @@
import type { ComponentInterface } from '@stencil/core';
import { Component, Host, h } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
/**
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-avatar',
styleUrls: {
ios: 'avatar.ios.scss',
md: 'avatar.md.scss',
ionic: 'avatar.md.scss',
},
shadow: true,
})
export class Avatar implements ComponentInterface {
render() {
const theme = getIonTheme(this);
return (
<Host class={getIonMode(this)}>
<Host
class={{
[theme]: true,
}}
>
<slot></slot>
</Host>
);

View File

@@ -7,11 +7,12 @@ import { createColorClasses, hostContext, openURL } from '@utils/theme';
import { arrowBackSharp, chevronBack } from 'ionicons/icons';
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { AnimationBuilder, Color } from '../../interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @part native - The native HTML button element that wraps all child elements.
* @part icon - The back button icon (uses ion-icon).
@@ -22,6 +23,7 @@ import type { AnimationBuilder, Color } from '../../interface';
styleUrls: {
ios: 'back-button.ios.scss',
md: 'back-button.md.scss',
ionic: 'back-button.md.scss',
},
shadow: true,
})
@@ -84,7 +86,7 @@ export class BackButton implements ComponentInterface, ButtonInterface {
return icon;
}
if (getIonMode(this) === 'ios') {
if (getIonTheme(this) === 'ios') {
// default ios back button icon
return config.get('backButtonIcon', chevronBack);
}
@@ -94,7 +96,7 @@ export class BackButton implements ComponentInterface, ButtonInterface {
}
get backButtonText() {
const defaultBackButtonText = getIonMode(this) === 'ios' ? 'Back' : null;
const defaultBackButtonText = getIonTheme(this) === 'ios' ? 'Back' : null;
return this.text != null ? this.text : config.get('backButtonText', defaultBackButtonText);
}
@@ -135,14 +137,14 @@ export class BackButton implements ComponentInterface, ButtonInterface {
inheritedAttributes,
} = this;
const showBackButton = defaultHref !== undefined;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const ariaLabel = inheritedAttributes['aria-label'] || backButtonText || 'back';
return (
<Host
onClick={this.onClick}
class={createColorClasses(color, {
[mode]: true,
[theme]: true,
button: true, // ion-buttons target .button
'back-button-disabled': disabled,
'back-button-has-icon-only': hasIconOnly,
@@ -170,7 +172,7 @@ export class BackButton implements ComponentInterface, ButtonInterface {
</span>
)}
</span>
{mode === 'md' && <ion-ripple-effect type={this.rippleType}></ion-ripple-effect>}
{theme === 'md' && <ion-ripple-effect type={this.rippleType}></ion-ripple-effect>}
</button>
</Host>
);

View File

@@ -2,13 +2,17 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Event, Host, Listen, Prop, h } from '@stencil/core';
import { GESTURE_CONTROLLER } from '@utils/gesture';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
/**
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-backdrop',
styleUrls: {
ios: 'backdrop.ios.scss',
md: 'backdrop.md.scss',
ionic: 'backdrop.md.scss',
},
shadow: true,
})
@@ -63,13 +67,13 @@ export class Backdrop implements ComponentInterface {
}
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
tabindex="-1"
aria-hidden="true"
class={{
[mode]: true,
[theme]: true,
'backdrop-hide': !this.visible,
'backdrop-no-tappable': !this.tappable,
}}

View File

@@ -2,17 +2,19 @@ import type { ComponentInterface } from '@stencil/core';
import { Component, Host, Prop, h } from '@stencil/core';
import { createColorClasses } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color } from '../../interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-badge',
styleUrls: {
ios: 'badge.ios.scss',
md: 'badge.md.scss',
ionic: 'badge.md.scss',
},
shadow: true,
})
@@ -25,11 +27,11 @@ export class Badge implements ComponentInterface {
@Prop({ reflect: true }) color?: Color;
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
})}
>
<slot></slot>

View File

@@ -5,14 +5,15 @@ import { inheritAriaAttributes } from '@utils/helpers';
import { createColorClasses, hostContext, openURL } from '@utils/theme';
import { chevronForwardOutline, ellipsisHorizontal } from 'ionicons/icons';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { AnimationBuilder, Color } from '../../interface';
import type { RouterDirection } from '../router/utils/interface';
import type { BreadcrumbCollapsedClickEventDetail } from './breadcrumb-interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @part native - The native HTML anchor or div element that wraps all child elements.
* @part separator - The separator element between each breadcrumb.
@@ -23,6 +24,7 @@ import type { BreadcrumbCollapsedClickEventDetail } from './breadcrumb-interface
styleUrls: {
ios: 'breadcrumb.ios.scss',
md: 'breadcrumb.md.scss',
ionic: 'breadcrumb.md.scss',
},
shadow: true,
})
@@ -168,7 +170,7 @@ export class Breadcrumb implements ComponentInterface {
// Links can still be tabbed to when set to disabled if they have an href
// in order to truly disable them we can keep it as an anchor but remove the href
const href = disabled ? undefined : this.href;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const attrs =
TagType === 'span'
? {}
@@ -188,7 +190,7 @@ export class Breadcrumb implements ComponentInterface {
onClick={(ev: Event) => openURL(href, ev, routerDirection, routerAnimation)}
aria-disabled={disabled ? 'true' : null}
class={createColorClasses(color, {
[mode]: true,
[theme]: true,
'breadcrumb-active': active,
'breadcrumb-collapsed': collapsed,
'breadcrumb-disabled': disabled,
@@ -233,7 +235,7 @@ export class Breadcrumb implements ComponentInterface {
*/
<span class="breadcrumb-separator" part="separator" aria-hidden="true">
<slot name="separator">
{mode === 'ios' ? (
{theme === 'ios' ? (
<ion-icon icon={chevronForwardOutline} lazy={false} flip-rtl></ion-icon>
) : (
<span>/</span>

View File

@@ -2,19 +2,20 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Listen, Prop, State, Watch, h } from '@stencil/core';
import { createColorClasses, hostContext } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color } from '../../interface';
import type { BreadcrumbCollapsedClickEventDetail } from '../breadcrumb/breadcrumb-interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
*
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-breadcrumbs',
styleUrls: {
ios: 'breadcrumbs.ios.scss',
md: 'breadcrumbs.md.scss',
ionic: 'breadcrumbs.md.scss',
},
shadow: true,
})
@@ -170,12 +171,12 @@ export class Breadcrumbs implements ComponentInterface {
render() {
const { color, collapsed } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={createColorClasses(color, {
[mode]: true,
[theme]: true,
'in-toolbar': hostContext('ion-toolbar', this.el),
'in-toolbar-color': hostContext('ion-toolbar[color]', this.el),
'breadcrumbs-collapsed': collapsed,

View File

@@ -0,0 +1 @@
@import "./button";

View File

@@ -6,12 +6,13 @@ import { inheritAriaAttributes, hasShadowDom } from '@utils/helpers';
import { printIonWarning } from '@utils/logging';
import { createColorClasses, hostContext, openURL } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { AnimationBuilder, Color } from '../../interface';
import type { RouterDirection } from '../router/utils/interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @slot - Content is placed between the named slots if provided without a slot.
* @slot icon-only - Should be used on an icon in a button that has no text.
@@ -25,6 +26,7 @@ import type { RouterDirection } from '../router/utils/interface';
styleUrls: {
ios: 'button.ios.scss',
md: 'button.md.scss',
ionic: 'button.ionic.scss',
},
shadow: true,
})
@@ -296,7 +298,8 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
};
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
const {
buttonType,
type,
@@ -312,6 +315,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
strong,
inheritedAttributes,
} = this;
const finalSize = size === undefined && this.inItem ? 'small' : size;
const TagType = href === undefined ? 'button' : ('a' as any);
const attrs =
@@ -348,7 +352,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
onClick={this.handleClick}
aria-disabled={disabled ? 'true' : null}
class={createColorClasses(color, {
[mode]: true,
[theme]: true,
[buttonType]: true,
[`${buttonType}-${expand}`]: expand !== undefined,
[`${buttonType}-${finalSize}`]: finalSize !== undefined,
@@ -379,7 +383,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
<slot></slot>
<slot name="end"></slot>
</span>
{mode === 'md' && <ion-ripple-effect type={this.rippleType}></ion-ripple-effect>}
{theme === 'md' && <ion-ripple-effect type={this.rippleType}></ion-ripple-effect>}
</TagType>
</Host>
);

View File

@@ -1,13 +1,17 @@
import type { ComponentInterface } from '@stencil/core';
import { Component, Host, Prop, h } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
/**
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-buttons',
styleUrls: {
ios: 'buttons.ios.scss',
md: 'buttons.md.scss',
ionic: 'buttons.md.scss',
},
scoped: true,
})
@@ -27,11 +31,11 @@ export class Buttons implements ComponentInterface {
@Prop() collapse = false;
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={{
[mode]: true,
[theme]: true,
['buttons-collapse']: this.collapse,
}}
></Host>

View File

@@ -1,28 +1,30 @@
import type { ComponentInterface } from '@stencil/core';
import { Component, Host, h } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-card-content',
styleUrls: {
ios: 'card-content.ios.scss',
md: 'card-content.md.scss',
ionic: 'card-content.md.scss',
},
})
export class CardContent implements ComponentInterface {
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={{
[mode]: true,
[theme]: true,
// Used internally for styling
[`card-content-${mode}`]: true,
[`card-content-${theme}`]: true,
}}
></Host>
);

View File

@@ -2,17 +2,19 @@ import type { ComponentInterface } from '@stencil/core';
import { Component, Host, Prop, h } from '@stencil/core';
import { createColorClasses } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color } from '../../interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-card-header',
styleUrls: {
ios: 'card-header.ios.scss',
md: 'card-header.md.scss',
ionic: 'card-header.md.scss',
},
shadow: true,
})
@@ -32,13 +34,13 @@ export class CardHeader implements ComponentInterface {
@Prop() translucent = false;
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={createColorClasses(this.color, {
'card-header-translucent': this.translucent,
'ion-inherit-color': true,
[mode]: true,
[theme]: true,
})}
>
<slot></slot>

View File

@@ -2,17 +2,19 @@ import type { ComponentInterface } from '@stencil/core';
import { Component, Host, Prop, h } from '@stencil/core';
import { createColorClasses } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color } from '../../interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-card-subtitle',
styleUrls: {
ios: 'card-subtitle.ios.scss',
md: 'card-subtitle.md.scss',
ionic: 'card-subtitle.md.scss',
},
shadow: true,
})
@@ -25,14 +27,14 @@ export class CardSubtitle implements ComponentInterface {
@Prop({ reflect: true }) color?: Color;
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
role="heading"
aria-level="3"
class={createColorClasses(this.color, {
'ion-inherit-color': true,
[mode]: true,
[theme]: true,
})}
>
<slot></slot>

View File

@@ -2,17 +2,19 @@ import type { ComponentInterface } from '@stencil/core';
import { Component, Host, Prop, h } from '@stencil/core';
import { createColorClasses } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color } from '../../interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-card-title',
styleUrls: {
ios: 'card-title.ios.scss',
md: 'card-title.md.scss',
ionic: 'card-title.md.scss',
},
shadow: true,
})
@@ -25,14 +27,14 @@ export class CardTitle implements ComponentInterface {
@Prop({ reflect: true }) color?: Color;
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
role="heading"
aria-level="2"
class={createColorClasses(this.color, {
'ion-inherit-color': true,
[mode]: true,
[theme]: true,
})}
>
<slot></slot>

View File

@@ -5,12 +5,13 @@ import type { Attributes } from '@utils/helpers';
import { inheritAttributes } from '@utils/helpers';
import { createColorClasses, openURL } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import type { AnimationBuilder, Color, Mode } from '../../interface';
import { getIonTheme } from '../../global/ionic-global';
import type { AnimationBuilder, Color, Theme } from '../../interface';
import type { RouterDirection } from '../router/utils/interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @part native - The native HTML button, anchor, or div element that wraps all child elements.
*/
@@ -19,6 +20,7 @@ import type { RouterDirection } from '../router/utils/interface';
styleUrls: {
ios: 'card.ios.scss',
md: 'card.md.scss',
ionic: 'card.md.scss',
},
shadow: true,
})
@@ -95,7 +97,7 @@ export class Card implements ComponentInterface, AnchorInterface, ButtonInterfac
return this.href !== undefined || this.button;
}
private renderCard(mode: Mode) {
private renderCard(theme: Theme) {
const clickable = this.isClickable();
if (!clickable) {
@@ -123,22 +125,23 @@ export class Card implements ComponentInterface, AnchorInterface, ButtonInterfac
onClick={(ev: Event) => openURL(href, ev, routerDirection, routerAnimation)}
>
<slot></slot>
{clickable && mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
{clickable && theme === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</TagType>
);
}
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
'card-disabled': this.disabled,
'ion-activatable': this.isClickable(),
})}
>
{this.renderCard(mode)}
{this.renderCard(theme)}
</Host>
);
}

View File

@@ -0,0 +1,154 @@
@import "./checkbox";
@import "./checkbox.ionic.vars";
// Material Design Checkbox
// --------------------------------------------------
:host {
// Border
--border-radius: calc(var(--size) * 0.125);
--border-width: #{$checkbox-ionic-icon-border-width};
--border-style: #{$checkbox-ionic-icon-border-style};
--border-color: #{$checkbox-ionic-icon-border-color-off};
--checkmark-width: 1.5;
// Background
--checkbox-background: #{$checkbox-ionic-icon-background-color-off};
--checkbox-background-checked: #1068eb;
--border-color-checked: #1068eb;
// Transition
--transition: #{background $checkbox-ionic-transition-duration $checkbox-ionic-transition-easing};
// Size
--size: #{$checkbox-ionic-icon-size};
// margin is required to make room for outline on focus, otherwise the outline may get cut off
@include margin($checkbox-ionic-outline-width);
}
.checkbox-icon path {
stroke-dasharray: 30;
stroke-dashoffset: 30;
}
// Checkbox: Focused
// --------------------------------------------------------
:host(.ion-focusable) .checkbox-icon {
outline: $checkbox-ionic-outline-width solid $checkbox-ionic-outline-color;
}
:host(.ion-focusable.checkbox-checked) .checkbox-icon,
:host(.ion-focusable.checkbox-indeterminate) .checkbox-icon {
outline: $checkbox-ionic-outline-width solid $checkbox-ionic-outline-color;
}
// unchecked checkbox with `required` property set to true
:host(.ion-focusable.checkbox-required:not(.checkbox-checked):not(.checkbox-indeterminate)) .checkbox-icon {
outline-color: #ffafaf;
}
// Checkbox: Hover
// --------------------------------------------------------
@media (any-hover: hover) {
:host(:hover) .checkbox-icon {
background-color: #ececec;
}
:host(:hover.checkbox-checked) .checkbox-icon,
:host(:hover.checkbox-indeterminate) .checkbox-icon {
background-color: #1061da;
}
}
// Checkbox: Active
// --------------------------------------------------------
:host(.ion-activated) .checkbox-icon {
background-color: #e3e3e3;
}
:host(.ion-activated.checkbox-checked) .checkbox-icon,
:host(.ion-activated.checkbox-indeterminate) .checkbox-icon {
background-color: #105ed1;
}
// Material Design Checkbox: Checked / Indeterminate
// --------------------------------------------------------
:host(.checkbox-checked) .checkbox-icon path,
:host(.checkbox-indeterminate) .checkbox-icon path {
stroke-dashoffset: 0;
transition: stroke-dashoffset 90ms linear 90ms;
}
// Material Design Checkbox: Disabled
// --------------------------------------------------------
// The checkbox itself should use the disabled
// opacity set by its spec, while the label
// should match the other form controls
:host(.legacy-checkbox.checkbox-disabled),
:host(.checkbox-disabled) .label-text-wrapper {
opacity: $checkbox-ionic-disabled-opacity;
}
:host(.checkbox-disabled) .native-wrapper {
opacity: $checkbox-ionic-icon-disabled-opacity;
}
:host(.checkbox-disabled.checkbox-checked) .checkbox-icon {
border-width: 0;
background-color: #c9c9c9;
}
:host(.checkbox-disabled.checkbox-indeterminate) .checkbox-icon {
border-width: 0;
background-color: #74aafc;
}
// disabled, unchecked checkbox
:host(.checkbox-disabled) .checkbox-icon {
border-color: #c9c9c9;
background-color: #f5f5f5;
}
// unchecked checkbox with `required` property set to true
:host(.checkbox-required:not(.checkbox-checked):not(.checkbox-indeterminate)) {
.checkbox-icon {
border-color: #f72c2c;
}
}
// Material Design Checkbox Within An Item
// TODO(FW-3100): remove this
// --------------------------------------------------------
:host(.in-item.legacy-checkbox) {
// end position by default
@include margin(
$checkbox-ionic-item-end-margin-top,
$checkbox-ionic-item-end-margin-end,
$checkbox-ionic-item-end-margin-bottom,
$checkbox-ionic-item-end-margin-start
);
display: block;
position: static;
}
:host(.in-item.legacy-checkbox[slot="start"]) {
@include margin(
$checkbox-ionic-item-start-margin-top,
$checkbox-ionic-item-start-margin-end,
$checkbox-ionic-item-start-margin-bottom,
$checkbox-ionic-item-start-margin-start
);
}

View File

@@ -0,0 +1,70 @@
@import "../../themes/ionic.globals.md";
@import "../item/item.md.vars";
// Material Design Checkbox
// --------------------------------------------------
/// @prop - Opacity of the disabled checkbox label
$checkbox-ionic-disabled-opacity: $form-control-md-disabled-opacity !default;
/// @prop - Background color of the checkbox icon when off
$checkbox-ionic-icon-background-color-off: $item-md-background !default;
/// @prop - Size of the checkbox icon
/// The icon size does not use dynamic font
/// because it does not scale in native.
$checkbox-ionic-icon-size: 24px !default;
/// @prop - Border width of the checkbox icon
$checkbox-ionic-icon-border-width: 1px !default;
/// @prop - Border style of the checkbox icon
$checkbox-ionic-icon-border-style: solid !default;
/// @prop - Border color of the checkbox icon when off
$checkbox-ionic-icon-border-color-off: #9a9a9a !default;
/// @prop - Outline width of the checkbox
$checkbox-ionic-outline-width: 2px !default;
/// @prop - Outline color of the checkbox
$checkbox-ionic-outline-color: #9ec4fd !default;
/// @prop - Transition duration of the checkbox
$checkbox-ionic-transition-duration: 180ms !default;
/// @prop - Transition easing of the checkbox
$checkbox-ionic-transition-easing: cubic-bezier(0.4, 0, 0.2, 1) !default;
/// @prop - Margin top of the start checkbox item
$checkbox-ionic-item-start-margin-top: 18px !default;
/// @prop - Margin end of the start checkbox item
$checkbox-ionic-item-start-margin-end: 36px !default;
/// @prop - Margin bottom of the start checkbox item
$checkbox-ionic-item-start-margin-bottom: $checkbox-ionic-item-start-margin-top !default;
/// @prop - Margin start of the start checkbox item
$checkbox-ionic-item-start-margin-start: 4px !default;
/// @prop - Margin top of the end checkbox item
$checkbox-ionic-item-end-margin-top: 18px !default;
/// @prop - Margin end of the end checkbox item
$checkbox-ionic-item-end-margin-end: 0 !default;
/// @prop - Margin bottom of the end checkbox item
$checkbox-ionic-item-end-margin-bottom: $checkbox-ionic-item-end-margin-top !default;
/// @prop - Margin start of the end checkbox item
$checkbox-ionic-item-end-margin-start: 0 !default;
/// @prop - Opacity of the disabled checkbox
/// This value is used because the checkbox color is set to
/// `rgb(0, 0, 0, 0.60)` when enabled and we need it to be
/// `rgb(0, 0, 0, 0.38)` when disabled but the disabled
/// opacity is applied on top of the transparent color so
/// this opacity gets us the equivalent of applying `0.38`
/// on top of an opaque checkbox `rgb(0, 0, 0, 1.0)`
$checkbox-ionic-icon-disabled-opacity: 0.63 !default;

View File

@@ -7,13 +7,14 @@ import { getAriaLabel, inheritAriaAttributes, renderHiddenInput } from '@utils/h
import { printIonWarning } from '@utils/logging';
import { createColorClasses, hostContext } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import type { Color, Mode, StyleEventDetail } from '../../interface';
import { getIonTheme } from '../../global/ionic-global';
import type { Color, StyleEventDetail, Theme } from '../../interface';
import type { CheckboxChangeEventDetail } from './checkbox-interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @slot - The label text to associate with the checkbox. Use the "labelPlacement" property to control where the label is placed relative to the checkbox.
*
@@ -26,6 +27,7 @@ import type { CheckboxChangeEventDetail } from './checkbox-interface';
styleUrls: {
ios: 'checkbox.ios.scss',
md: 'checkbox.md.scss',
ionic: 'checkbox.ionic.scss',
},
shadow: true,
})
@@ -68,6 +70,11 @@ export class Checkbox implements ComponentInterface {
*/
@Prop() disabled = false;
/**
* If `true`, the checkbox will be presented with an error style when it is unchecked.
*/
@Prop() required = false;
/**
* The value of the checkbox does not mean if it's checked or not, use the `checked`
* property for that.
@@ -241,26 +248,31 @@ export class Checkbox implements ComponentInterface {
justify,
labelPlacement,
name,
required,
value,
alignment,
} = this;
const mode = getIonMode(this);
const path = getSVGPath(mode, indeterminate);
const theme = getIonTheme(this);
const path = getSVGPath(theme, indeterminate);
renderHiddenInput(true, el, name, checked ? value : '', disabled);
return (
<Host
class={createColorClasses(color, {
[mode]: true,
[theme]: true,
'in-item': hostContext('ion-item', el),
'checkbox-checked': checked,
'checkbox-disabled': disabled,
'checkbox-indeterminate': indeterminate,
'checkbox-required': required,
interactive: true,
[`checkbox-justify-${justify}`]: true,
[`checkbox-alignment-${alignment}`]: true,
[`checkbox-label-placement-${labelPlacement}`]: true,
'ion-activatable': true,
'ion-focusable': true,
})}
onClick={this.onClick}
>
@@ -278,6 +290,8 @@ export class Checkbox implements ComponentInterface {
onFocus={() => this.onFocus()}
onBlur={() => this.onBlur()}
ref={(focusEl) => (this.focusEl = focusEl)}
aria-checked={indeterminate ? 'mixed' : `${checked}`}
required={required}
{...inheritedAttributes}
/>
<div
@@ -324,9 +338,9 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
}
const { color, checked, disabled, el, getSVGPath, indeterminate, inputId, name, value } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const { label, labelId, labelText } = getAriaLabel(el, inputId);
const path = getSVGPath(mode, indeterminate);
const path = getSVGPath(theme, indeterminate);
renderHiddenInput(true, el, name, checked ? value : '', disabled);
@@ -337,7 +351,7 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
aria-hidden={disabled ? 'true' : null}
role="checkbox"
class={createColorClasses(color, {
[mode]: true,
[theme]: true,
'in-item': hostContext('ion-item', el),
'checkbox-checked': checked,
'checkbox-disabled': disabled,
@@ -365,14 +379,14 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
);
}
private getSVGPath(mode: Mode, indeterminate: boolean): HTMLElement {
private getSVGPath(theme: Theme, indeterminate: boolean): HTMLElement {
let path = indeterminate ? (
<path d="M6 12L18 12" part="mark" />
) : (
<path d="M5.9,12.5l3.8,3.8l8.8-8.8" part="mark" />
);
if (mode === 'md') {
if (theme === 'md') {
path = indeterminate ? (
<path d="M2 12H22" part="mark" />
) : (
@@ -380,6 +394,14 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
);
}
if (theme === 'ionic') {
path = indeterminate ? (
<path d="M6.5 12H17.5" stroke-linecap="round" part="mark" />
) : (
<path d="M6 12.5L10 16.5L18.5 8" stroke-linecap="round" stroke-linejoin="round" part="mark" />
);
}
return path;
}
}

View File

@@ -2,17 +2,19 @@ import type { ComponentInterface } from '@stencil/core';
import { Component, Host, Prop, h } from '@stencil/core';
import { createColorClasses } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color } from '../../interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-chip',
styleUrls: {
ios: 'chip.ios.scss',
md: 'chip.md.scss',
ionic: 'chip.md.scss',
},
shadow: true,
})
@@ -35,20 +37,20 @@ export class Chip implements ComponentInterface {
@Prop() disabled = false;
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
aria-disabled={this.disabled ? 'true' : null}
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
'chip-outline': this.outline,
'chip-disabled': this.disabled,
'ion-activatable': true,
})}
>
<slot></slot>
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
{theme === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</Host>
);
}

View File

@@ -2,7 +2,7 @@ import type { ComponentInterface } from '@stencil/core';
import { Component, Host, Listen, Prop, forceUpdate, h } from '@stencil/core';
import { matchBreakpoint } from '@utils/media';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
const win = typeof (window as any) !== 'undefined' ? (window as any) : undefined;
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
@@ -249,11 +249,11 @@ export class Col implements ComponentInterface {
render() {
const isRTL = document.dir === 'rtl';
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={{
[mode]: true,
[theme]: true,
}}
style={{
...this.calculateOffset(isRTL),

View File

@@ -5,7 +5,7 @@ import { isPlatform } from '@utils/platform';
import { isRTL } from '@utils/rtl';
import { createColorClasses, hostContext } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonMode, getIonTheme } from '../../global/ionic-global';
import type { Color } from '../../interface';
import type { ScrollBaseDetail, ScrollDetail } from './content-interface';
@@ -425,9 +425,9 @@ export class Content implements ComponentInterface {
render() {
const { isMainContent, scrollX, scrollY, el } = this;
const rtl = isRTL(el) ? 'rtl' : 'ltr';
const mode = getIonMode(this);
const theme = getIonTheme(this);
const forceOverscroll = this.shouldForceOverscroll();
const transitionShadow = mode === 'ios';
const transitionShadow = theme === 'ios';
const TagType = isMainContent ? 'main' : ('div' as any);
this.resize();
@@ -435,7 +435,7 @@ export class Content implements ComponentInterface {
return (
<Host
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
'content-sizing': hostContext('ion-popover', this.el),
overscroll: forceOverscroll,
[`content-${rtl}`]: true,

View File

@@ -4,7 +4,7 @@ import { componentOnReady, addEventListener } from '@utils/helpers';
import { printIonError } from '@utils/logging';
import { createColorClasses } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color } from '../../interface';
import type { DatetimePresentation } from '../datetime/datetime-interface';
import { getToday } from '../datetime/utils/data';
@@ -12,7 +12,8 @@ import { getMonthAndYear, getMonthDayAndYear, getLocalizedDateTime, getLocalized
import { getHourCycle } from '../datetime/utils/helpers';
import { parseDate } from '../datetime/utils/parse';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @slot date-target - Content displayed inside of the date button.
* @slot time-target - Content displayed inside of the time button.
@@ -413,12 +414,12 @@ export class DatetimeButton implements ComponentInterface {
render() {
const { color, dateText, timeText, selectedButton, datetimeActive, disabled } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={createColorClasses(color, {
[mode]: true,
[theme]: true,
[`${selectedButton}-active`]: datetimeActive,
['datetime-button-disabled']: disabled,
})}
@@ -434,7 +435,7 @@ export class DatetimeButton implements ComponentInterface {
ref={(el) => (this.dateTargetEl = el)}
>
<slot name="date-target">{dateText}</slot>
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
{theme === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</button>
)}
@@ -449,7 +450,7 @@ export class DatetimeButton implements ComponentInterface {
ref={(el) => (this.timeTargetEl = el)}
>
<slot name="time-target">{timeText}</slot>
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
{theme === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</button>
)}
</Host>

View File

@@ -7,8 +7,8 @@ import { isRTL } from '@utils/rtl';
import { createColorClasses } from '@utils/theme';
import { caretDownSharp, caretUpSharp, chevronBack, chevronDown, chevronForward } from 'ionicons/icons';
import { getIonMode } from '../../global/ionic-global';
import type { Color, Mode, StyleEventDetail } from '../../interface';
import { getIonMode, getIonTheme } from '../../global/ionic-global';
import type { Color, StyleEventDetail, Theme } from '../../interface';
import type { PickerColumnItem } from '../picker-column-internal/picker-column-internal-interfaces';
import type {
@@ -70,7 +70,8 @@ import {
} from './utils/state';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @slot title - The title of the datetime.
* @slot buttons - The buttons in the datetime.
@@ -98,6 +99,7 @@ import {
styleUrls: {
ios: 'datetime.ios.scss',
md: 'datetime.md.scss',
ionic: 'datetime.md.scss',
},
shadow: true,
})
@@ -2048,10 +2050,10 @@ export class Datetime implements ComponentInterface {
* Grid Render Methods
*/
private renderCalendarHeader(mode: Mode) {
private renderCalendarHeader(theme: Theme) {
const { disabled } = this;
const expandedIcon = mode === 'ios' ? chevronDown : caretUpSharp;
const collapsedIcon = mode === 'ios' ? chevronForward : caretDownSharp;
const expandedIcon = theme === 'ios' ? chevronDown : caretUpSharp;
const collapsedIcon = theme === 'ios' ? chevronForward : caretDownSharp;
const prevMonthDisabled = disabled || isPrevMonthDisabled(this.workingParts, this.minParts, this.maxParts);
const nextMonthDisabled = disabled || isNextMonthDisabled(this.workingParts, this.maxParts);
@@ -2129,7 +2131,7 @@ export class Datetime implements ComponentInterface {
</div>
</div>
<div class="calendar-days-of-week" aria-hidden="true">
{getDaysOfWeek(this.locale, mode, this.firstDayOfWeek % 7).map((d) => {
{getDaysOfWeek(this.locale, theme, this.firstDayOfWeek % 7).map((d) => {
return <div class="day-of-week">{d}</div>;
})}
</div>
@@ -2336,10 +2338,10 @@ export class Datetime implements ComponentInterface {
</div>
);
}
private renderCalendar(mode: Mode) {
private renderCalendar(theme: Theme) {
return (
<div class="datetime-calendar" key="datetime-calendar">
{this.renderCalendarHeader(mode)}
{this.renderCalendarHeader(theme)}
{this.renderCalendarBody()}
</div>
);
@@ -2494,7 +2496,7 @@ export class Datetime implements ComponentInterface {
* All presentation types are rendered from here.
*/
private renderDatetime(mode: Mode) {
private renderDatetime(theme: Theme) {
const { presentation, preferWheel } = this;
/**
@@ -2510,7 +2512,7 @@ export class Datetime implements ComponentInterface {
case 'date-time':
return [
this.renderHeader(),
this.renderCalendar(mode),
this.renderCalendar(theme),
this.renderCalendarViewMonthYearPicker(),
this.renderTime(),
this.renderFooter(),
@@ -2519,7 +2521,7 @@ export class Datetime implements ComponentInterface {
return [
this.renderHeader(),
this.renderTime(),
this.renderCalendar(mode),
this.renderCalendar(theme),
this.renderCalendarViewMonthYearPicker(),
this.renderFooter(),
];
@@ -2532,7 +2534,7 @@ export class Datetime implements ComponentInterface {
default:
return [
this.renderHeader(),
this.renderCalendar(mode),
this.renderCalendar(theme),
this.renderCalendarViewMonthYearPicker(),
this.renderFooter(),
];
@@ -2553,7 +2555,7 @@ export class Datetime implements ComponentInterface {
size,
isGridStyle,
} = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const isMonthAndYearPresentation =
presentation === 'year' || presentation === 'month' || presentation === 'month-year';
const shouldShowMonthAndYear = showMonthAndYear || isMonthAndYearPresentation;
@@ -2570,7 +2572,7 @@ export class Datetime implements ComponentInterface {
onBlur={this.onBlur}
class={{
...createColorClasses(color, {
[mode]: true,
[theme]: true,
['datetime-readonly']: readonly,
['datetime-disabled']: disabled,
'show-month-and-year': shouldShowMonthAndYear,
@@ -2582,7 +2584,7 @@ export class Datetime implements ComponentInterface {
}),
}}
>
{this.renderDatetime(mode)}
{this.renderDatetime(theme)}
</Host>
);
}

View File

@@ -1,4 +1,4 @@
import type { Mode } from '../../../interface';
import type { Theme } from '../../../interface';
import type { PickerColumnItem } from '../../picker-column-internal/picker-column-internal-interfaces';
import type { DatetimeParts, DatetimeHourCycle } from '../datetime-interface';
@@ -65,13 +65,13 @@ const hour24 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 1
* MD should display days such as "M"
* or "T".
*/
export const getDaysOfWeek = (locale: string, mode: Mode, firstDayOfWeek = 0) => {
export const getDaysOfWeek = (locale: string, theme: Theme, firstDayOfWeek = 0) => {
/**
* Nov 1st, 2020 starts on a Sunday.
* ion-datetime assumes weeks start on Sunday,
* but is configurable via `firstDayOfWeek`.
*/
const weekdayFormat = mode === 'ios' ? 'short' : 'narrow';
const weekdayFormat = theme === 'ios' ? 'short' : 'narrow';
const intl = new Intl.DateTimeFormat(locale, { weekday: weekdayFormat });
const startDate = new Date('11/01/2020');
const daysOfWeek = [];

View File

@@ -6,12 +6,13 @@ import type { Attributes } from '@utils/helpers';
import { createColorClasses, hostContext, openURL } from '@utils/theme';
import { close } from 'ionicons/icons';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { AnimationBuilder, Color } from '../../interface';
import type { RouterDirection } from '../router/utils/interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @part native - The native HTML button or anchor element that wraps all child elements.
* @part close-icon - The close icon that is displayed when a fab list opens (uses ion-icon).
@@ -21,6 +22,7 @@ import type { RouterDirection } from '../router/utils/interface';
styleUrls: {
ios: 'fab-button.ios.scss',
md: 'fab-button.md.scss',
ionic: 'fab-button.md.scss',
},
shadow: true,
})
@@ -153,7 +155,7 @@ export class FabButton implements ComponentInterface, AnchorInterface, ButtonInt
render() {
const { el, disabled, color, href, activated, show, translucent, size, inheritedAttributes } = this;
const inList = hostContext('ion-fab-list', el);
const mode = getIonMode(this);
const theme = getIonTheme(this);
const TagType = href === undefined ? 'button' : ('a' as any);
const attrs =
TagType === 'button'
@@ -170,7 +172,7 @@ export class FabButton implements ComponentInterface, AnchorInterface, ButtonInt
onClick={this.onClick}
aria-disabled={disabled ? 'true' : null}
class={createColorClasses(color, {
[mode]: true,
[theme]: true,
'fab-button-in-list': inList,
'fab-button-translucent-in-list': inList && translucent,
'fab-button-close-active': activated,
@@ -202,7 +204,7 @@ export class FabButton implements ComponentInterface, AnchorInterface, ButtonInt
<span class="button-inner">
<slot></slot>
</span>
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
{theme === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</TagType>
</Host>
);

View File

@@ -1,7 +1,7 @@
import type { ComponentInterface } from '@stencil/core';
import { Component, Element, Host, Prop, Watch, h } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
@Component({
tag: 'ion-fab-list',
@@ -33,11 +33,11 @@ export class FabList implements ComponentInterface {
@Prop() side: 'start' | 'end' | 'top' | 'bottom' = 'bottom';
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={{
[mode]: true,
[theme]: true,
'fab-list-active': this.activated,
[`fab-list-side-${this.side}`]: true,
}}

View File

@@ -1,7 +1,7 @@
import type { ComponentInterface } from '@stencil/core';
import { Component, Element, Host, Method, Prop, Watch, h } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
@Component({
tag: 'ion-fab',
@@ -76,11 +76,11 @@ export class Fab implements ComponentInterface {
render() {
const { horizontal, vertical, edge } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={{
[mode]: true,
[theme]: true,
[`fab-horizontal-${horizontal}`]: horizontal !== undefined,
[`fab-vertical-${vertical}`]: vertical !== undefined,
'fab-edge': edge,

View File

@@ -4,18 +4,20 @@ import { findIonContent, getScrollElement, printIonContentErrorMsg } from '@util
import type { KeyboardController } from '@utils/keyboard/keyboard-controller';
import { createKeyboardController } from '@utils/keyboard/keyboard-controller';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import { handleFooterFade } from './footer.utils';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-footer',
styleUrls: {
ios: 'footer.ios.scss',
md: 'footer.md.scss',
ionic: 'footer.md.scss',
},
})
export class Footer implements ComponentInterface {
@@ -73,8 +75,8 @@ export class Footer implements ComponentInterface {
}
private checkCollapsibleFooter = () => {
const mode = getIonMode(this);
if (mode !== 'ios') {
const theme = getIonTheme(this);
if (theme !== 'ios') {
return;
}
@@ -119,7 +121,7 @@ export class Footer implements ComponentInterface {
render() {
const { translucent, collapse } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const tabs = this.el.closest('ion-tabs');
const tabBar = tabs?.querySelector(':scope > ion-tab-bar');
@@ -127,19 +129,19 @@ export class Footer implements ComponentInterface {
<Host
role="contentinfo"
class={{
[mode]: true,
[theme]: true,
// Used internally for styling
[`footer-${mode}`]: true,
[`footer-${theme}`]: true,
[`footer-translucent`]: translucent,
[`footer-translucent-${mode}`]: translucent,
[`footer-translucent-${theme}`]: translucent,
['footer-toolbar-padding']: !this.keyboardVisible && (!tabBar || tabBar.slot !== 'bottom'),
[`footer-collapse-${collapse}`]: collapse !== undefined,
}}
>
{mode === 'ios' && translucent && <div class="footer-background"></div>}
{theme === 'ios' && translucent && <div class="footer-background"></div>}
<slot></slot>
</Host>
);

View File

@@ -1,7 +1,7 @@
import type { ComponentInterface } from '@stencil/core';
import { Component, Host, Prop, h } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
@Component({
tag: 'ion-grid',
@@ -15,11 +15,11 @@ export class Grid implements ComponentInterface {
@Prop() fixed = false;
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={{
[mode]: true,
[theme]: true,
'grid-fixed': this.fixed,
}}
>

View File

@@ -5,7 +5,7 @@ import type { Attributes } from '@utils/helpers';
import { inheritAriaAttributes } from '@utils/helpers';
import { hostContext } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import {
cloneElement,
@@ -18,13 +18,15 @@ import {
} from './header.utils';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-header',
styleUrls: {
ios: 'header.ios.scss',
md: 'header.md.scss',
ionic: 'header.md.scss',
},
})
export class Header implements ComponentInterface {
@@ -71,9 +73,9 @@ export class Header implements ComponentInterface {
}
private async checkCollapsibleHeader() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
if (mode !== 'ios') {
if (theme !== 'ios') {
return;
}
@@ -206,7 +208,7 @@ export class Header implements ComponentInterface {
render() {
const { translucent, inheritedAttributes } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const collapse = this.collapse || 'none';
// banner role must be at top level, so remove role if inside a menu
@@ -216,18 +218,18 @@ export class Header implements ComponentInterface {
<Host
role={roleType}
class={{
[mode]: true,
[theme]: true,
// Used internally for styling
[`header-${mode}`]: true,
[`header-${theme}`]: true,
[`header-translucent`]: this.translucent,
[`header-collapse-${collapse}`]: true,
[`header-translucent-${mode}`]: this.translucent,
[`header-translucent-${theme}`]: this.translucent,
}}
{...inheritedAttributes}
>
{mode === 'ios' && translucent && <div class="header-background"></div>}
{theme === 'ios' && translucent && <div class="header-background"></div>}
<slot></slot>
</Host>
);

View File

@@ -3,7 +3,7 @@ import { Component, Element, Event, Host, Prop, State, Watch, h } from '@stencil
import type { Attributes } from '@utils/helpers';
import { inheritAttributes } from '@utils/helpers';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
/**
* @part image - The inner `img` element.
@@ -110,8 +110,13 @@ export class Img implements ComponentInterface {
render() {
const { loadSrc, alt, onLoad, loadError, inheritedAttributes } = this;
const { draggable } = inheritedAttributes;
const theme = getIonTheme(this);
return (
<Host class={getIonMode(this)}>
<Host
class={{
[theme]: true,
}}
>
<img
decoding="async"
src={loadSrc}

View File

@@ -4,15 +4,19 @@ import { ENABLE_HTML_CONTENT_DEFAULT } from '@utils/config';
import { sanitizeDOMString } from '@utils/sanitization';
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { IonicSafeString } from '../../utils/sanitization';
import type { SpinnerTypes } from '../spinner/spinner-configs';
/**
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-infinite-scroll-content',
styleUrls: {
ios: 'infinite-scroll-content.ios.scss',
md: 'infinite-scroll-content.md.scss',
ionic: 'infinite-scroll-content.md.scss',
},
})
export class InfiniteScrollContent implements ComponentInterface {
@@ -41,10 +45,10 @@ export class InfiniteScrollContent implements ComponentInterface {
componentDidLoad() {
if (this.loadingSpinner === undefined) {
const mode = getIonMode(this);
const theme = getIonTheme(this);
this.loadingSpinner = config.get(
'infiniteLoadingSpinner',
config.get('spinner', mode === 'ios' ? 'lines' : 'crescent')
config.get('spinner', theme === 'ios' ? 'lines' : 'crescent')
);
}
}
@@ -59,14 +63,14 @@ export class InfiniteScrollContent implements ComponentInterface {
}
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={{
[mode]: true,
[theme]: true,
// Used internally for styling
[`infinite-scroll-content-${mode}`]: true,
[`infinite-scroll-content-${theme}`]: true,
}}
>
<div class="infinite-loading">

View File

@@ -2,7 +2,7 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Method, Prop, State, Watch, h, readTask, writeTask } from '@stencil/core';
import { findClosestIonContent, getScrollElement, printIonContentErrorMsg } from '@utils/content';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
@Component({
tag: 'ion-infinite-scroll',
@@ -221,12 +221,12 @@ export class InfiniteScroll implements ComponentInterface {
}
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
const disabled = this.disabled;
return (
<Host
class={{
[mode]: true,
[theme]: true,
'infinite-scroll-loading': this.isLoading,
'infinite-scroll-enabled': !disabled,
}}

View File

@@ -16,14 +16,15 @@ import type { SlotMutationController } from '@utils/slot-mutation-controller';
import { createColorClasses, hostContext } from '@utils/theme';
import { closeCircle, closeSharp } from 'ionicons/icons';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { AutocompleteTypes, Color, StyleEventDetail, TextFieldTypes } from '../../interface';
import type { InputChangeEventDetail, InputInputEventDetail } from './input-interface';
import { getCounterText } from './input.utils';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @slot label - The label text to associate with the input. Use the `labelPlacement` property to control where the label is placed relative to the input. Use this if you need to render a label with custom HTML. (EXPERIMENTAL)
* @slot start - Content to display at the leading edge of the input. (EXPERIMENTAL)
@@ -34,6 +35,7 @@ import { getCounterText } from './input.utils';
styleUrls: {
ios: 'input.ios.scss',
md: 'input.md.scss',
ionic: 'input.md.scss',
},
scoped: true,
})
@@ -690,8 +692,8 @@ export class Input implements ComponentInterface {
* when fill="outline".
*/
private renderLabelContainer() {
const mode = getIonMode(this);
const hasOutlineFill = mode === 'md' && this.fill === 'outline';
const theme = getIonTheme(this);
const hasOutlineFill = theme === 'md' && this.fill === 'outline';
if (hasOutlineFill) {
/**
@@ -729,10 +731,10 @@ export class Input implements ComponentInterface {
private renderInput() {
const { disabled, fill, readonly, shape, inputId, labelPlacement, el, hasFocus } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const value = this.getValue();
const inItem = hostContext('ion-item', this.el);
const shouldRenderHighlight = mode === 'md' && fill !== 'outline' && !inItem;
const shouldRenderHighlight = theme === 'md' && fill !== 'outline' && !inItem;
const hasValue = this.hasValue();
const hasStartEndSlots = el.querySelector('[slot="start"], [slot="end"]') !== null;
@@ -760,7 +762,7 @@ export class Input implements ComponentInterface {
return (
<Host
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
'has-value': hasValue,
'has-focus': hasFocus,
'label-floating': labelShouldFloat,
@@ -833,7 +835,7 @@ export class Input implements ComponentInterface {
}}
onClick={this.clearTextInput}
>
<ion-icon aria-hidden="true" icon={mode === 'ios' ? closeCircle : closeSharp}></ion-icon>
<ion-icon aria-hidden="true" icon={theme === 'ios' ? closeCircle : closeSharp}></ion-icon>
</button>
)}
<slot name="end"></slot>
@@ -872,7 +874,7 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
this.hasLoggedDeprecationWarning = true;
}
const mode = getIonMode(this);
const theme = getIonTheme(this);
const value = this.getValue();
const labelId = this.inputId + '-lbl';
const label = findItemLabel(this.el);
@@ -884,7 +886,7 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
<Host
aria-disabled={this.disabled ? 'true' : null}
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
'has-value': this.hasValue(),
'has-focus': this.hasFocus,
'legacy-input': true,
@@ -940,7 +942,7 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
}}
onClick={this.clearTextInput}
>
<ion-icon aria-hidden="true" icon={mode === 'ios' ? closeCircle : closeSharp}></ion-icon>
<ion-icon aria-hidden="true" icon={theme === 'ios' ? closeCircle : closeSharp}></ion-icon>
</button>
)}
</Host>

View File

@@ -2,11 +2,12 @@ import type { ComponentInterface } from '@stencil/core';
import { Component, Element, Host, Prop, h } from '@stencil/core';
import { createColorClasses } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color } from '../../interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @slot - Content is placed between the named slots if provided without a slot.
* @slot start - Content is placed to the left of the divider text in LTR, and to the right in RTL.
@@ -17,6 +18,7 @@ import type { Color } from '../../interface';
styleUrls: {
ios: 'item-divider.ios.scss',
md: 'item-divider.md.scss',
ionic: 'item-divider.md.scss',
},
shadow: true,
})
@@ -40,11 +42,11 @@ export class ItemDivider implements ComponentInterface {
@Prop() sticky = false;
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
'item-divider-sticky': this.sticky,
item: true,
})}

View File

@@ -1,26 +1,30 @@
import type { ComponentInterface } from '@stencil/core';
import { Component, Host, h } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
/**
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-item-group',
styleUrls: {
ios: 'item-group.ios.scss',
md: 'item-group.md.scss',
ionic: 'item-group.md.scss',
},
})
export class ItemGroup implements ComponentInterface {
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
role="group"
class={{
[mode]: true,
[theme]: true,
// Used internally for styling
[`item-group-${mode}`]: true,
[`item-group-${theme}`]: true,
item: true,
}}

View File

@@ -3,11 +3,12 @@ import { Component, Element, Host, Prop, h } from '@stencil/core';
import type { AnchorInterface, ButtonInterface } from '@utils/element-interface';
import { createColorClasses } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color } from '../../interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @slot - Content is placed between the named slots if provided without a slot.
* @slot start - Content is placed to the left of the option text in LTR, and to the right in RTL.
@@ -23,6 +24,7 @@ import type { Color } from '../../interface';
styleUrls: {
ios: 'item-option.ios.scss',
md: 'item-option.md.scss',
ionic: 'item-option.md.scss',
},
shadow: true,
})
@@ -88,7 +90,7 @@ export class ItemOption implements ComponentInterface, AnchorInterface, ButtonIn
render() {
const { disabled, expandable, href } = this;
const TagType = href === undefined ? 'button' : ('a' as any);
const mode = getIonMode(this);
const theme = getIonTheme(this);
const attrs =
TagType === 'button'
? { type: this.type }
@@ -102,7 +104,7 @@ export class ItemOption implements ComponentInterface, AnchorInterface, ButtonIn
<Host
onClick={this.onClick}
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
'item-option-disabled': disabled,
'item-option-expandable': expandable,
'ion-activatable': true,
@@ -119,7 +121,7 @@ export class ItemOption implements ComponentInterface, AnchorInterface, ButtonIn
</div>
<slot name="bottom"></slot>
</span>
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
{theme === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</TagType>
</Host>
);

View File

@@ -2,14 +2,18 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Method, Prop, h } from '@stencil/core';
import { isEndSide } from '@utils/helpers';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Side } from '../menu/menu-interface';
/**
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-item-options',
styleUrls: {
ios: 'item-options.ios.scss',
md: 'item-options.md.scss',
ionic: 'item-options.md.scss',
},
})
export class ItemOptions implements ComponentInterface {
@@ -35,15 +39,15 @@ export class ItemOptions implements ComponentInterface {
}
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
const isEnd = isEndSide(this.side);
return (
<Host
class={{
[mode]: true,
[theme]: true,
// Used internally for styling
[`item-options-${mode}`]: true,
[`item-options-${theme}`]: true,
/**
* Note: The "start" and "end" terms refer to the

View File

@@ -4,7 +4,7 @@ import { findClosestIonContent, disableContentScrollY, resetContentScrollY } fro
import { isEndSide } from '@utils/helpers';
import { watchForOptions } from '@utils/watch-options';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Gesture, GestureDetail } from '../../interface';
import type { Side } from '../menu/menu-interface';
@@ -482,11 +482,11 @@ export class ItemSliding implements ComponentInterface {
}
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={{
[mode]: true,
[theme]: true,
'item-sliding-active-slide': this.state !== SlidingState.Disabled,
'item-sliding-active-options-end': (this.state & SlidingState.End) !== 0,
'item-sliding-active-options-start': (this.state & SlidingState.Start) !== 0,

View File

@@ -7,7 +7,7 @@ import { printIonError, printIonWarning } from '@utils/logging';
import { createColorClasses, hostContext, openURL } from '@utils/theme';
import { chevronForward } from 'ionicons/icons';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { AnimationBuilder, Color, CssClassMap, StyleEventDetail } from '../../interface';
import type { InputInputEventDetail } from '../input/input-interface';
import type { RouterDirection } from '../router/utils/interface';
@@ -15,7 +15,8 @@ import type { RouterDirection } from '../router/utils/interface';
import type { CounterFormatter } from './item-interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @slot - Content is placed between the named slots if provided without a slot.
* @slot start - Content is placed to the left of the item text in LTR, and to the right in RTL.
@@ -31,6 +32,7 @@ import type { CounterFormatter } from './item-interface';
styleUrls: {
ios: 'item.ios.scss',
md: 'item.md.scss',
ionic: 'item.md.scss',
},
shadow: {
delegatesFocus: true,
@@ -384,7 +386,7 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
multipleInputs,
} = this;
const childStyles = {} as StyleEventDetail;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const clickable = this.isClickable();
const canActivate = this.canActivate();
const TagType = clickable ? (href === undefined ? 'button' : 'a') : ('div' as any);
@@ -433,7 +435,7 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
};
}
const showDetail = detail !== undefined ? detail : mode === 'ios' && clickable;
const showDetail = detail !== undefined ? detail : theme === 'ios' && clickable;
this.itemStyles.forEach((value) => {
Object.assign(childStyles, value);
});
@@ -449,7 +451,7 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
...labelColorStyles,
...createColorClasses(this.color, {
item: true,
[mode]: true,
[theme]: true,
'item-lines-default': lines === undefined,
[`item-lines-${lines}`]: lines !== undefined,
[`item-fill-${fillValue}`]: true,
@@ -491,7 +493,7 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
)}
<div class="item-inner-highlight"></div>
</div>
{canActivate && mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
{canActivate && theme === 'md' && <ion-ripple-effect></ion-ripple-effect>}
<div class="item-highlight"></div>
</TagType>
<div class="item-bottom">

View File

@@ -2,17 +2,19 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Prop, State, Watch, h } from '@stencil/core';
import { createColorClasses, hostContext } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color, StyleEventDetail } from '../../interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-label',
styleUrls: {
ios: 'label.ios.scss',
md: 'label.md.scss',
ionic: 'label.md.scss',
},
scoped: true,
})
@@ -97,11 +99,11 @@ export class Label implements ComponentInterface {
render() {
const position = this.position;
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
'in-item-color': hostContext('ion-item.ion-color', this.el),
[`label-${position}`]: position !== undefined,
[`label-no-animate`]: this.noAnimate,

View File

@@ -2,17 +2,19 @@ import type { ComponentInterface } from '@stencil/core';
import { Component, Host, Prop, h } from '@stencil/core';
import { createColorClasses } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color } from '../../interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-list-header',
styleUrls: {
ios: 'list-header.ios.scss',
md: 'list-header.md.scss',
ionic: 'list-header.md.scss',
},
shadow: true,
})
@@ -31,12 +33,12 @@ export class ListHeader implements ComponentInterface {
render() {
const { lines } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
[`list-header-lines-${lines}`]: lines !== undefined,
})}
>

View File

@@ -1,16 +1,18 @@
import type { ComponentInterface } from '@stencil/core';
import { Component, Element, Host, Method, Prop, h } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-list',
styleUrls: {
ios: 'list.ios.scss',
md: 'list.md.scss',
ionic: 'list.md.scss',
},
})
export class List implements ComponentInterface {
@@ -42,20 +44,20 @@ export class List implements ComponentInterface {
}
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
const { lines, inset } = this;
return (
<Host
role="list"
class={{
[mode]: true,
[theme]: true,
// Used internally for styling
[`list-${mode}`]: true,
[`list-${theme}`]: true,
'list-inset': inset,
[`list-lines-${lines}`]: lines !== undefined,
[`list-${mode}-lines-${lines}`]: lines !== undefined,
[`list-${theme}-lines-${lines}`]: lines !== undefined,
}}
></Host>
);

View File

@@ -17,7 +17,7 @@ import { sanitizeDOMString } from '@utils/sanitization';
import { getClassMap } from '@utils/theme';
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { AnimationBuilder, FrameworkDelegate, OverlayInterface } from '../../interface';
import type { OverlayEventDetail } from '../../utils/overlays-interface';
import type { IonicSafeString } from '../../utils/sanitization';
@@ -31,13 +31,15 @@ import { mdLeaveAnimation } from './animations/md.leave';
// TODO(FW-2832): types
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-loading',
styleUrls: {
ios: 'loading.ios.scss',
md: 'loading.md.scss',
ionic: 'loading.md.scss',
},
scoped: true,
})
@@ -211,8 +213,8 @@ export class Loading implements ComponentInterface, OverlayInterface {
componentWillLoad() {
if (this.spinner === undefined) {
const mode = getIonMode(this);
this.spinner = config.get('loadingSpinner', config.get('spinner', mode === 'ios' ? 'lines' : 'crescent'));
const theme = getIonTheme(this);
this.spinner = config.get('loadingSpinner', config.get('spinner', theme === 'ios' ? 'lines' : 'crescent'));
}
setOverlayId(this.el);
}
@@ -326,7 +328,7 @@ export class Loading implements ComponentInterface, OverlayInterface {
render() {
const { message, spinner, htmlAttributes, overlayIndex } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const msgId = `loading-${overlayIndex}-msg`;
/**
* If the message is defined, use that as the label.
@@ -347,7 +349,7 @@ export class Loading implements ComponentInterface, OverlayInterface {
onIonBackdropTap={this.onBackdropTap}
class={{
...getClassMap(this.cssClass),
[mode]: true,
[theme]: true,
'overlay-hidden': true,
'loading-translucent': this.translucent,
}}

View File

@@ -8,12 +8,13 @@ import { createColorClasses, hostContext } from '@utils/theme';
import { menuOutline, menuSharp } from 'ionicons/icons';
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color } from '../../interface';
import { updateVisibility } from '../menu-toggle/menu-toggle-util';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @part native - The native HTML button element that wraps all child elements.
* @part icon - The menu button icon (uses ion-icon).
@@ -23,6 +24,7 @@ import { updateVisibility } from '../menu-toggle/menu-toggle-util';
styleUrls: {
ios: 'menu-button.ios.scss',
md: 'menu-button.md.scss',
ionic: 'menu-button.md.scss',
},
shadow: true,
})
@@ -80,8 +82,8 @@ export class MenuButton implements ComponentInterface, ButtonInterface {
render() {
const { color, disabled, inheritedAttributes } = this;
const mode = getIonMode(this);
const menuIcon = config.get('menuIcon', mode === 'ios' ? menuOutline : menuSharp);
const theme = getIonTheme(this);
const menuIcon = config.get('menuIcon', theme === 'ios' ? menuOutline : menuSharp);
const hidden = this.autoHide && !this.visible;
const attrs = {
@@ -96,7 +98,7 @@ export class MenuButton implements ComponentInterface, ButtonInterface {
aria-disabled={disabled ? 'true' : null}
aria-hidden={hidden ? 'true' : null}
class={createColorClasses(color, {
[mode]: true,
[theme]: true,
button: true, // ion-buttons target .button
'menu-button-hidden': hidden,
'menu-button-disabled': disabled,
@@ -109,10 +111,11 @@ export class MenuButton implements ComponentInterface, ButtonInterface {
<button {...attrs} disabled={disabled} class="button-native" part="native" aria-label={ariaLabel}>
<span class="button-inner">
<slot>
<ion-icon part="icon" icon={menuIcon} mode={mode} lazy={false} aria-hidden="true"></ion-icon>
{/* TODO double check the mode/theme attribute */}
<ion-icon part="icon" icon={menuIcon} mode={theme} lazy={false} aria-hidden="true"></ion-icon>
</slot>
</span>
{mode === 'md' && <ion-ripple-effect type="unbounded"></ion-ripple-effect>}
{theme === 'md' && <ion-ripple-effect type="unbounded"></ion-ripple-effect>}
</button>
</Host>
);

View File

@@ -2,7 +2,7 @@ import type { ComponentInterface } from '@stencil/core';
import { Component, Host, Listen, Prop, State, h } from '@stencil/core';
import { menuController } from '@utils/menu-controller';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import { updateVisibility } from './menu-toggle-util';
@@ -50,7 +50,7 @@ export class MenuToggle implements ComponentInterface {
};
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
const hidden = this.autoHide && !this.visible;
return (
@@ -58,7 +58,7 @@ export class MenuToggle implements ComponentInterface {
onClick={this.onClick}
aria-hidden={hidden ? 'true' : null}
class={{
[mode]: true,
[theme]: true,
'menu-toggle-hidden': hidden,
}}
>

View File

@@ -9,7 +9,7 @@ import { menuController } from '@utils/menu-controller';
import { getPresentedOverlay } from '@utils/overlays';
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { getIonMode, getIonTheme } from '../../global/ionic-global';
import type { Animation, Gesture, GestureDetail } from '../../interface';
import type { MenuChangeEventDetail, MenuI, Side } from './menu-interface';
@@ -22,6 +22,8 @@ const focusableQueryString =
'[tabindex]:not([tabindex^="-"]), input:not([type=hidden]):not([tabindex^="-"]), textarea:not([tabindex^="-"]), button:not([tabindex^="-"]), select:not([tabindex^="-"]), .ion-focusable:not([tabindex^="-"])';
/**
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @part container - The container for the menu content.
* @part backdrop - The backdrop that appears over the main content when the menu is open.
*/
@@ -30,6 +32,7 @@ const focusableQueryString =
styleUrls: {
ios: 'menu.ios.scss',
md: 'menu.md.scss',
ionic: 'menu.md.scss',
},
shadow: true,
})
@@ -779,7 +782,7 @@ export class Menu implements ComponentInterface, MenuI {
render() {
const { type, disabled, isPaneVisible, inheritedAttributes, side } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
/**
* If the Close Watcher is enabled then
@@ -792,7 +795,7 @@ export class Menu implements ComponentInterface, MenuI {
role="navigation"
aria-label={inheritedAttributes['aria-label'] || 'menu'}
class={{
[mode]: true,
[theme]: true,
[`menu-type-${type}`]: true,
'menu-enabled': !disabled,
[`menu-side-${side}`]: true,

View File

@@ -21,7 +21,7 @@ import { getClassMap } from '@utils/theme';
import { deepReady, waitForMount } from '@utils/transition';
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { getIonMode, getIonTheme } from '../../global/ionic-global';
import type {
Animation,
AnimationBuilder,
@@ -47,7 +47,8 @@ import { setCardStatusBarDark, setCardStatusBarDefault } from './utils';
// TODO(FW-2832): types
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @slot - Content is placed inside of the `.modal-content` element.
*
@@ -60,6 +61,7 @@ import { setCardStatusBarDark, setCardStatusBarDefault } from './utils';
styleUrls: {
ios: 'modal.ios.scss',
md: 'modal.md.scss',
ionic: 'modal.md.scss',
},
shadow: true,
})
@@ -871,8 +873,8 @@ export class Modal implements ComponentInterface, OverlayInterface {
const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior, inheritedAttributes } = this;
const showHandle = handle !== false && isSheetModal;
const mode = getIonMode(this);
const isCardModal = presentingElement !== undefined && mode === 'ios';
const theme = getIonTheme(this);
const isCardModal = presentingElement !== undefined && theme === 'ios';
const isHandleCycle = handleBehavior === 'cycle';
return (
@@ -884,7 +886,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
zIndex: `${20000 + this.overlayIndex}`,
}}
class={{
[mode]: true,
[theme]: true,
['modal-default']: !isCardModal && !isSheetModal,
[`modal-card`]: isCardModal,
[`modal-sheet`]: isSheetModal,
@@ -904,7 +906,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
part="backdrop"
/>
{mode === 'ios' && <div class="modal-shadow"></div>}
{theme === 'ios' && <div class="modal-shadow"></div>}
<div
/*

View File

@@ -61,7 +61,7 @@ export class Nav implements NavOutlet {
@Prop() animated = true;
/**
* By default `ion-nav` animates transition between pages based in the mode (ios or material design).
* By default `ion-nav` animates transition between pages based on the platform (ios or md).
* However, this property allows to create custom transition using `AnimationBuilder` functions.
*/
@Prop() animation?: AnimationBuilder;

View File

@@ -2,17 +2,19 @@ import type { ComponentInterface } from '@stencil/core';
import { Component, Host, Prop, h } from '@stencil/core';
import { createColorClasses } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color } from '../../interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-note',
styleUrls: {
ios: 'note.ios.scss',
md: 'note.md.scss',
ionic: 'note.md.scss',
},
shadow: true,
})
@@ -25,11 +27,11 @@ export class Note implements ComponentInterface {
@Prop({ reflect: true }) color?: Color;
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
})}
>
<slot></slot>

View File

@@ -5,14 +5,16 @@ import { hapticSelectionChanged, hapticSelectionEnd, hapticSelectionStart } from
import { isPlatform } from '@utils/platform';
import { createColorClasses } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color } from '../../interface';
import type { PickerInternalCustomEvent } from '../picker-internal/picker-internal-interfaces';
import type { PickerColumnItem } from './picker-column-internal-interfaces';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @internal
*/
@Component({
@@ -20,6 +22,7 @@ import type { PickerColumnItem } from './picker-column-internal-interfaces';
styleUrls: {
ios: 'picker-column-internal.ios.scss',
md: 'picker-column-internal.md.scss',
ionic: 'picker-column-internal.md.scss',
},
shadow: true,
})
@@ -422,7 +425,7 @@ export class PickerColumnInternal implements ComponentInterface {
render() {
const { items, color, disabled: pickerDisabled, isActive, numericInput } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
/**
* exportparts is needed so ion-datetime can expose the parts
@@ -437,7 +440,7 @@ export class PickerColumnInternal implements ComponentInterface {
disabled={pickerDisabled}
tabindex={pickerDisabled ? null : 0}
class={createColorClasses(color, {
[mode]: true,
[theme]: true,
['picker-column-active']: isActive,
['picker-column-numeric-input']: numericInput,
})}

View File

@@ -4,11 +4,13 @@ import { clamp } from '@utils/helpers';
import { hapticSelectionChanged, hapticSelectionEnd, hapticSelectionStart } from '@utils/native/haptic';
import { getClassMap } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Gesture, GestureDetail } from '../../interface';
import type { PickerColumn } from '../picker/picker-interface';
/**
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @internal
*/
@Component({
@@ -16,6 +18,7 @@ import type { PickerColumn } from '../picker/picker-interface';
styleUrls: {
ios: 'picker-column.ios.scss',
md: 'picker-column.md.scss',
ionic: 'picker-column.md.scss',
},
})
export class PickerColumnCmp implements ComponentInterface {
@@ -61,9 +64,9 @@ export class PickerColumnCmp implements ComponentInterface {
let pickerRotateFactor = 0;
let pickerScaleFactor = 0.81;
const mode = getIonMode(this);
const theme = getIonTheme(this);
if (mode === 'ios') {
if (theme === 'ios') {
pickerRotateFactor = -0.46;
pickerScaleFactor = 1;
}
@@ -394,11 +397,11 @@ export class PickerColumnCmp implements ComponentInterface {
render() {
const col = this.col;
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={{
[mode]: true,
[theme]: true,
'picker-col': true,
'picker-opts-left': this.col.align === 'left',
'picker-opts-right': this.col.align === 'right',

View File

@@ -5,7 +5,9 @@ import { getElementRoot } from '@utils/helpers';
import type { PickerInternalChangeEventDetail } from './picker-internal-interfaces';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @internal
*/
@Component({
@@ -13,6 +15,7 @@ import type { PickerInternalChangeEventDetail } from './picker-internal-interfac
styleUrls: {
ios: 'picker-internal.ios.scss',
md: 'picker-internal.md.scss',
ionic: 'picker-internal.md.scss',
},
shadow: true,
})

View File

@@ -16,7 +16,7 @@ import {
} from '@utils/overlays';
import { getClassMap } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { AnimationBuilder, CssClassMap, OverlayInterface, FrameworkDelegate } from '../../interface';
import type { OverlayEventDetail } from '../../utils/overlays-interface';
@@ -27,13 +27,15 @@ import type { PickerButton, PickerColumn } from './picker-interface';
// TODO(FW-2832): types
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-picker',
styleUrls: {
ios: 'picker.ios.scss',
md: 'picker.md.scss',
ionic: 'picker.md.scss',
},
scoped: true,
})
@@ -349,7 +351,7 @@ export class Picker implements ComponentInterface, OverlayInterface {
render() {
const { htmlAttributes } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
aria-modal="true"
@@ -359,10 +361,10 @@ export class Picker implements ComponentInterface, OverlayInterface {
zIndex: `${20000 + this.overlayIndex}`,
}}
class={{
[mode]: true,
[theme]: true,
// Used internally for styling
[`picker-${mode}`]: true,
[`picker-${theme}`]: true,
'overlay-hidden': true,
...getClassMap(this.cssClass),
}}

View File

@@ -5,6 +5,7 @@ import type {
FrameworkDelegate,
Mode,
OverlayInterface,
Theme,
} from '../../interface';
export interface PopoverInterface extends OverlayInterface {
@@ -22,7 +23,11 @@ export interface PopoverOptions<T extends ComponentRef = ComponentRef> {
delegate?: FrameworkDelegate;
animated?: boolean;
/**
* @deprecated
*/
mode?: Mode;
theme?: Theme;
keyboardClose?: boolean;
id?: string;
htmlAttributes?: { [key: string]: any };

View File

@@ -17,7 +17,7 @@ import { isPlatform } from '@utils/platform';
import { getClassMap } from '@utils/theme';
import { deepReady, waitForMount } from '@utils/transition';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { AnimationBuilder, ComponentProps, ComponentRef, FrameworkDelegate } from '../../interface';
import type { OverlayEventDetail } from '../../utils/overlays-interface';
@@ -38,7 +38,8 @@ import { configureDismissInteraction, configureKeyboardInteraction, configureTri
// TODO(FW-2832): types
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @slot - Content is placed inside of the `.popover-content` element.
*
@@ -51,6 +52,7 @@ import { configureDismissInteraction, configureKeyboardInteraction, configureTri
styleUrls: {
ios: 'popover.ios.scss',
md: 'popover.md.scss',
ionic: 'popover.md.scss',
},
shadow: true,
})
@@ -350,7 +352,7 @@ export class Popover implements ComponentInterface, PopoverInterface {
this.parentPopover = el.closest(`ion-popover:not(#${popoverId})`) as HTMLIonPopoverElement | null;
if (this.alignment === undefined) {
this.alignment = getIonMode(this) === 'ios' ? 'center' : 'start';
this.alignment = getIonTheme(this) === 'ios' ? 'center' : 'start';
}
}
@@ -662,7 +664,7 @@ export class Popover implements ComponentInterface, PopoverInterface {
};
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
const { onLifecycle, parentPopover, dismissOnSelect, side, arrow, htmlAttributes } = this;
const desktop = isPlatform('desktop');
const enableArrow = arrow && !parentPopover;
@@ -678,7 +680,7 @@ export class Popover implements ComponentInterface, PopoverInterface {
}}
class={{
...getClassMap(this.cssClass),
[mode]: true,
[theme]: true,
'popover-translucent': this.translucent,
'overlay-hidden': true,
'popover-desktop': desktop,

View File

@@ -4,11 +4,12 @@ import { clamp } from '@utils/helpers';
import { createColorClasses } from '@utils/theme';
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color } from '../../interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @part progress - The progress bar that shows the current value when `type` is `"determinate"` and slides back and forth when `type` is `"indeterminate"`.
* @part stream - The animated circles that appear while buffering. This only shows when `buffer` is set and `type` is `"determinate"`.
@@ -20,6 +21,7 @@ import type { Color } from '../../interface';
styleUrls: {
ios: 'progress-bar.ios.scss',
md: 'progress-bar.md.scss',
ionic: 'progress-bar.md.scss',
},
shadow: true,
})
@@ -58,7 +60,7 @@ export class ProgressBar implements ComponentInterface {
render() {
const { color, type, reversed, value, buffer } = this;
const paused = config.getBoolean('_testing');
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
role="progressbar"
@@ -66,7 +68,7 @@ export class ProgressBar implements ComponentInterface {
aria-valuemin="0"
aria-valuemax="1"
class={createColorClasses(color, {
[mode]: true,
[theme]: true,
[`progress-bar-${type}`]: true,
'progress-paused': paused,
'progress-bar-reversed': document.dir === 'rtl' ? !reversed : reversed,

View File

@@ -2,7 +2,7 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Listen, Prop, Watch, h } from '@stencil/core';
import { renderHiddenInput } from '@utils/helpers';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { RadioGroupChangeEventDetail, RadioGroupCompareFn } from './radio-group-interface';
@@ -215,11 +215,20 @@ export class RadioGroup implements ComponentInterface {
render() {
const { label, labelId, el, name, value } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
renderHiddenInput(true, el, name, value, false);
return <Host role="radiogroup" aria-labelledby={label ? labelId : null} onClick={this.onClick} class={mode}></Host>;
return (
<Host
class={{
[theme]: true,
}}
role="radiogroup"
aria-labelledby={label ? labelId : null}
onClick={this.onClick}
></Host>
);
}
}

View File

@@ -6,11 +6,12 @@ import { addEventListener, getAriaLabel, removeEventListener } from '@utils/help
import { printIonWarning } from '@utils/logging';
import { createColorClasses, hostContext } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color, StyleEventDetail } from '../../interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @slot - The label text to associate with the radio. Use the "labelPlacement" property to control where the label is placed relative to the radio.
*
@@ -23,6 +24,7 @@ import type { Color, StyleEventDetail } from '../../interface';
styleUrls: {
ios: 'radio.ios.scss',
md: 'radio.md.scss',
ionic: 'radio.md.scss',
},
shadow: true,
})
@@ -267,7 +269,7 @@ export class Radio implements ComponentInterface {
private renderRadio() {
const { checked, disabled, color, el, justify, labelPlacement, hasLabel, buttonTabindex, alignment } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const inItem = hostContext('ion-item', el);
return (
@@ -276,7 +278,7 @@ export class Radio implements ComponentInterface {
onBlur={this.onBlur}
onClick={this.onClick}
class={createColorClasses(color, {
[mode]: true,
[theme]: true,
'in-item': inItem,
'radio-checked': checked,
'radio-disabled': disabled,
@@ -333,7 +335,7 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
}
const { inputId, disabled, checked, color, el, buttonTabindex } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const { label, labelId, labelText } = getAriaLabel(el, inputId);
return (
@@ -347,7 +349,7 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
onBlur={this.onBlur}
onClick={this.onClick}
class={createColorClasses(color, {
[mode]: true,
[theme]: true,
'in-item': hostContext('ion-item', el),
interactive: true,
'radio-checked': checked,

View File

@@ -9,7 +9,7 @@ import { printIonWarning } from '@utils/logging';
import { isRTL } from '@utils/rtl';
import { createColorClasses, hostContext } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color, Gesture, GestureDetail, StyleEventDetail } from '../../interface';
import { roundToMaxDecimalPlaces } from '../../utils/floating-point';
@@ -25,7 +25,8 @@ import type {
// TODO(FW-2832): types
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @slot label - The label text to associate with the range. Use the "labelPlacement" property to control where the label is placed relative to the range.
* @slot start - Content is placed to the left of the range slider in LTR, and to the right in RTL.
@@ -44,6 +45,7 @@ import type {
styleUrls: {
ios: 'range.ios.scss',
md: 'range.md.scss',
ionic: 'range.md.scss',
},
shadow: true,
})
@@ -592,7 +594,7 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
const { el, pressedKnob, disabled, pin, rangeId } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
renderHiddenInput(true, el, this.name, JSON.stringify(this.getValue()), disabled);
@@ -602,7 +604,7 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
onFocusout={this.onBlur}
id={rangeId}
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
'in-item': hostContext('ion-item', el),
'range-disabled': disabled,
'range-pressed': pressedKnob !== undefined,
@@ -653,7 +655,7 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
const needsEndAdjustment = inItem && !hasEndContent;
const mode = getIonMode(this);
const theme = getIonTheme(this);
renderHiddenInput(true, el, this.name, JSON.stringify(this.getValue()), disabled);
@@ -663,7 +665,7 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
onFocusout={this.onBlur}
id={rangeId}
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
'in-item': inItem,
'range-disabled': disabled,
'range-pressed': pressedKnob !== undefined,

View File

@@ -5,7 +5,7 @@ import { sanitizeDOMString } from '@utils/sanitization';
import { arrowDown, caretBackSharp } from 'ionicons/icons';
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { IonicSafeString } from '../../utils/sanitization';
import { supportsRubberBandScrolling } from '../refresher/refresher.utils';
import type { SpinnerTypes } from '../spinner/spinner-configs';
@@ -62,6 +62,8 @@ export class RefresherContent implements ComponentInterface {
@Prop() refreshingText?: string | IonicSafeString;
componentWillLoad() {
const theme = getIonTheme(this);
if (this.pullingIcon === undefined) {
/**
* The native iOS refresher uses a spinner instead of
@@ -69,18 +71,16 @@ export class RefresherContent implements ComponentInterface {
* the native iOS refresher.
*/
const hasRubberBandScrolling = supportsRubberBandScrolling();
const mode = getIonMode(this);
const overflowRefresher = hasRubberBandScrolling ? 'lines' : arrowDown;
this.pullingIcon = config.get(
'refreshingIcon',
mode === 'ios' && hasRubberBandScrolling ? config.get('spinner', overflowRefresher) : 'circular'
theme === 'ios' && hasRubberBandScrolling ? config.get('spinner', overflowRefresher) : 'circular'
);
}
if (this.refreshingSpinner === undefined) {
const mode = getIonMode(this);
this.refreshingSpinner = config.get(
'refreshingSpinner',
config.get('spinner', mode === 'ios' ? 'lines' : 'circular')
config.get('spinner', theme === 'ios' ? 'lines' : 'circular')
);
}
}
@@ -106,16 +106,20 @@ export class RefresherContent implements ComponentInterface {
render() {
const pullingIcon = this.pullingIcon;
const hasSpinner = pullingIcon != null && (SPINNERS[pullingIcon] as any) !== undefined;
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host class={mode}>
<Host
class={{
[theme]: true,
}}
>
<div class="refresher-pulling">
{this.pullingIcon && hasSpinner && (
<div class="refresher-pulling-icon">
<div class="spinner-arrow-container">
<ion-spinner name={this.pullingIcon as SpinnerTypes} paused></ion-spinner>
{mode === 'md' && this.pullingIcon === 'circular' && (
{theme === 'md' && this.pullingIcon === 'circular' && (
<div class="arrow-container">
<ion-icon icon={caretBackSharp} aria-hidden="true"></ion-icon>
</div>

View File

@@ -10,7 +10,7 @@ import {
import { clamp, componentOnReady, getElementRoot, raf, transitionEndAsync } from '@utils/helpers';
import { ImpactStyle, hapticImpact } from '@utils/native/haptic';
import { getIonMode } from '../../global/ionic-global';
import { getIonMode, getIonTheme } from '../../global/ionic-global';
import type { Animation, Gesture, GestureDetail } from '../../interface';
import type { RefresherEventDetail } from './refresher-interface';
@@ -26,13 +26,15 @@ import {
} from './refresher.utils';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-refresher',
styleUrls: {
ios: 'refresher.ios.scss',
md: 'refresher.md.scss',
ionic: 'refresher.md.scss',
},
})
export class Refresher implements ComponentInterface {
@@ -801,15 +803,15 @@ export class Refresher implements ComponentInterface {
}
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
slot="fixed"
class={{
[mode]: true,
[theme]: true,
// Used internally for styling
[`refresher-${mode}`]: true,
[`refresher-${theme}`]: true,
'refresher-native': this.nativeRefresher,
'refresher-active': this.state !== RefresherState.Inactive,
'refresher-pulling': this.state === RefresherState.Pulling,

View File

@@ -4,7 +4,7 @@ import { findClosestIonContent, getScrollElement } from '@utils/content';
import { raf } from '@utils/helpers';
import { hapticSelectionChanged, hapticSelectionEnd, hapticSelectionStart } from '@utils/native/haptic';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Gesture, GestureDetail } from '../../interface';
import type { ItemReorderEventDetail } from './reorder-group-interface';
@@ -301,11 +301,11 @@ export class ReorderGroup implements ComponentInterface {
}
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={{
[mode]: true,
[theme]: true,
'reorder-enabled': !this.disabled,
'reorder-list-active': this.state !== ReorderGroupState.Idle,
}}

View File

@@ -2,9 +2,11 @@ import type { ComponentInterface } from '@stencil/core';
import { Component, Element, Host, Listen, h } from '@stencil/core';
import { reorderThreeOutline, reorderTwoSharp } from 'ionicons/icons';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
/**
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @part icon - The icon of the reorder handle (uses ion-icon).
*/
@Component({
@@ -12,6 +14,7 @@ import { getIonMode } from '../../global/ionic-global';
styleUrls: {
ios: 'reorder.ios.scss',
md: 'reorder.md.scss',
ionic: 'reorder.md.scss',
},
shadow: true,
})
@@ -32,10 +35,14 @@ export class Reorder implements ComponentInterface {
}
render() {
const mode = getIonMode(this);
const reorderIcon = mode === 'ios' ? reorderThreeOutline : reorderTwoSharp;
const theme = getIonTheme(this);
const reorderIcon = theme === 'ios' ? reorderThreeOutline : reorderTwoSharp;
return (
<Host class={mode}>
<Host
class={{
[theme]: true,
}}
>
<slot>
<ion-icon icon={reorderIcon} lazy={false} class="reorder-icon" part="icon" aria-hidden="true" />
</slot>

View File

@@ -1,7 +1,7 @@
import type { ComponentInterface } from '@stencil/core';
import { Component, Element, Host, Method, Prop, h, readTask, writeTask } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
@Component({
tag: 'ion-ripple-effect',
@@ -78,12 +78,12 @@ export class RippleEffect implements ComponentInterface {
}
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
role="presentation"
class={{
[mode]: true,
[theme]: true,
unbounded: this.unbounded,
}}
></Host>

View File

@@ -2,7 +2,7 @@ import type { ComponentInterface } from '@stencil/core';
import { Component, Host, Prop, h } from '@stencil/core';
import { createColorClasses, openURL } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { AnimationBuilder, Color } from '../../interface';
import type { RouterDirection } from '../router/utils/interface';
@@ -55,7 +55,7 @@ export class RouterLink implements ComponentInterface {
};
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
const attrs = {
href: this.href,
rel: this.rel,
@@ -65,7 +65,7 @@ export class RouterLink implements ComponentInterface {
<Host
onClick={this.onClick}
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
'ion-activatable': true,
})}
>

View File

@@ -1,7 +1,7 @@
import type { ComponentInterface } from '@stencil/core';
import { Component, Host, h } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
@Component({
tag: 'ion-row',
@@ -10,8 +10,14 @@ import { getIonMode } from '../../global/ionic-global';
})
export class Row implements ComponentInterface {
render() {
const theme = getIonTheme(this);
return (
<Host class={getIonMode(this)}>
<Host
class={{
[theme]: true,
}}
>
<slot></slot>
</Host>
);

View File

@@ -6,19 +6,21 @@ import { createColorClasses } from '@utils/theme';
import { arrowBackSharp, closeCircle, closeSharp, searchOutline, searchSharp } from 'ionicons/icons';
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { AutocompleteTypes, Color, StyleEventDetail } from '../../interface';
import type { SearchbarChangeEventDetail, SearchbarInputEventDetail } from './searchbar-interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-searchbar',
styleUrls: {
ios: 'searchbar.ios.scss',
md: 'searchbar.md.scss',
ionic: 'searchbar.md.scss',
},
scoped: true,
})
@@ -423,11 +425,11 @@ export class Searchbar implements ComponentInterface {
private positionElements() {
const value = this.getValue();
const prevAlignLeft = this.shouldAlignLeft;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const shouldAlignLeft = !this.animated || value.trim() !== '' || !!this.focused;
this.shouldAlignLeft = shouldAlignLeft;
if (mode !== 'ios') {
if (theme !== 'ios') {
return;
}
@@ -559,9 +561,9 @@ export class Searchbar implements ComponentInterface {
render() {
const { cancelButtonText } = this;
const animated = this.animated && config.getBoolean('animated', true);
const mode = getIonMode(this);
const clearIcon = this.clearIcon || (mode === 'ios' ? closeCircle : closeSharp);
const searchIcon = this.searchIcon || (mode === 'ios' ? searchOutline : searchSharp);
const theme = getIonTheme(this);
const clearIcon = this.clearIcon || (theme === 'ios' ? closeCircle : closeSharp);
const searchIcon = this.searchIcon || (theme === 'ios' ? searchOutline : searchSharp);
const shouldShowCancelButton = this.shouldShowCancelButton();
const cancelButton = this.showCancelButton !== 'never' && (
@@ -570,14 +572,15 @@ export class Searchbar implements ComponentInterface {
// Screen readers should not announce button if it is not visible on screen
aria-hidden={shouldShowCancelButton ? undefined : 'true'}
type="button"
tabIndex={mode === 'ios' && !shouldShowCancelButton ? -1 : undefined}
tabIndex={theme === 'ios' && !shouldShowCancelButton ? -1 : undefined}
onMouseDown={this.onCancelSearchbar}
onTouchStart={this.onCancelSearchbar}
class="searchbar-cancel-button"
>
<div aria-hidden="true">
{mode === 'md' ? (
<ion-icon aria-hidden="true" mode={mode} icon={this.cancelButtonIcon} lazy={false}></ion-icon>
{theme === 'md' ? (
// TODO look into the mode/theme
<ion-icon aria-hidden="true" mode={theme} icon={this.cancelButtonIcon} lazy={false}></ion-icon>
) : (
cancelButtonText
)}
@@ -590,7 +593,7 @@ export class Searchbar implements ComponentInterface {
role="search"
aria-disabled={this.disabled ? 'true' : null}
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
'searchbar-animated': animated,
'searchbar-disabled': this.disabled,
'searchbar-no-animate': animated && this.noAnimate,
@@ -622,11 +625,11 @@ export class Searchbar implements ComponentInterface {
spellcheck={this.spellcheck}
/>
{mode === 'md' && cancelButton}
{theme === 'md' && cancelButton}
<ion-icon
aria-hidden="true"
mode={mode}
mode={theme}
icon={searchIcon}
lazy={false}
class="searchbar-search-icon"
@@ -649,14 +652,14 @@ export class Searchbar implements ComponentInterface {
>
<ion-icon
aria-hidden="true"
mode={mode}
mode={theme}
icon={clearIcon}
lazy={false}
class="searchbar-clear-icon"
></ion-icon>
</button>
</div>
{mode === 'ios' && cancelButton}
{theme === 'ios' && cancelButton}
</Host>
);
}

View File

@@ -5,7 +5,7 @@ import type { Attributes } from '@utils/helpers';
import { addEventListener, removeEventListener, inheritAttributes } from '@utils/helpers';
import { hostContext } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { SegmentValue } from '../segment/segment-interface';
import type { SegmentButtonLayout } from './segment-button-interface';
@@ -13,7 +13,8 @@ import type { SegmentButtonLayout } from './segment-button-interface';
let ids = 0;
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @part native - The native HTML button element that wraps all child elements.
* @part indicator - The indicator displayed on the checked segment button.
@@ -24,6 +25,7 @@ let ids = 0;
styleUrls: {
ios: 'segment-button.ios.scss',
md: 'segment-button.md.scss',
ionic: 'segment-button.md.scss',
},
shadow: true,
})
@@ -124,12 +126,12 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
render() {
const { checked, type, disabled, hasIcon, hasLabel, layout, segmentEl } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const hasSegmentColor = () => segmentEl?.color !== undefined;
return (
<Host
class={{
[mode]: true,
[theme]: true,
'in-toolbar': hostContext('ion-toolbar', this.el),
'in-toolbar-color': hostContext('ion-toolbar[color]', this.el),
'in-segment': hostContext('ion-segment', this.el),
@@ -159,7 +161,7 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
<span class="button-inner">
<slot></slot>
</span>
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
{theme === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</button>
<div
part="indicator"

View File

@@ -5,19 +5,21 @@ import { raf } from '@utils/helpers';
import { isRTL } from '@utils/rtl';
import { createColorClasses, hostContext } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color, StyleEventDetail } from '../../interface';
import type { SegmentChangeEventDetail, SegmentValue } from './segment-interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-segment',
styleUrls: {
ios: 'segment.ios.scss',
md: 'segment.md.scss',
ionic: 'segment.md.scss',
},
shadow: true,
})
@@ -558,13 +560,13 @@ export class Segment implements ComponentInterface {
}
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
role="tablist"
onClick={this.onClick}
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
'in-toolbar': hostContext('ion-toolbar', this.el),
'in-toolbar-color': hostContext('ion-toolbar[color]', this.el),
'segment-activated': this.activated,

View File

@@ -1,7 +1,7 @@
import type { ComponentInterface } from '@stencil/core';
import { Component, Element, Host, Prop, h } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
@Component({
tag: 'ion-select-option',
@@ -24,7 +24,16 @@ export class SelectOption implements ComponentInterface {
@Prop() value?: any | null;
render() {
return <Host role="option" id={this.inputId} class={getIonMode(this)}></Host>;
const theme = getIonTheme(this);
return (
<Host
class={{
[theme]: true,
}}
role="option"
id={this.inputId}
></Host>
);
}
}

View File

@@ -3,13 +3,15 @@ import { Element, Component, Host, Prop, h, forceUpdate } from '@stencil/core';
import { safeCall } from '@utils/overlays';
import { getClassMap } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { CheckboxCustomEvent } from '../checkbox/checkbox-interface';
import type { RadioGroupCustomEvent } from '../radio-group/radio-group-interface';
import type { SelectPopoverOption } from './select-popover-interface';
/**
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @internal
*/
@Component({
@@ -17,6 +19,7 @@ import type { SelectPopoverOption } from './select-popover-interface';
styleUrls: {
ios: 'select-popover.ios.scss',
md: 'select-popover.md.scss',
ionic: 'select-popover.md.scss',
},
scoped: true,
})
@@ -181,9 +184,14 @@ export class SelectPopover implements ComponentInterface {
render() {
const { header, message, options, subHeader } = this;
const hasSubHeaderOrMessage = subHeader !== undefined || message !== undefined;
const theme = getIonTheme(this);
return (
<Host class={getIonMode(this)}>
<Host
class={{
[theme]: true,
}}
>
<ion-list>
{header !== undefined && <ion-list-header>{header}</ion-list-header>}
{hasSubHeaderOrMessage && (

View File

@@ -12,7 +12,7 @@ import { createColorClasses, hostContext } from '@utils/theme';
import { watchForOptions } from '@utils/watch-options';
import { caretDownSharp, chevronExpand } from 'ionicons/icons';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type {
ActionSheetOptions,
AlertOptions,
@@ -30,7 +30,8 @@ import type { SelectChangeEventDetail, SelectInterface, SelectCompareFn } from '
// TODO(FW-2832): types
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @slot label - The label text to associate with the select. Use the `labelPlacement` property to control where the label is placed relative to the select. Use this if you need to render a label with custom HTML.
* @slot start - Content to display at the leading edge of the select.
@@ -47,6 +48,7 @@ import type { SelectChangeEventDetail, SelectInterface, SelectCompareFn } from '
styleUrls: {
ios: 'select.ios.scss',
md: 'select.md.scss',
ionic: 'select.md.scss',
},
shadow: true,
})
@@ -507,8 +509,8 @@ export class Select implements ComponentInterface {
private async openPopover(ev: UIEvent) {
const { fill, labelPlacement } = this;
const interfaceOptions = this.interfaceOptions;
const mode = getIonMode(this);
const showBackdrop = mode === 'md' ? false : true;
const theme = getIonTheme(this);
const showBackdrop = theme === 'md' ? false : true;
const multiple = this.multiple;
const value = this.value;
@@ -537,7 +539,7 @@ export class Select implements ComponentInterface {
* when using a fill in MD mode or if the
* label is floating/stacked.
*/
if (hasFloatingOrStackedLabel || (mode === 'md' && fill !== undefined)) {
if (hasFloatingOrStackedLabel || (theme === 'md' && fill !== undefined)) {
size = 'cover';
/**
@@ -556,7 +558,7 @@ export class Select implements ComponentInterface {
}
const popoverOpts: PopoverOptions = {
mode,
theme,
event,
alignment: 'center',
size,
@@ -592,10 +594,10 @@ export class Select implements ComponentInterface {
}
private async openActionSheet() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
const interfaceOptions = this.interfaceOptions;
const actionSheetOpts: ActionSheetOptions = {
mode,
theme,
...interfaceOptions,
buttons: this.createActionSheetButtons(this.childOpts, this.value),
@@ -637,10 +639,10 @@ export class Select implements ComponentInterface {
const interfaceOptions = this.interfaceOptions;
const inputType = this.multiple ? 'checkbox' : 'radio';
const mode = getIonMode(this);
const theme = getIonTheme(this);
const alertOpts: AlertOptions = {
mode,
theme,
...interfaceOptions,
header: interfaceOptions.header ? interfaceOptions.header : labelText,
@@ -852,8 +854,8 @@ export class Select implements ComponentInterface {
* when fill="outline".
*/
private renderLabelContainer() {
const mode = getIonMode(this);
const hasOutlineFill = mode === 'md' && this.fill === 'outline';
const theme = getIonTheme(this);
const hasOutlineFill = theme === 'md' && this.fill === 'outline';
if (hasOutlineFill) {
/**
@@ -892,12 +894,12 @@ export class Select implements ComponentInterface {
private renderSelect() {
const { disabled, el, isExpanded, expandedIcon, labelPlacement, justify, placeholder, fill, shape, name, value } =
this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const hasFloatingOrStackedLabel = labelPlacement === 'floating' || labelPlacement === 'stacked';
const justifyEnabled = !hasFloatingOrStackedLabel;
const rtl = isRTL(el) ? 'rtl' : 'ltr';
const inItem = hostContext('ion-item', this.el);
const shouldRenderHighlight = mode === 'md' && fill !== 'outline' && !inItem;
const shouldRenderHighlight = theme === 'md' && fill !== 'outline' && !inItem;
const hasValue = this.hasValue();
const hasStartEndSlots = el.querySelector('[slot="start"], [slot="end"]') !== null;
@@ -928,7 +930,7 @@ export class Select implements ComponentInterface {
<Host
onClick={this.onClick}
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
'in-item': inItem,
'in-item-color': hostContext('ion-item.ion-color', el),
'select-disabled': disabled,
@@ -996,7 +998,7 @@ Developers can use the "legacy" property to continue using the legacy form marku
}
const { disabled, el, inputId, isExpanded, expandedIcon, name, placeholder, value } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const { labelText, labelId } = getAriaLabel(el, inputId);
renderHiddenInput(true, el, name, parseValue(value), disabled);
@@ -1023,7 +1025,7 @@ Developers can use the "legacy" property to continue using the legacy form marku
aria-disabled={disabled ? 'true' : null}
aria-label={displayLabel}
class={{
[mode]: true,
[theme]: true,
'in-item': hostContext('ion-item', el),
'in-item-color': hostContext('ion-item.ion-color', el),
'select-disabled': disabled,
@@ -1076,14 +1078,14 @@ Developers can use the "legacy" property to continue using the legacy form marku
* next to the select text.
*/
private renderSelectIcon() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
const { isExpanded, toggleIcon, expandedIcon } = this;
let icon: string;
if (isExpanded && expandedIcon !== undefined) {
icon = expandedIcon;
} else {
const defaultIcon = mode === 'ios' ? chevronExpand : caretDownSharp;
const defaultIcon = theme === 'ios' ? chevronExpand : caretDownSharp;
icon = toggleIcon ?? defaultIcon;
}

View File

@@ -3,7 +3,7 @@ import { Component, Element, Event, Host, Prop, h } from '@stencil/core';
import { hostContext } from '@utils/theme';
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { StyleEventDetail } from '../../interface';
@Component({
@@ -43,12 +43,12 @@ export class SkeletonText implements ComponentInterface {
render() {
const animated = this.animated && config.getBoolean('animated', true);
const inMedia = hostContext('ion-avatar', this.el) || hostContext('ion-thumbnail', this.el);
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={{
[mode]: true,
[theme]: true,
'skeleton-text-animated': animated,
'in-media': inMedia,
}}

View File

@@ -3,7 +3,7 @@ import { Component, Host, Prop, h } from '@stencil/core';
import { createColorClasses } from '@utils/theme';
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color } from '../../interface';
import type { SpinnerTypes } from './spinner-configs';
@@ -41,16 +41,16 @@ export class Spinner implements ComponentInterface {
private getName(): SpinnerTypes {
const spinnerName = this.name || config.get('spinner');
const mode = getIonMode(this);
const theme = getIonTheme(this);
if (spinnerName) {
return spinnerName;
}
return mode === 'ios' ? 'lines' : 'circular';
return theme === 'ios' ? 'lines' : 'circular';
}
render() {
const self = this;
const mode = getIonMode(self);
const theme = getIonTheme(self);
const spinnerName = self.getName();
const spinner = SPINNERS[spinnerName] ?? SPINNERS['lines'];
const duration = typeof self.duration === 'number' && self.duration > 10 ? self.duration : spinner.dur;
@@ -69,7 +69,7 @@ export class Spinner implements ComponentInterface {
return (
<Host
class={createColorClasses(self.color, {
[mode]: true,
[theme]: true,
[`spinner-${spinnerName}`]: true,
'spinner-paused': self.paused || config.getBoolean('_testing'),
})}

View File

@@ -1,7 +1,7 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Build, Component, Element, Event, Host, Prop, State, Watch, h } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
// TODO(FW-2832): types
@@ -16,11 +16,15 @@ const QUERY: { [key: string]: string } = {
never: '',
};
/**
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-split-pane',
styleUrls: {
ios: 'split-pane.ios.scss',
md: 'split-pane.md.scss',
ionic: 'split-pane.md.scss',
},
shadow: true,
})
@@ -157,14 +161,14 @@ export class SplitPane implements ComponentInterface {
}
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={{
[mode]: true,
[theme]: true,
// Used internally for styling
[`split-pane-${mode}`]: true,
[`split-pane-${theme}`]: true,
'split-pane-visible': this.visible,
}}

View File

@@ -4,19 +4,21 @@ import type { KeyboardController } from '@utils/keyboard/keyboard-controller';
import { createKeyboardController } from '@utils/keyboard/keyboard-controller';
import { createColorClasses } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color } from '../../interface';
import type { TabBarChangedEventDetail } from './tab-bar-interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-tab-bar',
styleUrls: {
ios: 'tab-bar.ios.scss',
md: 'tab-bar.md.scss',
ionic: 'tab-bar.md.scss',
},
shadow: true,
})
@@ -96,7 +98,7 @@ export class TabBar implements ComponentInterface {
render() {
const { color, translucent, keyboardVisible } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const shouldHide = keyboardVisible && this.el.getAttribute('slot') !== 'top';
return (
@@ -104,7 +106,7 @@ export class TabBar implements ComponentInterface {
role="tablist"
aria-hidden={shouldHide ? 'true' : null}
class={createColorClasses(color, {
[mode]: true,
[theme]: true,
'tab-bar-translucent': translucent,
'tab-bar-hidden': shouldHide,
})}

View File

@@ -5,7 +5,7 @@ import type { Attributes } from '@utils/helpers';
import { inheritAttributes } from '@utils/helpers';
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type {
TabBarChangedEventDetail,
TabButtonClickEventDetail,
@@ -13,7 +13,8 @@ import type {
} from '../tab-bar/tab-bar-interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @part native - The native HTML anchor element that wraps all child elements.
*/
@@ -22,6 +23,7 @@ import type {
styleUrls: {
ios: 'tab-button.ios.scss',
md: 'tab-button.md.scss',
ionic: 'tab-button.md.scss',
},
shadow: true,
})
@@ -138,7 +140,7 @@ export class TabButton implements ComponentInterface, AnchorInterface {
render() {
const { disabled, hasIcon, hasLabel, href, rel, target, layout, selected, tab, inheritedAttributes } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const attrs = {
download: this.download,
href,
@@ -152,7 +154,7 @@ export class TabButton implements ComponentInterface, AnchorInterface {
onKeyup={this.onKeyUp}
id={tab !== undefined ? `tab-button-${tab}` : null}
class={{
[mode]: true,
[theme]: true,
'tab-selected': selected,
'tab-disabled': disabled,
'tab-has-label': hasLabel,
@@ -178,7 +180,7 @@ export class TabButton implements ComponentInterface, AnchorInterface {
<span class="button-inner">
<slot></slot>
</span>
{mode === 'md' && <ion-ripple-effect type="unbounded"></ion-ripple-effect>}
{theme === 'md' && <ion-ripple-effect type="unbounded"></ion-ripple-effect>}
</a>
</Host>
);

View File

@@ -2,11 +2,11 @@ import type { ComponentInterface } from '@stencil/core';
import { Component, Host, Prop, h } from '@stencil/core';
import { createColorClasses } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color } from '../../interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
*/
@Component({
tag: 'ion-text',
@@ -22,11 +22,11 @@ export class Text implements ComponentInterface {
@Prop({ reflect: true }) color?: Color;
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
return (
<Host
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
})}
>
<slot></slot>

View File

@@ -28,14 +28,15 @@ import { createSlotMutationController } from '@utils/slot-mutation-controller';
import type { SlotMutationController } from '@utils/slot-mutation-controller';
import { createColorClasses, hostContext } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color, StyleEventDetail } from '../../interface';
import { getCounterText } from '../input/input.utils';
import type { TextareaChangeEventDetail, TextareaInputEventDetail } from './textarea-interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @slot label - The label text to associate with the textarea. Use the `labelPlacement` property to control where the label is placed relative to the textarea. Use this if you need to render a label with custom HTML. (EXPERIMENTAL)
* @slot start - Content to display at the leading edge of the textarea. (EXPERIMENTAL)
@@ -46,6 +47,7 @@ import type { TextareaChangeEventDetail, TextareaInputEventDetail } from './text
styleUrls: {
ios: 'textarea.ios.scss',
md: 'textarea.md.scss',
ionic: 'textarea.md.scss',
},
scoped: true,
})
@@ -572,7 +574,7 @@ Developers can use the "legacy" property to continue using the legacy form marku
this.hasLoggedDeprecationWarning = true;
}
const mode = getIonMode(this);
const theme = getIonTheme(this);
const value = this.getValue();
const labelId = this.inputId + '-lbl';
const label = findItemLabel(this.el);
@@ -584,7 +586,7 @@ Developers can use the "legacy" property to continue using the legacy form marku
<Host
aria-disabled={this.disabled ? 'true' : null}
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
'legacy-textarea': true,
})}
>
@@ -659,8 +661,8 @@ Developers can use the "legacy" property to continue using the legacy form marku
* Renders the border container when fill="outline".
*/
private renderLabelContainer() {
const mode = getIonMode(this);
const hasOutlineFill = mode === 'md' && this.fill === 'outline';
const theme = getIonTheme(this);
const hasOutlineFill = theme === 'md' && this.fill === 'outline';
if (hasOutlineFill) {
/**
@@ -741,10 +743,10 @@ Developers can use the "legacy" property to continue using the legacy form marku
private renderTextarea() {
const { inputId, disabled, fill, shape, labelPlacement, el, hasFocus } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const value = this.getValue();
const inItem = hostContext('ion-item', this.el);
const shouldRenderHighlight = mode === 'md' && fill !== 'outline' && !inItem;
const shouldRenderHighlight = theme === 'md' && fill !== 'outline' && !inItem;
const hasValue = this.hasValue();
const hasStartEndSlots = el.querySelector('[slot="start"], [slot="end"]') !== null;
@@ -772,7 +774,7 @@ Developers can use the "legacy" property to continue using the legacy form marku
return (
<Host
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
'has-value': hasValue,
'has-focus': hasFocus,
'label-floating': labelShouldFloat,

View File

@@ -1,7 +1,7 @@
import type { ComponentInterface } from '@stencil/core';
import { Component, Host, h } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
@Component({
tag: 'ion-thumbnail',
@@ -10,8 +10,13 @@ import { getIonMode } from '../../global/ionic-global';
})
export class Thumbnail implements ComponentInterface {
render() {
const theme = getIonTheme(this);
return (
<Host class={getIonMode(this)}>
<Host
class={{
[theme]: true,
}}
>
<slot></slot>
</Host>
);

View File

@@ -2,14 +2,18 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Prop, Watch, h } from '@stencil/core';
import { createColorClasses } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color, StyleEventDetail } from '../../interface';
/**
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*/
@Component({
tag: 'ion-title',
styleUrls: {
ios: 'title.ios.scss',
md: 'title.md.scss',
ionic: 'title.md.scss',
},
shadow: true,
})
@@ -56,13 +60,13 @@ export class ToolbarTitle implements ComponentInterface {
}
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
const size = this.getSize();
return (
<Host
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
[`title-${size}`]: true,
'title-rtl': document.dir === 'rtl',
})}

View File

@@ -1,7 +1,7 @@
import { win } from '@utils/browser';
import { printIonWarning } from '@utils/logging';
import type { Mode } from 'src/interface';
import type { Mode } from '../../../interface';
import type { ToastAnimationPosition, ToastPosition } from '../toast-interface';
/**
@@ -18,7 +18,7 @@ import type { ToastAnimationPosition, ToastPosition } from '../toast-interface';
* @param position The value of the toast's position prop.
* @param positionAnchor The element the toast should be anchored to,
* if applicable.
* @param mode The toast component's mode (md, ios, etc).
* @param mode The toast component's platform (md or ios).
* @param toast A reference to the toast element itself.
*/
export function getAnimationPosition(

View File

@@ -21,7 +21,7 @@ import { sanitizeDOMString } from '@utils/sanitization';
import { createColorClasses, getClassMap } from '@utils/theme';
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { getIonMode, getIonTheme } from '../../global/ionic-global';
import type { AnimationBuilder, Color, CssClassMap, OverlayInterface, FrameworkDelegate } from '../../interface';
import type { OverlayEventDetail } from '../../utils/overlays-interface';
import type { IonicSafeString } from '../../utils/sanitization';
@@ -45,7 +45,8 @@ import type {
// TODO(FW-2832): types
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @part button - Any button element that is displayed inside of the toast.
* @part button cancel - Any button element with role "cancel" that is displayed inside of the toast.
@@ -59,6 +60,7 @@ import type {
styleUrls: {
ios: 'toast.ios.scss',
md: 'toast.md.scss',
ionic: 'toast.md.scss',
},
shadow: true,
})
@@ -350,13 +352,14 @@ export class Toast implements ComponentInterface, OverlayInterface {
*/
@Method()
async present(): Promise<void> {
const mode = getIonMode(this);
const unlock = await this.lockController.lock();
await this.delegateController.attachViewToDom();
const { el, position } = this;
const anchor = this.getAnchorElement();
const animationPosition = getAnimationPosition(position, anchor, getIonMode(this), el);
const animationPosition = getAnimationPosition(position, anchor, mode, el);
/**
* Cache the calculated position of the toast, so we can re-use it
@@ -608,7 +611,7 @@ export class Toast implements ComponentInterface, OverlayInterface {
return;
}
const mode = getIonMode(this);
const theme = getIonTheme(this);
const buttonGroupsClasses = {
'toast-button-group': true,
[`toast-button-group-${side}`]: true,
@@ -635,7 +638,7 @@ export class Toast implements ComponentInterface, OverlayInterface {
)}
{b.text}
</div>
{mode === 'md' && (
{theme === 'md' && (
<ion-ripple-effect
type={b.icon !== undefined && b.text === undefined ? 'unbounded' : 'bounded'}
></ion-ripple-effect>
@@ -690,7 +693,7 @@ export class Toast implements ComponentInterface, OverlayInterface {
const allButtons = this.getButtons();
const startButtons = allButtons.filter((b) => b.side === 'start');
const endButtons = allButtons.filter((b) => b.side !== 'start');
const mode = getIonMode(this);
const theme = getIonTheme(this);
const wrapperClass = {
'toast-wrapper': true,
[`toast-${this.position}`]: true,
@@ -716,7 +719,7 @@ export class Toast implements ComponentInterface, OverlayInterface {
zIndex: `${60000 + this.overlayIndex}`,
}}
class={createColorClasses(this.color, {
[mode]: true,
[theme]: true,
...getClassMap(this.cssClass),
'overlay-hidden': true,
'toast-translucent': this.translucent,

View File

@@ -11,13 +11,14 @@ import { createColorClasses, hostContext } from '@utils/theme';
import { checkmarkOutline, removeOutline, ellipseOutline } from 'ionicons/icons';
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import type { Color, Gesture, GestureDetail, Mode, StyleEventDetail } from '../../interface';
import { getIonTheme } from '../../global/ionic-global';
import type { Color, Gesture, GestureDetail, StyleEventDetail, Theme } from '../../interface';
import type { ToggleChangeEventDetail } from './toggle-interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @slot - The label text to associate with the toggle. Use the "labelPlacement" property to control where the label is placed relative to the toggle.
*
@@ -30,6 +31,7 @@ import type { ToggleChangeEventDetail } from './toggle-interface';
styleUrls: {
ios: 'toggle.ios.scss',
md: 'toggle.md.scss',
ionic: 'toggle.md.scss',
},
shadow: true,
})
@@ -281,15 +283,15 @@ export class Toggle implements ComponentInterface {
this.ionBlur.emit();
};
private getSwitchLabelIcon = (mode: Mode, checked: boolean) => {
if (mode === 'md') {
private getSwitchLabelIcon = (theme: Theme, checked: boolean) => {
if (theme === 'md') {
return checked ? checkmarkOutline : removeOutline;
}
return checked ? removeOutline : ellipseOutline;
};
private renderOnOffSwitchLabels(mode: Mode, checked: boolean) {
const icon = this.getSwitchLabelIcon(mode, checked);
private renderOnOffSwitchLabels(theme: Theme, checked: boolean) {
const icon = this.getSwitchLabelIcon(theme, checked);
return (
<ion-icon
@@ -304,7 +306,7 @@ export class Toggle implements ComponentInterface {
}
private renderToggleControl() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
const { enableOnOffLabels, checked } = this;
return (
@@ -313,10 +315,10 @@ export class Toggle implements ComponentInterface {
since the wrapper is translated when the handle is interacted with and
this would move the on/off labels outside of the view box */}
{enableOnOffLabels &&
mode === 'ios' && [this.renderOnOffSwitchLabels(mode, true), this.renderOnOffSwitchLabels(mode, false)]}
theme === 'ios' && [this.renderOnOffSwitchLabels(theme, true), this.renderOnOffSwitchLabels(theme, false)]}
<div class="toggle-icon-wrapper">
<div class="toggle-inner" part="handle">
{enableOnOffLabels && mode === 'md' && this.renderOnOffSwitchLabels(mode, checked)}
{enableOnOffLabels && theme === 'md' && this.renderOnOffSwitchLabels(theme, checked)}
</div>
</div>
</div>
@@ -336,7 +338,7 @@ export class Toggle implements ComponentInterface {
private renderToggle() {
const { activated, color, checked, disabled, el, justify, labelPlacement, inputId, name, alignment } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const value = this.getValue();
const rtl = isRTL(el) ? 'rtl' : 'ltr';
renderHiddenInput(true, el, name, checked ? value : '', disabled);
@@ -345,7 +347,7 @@ export class Toggle implements ComponentInterface {
<Host
onClick={this.onClick}
class={createColorClasses(color, {
[mode]: true,
[theme]: true,
'in-item': hostContext('ion-item', el),
'toggle-activated': activated,
'toggle-checked': checked,
@@ -413,7 +415,7 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
}
const { activated, color, checked, disabled, el, inputId, name } = this;
const mode = getIonMode(this);
const theme = getIonTheme(this);
const { label, labelId, labelText } = getAriaLabel(el, inputId);
const value = this.getValue();
const rtl = isRTL(el) ? 'rtl' : 'ltr';
@@ -428,7 +430,7 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
aria-hidden={disabled ? 'true' : null}
role="switch"
class={createColorClasses(color, {
[mode]: true,
[theme]: true,
'in-item': hostContext('ion-item', el),
'toggle-activated': activated,
'toggle-checked': checked,

View File

@@ -2,11 +2,12 @@ import type { ComponentInterface } from '@stencil/core';
import { Component, Element, Host, Listen, Prop, forceUpdate, h } from '@stencil/core';
import { createColorClasses, hostContext } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import { getIonTheme } from '../../global/ionic-global';
import type { Color, CssClassMap, StyleEventDetail } from '../../interface';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of components.
* @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the appearance of components.
*
* @slot - Content is placed between the named slots if provided without a slot.
* @slot start - Content is placed to the left of the toolbar text in LTR, and to the right in RTL.
@@ -19,6 +20,7 @@ import type { Color, CssClassMap, StyleEventDetail } from '../../interface';
styleUrls: {
ios: 'toolbar.ios.scss',
md: 'toolbar.md.scss',
ionic: 'toolbar.md.scss',
},
shadow: true,
})
@@ -82,7 +84,7 @@ export class Toolbar implements ComponentInterface {
}
render() {
const mode = getIonMode(this);
const theme = getIonTheme(this);
const childStyles = {};
this.childrenStyles.forEach((value) => {
Object.assign(childStyles, value);
@@ -92,7 +94,7 @@ export class Toolbar implements ComponentInterface {
class={{
...childStyles,
...createColorClasses(this.color, {
[mode]: true,
[theme]: true,
'in-toolbar': hostContext('ion-toolbar', this.el),
}),
}}

View File

@@ -1,18 +1,58 @@
import { getMode, setMode, setPlatformHelpers } from '@stencil/core';
import { getMode, setMode, setPlatformHelpers, getElement } from '@stencil/core';
import { printIonWarning } from '@utils/logging';
import type { IonicConfig, Mode } from '../interface';
import type { IonicConfig, Mode, Theme } from '../interface';
import { isPlatform, setupPlatforms } from '../utils/platform';
import { config, configFromSession, configFromURL, saveConfig } from './config';
// TODO(FW-2832): types
let defaultMode: Mode;
let defaultTheme: Theme;
export const getIonMode = (ref?: any): Mode => {
return (ref && getMode(ref)) || defaultMode;
if (ref?.mode) {
return ref.mode;
}
const theme = getMode(ref);
if (theme === 'ios' || theme === 'md') {
return theme;
}
const el = getElement(ref);
return el.closest('[mode]')?.getAttribute('mode') as Mode || defaultMode;
};
export const getIonTheme = (ref?: any): Theme => {
return (ref && getMode(ref)) || getIonMode(ref) || defaultTheme;
}
// export const getIonPlatform = (ref?: any): Platform => {
// if (ref?.platform) {
// return ref.platform;
// }
// // TODO remove mode in the future
// if (ref?.mode) {
// return ref.mode;
// }
// const el = getElement(ref);
// return el.closest('[platform]')?.getAttribute('platform') as Platform || defaultPlatform;
// }
/**
* @deprecated
*/
const isModeSupported = (elmMode: string) => ['ios', 'md'].includes(elmMode);
const isThemeSupported = (theme: string) => ['ios', 'md'].includes(theme);
const isIonicElement = (elm: HTMLElement) => elm.tagName?.startsWith('ION-');
export const initialize = (userConfig: IonicConfig = {}) => {
if (typeof (window as any) === 'undefined') {
return;
@@ -56,35 +96,57 @@ export const initialize = (userConfig: IonicConfig = {}) => {
// which could have been set by the user, or by pre-rendering
// otherwise get the mode via config settings, and fallback to md
Ionic.config = config;
Ionic.mode = defaultMode = config.get(
'mode',
doc.documentElement.getAttribute('mode') || (isPlatform(win, 'ios') ? 'ios' : 'md')
);
config.set('mode', defaultMode);
doc.documentElement.setAttribute('mode', defaultMode);
doc.documentElement.classList.add(defaultMode);
const theme = config.get('theme', doc.documentElement.getAttribute('theme'));
if (theme) {
Ionic.theme = defaultTheme = theme;
config.set('theme', defaultTheme);
doc.documentElement.setAttribute('theme', defaultTheme);
doc.documentElement.classList.add(defaultTheme);
} else {
Ionic.mode = defaultMode = config.get(
'mode',
doc.documentElement.getAttribute('mode') || (isPlatform(win, 'ios') ? 'ios' : 'md')
);
config.set('mode', defaultMode);
doc.documentElement.setAttribute('mode', defaultMode);
doc.documentElement.classList.add(defaultMode);
}
if (config.getBoolean('_testing')) {
config.set('animated', false);
}
const isIonicElement = (elm: any) => elm.tagName?.startsWith('ION-');
const isAllowedIonicModeValue = (elmMode: string) => ['ios', 'md'].includes(elmMode);
setMode((elm: any) => {
while (elm) {
const elmMode = (elm as any).mode || elm.getAttribute('mode');
if (elmMode) {
if (isAllowedIonicModeValue(elmMode)) {
return elmMode;
const theme = (elm as any).theme || elm.getAttribute('theme');
if (theme) {
if (isThemeSupported(theme)) {
return theme;
} else if (isIonicElement(elm)) {
console.warn('Invalid ionic mode: "' + elmMode + '", expected: "ios" or "md"');
printIonWarning(`Invalid theme: "${theme}". Supported themes include: "ios" or "md".`);
}
}
/**
* If a theme is not detected, then fallback to using the
* `mode` attribute to determine the style sheets to use.
*/
const elmMode = (elm as any).mode || elm.getAttribute('mode');
if (elmMode) {
if (isModeSupported(elmMode)) {
return elmMode;
} else if (isIonicElement(elm)) {
printIonWarning(`Invalid mode: "${elmMode}". Ionic modes can be only "ios" or "md"`);
}
}
elm = elm.parentElement;
}
return defaultMode;
return defaultTheme;
});
};

View File

@@ -134,6 +134,9 @@ export type PredefinedColors =
export type LiteralUnion<T extends U, U = string> = T | (U & Record<never, never>);
export type Color = LiteralUnion<PredefinedColors, string>;
export type Theme = 'ios' | 'md' | 'ionic';
export type Mode = 'ios' | 'md';
export type ComponentTags = string;
// eslint-disable-next-line

View File

@@ -1,6 +1,6 @@
import type { SpinnerTypes } from '../components/spinner/spinner-configs';
import type { TabButtonLayout } from '../components/tab-bar/tab-bar-interface';
import type { AnimationBuilder, Mode } from '../interface';
import type { AnimationBuilder, Mode, Theme } from '../interface';
import type { PlatformConfig } from './platform';
@@ -22,6 +22,11 @@ export interface IonicConfig {
*/
mode?: Mode;
/**
* The theme determines the appearance of components.
*/
theme?: Theme;
/**
* Wherever ionic will respond to hardware go back buttons in an Android device.
* Defaults to `true` when ionic runs in a mobile device.

View File

@@ -16,6 +16,7 @@ export const menuPushAnimation = (menu: MenuI): Animation => {
const mode = getIonMode(menu);
const width = menu.width;
const isIos = mode === 'ios';
if (menu.isEndSide) {
contentOpenedX = -width + 'px';
@@ -35,5 +36,5 @@ export const menuPushAnimation = (menu: MenuI): Animation => {
const backdropAnimation = createAnimation().addElement(menu.backdropEl!).fromTo('opacity', 0.01, 0.32);
return baseAnimation(mode === 'ios').addAnimation([menuAnimation, contentAnimation, backdropAnimation]);
return baseAnimation(isIos).addAnimation([menuAnimation, contentAnimation, backdropAnimation]);
};

View File

@@ -12,10 +12,11 @@ import { baseAnimation } from './base';
*/
export const menuRevealAnimation = (menu: MenuI): Animation => {
const mode = getIonMode(menu);
const isIos = mode === 'ios';
const openedX = menu.width * (menu.isEndSide ? -1 : 1) + 'px';
const contentOpen = createAnimation()
.addElement(menu.contentEl!) // REVIEW
.fromTo('transform', 'translateX(0px)', `translateX(${openedX})`);
return baseAnimation(mode === 'ios').addAnimation(contentOpen);
return baseAnimation(isIos).addAnimation(contentOpen);
};

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