fix(callout): Grid-based callout collapsible animation (#1944)
* Fixed broken nested callout maxHeight calculation
* Implemented grid-based callout collapsible
| | |
| | | function toggleCallout(this: HTMLElement) { |
| | | const outerBlock = this.parentElement! |
| | | outerBlock.classList.toggle("is-collapsed") |
| | | const content = outerBlock.getElementsByClassName("callout-content")[0] as HTMLElement |
| | | if (!content) return |
| | | const collapsed = outerBlock.classList.contains("is-collapsed") |
| | | const height = collapsed ? this.scrollHeight : outerBlock.scrollHeight |
| | | outerBlock.style.maxHeight = height + "px" |
| | | |
| | | // walk and adjust height of all parents |
| | | let current = outerBlock |
| | | let parent = outerBlock.parentElement |
| | | while (parent) { |
| | | if (!parent.classList.contains("callout")) { |
| | | return |
| | | } |
| | | |
| | | const collapsed = parent.classList.contains("is-collapsed") |
| | | const height = collapsed ? parent.scrollHeight : parent.scrollHeight + current.scrollHeight |
| | | parent.style.maxHeight = height + "px" |
| | | |
| | | current = parent |
| | | parent = parent.parentElement |
| | | } |
| | | content.style.gridTemplateRows = collapsed ? "0fr" : "1fr" |
| | | } |
| | | |
| | | function setupCallout() { |
| | |
| | | `callout is-collapsible`, |
| | | ) as HTMLCollectionOf<HTMLElement> |
| | | for (const div of collapsible) { |
| | | const title = div.firstElementChild |
| | | if (!title) continue |
| | | const title = div.getElementsByClassName("callout-title")[0] as HTMLElement |
| | | const content = div.getElementsByClassName("callout-content")[0] as HTMLElement |
| | | if (!title || !content) continue |
| | | |
| | | title.addEventListener("click", toggleCallout) |
| | | window.addCleanup(() => title.removeEventListener("click", toggleCallout)) |
| | | |
| | | const collapsed = div.classList.contains("is-collapsed") |
| | | const height = collapsed ? title.scrollHeight : div.scrollHeight |
| | | div.style.maxHeight = height + "px" |
| | | content.style.gridTemplateRows = collapsed ? "0fr" : "1fr" |
| | | } |
| | | } |
| | | |
| | |
| | | }) |
| | | } |
| | | |
| | | // For the rest of the MD callout elements other than the title, wrap them with |
| | | // two nested HTML <div>s (use some hacked mdhast component to achieve this) of |
| | | // class `callout-content` and `callout-content-inner` respectively for |
| | | // grid-based collapsible animation. |
| | | if (calloutContent.length > 0) { |
| | | node.children = [ |
| | | node.children[0], |
| | | { |
| | | data: { hProperties: { className: ["callout-content"] }, hName: "div" }, |
| | | type: "blockquote", |
| | | children: [ |
| | | { |
| | | data: { |
| | | hProperties: { className: ["callout-content-inner"] }, |
| | | hName: "div", |
| | | }, |
| | | type: "blockquote", |
| | | children: [...calloutContent], |
| | | }, |
| | | ], |
| | | }, |
| | | ] |
| | | } |
| | | |
| | | // replace first line of blockquote with title and rest of the paragraph text |
| | | node.children.splice(0, 1, ...blockquoteContent) |
| | | |
| | |
| | | "data-callout-metadata": calloutMetaData, |
| | | }, |
| | | } |
| | | |
| | | // Add callout-content class to callout body if it has one. |
| | | if (calloutContent.length > 0) { |
| | | const contentData: BlockContent | DefinitionContent = { |
| | | data: { |
| | | hProperties: { |
| | | className: "callout-content", |
| | | }, |
| | | hName: "div", |
| | | }, |
| | | type: "blockquote", |
| | | children: [...calloutContent], |
| | | } |
| | | node.children = [node.children[0], contentData] |
| | | } |
| | | } |
| | | }) |
| | | } |
| | |
| | | border-radius: 5px; |
| | | padding: 0 1rem; |
| | | overflow-y: hidden; |
| | | transition: max-height 0.3s ease; |
| | | box-sizing: border-box; |
| | | |
| | | & > .callout-content > :first-child { |
| | | & > .callout-content { |
| | | display: grid; |
| | | transition: grid-template-rows 0.3s ease; |
| | | |
| | | & > .callout-content-inner { |
| | | overflow: hidden; |
| | | |
| | | & > :first-child { |
| | | margin-top: 0; |
| | | } |
| | | } |
| | | } |
| | | |
| | | --callout-icon-note: url('data:image/svg+xml; utf8, <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="2" x2="22" y2="6"></line><path d="M7.5 20.5 19 9l-4-4L3.5 16.5 2 22z"></path></svg>'); |
| | | --callout-icon-abstract: url('data:image/svg+xml; utf8, <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><path d="M12 11h4"></path><path d="M12 16h4"></path><path d="M8 11h.01"></path><path d="M8 16h.01"></path></svg>'); |