@qaiddev/thumbs-embed 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -12
- package/dist/bootstrap.d.ts +4 -2
- package/dist/chunks/{loader-DHtXHt1z.js → loader-XbJ2b8KE.js} +204 -158
- package/dist/chunks/{loader-DHtXHt1z.js.map → loader-XbJ2b8KE.js.map} +1 -1
- package/dist/chunks/{modal-CTbqN-h9.js → modal-XxYobTKE.js} +2 -2
- package/dist/chunks/{modal-CTbqN-h9.js.map → modal-XxYobTKE.js.map} +1 -1
- package/dist/index.js +381 -335
- package/dist/index.js.map +1 -1
- package/dist/loader.js +1 -1
- package/dist/qaid.umd.cjs +14 -14
- package/dist/qaid.umd.cjs.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as u } from "./loader-
|
|
1
|
+
import { c as u } from "./loader-XbJ2b8KE.js";
|
|
2
2
|
import { h as i } from "./dom-DvdtcUS8.js";
|
|
3
3
|
import { T as r, a as h, F as b, D as f, E as g } from "./annotate-D-o8xCV9.js";
|
|
4
4
|
class v {
|
|
@@ -210,4 +210,4 @@ class v {
|
|
|
210
210
|
export {
|
|
211
211
|
v as ModalController
|
|
212
212
|
};
|
|
213
|
-
//# sourceMappingURL=modal-
|
|
213
|
+
//# sourceMappingURL=modal-XxYobTKE.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modal-CTbqN-h9.js","sources":["../../src/modal.ts"],"sourcesContent":["/**\n * Feedback message modal — extracted from embed.ts into its own lazily-loaded\n * chunk. It's fetched the first time a message modal opens (after feedback is\n * submitted), and pre-warmed while the user is targeting an element.\n *\n * The controller owns the modal/backdrop DOM and the message PATCH flow, and\n * talks back to the embed through the narrow `ModalHost` interface.\n */\n\nimport { calculateModalAndArrowPosition } from \"./modal-positioning\";\nimport { h } from \"./dom\";\nimport {\n THUMBS_UP_ICON,\n THUMBS_DOWN_ICON,\n FEEDBACK_ICON,\n DONE_ICON,\n ERROR_ICON,\n} from \"./icons\";\nimport type {\n ResolvedFeedbackConfig,\n EmbedState,\n FeedbackData,\n SelectedBounds,\n} from \"./types\";\n\n/** The slice of the embed the modal needs. */\nexport interface ModalHost {\n readonly config: ResolvedFeedbackConfig;\n readonly uid: string;\n readonly isMobile: boolean;\n readonly state: EmbedState;\n readonly boundKeyDown: (e: KeyboardEvent) => void;\n /** Live references — the toggle mutates feedbackType; positioning reads bounds. */\n readonly feedbackData: FeedbackData;\n readonly selectedBounds: SelectedBounds;\n readonly feedbackId: number | null;\n setFeedbackId(id: number | null): void;\n ensureOverlayHost(): ShadowRoot;\n applyVars(el: HTMLElement): void;\n announceMsg(message: string, assertive?: boolean): void;\n openDialogA11y(\n container: HTMLElement,\n opts: { labelledbyId?: string; describedbyId?: string; label?: string }\n ): void;\n closeDialogA11y(): void;\n resetFeedbackUi(): void;\n}\n\nexport class ModalController {\n private modalContainer: HTMLDivElement | null = null;\n private backdrop: HTMLDivElement | null = null;\n\n constructor(private host: ModalHost) {}\n\n open(): void {\n const root = this.host.ensureOverlayHost();\n\n // Create backdrop\n this.backdrop = document.createElement(\"div\");\n this.backdrop.className = \"qaid-backdrop\";\n this.backdrop.style.zIndex = String(this.host.config.zIndex + 2);\n this.backdrop.style.background = `rgba(0, 0, 0, ${this.host.config.backdropOpacity})`;\n this.backdrop.addEventListener(\"click\", () => this.close());\n this.host.applyVars(this.backdrop);\n\n if (this.host.isMobile) {\n this.showBottomSheet();\n } else {\n this.showPositionedModal();\n }\n\n root.appendChild(this.backdrop);\n document.addEventListener(\"keydown\", this.host.boundKeyDown);\n }\n\n /** Escape / external close. Runs the finalize-PATCH + teardown. */\n close(): void {\n // If the modal is open and we're closing without submitting, still finalize.\n if (this.host.state === \"MODAL_OPEN\" && this.host.feedbackId) {\n fetch(`${this.host.config.endpoint}/${this.host.feedbackId}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ message: null }),\n }).catch((err) => console.error(\"Failed to finalize feedback:\", err));\n }\n\n this.teardown();\n this.host.resetFeedbackUi();\n }\n\n /** DOM/listener teardown only — no PATCH, no state reset (for embed destroy). */\n destroy(): void {\n this.teardown();\n }\n\n private teardown(): void {\n this.host.closeDialogA11y();\n if (this.modalContainer) {\n this.modalContainer.remove();\n this.modalContainer = null;\n }\n if (this.backdrop) {\n this.backdrop.remove();\n this.backdrop = null;\n }\n document.removeEventListener(\"keydown\", this.host.boundKeyDown);\n }\n\n private showBottomSheet(): void {\n const root = this.host.ensureOverlayHost();\n\n const sheet = document.createElement(\"div\");\n sheet.className = \"qaid-bottom-sheet\";\n sheet.style.zIndex = String(this.host.config.zIndex + 3);\n\n sheet.appendChild(\n h(\n \"div\",\n { class: \"qaid-bottom-sheet-content\" },\n h(\"div\", { class: \"qaid-bottom-sheet-handle\" }),\n this.buildModalContent()\n )\n );\n\n this.host.applyVars(sheet);\n root.appendChild(sheet);\n this.modalContainer = sheet;\n\n this.setupModalInteractions();\n }\n\n private showPositionedModal(): void {\n const root = this.host.ensureOverlayHost();\n\n const { modal, arrow } = calculateModalAndArrowPosition(\n this.host.selectedBounds,\n window.innerWidth,\n window.innerHeight,\n {\n width: this.host.config.modalWidth,\n height: 280,\n arrowHeight: 12,\n gap: 8,\n viewportPadding: 16,\n }\n );\n\n this.modalContainer = document.createElement(\"div\");\n this.modalContainer.className = `qaid-modal-container qaid-${modal.position}`;\n this.modalContainer.style.top = `${modal.top}px`;\n this.modalContainer.style.left = `${modal.left}px`;\n this.modalContainer.style.zIndex = String(this.host.config.zIndex + 3);\n\n const arrowEl = document.createElement(\"div\");\n arrowEl.className = \"qaid-modal-arrow\";\n arrowEl.style.left = `${arrow.left}px`;\n\n const box = document.createElement(\"div\");\n box.className = \"qaid-modal-box\";\n box.appendChild(this.buildModalContent());\n\n this.modalContainer.appendChild(arrowEl);\n this.modalContainer.appendChild(box);\n this.host.applyVars(this.modalContainer);\n root.appendChild(this.modalContainer);\n\n this.setupModalInteractions();\n }\n\n /**\n * The modal's contents, built as nodes rather than an HTML string.\n *\n * Every piece of copy (title, subtitle, placeholder, button) goes in as\n * text, so a `<`, `&` or `\"` in a translation shows as written instead of\n * being parsed as markup or cutting an attribute short. Only the icons are\n * markup, because they are documented as SVG/HTML strings.\n */\n private buildModalContent(): DocumentFragment {\n const { config, uid } = this.host;\n const text = config.text;\n const type = this.host.feedbackData.feedbackType;\n const isUp = type === \"up\";\n const positiveIcon = config.positiveIcon || THUMBS_UP_ICON;\n const negativeIcon = config.negativeIcon || THUMBS_DOWN_ICON;\n const toggleClass = config.buttonClass\n ? `qaid-type-toggle qaid-type-toggle-custom ${config.buttonClass} ${isUp ? \"qaid-btn-up\" : \"qaid-btn-down\"}`\n : `qaid-type-toggle ${isUp ? \"qaid-type-up\" : \"qaid-type-down\"}`;\n const toggleLabel = isUp ? \"Feedback type: positive\" : \"Feedback type: negative\";\n // Neutral (single-button) feedback has no sentiment to toggle — show a\n // static feedback icon in place of the up/down toggle.\n const header =\n type === \"neutral\"\n ? h(\"span\", {\n class: \"qaid-type-static\",\n html: config.feedbackIcon || FEEDBACK_ICON,\n attrs: { \"aria-hidden\": \"true\" },\n })\n : h(\"button\", {\n class: toggleClass,\n html: isUp ? positiveIcon : negativeIcon,\n attrs: {\n type: \"button\",\n title: \"Click to switch\",\n \"aria-pressed\": String(isUp),\n \"aria-label\": toggleLabel,\n },\n });\n\n const fragment = document.createDocumentFragment();\n fragment.append(\n h(\n \"div\",\n { class: \"qaid-modal-header\" },\n header,\n h(\n \"div\",\n { class: \"qaid-modal-header-text\" },\n h(\"h3\", {\n class: \"qaid-modal-title\",\n text: text.modalTitle,\n attrs: { id: `qaid-modal-title-${uid}` },\n }),\n h(\"p\", {\n class: \"qaid-modal-subtitle\",\n text: text.modalSubtitle,\n attrs: { id: `qaid-modal-subtitle-${uid}` },\n })\n )\n ),\n h(\"textarea\", {\n class: \"qaid-textarea\",\n attrs: { \"aria-label\": text.modalSubtitle, placeholder: text.placeholder },\n }),\n h(\n \"div\",\n { class: \"qaid-btn-row\" },\n h(\"button\", {\n class: \"qaid-btn-submit\",\n text: text.skipButton,\n attrs: { type: \"button\" },\n })\n )\n );\n return fragment;\n }\n\n private setupModalInteractions(): void {\n // Dialog semantics + focus trap + background inert\n this.host.openDialogA11y(this.modalContainer!, {\n labelledbyId: `qaid-modal-title-${this.host.uid}`,\n describedbyId: `qaid-modal-subtitle-${this.host.uid}`,\n });\n\n const textarea =\n this.modalContainer!.querySelector<HTMLTextAreaElement>(\".qaid-textarea\");\n const submitBtn =\n this.modalContainer!.querySelector<HTMLButtonElement>(\".qaid-btn-submit\");\n if (textarea) {\n // Auto-focus\n setTimeout(() => textarea.focus(), 100);\n\n // Update button text based on content\n textarea.addEventListener(\"input\", () => {\n if (submitBtn) {\n submitBtn.textContent = textarea.value.trim()\n ? this.host.config.text.submitButton\n : this.host.config.text.skipButton;\n }\n });\n }\n\n if (submitBtn) {\n submitBtn.addEventListener(\"click\", () => {\n const message = textarea?.value.trim() || null;\n this.submitMessage(message);\n });\n }\n\n // Handle feedback type toggle\n const typeToggle = this.modalContainer!.querySelector<HTMLButtonElement>(\".qaid-type-toggle\");\n if (typeToggle) {\n typeToggle.addEventListener(\"click\", () => {\n const newType = this.host.feedbackData.feedbackType === \"up\" ? \"down\" : \"up\";\n this.host.feedbackData.feedbackType = newType;\n\n // Update UI — use different classes depending on custom buttonClass\n if (this.host.config.buttonClass) {\n typeToggle.classList.toggle(\"qaid-btn-up\", newType === \"up\");\n typeToggle.classList.toggle(\"qaid-btn-down\", newType === \"down\");\n } else {\n typeToggle.classList.toggle(\"qaid-type-up\", newType === \"up\");\n typeToggle.classList.toggle(\"qaid-type-down\", newType === \"down\");\n }\n const positiveIcon = this.host.config.positiveIcon || THUMBS_UP_ICON;\n const negativeIcon = this.host.config.negativeIcon || THUMBS_DOWN_ICON;\n typeToggle.innerHTML = newType === \"up\" ? positiveIcon : negativeIcon;\n\n // Update accessible state and announce the change\n const toggleLabel = newType === \"up\" ? \"Feedback type: positive\" : \"Feedback type: negative\";\n typeToggle.setAttribute(\"aria-pressed\", String(newType === \"up\"));\n typeToggle.setAttribute(\"aria-label\", toggleLabel);\n this.host.announceMsg(toggleLabel);\n\n // Update feedback on server\n if (this.host.feedbackId) {\n fetch(`${this.host.config.endpoint}/${this.host.feedbackId}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ feedbackType: newType }),\n }).catch((err) => console.error(\"Failed to update feedback type:\", err));\n }\n });\n }\n }\n\n private async submitMessage(message: string | null): Promise<void> {\n // null = nothing was attempted (the initial POST never produced an id);\n // otherwise whether the server accepted the PATCH.\n let delivered: boolean | null = null;\n\n if (this.host.feedbackId) {\n try {\n const response = await fetch(`${this.host.config.endpoint}/${this.host.feedbackId}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ message }),\n });\n // fetch resolves on any HTTP reply. A 4xx/5xx means the server\n // refused or lost the message, so it is not a success.\n delivered = response.ok;\n if (!response.ok) {\n console.error(\"Failed to submit feedback message: HTTP\", response.status);\n }\n } catch (error) {\n console.error(\"Failed to submit feedback message:\", error);\n delivered = false;\n }\n // Clear feedbackId so close() doesn't send another PATCH.\n this.host.setFeedbackId(null);\n }\n\n // The message was lost. Closing quietly reads as success, so say so —\n // even with hideConfirmation, which only opts out of the success screen.\n if (delivered === false) {\n this.showResult(false);\n return;\n }\n\n // A submit that simply removes the modal reads as a failure — nothing\n // acknowledges that the message was sent. Show a success screen unless the\n // host opted out.\n //\n // With no feedbackId the initial POST never landed and there was nothing\n // to send the message to; that path closes exactly as it did before.\n if (delivered && !this.host.config.hideConfirmation) {\n this.showResult(true);\n return;\n }\n this.close();\n }\n\n /**\n * Replace the modal's contents with the outcome: a checkmark and a short\n * acknowledgement, or an error mark and a \"not sent\" notice.\n *\n * The submit button that had focus is gone by this point, so focus moves to\n * the heading (WCAG 2.4.3) and the message is announced. Mirrors the quests\n * embed's thank-you screen so the two products confirm the same way. Copy\n * goes in as text, never markup.\n */\n private showResult(delivered: boolean): void {\n const box = this.modalContainer?.querySelector(\".qaid-modal-box\")\n ?? this.modalContainer?.querySelector(\".qaid-bottom-sheet-content\")\n ?? this.modalContainer;\n\n if (!box) {\n // Nothing to draw into — never strand the user with an open modal.\n this.close();\n return;\n }\n\n const text = this.host.config.text;\n const heading = delivered ? text.confirmationTitle : text.errorTitle;\n const body = delivered ? text.confirmationMessage : text.errorMessage;\n\n const title = h(\"h3\", {\n class: \"qaid-confirm-title\",\n text: heading,\n attrs: { id: `qaid-confirm-title-${this.host.uid}`, tabindex: \"-1\" },\n });\n\n box.textContent = \"\";\n box.appendChild(\n h(\n \"div\",\n { class: delivered ? \"qaid-confirm\" : \"qaid-confirm qaid-confirm-error\" },\n h(\"span\", {\n class: \"qaid-confirm-icon\",\n html: delivered ? DONE_ICON : ERROR_ICON,\n attrs: { \"aria-hidden\": \"true\" },\n }),\n title,\n h(\"p\", { class: \"qaid-confirm-message\", text: body }),\n h(\"button\", {\n class: \"qaid-btn-submit qaid-confirm-close\",\n text: text.confirmationClose,\n attrs: { type: \"button\" },\n on: { click: () => this.close() },\n })\n )\n );\n\n // Focus after paint, matching the quests embed.\n requestAnimationFrame(() => title.focus());\n this.host.announceMsg(`${heading} ${body}`, true);\n }\n}\n"],"names":["ModalController","host","root","err","sheet","h","modal","arrow","calculateModalAndArrowPosition","arrowEl","box","config","uid","text","type","isUp","positiveIcon","THUMBS_UP_ICON","negativeIcon","THUMBS_DOWN_ICON","toggleClass","toggleLabel","header","FEEDBACK_ICON","fragment","textarea","submitBtn","message","typeToggle","newType","delivered","response","error","heading","body","title","DONE_ICON","ERROR_ICON"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"modal-XxYobTKE.js","sources":["../../src/modal.ts"],"sourcesContent":["/**\n * Feedback message modal — extracted from embed.ts into its own lazily-loaded\n * chunk. It's fetched the first time a message modal opens (after feedback is\n * submitted), and pre-warmed while the user is targeting an element.\n *\n * The controller owns the modal/backdrop DOM and the message PATCH flow, and\n * talks back to the embed through the narrow `ModalHost` interface.\n */\n\nimport { calculateModalAndArrowPosition } from \"./modal-positioning\";\nimport { h } from \"./dom\";\nimport {\n THUMBS_UP_ICON,\n THUMBS_DOWN_ICON,\n FEEDBACK_ICON,\n DONE_ICON,\n ERROR_ICON,\n} from \"./icons\";\nimport type {\n ResolvedFeedbackConfig,\n EmbedState,\n FeedbackData,\n SelectedBounds,\n} from \"./types\";\n\n/** The slice of the embed the modal needs. */\nexport interface ModalHost {\n readonly config: ResolvedFeedbackConfig;\n readonly uid: string;\n readonly isMobile: boolean;\n readonly state: EmbedState;\n readonly boundKeyDown: (e: KeyboardEvent) => void;\n /** Live references — the toggle mutates feedbackType; positioning reads bounds. */\n readonly feedbackData: FeedbackData;\n readonly selectedBounds: SelectedBounds;\n readonly feedbackId: number | null;\n setFeedbackId(id: number | null): void;\n ensureOverlayHost(): ShadowRoot;\n applyVars(el: HTMLElement): void;\n announceMsg(message: string, assertive?: boolean): void;\n openDialogA11y(\n container: HTMLElement,\n opts: { labelledbyId?: string; describedbyId?: string; label?: string }\n ): void;\n closeDialogA11y(): void;\n resetFeedbackUi(): void;\n}\n\nexport class ModalController {\n private modalContainer: HTMLDivElement | null = null;\n private backdrop: HTMLDivElement | null = null;\n\n constructor(private host: ModalHost) {}\n\n open(): void {\n const root = this.host.ensureOverlayHost();\n\n // Create backdrop\n this.backdrop = document.createElement(\"div\");\n this.backdrop.className = \"qaid-backdrop\";\n this.backdrop.style.zIndex = String(this.host.config.zIndex + 2);\n this.backdrop.style.background = `rgba(0, 0, 0, ${this.host.config.backdropOpacity})`;\n this.backdrop.addEventListener(\"click\", () => this.close());\n this.host.applyVars(this.backdrop);\n\n if (this.host.isMobile) {\n this.showBottomSheet();\n } else {\n this.showPositionedModal();\n }\n\n root.appendChild(this.backdrop);\n document.addEventListener(\"keydown\", this.host.boundKeyDown);\n }\n\n /** Escape / external close. Runs the finalize-PATCH + teardown. */\n close(): void {\n // If the modal is open and we're closing without submitting, still finalize.\n if (this.host.state === \"MODAL_OPEN\" && this.host.feedbackId) {\n fetch(`${this.host.config.endpoint}/${this.host.feedbackId}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ message: null }),\n }).catch((err) => console.error(\"Failed to finalize feedback:\", err));\n }\n\n this.teardown();\n this.host.resetFeedbackUi();\n }\n\n /** DOM/listener teardown only — no PATCH, no state reset (for embed destroy). */\n destroy(): void {\n this.teardown();\n }\n\n private teardown(): void {\n this.host.closeDialogA11y();\n if (this.modalContainer) {\n this.modalContainer.remove();\n this.modalContainer = null;\n }\n if (this.backdrop) {\n this.backdrop.remove();\n this.backdrop = null;\n }\n document.removeEventListener(\"keydown\", this.host.boundKeyDown);\n }\n\n private showBottomSheet(): void {\n const root = this.host.ensureOverlayHost();\n\n const sheet = document.createElement(\"div\");\n sheet.className = \"qaid-bottom-sheet\";\n sheet.style.zIndex = String(this.host.config.zIndex + 3);\n\n sheet.appendChild(\n h(\n \"div\",\n { class: \"qaid-bottom-sheet-content\" },\n h(\"div\", { class: \"qaid-bottom-sheet-handle\" }),\n this.buildModalContent()\n )\n );\n\n this.host.applyVars(sheet);\n root.appendChild(sheet);\n this.modalContainer = sheet;\n\n this.setupModalInteractions();\n }\n\n private showPositionedModal(): void {\n const root = this.host.ensureOverlayHost();\n\n const { modal, arrow } = calculateModalAndArrowPosition(\n this.host.selectedBounds,\n window.innerWidth,\n window.innerHeight,\n {\n width: this.host.config.modalWidth,\n height: 280,\n arrowHeight: 12,\n gap: 8,\n viewportPadding: 16,\n }\n );\n\n this.modalContainer = document.createElement(\"div\");\n this.modalContainer.className = `qaid-modal-container qaid-${modal.position}`;\n this.modalContainer.style.top = `${modal.top}px`;\n this.modalContainer.style.left = `${modal.left}px`;\n this.modalContainer.style.zIndex = String(this.host.config.zIndex + 3);\n\n const arrowEl = document.createElement(\"div\");\n arrowEl.className = \"qaid-modal-arrow\";\n arrowEl.style.left = `${arrow.left}px`;\n\n const box = document.createElement(\"div\");\n box.className = \"qaid-modal-box\";\n box.appendChild(this.buildModalContent());\n\n this.modalContainer.appendChild(arrowEl);\n this.modalContainer.appendChild(box);\n this.host.applyVars(this.modalContainer);\n root.appendChild(this.modalContainer);\n\n this.setupModalInteractions();\n }\n\n /**\n * The modal's contents, built as nodes rather than an HTML string.\n *\n * Every piece of copy (title, subtitle, placeholder, button) goes in as\n * text, so a `<`, `&` or `\"` in a translation shows as written instead of\n * being parsed as markup or cutting an attribute short. Only the icons are\n * markup, because they are documented as SVG/HTML strings.\n */\n private buildModalContent(): DocumentFragment {\n const { config, uid } = this.host;\n const text = config.text;\n const type = this.host.feedbackData.feedbackType;\n const isUp = type === \"up\";\n const positiveIcon = config.positiveIcon || THUMBS_UP_ICON;\n const negativeIcon = config.negativeIcon || THUMBS_DOWN_ICON;\n const toggleClass = config.buttonClass\n ? `qaid-type-toggle qaid-type-toggle-custom ${config.buttonClass} ${isUp ? \"qaid-btn-up\" : \"qaid-btn-down\"}`\n : `qaid-type-toggle ${isUp ? \"qaid-type-up\" : \"qaid-type-down\"}`;\n const toggleLabel = isUp ? \"Feedback type: positive\" : \"Feedback type: negative\";\n // Neutral (single-button) feedback has no sentiment to toggle — show a\n // static feedback icon in place of the up/down toggle.\n const header =\n type === \"neutral\"\n ? h(\"span\", {\n class: \"qaid-type-static\",\n html: config.feedbackIcon || FEEDBACK_ICON,\n attrs: { \"aria-hidden\": \"true\" },\n })\n : h(\"button\", {\n class: toggleClass,\n html: isUp ? positiveIcon : negativeIcon,\n attrs: {\n type: \"button\",\n title: \"Click to switch\",\n \"aria-pressed\": String(isUp),\n \"aria-label\": toggleLabel,\n },\n });\n\n const fragment = document.createDocumentFragment();\n fragment.append(\n h(\n \"div\",\n { class: \"qaid-modal-header\" },\n header,\n h(\n \"div\",\n { class: \"qaid-modal-header-text\" },\n h(\"h3\", {\n class: \"qaid-modal-title\",\n text: text.modalTitle,\n attrs: { id: `qaid-modal-title-${uid}` },\n }),\n h(\"p\", {\n class: \"qaid-modal-subtitle\",\n text: text.modalSubtitle,\n attrs: { id: `qaid-modal-subtitle-${uid}` },\n })\n )\n ),\n h(\"textarea\", {\n class: \"qaid-textarea\",\n attrs: { \"aria-label\": text.modalSubtitle, placeholder: text.placeholder },\n }),\n h(\n \"div\",\n { class: \"qaid-btn-row\" },\n h(\"button\", {\n class: \"qaid-btn-submit\",\n text: text.skipButton,\n attrs: { type: \"button\" },\n })\n )\n );\n return fragment;\n }\n\n private setupModalInteractions(): void {\n // Dialog semantics + focus trap + background inert\n this.host.openDialogA11y(this.modalContainer!, {\n labelledbyId: `qaid-modal-title-${this.host.uid}`,\n describedbyId: `qaid-modal-subtitle-${this.host.uid}`,\n });\n\n const textarea =\n this.modalContainer!.querySelector<HTMLTextAreaElement>(\".qaid-textarea\");\n const submitBtn =\n this.modalContainer!.querySelector<HTMLButtonElement>(\".qaid-btn-submit\");\n if (textarea) {\n // Auto-focus\n setTimeout(() => textarea.focus(), 100);\n\n // Update button text based on content\n textarea.addEventListener(\"input\", () => {\n if (submitBtn) {\n submitBtn.textContent = textarea.value.trim()\n ? this.host.config.text.submitButton\n : this.host.config.text.skipButton;\n }\n });\n }\n\n if (submitBtn) {\n submitBtn.addEventListener(\"click\", () => {\n const message = textarea?.value.trim() || null;\n this.submitMessage(message);\n });\n }\n\n // Handle feedback type toggle\n const typeToggle = this.modalContainer!.querySelector<HTMLButtonElement>(\".qaid-type-toggle\");\n if (typeToggle) {\n typeToggle.addEventListener(\"click\", () => {\n const newType = this.host.feedbackData.feedbackType === \"up\" ? \"down\" : \"up\";\n this.host.feedbackData.feedbackType = newType;\n\n // Update UI — use different classes depending on custom buttonClass\n if (this.host.config.buttonClass) {\n typeToggle.classList.toggle(\"qaid-btn-up\", newType === \"up\");\n typeToggle.classList.toggle(\"qaid-btn-down\", newType === \"down\");\n } else {\n typeToggle.classList.toggle(\"qaid-type-up\", newType === \"up\");\n typeToggle.classList.toggle(\"qaid-type-down\", newType === \"down\");\n }\n const positiveIcon = this.host.config.positiveIcon || THUMBS_UP_ICON;\n const negativeIcon = this.host.config.negativeIcon || THUMBS_DOWN_ICON;\n typeToggle.innerHTML = newType === \"up\" ? positiveIcon : negativeIcon;\n\n // Update accessible state and announce the change\n const toggleLabel = newType === \"up\" ? \"Feedback type: positive\" : \"Feedback type: negative\";\n typeToggle.setAttribute(\"aria-pressed\", String(newType === \"up\"));\n typeToggle.setAttribute(\"aria-label\", toggleLabel);\n this.host.announceMsg(toggleLabel);\n\n // Update feedback on server\n if (this.host.feedbackId) {\n fetch(`${this.host.config.endpoint}/${this.host.feedbackId}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ feedbackType: newType }),\n }).catch((err) => console.error(\"Failed to update feedback type:\", err));\n }\n });\n }\n }\n\n private async submitMessage(message: string | null): Promise<void> {\n // null = nothing was attempted (the initial POST never produced an id);\n // otherwise whether the server accepted the PATCH.\n let delivered: boolean | null = null;\n\n if (this.host.feedbackId) {\n try {\n const response = await fetch(`${this.host.config.endpoint}/${this.host.feedbackId}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ message }),\n });\n // fetch resolves on any HTTP reply. A 4xx/5xx means the server\n // refused or lost the message, so it is not a success.\n delivered = response.ok;\n if (!response.ok) {\n console.error(\"Failed to submit feedback message: HTTP\", response.status);\n }\n } catch (error) {\n console.error(\"Failed to submit feedback message:\", error);\n delivered = false;\n }\n // Clear feedbackId so close() doesn't send another PATCH.\n this.host.setFeedbackId(null);\n }\n\n // The message was lost. Closing quietly reads as success, so say so —\n // even with hideConfirmation, which only opts out of the success screen.\n if (delivered === false) {\n this.showResult(false);\n return;\n }\n\n // A submit that simply removes the modal reads as a failure — nothing\n // acknowledges that the message was sent. Show a success screen unless the\n // host opted out.\n //\n // With no feedbackId the initial POST never landed and there was nothing\n // to send the message to; that path closes exactly as it did before.\n if (delivered && !this.host.config.hideConfirmation) {\n this.showResult(true);\n return;\n }\n this.close();\n }\n\n /**\n * Replace the modal's contents with the outcome: a checkmark and a short\n * acknowledgement, or an error mark and a \"not sent\" notice.\n *\n * The submit button that had focus is gone by this point, so focus moves to\n * the heading (WCAG 2.4.3) and the message is announced. Mirrors the quests\n * embed's thank-you screen so the two products confirm the same way. Copy\n * goes in as text, never markup.\n */\n private showResult(delivered: boolean): void {\n const box = this.modalContainer?.querySelector(\".qaid-modal-box\")\n ?? this.modalContainer?.querySelector(\".qaid-bottom-sheet-content\")\n ?? this.modalContainer;\n\n if (!box) {\n // Nothing to draw into — never strand the user with an open modal.\n this.close();\n return;\n }\n\n const text = this.host.config.text;\n const heading = delivered ? text.confirmationTitle : text.errorTitle;\n const body = delivered ? text.confirmationMessage : text.errorMessage;\n\n const title = h(\"h3\", {\n class: \"qaid-confirm-title\",\n text: heading,\n attrs: { id: `qaid-confirm-title-${this.host.uid}`, tabindex: \"-1\" },\n });\n\n box.textContent = \"\";\n box.appendChild(\n h(\n \"div\",\n { class: delivered ? \"qaid-confirm\" : \"qaid-confirm qaid-confirm-error\" },\n h(\"span\", {\n class: \"qaid-confirm-icon\",\n html: delivered ? DONE_ICON : ERROR_ICON,\n attrs: { \"aria-hidden\": \"true\" },\n }),\n title,\n h(\"p\", { class: \"qaid-confirm-message\", text: body }),\n h(\"button\", {\n class: \"qaid-btn-submit qaid-confirm-close\",\n text: text.confirmationClose,\n attrs: { type: \"button\" },\n on: { click: () => this.close() },\n })\n )\n );\n\n // Focus after paint, matching the quests embed.\n requestAnimationFrame(() => title.focus());\n this.host.announceMsg(`${heading} ${body}`, true);\n }\n}\n"],"names":["ModalController","host","root","err","sheet","h","modal","arrow","calculateModalAndArrowPosition","arrowEl","box","config","uid","text","type","isUp","positiveIcon","THUMBS_UP_ICON","negativeIcon","THUMBS_DOWN_ICON","toggleClass","toggleLabel","header","FEEDBACK_ICON","fragment","textarea","submitBtn","message","typeToggle","newType","delivered","response","error","heading","body","title","DONE_ICON","ERROR_ICON"],"mappings":"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"}
|