Files
2023-04-12 13:25:14 -07:00

270 lines
8.3 KiB
TypeScript

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';
const win = typeof (window as any) !== 'undefined' ? (window as any) : undefined;
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
const SUPPORTS_VARS = win && !!(win.CSS && win.CSS.supports && win.CSS.supports('--a: 0'));
const BREAKPOINTS = ['', 'xs', 'sm', 'md', 'lg', 'xl'];
@Component({
tag: 'ion-col',
styleUrl: 'col.scss',
shadow: true,
})
export class Col implements ComponentInterface {
/**
* The amount to offset the column, in terms of how many columns it should shift to the end
* of the total available.
*/
@Prop() offset?: string;
/**
* The amount to offset the column for xs screens, in terms of how many columns it should shift
* to the end of the total available.
*/
@Prop() offsetXs?: string;
/**
* The amount to offset the column for sm screens, in terms of how many columns it should shift
* to the end of the total available.
*/
@Prop() offsetSm?: string;
/**
* The amount to offset the column for md screens, in terms of how many columns it should shift
* to the end of the total available.
*/
@Prop() offsetMd?: string;
/**
* The amount to offset the column for lg screens, in terms of how many columns it should shift
* to the end of the total available.
*/
@Prop() offsetLg?: string;
/**
* The amount to offset the column for xl screens, in terms of how many columns it should shift
* to the end of the total available.
*/
@Prop() offsetXl?: string;
/**
* The amount to pull the column, in terms of how many columns it should shift to the start of
* the total available.
*/
@Prop() pull?: string;
/**
* The amount to pull the column for xs screens, in terms of how many columns it should shift
* to the start of the total available.
*/
@Prop() pullXs?: string;
/**
* The amount to pull the column for sm screens, in terms of how many columns it should shift
* to the start of the total available.
*/
@Prop() pullSm?: string;
/**
* The amount to pull the column for md screens, in terms of how many columns it should shift
* to the start of the total available.
*/
@Prop() pullMd?: string;
/**
* The amount to pull the column for lg screens, in terms of how many columns it should shift
* to the start of the total available.
*/
@Prop() pullLg?: string;
/**
* The amount to pull the column for xl screens, in terms of how many columns it should shift
* to the start of the total available.
*/
@Prop() pullXl?: string;
/**
* The amount to push the column, in terms of how many columns it should shift to the end
* of the total available.
*/
@Prop() push?: string;
/**
* The amount to push the column for xs screens, in terms of how many columns it should shift
* to the end of the total available.
*/
@Prop() pushXs?: string;
/**
* The amount to push the column for sm screens, in terms of how many columns it should shift
* to the end of the total available.
*/
@Prop() pushSm?: string;
/**
* The amount to push the column for md screens, in terms of how many columns it should shift
* to the end of the total available.
*/
@Prop() pushMd?: string;
/**
* The amount to push the column for lg screens, in terms of how many columns it should shift
* to the end of the total available.
*/
@Prop() pushLg?: string;
/**
* The amount to push the column for xl screens, in terms of how many columns it should shift
* to the end of the total available.
*/
@Prop() pushXl?: string;
/**
* The size of the column, in terms of how many columns it should take up out of the total
* available. If `"auto"` is passed, the column will be the size of its content.
*/
@Prop() size?: string;
/**
* The size of the column for xs screens, in terms of how many columns it should take up out
* of the total available. If `"auto"` is passed, the column will be the size of its content.
*/
@Prop() sizeXs?: string;
/**
* The size of the column for sm screens, in terms of how many columns it should take up out
* of the total available. If `"auto"` is passed, the column will be the size of its content.
*/
@Prop() sizeSm?: string;
/**
* The size of the column for md screens, in terms of how many columns it should take up out
* of the total available. If `"auto"` is passed, the column will be the size of its content.
*/
@Prop() sizeMd?: string;
/**
* The size of the column for lg screens, in terms of how many columns it should take up out
* of the total available. If `"auto"` is passed, the column will be the size of its content.
*/
@Prop() sizeLg?: string;
/**
* The size of the column for xl screens, in terms of how many columns it should take up out
* of the total available. If `"auto"` is passed, the column will be the size of its content.
*/
@Prop() sizeXl?: string;
@Listen('resize', { target: 'window' })
onResize() {
forceUpdate(this);
}
// Loop through all of the breakpoints to see if the media query
// matches and grab the column value from the relevant prop if so
private getColumns(property: string) {
let matched;
for (const breakpoint of BREAKPOINTS) {
const matches = matchBreakpoint(breakpoint);
// Grab the value of the property, if it exists and our
// media query matches we return the value
const columns = (this as any)[property + breakpoint.charAt(0).toUpperCase() + breakpoint.slice(1)];
if (matches && columns !== undefined) {
matched = columns;
}
}
// Return the last matched columns since the breakpoints
// increase in size and we want to return the largest match
return matched;
}
private calculateSize() {
const columns = this.getColumns('size');
// If size wasn't set for any breakpoint
// or if the user set the size without a value
// it means we need to stick with the default and return
// e.g. <ion-col size-md>
if (!columns || columns === '') {
return;
}
// If the size is set to auto then don't calculate a size
const colSize =
columns === 'auto'
? 'auto'
: // If CSS supports variables we should use the grid columns var
SUPPORTS_VARS
? `calc(calc(${columns} / var(--ion-grid-columns, 12)) * 100%)`
: // Convert the columns to a percentage by dividing by the total number
// of columns (12) and then multiplying by 100
(columns / 12) * 100 + '%';
return {
flex: `0 0 ${colSize}`,
width: `${colSize}`,
'max-width': `${colSize}`,
};
}
// Called by push, pull, and offset since they use the same calculations
private calculatePosition(property: string, modifier: string) {
const columns = this.getColumns(property);
if (!columns) {
return;
}
// If the number of columns passed are greater than 0 and less than
// 12 we can position the column, else default to auto
const amount = SUPPORTS_VARS
? // If CSS supports variables we should use the grid columns var
`calc(calc(${columns} / var(--ion-grid-columns, 12)) * 100%)`
: // Convert the columns to a percentage by dividing by the total number
// of columns (12) and then multiplying by 100
columns > 0 && columns < 12
? (columns / 12) * 100 + '%'
: 'auto';
return {
[modifier]: amount,
};
}
private calculateOffset(isRTL: boolean) {
return this.calculatePosition('offset', isRTL ? 'margin-right' : 'margin-left');
}
private calculatePull(isRTL: boolean) {
return this.calculatePosition('pull', isRTL ? 'left' : 'right');
}
private calculatePush(isRTL: boolean) {
return this.calculatePosition('push', isRTL ? 'right' : 'left');
}
render() {
const isRTL = document.dir === 'rtl';
const mode = getIonMode(this);
return (
<Host
class={{
[mode]: true,
}}
style={{
...this.calculateOffset(isRTL),
...this.calculatePull(isRTL),
...this.calculatePush(isRTL),
...this.calculateSize(),
}}
>
<slot></slot>
</Host>
);
}
}