UNPKG

@carbon/ibm-products-web-components

Version:
295 lines (293 loc) 10.8 kB
/** * Copyright IBM Corp. 2020, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ import "../../globals/settings.js"; import { __decorate } from "../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.js"; import coachmark_default$1 from "./coachmark.scss.js"; import { makeDraggable } from "../../utilities/makeDraggable/makeDraggable.js"; import { resetCoachmarkDetailsSignal, updateCoachmarkDetailsSignal } from "./coachmark-context.js"; import { LitElement, html } from "lit"; import { property, state } from "lit/decorators.js"; import HostListenerMixin from "@carbon/web-components/es/globals/mixins/host-listener.js"; import { carbonElement } from "@carbon/web-components/es/globals/decorators/carbon-element.js"; import { SignalWatcher } from "@lit-labs/signals"; import '@carbon/web-components/es-custom/components/popover/index.js'; import { POPOVER_ALIGNMENT } from "@carbon/web-components/es/components/popover/defs.js"; //#region src/components/coachmark/coachmark.ts /** * @license * * Copyright IBM Corp. 2025 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ const blockClass = `c4p--coachmark`; /** * coachmark main component * @element c4p-coachmark * @fires c4p-coachmark-opened * The custom event fired when the coachmark is opened. * This event can be used to perform actions such as focusing elements when the coachmark becomes visible. * @fires c4p-coachmark-closed * The custom event fired when the coachmark is closed. * This event can be used to perform actions such as restoring focus when the coachmark is dismissed. */ let CDSCoachmark = class CDSCoachmark extends SignalWatcher(HostListenerMixin(LitElement)) { constructor(..._args) { super(..._args); this.open = false; this.align = POPOVER_ALIGNMENT.TOP; this.floating = false; this.highContrast = false; this.dropShadow = false; this.caret = false; this.dragAriaLabel = "Picked up the draggable coachmark"; this.dragCleanup = null; this.keydownHandler = null; this.moveCounter = 0; this.isDragging = null; this.moveAnnouncement = ""; this.handlePopoverClosed = () => { if (this.open) this.open = false; }; } disconnectedCallback() { super.disconnectedCallback(); if (this.dragCleanup) this.dragCleanup(); if (this.keydownHandler) document.removeEventListener("keydown", this.keydownHandler); resetCoachmarkDetailsSignal(); } async setupDraggable() { const popoverContent = (this.shadowRoot?.querySelector(`.${blockClass}--popover`)).querySelector("cds-custom-popover-content"); const header = (popoverContent.querySelector(`.${blockClass}--wrapper`).querySelector("slot")?.assignedElements({ flatten: true }))?.find((el) => el.tagName.toLowerCase() === `c4p-coachmark-header`); if (header?.updateComplete) await header.updateComplete; requestAnimationFrame(() => { const dragHandle = header.shadowRoot?.querySelector(`.c4p--coachmark-header-drag-handle`); const dragInactiveInstruction = "Press Enter or Space to activate drag mode."; const dragActiveInstruction = "Use arrow keys to move the coachmark. Press Enter or Space to exit drag mode."; if (dragHandle) dragHandle.setAttribute("aria-label", dragInactiveInstruction); const draggable = makeDraggable({ el: popoverContent, dragHandle: header, focusableDragHandle: dragHandle }); const onDragStart = () => { this.isDragging = true; this.moveCounter = 0; this.moveAnnouncement = ""; popoverContent.classList.add(`${blockClass}--is-dragging`); if (this.dragAriaLabel) popoverContent.setAttribute("aria-label", this.dragAriaLabel); if (dragHandle) dragHandle.setAttribute("aria-label", dragActiveInstruction); updateCoachmarkDetailsSignal({ name: "isDragging", detail: true }); }; const onDragEnd = () => { this.isDragging = false; this.moveCounter = 0; this.moveAnnouncement = ""; popoverContent.classList.remove(`${blockClass}--is-dragging`); popoverContent.removeAttribute("aria-label"); if (dragHandle) dragHandle.setAttribute("aria-label", dragInactiveInstruction); updateCoachmarkDetailsSignal({ name: "isDragging", detail: false }); }; const onDragMove = (event) => { const { direction, distance } = event.detail; this.moveCounter += 1; const uniqueMarker = "​".repeat(this.moveCounter); this.moveAnnouncement = `Moved ${direction} ${distance} pixels${uniqueMarker}`; }; popoverContent.addEventListener("dragstart", onDragStart); popoverContent.addEventListener("dragend", onDragEnd); popoverContent.addEventListener("dragmove", onDragMove); this.dragCleanup = () => { popoverContent.removeEventListener("dragstart", onDragStart); popoverContent.removeEventListener("dragend", onDragEnd); popoverContent.removeEventListener("dragmove", onDragMove); draggable.cleanup?.(); }; }); } firstUpdated() { this.keydownHandler = (event) => { if (event.key === "Escape" && this.open) this.open = false; }; document.addEventListener("keydown", this.keydownHandler); } updated(changedProps) { if (changedProps.has("floating")) if (this.floating) { this.classList.add(`${blockClass}--floating`); this.setupDraggable(); updateCoachmarkDetailsSignal({ name: "floating", detail: this.floating }); } else { this.classList.remove(`${blockClass}--floating`); if (this.dragCleanup) { this.dragCleanup(); this.dragCleanup = null; } updateCoachmarkDetailsSignal({ name: "floating", detail: false }); } if (changedProps.has("position")) { const { x = 0, y = 0 } = this.position ?? {}; if (x !== 0 || y !== 0) this.style.transform = `translate(${x}px, ${y}px)`; } if (changedProps.has("open")) { const init = { bubbles: true, composed: true, detail: { open: this.open } }; if (this.open) { this.dispatchEvent(new CustomEvent(this.constructor.eventOpen, init)); updateCoachmarkDetailsSignal({ name: "floating", detail: this.floating ?? false }); if (this.floating) { if (this.dragCleanup) { this.dragCleanup(); this.dragCleanup = null; } this.setupDraggable(); } setTimeout(() => { requestAnimationFrame(() => { let elementToFocus = null; if (this.selectorPrimaryFocus) { const raw = this.selectorPrimaryFocus.trim(); const selector = raw.startsWith("#") || raw.startsWith(".") || raw.startsWith("[") ? raw : `.${raw}`; const found = document.querySelector(selector); elementToFocus = found?.shadowRoot?.querySelector("button, [tabindex]") ?? found; } if (!elementToFocus) { const header = (this.shadowRoot?.querySelector(`.${blockClass}--popover cds-custom-popover-content .${blockClass}--wrapper`))?.querySelector("slot")?.assignedElements({ flatten: true })?.find((el) => el.tagName.toLowerCase() === `c4p-coachmark-header`); if (header?.shadowRoot) { const dragButton = this.floating ? header.shadowRoot.querySelector(`.c4p--coachmark-header-drag-handle`) : null; const closeButton = header.shadowRoot.querySelector(`.c4p--coachmark-header-close-button`); elementToFocus = dragButton || closeButton; } } if (elementToFocus) elementToFocus.focus(); }); }, 100); } else { this.dispatchEvent(new CustomEvent(this.constructor.eventClose, init)); updateCoachmarkDetailsSignal({ name: "floating", detail: false }); requestAnimationFrame(() => { ((this.shadowRoot?.querySelector("slot[name=\"trigger\"]"))?.assignedElements({ flatten: true })[0])?.focus(); }); } } } render() { const caretValue = this.caret !== void 0 ? this.caret : this.floating === true ? false : true; return html` <cds-custom-popover part="popover" exportparts="popover-container" class="${blockClass}--popover" ?open=${this.open} .caret=${caretValue} ?highContrast=${this.highContrast} align=${this.align} .dropShadow=${this.dropShadow} @cds-custom-popover-closed=${this.handlePopoverClosed} > <slot name="trigger"></slot> <cds-custom-popover-content part="popover-content" exportparts="content"> <div role="status" aria-live="polite" aria-atomic="true" class="${"c4p"}--visually-hidden" > ${this.floating ? this.isDragging === true ? "Drag mode active." : this.isDragging === false ? "Drag mode ended." : "" : ""} </div> <div role="status" aria-live="assertive" aria-atomic="true" class="${"c4p"}--visually-hidden" > ${this.floating ? this.moveAnnouncement : ""} </div> <div class="${blockClass}--wrapper"> <slot></slot> </div> </cds-custom-popover-content> </cds-custom-popover> `; } /** * The name of the custom event fired when this coachmark is opened. */ static get eventOpen() { return `c4p-coachmark-opened`; } /** * The name of the custom event fired when this coachmark is closed. */ static get eventClose() { return `c4p-coachmark-closed`; } static { this.styles = coachmark_default$1; } }; __decorate([property({ type: Boolean, reflect: true })], CDSCoachmark.prototype, "open", void 0); __decorate([property({ reflect: true, type: String })], CDSCoachmark.prototype, "align", void 0); __decorate([property({ type: Object })], CDSCoachmark.prototype, "position", void 0); __decorate([property({ type: Boolean, reflect: true })], CDSCoachmark.prototype, "floating", void 0); __decorate([property({ type: Boolean, reflect: true })], CDSCoachmark.prototype, "highContrast", void 0); __decorate([property({ type: Boolean, reflect: true })], CDSCoachmark.prototype, "dropShadow", void 0); __decorate([property({ type: Boolean, reflect: true })], CDSCoachmark.prototype, "caret", void 0); __decorate([property({ attribute: "selector-primary-focus", reflect: true })], CDSCoachmark.prototype, "selectorPrimaryFocus", void 0); __decorate([property({ attribute: "drag-aria-label", reflect: true })], CDSCoachmark.prototype, "dragAriaLabel", void 0); __decorate([state()], CDSCoachmark.prototype, "isDragging", void 0); __decorate([state()], CDSCoachmark.prototype, "moveAnnouncement", void 0); CDSCoachmark = __decorate([carbonElement(`c4p-coachmark`)], CDSCoachmark); var coachmark_default = CDSCoachmark; //#endregion export { blockClass, coachmark_default as default }; //# sourceMappingURL=coachmark.js.map