@ionic/core
Version:
Base components for Ionic
384 lines (378 loc) • 19.7 kB
JavaScript
/*!
* (C) Ionic http://ionicframework.com - MIT License
*/
import { proxyCustomElement, HTMLElement, h, Host } from '@stencil/core/internal/client';
import { a as addEventListener, c as componentOnReady } from './helpers.js';
import { a as printIonError } from './index6.js';
import { c as createColorClasses } from './theme.js';
import { b as getIonMode } from './ionic-global.js';
import { s as parseDate, x as getToday, L as getHourCycle, N as getLocalizedDateTime, M as getLocalizedTime } from './data.js';
import { d as defineCustomElement$2 } from './ripple-effect.js';
const datetimeButtonIosCss = ":host{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}:host button{border-radius:8px;-webkit-margin-start:2px;margin-inline-start:2px;-webkit-margin-end:2px;margin-inline-end:2px;margin-top:0px;margin-bottom:0px;position:relative;-webkit-transition:150ms color ease-in-out;transition:150ms color ease-in-out;border:none;background:var(--ion-color-step-300, var(--ion-background-color-step-300, #edeef0));color:var(--ion-text-color, #000);font-family:inherit;font-size:1rem;cursor:pointer;overflow:hidden;-webkit-appearance:none;-moz-appearance:none;appearance:none}:host(.time-active) #time-button,:host(.date-active) #date-button{color:var(--ion-color-base)}:host(.datetime-button-disabled){pointer-events:none}:host(.datetime-button-disabled) button{opacity:0.4}:host button{-webkit-padding-start:13px;padding-inline-start:13px;-webkit-padding-end:13px;padding-inline-end:13px;padding-top:7px;padding-bottom:7px}:host button.ion-activated{color:var(--ion-color-step-600, var(--ion-text-color-step-400, #666666))}";
const IonDatetimeButtonIosStyle0 = datetimeButtonIosCss;
const datetimeButtonMdCss = ":host{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}:host button{border-radius:8px;-webkit-margin-start:2px;margin-inline-start:2px;-webkit-margin-end:2px;margin-inline-end:2px;margin-top:0px;margin-bottom:0px;position:relative;-webkit-transition:150ms color ease-in-out;transition:150ms color ease-in-out;border:none;background:var(--ion-color-step-300, var(--ion-background-color-step-300, #edeef0));color:var(--ion-text-color, #000);font-family:inherit;font-size:1rem;cursor:pointer;overflow:hidden;-webkit-appearance:none;-moz-appearance:none;appearance:none}:host(.time-active) #time-button,:host(.date-active) #date-button{color:var(--ion-color-base)}:host(.datetime-button-disabled){pointer-events:none}:host(.datetime-button-disabled) button{opacity:0.4}:host button{-webkit-padding-start:12px;padding-inline-start:12px;-webkit-padding-end:12px;padding-inline-end:12px;padding-top:6px;padding-bottom:6px}";
const IonDatetimeButtonMdStyle0 = datetimeButtonMdCss;
const DatetimeButton = /*@__PURE__*/ proxyCustomElement(class DatetimeButton extends HTMLElement {
constructor() {
super();
this.__registerHost();
this.__attachShadow();
this.datetimeEl = null;
this.overlayEl = null;
/**
* Accepts one or more string values and converts
* them to DatetimeParts. This is done so datetime-button
* can work with an array internally and not need
* to keep checking if the datetime value is `string` or `string[]`.
*/
this.getParsedDateValues = (value) => {
if (value === undefined || value === null) {
return [];
}
if (Array.isArray(value)) {
return value;
}
return [value];
};
/**
* Check the value property on the linked
* ion-datetime and then format it according
* to the locale specified on ion-datetime.
*/
this.setDateTimeText = () => {
var _a, _b, _c, _d, _e;
const { datetimeEl, datetimePresentation } = this;
if (!datetimeEl) {
return;
}
const { value, locale, formatOptions, hourCycle, preferWheel, multiple, titleSelectedDatesFormatter } = datetimeEl;
const parsedValues = this.getParsedDateValues(value);
/**
* Both ion-datetime and ion-datetime-button default
* to today's date and time if no value is set.
*/
const parsedDatetimes = parseDate(parsedValues.length > 0 ? parsedValues : [getToday()]);
if (!parsedDatetimes) {
return;
}
/**
* If developers incorrectly use multiple="true"
* with non "date" datetimes, then just select
* the first value so the interface does
* not appear broken. Datetime will provide a
* warning in the console.
*/
const firstParsedDatetime = parsedDatetimes[0];
const computedHourCycle = getHourCycle(locale, hourCycle);
this.dateText = this.timeText = undefined;
switch (datetimePresentation) {
case 'date-time':
case 'time-date':
const dateText = getLocalizedDateTime(locale, firstParsedDatetime, (_a = formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.date) !== null && _a !== void 0 ? _a : { month: 'short', day: 'numeric', year: 'numeric' });
const timeText = getLocalizedTime(locale, firstParsedDatetime, computedHourCycle, formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.time);
if (preferWheel) {
this.dateText = `${dateText} ${timeText}`;
}
else {
this.dateText = dateText;
this.timeText = timeText;
}
break;
case 'date':
if (multiple && parsedValues.length !== 1) {
let headerText = `${parsedValues.length} days`; // default/fallback for multiple selection
if (titleSelectedDatesFormatter !== undefined) {
try {
headerText = titleSelectedDatesFormatter(parsedValues);
}
catch (e) {
printIonError('Exception in provided `titleSelectedDatesFormatter`: ', e);
}
}
this.dateText = headerText;
}
else {
this.dateText = getLocalizedDateTime(locale, firstParsedDatetime, (_b = formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.date) !== null && _b !== void 0 ? _b : { month: 'short', day: 'numeric', year: 'numeric' });
}
break;
case 'time':
this.timeText = getLocalizedTime(locale, firstParsedDatetime, computedHourCycle, formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.time);
break;
case 'month-year':
this.dateText = getLocalizedDateTime(locale, firstParsedDatetime, (_c = formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.date) !== null && _c !== void 0 ? _c : { month: 'long', year: 'numeric' });
break;
case 'month':
this.dateText = getLocalizedDateTime(locale, firstParsedDatetime, (_d = formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.time) !== null && _d !== void 0 ? _d : { month: 'long' });
break;
case 'year':
this.dateText = getLocalizedDateTime(locale, firstParsedDatetime, (_e = formatOptions === null || formatOptions === void 0 ? void 0 : formatOptions.time) !== null && _e !== void 0 ? _e : { year: 'numeric' });
break;
}
};
/**
* Waits for the ion-datetime to re-render.
* This is needed in order to correctly position
* a popover relative to the trigger element.
*/
this.waitForDatetimeChanges = async () => {
const { datetimeEl } = this;
if (!datetimeEl) {
return Promise.resolve();
}
return new Promise((resolve) => {
addEventListener(datetimeEl, 'ionRender', resolve, { once: true });
});
};
this.handleDateClick = async (ev) => {
const { datetimeEl, datetimePresentation } = this;
if (!datetimeEl) {
return;
}
let needsPresentationChange = false;
/**
* When clicking the date button,
* we need to make sure that only a date
* picker is displayed. For presentation styles
* that display content other than a date picker,
* we need to update the presentation style.
*/
switch (datetimePresentation) {
case 'date-time':
case 'time-date':
const needsChange = datetimeEl.presentation !== 'date';
/**
* The date+time wheel picker
* shows date and time together,
* so do not adjust the presentation
* in that case.
*/
if (!datetimeEl.preferWheel && needsChange) {
datetimeEl.presentation = 'date';
needsPresentationChange = true;
}
break;
}
/**
* Track which button was clicked
* so that it can have the correct
* activated styles applied when
* the modal/popover containing
* the datetime is opened.
*/
this.selectedButton = 'date';
this.presentOverlay(ev, needsPresentationChange, this.dateTargetEl);
};
this.handleTimeClick = (ev) => {
const { datetimeEl, datetimePresentation } = this;
if (!datetimeEl) {
return;
}
let needsPresentationChange = false;
/**
* When clicking the time button,
* we need to make sure that only a time
* picker is displayed. For presentation styles
* that display content other than a time picker,
* we need to update the presentation style.
*/
switch (datetimePresentation) {
case 'date-time':
case 'time-date':
const needsChange = datetimeEl.presentation !== 'time';
if (needsChange) {
datetimeEl.presentation = 'time';
needsPresentationChange = true;
}
break;
}
/**
* Track which button was clicked
* so that it can have the correct
* activated styles applied when
* the modal/popover containing
* the datetime is opened.
*/
this.selectedButton = 'time';
this.presentOverlay(ev, needsPresentationChange, this.timeTargetEl);
};
/**
* If the datetime is presented in an
* overlay, the datetime and overlay
* should be appropriately sized.
* These classes provide default sizing values
* that developers can customize.
* The goal is to provide an overlay that is
* reasonably sized with a datetime that
* fills the entire container.
*/
this.presentOverlay = async (ev, needsPresentationChange, triggerEl) => {
const { overlayEl } = this;
if (!overlayEl) {
return;
}
if (overlayEl.tagName === 'ION-POPOVER') {
/**
* When the presentation on datetime changes,
* we need to wait for the component to re-render
* otherwise the computed width/height of the
* popover content will be wrong, causing
* the popover to not align with the trigger element.
*/
if (needsPresentationChange) {
await this.waitForDatetimeChanges();
}
/**
* We pass the trigger button element
* so that the popover aligns with the individual
* button that was clicked, not the component container.
*/
overlayEl.present(Object.assign(Object.assign({}, ev), { detail: {
ionShadowTarget: triggerEl,
} }));
}
else {
overlayEl.present();
}
};
this.datetimePresentation = 'date-time';
this.dateText = undefined;
this.timeText = undefined;
this.datetimeActive = false;
this.selectedButton = undefined;
this.color = 'primary';
this.disabled = false;
this.datetime = undefined;
}
async componentWillLoad() {
const { datetime } = this;
if (!datetime) {
printIonError('An ID associated with an ion-datetime instance is required for ion-datetime-button to function properly.', this.el);
return;
}
const datetimeEl = (this.datetimeEl = document.getElementById(datetime));
if (!datetimeEl) {
printIonError(`No ion-datetime instance found for ID '${datetime}'.`, this.el);
return;
}
/**
* The element reference must be an ion-datetime. Print an error
* if a non-datetime element was provided.
*/
if (datetimeEl.tagName !== 'ION-DATETIME') {
printIonError(`Expected an ion-datetime instance for ID '${datetime}' but received '${datetimeEl.tagName.toLowerCase()}' instead.`, datetimeEl);
return;
}
/**
* Since the datetime can be used in any context (overlays, accordion, etc)
* we track when it is visible to determine when it is active.
* This informs which button is highlighted as well as the
* aria-expanded state.
*/
const io = new IntersectionObserver((entries) => {
const ev = entries[0];
this.datetimeActive = ev.isIntersecting;
}, {
threshold: 0.01,
});
io.observe(datetimeEl);
/**
* Get a reference to any modal/popover
* the datetime is being used in so we can
* correctly size it when it is presented.
*/
const overlayEl = (this.overlayEl = datetimeEl.closest('ion-modal, ion-popover'));
/**
* The .ion-datetime-button-overlay class contains
* styles that allow any modal/popover to be
* sized according to the dimensions of the datetime.
* If developers want a smaller/larger overlay all they need
* to do is change the width/height of the datetime.
* Additionally, this lets us avoid having to set
* explicit widths on each variant of datetime.
*/
if (overlayEl) {
overlayEl.classList.add('ion-datetime-button-overlay');
}
componentOnReady(datetimeEl, () => {
const datetimePresentation = (this.datetimePresentation = datetimeEl.presentation || 'date-time');
/**
* Set the initial display
* in the rendered buttons.
*
* From there, we need to listen
* for ionChange to be emitted
* from datetime so we know when
* to re-render the displayed
* text in the buttons.
*/
this.setDateTimeText();
addEventListener(datetimeEl, 'ionValueChange', this.setDateTimeText);
/**
* Configure the initial selected button
* in the event that the datetime is displayed
* without clicking one of the datetime buttons.
* For example, a datetime could be expanded
* in an accordion. In this case users only
* need to click the accordion header to show
* the datetime.
*/
switch (datetimePresentation) {
case 'date-time':
case 'date':
case 'month-year':
case 'month':
case 'year':
this.selectedButton = 'date';
break;
case 'time-date':
case 'time':
this.selectedButton = 'time';
break;
}
});
}
render() {
const { color, dateText, timeText, selectedButton, datetimeActive, disabled } = this;
const mode = getIonMode(this);
return (h(Host, { key: '26e606af6f067a5774db37ed41387ffebb941777', class: createColorClasses(color, {
[mode]: true,
[`${selectedButton}-active`]: datetimeActive,
['datetime-button-disabled']: disabled,
}) }, dateText && (h("button", { key: '6b7aa66a15b4a6d89d411e40586de28a2ac9f343', class: "ion-activatable", id: "date-button", "aria-expanded": datetimeActive ? 'true' : 'false', onClick: this.handleDateClick, disabled: disabled, part: "native", ref: (el) => (this.dateTargetEl = el) }, h("slot", { key: 'd42f34fd167be34386319e7ea788c2ab03c90b87', name: "date-target" }, dateText), mode === 'md' && h("ion-ripple-effect", { key: '47dd34f3c2799064cac7a5fe25440ecc043950f0' }))), timeText && (h("button", { key: 'd77424a20fae320654774c7bfc8a8e2369d3afe3', class: "ion-activatable", id: "time-button", "aria-expanded": datetimeActive ? 'true' : 'false', onClick: this.handleTimeClick, disabled: disabled, part: "native", ref: (el) => (this.timeTargetEl = el) }, h("slot", { key: 'ac088a78141bb53f2efd48dd7745f8954c92378b', name: "time-target" }, timeText), mode === 'md' && h("ion-ripple-effect", { key: 'b3a58ddfd28b9396e2518ffd62a045ec13d8b9d0' })))));
}
get el() { return this; }
static get style() { return {
ios: IonDatetimeButtonIosStyle0,
md: IonDatetimeButtonMdStyle0
}; }
}, [33, "ion-datetime-button", {
"color": [513],
"disabled": [516],
"datetime": [1],
"datetimePresentation": [32],
"dateText": [32],
"timeText": [32],
"datetimeActive": [32],
"selectedButton": [32]
}]);
function defineCustomElement$1() {
if (typeof customElements === "undefined") {
return;
}
const components = ["ion-datetime-button", "ion-ripple-effect"];
components.forEach(tagName => { switch (tagName) {
case "ion-datetime-button":
if (!customElements.get(tagName)) {
customElements.define(tagName, DatetimeButton);
}
break;
case "ion-ripple-effect":
if (!customElements.get(tagName)) {
defineCustomElement$2();
}
break;
} });
}
const IonDatetimeButton = DatetimeButton;
const defineCustomElement = defineCustomElement$1;
export { IonDatetimeButton, defineCustomElement };