mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-25 09:23:28 +01:00
130 lines
2.8 KiB
TypeScript
130 lines
2.8 KiB
TypeScript
|
import type {ReactiveController, ReactiveControllerHost} from 'lit';
|
||
|
|
||
|
/**
|
||
|
* A reactive controller that determines when slots exist.
|
||
|
*
|
||
|
* Copied from Shoelace
|
||
|
* /src/internal/slot.ts
|
||
|
*/
|
||
|
export class HasSlotController implements ReactiveController
|
||
|
{
|
||
|
host : ReactiveControllerHost & Element;
|
||
|
slotNames : string[] = [];
|
||
|
|
||
|
constructor(host : ReactiveControllerHost & Element, ...slotNames : string[])
|
||
|
{
|
||
|
(this.host = host).addController(this);
|
||
|
this.slotNames = slotNames;
|
||
|
}
|
||
|
|
||
|
private hasDefaultSlot()
|
||
|
{
|
||
|
return [...this.host.childNodes].some(node =>
|
||
|
{
|
||
|
if(node.nodeType === node.TEXT_NODE && node.textContent!.trim() !== '')
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if(node.nodeType === node.ELEMENT_NODE)
|
||
|
{
|
||
|
const el = node as HTMLElement;
|
||
|
const tagName = el.tagName.toLowerCase();
|
||
|
|
||
|
// Ignore visually hidden elements since they aren't rendered
|
||
|
if(tagName === 'sl-visually-hidden')
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// If it doesn't have a slot attribute, it's part of the default slot
|
||
|
if(!el.hasAttribute('slot'))
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
private hasNamedSlot(name : string)
|
||
|
{
|
||
|
return this.host.querySelector(`:scope > [slot="${name}"]`) !== null;
|
||
|
}
|
||
|
|
||
|
test(slotName : string)
|
||
|
{
|
||
|
return slotName === '[default]' ? this.hasDefaultSlot() : this.hasNamedSlot(slotName);
|
||
|
}
|
||
|
|
||
|
hostConnected()
|
||
|
{
|
||
|
this.host.shadowRoot!.addEventListener('slotchange', this.handleSlotChange);
|
||
|
}
|
||
|
|
||
|
hostDisconnected()
|
||
|
{
|
||
|
this.host.shadowRoot!.removeEventListener('slotchange', this.handleSlotChange);
|
||
|
}
|
||
|
|
||
|
private handleSlotChange = (event : Event) =>
|
||
|
{
|
||
|
const slot = event.target as HTMLSlotElement;
|
||
|
|
||
|
if((this.slotNames.includes('[default]') && !slot.name) || (slot.name && this.slotNames.includes(slot.name)))
|
||
|
{
|
||
|
this.host.requestUpdate();
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Given a slot, this function iterates over all of its assigned element and text nodes and returns the concatenated
|
||
|
* HTML as a string. This is useful because we can't use slot.innerHTML as an alternative.
|
||
|
*/
|
||
|
export function getInnerHTML(slot : HTMLSlotElement) : string
|
||
|
{
|
||
|
const nodes = slot.assignedNodes({flatten: true});
|
||
|
let html = '';
|
||
|
|
||
|
[...nodes].forEach(node =>
|
||
|
{
|
||
|
if(node.nodeType === Node.ELEMENT_NODE)
|
||
|
{
|
||
|
html += (node as HTMLElement).outerHTML;
|
||
|
}
|
||
|
|
||
|
if(node.nodeType === Node.TEXT_NODE)
|
||
|
{
|
||
|
html += node.textContent;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return html;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Given a slot, this function iterates over all of its assigned text nodes and returns the concatenated text as a
|
||
|
* string. This is useful because we can't use slot.textContent as an alternative.
|
||
|
*/
|
||
|
export function getTextContent(slot : HTMLSlotElement | undefined | null) : string
|
||
|
{
|
||
|
if(!slot)
|
||
|
{
|
||
|
return '';
|
||
|
}
|
||
|
const nodes = slot.assignedNodes({flatten: true});
|
||
|
let text = '';
|
||
|
|
||
|
[...nodes].forEach(node =>
|
||
|
{
|
||
|
if(node.nodeType === Node.TEXT_NODE)
|
||
|
{
|
||
|
text += node.textContent;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return text;
|
||
|
}
|