Skip to content

Commit

Permalink
fix(ld-tooltip): sync tooltip content on slot change
Browse files Browse the repository at this point in the history
  • Loading branch information
borisdiakur committed Jun 1, 2023
1 parent e9d770b commit 116ab85
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 10 deletions.
33 changes: 29 additions & 4 deletions src/liquid/components/ld-tooltip/ld-tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export class LdTooltip {
private popper?: Tether
private tooltipRef!: HTMLElement
private triggerRef!: HTMLSpanElement
private isObserverEnabled = true

/** Show arrow */
@Prop() arrow?: boolean
Expand Down Expand Up @@ -158,15 +159,34 @@ export class LdTooltip {
}

private syncContent = () => {
this.isObserverEnabled = false

// Grab the new content in the slot.
const tooltipContent = this.contentRef.querySelector('slot').assignedNodes()

// Delete old content in popper element. Using textContent
// is faster than innerHTML as no HTML parsers needs to be
// invoked. Instead, this immediately replaces all children
// of the tooltip ref with a single #text node.
this.tooltipRef.textContent = ''

// Move original nodes to popper element,
// including all event listeners!
tooltipContent.forEach((node) => {
copySlottedNodes(node)
// We put original node will be put in the tooltipRef, because we cannot
// clone event listeners. The original event listeners must be
// present on the node that is located in the tooltipRef element.
this.tooltipRef.appendChild(node)
})

// The timeout is required. Without the setTimeout,
// there is a possibility that the observer could be
// re-enabled immediately after the content synchronization
// code has been executed, but before the DOM modifications
// have been fully processed. This could result in the
// observer detecting incomplete or inconsistent changes,
// leading to unexpected behavior.
setTimeout(() => {
this.isObserverEnabled = true
})
}

private initTooltip = async () => {
Expand Down Expand Up @@ -324,6 +344,9 @@ export class LdTooltip {
}

private handleSlotChange = () => {
if (!this.isObserverEnabled) return

// Remove all content from the popper element except for the tether-element-marker.
this.tooltipRef.childNodes.forEach((node) => {
if (
isElement(node) &&
Expand All @@ -334,6 +357,8 @@ export class LdTooltip {

node.remove()
})

// Put all content from the slot in the popper element.
this.syncContent()
}

Expand All @@ -342,7 +367,7 @@ export class LdTooltip {
this.observer.observe(this.el, {
subtree: true,
childList: true,
attributes: true,
attributes: false,
})
}

Expand Down
17 changes: 12 additions & 5 deletions src/liquid/components/ld-tooltip/test/ld-tooltip.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -612,15 +612,22 @@ describe('ld-tooltip', () => {
const trigger = component.shadowRoot.querySelector('.ld-tooltip__trigger')

component.querySelector('p').textContent = 'Changed content'
const defaultSlot = component.shadowRoot.querySelector<HTMLSlotElement>(
'.ld-tooltip__content slot'
)

const assignedNodes = component.querySelectorAll('> *') as unknown as Node[]

// TODO: remove as soon as https://github.com/ionic-team/stencil/issues/2830 is resolved
defaultSlot.assignedNodes = () =>
component.querySelectorAll('> *') as unknown as Node[]
const mockAssignedNodesOnDefaultSlot = () => {
component.shadowRoot.querySelector<HTMLSlotElement>(
'.ld-tooltip__content slot'
).assignedNodes = () => assignedNodes
}
mockAssignedNodesOnDefaultSlot()

trigger.dispatchEvent(new MouseEvent('mouseenter'))
jest.advanceTimersByTime(0)

mockAssignedNodesOnDefaultSlot()

await page.waitForChanges()
getTriggerableMutationObserver().trigger([])
await page.waitForChanges()
Expand Down
2 changes: 1 addition & 1 deletion src/liquid/components/ld-typo/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@ Here are some examples on how you can apply different colors on headings:
| `key` | `key` | for tracking the node's identity when working with lists | `string \| number` | `undefined` |
| `ref` | `ref` | reference to component | `any` | `undefined` |
| `tag` | `tag` | The rendered HTML tag. Overrides tag inferred from the variant. | `string` | `undefined` |
| `variant` | `variant` | The font style. Every variant has a default tag that it renders with. | `"h1" \| "h2" \| "h3" \| "h4" \| "h5" \| "h6" \| "body-s" \| "body-xs" \| "body-m" \| "body-l" \| "body-xl" \| "cap-m" \| "cap-l" \| "label-s" \| "label-m" \| "b1" \| "b2" \| "b3" \| "b4" \| "b5" \| "b6" \| "xb1" \| "xb2" \| "xb3" \| "xh1" \| "xh2" \| "xh3" \| "xh4" \| "xh5" \| "xh6"` | `'body-m'` |
| `variant` | `variant` | The font style. Every variant has a default tag that it renders with. | `"h1" \| "h2" \| "h3" \| "h4" \| "h5" \| "h6" \| "body-xs" \| "body-s" \| "body-m" \| "body-l" \| "body-xl" \| "cap-m" \| "cap-l" \| "label-s" \| "label-m" \| "b1" \| "b2" \| "b3" \| "b4" \| "b5" \| "b6" \| "xb1" \| "xb2" \| "xb3" \| "xh1" \| "xh2" \| "xh3" \| "xh4" \| "xh5" \| "xh6"` | `'body-m'` |


## Shadow Parts
Expand Down

0 comments on commit 116ab85

Please sign in to comment.