@freshworks/crayons
Version:
Crayons Web Components library
437 lines (432 loc) • 17.7 kB
JavaScript
import { attachShadow, createEvent, h, proxyCustomElement } from '@stencil/core/internal/client';
import { g as getFocusableChildren } from './index2.js';
import { i as i18n } from './Translation.js';
import { d as defineCustomElement$8 } from './button.js';
import { d as defineCustomElement$2, a as defineCustomElement$7 } from './icon.js';
import { d as defineCustomElement$6 } from './modal-content.js';
import { d as defineCustomElement$5 } from './modal-footer.js';
import { d as defineCustomElement$4 } from './modal-title.js';
import { d as defineCustomElement$3 } from './spinner.js';
const modalCss = ":host{font-family:var(--fw-font-family, -apple-system, blinkmacsystemfont, \"Segoe UI\", roboto, oxygen, ubuntu, cantarell, \"Open Sans\", \"Helvetica Neue\", sans-serif);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-box-sizing:border-box;box-sizing:border-box}.modal-overlay{height:100vh;width:100vw;position:fixed;top:0;right:0;bottom:0;left:0;display:none;z-index:990;background-color:rgba(18, 52, 77, 0.5);-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;-webkit-transition:all 0.3s linear;transition:all 0.3s linear}.modal{position:relative;display:-ms-flexbox;display:flex;max-height:70vh;background:#fff;-webkit-box-shadow:0px 2px 18px rgba(18, 52, 77, 0.2);box-shadow:0px 2px 18px rgba(18, 52, 77, 0.2);border-radius:4px;z-index:999;-webkit-animation:\"modal-entry\" 0.5s 1;animation:\"modal-entry\" 0.5s 1;overflow-wrap:anywhere;word-break:break-word;white-space:normal}.modal .modal-container{position:relative;width:100%;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.modal .modal-container .content{padding:0px 32px 32px;overflow:visible;-webkit-box-sizing:border-box;box-sizing:border-box}.modal .close-btn{background-color:transparent;background-image:none;border:1px solid transparent;color:#264966;padding:4px 6px;min-width:16px;height:24px;position:absolute;top:8px;right:8px;-webkit-transition:all 0.3s;transition:all 0.3s;z-index:1}.modal .close-btn:hover,.modal .close-btn:focus{background-color:#ebeff3;border-radius:4px;border-color:#ebeff3;cursor:pointer}.standard{width:512px}.small{width:424px}.large{width:800px}.modal-overlay.slider{-ms-flex-pack:end;justify-content:flex-end}.modal-overlay.slider .modal{height:100vh;max-height:100vh;border-radius:0px;width:600px;-webkit-animation:\"modal-entry-right\" 0.5s 1;animation:\"modal-entry-right\" 0.5s 1}.modal-overlay.slider .modal .close-btn{height:24px;width:24px;-webkit-box-sizing:border-box;box-sizing:border-box;top:0px;right:600px;background-color:#12344d;border-radius:2px 0px 0px 2px;padding:0px;margin:0px;line-height:24px;text-align:center}.modal-overlay.slider .modal .close-btn:hover,.modal-overlay.slider .modal .close-btn:focus,.modal-overlay.slider .modal .close-btn:focus-visible{background-color:#12344d;border-radius:2px 0px 0px 2px;border-color:#12344d;outline:0px}.modal-overlay.slider .modal .close-btn:focus,.modal-overlay.slider .modal .close-btn:focus-visible{border:1px solid #2c5cc5;-webkit-box-shadow:#2c5cc5 0px 0px 0px 1px;box-shadow:#2c5cc5 0px 0px 0px 1px}.modal-overlay.slider .modal .close-btn fw-icon{height:12px;width:12px}.modal-overlay.slider .modal.small,.modal-overlay.slider .modal.standard,.modal-overlay.slider .modal.large{width:600px}.visible{display:-ms-flexbox;display:flex}@-webkit-keyframes modal-entry{0%{-webkit-transform:translateY(-10px);transform:translateY(-10px)}100%{-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes modal-entry{0%{-webkit-transform:translateY(-10px);transform:translateY(-10px)}100%{-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes modal-entry-right{0%{-webkit-transform:translateX(calc(100% - 520px));transform:translateX(calc(100% - 520px))}100%{-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes modal-entry-right{0%{-webkit-transform:translateX(calc(100% - 520px));transform:translateX(calc(100% - 520px))}100%{-webkit-transform:translateX(0);transform:translateX(0)}}";
var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
r = Reflect.decorate(decorators, target, key, desc);
else
for (var i = decorators.length - 1; i >= 0; i--)
if (d = decorators[i])
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
let Modal = class extends HTMLElement {
constructor() {
super();
this.__registerHost();
attachShadow(this);
this.fwSubmit = createEvent(this, "fwSubmit", 7);
this.fwOpen = createEvent(this, "fwOpen", 7);
this.fwClose = createEvent(this, "fwClose", 7);
/**
* Property to add or remove the top right close icon button
*/
this.hasCloseIconButton = true;
/**
* The icon to be displayed with the title
*/
this.icon = '';
/**
* Size of the modal
*/
this.size = 'standard';
/**
* The text for the submit button
*/
this.submitText = '';
/**
* The text for the cancel button
*/
this.cancelText = '';
/**
* Default state of submit button
*/
this.submitDisabled = false;
/**
* The color of submit button
*/
this.submitColor = 'primary';
/**
* Hide footer for the modal
*/
this.hideFooter = false;
/**
* Convert modal to slider
*/
this.slider = false;
/**
* Toggle the visibility of the modal
*/
this.isOpen = false;
/**
* private
* Modal container ref
*/
this.modalContainer = null;
/**
* private
* Handler to run on modal container opening
*/
this.modalContainerHandler = null;
/**
* private
* Modal first focus element
*/
this.firstFocusableElement = null;
/**
* private
* Handler to first focusable element. Focuses last element on tab for focus locking.
*/
this.firstFocusableElementHandler = null;
/**
* private
* Modal last focus element
*/
this.lastFocusableElement = null;
/**
* private
* Handler for last focusable element. Focus first element on shift+tab for focus locking.
*/
this.lastFocusableElementHandler = null;
/**
* private
* Modal element to focus on open
*/
this.modalOpenFocusElement = null;
/**
* private
* flag to add events to elements only on initial modal load
*/
this.accessibilityAdded = false;
/**
* private
* escape key handler
*/
this.escapeHandler = null;
/**
* private
* styleVariation styles that vary in normal and slider variations
*/
this.styleVariation = {
closeColor: {
modal: '#475867',
slider: '#FFFFFF',
},
closeSize: {
modal: 10,
slider: 12,
},
closeName: {
modal: 'cross',
slider: 'cross-big',
},
};
}
/**
* lifecycle event, called once just after the component is first connected to the DOM
*/
componentWillLoad() {
if (!this.modalTitle) {
this.modalTitle = this.el.querySelector('fw-modal-title');
}
if (!this.modalFooter) {
this.modalFooter = this.el.querySelector('fw-modal-footer');
if (this.modalFooter) {
this.modalFooter.submit = this.submit.bind(this);
this.modalFooter.close = this.close.bind(this);
}
}
if (!this.modalContent) {
this.modalContent = this.el.querySelector('fw-modal-content');
}
if (this.hideFooter && this.modalFooter) {
this.modalFooter.style.display = 'none';
}
if (!this.modalContent && (this.modalTitle || this.modalFooter)) {
/**
* Error that occurs when fw-modal-header or fw-modal-footer is used without
* fw-modal-content component. If this not handled, the content that goes inside
* fw-modal-content would have fw-modal-header or fw-modal-footer.
* This would lead to unexpected results such as header or footer having padding and scroll.
*/
throw new Error('Composition Error: fw-modal component must have fw-modal-content component when \
either fw-modal-header or fw-modal-footer components are used for modal composition');
}
}
componentDidLoad() {
if (this.isOpen && !this.accessibilityAdded) {
document.body.style.overflow = 'hidden';
this.addAccesibilityEvents();
}
}
disconnectedCallback() {
if (this.isOpen) {
document.body.style.overflow = 'auto';
this.removeAccesibilityEvents();
}
}
visibilityChange(open) {
if (!open) {
document.body.style.overflow = 'auto';
this.removeAccesibilityEvents();
this.fwClose.emit();
}
else {
document.body.style.overflow = 'hidden';
this.addAccesibilityEvents();
this.fwOpen.emit();
}
}
footerVisibilityChange(hideFooter) {
if (this.modalFooter) {
if (hideFooter) {
this.modalFooter.style.display = 'none';
}
else {
this.modalFooter.style.display = 'block';
}
}
}
/**
* Method available from the component to perform close action on the modal
* @returns promise that resolves to true
*/
async close() {
this.isOpen = false;
return true;
}
/**
* Method available from the component to perform open action on the modal
* @returns promise that resolves to true
*/
async open() {
this.isOpen = true;
return true;
}
/**
* private submit
*/
submit() {
this.fwSubmit.emit();
}
/**
* Adds accesibility related events to the component.
* Major actions would be to focus-lock inside modal and to focus on close button when opening the component.
*/
addAccesibilityEvents() {
var _a, _b, _c;
if (!this.accessibilityAdded ||
!((_a = this.firstFocusableElement) === null || _a === void 0 ? void 0 : _a.parentNode) ||
!((_b = this.modalOpenFocusElement) === null || _b === void 0 ? void 0 : _b.parentNode) ||
!((_c = this.lastFocusableElement) === null || _c === void 0 ? void 0 : _c.parentNode)) {
this.modalContainerHandler &&
this.modalContainer.removeEventListener('animationend', this.modalContainerHandler);
this.modalContainerHandler = (() => {
this.firstFocusableElementHandler &&
this.firstFocusableElement.removeEventListener('keydown', this.firstFocusableElementHandler);
this.lastFocusableElementHandler &&
this.lastFocusableElement.removeEventListener('keydown', this.lastFocusableElementHandler);
/**
* Focus trapping inside Modal. Below function gets all focusable elements from the modal.
* These include elements inside shadow dom too.
*/
const focusableElements = getFocusableChildren(this.el);
if (focusableElements.length) {
this.firstFocusableElement = focusableElements[0];
this.lastFocusableElement =
focusableElements[focusableElements.length - 1];
this.modalOpenFocusElement =
focusableElements.length > 1 &&
this.firstFocusableElement.classList.contains('close-btn')
? focusableElements[1]
: this.firstFocusableElement;
this.lastFocusableElementHandler = ((e) => {
if (!e.shiftKey && e.keyCode === 9) {
e.preventDefault();
this.focusElement(this.firstFocusableElement);
}
}).bind(this);
this.firstFocusableElementHandler = ((e) => {
if (e.shiftKey && e.keyCode === 9) {
e.preventDefault();
this.focusElement(this.lastFocusableElement);
}
}).bind(this);
this.lastFocusableElement.addEventListener('keydown', this.lastFocusableElementHandler);
this.firstFocusableElement.addEventListener('keydown', this.firstFocusableElementHandler);
}
if (this.isOpen && this.modalOpenFocusElement) {
this.focusElement(this.modalOpenFocusElement);
}
}).bind(this);
this.modalContainer &&
this.modalContainer.addEventListener('animationend', this.modalContainerHandler);
this.accessibilityAdded = true;
}
this.escapeHandler = ((e) => {
if (e.keyCode === 27) {
this.isOpen = false;
}
}).bind(this);
document.addEventListener('keydown', this.escapeHandler);
}
/**
* Removes accesibility related events bound to the document.
*/
removeAccesibilityEvents() {
if (this.escapeHandler) {
document.removeEventListener('keydown', this.escapeHandler);
this.escapeHandler = null;
}
}
/**
* @param element element to focus on
*/
focusElement(element) {
try {
if (element.setFocus) {
element.setFocus();
}
else {
element.focus();
}
}
catch (error) {
console.log(error);
}
}
/**
* Renders Icon in Modal header.
* @returns {JSX.Element}
*/
renderIcon() {
return h("fw-icon", { class: 'icon', name: this.icon, size: 16 });
}
/**
* Renders title text and description in modal header.
* @returns {JSX.Element}
*/
renderTitle() {
return (h("fw-modal-title", { icon: this.icon, titleText: this.titleText, description: this.description }));
}
/**
* renders the slot content in the modal.
* @returns {JSX.Element}
*/
renderContent() {
return (h("fw-modal-content", null, h("slot", null)));
}
/**
* renders the default footer in the modal
* @returns {JSX.Element}
*/
renderFooter() {
return (h("fw-modal-footer", { submitText: this.submitText, cancelText: this.cancelText, submitDisabled: this.submitDisabled, submitColor: this.submitColor, submit: this.submit.bind(this), close: this.close.bind(this), style: { display: this.hideFooter ? 'none' : 'block' } }));
}
/**
* renders either default modal content based on attributes or renders the modal child components like
* modal-title, modal-content and modal-footer components.
* @returns {JSX.Element}
*/
render() {
const variation = this.styleVariation;
return (h("div", { class: {
'modal-overlay': true,
'visible': this.isOpen,
'slider': this.slider,
} }, h("div", { class: { modal: true, [this.size]: true }, "aria-modal": 'true', ref: (el) => (this.modalContainer = el) }, this.hasCloseIconButton && (h("button", { class: 'close-btn', onClick: () => this.close() }, h("fw-icon", { name: this.slider
? variation.closeName.slider
: variation.closeName.modal, library: 'system', color: this.slider
? variation.closeColor.slider
: variation.closeColor.modal, size: this.slider
? variation.closeSize.slider
: variation.closeSize.modal }))), h("div", { class: 'modal-container' }, this.modalTitle ? '' : this.titleText ? this.renderTitle() : '', this.modalContent ? h("slot", null) : this.renderContent(), this.modalFooter ? '' : this.renderFooter()))));
}
get el() { return this; }
static get watchers() { return {
"isOpen": ["visibilityChange"],
"hideFooter": ["footerVisibilityChange"]
}; }
static get style() { return modalCss; }
};
__decorate([
i18n({ keyName: 'modal.ok' })
], Modal.prototype, "submitText", void 0);
__decorate([
i18n({ keyName: 'modal.cancel' })
], Modal.prototype, "cancelText", void 0);
Modal = /*@__PURE__*/ proxyCustomElement(Modal, [1, "fw-modal", {
"hasCloseIconButton": [4, "has-close-icon-button"],
"titleText": [1, "title-text"],
"description": [1],
"icon": [1],
"size": [1],
"submitText": [1025, "submit-text"],
"cancelText": [1025, "cancel-text"],
"submitDisabled": [4, "submit-disabled"],
"submitColor": [1, "submit-color"],
"hideFooter": [4, "hide-footer"],
"slider": [4],
"isOpen": [1540, "is-open"],
"modalTitle": [32],
"modalFooter": [32],
"modalContent": [32],
"close": [64],
"open": [64]
}]);
function defineCustomElement$1() {
const components = ["fw-modal", "fw-button", "fw-icon", "fw-modal-content", "fw-modal-footer", "fw-modal-title", "fw-spinner", "fw-toast-message"];
components.forEach(tagName => { switch (tagName) {
case "fw-modal":
if (!customElements.get(tagName)) {
customElements.define(tagName, Modal);
}
break;
case "fw-button":
if (!customElements.get(tagName)) {
defineCustomElement$8();
}
break;
case "fw-icon":
if (!customElements.get(tagName)) {
defineCustomElement$7();
}
break;
case "fw-modal-content":
if (!customElements.get(tagName)) {
defineCustomElement$6();
}
break;
case "fw-modal-footer":
if (!customElements.get(tagName)) {
defineCustomElement$5();
}
break;
case "fw-modal-title":
if (!customElements.get(tagName)) {
defineCustomElement$4();
}
break;
case "fw-spinner":
if (!customElements.get(tagName)) {
defineCustomElement$3();
}
break;
case "fw-toast-message":
if (!customElements.get(tagName)) {
defineCustomElement$2();
}
break;
} });
}
const FwModal = Modal;
const defineCustomElement = defineCustomElement$1;
export { FwModal, defineCustomElement };