UNPKG

@agnos-ui/core

Version:

Framework-agnostic headless component library.

255 lines (254 loc) 7.57 kB
import { computed, readable } from "@amadeus-it-group/tansu"; import { c as typeBoolean, g as typeFunction, e as typeString, h as typeHTMLElementOrNull } from "./writables-CgpOQ4hA.js"; import { writablesForProps, stateStores } from "./utils/stores.js"; import { createTransition } from "./services/transitions/baseTransitions.js"; import { d as promiseFromStore } from "./promise-XSP94FjG.js"; import { noop } from "./utils/func.js"; import { a as bindDirective, r as registrationArray, n as createAttributesDirective, k as mergeDirectives, d as directiveSubscribe, b as browserDirective } from "./dom-gfxqXJpK.js"; import { portal } from "./services/portal.js"; import { siblingsInert } from "./services/siblingsInert.js"; import { createWidgetFactory } from "./utils/widget.js"; const internalRemoveScrollbars = () => { const scrollbarWidth = Math.abs(window.innerWidth - document.documentElement.clientWidth); const body = document.body; const bodyStyle = body.style; const { overflow, paddingRight } = bodyStyle; if (scrollbarWidth > 0) { const actualPadding = parseFloat(window.getComputedStyle(body).paddingRight); bodyStyle.paddingRight = `${actualPadding + scrollbarWidth}px`; } bodyStyle.overflow = "hidden"; return () => { if (scrollbarWidth > 0) { bodyStyle.paddingRight = paddingRight; } bodyStyle.overflow = overflow; }; }; let internalRevert = noop; const removeScrollbars = () => { internalRevert(); internalRevert = internalRemoveScrollbars(); }; const revertScrollbars = () => { internalRevert(); internalRevert = noop; }; const modalOutsideClick = Symbol(); const modalCloseButtonClick = Symbol(); const defaultConfig = { animated: true, ariaCloseButtonLabel: "Close", backdrop: true, backdropClass: "", backdropTransition: noop, closeButton: true, closeOnOutsideClick: true, container: typeof window !== "undefined" ? document.body : null, className: "", modalTransition: noop, onBeforeClose: noop, onVisibleChange: noop, onHidden: noop, onShown: noop, visible: false }; const configValidator = { animated: typeBoolean, ariaCloseButtonLabel: typeString, backdrop: typeBoolean, backdropClass: typeString, backdropTransition: typeFunction, closeButton: typeBoolean, closeOnOutsideClick: typeBoolean, container: typeHTMLElementOrNull, className: typeString, modalTransition: typeFunction, onBeforeClose: typeFunction, onVisibleChange: typeFunction, onHidden: typeFunction, onShown: typeFunction, visible: typeBoolean }; function getModalDefaultConfig() { return { ...defaultConfig }; } const modals$ = registrationArray(); const hasModals$ = computed(() => modals$().length > 0); const scrollbarsAction$ = computed(() => { if (hasModals$()) { removeScrollbars(); } else { revertScrollbars(); } }); const modalsAction$ = computed(() => { scrollbarsAction$(); }); const createModal = createWidgetFactory("modal", (config$) => { const [ { animated$, backdrop$, backdropTransition$, closeOnOutsideClick$, container$, modalTransition$, onBeforeClose$, onVisibleChange$, onHidden$, onShown$, ariaCloseButtonLabel$, className$, backdropClass$, visible$: requestedVisible$, ...stateProps }, patch ] = writablesForProps(defaultConfig, config$, configValidator); const modalTransition = createTransition({ props: { transition: modalTransition$, visible: requestedVisible$, animated: animated$, animatedOnInit: animated$, onVisibleChange: onVisibleChange$, // TODO: for onHidden and onShown, should we combine with information from the backdrop transition? // (especially in case one of the two transitions takes more time than the other) onHidden: onHidden$, onShown: onShown$ } }); const visible$ = modalTransition.stores.visible$; const backdropTransition = createTransition({ props: { transition: backdropTransition$, visible: requestedVisible$, animated: animated$, animatedOnInit: animated$ } }); const transitioning$ = computed(() => modalTransition.stores.transitioning$() || backdrop$() && backdropTransition.stores.transitioning$()); const hidden$ = computed(() => !transitioning$() && !visible$()); const backdropHidden$ = computed(() => !backdrop$() || hidden$()); let hideResult; const close = (result) => { hideResult = result; const beforeCloseEvent = { get result() { return hideResult; }, set result(value) { hideResult = value; }, cancel: false }; onBeforeClose$()(beforeCloseEvent); if (beforeCloseEvent.cancel) { return; } patch({ visible: false }); }; const modalPortalDirective = bindDirective( portal, computed(() => ({ container: container$() })) ); const backdropPortalDirective = bindDirective( portal, computed(() => { const container = container$(); const element = container ? modalTransition.stores.element$() : void 0; return { container, insertBefore: (element == null ? void 0 : element.parentElement) === container ? element : void 0 }; }) ); const registerModalAction$ = readable(void 0, () => modals$.register(res)); const action$ = computed(() => { if (modalTransition.stores.elementPresent$() && !hidden$()) { registerModalAction$(); } modalsAction$(); }); const closeButtonDirective = createAttributesDirective(() => ({ attributes: { type: readable("button"), "aria-label": ariaCloseButtonLabel$ }, events: { click: () => close(modalCloseButtonClick) } })); const backdropAttributeDirective = createAttributesDirective(() => ({ attributes: { class: backdropClass$ } })); const modalAttributeDirective = createAttributesDirective(() => ({ attributes: { class: className$ }, events: { click: (event) => { if (event.currentTarget === event.target && closeOnOutsideClick$()) { close(modalOutsideClick); } } } })); const res = { ...stateStores({ backdropHidden$, container$, hidden$, transitioning$, visible$, modalElement$: modalTransition.stores.element$, ariaCloseButtonLabel$, className$, backdropClass$, ...stateProps }), directives: { modalPortalDirective, backdropPortalDirective, backdropDirective: mergeDirectives(backdropTransition.directives.directive, backdropAttributeDirective), modalDirective: mergeDirectives(modalTransition.directives.directive, siblingsInert, directiveSubscribe(action$), modalAttributeDirective), closeButtonDirective, dialogDirective: bindDirective( browserDirective((dialog, visible) => { const update = (visible2) => { if (visible2) { dialog.showModal(); } else { dialog.close(); } }; update(visible); return { update }; }), visible$ ) }, patch, api: { close, async open() { patch({ visible: true }); await promiseFromStore(hidden$).promise; return hideResult; }, patch } }; return res; }); export { modalCloseButtonClick as a, createModal as c, getModalDefaultConfig as g, modalOutsideClick as m };