import { Arr, type Optional } from '@ephox/katamari'; import { Attribute, Css, type SugarElement } from '@ephox/sugar'; import { Positioning } from '../../api/behaviour/Positioning'; import type { AlloyComponent } from '../../api/component/ComponentApi'; import type { AlloySpec } from '../../api/component/SpecTypes'; import * as Attachment from '../../api/system/Attachment'; import type { SandboxingConfig, SandboxingState } from './SandboxingTypes'; // NOTE: A sandbox should not start as part of the world. It is expected to be // added to the sink on rebuild. const rebuild = (sandbox: AlloyComponent, sConfig: SandboxingConfig, sState: SandboxingState, data: AlloySpec): AlloyComponent => { sState.get().each((_data) => { // If currently has data, so it hasn't been removed yet. It is // being "re-opened" Attachment.detachChildren(sandbox); }); const point = sConfig.getAttachPoint(sandbox); Attachment.attach(point, sandbox); // Must be after the sandbox is in the system const built = sandbox.getSystem().build(data); Attachment.attach(sandbox, built); sState.set(built); return built; }; // Open sandbox transfers focus to the opened menu const open = (sandbox: AlloyComponent, sConfig: SandboxingConfig, sState: SandboxingState, data: AlloySpec): AlloyComponent => { const newState = rebuild(sandbox, sConfig, sState, data); sConfig.onOpen(sandbox, newState); return newState; }; const setContent = (sandbox: AlloyComponent, sConfig: SandboxingConfig, sState: SandboxingState, data: AlloySpec): Optional => sState.get().map(() => rebuild(sandbox, sConfig, sState, data)); // TODO AP-191 write a test for openWhileCloaked const openWhileCloaked = (sandbox: AlloyComponent, sConfig: SandboxingConfig, sState: SandboxingState, data: AlloySpec, transaction: () => void): void => { cloak(sandbox, sConfig, sState); open(sandbox, sConfig, sState, data); transaction(); decloak(sandbox, sConfig, sState); }; const close = (sandbox: AlloyComponent, sConfig: SandboxingConfig, sState: SandboxingState): void => { sState.get().each((data) => { Attachment.detachChildren(sandbox); Attachment.detach(sandbox); sConfig.onClose(sandbox, data); sState.clear(); }); }; const isOpen = (_sandbox: AlloyComponent, _sConfig: SandboxingConfig, sState: SandboxingState): boolean => sState.isOpen(); const isPartOf = (sandbox: AlloyComponent, sConfig: SandboxingConfig, sState: SandboxingState, queryElem: SugarElement): boolean => isOpen(sandbox, sConfig, sState) && sState.get().exists( (data) => sConfig.isPartOf(sandbox, data, queryElem) ); const getState = (_sandbox: AlloyComponent, _sConfig: SandboxingConfig, sState: SandboxingState): Optional => sState.get(); const store = (sandbox: AlloyComponent, cssKey: string, attr: string, newValue: string): void => { Css.getRaw(sandbox.element, cssKey).fold(() => { Attribute.remove(sandbox.element, attr); }, (v) => { Attribute.set(sandbox.element, attr, v); }); Css.set(sandbox.element, cssKey, newValue); }; const restore = (sandbox: AlloyComponent, cssKey: string, attr: string): void => { Attribute.getOpt(sandbox.element, attr).fold( () => Css.remove(sandbox.element, cssKey), (oldValue) => Css.set(sandbox.element, cssKey, oldValue) ); }; const cloak = (sandbox: AlloyComponent, sConfig: SandboxingConfig, _sState: SandboxingState): void => { const sink = sConfig.getAttachPoint(sandbox); // Use the positioning mode of the sink, so that it does not interfere with the sink's positioning // We add it here to stop it causing layout problems. Css.set(sandbox.element, 'position', Positioning.getMode(sink)); store(sandbox, 'visibility', sConfig.cloakVisibilityAttr, 'hidden'); }; const hasPosition = (element: SugarElement): boolean => Arr.exists( [ 'top', 'left', 'right', 'bottom' ], (pos) => Css.getRaw(element, pos).isSome() ); const decloak = (sandbox: AlloyComponent, sConfig: SandboxingConfig, _sState: SandboxingState): void => { if (!hasPosition(sandbox.element)) { // If a position value was not added to the sandbox during cloaking, remove it // otherwise certain position values (absolute, relative) will impact the child that _was_ positioned Css.remove(sandbox.element, 'position'); } restore(sandbox, 'visibility', sConfig.cloakVisibilityAttr); }; export { cloak, decloak, open, openWhileCloaked, close, isOpen, isPartOf, getState, setContent };