/** @jsxImportSource react */ import { Widget, VDOM, getContent } from "../../ui/Widget"; import { Overlay, OverlayBase, OverlayBeacon, OverlayComponent, OverlayComponentProps, OverlayComponentState, OverlayConfig, OverlayInstance, } from "./Overlay"; import { ContentPlaceholder, ContentPlaceholderInstance } from "../../ui/layout/ContentPlaceholder"; import { ZIndexManager } from "../../ui/ZIndexManager"; import { Button } from "../Button"; import { parseStyle } from "../../util/parseStyle"; import { Localization } from "../../ui/Localization"; import { stopPropagation } from "../../util/eventCallbacks"; import { ddMouseDown, ddDetect, ddMouseUp } from "../drag-drop/ops"; import { isDefined } from "../../util/isDefined"; import { isString } from "../../util/isString"; import { BooleanProp, StringProp, StyleProp, ClassProp } from "../../ui/Prop"; import { RenderingContext } from "../../ui/RenderingContext"; export interface WindowConfig extends OverlayConfig { /** Text to be displayed in the header. */ title?: StringProp; /** Controls the close button visibility. Defaults to `true`. */ closable?: BooleanProp; /** A custom style which will be applied to the body. */ bodyStyle?: StyleProp; /** A custom style which will be applied to the header. */ headerStyle?: StyleProp; /** A custom style which will be applied to the footer. */ footerStyle?: StyleProp; /** Base CSS class to be applied to the field. Defaults to `window`. */ baseClass?: string; /** Additional CSS class to be applied to the section body. */ bodyClass?: ClassProp; /** Set to `true` to enable resizing. */ resizable?: boolean; /** Set to `true` to automatically focus the field, after it renders for the first time. */ autoFocus?: boolean; /** Set to `false` to prevent the window itself to be focusable. Default value is true.*/ focusable?: boolean; /** Set to `true` to disable moving the window by dragging the header. */ fixed?: boolean; /** Set to `true` to add default padding to the window body. */ pad?: boolean; /** Optional header widget. */ header?: any; /** Optional footer widget. */ footer?: any; /** Custom event handler for closeable. */ closeable?: BooleanProp; } export class WindowInstance extends OverlayInstance { headerEl?: HTMLElement; footerEl?: HTMLElement; bodyEl?: HTMLElement; containerEl?: HTMLElement; } export class Window extends OverlayBase { declare closable?: BooleanProp; declare closeable?: BooleanProp; declare resizable?: boolean; declare fixed?: boolean; declare autoFocus?: boolean; declare focusable?: boolean; declare pad?: boolean; declare bodyStyle?: StyleProp; declare headerStyle?: StyleProp; declare footerStyle?: StyleProp; declare bodyClass?: ClassProp; declare title?: StringProp; declare header?: any; declare footer?: any; declare baseClass: string; init() { if (isDefined(this.closeable)) this.closable = this.closeable; if (isString(this.headerStyle)) this.headerStyle = parseStyle(this.headerStyle); if (isString(this.footerStyle)) this.footerStyle = parseStyle(this.footerStyle); if (isString(this.bodyStyle)) this.bodyStyle = parseStyle(this.bodyStyle); super.init(); } declareData(...args: any) { return super.declareData(...args, { title: undefined, closable: undefined, bodyStyle: { structured: true }, bodyClass: { structured: true }, headerStyle: { structured: true }, footerStyle: { structured: true }, }); } initHelpers(...args: any) { return super.initHelpers(...args, { header: Widget.create( this.header || { type: ContentPlaceholder, name: "header", scoped: true, }, ), footer: Widget.create( this.footer || { type: ContentPlaceholder, name: "footer", scoped: true, }, ), close: this.closable && Button.create({ mod: "hollow", dismiss: true, icon: "close", style: "margin-left: auto", onTouchStart: stopPropagation, onMouseDown: stopPropagation, }), }); } exploreCleanup(context: RenderingContext, instance: WindowInstance): void { super.exploreCleanup(context, instance); let { helpers } = instance; let unregisterHeader = helpers!.header && (helpers!.header as ContentPlaceholderInstance).unregisterContentPlaceholder; if (unregisterHeader) unregisterHeader(); let unregisterFooter = helpers!.footer && (helpers!.footer as ContentPlaceholderInstance).unregisterContentPlaceholder; if (unregisterFooter) unregisterFooter(); } renderHeader(context: RenderingContext, instance: WindowInstance, key: string): any[] { let { data } = instance; let result = []; if (data.title) result.push(data.title); if (instance.helpers) { let header = getContent(instance.helpers.header && instance.helpers.header.render(context)); if (header) result.push(header); if (data.closable && instance.helpers.close) result.push(getContent(instance.helpers.close.render(context))); } return result; } renderFooter(context: RenderingContext, instance: WindowInstance, key: string): any { return getContent(instance.helpers && instance.helpers.footer && instance.helpers.footer.render(context)); } render(context: RenderingContext, instance: WindowInstance, key: string): any { var header = this.renderHeader(context, instance, "header"); var footer = this.renderFooter(context, instance, "footer"); return ( ( {this.renderContents(context, instance)} )} /> ); } } Window.prototype.baseClass = "window"; Window.prototype.closable = true; Window.prototype.resizable = false; Window.prototype.fixed = false; Window.prototype.autoFocus = true; Window.prototype.focusable = true; Window.prototype.pad = true; Window.prototype.needsBeacon = true; Widget.alias("window", Window); Localization.registerPrototype("cx/widgets/Window", Window); interface WindowComponentProps extends OverlayComponentProps { header: any[]; footer: any; instance: WindowInstance; } interface WindowComponentState extends OverlayComponentState { active?: boolean; } class WindowComponent extends OverlayComponent { headerEl?: HTMLElement | null; footerEl?: HTMLElement | null; bodyEl?: HTMLElement | null; renderOverlayBody() { var { widget, data } = this.props.instance; var { CSS, baseClass, pad } = widget; let header, footer; if (this.props.header.length > 0) { header = (
{ this.headerEl = c; }} className={CSS.element(baseClass, "header")} style={data.headerStyle} onMouseDown={this.onHeaderMouseDown.bind(this)} onMouseUp={ddMouseUp} onMouseMove={this.onHeaderMouseMove.bind(this)} onTouchStart={this.onHeaderMouseDown.bind(this)} onTouchEnd={ddMouseUp} onTouchMove={this.onHeaderMouseMove.bind(this)} > {this.props.header}
); } if (this.props.footer) { footer = (
{ this.footerEl = c; }} className={CSS.element(baseClass, "footer")} style={data.footerStyle} > {this.props.footer}
); } var body = (
{ this.bodyEl = c; }} className={CSS.expand(CSS.element(baseClass, "body", { pad }), data.bodyClass)} style={data.bodyStyle} > {this.props.children}
); return [header, body, footer]; } getOverlayCssClass() { var cls = super.getOverlayCssClass(); if (this.state.active) cls += " cxs-active"; return cls; } onFocusIn() { super.onFocusIn(); if (!this.state.active) { if (this.containerEl?.contains(document.activeElement)) this.setZIndex(ZIndexManager.next()); this.setState({ active: true }); } } onFocusOut() { super.onFocusOut(); if (this.state.active) { this.setState({ active: false, }); } } onHeaderMouseDown(e: any) { e.stopPropagation(); ddMouseDown(e); } onHeaderMouseMove(e: any) { e.stopPropagation(); if (!(this.props.instance.widget as Window).fixed && ddDetect(e)) { this.startMoveOperation(e); } } }