kopia lustrzana https://github.com/shoelace-style/shoelace
add sl-mutation-observer
rodzic
8fa9d629a3
commit
eec24d2ed1
|
@ -59,6 +59,7 @@
|
|||
- [Format Date](/components/format-date)
|
||||
- [Format Number](/components/format-number)
|
||||
- [Include](/components/include)
|
||||
- [Mutation Observer](/components/mutation-observer)
|
||||
- [Relative Time](/components/relative-time)
|
||||
- [Resize Observer](/components/resize-observer)
|
||||
- [Responsive Media](/components/responsive-media)
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
# Mutation Observer
|
||||
|
||||
[component-header:sl-mutation-observer]
|
||||
|
||||
The Mutation Observer component offers a thin, declarative interface to the [`MutationObserver API`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver).
|
||||
|
||||
The mutation observer will report changes to the content it wraps through the `sl-mutation` event. When emitted, a collection of [MutationRecord](https://developer.mozilla.org/en-US/docs/Web/API/MutationRecord) objects will be attached to `event.detail` that contains information about how it changed.
|
||||
|
||||
```html preview
|
||||
<div class="mutation-overview">
|
||||
<sl-mutation-observer attr>
|
||||
<sl-button type="primary">Click to mutate</sl-button>
|
||||
</sl-mutation-observer>
|
||||
|
||||
<br>
|
||||
👆 Click the button and watch the console
|
||||
|
||||
<script>
|
||||
const container = document.querySelector('.mutation-overview');
|
||||
const mutationObserver = container.querySelector('sl-mutation-observer');
|
||||
const button = container.querySelector('sl-button');
|
||||
const types = ['primary', 'success', 'neutral', 'warning', 'danger'];
|
||||
let clicks = 0;
|
||||
|
||||
// Change the button's type attribute
|
||||
button.addEventListener('click', () => {
|
||||
clicks++;
|
||||
button.setAttribute('type', types[clicks % types.length]);
|
||||
});
|
||||
|
||||
// Log mutations
|
||||
mutationObserver.addEventListener('sl-mutation', event => {
|
||||
console.log(event.detail);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.mutation-overview sl-button {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
```
|
||||
|
||||
?> When you create a mutation observer, you must indicate what changes it should respond to by including at least one of `attr`, `child-list`, or `char-data`. If you don't specify at least one of these attributes, no mutation events will be emitted.
|
||||
|
||||
## Examples
|
||||
|
||||
### Child List
|
||||
|
||||
Use the `child-list` attribute to watch for new child elements that are added or removed.
|
||||
|
||||
```html preview
|
||||
<div class="mutation-child-list">
|
||||
<sl-mutation-observer child-list>
|
||||
<div class="buttons">
|
||||
<sl-button type="primary">Add button</sl-button>
|
||||
</div>
|
||||
</sl-mutation-observer>
|
||||
|
||||
👆 Add and remove buttons and watch the console
|
||||
|
||||
<script>
|
||||
const container = document.querySelector('.mutation-child-list');
|
||||
const mutationObserver = container.querySelector('sl-mutation-observer');
|
||||
const buttons = container.querySelector('.buttons');
|
||||
const button = container.querySelector('sl-button[type="primary"]');
|
||||
let i = 0;
|
||||
|
||||
// Add a button
|
||||
button.addEventListener('click', () => {
|
||||
const button = document.createElement('sl-button');
|
||||
button.textContent = ++i;
|
||||
buttons.append(button);
|
||||
});
|
||||
|
||||
// Remove a button
|
||||
buttons.addEventListener('click', event => {
|
||||
const target = event.target.closest('sl-button:not([type="primary"])');
|
||||
event.stopPropagation();
|
||||
|
||||
if (target) {
|
||||
target.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Log mutations
|
||||
mutationObserver.addEventListener('sl-mutation', event => {
|
||||
console.log(event.detail);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.mutation-child-list .buttons {
|
||||
display: flex;
|
||||
gap: .25rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
```
|
||||
|
||||
[component-metadata:sl-mutation-observer]
|
|
@ -11,6 +11,7 @@ _During the beta period, these restrictions may be relaxed in the event of a mis
|
|||
- 🚨 BREAKING: removed `<sl-menu-divider>` (use `<sl-divider>` instead)
|
||||
- 🚨 BREAKING: removed `percentage` attribute from `<sl-progress-bar>` and `<sl-progress-ring>` (use `value`) instead
|
||||
- 🚨 BREAKING: switched the default `type` of `<sl-tag>` from `primary` to `neutral`
|
||||
- Added the experimental `<sl-mutation-observer>` component
|
||||
- Added the `<sl-divider>` component
|
||||
- Added `--sl-surface-base` and `--sl-surface-base-alt` as early surface tokens to improve the appearance of alert, card, and panels in dark mode
|
||||
- Added the `--sl-panel-border-width` design token
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
:host {
|
||||
display: contents;
|
||||
}
|
||||
`;
|
|
@ -0,0 +1,13 @@
|
|||
import { expect, fixture, html, waitUntil } from '@open-wc/testing';
|
||||
// import sinon from 'sinon';
|
||||
|
||||
import '../../../dist/shoelace.js';
|
||||
import type SlMutationObserver from './mutation-observer';
|
||||
|
||||
describe('<sl-mutation-observer>', () => {
|
||||
it('should render a component', async () => {
|
||||
const el = await fixture(html` <sl-mutation-observer></sl-mutation-observer> `);
|
||||
|
||||
expect(el).to.exist;
|
||||
});
|
||||
});
|
|
@ -0,0 +1,112 @@
|
|||
import { LitElement, html } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { emit } from '../../internal/event';
|
||||
import { watch } from '../../internal/watch';
|
||||
import styles from './mutation-observer.styles';
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
* @status experimental
|
||||
*
|
||||
* @event sl-mutation - Emitted when a mutation occurs.
|
||||
*
|
||||
* @slot - The content to watch for mutations.
|
||||
*/
|
||||
@customElement('sl-mutation-observer')
|
||||
export default class SlMutationObserver extends LitElement {
|
||||
static styles = styles;
|
||||
|
||||
private mutationObserver: MutationObserver;
|
||||
|
||||
/**
|
||||
* Watches for changes to attributes. If empty, all changes will be reported. To watch only specific attributes,
|
||||
* separate them by a space.
|
||||
*/
|
||||
@property({ reflect: true }) attr: string;
|
||||
|
||||
/** Indicates whether or not the attribute's previous value should be recorded when monitoring changes. */
|
||||
@property({ attribute: 'attr-old-value', type: Boolean, reflect: true }) attrOldValue = false;
|
||||
|
||||
/** Watches for changes to the character data contained within the node. */
|
||||
@property({ attribute: 'char-data', type: Boolean, reflect: true }) charData = false;
|
||||
|
||||
/** Indicates whether or not the previous value of the node's text should be recorded. */
|
||||
@property({ attribute: 'char-data-old-value', type: Boolean, reflect: true }) charDataOldValue = false;
|
||||
|
||||
/** Watches for the addition or removal of new child nodes. */
|
||||
@property({ attribute: 'child-list', type: Boolean, reflect: true }) childList = false;
|
||||
|
||||
/** Disables the observer. */
|
||||
@property({ type: Boolean, reflect: true }) disabled = false;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.handleMutation = this.handleMutation.bind(this);
|
||||
|
||||
this.mutationObserver = new MutationObserver(this.handleMutation);
|
||||
this.startObserver();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.stopObserver();
|
||||
}
|
||||
|
||||
@watch('disabled')
|
||||
handleDisabledChange() {
|
||||
if (this.disabled) {
|
||||
this.stopObserver();
|
||||
} else {
|
||||
this.startObserver();
|
||||
}
|
||||
}
|
||||
|
||||
@watch('attr', { waitUntilFirstUpdate: true })
|
||||
@watch('attr-old-value', { waitUntilFirstUpdate: true })
|
||||
@watch('char-data', { waitUntilFirstUpdate: true })
|
||||
@watch('char-data-old-value', { waitUntilFirstUpdate: true })
|
||||
@watch('childList', { waitUntilFirstUpdate: true })
|
||||
handleChange() {
|
||||
this.stopObserver();
|
||||
this.startObserver();
|
||||
}
|
||||
|
||||
handleMutation(mutationList: MutationRecord[]) {
|
||||
emit(this, 'sl-mutation', {
|
||||
detail: { mutationList }
|
||||
});
|
||||
}
|
||||
|
||||
startObserver() {
|
||||
try {
|
||||
this.mutationObserver.observe(this, {
|
||||
subtree: true,
|
||||
childList: this.childList,
|
||||
attributes: typeof this.attr === 'string',
|
||||
attributeFilter: typeof this.attr === 'string' && this.attr.length > 0 ? this.attr.split(' ') : undefined,
|
||||
attributeOldValue: this.attrOldValue,
|
||||
characterData: this.charData,
|
||||
characterDataOldValue: this.charDataOldValue
|
||||
});
|
||||
} catch {
|
||||
//
|
||||
// A mutation observer was created without one of the required attributes: attr, char-data, or child-list. The
|
||||
// browser will normally throw an error, but we'll suppress that so it doesn't appear as attributes are added
|
||||
// and removed.
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
stopObserver() {
|
||||
this.mutationObserver.disconnect();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html` <slot></slot> `;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'sl-mutation-observer': SlMutationObserver;
|
||||
}
|
||||
}
|
|
@ -27,6 +27,7 @@ export { default as SlInput } from './components/input/input';
|
|||
export { default as SlMenu } from './components/menu/menu';
|
||||
export { default as SlMenuItem } from './components/menu-item/menu-item';
|
||||
export { default as SlMenuLabel } from './components/menu-label/menu-label';
|
||||
export { default as SlMutationObserver } from './components/mutation-observer/mutation-observer';
|
||||
export { default as SlProgressBar } from './components/progress-bar/progress-bar';
|
||||
export { default as SlProgressRing } from './components/progress-ring/progress-ring';
|
||||
export { default as SlQrCode } from './components/qr-code/qr-code';
|
||||
|
|
Ładowanie…
Reference in New Issue